@featherk/ui 0.17.1 → 0.18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +73 -12
  2. package/dist/address.cjs +2 -0
  3. package/dist/address.cjs.map +1 -0
  4. package/dist/address.mjs +237 -0
  5. package/dist/address.mjs.map +1 -0
  6. package/dist/chunks/picker.css +1 -0
  7. package/dist/chunks/shared-BHFhmbuH.cjs +2 -0
  8. package/dist/chunks/shared-BHFhmbuH.cjs.map +1 -0
  9. package/dist/chunks/shared-CHgC5LLL.mjs +10 -0
  10. package/dist/chunks/shared-CHgC5LLL.mjs.map +1 -0
  11. package/dist/{docs-manifest.d.ts → docs-manifest.d.mts} +3 -0
  12. package/dist/docs-manifest.mjs +25 -0
  13. package/dist/index.cjs +2 -0
  14. package/dist/index.cjs.map +1 -0
  15. package/dist/index.d.ts +1 -0
  16. package/dist/index.mjs +12 -0
  17. package/dist/index.mjs.map +1 -0
  18. package/dist/picker/DateField.vue.d.ts +28 -0
  19. package/dist/picker/DateRangeField.vue.d.ts +34 -0
  20. package/dist/picker/TimeField.vue.d.ts +28 -0
  21. package/dist/picker/errorKey.d.ts +1 -0
  22. package/dist/picker/index.d.ts +4 -0
  23. package/dist/picker/types.d.ts +18 -0
  24. package/dist/picker.cjs +2 -0
  25. package/dist/picker.cjs.map +1 -0
  26. package/dist/picker.mjs +366 -0
  27. package/dist/picker.mjs.map +1 -0
  28. package/dist/table/types.d.ts +1 -1
  29. package/dist/table/useActionCellMenu.d.ts +2 -1
  30. package/dist/table.cjs +2 -0
  31. package/dist/table.cjs.map +1 -0
  32. package/dist/table.mjs +157 -0
  33. package/dist/table.mjs.map +1 -0
  34. package/docs/README.md +4 -4
  35. package/docs/address/USAddress.md +1 -2
  36. package/docs/picker/DateField.md +89 -0
  37. package/docs/picker/DateRangeField.md +99 -0
  38. package/docs/picker/TimeField.md +88 -0
  39. package/package.json +36 -22
  40. package/dist/docs-manifest.js +0 -19
  41. package/dist/featherk-ui.es.js +0 -392
  42. package/dist/featherk-ui.es.js.map +0 -1
  43. package/dist/featherk-ui.umd.js +0 -2
  44. package/dist/featherk-ui.umd.js.map +0 -1
  45. /package/dist/{ui.css → chunks/address.css} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"table.cjs","sources":["../src/table/ActionCell.vue","../src/table/useActionCellMenu.ts"],"sourcesContent":["<template>\n <td\n :id=\"id ?? ids.instanceId\"\n :class=\"['fkui-action-cell', 'k-table-td', className]\"\n :style=\"style\"\n :colspan=\"colSpan\"\n role=\"gridcell\"\n :aria-colindex=\"ariaColumnIndex\"\n :aria-selected=\"isSelected\"\n :data-grid-col-index=\"columnIndex\"\n >\n <Button\n v-for=\"trigger in config.triggers\"\n :key=\"trigger.key\"\n :ref=\"\n hasMenu(trigger)\n ? (element) => registerTrigger(trigger, element)\n : undefined\n \"\n :id=\"ids.compose(trigger.key, 'trigger')\"\n :svg-icon=\"trigger.icon\"\n type=\"button\"\n fill-mode=\"flat\"\n theme-color=\"primary\"\n rounded=\"full\"\n :aria-label=\"trigger.ariaLabel\"\n :aria-haspopup=\"hasMenu(trigger) ? 'menu' : undefined\"\n :aria-expanded=\"hasMenu(trigger) ? isTriggerActive(trigger) : undefined\"\n :disabled=\"trigger.disabled?.(dataItem)\"\n :tabindex=\"tabIndex\"\n @click=\"onTriggerClick(trigger)\"\n @keydown=\"onTriggerKeydown(trigger, $event)\"\n />\n </td>\n</template>\n\n<script setup lang=\"ts\">\nimport type { ComponentPublicInstance } from \"vue\";\nimport { Button } from \"@progress/kendo-vue-buttons\";\nimport type { GridCellProps } from \"@progress/kendo-vue-grid\";\nimport { useCompositeId } from \"@featherk/composables/id\";\nimport type { RegisterableElement } from \"@featherk/composables/registry\";\nimport type {\n ActionCellConfig,\n ActionCellMenuController,\n ActionCellTrigger,\n} from \"./types\";\n\ndefineOptions({ inheritAttrs: false });\n\ntype ActionCellProps = Partial<\n Pick<\n GridCellProps,\n | \"id\"\n | \"className\"\n | \"style\"\n | \"colSpan\"\n | \"ariaColumnIndex\"\n | \"isSelected\"\n | \"columnIndex\"\n >\n> & {\n dataItem: any;\n config: ActionCellConfig<any>;\n menuController: ActionCellMenuController<any>;\n tabIndex?: number;\n};\n\nconst props = withDefaults(defineProps<ActionCellProps>(), {\n tabIndex: -1,\n});\n\nconst ids = useCompositeId(\"action-cell\");\n\nconst getTriggerId = (trigger: ActionCellTrigger<any>) =>\n props.menuController.getTriggerId(trigger, props.dataItem);\n\nconst isTriggerActive = (trigger: ActionCellTrigger<any>) =>\n props.menuController.registry.isIdActive(getTriggerId(trigger));\n\nconst hasMenu = (trigger: ActionCellTrigger<any>) =>\n typeof trigger.items === \"function\";\n\nconst registerTrigger = (\n trigger: ActionCellTrigger<any>,\n element: Element | ComponentPublicInstance | null,\n) => {\n props.menuController.registry.register(\n getTriggerId(trigger),\n element as RegisterableElement,\n );\n};\n\nconst onTriggerClick = (trigger: ActionCellTrigger<any>) => {\n if (!hasMenu(trigger)) {\n props.menuController.handleDirectAction?.(trigger, props.dataItem);\n return;\n }\n props.menuController.handleTriggerClick(getTriggerId(trigger));\n};\n\nconst onTriggerKeydown = (\n trigger: ActionCellTrigger<any>,\n ...args: unknown[]\n) => {\n if (!hasMenu(trigger)) return;\n props.menuController.handleTriggerKeydown(getTriggerId(trigger), ...args);\n};\n</script>\n","import {\n computed,\n nextTick,\n toValue,\n type ComputedRef,\n type MaybeRefOrGetter,\n} from \"vue\";\nimport {\n useActiveIdRegistry,\n type UseExclusiveGroupReturn,\n} from \"@featherk/composables/registry\";\nimport {\n usePopupMenu,\n type KendoMenuSelectEvent,\n type PopupMenuAnchorOptions,\n type PopupMenuElementRef,\n type PopupMenuOffset,\n type PopupMenuTriggerMode,\n} from \"@featherk/composables/menu\";\nimport type {\n ActionCellConfig,\n ActionCellDataItem,\n ActionCellMenuController,\n ActionCellMenuItem,\n} from \"./types\";\n\nexport type UseActionCellMenuOptions<\n TData extends ActionCellDataItem = ActionCellDataItem,\n TConfig extends ActionCellConfig<TData> = ActionCellConfig<TData>,\n> = {\n /** Repeatable trigger configuration, rendered by every `ActionCell` instance. */\n config: TConfig;\n /** All rows currently rendered; used to resolve the active row from its registry id. */\n rows: MaybeRefOrGetter<TData[]>;\n /** Derives a stable row id used to build the default registry id for each trigger. */\n getRowId: (row: TData) => string | number;\n /**\n * Overrides the registry ID formatting while preserving `<trigger.key>:<rowId>` semantics.\n * Active row and trigger resolution parse the first colon-separated segment as the trigger\n * key and the remaining segments as the row ID.\n */\n getTriggerId?: (trigger: TConfig[\"triggers\"][number], row: TData) => string;\n /** Ref to the single shared Kendo Menu instance. */\n menuRef: PopupMenuElementRef;\n /**\n * Optional custom popup measurement source forwarded to `usePopupMenu`.\n * Normal Kendo Popup markup is measured from the nearest `.k-popup` border\n * box automatically, including `activeTitle` and other sibling content.\n */\n popupContentRef?: PopupMenuElementRef;\n /** Forwarded to `usePopupMenu`. Defaults to `\"button\"`. */\n triggerMode?: PopupMenuTriggerMode;\n /** Forwarded to `usePopupMenu`. Defaults to `true` for shared-popup positioning. */\n anchor?: PopupMenuAnchorOptions | true;\n /** Forwarded to `usePopupMenu`. */\n resolveFocusTarget?: () => HTMLElement | null;\n /** Forwarded to `usePopupMenu`. */\n focusTargetContainerSelector?: string;\n /** Coordinates mutual exclusivity with other active-id registries in this group. */\n group?: UseExclusiveGroupReturn;\n /**\n * Runs after a menu item is selected, with its original item and resolved row/trigger.\n * Direct-action triggers have no menu item and pass `undefined`.\n */\n onActionSelected: (\n item: ActionCellMenuItem | undefined,\n row: TData,\n trigger: TConfig[\"triggers\"][number],\n ) => void;\n};\n\nexport type UseActionCellMenuReturn<\n TData extends ActionCellDataItem = ActionCellDataItem,\n TConfig extends ActionCellConfig<TData> = ActionCellConfig<TData>,\n> = {\n /** Spread onto every `<ActionCell>` instance. */\n actionCellProps: {\n config: TConfig;\n menuController: ActionCellMenuController<TData>;\n };\n /** Spread onto the single shared `<Popup>`. */\n popupProps: ComputedRef<{\n show: boolean;\n offset: PopupMenuOffset;\n onClose: () => void;\n }>;\n /** Spread onto the single shared Kendo `<Menu>`. */\n menuProps: ComputedRef<{\n items: ActionCellMenuItem[];\n vertical: true;\n onKeydown: (event: KeyboardEvent) => void;\n onSelect: (event: KendoMenuSelectEvent<ActionCellMenuItem>) => void;\n }>;\n /** Row resolved from the currently active trigger id, if any. */\n activeRow: ComputedRef<TData | null>;\n /** Trigger config resolved from the currently active trigger id, if any. */\n activeTrigger: ComputedRef<TConfig[\"triggers\"][number] | null>;\n /** Resolved `trigger.title(activeRow)`, or `null` when unset/no trigger is active. Render this yourself (e.g. `<strong>`) above the `<Menu>` — it is not part of `menuProps.items`. */\n activeTitle: ComputedRef<string | null>;\n};\n\n/**\n * Bundles the shared-popup wiring a parent needs to drive `ActionCell`: one\n * `useActiveIdRegistry`, one `usePopupMenu`, and the derived active\n * row/trigger/menu items. The parent still owns the `<Popup>`/`<Menu>`\n * template elements and any business logic for the selected action.\n */\nexport const useActionCellMenu = <\n TData extends ActionCellDataItem = ActionCellDataItem,\n TConfig extends ActionCellConfig<TData> = ActionCellConfig<TData>,\n>(\n options: UseActionCellMenuOptions<TData, TConfig>,\n): UseActionCellMenuReturn<TData, TConfig> => {\n const registry = useActiveIdRegistry<string>();\n options.group?.register(registry);\n\n const getTriggerId =\n options.getTriggerId ??\n ((trigger: TConfig[\"triggers\"][number], row: TData) =>\n `${trigger.key}:${options.getRowId(row)}`);\n\n const activeTrigger = computed(() => {\n const key = registry.activeId.value?.split(\":\")[0];\n return (\n (options.config.triggers.find((trigger) => trigger.key === key) as\n | TConfig[\"triggers\"][number]\n | undefined) ?? null\n );\n });\n\n const activeRow = computed(() => {\n const rowId = registry.activeId.value?.split(\":\").slice(1).join(\":\");\n if (rowId === undefined) return null;\n return (\n toValue(options.rows).find(\n (row) => String(options.getRowId(row)) === rowId,\n ) ?? null\n );\n });\n\n const activeMenuItems = computed<ActionCellMenuItem[]>(() => {\n const row = activeRow.value;\n const trigger = activeTrigger.value;\n return row && trigger ? (trigger.items?.(row) ?? []) : [];\n });\n\n const activeTitle = computed<string | null>(() => {\n const row = activeRow.value;\n const trigger = activeTrigger.value;\n return row &&\n trigger &&\n typeof trigger.items === \"function\" &&\n trigger.title\n ? trigger.title(row)\n : null;\n });\n\n const popupMenu = usePopupMenu({\n isOpen: registry.isActive,\n triggerRef: registry.activeElement,\n menuRef: options.menuRef,\n popupContentRef: options.popupContentRef,\n triggerMode: options.triggerMode ?? \"button\",\n anchor: options.anchor ?? true,\n resolveFocusTarget: options.resolveFocusTarget,\n focusTargetContainerSelector: options.focusTargetContainerSelector,\n requestShow: (id) => {\n if (typeof id === \"string\") registry.activate(id);\n },\n requestHide: () => registry.deactivate(),\n menuLabel: activeTitle,\n });\n\n let triggerInteractionSequence = 0;\n\n const handleTriggerClick = (id: string) => {\n const interactionSequence = ++triggerInteractionSequence;\n options.group?.deactivateOthers(registry);\n\n if (registry.isActive.value && !registry.isIdActive(id)) {\n registry.deactivate();\n void nextTick(() => {\n if (interactionSequence === triggerInteractionSequence) {\n popupMenu.handleTriggerClick(id);\n }\n });\n return;\n }\n\n popupMenu.handleTriggerClick(id);\n };\n\n const handleDirectAction = (\n trigger: TConfig[\"triggers\"][number],\n row: TData,\n ) => {\n triggerInteractionSequence++;\n options.group?.deactivateOthers(registry);\n registry.deactivate();\n options.onActionSelected(undefined, row, trigger);\n };\n\n const handleTriggerKeydown = (id: string, ...args: unknown[]) => {\n const event = args[0] as KeyboardEvent;\n if (event.key === \"Enter\" || event.code === \"Space\") {\n event.preventDefault();\n handleTriggerClick(id);\n return;\n }\n popupMenu.handleTriggerKeydown(event, id);\n };\n\n const onMenuKeydown = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") popupMenu.handleMenuEscape(event);\n };\n\n const onMenuSelect = (event: KendoMenuSelectEvent<ActionCellMenuItem>) => {\n const row = activeRow.value;\n const trigger = activeTrigger.value;\n popupMenu.handleMenuSelect(event, () => {\n if (row && trigger) {\n options.onActionSelected(event.item, row, trigger);\n }\n });\n };\n\n return {\n actionCellProps: {\n config: options.config,\n menuController: {\n registry,\n getTriggerId,\n handleTriggerClick,\n handleDirectAction,\n handleTriggerKeydown,\n },\n },\n popupProps: computed(() => ({\n show: registry.isActive.value,\n offset: popupMenu.offset.value,\n onClose: popupMenu.handlePopupClose,\n })),\n menuProps: computed(() => ({\n items: activeMenuItems.value,\n vertical: true as const,\n onKeydown: onMenuKeydown,\n onSelect: onMenuSelect,\n })),\n activeRow,\n activeTrigger,\n activeTitle,\n };\n};\n"],"names":["props","__props","ids","useCompositeId","getTriggerId","trigger","isTriggerActive","hasMenu","registerTrigger","element","onTriggerClick","onTriggerKeydown","args","_createElementBlock","_unref","_openBlock","_Fragment","_renderList","_createBlock","Button","$event","useActionCellMenu","options","registry","useActiveIdRegistry","row","activeTrigger","computed","key","activeRow","rowId","toValue","activeMenuItems","activeTitle","popupMenu","usePopupMenu","id","triggerInteractionSequence","handleTriggerClick","interactionSequence","nextTick","handleDirectAction","handleTriggerKeydown","event","onMenuKeydown","onMenuSelect"],"mappings":"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"}
