Files
KArtSell.Aegis/frontend/src/shared/ui/adapter/contracts.ts
T
kjh2064 b12fc7411d feat(fe): Add form field navigation - Enter key moves to next field
- Create useFormFieldNavigation composable for Tab-like Enter behavior
- KsTextField: Enter -> next field
- KsTextArea: Ctrl+Enter for newline, Enter -> next field
- KsSelect: Enter -> next field after selection
- Implements standard form navigation pattern across input components
2026-08-16 21:50:50 +09:00

120 lines
3.5 KiB
TypeScript

import type { Component, InjectionKey } from 'vue'
import type { UiGridStatusMap } from '../gridStatus'
export type UiSeverity = 'primary' | 'secondary' | 'success' | 'info' | 'warning' | 'danger'
export type UiButtonType = 'button' | 'submit' | 'reset'
export type UiAdapterId = 'primevue-aggrid' | 'native-accessible'
export type UiAdapterContractVersion = '4.0'
export type UiAdapterCapability =
| 'button'
| 'text-field'
| 'text-area'
| 'select'
| 'multi-select'
| 'checkbox'
| 'date-field'
| 'number-field'
| 'dialog'
| 'status-tag'
| 'inline-message'
| 'paginator'
| 'tabs'
| 'data-grid'
export interface UiSelectOption {
label: string
value: string | number | boolean | null
disabled?: boolean
}
export interface UiTabItem {
id: string
label: string
disabled?: boolean
badge?: string | number
}
export type UiGridSortDirection = 'asc' | 'desc'
export interface UiGridSort { field: string; direction: UiGridSortDirection }
export interface UiGridFilter { field: string; operator: string; value: unknown }
export interface UiGridQuery {
page: number
pageSize: number
sorts: UiGridSort[]
filters: UiGridFilter[]
search?: string
}
export interface UiGridColumn {
field: string
header: string
width?: number
minWidth?: number
maxWidth?: number
flex?: number
sortable?: boolean
filterable?: boolean
sensitive?: boolean
editable?: boolean
formatter?: (value: unknown, row: unknown) => string
statusMap?: UiGridStatusMap
}
export type UiTextFieldType = 'text' | 'email' | 'password' | 'number' | 'date'
export interface UiAdapterDescriptor {
readonly id: UiAdapterId
readonly version: string
readonly contractVersion: UiAdapterContractVersion
readonly vendor: string
readonly capabilities: ReadonlySet<UiAdapterCapability>
readonly productionEligible: boolean
readonly accessibilityBaseline: 'WCAG_2_2_AA_TARGET'
}
export interface UiAdapter {
readonly descriptor: UiAdapterDescriptor
readonly components: {
Button: Component
TextField: Component
TextArea: Component
Select: Component
MultiSelect: Component
Checkbox: Component
DateField: Component
NumberField: Component
Dialog: Component
StatusTag: Component
InlineMessage: Component
Paginator: Component
Tabs: Component
DataGrid: Component
}
}
export const requiredUiAdapterCapabilities: readonly UiAdapterCapability[] = Object.freeze([
'button', 'text-field', 'text-area', 'select', 'multi-select', 'checkbox', 'date-field',
'number-field', 'dialog', 'status-tag', 'inline-message', 'paginator', 'tabs', 'data-grid'
])
export function assertUiAdapterContract(adapter: UiAdapter): void {
if (adapter.descriptor.contractVersion !== '4.0') {
throw new Error(`Unsupported UI adapter contract: ${adapter.descriptor.contractVersion}`)
}
const missing = requiredUiAdapterCapabilities.filter(x => !adapter.descriptor.capabilities.has(x))
if (missing.length > 0) {
throw new Error(`UI adapter ${adapter.descriptor.id} is missing capabilities: ${missing.join(', ')}`)
}
const componentNames = [
'Button', 'TextField', 'TextArea', 'Select', 'MultiSelect', 'Checkbox', 'DateField',
'NumberField', 'Dialog', 'StatusTag', 'InlineMessage', 'Paginator', 'Tabs', 'DataGrid'
] as const
for (const name of componentNames) {
if (!adapter.components[name]) throw new Error(`UI adapter ${adapter.descriptor.id} has no component for ${name}`)
}
}
export const uiAdapterKey: InjectionKey<UiAdapter> = Symbol('KArtSellUiAdapterV4')