package/dist/table.mjs ADDED
@@ -0,0 +1,157 @@
1
+ import { defineComponent as T, openBlock as C, createElementBlock as I, normalizeStyle as k, normalizeClass as w, unref as p, Fragment as x, renderList as S, createBlock as A, computed as m, toValue as b, nextTick as M } from "vue";
2
+ import { Button as R } from "@progress/kendo-vue-buttons";
3
+ import { useCompositeId as K } from "@featherk/composables/id";
4
+ import { useActiveIdRegistry as B } from "@featherk/composables/registry";
5
+ import { usePopupMenu as E } from "@featherk/composables/menu";
6
+ const P = ["id", "colspan", "aria-colindex", "aria-selected", "data-grid-col-index"], $ = /* @__PURE__ */ T({
7
+ inheritAttrs: !1,
8
+ __name: "ActionCell",
9
+ props: {
10
+ id: {},
11
+ className: {},
12
+ style: {},
13
+ colSpan: {},
14
+ ariaColumnIndex: {},
15
+ isSelected: { type: Boolean },
16
+ columnIndex: {},
17
+ dataItem: {},
18
+ config: {},
19
+ menuController: {},
20
+ tabIndex: { default: -1 }
21
+ },
22
+ setup(t) {
23
+ const n = t, v = K("action-cell"), l = (r) => n.menuController.getTriggerId(r, n.dataItem), u = (r) => n.menuController.registry.isIdActive(l(r)), c = (r) => typeof r.items == "function", y = (r, s) => {
24
+ n.menuController.registry.register(
25
+ l(r),
26
+ s
27
+ );
28
+ }, a = (r) => {
29
+ if (!c(r)) {
30
+ n.menuController.handleDirectAction?.(r, n.dataItem);
31
+ return;
32
+ }
33
+ n.menuController.handleTriggerClick(l(r));
34
+ }, f = (r, ...s) => {
35
+ c(r) && n.menuController.handleTriggerKeydown(l(r), ...s);
36
+ };
37
+ return (r, s) => (C(), I("td", {
38
+ id: t.id ?? p(v).instanceId,
39
+ class: w(["fkui-action-cell", "k-table-td", t.className]),
40
+ style: k(t.style),
41
+ colspan: t.colSpan,
42
+ role: "gridcell",
43
+ "aria-colindex": t.ariaColumnIndex,
44
+ "aria-selected": t.isSelected,
45
+ "data-grid-col-index": t.columnIndex
46
+ }, [
47
+ (C(!0), I(x, null, S(t.config.triggers, (i) => (C(), A(p(R), {
48
+ key: i.key,
49
+ ref_for: !0,
50
+ ref: c(i) ? (g) => y(i, g) : void 0,
51
+ id: p(v).compose(i.key, "trigger"),
52
+ "svg-icon": i.icon,
53
+ type: "button",
54
+ "fill-mode": "flat",
55
+ "theme-color": "primary",
56
+ rounded: "full",
57
+ "aria-label": i.ariaLabel,
58
+ "aria-haspopup": c(i) ? "menu" : void 0,
59
+ "aria-expanded": c(i) ? u(i) : void 0,
60
+ disabled: i.disabled?.(t.dataItem),
61
+ tabindex: t.tabIndex,
62
+ onClick: (g) => a(i),
63
+ onKeydown: (g) => f(i, g)
64
+ }, null, 8, ["id", "svg-icon", "aria-label", "aria-haspopup", "aria-expanded", "disabled", "tabindex", "onClick", "onKeydown"]))), 128))
65
+ ], 14, P));
66
+ }
67
+ }), z = (t) => {
68
+ const n = B();
69
+ t.group?.register(n);
70
+ const v = t.getTriggerId ?? ((e, o) => `${e.key}:${t.getRowId(o)}`), l = m(() => {
71
+ const e = n.activeId.value?.split(":")[0];
72
+ return t.config.triggers.find((o) => o.key === e) ?? null;
73
+ }), u = m(() => {
74
+ const e = n.activeId.value?.split(":").slice(1).join(":");
75
+ return e === void 0 ? null : b(t.rows).find(
76
+ (o) => String(t.getRowId(o)) === e
77
+ ) ?? null;
78
+ }), c = m(() => {
79
+ const e = u.value, o = l.value;
80
+ return e && o ? o.items?.(e) ?? [] : [];
81
+ }), y = m(() => {
82
+ const e = u.value, o = l.value;
83
+ return e && o && typeof o.items == "function" && o.title ? o.title(e) : null;
84
+ }), a = E({
85
+ isOpen: n.isActive,
86
+ triggerRef: n.activeElement,
87
+ menuRef: t.menuRef,
88
+ popupContentRef: t.popupContentRef,
89
+ triggerMode: t.triggerMode ?? "button",
90
+ anchor: t.anchor ?? !0,
91
+ resolveFocusTarget: t.resolveFocusTarget,
92
+ focusTargetContainerSelector: t.focusTargetContainerSelector,
93
+ requestShow: (e) => {
94
+ typeof e == "string" && n.activate(e);
95
+ },
96
+ requestHide: () => n.deactivate(),
97
+ menuLabel: y
98
+ });
99
+ let f = 0;
100
+ const r = (e) => {
101
+ const o = ++f;
102
+ if (t.group?.deactivateOthers(n), n.isActive.value && !n.isIdActive(e)) {
103
+ n.deactivate(), M(() => {
104
+ o === f && a.handleTriggerClick(e);
105
+ });
106
+ return;
107
+ }
108
+ a.handleTriggerClick(e);
109
+ }, s = (e, o) => {
110
+ f++, t.group?.deactivateOthers(n), n.deactivate(), t.onActionSelected(void 0, o, e);
111
+ }, i = (e, ...o) => {
112
+ const d = o[0];
113
+ if (d.key === "Enter" || d.code === "Space") {
114
+ d.preventDefault(), r(e);
115
+ return;
116
+ }
117
+ a.handleTriggerKeydown(d, e);
118
+ }, g = (e) => {
119
+ e.key === "Escape" && a.handleMenuEscape(e);
120
+ }, h = (e) => {
121
+ const o = u.value, d = l.value;
122
+ a.handleMenuSelect(e, () => {
123
+ o && d && t.onActionSelected(e.item, o, d);
124
+ });
125
+ };
126
+ return {
127
+ actionCellProps: {
128
+ config: t.config,
129
+ menuController: {
130
+ registry: n,
131
+ getTriggerId: v,
132
+ handleTriggerClick: r,
133
+ handleDirectAction: s,
134
+ handleTriggerKeydown: i
135
+ }
136
+ },
137
+ popupProps: m(() => ({
138
+ show: n.isActive.value,
139
+ offset: a.offset.value,
140
+ onClose: a.handlePopupClose
141
+ })),
142
+ menuProps: m(() => ({
143
+ items: c.value,
144
+ vertical: !0,
145
+ onKeydown: g,
146
+ onSelect: h
147
+ })),
148
+ activeRow: u,
149
+ activeTrigger: l,
150
+ activeTitle: y
151
+ };
152
+ };
153
+ export {
154
+ $ as ActionCell,
155
+ z as useActionCellMenu
156
+ };
157
+ //# sourceMappingURL=table.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"table.mjs","sources":["../src/table/ActionCell.vue","../src/table/useActionCellMenu.ts"],"sourcesContent":["<template>\n <td\n :id=\"id ?? ids.instanceId\"\n :class=\"['fkui-action-cell', 'k-table-td', className]\"\n :style=\"style\"\n :colspan=\"colSpan\"\n role=\"gridcell\"\n :aria-colindex=\"ariaColumnIndex\"\n :aria-selected=\"isSelected\"\n :data-grid-col-index=\"columnIndex\"\n >\n <Button\n v-for=\"trigger in config.triggers\"\n :key=\"trigger.key\"\n :ref=\"\n hasMenu(trigger)\n ? (element) => registerTrigger(trigger, element)\n : undefined\n \"\n :id=\"ids.compose(trigger.key, 'trigger')\"\n :svg-icon=\"trigger.icon\"\n type=\"button\"\n fill-mode=\"flat\"\n theme-color=\"primary\"\n rounded=\"full\"\n :aria-label=\"trigger.ariaLabel\"\n :aria-haspopup=\"hasMenu(trigger) ? 'menu' : undefined\"\n :aria-expanded=\"hasMenu(trigger) ? isTriggerActive(trigger) : undefined\"\n :disabled=\"trigger.disabled?.(dataItem)\"\n :tabindex=\"tabIndex\"\n @click=\"onTriggerClick(trigger)\"\n @keydown=\"onTriggerKeydown(trigger, $event)\"\n />\n </td>\n</template>\n\n<script setup lang=\"ts\">\nimport type { ComponentPublicInstance } from \"vue\";\nimport { Button } from \"@progress/kendo-vue-buttons\";\nimport type { GridCellProps } from \"@progress/kendo-vue-grid\";\nimport { useCompositeId } from \"@featherk/composables/id\";\nimport type { RegisterableElement } from \"@featherk/composables/registry\";\nimport type {\n ActionCellConfig,\n ActionCellMenuController,\n ActionCellTrigger,\n} from \"./types\";\n\ndefineOptions({ inheritAttrs: false });\n\ntype ActionCellProps = Partial<\n Pick<\n GridCellProps,\n | \"id\"\n | \"className\"\n | \"style\"\n | \"colSpan\"\n | \"ariaColumnIndex\"\n | \"isSelected\"\n | \"columnIndex\"\n >\n> & {\n dataItem: any;\n config: ActionCellConfig<any>;\n menuController: ActionCellMenuController<any>;\n tabIndex?: number;\n};\n\nconst props = withDefaults(defineProps<ActionCellProps>(), {\n tabIndex: -1,\n});\n\nconst ids = useCompositeId(\"action-cell\");\n\nconst getTriggerId = (trigger: ActionCellTrigger<any>) =>\n props.menuController.getTriggerId(trigger, props.dataItem);\n\nconst isTriggerActive = (trigger: ActionCellTrigger<any>) =>\n props.menuController.registry.isIdActive(getTriggerId(trigger));\n\nconst hasMenu = (trigger: ActionCellTrigger<any>) =>\n typeof trigger.items === \"function\";\n\nconst registerTrigger = (\n trigger: ActionCellTrigger<any>,\n element: Element | ComponentPublicInstance | null,\n) => {\n props.menuController.registry.register(\n getTriggerId(trigger),\n element as RegisterableElement,\n );\n};\n\nconst onTriggerClick = (trigger: ActionCellTrigger<any>) => {\n if (!hasMenu(trigger)) {\n props.menuController.handleDirectAction?.(trigger, props.dataItem);\n return;\n }\n props.menuController.handleTriggerClick(getTriggerId(trigger));\n};\n\nconst onTriggerKeydown = (\n trigger: ActionCellTrigger<any>,\n ...args: unknown[]\n) => {\n if (!hasMenu(trigger)) return;\n props.menuController.handleTriggerKeydown(getTriggerId(trigger), ...args);\n};\n</script>\n","import {\n computed,\n nextTick,\n toValue,\n type ComputedRef,\n type MaybeRefOrGetter,\n} from \"vue\";\nimport {\n useActiveIdRegistry,\n type UseExclusiveGroupReturn,\n} from \"@featherk/composables/registry\";\nimport {\n usePopupMenu,\n type KendoMenuSelectEvent,\n type PopupMenuAnchorOptions,\n type PopupMenuElementRef,\n type PopupMenuOffset,\n type PopupMenuTriggerMode,\n} from \"@featherk/composables/menu\";\nimport type {\n ActionCellConfig,\n ActionCellDataItem,\n ActionCellMenuController,\n ActionCellMenuItem,\n} from \"./types\";\n\nexport type UseActionCellMenuOptions<\n TData extends ActionCellDataItem = ActionCellDataItem,\n TConfig extends ActionCellConfig<TData> = ActionCellConfig<TData>,\n> = {\n /** Repeatable trigger configuration, rendered by every `ActionCell` instance. */\n config: TConfig;\n /** All rows currently rendered; used to resolve the active row from its registry id. */\n rows: MaybeRefOrGetter<TData[]>;\n /** Derives a stable row id used to build the default registry id for each trigger. */\n getRowId: (row: TData) => string | number;\n /**\n * Overrides the registry ID formatting while preserving `<trigger.key>:<rowId>` semantics.\n * Active row and trigger resolution parse the first colon-separated segment as the trigger\n * key and the remaining segments as the row ID.\n */\n getTriggerId?: (trigger: TConfig[\"triggers\"][number], row: TData) => string;\n /** Ref to the single shared Kendo Menu instance. */\n menuRef: PopupMenuElementRef;\n /**\n * Optional custom popup measurement source forwarded to `usePopupMenu`.\n * Normal Kendo Popup markup is measured from the nearest `.k-popup` border\n * box automatically, including `activeTitle` and other sibling content.\n */\n popupContentRef?: PopupMenuElementRef;\n /** Forwarded to `usePopupMenu`. Defaults to `\"button\"`. */\n triggerMode?: PopupMenuTriggerMode;\n /** Forwarded to `usePopupMenu`. Defaults to `true` for shared-popup positioning. */\n anchor?: PopupMenuAnchorOptions | true;\n /** Forwarded to `usePopupMenu`. */\n resolveFocusTarget?: () => HTMLElement | null;\n /** Forwarded to `usePopupMenu`. */\n focusTargetContainerSelector?: string;\n /** Coordinates mutual exclusivity with other active-id registries in this group. */\n group?: UseExclusiveGroupReturn;\n /**\n * Runs after a menu item is selected, with its original item and resolved row/trigger.\n * Direct-action triggers have no menu item and pass `undefined`.\n */\n onActionSelected: (\n item: ActionCellMenuItem | undefined,\n row: TData,\n trigger: TConfig[\"triggers\"][number],\n ) => void;\n};\n\nexport type UseActionCellMenuReturn<\n TData extends ActionCellDataItem = ActionCellDataItem,\n TConfig extends ActionCellConfig<TData> = ActionCellConfig<TData>,\n> = {\n /** Spread onto every `<ActionCell>` instance. */\n actionCellProps: {\n config: TConfig;\n menuController: ActionCellMenuController<TData>;\n };\n /** Spread onto the single shared `<Popup>`. */\n popupProps: ComputedRef<{\n show: boolean;\n offset: PopupMenuOffset;\n onClose: () => void;\n }>;\n /** Spread onto the single shared Kendo `<Menu>`. */\n menuProps: ComputedRef<{\n items: ActionCellMenuItem[];\n vertical: true;\n onKeydown: (event: KeyboardEvent) => void;\n onSelect: (event: KendoMenuSelectEvent<ActionCellMenuItem>) => void;\n }>;\n /** Row resolved from the currently active trigger id, if any. */\n activeRow: ComputedRef<TData | null>;\n /** Trigger config resolved from the currently active trigger id, if any. */\n activeTrigger: ComputedRef<TConfig[\"triggers\"][number] | null>;\n /** Resolved `trigger.title(activeRow)`, or `null` when unset/no trigger is active. Render this yourself (e.g. `<strong>`) above the `<Menu>` — it is not part of `menuProps.items`. */\n activeTitle: ComputedRef<string | null>;\n};\n\n/**\n * Bundles the shared-popup wiring a parent needs to drive `ActionCell`: one\n * `useActiveIdRegistry`, one `usePopupMenu`, and the derived active\n * row/trigger/menu items. The parent still owns the `<Popup>`/`<Menu>`\n * template elements and any business logic for the selected action.\n */\nexport const useActionCellMenu = <\n TData extends ActionCellDataItem = ActionCellDataItem,\n TConfig extends ActionCellConfig<TData> = ActionCellConfig<TData>,\n>(\n options: UseActionCellMenuOptions<TData, TConfig>,\n): UseActionCellMenuReturn<TData, TConfig> => {\n const registry = useActiveIdRegistry<string>();\n options.group?.register(registry);\n\n const getTriggerId =\n options.getTriggerId ??\n ((trigger: TConfig[\"triggers\"][number], row: TData) =>\n `${trigger.key}:${options.getRowId(row)}`);\n\n const activeTrigger = computed(() => {\n const key = registry.activeId.value?.split(\":\")[0];\n return (\n (options.config.triggers.find((trigger) => trigger.key === key) as\n | TConfig[\"triggers\"][number]\n | undefined) ?? null\n );\n });\n\n const activeRow = computed(() => {\n const rowId = registry.activeId.value?.split(\":\").slice(1).join(\":\");\n if (rowId === undefined) return null;\n return (\n toValue(options.rows).find(\n (row) => String(options.getRowId(row)) === rowId,\n ) ?? null\n );\n });\n\n const activeMenuItems = computed<ActionCellMenuItem[]>(() => {\n const row = activeRow.value;\n const trigger = activeTrigger.value;\n return row && trigger ? (trigger.items?.(row) ?? []) : [];\n });\n\n const activeTitle = computed<string | null>(() => {\n const row = activeRow.value;\n const trigger = activeTrigger.value;\n return row &&\n trigger &&\n typeof trigger.items === \"function\" &&\n trigger.title\n ? trigger.title(row)\n : null;\n });\n\n const popupMenu = usePopupMenu({\n isOpen: registry.isActive,\n triggerRef: registry.activeElement,\n menuRef: options.menuRef,\n popupContentRef: options.popupContentRef,\n triggerMode: options.triggerMode ?? \"button\",\n anchor: options.anchor ?? true,\n resolveFocusTarget: options.resolveFocusTarget,\n focusTargetContainerSelector: options.focusTargetContainerSelector,\n requestShow: (id) => {\n if (typeof id === \"string\") registry.activate(id);\n },\n requestHide: () => registry.deactivate(),\n menuLabel: activeTitle,\n });\n\n let triggerInteractionSequence = 0;\n\n const handleTriggerClick = (id: string) => {\n const interactionSequence = ++triggerInteractionSequence;\n options.group?.deactivateOthers(registry);\n\n if (registry.isActive.value && !registry.isIdActive(id)) {\n registry.deactivate();\n void nextTick(() => {\n if (interactionSequence === triggerInteractionSequence) {\n popupMenu.handleTriggerClick(id);\n }\n });\n return;\n }\n\n popupMenu.handleTriggerClick(id);\n };\n\n const handleDirectAction = (\n trigger: TConfig[\"triggers\"][number],\n row: TData,\n ) => {\n triggerInteractionSequence++;\n options.group?.deactivateOthers(registry);\n registry.deactivate();\n options.onActionSelected(undefined, row, trigger);\n };\n\n const handleTriggerKeydown = (id: string, ...args: unknown[]) => {\n const event = args[0] as KeyboardEvent;\n if (event.key === \"Enter\" || event.code === \"Space\") {\n event.preventDefault();\n handleTriggerClick(id);\n return;\n }\n popupMenu.handleTriggerKeydown(event, id);\n };\n\n const onMenuKeydown = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") popupMenu.handleMenuEscape(event);\n };\n\n const onMenuSelect = (event: KendoMenuSelectEvent<ActionCellMenuItem>) => {\n const row = activeRow.value;\n const trigger = activeTrigger.value;\n popupMenu.handleMenuSelect(event, () => {\n if (row && trigger) {\n options.onActionSelected(event.item, row, trigger);\n }\n });\n };\n\n return {\n actionCellProps: {\n config: options.config,\n menuController: {\n registry,\n getTriggerId,\n handleTriggerClick,\n handleDirectAction,\n handleTriggerKeydown,\n },\n },\n popupProps: computed(() => ({\n show: registry.isActive.value,\n offset: popupMenu.offset.value,\n onClose: popupMenu.handlePopupClose,\n })),\n menuProps: computed(() => ({\n items: activeMenuItems.value,\n vertical: true as const,\n onKeydown: onMenuKeydown,\n onSelect: onMenuSelect,\n })),\n activeRow,\n activeTrigger,\n activeTitle,\n };\n};\n"],"names":["props","__props","ids","useCompositeId","getTriggerId","trigger","isTriggerActive","hasMenu","registerTrigger","element","onTriggerClick","onTriggerKeydown","args","_createElementBlock","_unref","_openBlock","_Fragment","_renderList","_createBlock","Button","$event","useActionCellMenu","options","registry","useActiveIdRegistry","row","activeTrigger","computed","key","activeRow","rowId","toValue","activeMenuItems","activeTitle","popupMenu","usePopupMenu","id","triggerInteractionSequence","handleTriggerClick","interactionSequence","nextTick","handleDirectAction","handleTriggerKeydown","event","onMenuKeydown","onMenuSelect"],"mappings":"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"}
package/docs/README.md CHANGED
@@ -19,14 +19,14 @@ Copy this template for each new component guide.
19
19
  ## Import
20
20
 
21
21
  ```ts
22
- import { <ComponentName> } from "@featherk/ui";
22
+ import { <ComponentName> } from "@featherk/ui/<feature>";
23
23
  ```
24
24
 
25
25
  ## Basic Usage
26
26
 
27
27
  ```html
28
28
  <script setup lang="ts">
29
- import { <ComponentName> } from "@featherk/ui";
29
+ import { <ComponentName> } from "@featherk/ui/<feature>";
30
30
  </script>
31
31
 
32
32
  <template>
@@ -53,6 +53,6 @@ import { <ComponentName> } from "@featherk/ui";
53
53
 
54
54
  - Add component guide at `docs/<domain>/<ComponentName>.md`
55
55
  - Add guide link under Components Overview in `packages/ui/README.md`
56
- - Ensure component is exported from `packages/ui/src/index.ts`
57
- - Keep runtime entry at root (`@featherk/ui`) unless subpath runtime entrypoints are intentionally added
56
+ - Export the component from its feature barrel (`packages/ui/src/<feature>/index.ts`), which the root `src/index.ts` re-exports
57
+ - For a new feature folder, add its subpath to `package.json` `exports`; the build creates the entry from it
58
58
  - Run `npx turbo build --filter=@featherk/ui`
@@ -57,8 +57,7 @@ Exported UI types include:
57
57
  ```html
58
58
  <script setup lang="ts">
59
59
  import { ref } from "vue";
60
- import { USAddress } from "@featherk/ui/address";
61
- import type { USAddressMessages } from "@featherk/ui/address";
60
+ import { USAddress, type USAddressMessages } from "@featherk/ui/address";
62
61
  import { useUSAddress } from "@featherk/composables/address";
63
62
 
64
63
  const shippingFieldsetRef = ref<HTMLFieldSetElement | null>(null);
@@ -0,0 +1,89 @@
1
+ # `DateField`
2
+
3
+ `DateField` renders a Kendo `DatePicker` with a masked date input, driven by a parent-owned `useMaskedDateInput`.
4
+
5
+ It wires the `MaskedTextBox`, its event handlers, `Label`, `Hint`, `Error` and the popup focus trap, so the consumer only calls the composable and binds two refs.
6
+
7
+ ## Import
8
+
9
+ ```ts
10
+ import { DateField, type DateFieldMessages } from "@featherk/ui/picker";
11
+ import { useMaskedDateInput } from "@featherk/composables/date";
12
+ ```
13
+
14
+ ## Basic Usage
15
+
16
+ ```html
17
+ <script setup lang="ts">
18
+ import { ref } from "vue";
19
+ import { DateField } from "@featherk/ui/picker";
20
+ import { useMaskedDateInput } from "@featherk/composables/date";
21
+
22
+ const date = ref<Date | null | undefined>();
23
+ const show = ref(false);
24
+
25
+ const dateInput = useMaskedDateInput({
26
+ id: "start-date",
27
+ externalValue: date,
28
+ min: new Date(1900, 0, 1),
29
+ max: new Date(),
30
+ required: true,
31
+ onChange: ({ value }) => (date.value = value),
32
+ onShowCalendar: () => (show.value = true),
33
+ });
34
+ </script>
35
+
36
+ <template>
37
+ <DateField
38
+ v-model="date"
39
+ v-model:show="show"
40
+ :input="dateInput"
41
+ label="Start date"
42
+ />
43
+ </template>
44
+ ```
45
+
46
+ ## Props
47
+
48
+ - `input: UseMaskedDateInputReturn` (required): the return value of `useMaskedDateInput`.
49
+ - `label?: string`: renders a Kendo `Label` linked to the input.
50
+ - `messages?: DateFieldMessages`: placeholder, hint and error text. See Messages.
51
+ - `min?: Date`, `max?: Date`: calendar bounds. Default to the composable's `min` and `max`.
52
+ - `v-model` (`modelValue?: Date | string | null`): the selected date. Bind the same ref passed to the composable as `externalValue`. A required field left empty is set to `""` by the composable on blur; the component passes Kendo `undefined` in that case.
53
+ - `v-model:show` (`show?: boolean`, default `false`): popup open state. Bind the same ref the composable's `onShowCalendar` sets.
54
+
55
+ Other attributes are passed to the Kendo `DatePicker`. `fillMode="outline"`, `rounded="medium"` and `size="medium"` are set by default and can be overridden. `disabled` and `readonly` reach the native input, block keyboard and wheel edits, and keep the calendar from opening.
56
+
57
+ ## Messages
58
+
59
+ Pass `messages` (type `DateFieldMessages`) to replace the default text. Every key is optional.
60
+
61
+ ```ts
62
+ const messages: DateFieldMessages = {
63
+ placeholder: "MM/DD/YYYY",
64
+ hint: "Shown under the input while there is no error.",
65
+ error: {
66
+ "required": "Enter a start date.",
67
+ "invalid-date": "Use MM/DD/YYYY.",
68
+ "out-of-bounds": "Pick a date in the allowed range.",
69
+ },
70
+ };
71
+ ```
72
+
73
+ - `placeholder`: input placeholder.
74
+ - `hint`: rendered in a Kendo `Hint` (id `<id>-hint`) while there is no error, and linked with `aria-describedby`.
75
+ - `error`: text per error key. The keys are the composable's `reason` values, except that `incomplete` is called `required` to match `USAddressMessages`. The composable still decides when an error shows. Keys left out keep the composable's message.
76
+
77
+ ## Events
78
+
79
+ - `update:modelValue`: a date was picked from the calendar.
80
+ - `update:show`: the popup opened or closed.
81
+
82
+ Typed input is reported through the composable's `onChange`.
83
+
84
+ ## Notes
85
+
86
+ - The input `id` comes from the composable's `id` option. Use a unique id per instance.
87
+ - Keyboard: Space or Alt+ArrowDown opens the calendar, ArrowUp/ArrowDown step the date part under the caret (from `useMaskedDateInput`). Focus is trapped in the calendar while it is open.
88
+ - The `Error` gets the id `<id>-error` and the hint `<id>-hint`. Only one shows at a time, and the input's `aria-describedby` points to it. `aria-invalid` follows the composable's `isValid`.
89
+ - Styling comes from `@featherk/styles`: the component root has the `fk-datepicker` class used by its external styles.
@@ -0,0 +1,99 @@
1
+ # `DateRangeField`
2
+
3
+ `DateRangeField` renders a masked date range input with a range-mode calendar popup, driven by a parent-owned `useMaskedDateRangeInput`.
4
+
5
+ It wires the `MaskedTextBox` and its handlers, the calendar icon toggle, the anchored `Popup`, a `Tooltip` around a range-mode `Calendar`, `Hint`, `Label`, `Error` and the popup focus trap.
6
+
7
+ ## Import
8
+
9
+ ```ts
10
+ import {
11
+ DateRangeField,
12
+ type DateRangeFieldMessages,
13
+ } from "@featherk/ui/picker";
14
+ import { useMaskedDateRangeInput } from "@featherk/composables/range";
15
+ ```
16
+
17
+ ## Basic Usage
18
+
19
+ ```html
20
+ <script setup lang="ts">
21
+ import { ref } from "vue";
22
+ import type { SelectionRange } from "@progress/kendo-vue-dateinputs";
23
+ import { DateRangeField } from "@featherk/ui/picker";
24
+ import { useMaskedDateRangeInput } from "@featherk/composables/range";
25
+
26
+ const range = ref<SelectionRange | null | undefined>();
27
+ const show = ref(false);
28
+
29
+ const rangeInput = useMaskedDateRangeInput({
30
+ id: "visit-range",
31
+ externalValue: range,
32
+ isOpen: show,
33
+ maxSpanDays: 90,
34
+ onChange: ({ value }) => (range.value = value),
35
+ onShowCalendar: () => (show.value = true),
36
+ onRequestClose: () => (show.value = false),
37
+ });
38
+ </script>
39
+
40
+ <template>
41
+ <DateRangeField
42
+ v-model="range"
43
+ v-model:show="show"
44
+ :input="rangeInput"
45
+ label="Visit range"
46
+ :messages="{ hint: 'Maximum 90 day range.' }"
47
+ />
48
+ </template>
49
+ ```
50
+
51
+ ## Props
52
+
53
+ - `input: UseMaskedDateRangeInputReturn` (required): the return value of `useMaskedDateRangeInput`.
54
+ - `label?: string`: renders a Kendo `Label` linked to the input.
55
+ - `messages?: DateRangeFieldMessages`: placeholder, hint and error text. See Messages.
56
+ - `views?: 1 | 2 | 3 | 4` (default `4`): months shown in the calendar. Values outside 1–4 are clamped.
57
+ - `min?: Date`, `max?: Date`: calendar bounds. Default to the composable's `min` and `max`. Pass them when the calendar should show a narrower window than typed entry allows.
58
+ - `v-model` (`modelValue?: SelectionRange | null`): the selected range. Bind the same ref passed to the composable as `externalValue`.
59
+ - `v-model:show` (`show?: boolean`, default `false`): popup open state. Bind the same ref passed to the composable as `isOpen`.
60
+
61
+ Other attributes are passed to the Kendo `MaskedTextBox`. `fillMode="outline"`, `rounded="medium"`, `size="medium"` and `showClearButton` are set by default and can be overridden. `disabled` and `readonly` reach the native input, block keyboard and wheel edits, and keep the calendar and clear button from changing the value. The placeholder defaults to `Start Date - End Date` and is set through `messages.placeholder`.
62
+
63
+ ## Messages
64
+
65
+ Pass `messages` (type `DateRangeFieldMessages`) to replace the default text. Every key is optional.
66
+
67
+ ```ts
68
+ const messages: DateRangeFieldMessages = {
69
+ placeholder: "Start Date - End Date",
70
+ hint: "Shown under the input while there is no error.",
71
+ error: {
72
+ "required": "Enter a start and end date.",
73
+ "invalid-date": "Use MM/DD/YYYY - MM/DD/YYYY.",
74
+ "out-of-bounds": "Dates must be within the last year.",
75
+ "reversed-range": "Start date must come first.",
76
+ "span-exceeds-limit": "Pick 90 days or fewer.",
77
+ },
78
+ };
79
+ ```
80
+
81
+ - `placeholder`: input placeholder.
82
+ - `hint`: rendered in a Kendo `Hint` (id `<id>-hint`) while there is no error, and linked with `aria-describedby`.
83
+ - `error`: text per error key. The keys are the composable's `reason` values, except that `incomplete` is called `required` to match `USAddressMessages`. The composable still decides when an error shows. Keys left out keep the composable's message.
84
+
85
+ ## Events
86
+
87
+ - `update:modelValue`: a range was picked in the calendar.
88
+ - `update:show`: the popup opened or closed.
89
+
90
+ Typed input is reported through the composable's `onChange`.
91
+
92
+ ## Notes
93
+
94
+ - The input `id` comes from the composable's `id` option. Use a unique id per instance. The calendar, error and hint get `<id>-calendar`, `<id>-error` and `<id>-hint`.
95
+ - Keyboard: Space or Alt+ArrowDown opens the calendar, ArrowUp/ArrowDown step the date part under the caret, Escape closes the calendar. Focus is trapped in the calendar while it is open.
96
+ - The input has `aria-haspopup="grid"`, `aria-expanded` and `aria-controls` for the calendar. `aria-describedby` points to the error while invalid, otherwise to the hint. `aria-invalid` and the invalid styling follow the composable's `validComputed`: nothing is flagged before the first blur, and afterwards they update as the user types, together with the error text.
97
+ - The calendar icon is mouse-only (`mousedown.prevent` keeps focus in the input). Keyboard users open the calendar from the input.
98
+ - With the FeatherK external styles, four months show as a 2×2 grid and two as one row.
99
+ - Styling comes from `@featherk/styles`: the component root has the `fk-daterangepicker` class used by its external styles.
@@ -0,0 +1,88 @@
1
+ # `TimeField`
2
+
3
+ `TimeField` renders a Kendo `TimePicker` with a masked time input, driven by a parent-owned `useMaskedTimeInput`.
4
+
5
+ It wires the `MaskedTextBox` (mask, rules and event handlers), the `TimePicker`'s own `change` event (Now and Set buttons), `Label`, `Hint`, `Error` and the popup focus trap.
6
+
7
+ ## Import
8
+
9
+ ```ts
10
+ import { TimeField, type TimeFieldMessages } from "@featherk/ui/picker";
11
+ import { useMaskedTimeInput } from "@featherk/composables/time";
12
+ ```
13
+
14
+ ## Basic Usage
15
+
16
+ ```html
17
+ <script setup lang="ts">
18
+ import { ref } from "vue";
19
+ import { TimeField } from "@featherk/ui/picker";
20
+ import { useMaskedTimeInput } from "@featherk/composables/time";
21
+
22
+ const time = ref<Date | null | undefined>();
23
+ const show = ref(false);
24
+
25
+ const timeInput = useMaskedTimeInput({
26
+ id: "start-time",
27
+ externalValue: time,
28
+ minuteStep: 5,
29
+ required: true,
30
+ onChange: ({ value }) => (time.value = value),
31
+ onShowPicker: () => (show.value = true),
32
+ });
33
+ </script>
34
+
35
+ <template>
36
+ <TimeField
37
+ v-model="time"
38
+ v-model:show="show"
39
+ :input="timeInput"
40
+ label="Start time"
41
+ />
42
+ </template>
43
+ ```
44
+
45
+ ## Props
46
+
47
+ - `input: UseMaskedTimeInputReturn` (required): the return value of `useMaskedTimeInput`.
48
+ - `label?: string`: renders a Kendo `Label` linked to the input.
49
+ - `messages?: TimeFieldMessages`: placeholder, hint and error text. See Messages.
50
+ - `min?: Date`, `max?: Date`: time list bounds. Default to the composable's `min` and `max`.
51
+ - `v-model` (`modelValue?: Date | string | null`): the selected time. Bind the same ref passed to the composable as `externalValue`. A required field left empty is set to `""` by the composable on blur; the component passes Kendo `undefined` in that case.
52
+ - `v-model:show` (`show?: boolean`, default `false`): popup open state. Bind the same ref the composable's `onShowPicker` sets.
53
+
54
+ Other attributes are passed to the Kendo `TimePicker`. `fillMode="outline"`, `rounded="medium"`, `size="medium"` and `format="hh:mm a"` are set by default and can be overridden. `disabled` and `readonly` reach the native input, block keyboard and wheel edits, and keep the time list from opening.
55
+
56
+ ## Messages
57
+
58
+ Pass `messages` (type `TimeFieldMessages`) to replace the default text. Every key is optional.
59
+
60
+ ```ts
61
+ const messages: TimeFieldMessages = {
62
+ placeholder: "hh:mm AM",
63
+ hint: "Shown under the input while there is no error.",
64
+ error: {
65
+ "required": "Enter a start time.",
66
+ "invalid-time": "Use hh:mm AM.",
67
+ "out-of-bounds": "Pick a time during office hours.",
68
+ },
69
+ };
70
+ ```
71
+
72
+ - `placeholder`: input placeholder.
73
+ - `hint`: rendered in a Kendo `Hint` (id `<id>-hint`) while there is no error, and linked with `aria-describedby`.
74
+ - `error`: text per error key. The keys are the composable's `reason` values, except that `incomplete` is called `required` to match `USAddressMessages`. The composable still decides when an error shows. Keys left out keep the composable's message.
75
+
76
+ ## Events
77
+
78
+ - `update:modelValue`: a time was set from the popup.
79
+ - `update:show`: the popup opened or closed.
80
+
81
+ Typed input is reported through the composable's `onChange`.
82
+
83
+ ## Notes
84
+
85
+ - The input `id` comes from the composable's `id` option. Use a unique id per instance.
86
+ - Keyboard: Space or Alt+ArrowDown opens the time list, ArrowUp/ArrowDown step the hour, minute (by `minuteStep`) or AM/PM under the caret (from `useMaskedTimeInput`). Focus is trapped in the popup while it is open. Closing it with Set, Now, Cancel or Escape returns focus to the input; an outside click does not.
87
+ - The `Error` gets the id `<id>-error` and the hint `<id>-hint`. Only one shows at a time, and the input's `aria-describedby` points to it. `aria-invalid` follows the composable's `isValid`.
88
+ - Styling comes from `@featherk/styles`: the component root has the `fk-timepicker` class used by its external styles.