@featherk/composables 0.17.0 → 0.18.0

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 (119) hide show
  1. package/README.md +62 -15
  2. package/dist/address.cjs +2 -0
  3. package/dist/address.cjs.map +1 -0
  4. package/dist/address.mjs +6 -0
  5. package/dist/address.mjs.map +1 -0
  6. package/dist/chunks/useActiveIdRegistry-BG8VR__Z.mjs +43 -0
  7. package/dist/chunks/useActiveIdRegistry-BG8VR__Z.mjs.map +1 -0
  8. package/dist/chunks/useActiveIdRegistry-C1b00tqW.cjs +2 -0
  9. package/dist/chunks/useActiveIdRegistry-C1b00tqW.cjs.map +1 -0
  10. package/dist/chunks/useCompositeId-DyrJ0Fqj.cjs +2 -0
  11. package/dist/chunks/useCompositeId-DyrJ0Fqj.cjs.map +1 -0
  12. package/dist/chunks/useCompositeId-xg87ehXQ.mjs +9 -0
  13. package/dist/chunks/useCompositeId-xg87ehXQ.mjs.map +1 -0
  14. package/dist/chunks/useFieldsetValidationKit-7W1XDbbr.cjs +2 -0
  15. package/dist/chunks/useFieldsetValidationKit-7W1XDbbr.cjs.map +1 -0
  16. package/dist/chunks/useFieldsetValidationKit-x0Rh48mE.mjs +64 -0
  17. package/dist/chunks/useFieldsetValidationKit-x0Rh48mE.mjs.map +1 -0
  18. package/dist/chunks/useGridRowAction-Di25yeqb.cjs +2 -0
  19. package/dist/chunks/useGridRowAction-Di25yeqb.cjs.map +1 -0
  20. package/dist/chunks/useGridRowAction-akjwkV_F.mjs +532 -0
  21. package/dist/chunks/useGridRowAction-akjwkV_F.mjs.map +1 -0
  22. package/dist/chunks/useIntersectionObserver-A4xQV_pl.mjs +23 -0
  23. package/dist/chunks/useIntersectionObserver-A4xQV_pl.mjs.map +1 -0
  24. package/dist/chunks/useIntersectionObserver-CfgNAo2N.cjs +2 -0
  25. package/dist/chunks/useIntersectionObserver-CfgNAo2N.cjs.map +1 -0
  26. package/dist/chunks/useMaskedDateInput-COOksUM_.mjs +276 -0
  27. package/dist/chunks/useMaskedDateInput-COOksUM_.mjs.map +1 -0
  28. package/dist/chunks/useMaskedDateInput-wRnYlR3L.cjs +2 -0
  29. package/dist/chunks/useMaskedDateInput-wRnYlR3L.cjs.map +1 -0
  30. package/dist/chunks/useMaskedDateRangeInput-DST5aQe0.cjs +2 -0
  31. package/dist/chunks/useMaskedDateRangeInput-DST5aQe0.cjs.map +1 -0
  32. package/dist/chunks/useMaskedDateRangeInput-owYSg36r.mjs +328 -0
  33. package/dist/chunks/useMaskedDateRangeInput-owYSg36r.mjs.map +1 -0
  34. package/dist/chunks/useMaskedTimeInput-DPd2lysd.cjs +2 -0
  35. package/dist/chunks/useMaskedTimeInput-DPd2lysd.cjs.map +1 -0
  36. package/dist/chunks/useMaskedTimeInput-DjtBdvsf.mjs +363 -0
  37. package/dist/chunks/useMaskedTimeInput-DjtBdvsf.mjs.map +1 -0
  38. package/dist/chunks/usePopupMenu-CZLtgrXk.mjs +296 -0
  39. package/dist/chunks/usePopupMenu-CZLtgrXk.mjs.map +1 -0
  40. package/dist/chunks/usePopupMenu-DeTWakUH.cjs +2 -0
  41. package/dist/chunks/usePopupMenu-DeTWakUH.cjs.map +1 -0
  42. package/dist/chunks/usePopupTrap-DQudM6YA.cjs +2 -0
  43. package/dist/chunks/usePopupTrap-DQudM6YA.cjs.map +1 -0
  44. package/dist/chunks/usePopupTrap-DeuXHLZR.mjs +105 -0
  45. package/dist/chunks/usePopupTrap-DeuXHLZR.mjs.map +1 -0
  46. package/dist/chunks/useUSAddress-BYZZfbHc.cjs +2 -0
  47. package/dist/chunks/useUSAddress-BYZZfbHc.cjs.map +1 -0
  48. package/dist/chunks/useUSAddress-CRcqdqrZ.mjs +291 -0
  49. package/dist/chunks/useUSAddress-CRcqdqrZ.mjs.map +1 -0
  50. package/dist/chunks/useWindow-C4XFBuJA.mjs +210 -0
  51. package/dist/chunks/useWindow-C4XFBuJA.mjs.map +1 -0
  52. package/dist/chunks/useWindow-Yia2I6zX.cjs +2 -0
  53. package/dist/chunks/useWindow-Yia2I6zX.cjs.map +1 -0
  54. package/dist/date/useMaskedDateInput.d.ts +4 -0
  55. package/dist/date.cjs +2 -0
  56. package/dist/date.cjs.map +1 -0
  57. package/dist/date.mjs +5 -0
  58. package/dist/date.mjs.map +1 -0
  59. package/dist/{docs-manifest.js → docs-manifest.mjs} +24 -24
  60. package/dist/form.cjs +2 -0
  61. package/dist/form.cjs.map +1 -0
  62. package/dist/form.mjs +6 -0
  63. package/dist/form.mjs.map +1 -0
  64. package/dist/geometry.cjs +2 -0
  65. package/dist/geometry.cjs.map +1 -0
  66. package/dist/geometry.mjs +60 -0
  67. package/dist/geometry.mjs.map +1 -0
  68. package/dist/grid.cjs +2 -0
  69. package/dist/grid.cjs.map +1 -0
  70. package/dist/grid.mjs +7 -0
  71. package/dist/grid.mjs.map +1 -0
  72. package/dist/id.cjs +2 -0
  73. package/dist/id.cjs.map +1 -0
  74. package/dist/id.mjs +5 -0
  75. package/dist/id.mjs.map +1 -0
  76. package/dist/index.cjs +2 -0
  77. package/dist/index.cjs.map +1 -0
  78. package/dist/index.d.ts +3 -3
  79. package/dist/index.mjs +37 -0
  80. package/dist/index.mjs.map +1 -0
  81. package/dist/menu.cjs +2 -0
  82. package/dist/menu.cjs.map +1 -0
  83. package/dist/menu.mjs +5 -0
  84. package/dist/menu.mjs.map +1 -0
  85. package/dist/observer.cjs +2 -0
  86. package/dist/observer.cjs.map +1 -0
  87. package/dist/observer.mjs +5 -0
  88. package/dist/observer.mjs.map +1 -0
  89. package/dist/range/useMaskedDateRangeInput.d.ts +4 -0
  90. package/dist/range.cjs +2 -0
  91. package/dist/range.cjs.map +1 -0
  92. package/dist/range.mjs +5 -0
  93. package/dist/range.mjs.map +1 -0
  94. package/dist/registry.cjs +2 -0
  95. package/dist/registry.cjs.map +1 -0
  96. package/dist/registry.mjs +6 -0
  97. package/dist/registry.mjs.map +1 -0
  98. package/dist/time/useMaskedTimeInput.d.ts +4 -0
  99. package/dist/time.cjs +2 -0
  100. package/dist/time.cjs.map +1 -0
  101. package/dist/time.mjs +5 -0
  102. package/dist/time.mjs.map +1 -0
  103. package/dist/trap.cjs +2 -0
  104. package/dist/trap.cjs.map +1 -0
  105. package/dist/trap.mjs +7 -0
  106. package/dist/trap.mjs.map +1 -0
  107. package/dist/window.cjs +2 -0
  108. package/dist/window.cjs.map +1 -0
  109. package/dist/window.mjs +6 -0
  110. package/dist/window.mjs.map +1 -0
  111. package/docs/date/useMaskedDateInput.md +3 -0
  112. package/docs/range/useMaskedDateRangeInput.md +6 -7
  113. package/docs/time/useMaskedTimeInput.md +3 -0
  114. package/package.json +38 -36
  115. package/dist/featherk-composables.es.js +0 -2628
  116. package/dist/featherk-composables.es.js.map +0 -1
  117. package/dist/featherk-composables.umd.js +0 -2
  118. package/dist/featherk-composables.umd.js.map +0 -1
  119. /package/dist/{docs-manifest.d.ts → docs-manifest.d.mts} +0 -0
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # `@featherk/composables`
1
+ # @featherk/composables
2
2
 
3
3
  Vue 3 composables that improve Kendo UI for Vue behavior and accessibility.
4
4
 
@@ -45,15 +45,15 @@ Vue 3 composables that improve Kendo UI for Vue behavior and accessibility.
45
45
  - **Window**: `useWindow` — Zero/low-config Kendo `Window` wrapper: viewport-clamped dragging and resizing, stage transitions, and an internal pointerdown-capture resize/text-selection workaround.
46
46
  - Guide: [docs/window/useWindow.md](https://github.com/NantHealth/featherk/blob/integration/packages/composables/docs/window/useWindow.md)
47
47
 
48
- ## Utility Export
48
+ ### Utility Export
49
49
 
50
50
  `computeCollisionOffset` is a pure utility export, not a Vue composable. It applies per-axis `fit` or `flip` collision behavior and is available from `@featherk/composables/geometry` or the package root.
51
51
 
52
52
  ## Importing Composables
53
53
 
54
- You can import directly from subpaths or from the package root.
54
+ Import from the feature subpath. Each subpath is a separate entry that loads only its own composables, which also keeps the dev server fast. The package root re-exports every feature; see [What Gets Installed vs. What Ships](#what-gets-installed-vs-what-ships). Don't import internal `dist` paths.
55
55
 
56
- Subpath imports (recommended for clarity):
56
+ Subpath imports (recommended):
57
57
 
58
58
  ```ts
59
59
  // Grid
@@ -67,7 +67,7 @@ import {
67
67
  // Date input
68
68
  import {
69
69
  useMaskedDateInput,
70
- type DateChangePayload,
70
+ type ChangePayload,
71
71
  } from "@featherk/composables/date";
72
72
 
73
73
  // Date range input
@@ -79,7 +79,7 @@ import {
79
79
  // Time input
80
80
  import {
81
81
  useMaskedTimeInput,
82
- type TimeChangePayload,
82
+ type ChangePayload,
83
83
  } from "@featherk/composables/time";
84
84
 
85
85
  // Popup focus trap utilities
@@ -123,7 +123,7 @@ import {
123
123
  import { useWindow } from "@featherk/composables/window";
124
124
  ```
125
125
 
126
- Root imports (types are aliased to avoid name collisions):
126
+ The package root re-exports every feature. Date and time payload types are aliased there to avoid name collisions:
127
127
 
128
128
  ```ts
129
129
  import {
@@ -152,7 +152,19 @@ import {
152
152
  } from "@featherk/composables";
153
153
  ```
154
154
 
155
- `usePopupTrap` depends on VueUse integrations and focus-trap internally. Those runtime packages are shipped as regular dependencies of `@featherk/composables`, so consumers do not need to install them separately.
155
+ For complete setup, behavior, and examples, use the guide listed in Composables Overview.
156
+
157
+ ## What Gets Installed vs. What Ships
158
+
159
+ FeatherK packages should just work once installed: you should never have to track down a missing dependency or silence a peer warning. When that conflicts with install size, FeatherK accepts a larger `node_modules` and keeps the browser bundle small.
160
+
161
+ | You import | Installed in `node_modules` | Shipped to the browser (production build) |
162
+ | -------------------------------------------------------- | ---------------------------------------------------------------------- | ----------------------------------------- |
163
+ | `import { useX } from "@featherk/composables/<feature>"` | Every runtime dependency (VueUse, focus-trap), installed automatically | `useX` and the dependencies it imports |
164
+ | `import { useX } from "@featherk/composables"` | Same | Same as the subpath import |
165
+ | Nothing from a feature | Its dependencies are still installed | Nothing from that feature |
166
+
167
+ During `vite dev`, a root import loads every feature. This affects only the dev server, not what users download.
156
168
 
157
169
  ## Kendo UI Compatibility
158
170
 
@@ -167,20 +179,55 @@ Built and tested against **Kendo UI for Vue 6.4.1**. The composables do not impo
167
179
  | `useMaskedDateInput`, `useMaskedDateRangeInput`, `useMaskedTimeInput` | `@progress/kendo-vue-dateinputs` |
168
180
  | `useZipTextBox`, `useUSAddress` | `@progress/kendo-vue-inputs` |
169
181
 
170
- ## Peer Dependencies
182
+ ## Dependencies
183
+
184
+ - Required peers: `vue` (3.5+)
185
+
186
+ npm and pnpm install it automatically; with Yarn Berry, install it yourself.
187
+
188
+ - Optional peers: the Kendo packages listed under [Kendo UI Compatibility](#kendo-ui-compatibility), for the composables you use. They're used only for types, and your app already installs them for the Kendo components it renders.
189
+ - Runtime dependencies: `@vueuse/core`, `@vueuse/integrations` and `focus-trap`, installed with this package. `usePopupTrap` uses VueUse integrations and focus-trap internally.
190
+
191
+ Keep all `@featherk/*` packages on the same version. They release together, and each `@featherk/ui` release requires at least the matching `@featherk/composables`. With Renovate or Dependabot, group them into one update.
192
+
193
+ ## Styling
171
194
 
172
- - Required: `vue` (3.5+)
195
+ The composables ship no CSS. Some, such as `useWindow` and `useGridA11y`, add `fk-*` classes to the Kendo components they manage, and `@featherk/styles` themes those classes.
196
+
197
+ ## Testing with Vitest
198
+
199
+ To mock a composable, mock its subpath, and import it from that same subpath in the code under test. Each subpath is a separate module, and a mock replaces only the module it names. A root mock doesn't reach code that imports a subpath, which includes every `@featherk/ui` component, and a subpath mock doesn't reach code that imports the root. Neither case raises an error; the test simply runs the real composable.
200
+
201
+ ```ts
202
+ // Before 0.18.0 every subpath was one module, so either specifier worked.
203
+ vi.mock("@featherk/composables", …);
204
+
205
+ // Now: mock the subpath the code imports.
206
+ vi.mock("@featherk/composables/trap", …);
207
+ ```
208
+
209
+ ## Build
210
+
211
+ Run from repository root:
173
212
 
174
213
  ```bash
175
- npm install vue
214
+ npm run build -- --filter=@featherk/composables
176
215
  ```
177
216
 
178
- - Optional: the Kendo packages listed under [Kendo UI Compatibility](#kendo-ui-compatibility), for the composables you use.
217
+ Or run package-local:
218
+
219
+ ```bash
220
+ npm run build
221
+ ```
179
222
 
180
- ## Notes
223
+ ## Maintainer Notes
181
224
 
182
- - Every public feature area has a source `index.ts` barrel, is re-exported from the package root, and has a matching public package subpath. For example, import `useIntersectionObserver` from `@featherk/composables/observer`, `useCompositeId` from `@featherk/composables/id`, or either from the package root. Consumers should not import internal `dist` paths.
183
- - The runtime files for `useGridA11y`, `useGridActiveFilter`, and `useGridRowAction` live in `src/grid/`. All three are re-exported from the package root and from `@featherk/composables/grid` — no import path changes are needed by consumers.
225
+ - Every feature folder has an `index.ts` barrel, is re-exported from `src/index.ts`, and has a matching `exports` subpath. For example, `useGridA11y`, `useGridActiveFilter` and `useGridRowAction` live in `src/grid/` and ship from `@featherk/composables/grid`.
226
+ - `vite.config.ts` builds one ES and one CommonJS entry for the root and for each `package.json` `exports` subpath. A new feature folder only needs its subpath added to `exports`.
227
+ - `package.json` sets `"sideEffects": false`, so a root import tree-shakes to the same code as the subpath. Keep modules free of import-time side effects. A module that must run on import has to be listed in `sideEffects`, or bundlers will drop it.
228
+ - Keep shared state out of module scope: no module-level refs, Maps or `provide`/`inject` keys. An app can load both the ES and CommonJS builds, and the two copies don't share state. Create state in the composable and pass the instance, as `useActiveIdRegistry` does.
229
+ - The repo's `scripts/check-entries.mjs` runs at the end of the build. It fails if an export target is missing, an entry imports a package that isn't a dependency or peer, or any entry imports an optional peer (npm doesn't install those). Kendo packages are optional peers, so this keeps them type-only.
230
+ - The repo's `scripts/check-peers.mjs` doesn't run here, because this package has no `@featherk/*` peers. If it ever gains one, add the sibling as a `file:` dev dependency and the peer check to the `build` script, as `@featherk/ui` does.
184
231
 
185
232
  ## Changelog
186
233
 
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./chunks/useUSAddress-BYZZfbHc.cjs");exports.useUSAddress=e.useUSAddress;exports.useZipTextBox=e.useZipTextBox;
2
+ //# sourceMappingURL=address.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"address.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":""}
@@ -0,0 +1,6 @@
1
+ import { u as a, a as o } from "./chunks/useUSAddress-CRcqdqrZ.mjs";
2
+ export {
3
+ a as useUSAddress,
4
+ o as useZipTextBox
5
+ };
6
+ //# sourceMappingURL=address.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"address.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
@@ -0,0 +1,43 @@
1
+ import { onUnmounted as v, shallowReactive as d, ref as f, computed as i, readonly as m } from "vue";
2
+ const p = () => {
3
+ const e = /* @__PURE__ */ new Set();
4
+ return { register: (n) => {
5
+ e.add(n), v(() => e.delete(n));
6
+ }, deactivateOthers: (n) => {
7
+ e.forEach((s) => {
8
+ s !== n && s.deactivate();
9
+ });
10
+ }, deactivateAll: () => {
11
+ e.forEach((n) => n.deactivate());
12
+ } };
13
+ }, E = (e) => {
14
+ if (e instanceof HTMLElement) return e;
15
+ const t = e && "$el" in e ? e.$el : null;
16
+ return t instanceof HTMLElement ? t : null;
17
+ }, h = () => {
18
+ const e = d(/* @__PURE__ */ new Map()), t = f(null), a = (l, u) => {
19
+ const o = E(u);
20
+ o ? e.set(l, o) : e.delete(l);
21
+ }, c = (l) => e.get(l) ?? null, n = (l) => {
22
+ t.value = l;
23
+ }, s = () => {
24
+ t.value = null;
25
+ }, r = (l) => t.value === l;
26
+ return {
27
+ activeId: m(t),
28
+ isActive: i(() => t.value !== null),
29
+ activeElement: i(
30
+ () => t.value !== null ? c(t.value) : null
31
+ ),
32
+ register: a,
33
+ resolve: c,
34
+ activate: n,
35
+ deactivate: s,
36
+ isIdActive: r
37
+ };
38
+ };
39
+ export {
40
+ p as a,
41
+ h as u
42
+ };
43
+ //# sourceMappingURL=useActiveIdRegistry-BG8VR__Z.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useActiveIdRegistry-BG8VR__Z.mjs","sources":["../../src/registry/useExclusiveGroup.ts","../../src/registry/useActiveIdRegistry.ts"],"sourcesContent":["import { onUnmounted } from \"vue\";\n\n/** A participant that can be deactivated when another group member becomes active. */\nexport type ExclusiveGroupMember = {\n deactivate: () => void;\n};\n\n/** Coordinates mutually exclusive activation across registered members. */\nexport type UseExclusiveGroupReturn = {\n /** Adds a member and unregisters it automatically when the current owner unmounts. */\n register: (member: ExclusiveGroupMember) => void;\n /** Deactivates every registered member except the activating member. */\n deactivateOthers: (activeMember: ExclusiveGroupMember) => void;\n /** Deactivates every registered member. */\n deactivateAll: () => void;\n};\n\n/**\n * Coordinates mutually exclusive active-id registries within a Vue owner.\n *\n * Registered members are removed automatically when their owning component or composable\n * unmounts. Call `register` from setup so Vue can associate cleanup with that owner.\n */\nexport const useExclusiveGroup = (): UseExclusiveGroupReturn => {\n const members = new Set<ExclusiveGroupMember>();\n\n const register = (member: ExclusiveGroupMember) => {\n members.add(member);\n onUnmounted(() => members.delete(member));\n };\n\n const deactivateOthers = (activeMember: ExclusiveGroupMember) => {\n members.forEach((member) => {\n if (member !== activeMember) member.deactivate();\n });\n };\n\n const deactivateAll = () => {\n members.forEach((member) => member.deactivate());\n };\n\n return { register, deactivateOthers, deactivateAll };\n};\n","import {\n computed,\n readonly,\n ref,\n shallowReactive,\n type ComputedRef,\n type DeepReadonly,\n type Ref,\n} from \"vue\";\n\n/** An element ref value that may be a plain element or a Vue/Kendo ref-like object exposing `$el`. */\nexport type RegisterableElement =\n | HTMLElement\n | { $el?: HTMLElement | null }\n | null;\n\nconst resolveRegisteredElement = (\n value: RegisterableElement,\n): HTMLElement | null => {\n if (value instanceof HTMLElement) return value;\n const componentElement = value && \"$el\" in value ? value.$el : null;\n return componentElement instanceof HTMLElement ? componentElement : null;\n};\n\n/** Functions and state returned by {@link useActiveIdRegistry}. */\nexport type UseActiveIdRegistryReturn<Id> = {\n /** The currently active id, or `null` when nothing is active. Read-only; use `activate`/`deactivate` to change it. */\n activeId: DeepReadonly<Ref<Id | null>>;\n /** `true` while any id is active. */\n isActive: ComputedRef<boolean>;\n /** The registered element for the active id, or `null`. */\n activeElement: ComputedRef<HTMLElement | null>;\n /** Registers (or unregisters, when `el` is `null`) the element for `id`. */\n register: (id: Id, el: RegisterableElement) => void;\n /** Returns the currently registered element for `id`, if any. */\n resolve: (id: Id) => HTMLElement | null;\n /** Marks `id` as active. */\n activate: (id: Id) => void;\n /** Clears the active id. */\n deactivate: () => void;\n /** Returns whether `id` is the currently active id. */\n isIdActive: (id: Id) => boolean;\n};\n\n/**\n * Tracks which single id, out of a rendered list of items (grid rows, tabs,\n * action buttons, etc.), is currently \"active,\" alongside a registry\n * resolving each id to its mounted DOM element.\n *\n * This helper is useful for shared-instance patterns where one popup/menu instance\n * is reused across a list while a single item remains active at a time.\n *\n * @returns Active-id state, an element registry, and activation helpers.\n */\nexport const useActiveIdRegistry = <\n Id = string | number,\n>(): UseActiveIdRegistryReturn<Id> => {\n const registry = shallowReactive(new Map<Id, HTMLElement>());\n const activeId = ref<Id | null>(null) as Ref<Id | null>;\n\n const register = (id: Id, el: RegisterableElement) => {\n const element = resolveRegisteredElement(el);\n if (element) {\n registry.set(id, element);\n } else {\n registry.delete(id);\n }\n };\n\n const resolve = (id: Id) => registry.get(id) ?? null;\n\n const activate = (id: Id) => {\n activeId.value = id;\n };\n\n const deactivate = () => {\n activeId.value = null;\n };\n\n const isIdActive = (id: Id) => activeId.value === id;\n\n return {\n activeId: readonly(activeId),\n isActive: computed(() => activeId.value !== null),\n activeElement: computed(() =>\n activeId.value !== null ? resolve(activeId.value) : null,\n ),\n register,\n resolve,\n activate,\n deactivate,\n isIdActive,\n };\n};\n"],"names":["useExclusiveGroup","members","member","onUnmounted","activeMember","resolveRegisteredElement","value","componentElement","useActiveIdRegistry","registry","shallowReactive","activeId","ref","register","id","el","element","resolve","activate","deactivate","isIdActive","readonly","computed"],"mappings":";AAuBO,MAAMA,IAAoB,MAA+B;AAC9D,QAAMC,wBAAc,IAAA;AAiBpB,SAAO,EAAE,UAfQ,CAACC,MAAiC;AACjD,IAAAD,EAAQ,IAAIC,CAAM,GAClBC,EAAY,MAAMF,EAAQ,OAAOC,CAAM,CAAC;AAAA,EAC1C,GAYmB,kBAVM,CAACE,MAAuC;AAC/D,IAAAH,EAAQ,QAAQ,CAACC,MAAW;AAC1B,MAAIA,MAAWE,KAAcF,EAAO,WAAA;AAAA,IACtC,CAAC;AAAA,EACH,GAMqC,eAJf,MAAM;AAC1B,IAAAD,EAAQ,QAAQ,CAACC,MAAWA,EAAO,YAAY;AAAA,EACjD,EAEqC;AACvC,GC1BMG,IAA2B,CAC/BC,MACuB;AACvB,MAAIA,aAAiB,YAAa,QAAOA;AACzC,QAAMC,IAAmBD,KAAS,SAASA,IAAQA,EAAM,MAAM;AAC/D,SAAOC,aAA4B,cAAcA,IAAmB;AACtE,GAgCaC,IAAsB,MAEG;AACpC,QAAMC,IAAWC,EAAgB,oBAAI,KAAsB,GACrDC,IAAWC,EAAe,IAAI,GAE9BC,IAAW,CAACC,GAAQC,MAA4B;AACpD,UAAMC,IAAUX,EAAyBU,CAAE;AAC3C,IAAIC,IACFP,EAAS,IAAIK,GAAIE,CAAO,IAExBP,EAAS,OAAOK,CAAE;AAAA,EAEtB,GAEMG,IAAU,CAACH,MAAWL,EAAS,IAAIK,CAAE,KAAK,MAE1CI,IAAW,CAACJ,MAAW;AAC3B,IAAAH,EAAS,QAAQG;AAAA,EACnB,GAEMK,IAAa,MAAM;AACvB,IAAAR,EAAS,QAAQ;AAAA,EACnB,GAEMS,IAAa,CAACN,MAAWH,EAAS,UAAUG;AAElD,SAAO;AAAA,IACL,UAAUO,EAASV,CAAQ;AAAA,IAC3B,UAAUW,EAAS,MAAMX,EAAS,UAAU,IAAI;AAAA,IAChD,eAAeW;AAAA,MAAS,MACtBX,EAAS,UAAU,OAAOM,EAAQN,EAAS,KAAK,IAAI;AAAA,IAAA;AAAA,IAEtD,UAAAE;AAAA,IACA,SAAAI;AAAA,IACA,UAAAC;AAAA,IACA,YAAAC;AAAA,IACA,YAAAC;AAAA,EAAA;AAEJ;"}
@@ -0,0 +1,2 @@
1
+ "use strict";const c=require("vue"),v=()=>{const e=new Set;return{register:n=>{e.add(n),c.onUnmounted(()=>e.delete(n))},deactivateOthers:n=>{e.forEach(l=>{l!==n&&l.deactivate()})},deactivateAll:()=>{e.forEach(n=>n.deactivate())}}},d=e=>{if(e instanceof HTMLElement)return e;const t=e&&"$el"in e?e.$el:null;return t instanceof HTMLElement?t:null},E=()=>{const e=c.shallowReactive(new Map),t=c.ref(null),o=(s,u)=>{const r=d(u);r?e.set(s,r):e.delete(s)},i=s=>e.get(s)??null,n=s=>{t.value=s},l=()=>{t.value=null},a=s=>t.value===s;return{activeId:c.readonly(t),isActive:c.computed(()=>t.value!==null),activeElement:c.computed(()=>t.value!==null?i(t.value):null),register:o,resolve:i,activate:n,deactivate:l,isIdActive:a}};exports.useActiveIdRegistry=E;exports.useExclusiveGroup=v;
2
+ //# sourceMappingURL=useActiveIdRegistry-C1b00tqW.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useActiveIdRegistry-C1b00tqW.cjs","sources":["../../src/registry/useExclusiveGroup.ts","../../src/registry/useActiveIdRegistry.ts"],"sourcesContent":["import { onUnmounted } from \"vue\";\n\n/** A participant that can be deactivated when another group member becomes active. */\nexport type ExclusiveGroupMember = {\n deactivate: () => void;\n};\n\n/** Coordinates mutually exclusive activation across registered members. */\nexport type UseExclusiveGroupReturn = {\n /** Adds a member and unregisters it automatically when the current owner unmounts. */\n register: (member: ExclusiveGroupMember) => void;\n /** Deactivates every registered member except the activating member. */\n deactivateOthers: (activeMember: ExclusiveGroupMember) => void;\n /** Deactivates every registered member. */\n deactivateAll: () => void;\n};\n\n/**\n * Coordinates mutually exclusive active-id registries within a Vue owner.\n *\n * Registered members are removed automatically when their owning component or composable\n * unmounts. Call `register` from setup so Vue can associate cleanup with that owner.\n */\nexport const useExclusiveGroup = (): UseExclusiveGroupReturn => {\n const members = new Set<ExclusiveGroupMember>();\n\n const register = (member: ExclusiveGroupMember) => {\n members.add(member);\n onUnmounted(() => members.delete(member));\n };\n\n const deactivateOthers = (activeMember: ExclusiveGroupMember) => {\n members.forEach((member) => {\n if (member !== activeMember) member.deactivate();\n });\n };\n\n const deactivateAll = () => {\n members.forEach((member) => member.deactivate());\n };\n\n return { register, deactivateOthers, deactivateAll };\n};\n","import {\n computed,\n readonly,\n ref,\n shallowReactive,\n type ComputedRef,\n type DeepReadonly,\n type Ref,\n} from \"vue\";\n\n/** An element ref value that may be a plain element or a Vue/Kendo ref-like object exposing `$el`. */\nexport type RegisterableElement =\n | HTMLElement\n | { $el?: HTMLElement | null }\n | null;\n\nconst resolveRegisteredElement = (\n value: RegisterableElement,\n): HTMLElement | null => {\n if (value instanceof HTMLElement) return value;\n const componentElement = value && \"$el\" in value ? value.$el : null;\n return componentElement instanceof HTMLElement ? componentElement : null;\n};\n\n/** Functions and state returned by {@link useActiveIdRegistry}. */\nexport type UseActiveIdRegistryReturn<Id> = {\n /** The currently active id, or `null` when nothing is active. Read-only; use `activate`/`deactivate` to change it. */\n activeId: DeepReadonly<Ref<Id | null>>;\n /** `true` while any id is active. */\n isActive: ComputedRef<boolean>;\n /** The registered element for the active id, or `null`. */\n activeElement: ComputedRef<HTMLElement | null>;\n /** Registers (or unregisters, when `el` is `null`) the element for `id`. */\n register: (id: Id, el: RegisterableElement) => void;\n /** Returns the currently registered element for `id`, if any. */\n resolve: (id: Id) => HTMLElement | null;\n /** Marks `id` as active. */\n activate: (id: Id) => void;\n /** Clears the active id. */\n deactivate: () => void;\n /** Returns whether `id` is the currently active id. */\n isIdActive: (id: Id) => boolean;\n};\n\n/**\n * Tracks which single id, out of a rendered list of items (grid rows, tabs,\n * action buttons, etc.), is currently \"active,\" alongside a registry\n * resolving each id to its mounted DOM element.\n *\n * This helper is useful for shared-instance patterns where one popup/menu instance\n * is reused across a list while a single item remains active at a time.\n *\n * @returns Active-id state, an element registry, and activation helpers.\n */\nexport const useActiveIdRegistry = <\n Id = string | number,\n>(): UseActiveIdRegistryReturn<Id> => {\n const registry = shallowReactive(new Map<Id, HTMLElement>());\n const activeId = ref<Id | null>(null) as Ref<Id | null>;\n\n const register = (id: Id, el: RegisterableElement) => {\n const element = resolveRegisteredElement(el);\n if (element) {\n registry.set(id, element);\n } else {\n registry.delete(id);\n }\n };\n\n const resolve = (id: Id) => registry.get(id) ?? null;\n\n const activate = (id: Id) => {\n activeId.value = id;\n };\n\n const deactivate = () => {\n activeId.value = null;\n };\n\n const isIdActive = (id: Id) => activeId.value === id;\n\n return {\n activeId: readonly(activeId),\n isActive: computed(() => activeId.value !== null),\n activeElement: computed(() =>\n activeId.value !== null ? resolve(activeId.value) : null,\n ),\n register,\n resolve,\n activate,\n deactivate,\n isIdActive,\n };\n};\n"],"names":["useExclusiveGroup","members","member","onUnmounted","activeMember","resolveRegisteredElement","value","componentElement","useActiveIdRegistry","registry","shallowReactive","activeId","ref","register","id","el","element","resolve","activate","deactivate","isIdActive","readonly","computed"],"mappings":"oCAuBaA,EAAoB,IAA+B,CAC9D,MAAMC,MAAc,IAiBpB,MAAO,CAAE,SAfSC,GAAiC,CACjDD,EAAQ,IAAIC,CAAM,EAClBC,EAAAA,YAAY,IAAMF,EAAQ,OAAOC,CAAM,CAAC,CAC1C,EAYmB,iBAVOE,GAAuC,CAC/DH,EAAQ,QAASC,GAAW,CACtBA,IAAWE,GAAcF,EAAO,WAAA,CACtC,CAAC,CACH,EAMqC,cAJf,IAAM,CAC1BD,EAAQ,QAASC,GAAWA,EAAO,YAAY,CACjD,CAEqC,CACvC,EC1BMG,EACJC,GACuB,CACvB,GAAIA,aAAiB,YAAa,OAAOA,EACzC,MAAMC,EAAmBD,GAAS,QAASA,EAAQA,EAAM,IAAM,KAC/D,OAAOC,aAA4B,YAAcA,EAAmB,IACtE,EAgCaC,EAAsB,IAEG,CACpC,MAAMC,EAAWC,EAAAA,gBAAgB,IAAI,GAAsB,EACrDC,EAAWC,EAAAA,IAAe,IAAI,EAE9BC,EAAW,CAACC,EAAQC,IAA4B,CACpD,MAAMC,EAAUX,EAAyBU,CAAE,EACvCC,EACFP,EAAS,IAAIK,EAAIE,CAAO,EAExBP,EAAS,OAAOK,CAAE,CAEtB,EAEMG,EAAWH,GAAWL,EAAS,IAAIK,CAAE,GAAK,KAE1CI,EAAYJ,GAAW,CAC3BH,EAAS,MAAQG,CACnB,EAEMK,EAAa,IAAM,CACvBR,EAAS,MAAQ,IACnB,EAEMS,EAAcN,GAAWH,EAAS,QAAUG,EAElD,MAAO,CACL,SAAUO,EAAAA,SAASV,CAAQ,EAC3B,SAAUW,EAAAA,SAAS,IAAMX,EAAS,QAAU,IAAI,EAChD,cAAeW,EAAAA,SAAS,IACtBX,EAAS,QAAU,KAAOM,EAAQN,EAAS,KAAK,EAAI,IAAA,EAEtD,SAAAE,EACA,QAAAI,EACA,SAAAC,EACA,WAAAC,EACA,WAAAC,CAAA,CAEJ"}
@@ -0,0 +1,2 @@
1
+ "use strict";const i=require("vue"),n=(s="fkid")=>{const e=`${s}-${i.useId()}`;return{instanceId:e,compose:(...t)=>[e,...t].filter(o=>o!=null).map(String).join("-")}};exports.useCompositeId=n;
2
+ //# sourceMappingURL=useCompositeId-DyrJ0Fqj.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useCompositeId-DyrJ0Fqj.cjs","sources":["../../src/id/useCompositeId.ts"],"sourcesContent":["import { useId } from \"vue\";\n\n/** A stable segment used to compose a component-scoped DOM identifier. */\nexport type CompositeIdPart = string | number | null | undefined;\n\n/** Values returned by {@link useCompositeId}. */\nexport type UseCompositeIdReturn = {\n /** Unique Vue component-instance identifier, prefixed for FeatherK markup. */\n instanceId: string;\n /** Combines the component instance ID with stable logical identifier segments. */\n compose: (...parts: CompositeIdPart[]) => string;\n};\n\n/**\n * Creates component-scoped DOM identifiers from Vue's SSR-safe `useId()`.\n *\n * Use the resulting `instanceId` for a stable local base and `compose` for\n * related trigger, menu, popup, and ARIA relationship IDs. This does not\n * replace consumer-owned domain IDs used for shared state or registries.\n */\nexport const useCompositeId = (prefix = \"fkid\"): UseCompositeIdReturn => {\n const instanceId = `${prefix}-${useId()}`;\n\n const compose = (...parts: CompositeIdPart[]) =>\n [instanceId, ...parts]\n .filter((part) => part !== null && part !== undefined)\n .map(String)\n .join(\"-\");\n\n return { instanceId, compose };\n};\n"],"names":["useCompositeId","prefix","instanceId","useId","parts","part"],"mappings":"oCAoBaA,EAAiB,CAACC,EAAS,SAAiC,CACvE,MAAMC,EAAa,GAAGD,CAAM,IAAIE,EAAAA,OAAO,GAQvC,MAAO,CAAE,WAAAD,EAAY,QANL,IAAIE,IAClB,CAACF,EAAY,GAAGE,CAAK,EAClB,OAAQC,GAASA,GAAS,IAA0B,EACpD,IAAI,MAAM,EACV,KAAK,GAAG,CAEQ,CACvB"}
@@ -0,0 +1,9 @@
1
+ import { useId as t } from "vue";
2
+ const m = (e = "fkid") => {
3
+ const o = `${e}-${t()}`;
4
+ return { instanceId: o, compose: (...n) => [o, ...n].filter((s) => s != null).map(String).join("-") };
5
+ };
6
+ export {
7
+ m as u
8
+ };
9
+ //# sourceMappingURL=useCompositeId-xg87ehXQ.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useCompositeId-xg87ehXQ.mjs","sources":["../../src/id/useCompositeId.ts"],"sourcesContent":["import { useId } from \"vue\";\n\n/** A stable segment used to compose a component-scoped DOM identifier. */\nexport type CompositeIdPart = string | number | null | undefined;\n\n/** Values returned by {@link useCompositeId}. */\nexport type UseCompositeIdReturn = {\n /** Unique Vue component-instance identifier, prefixed for FeatherK markup. */\n instanceId: string;\n /** Combines the component instance ID with stable logical identifier segments. */\n compose: (...parts: CompositeIdPart[]) => string;\n};\n\n/**\n * Creates component-scoped DOM identifiers from Vue's SSR-safe `useId()`.\n *\n * Use the resulting `instanceId` for a stable local base and `compose` for\n * related trigger, menu, popup, and ARIA relationship IDs. This does not\n * replace consumer-owned domain IDs used for shared state or registries.\n */\nexport const useCompositeId = (prefix = \"fkid\"): UseCompositeIdReturn => {\n const instanceId = `${prefix}-${useId()}`;\n\n const compose = (...parts: CompositeIdPart[]) =>\n [instanceId, ...parts]\n .filter((part) => part !== null && part !== undefined)\n .map(String)\n .join(\"-\");\n\n return { instanceId, compose };\n};\n"],"names":["useCompositeId","prefix","instanceId","useId","parts","part"],"mappings":";AAoBO,MAAMA,IAAiB,CAACC,IAAS,WAAiC;AACvE,QAAMC,IAAa,GAAGD,CAAM,IAAIE,GAAO;AAQvC,SAAO,EAAE,YAAAD,GAAY,SANL,IAAIE,MAClB,CAACF,GAAY,GAAGE,CAAK,EAClB,OAAO,CAACC,MAASA,KAAS,IAA0B,EACpD,IAAI,MAAM,EACV,KAAK,GAAG,EAEQ;AACvB;"}
@@ -0,0 +1,2 @@
1
+ "use strict";const e=require("vue");function f(r,s){const t=e.ref(!1);e.watchEffect(()=>{r.value?.setAttribute("aria-invalid",String(!s.isValid()))});function a(l){const i=r.value??l.currentTarget,o=l.relatedTarget;(!i||!i.contains(o))&&(t.value=!0)}function n(){t.value=!1}return{touched:t,onFocusout:a,reset:n}}function m(r,s){const t=e.ref(!1),a=e.ref(!1),n=e.computed(()=>e.toValue(s.submitted)||t.value),l=e.computed(()=>!n.value||s.isValid()),i=f(r,{isValid:()=>l.value});function o(u){return e.computed(()=>!n.value||e.toValue(u))}function v(u){return Object.fromEntries(Object.entries(u).map(([c,d])=>[c,o(d)]))}function V(u){const c=e.toValue(u);if(!c)return;c.querySelector('.k-invalid input, .k-invalid[tabindex="0"]')?.focus()}function F(u){if(a.value){a.value=!1;return}i.onFocusout(u),i.touched.value&&(t.value=!0)}function h(){t.value=!1,a.value=!0,i.reset()}return{touched:t,fieldsetValidate:n,onFocusout:F,reset:h,createFieldValidity:o,createFieldValidationsMap:v,focusFirstInvalidField:V}}exports.useFieldsetTouchTracker=f;exports.useFieldsetValidationKit=m;
2
+ //# sourceMappingURL=useFieldsetValidationKit-7W1XDbbr.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useFieldsetValidationKit-7W1XDbbr.cjs","sources":["../../src/form/useFieldsetTouchTracker.ts","../../src/form/useFieldsetValidationKit.ts"],"sourcesContent":["import { ref, watchEffect } from \"vue\";\nimport type { Ref } from \"vue\";\n\nexport interface UseFieldsetTouchTrackerOptions {\n /**\n * Function that returns whether the fieldset is valid.\n */\n isValid: () => boolean;\n}\n\nexport interface UseFieldsetTouchTrackerApi {\n touched: Ref<boolean>;\n onFocusout: (event: FocusEvent) => void;\n reset: () => void;\n}\n\nexport function useFieldsetTouchTracker(\n fieldsetRef: Ref<HTMLFieldSetElement | null>,\n options: UseFieldsetTouchTrackerOptions,\n): UseFieldsetTouchTrackerApi {\n const touched = ref(false);\n\n // Keep aria-invalid synchronized with the composed validator function.\n watchEffect(() => {\n fieldsetRef.value?.setAttribute(\"aria-invalid\", String(!options.isValid()));\n });\n\n function onFocusout(event: FocusEvent) {\n const fieldset =\n fieldsetRef.value ?? (event.currentTarget as HTMLElement | null);\n const next = event.relatedTarget as Node | null;\n\n if (!fieldset || !fieldset.contains(next)) {\n touched.value = true;\n }\n }\n\n function reset() {\n touched.value = false;\n }\n\n return { touched, onFocusout, reset };\n}\n","import {\n computed,\n ref,\n toValue,\n type ComputedRef,\n type MaybeRefOrGetter,\n type Ref,\n} from \"vue\";\nimport { useFieldsetTouchTracker } from \"./useFieldsetTouchTracker\";\n\nexport interface UseFieldsetValidationKitOptions {\n /**\n * Consumer-owned submit state. The kit reads this value but never mutates it.\n */\n submitted: MaybeRefOrGetter<boolean>;\n /**\n * Returns the real validity of the fieldset when validation is active.\n */\n isValid: () => boolean;\n}\n\n/**\n * API returned by {@link useFieldsetValidationKit}.\n */\nexport interface UseFieldsetValidationKitApi {\n /**\n * Becomes true after the first focus transition that leaves the fieldset.\n */\n touched: Ref<boolean>;\n /**\n * Delayed validation gate. True when either submitted or touched is true.\n */\n fieldsetValidate: ComputedRef<boolean>;\n /**\n * Forwards fieldset focusout events to the internal touch tracker.\n */\n onFocusout: (event: FocusEvent) => void;\n /**\n * Clears touched state and suppresses one immediate post-reset focusout.\n *\n * This avoids re-activating delayed validation from reset-related focus\n * transitions (for example, clicking Reset while an input is focused).\n */\n reset: () => void;\n /**\n * Wraps a field-level validity source with delayed validation behavior.\n */\n createFieldValidity: (\n isValid: MaybeRefOrGetter<boolean>,\n ) => ComputedRef<boolean>;\n /**\n * Builds a named map of delayed field validity refs for ergonomic form setup.\n */\n createFieldValidationsMap: <\n T extends Record<string, MaybeRefOrGetter<boolean>>,\n >(\n isValidMap: T,\n ) => { [K in keyof T]: ComputedRef<boolean> };\n /**\n * Moves focus to the first invalid Kendo control found within the provided form.\n */\n focusFirstInvalidField: (\n formRef: MaybeRefOrGetter<HTMLFormElement | null>,\n ) => void;\n}\n\n/**\n * Orchestrates delayed fieldset validation using submit state and touch state.\n *\n * Intended as the primary form-level API for gated validity UX:\n * - validation displays after submit or first fieldset exit\n * - field-level validity can be composed through helper factories\n * - invalid focus and reset behavior are handled consistently\n *\n * @param fieldsetRef Reference to the fieldset whose focus transitions are tracked.\n * @param options Consumer-provided submit state and base validity function.\n * @returns Validation kit API for wiring fieldset-level and field-level behavior.\n */\nexport function useFieldsetValidationKit(\n fieldsetRef: Ref<HTMLFieldSetElement | null>,\n options: UseFieldsetValidationKitOptions,\n): UseFieldsetValidationKitApi {\n const touched = ref(false);\n // After reset, browser/UI focus can emit an immediate focusout that does not\n // represent a user intentionally leaving the fieldset. Ignore exactly one\n // focusout to prevent delayed validation from re-arming right away.\n const suppressNextTouch = ref(false);\n const fieldsetValidate = computed(\n () => toValue(options.submitted) || touched.value,\n );\n\n // Computes the effective validity of the fieldset, taking into account the\n // delayed validation gate.\n const fieldsetIsValid = computed(() => {\n return !fieldsetValidate.value || options.isValid();\n });\n\n // Initializes the internal touch tracker for the fieldset.\n const tracker = useFieldsetTouchTracker(fieldsetRef, {\n isValid: () => fieldsetIsValid.value,\n });\n\n // Creates a delayed validity ref for a single field based on the fieldset's\n // validation state.\n function createFieldValidity(isValid: MaybeRefOrGetter<boolean>) {\n return computed(() => !fieldsetValidate.value || toValue(isValid));\n }\n\n // Builds a named map of delayed field validity refs from the provided validity map.\n // (e.g. { firstName: () => firstNameValid, lastName: () => lastNameValid })\n // show example map?\n // Example usage:\n // const fieldValidations = createFieldValidationsMap({\n // firstName: () => firstNameValid,\n // lastName: () => lastNameValid,\n // });\n // The returned object will have the same keys as the input map, but each value will\n // be a computed ref that reflects the delayed validation state.\n function createFieldValidationsMap<\n T extends Record<string, MaybeRefOrGetter<boolean>>,\n >(isValidMap: T) {\n return Object.fromEntries(\n Object.entries(isValidMap).map(([key, isValid]) => [\n key,\n createFieldValidity(isValid),\n ]),\n ) as { [K in keyof T]: ComputedRef<boolean> };\n }\n\n // Focuses the first invalid field within the provided FORM.\n // TODO: Leaving this here for now as a convenience, but it does not belong\n // in the core validation kit. This should be its own composable or utility\n // function. I may move this to USAddress component-specific logic.\n function focusFirstInvalidField(\n formRef: MaybeRefOrGetter<HTMLFormElement | null>,\n ) {\n const form = toValue(formRef);\n if (!form) return;\n\n const firstInvalid = form.querySelector(\n '.k-invalid input, .k-invalid[tabindex=\"0\"]',\n ) as HTMLElement | null;\n\n firstInvalid?.focus();\n }\n\n // Forwards fieldset focusout events to the internal touch tracker.\n function onFocusout(event: FocusEvent) {\n if (suppressNextTouch.value) {\n // Consume the first post-reset focusout caused by reset/click focus\n // churn.\n suppressNextTouch.value = false;\n return;\n }\n\n tracker.onFocusout(event);\n\n if (tracker.touched.value) {\n touched.value = true;\n }\n }\n\n // Resets the touched state and suppresses one immediate post-reset focusout.\n function reset() {\n touched.value = false;\n suppressNextTouch.value = true;\n tracker.reset();\n }\n\n return {\n touched,\n fieldsetValidate,\n onFocusout,\n reset,\n createFieldValidity,\n createFieldValidationsMap,\n focusFirstInvalidField,\n };\n}\n"],"names":["useFieldsetTouchTracker","fieldsetRef","options","touched","ref","watchEffect","onFocusout","event","fieldset","next","reset","useFieldsetValidationKit","suppressNextTouch","fieldsetValidate","computed","toValue","fieldsetIsValid","tracker","createFieldValidity","isValid","createFieldValidationsMap","isValidMap","key","focusFirstInvalidField","formRef","form"],"mappings":"oCAgBO,SAASA,EACdC,EACAC,EAC4B,CAC5B,MAAMC,EAAUC,EAAAA,IAAI,EAAK,EAGzBC,EAAAA,YAAY,IAAM,CAChBJ,EAAY,OAAO,aAAa,eAAgB,OAAO,CAACC,EAAQ,QAAA,CAAS,CAAC,CAC5E,CAAC,EAED,SAASI,EAAWC,EAAmB,CACrC,MAAMC,EACJP,EAAY,OAAUM,EAAM,cACxBE,EAAOF,EAAM,eAEf,CAACC,GAAY,CAACA,EAAS,SAASC,CAAI,KACtCN,EAAQ,MAAQ,GAEpB,CAEA,SAASO,GAAQ,CACfP,EAAQ,MAAQ,EAClB,CAEA,MAAO,CAAE,QAAAA,EAAS,WAAAG,EAAY,MAAAI,CAAA,CAChC,CCoCO,SAASC,EACdV,EACAC,EAC6B,CAC7B,MAAMC,EAAUC,EAAAA,IAAI,EAAK,EAInBQ,EAAoBR,EAAAA,IAAI,EAAK,EAC7BS,EAAmBC,EAAAA,SACvB,IAAMC,UAAQb,EAAQ,SAAS,GAAKC,EAAQ,KAAA,EAKxCa,EAAkBF,EAAAA,SAAS,IACxB,CAACD,EAAiB,OAASX,EAAQ,QAAA,CAC3C,EAGKe,EAAUjB,EAAwBC,EAAa,CACnD,QAAS,IAAMe,EAAgB,KAAA,CAChC,EAID,SAASE,EAAoBC,EAAoC,CAC/D,OAAOL,EAAAA,SAAS,IAAM,CAACD,EAAiB,OAASE,EAAAA,QAAQI,CAAO,CAAC,CACnE,CAYA,SAASC,EAEPC,EAAe,CACf,OAAO,OAAO,YACZ,OAAO,QAAQA,CAAU,EAAE,IAAI,CAAC,CAACC,EAAKH,CAAO,IAAM,CACjDG,EACAJ,EAAoBC,CAAO,CAAA,CAC5B,CAAA,CAEL,CAMA,SAASI,EACPC,EACA,CACA,MAAMC,EAAOV,EAAAA,QAAQS,CAAO,EAC5B,GAAI,CAACC,EAAM,OAEUA,EAAK,cACxB,4CAAA,GAGY,MAAA,CAChB,CAGA,SAASnB,EAAWC,EAAmB,CACrC,GAAIK,EAAkB,MAAO,CAG3BA,EAAkB,MAAQ,GAC1B,MACF,CAEAK,EAAQ,WAAWV,CAAK,EAEpBU,EAAQ,QAAQ,QAClBd,EAAQ,MAAQ,GAEpB,CAGA,SAASO,GAAQ,CACfP,EAAQ,MAAQ,GAChBS,EAAkB,MAAQ,GAC1BK,EAAQ,MAAA,CACV,CAEA,MAAO,CACL,QAAAd,EACA,iBAAAU,EACA,WAAAP,EACA,MAAAI,EACA,oBAAAQ,EACA,0BAAAE,EACA,uBAAAG,CAAA,CAEJ"}
@@ -0,0 +1,64 @@
1
+ import { ref as d, watchEffect as h, computed as c, toValue as f } from "vue";
2
+ function b(n, r) {
3
+ const e = d(!1);
4
+ h(() => {
5
+ n.value?.setAttribute("aria-invalid", String(!r.isValid()));
6
+ });
7
+ function i(s) {
8
+ const u = n.value ?? s.currentTarget, l = s.relatedTarget;
9
+ (!u || !u.contains(l)) && (e.value = !0);
10
+ }
11
+ function a() {
12
+ e.value = !1;
13
+ }
14
+ return { touched: e, onFocusout: i, reset: a };
15
+ }
16
+ function k(n, r) {
17
+ const e = d(!1), i = d(!1), a = c(
18
+ () => f(r.submitted) || e.value
19
+ ), s = c(() => !a.value || r.isValid()), u = b(n, {
20
+ isValid: () => s.value
21
+ });
22
+ function l(t) {
23
+ return c(() => !a.value || f(t));
24
+ }
25
+ function F(t) {
26
+ return Object.fromEntries(
27
+ Object.entries(t).map(([o, v]) => [
28
+ o,
29
+ l(v)
30
+ ])
31
+ );
32
+ }
33
+ function V(t) {
34
+ const o = f(t);
35
+ if (!o) return;
36
+ o.querySelector(
37
+ '.k-invalid input, .k-invalid[tabindex="0"]'
38
+ )?.focus();
39
+ }
40
+ function m(t) {
41
+ if (i.value) {
42
+ i.value = !1;
43
+ return;
44
+ }
45
+ u.onFocusout(t), u.touched.value && (e.value = !0);
46
+ }
47
+ function p() {
48
+ e.value = !1, i.value = !0, u.reset();
49
+ }
50
+ return {
51
+ touched: e,
52
+ fieldsetValidate: a,
53
+ onFocusout: m,
54
+ reset: p,
55
+ createFieldValidity: l,
56
+ createFieldValidationsMap: F,
57
+ focusFirstInvalidField: V
58
+ };
59
+ }
60
+ export {
61
+ k as a,
62
+ b as u
63
+ };
64
+ //# sourceMappingURL=useFieldsetValidationKit-x0Rh48mE.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useFieldsetValidationKit-x0Rh48mE.mjs","sources":["../../src/form/useFieldsetTouchTracker.ts","../../src/form/useFieldsetValidationKit.ts"],"sourcesContent":["import { ref, watchEffect } from \"vue\";\nimport type { Ref } from \"vue\";\n\nexport interface UseFieldsetTouchTrackerOptions {\n /**\n * Function that returns whether the fieldset is valid.\n */\n isValid: () => boolean;\n}\n\nexport interface UseFieldsetTouchTrackerApi {\n touched: Ref<boolean>;\n onFocusout: (event: FocusEvent) => void;\n reset: () => void;\n}\n\nexport function useFieldsetTouchTracker(\n fieldsetRef: Ref<HTMLFieldSetElement | null>,\n options: UseFieldsetTouchTrackerOptions,\n): UseFieldsetTouchTrackerApi {\n const touched = ref(false);\n\n // Keep aria-invalid synchronized with the composed validator function.\n watchEffect(() => {\n fieldsetRef.value?.setAttribute(\"aria-invalid\", String(!options.isValid()));\n });\n\n function onFocusout(event: FocusEvent) {\n const fieldset =\n fieldsetRef.value ?? (event.currentTarget as HTMLElement | null);\n const next = event.relatedTarget as Node | null;\n\n if (!fieldset || !fieldset.contains(next)) {\n touched.value = true;\n }\n }\n\n function reset() {\n touched.value = false;\n }\n\n return { touched, onFocusout, reset };\n}\n","import {\n computed,\n ref,\n toValue,\n type ComputedRef,\n type MaybeRefOrGetter,\n type Ref,\n} from \"vue\";\nimport { useFieldsetTouchTracker } from \"./useFieldsetTouchTracker\";\n\nexport interface UseFieldsetValidationKitOptions {\n /**\n * Consumer-owned submit state. The kit reads this value but never mutates it.\n */\n submitted: MaybeRefOrGetter<boolean>;\n /**\n * Returns the real validity of the fieldset when validation is active.\n */\n isValid: () => boolean;\n}\n\n/**\n * API returned by {@link useFieldsetValidationKit}.\n */\nexport interface UseFieldsetValidationKitApi {\n /**\n * Becomes true after the first focus transition that leaves the fieldset.\n */\n touched: Ref<boolean>;\n /**\n * Delayed validation gate. True when either submitted or touched is true.\n */\n fieldsetValidate: ComputedRef<boolean>;\n /**\n * Forwards fieldset focusout events to the internal touch tracker.\n */\n onFocusout: (event: FocusEvent) => void;\n /**\n * Clears touched state and suppresses one immediate post-reset focusout.\n *\n * This avoids re-activating delayed validation from reset-related focus\n * transitions (for example, clicking Reset while an input is focused).\n */\n reset: () => void;\n /**\n * Wraps a field-level validity source with delayed validation behavior.\n */\n createFieldValidity: (\n isValid: MaybeRefOrGetter<boolean>,\n ) => ComputedRef<boolean>;\n /**\n * Builds a named map of delayed field validity refs for ergonomic form setup.\n */\n createFieldValidationsMap: <\n T extends Record<string, MaybeRefOrGetter<boolean>>,\n >(\n isValidMap: T,\n ) => { [K in keyof T]: ComputedRef<boolean> };\n /**\n * Moves focus to the first invalid Kendo control found within the provided form.\n */\n focusFirstInvalidField: (\n formRef: MaybeRefOrGetter<HTMLFormElement | null>,\n ) => void;\n}\n\n/**\n * Orchestrates delayed fieldset validation using submit state and touch state.\n *\n * Intended as the primary form-level API for gated validity UX:\n * - validation displays after submit or first fieldset exit\n * - field-level validity can be composed through helper factories\n * - invalid focus and reset behavior are handled consistently\n *\n * @param fieldsetRef Reference to the fieldset whose focus transitions are tracked.\n * @param options Consumer-provided submit state and base validity function.\n * @returns Validation kit API for wiring fieldset-level and field-level behavior.\n */\nexport function useFieldsetValidationKit(\n fieldsetRef: Ref<HTMLFieldSetElement | null>,\n options: UseFieldsetValidationKitOptions,\n): UseFieldsetValidationKitApi {\n const touched = ref(false);\n // After reset, browser/UI focus can emit an immediate focusout that does not\n // represent a user intentionally leaving the fieldset. Ignore exactly one\n // focusout to prevent delayed validation from re-arming right away.\n const suppressNextTouch = ref(false);\n const fieldsetValidate = computed(\n () => toValue(options.submitted) || touched.value,\n );\n\n // Computes the effective validity of the fieldset, taking into account the\n // delayed validation gate.\n const fieldsetIsValid = computed(() => {\n return !fieldsetValidate.value || options.isValid();\n });\n\n // Initializes the internal touch tracker for the fieldset.\n const tracker = useFieldsetTouchTracker(fieldsetRef, {\n isValid: () => fieldsetIsValid.value,\n });\n\n // Creates a delayed validity ref for a single field based on the fieldset's\n // validation state.\n function createFieldValidity(isValid: MaybeRefOrGetter<boolean>) {\n return computed(() => !fieldsetValidate.value || toValue(isValid));\n }\n\n // Builds a named map of delayed field validity refs from the provided validity map.\n // (e.g. { firstName: () => firstNameValid, lastName: () => lastNameValid })\n // show example map?\n // Example usage:\n // const fieldValidations = createFieldValidationsMap({\n // firstName: () => firstNameValid,\n // lastName: () => lastNameValid,\n // });\n // The returned object will have the same keys as the input map, but each value will\n // be a computed ref that reflects the delayed validation state.\n function createFieldValidationsMap<\n T extends Record<string, MaybeRefOrGetter<boolean>>,\n >(isValidMap: T) {\n return Object.fromEntries(\n Object.entries(isValidMap).map(([key, isValid]) => [\n key,\n createFieldValidity(isValid),\n ]),\n ) as { [K in keyof T]: ComputedRef<boolean> };\n }\n\n // Focuses the first invalid field within the provided FORM.\n // TODO: Leaving this here for now as a convenience, but it does not belong\n // in the core validation kit. This should be its own composable or utility\n // function. I may move this to USAddress component-specific logic.\n function focusFirstInvalidField(\n formRef: MaybeRefOrGetter<HTMLFormElement | null>,\n ) {\n const form = toValue(formRef);\n if (!form) return;\n\n const firstInvalid = form.querySelector(\n '.k-invalid input, .k-invalid[tabindex=\"0\"]',\n ) as HTMLElement | null;\n\n firstInvalid?.focus();\n }\n\n // Forwards fieldset focusout events to the internal touch tracker.\n function onFocusout(event: FocusEvent) {\n if (suppressNextTouch.value) {\n // Consume the first post-reset focusout caused by reset/click focus\n // churn.\n suppressNextTouch.value = false;\n return;\n }\n\n tracker.onFocusout(event);\n\n if (tracker.touched.value) {\n touched.value = true;\n }\n }\n\n // Resets the touched state and suppresses one immediate post-reset focusout.\n function reset() {\n touched.value = false;\n suppressNextTouch.value = true;\n tracker.reset();\n }\n\n return {\n touched,\n fieldsetValidate,\n onFocusout,\n reset,\n createFieldValidity,\n createFieldValidationsMap,\n focusFirstInvalidField,\n };\n}\n"],"names":["useFieldsetTouchTracker","fieldsetRef","options","touched","ref","watchEffect","onFocusout","event","fieldset","next","reset","useFieldsetValidationKit","suppressNextTouch","fieldsetValidate","computed","toValue","fieldsetIsValid","tracker","createFieldValidity","isValid","createFieldValidationsMap","isValidMap","key","focusFirstInvalidField","formRef","form"],"mappings":";AAgBO,SAASA,EACdC,GACAC,GAC4B;AAC5B,QAAMC,IAAUC,EAAI,EAAK;AAGzB,EAAAC,EAAY,MAAM;AAChB,IAAAJ,EAAY,OAAO,aAAa,gBAAgB,OAAO,CAACC,EAAQ,QAAA,CAAS,CAAC;AAAA,EAC5E,CAAC;AAED,WAASI,EAAWC,GAAmB;AACrC,UAAMC,IACJP,EAAY,SAAUM,EAAM,eACxBE,IAAOF,EAAM;AAEnB,KAAI,CAACC,KAAY,CAACA,EAAS,SAASC,CAAI,OACtCN,EAAQ,QAAQ;AAAA,EAEpB;AAEA,WAASO,IAAQ;AACf,IAAAP,EAAQ,QAAQ;AAAA,EAClB;AAEA,SAAO,EAAE,SAAAA,GAAS,YAAAG,GAAY,OAAAI,EAAA;AAChC;ACoCO,SAASC,EACdV,GACAC,GAC6B;AAC7B,QAAMC,IAAUC,EAAI,EAAK,GAInBQ,IAAoBR,EAAI,EAAK,GAC7BS,IAAmBC;AAAA,IACvB,MAAMC,EAAQb,EAAQ,SAAS,KAAKC,EAAQ;AAAA,EAAA,GAKxCa,IAAkBF,EAAS,MACxB,CAACD,EAAiB,SAASX,EAAQ,QAAA,CAC3C,GAGKe,IAAUjB,EAAwBC,GAAa;AAAA,IACnD,SAAS,MAAMe,EAAgB;AAAA,EAAA,CAChC;AAID,WAASE,EAAoBC,GAAoC;AAC/D,WAAOL,EAAS,MAAM,CAACD,EAAiB,SAASE,EAAQI,CAAO,CAAC;AAAA,EACnE;AAYA,WAASC,EAEPC,GAAe;AACf,WAAO,OAAO;AAAA,MACZ,OAAO,QAAQA,CAAU,EAAE,IAAI,CAAC,CAACC,GAAKH,CAAO,MAAM;AAAA,QACjDG;AAAA,QACAJ,EAAoBC,CAAO;AAAA,MAAA,CAC5B;AAAA,IAAA;AAAA,EAEL;AAMA,WAASI,EACPC,GACA;AACA,UAAMC,IAAOV,EAAQS,CAAO;AAC5B,QAAI,CAACC,EAAM;AAMX,IAJqBA,EAAK;AAAA,MACxB;AAAA,IAAA,GAGY,MAAA;AAAA,EAChB;AAGA,WAASnB,EAAWC,GAAmB;AACrC,QAAIK,EAAkB,OAAO;AAG3B,MAAAA,EAAkB,QAAQ;AAC1B;AAAA,IACF;AAEA,IAAAK,EAAQ,WAAWV,CAAK,GAEpBU,EAAQ,QAAQ,UAClBd,EAAQ,QAAQ;AAAA,EAEpB;AAGA,WAASO,IAAQ;AACf,IAAAP,EAAQ,QAAQ,IAChBS,EAAkB,QAAQ,IAC1BK,EAAQ,MAAA;AAAA,EACV;AAEA,SAAO;AAAA,IACL,SAAAd;AAAA,IACA,kBAAAU;AAAA,IACA,YAAAP;AAAA,IACA,OAAAI;AAAA,IACA,qBAAAQ;AAAA,IACA,2BAAAE;AAAA,IACA,wBAAAG;AAAA,EAAA;AAEJ;"}
@@ -0,0 +1,2 @@
1
+ "use strict";const h=require("vue"),P=(l,k)=>{const d=h.ref(null);let K=!1,A=null,y=null;const E=[],q=".k-table-row[data-grid-row-index] [tabindex]",v=()=>{const e=l?.value?.$el;return e?e.closest(".k-grid"):null},X=e=>{const n=v();if(!n)return!1;const t=e.closest(".k-grid-aria-root");if(t)return n.contains(t);const o=e.closest(".k-grid-header, .k-grid-content, .k-grid-content-locked");return o?n.contains(o):!1},N=e=>e.key==="Enter"||e.code==="Enter"||e.code==="NumpadEnter",D=()=>l?.value?.columns,R=e=>{const n=e.key||e.code;([" ","Spacebar","Space"].includes(n)||N(e))&&(e.preventDefault(),e.stopPropagation(),d.value=e.target,e.target.click())},I=e=>{if(!d.value)return;if(e.code==="Escape"){e.stopPropagation(),d.value&&d.value.focus();return}const n=Array.from(document.querySelectorAll(".k-animation-container .k-popup .k-column-menu .k-columnmenu-item-wrapper .k-columnmenu-item")),t=document.querySelector(".k-animation-container .k-popup .k-column-menu .k-filter-menu-container");if(t){if(e.code==="Tab"){const o=[".k-dropdownlist[tabindex='0']","input.k-input-inner:not([tabindex='-1']):not([disabled])","button:not([tabindex='-1']):not([disabled])",".k-checkbox"],c=k?.focusableMenuSelectors??o,i=Array.from(t.querySelectorAll(c.join(",")));if(i.length===0)return;const a=i.findIndex(m=>m===document.activeElement);let f;a===-1?f=0:e.shiftKey?f=(a-1+i.length)%i.length:f=(a+1)%i.length,e.preventDefault(),e.stopPropagation(),i[f]?.focus()}}else if(e.code==="ArrowUp"||e.code==="ArrowDown"){e.preventDefault(),e.stopPropagation();const o=n.findIndex(i=>i===document.activeElement);let c=o;e.code==="ArrowUp"?c=o>0?o-1:n.length-1:e.code==="ArrowDown"&&(c=o<n.length-1?o+1:0),n[c]?.focus();return}if(e.code==="Tab"){e.preventDefault(),e.stopPropagation();const o=d.value?.parentElement;(e.shiftKey?o?.previousElementSibling:o?.nextElementSibling)?.querySelector("button, [role='button'], .k-link")?.focus()}},Y=()=>{A=new MutationObserver(e=>{e.forEach(n=>{n.addedNodes.forEach(t=>{if(t.nodeType===Node.ELEMENT_NODE){const o=t;if(o.classList.contains("k-animation-container")){const i=d.value;i&&(i.dataset.featherKSortable==="true"||h.nextTick(()=>{o.querySelectorAll(".k-columnmenu-item-wrapper").forEach(m=>{m.textContent?.toLowerCase().includes("sort")&&m.remove()})})),o.addEventListener("keydown",I),h.nextTick(()=>{const a=()=>{const f=Array.from(o.querySelectorAll(".k-animation-container .k-popup .k-column-menu .k-columnmenu-item-wrapper .k-columnmenu-item"));if(f.length===1)f[0].focus(),f[0].click(),a.attempts=0;else if(f.length>1){a.attempts=0;return}else a.attempts===void 0&&(a.attempts=0),a.attempts++<3&&setTimeout(a,200)};a()})}o.querySelectorAll(".k-animation-container").forEach(i=>{i.addEventListener("keydown",I)})}}),n.removedNodes.forEach(t=>{if(t.nodeType===Node.ELEMENT_NODE){const o=t;o.classList.contains("k-animation-container")&&o.removeEventListener("keydown",I),o.querySelectorAll(".k-animation-container").forEach(i=>{i.removeEventListener("keydown",I)})}})})}),A.observe(document.body,{childList:!0,subtree:!0})},r=e=>{const n=e.tagName.toLowerCase();return!!(["input","textarea","select","button"].includes(n)||e.closest('.k-picker-wrap, .k-input-inner, .k-textbox, [role="combobox"]'))},s=e=>{if(!e.type||!e)return;const n=e.target;if(!n)return;const t=n.closest(".k-table-row[data-grid-row-index]");if(!X(n))return;const o=["ArrowLeft","ArrowRight"].includes(e.code);if(!(r(n)&&!(t&&o))){if(e.code==="Escape"){const c=document.activeElement?.closest(".k-table-row[data-grid-row-index]");if(c){e.preventDefault(),e.stopPropagation();try{Array.from(l?.value.$el.querySelectorAll(".k-table-row[data-grid-row-index]")).forEach(a=>a.setAttribute("tabindex","-1"))}catch{}c.setAttribute("tabindex","0"),c.focus();return}}if(["ArrowDown","ArrowLeft","ArrowRight","ArrowUp","Enter","NumpadEnter","Space"].includes(e.code)){if(n.classList.contains("k-grid-header-menu")&&n.classList.contains("k-grid-column-menu")){d.value=n;return}if(t){if(["ArrowDown","ArrowUp"].includes(e.code)){e.preventDefault(),e.stopPropagation();const c=(a,f)=>{let m=f==="next"?a.nextElementSibling:a.previousElementSibling;for(;m;){const T=m;try{if(T.classList.contains("k-table-row"))return T}catch{}m=f==="next"?m.nextElementSibling:m.previousElementSibling}return null},i=e.code==="ArrowDown"?c(t,"next"):c(t,"previous");i&&(t.setAttribute("tabindex","-1"),i.setAttribute("tabindex","0"),i.focus());return}if(["ArrowLeft","ArrowRight"].includes(e.code)){e.preventDefault(),e.stopPropagation();const c=t.querySelectorAll(q);if(c.length===0)return;let i=Array.from(c).findIndex(a=>a===document.activeElement);if(i===-1&&document.activeElement===t){c[0].focus();return}e.code==="ArrowRight"?i=i===c.length-1?0:i+1:e.code==="ArrowLeft"&&(i=i===0?c.length-1:i-1),c[i].focus();return}}}}},u=()=>{h.nextTick(()=>{const e=l.value?.$el;if(!e||typeof e.closest!="function")return;const n=e.closest(".k-grid");n&&n.classList.add("fk-grid")})},p=()=>{if(!y)try{const e=()=>{try{const o=Array.from(l.value.$el.querySelectorAll(".k-table-row[data-grid-row-index]"));if(!o||o.length===0||o.filter(a=>a.getAttribute("tabindex")==="0").length===1)return;const i=o.find(a=>a===document.activeElement||a.contains(document.activeElement));if(o.forEach(a=>a.setAttribute("tabindex","-1")),i){i.setAttribute("tabindex","0");return}o[0].setAttribute("tabindex","0")}catch(o){console.error("ensureSingleTabindex error:",o)}},n=Array.from(l.value.$el.querySelectorAll(".k-table-row[data-grid-row-index]"));n.length>0&&n.forEach((o,c)=>{o.setAttribute("tabindex",c===0?"0":"-1")});const t=l.value.$el.querySelector(".k-table-tbody");t&&(y=new MutationObserver(()=>{e()}),y.observe(t,{childList:!0,subtree:!0}))}catch(e){console.error("Error setting up row navigation:",e)}},w=()=>{h.nextTick(()=>{const n=document.querySelectorAll(".k-grid-header .k-grid-header-menu.k-grid-column-menu");n&&n.forEach(t=>{t.setAttribute("role","button"),t.addEventListener("keydown",R)}),Y(),x()})},g=()=>{const n=document.querySelectorAll(".k-grid-header .k-grid-header-menu.k-grid-column-menu");n&&n.forEach(t=>{t.removeEventListener("keydown",R)}),A&&(A.disconnect(),A=null),y&&(y.disconnect(),y=null),E.forEach(t=>t()),E.length=0},x=()=>{document.querySelectorAll(".k-grid-header .k-table-thead th").forEach((t,o)=>{if(t.dataset.featherKHeaderBound==="true")return;const c=t.querySelector(".k-grid-header-menu.k-grid-column-menu");if(!c)return;const i=D();if(i&&i[o]){const b=i[o].field??"";t.setAttribute("data-feather-k-field",b),t.setAttribute("data-feather-k-filterable",i[o].filterable===!1?"false":"true"),t.setAttribute("data-feather-k-sortable",i[o].sortable===!1?"false":"true")}const a=t.dataset.featherKFilterable!=="false",f=t.dataset.featherKSortable!=="false";c.setAttribute("tabindex","-1"),a||f?t.style.cursor="pointer":t.style.cursor="default",a?(t.setAttribute("tabindex","0"),t.setAttribute("role","button"),t.setAttribute("aria-haspopup","menu")):(t.setAttribute("tabindex","0"),t.setAttribute("role","columnheader"),t.removeAttribute("aria-haspopup"));const m=b=>{b.target?.closest(".k-column-resizer")||(d.value=t,c.click())},T=b=>{if(a)d.value=t,b.preventDefault(),b.stopPropagation(),m(b);else if(f){d.value=t;const C=t.querySelector(".k-link");C&&C.click()}};t.addEventListener("click",T),E.push(()=>{t.removeEventListener("click",T)});const F=b=>{if((N(b)||b.code==="Space")&&(a||f)){if(b.preventDefault(),b.stopPropagation(),d.value=t,d.value.focus(),a)m(b);else if(f){const C=t.querySelector(".k-link");C&&C.click()}}};t.addEventListener("keydown",F,!0),E.push(()=>{t.removeEventListener("keydown",F,!0)}),t.dataset.featherKHeaderBound="true"});const n=document.querySelector(".k-grid-header .k-table-thead");if(n&&n.dataset.featherKHeaderBound!=="true"){const t=o=>{const c=o.target.closest("th");c&&(N(o)||o.code==="Space")&&c.dataset.featherKFilterable==="false"&&c.dataset.featherKSortable==="false"&&(o.preventDefault(),o.stopImmediatePropagation())};n.addEventListener("keydown",t,!0),E.push(()=>{n.removeEventListener("keydown",t,!0)}),n.dataset.featherKHeaderBound="true"}},S=function(e,n){const t=e?.event?.event.target,o=D();if(!t||!o)return;const c=t.classList.contains("k-grid-header-menu"),i=t.classList.contains("k-columnmenu-item"),a=o.find(f=>f.field===e?.event?.field)?.sortable&&!0;if(!c){if(i&&!a){e?.event?.sort?.filter(m=>m.field!==e?.event?.field);return}typeof n=="function"&&h.nextTick(()=>{d.value&&d.value.focus(),n(e),h.nextTick(()=>{x()})})}},L=()=>{if(!K)try{u(),p(),w(),K=!0}catch(e){console.error("initA11y failed:",e),g()}};return h.onBeforeUnmount(()=>{g()}),{activeFilterButton:d,handleGridKeyDown:s,handleSortChange:S,initA11y:L}},M=l=>!!l&&typeof l=="object"&&"filters"in l&&Array.isArray(l.filters),G=l=>!!l&&typeof l=="object"&&"field"in l,B=(l,k)=>!l||typeof l!="object"?!1:M(l)?l.filters.some(d=>B(d,k)):G(l)?l.field===k:!1,O=l=>k=>l.value?.filters&&l.value.filters.some(d=>B(d,k))?"active":"",H=["button","a","input","select","textarea",'[role="button"]','[role="checkbox"]','[role="link"]','[role="menuitem"]','[role="option"]',".k-checkbox",".k-button",".k-dropdown",".k-picker",".k-hierarchy-cell",".k-grid-header-menu",".k-column-menu"],U=l=>{const{onRowAction:k,gridRef:d,dataItems:K,ignoreSelectors:A=[],shouldIgnoreTarget:y,enableKeyboardAction:E=!0}=l,q=[...H,...A],v=(r,s)=>{if(!r)return!1;const u=q.join(",");return r.closest&&r.closest(u)?!0:y&&s?y(r,s):!1},X=r=>{const u=r?.closest?.(".k-table-row[data-grid-row-index]")?.getAttribute("data-grid-row-index");if(u==null)return;const p=parseInt(u,10);return Number.isNaN(p)?void 0:p},N=r=>{if(!r||typeof r.clientX!="number")return;const s=typeof window<"u"&&window.scrollX||0,u=typeof window<"u"&&window.scrollY||0,p=typeof window<"u"&&window.screenX||0,w=typeof window<"u"&&window.screenY||0;return{clientX:r.clientX,clientY:r.clientY,pageX:typeof r.pageX=="number"?r.pageX:r.clientX+s,pageY:typeof r.pageY=="number"?r.pageY:r.clientY+u,screenX:typeof r.screenX=="number"?r.screenX:r.clientX+p,screenY:typeof r.screenY=="number"?r.screenY:r.clientY+w}},D=r=>{if(!r||typeof r.getBoundingClientRect!="function")return;const s=r.getBoundingClientRect();if(!s)return;const u=s.left,p=s.bottom,w=typeof window<"u"&&window.scrollX||0,g=typeof window<"u"&&window.scrollY||0;return{clientX:u,clientY:p,pageX:u+w,pageY:p+g,screenX:u,screenY:p}},R=r=>{if(typeof window>"u")return!1;const s=window.getSelection();if(!s||s.isCollapsed||!s.toString().trim())return!1;if(r&&s.anchorNode&&s.focusNode){const u=r.closest(".k-table-row")||r.closest(".k-grid")||r,p=s.anchorNode.nodeType===Node.ELEMENT_NODE?s.anchorNode:s.anchorNode.parentElement,w=s.focusNode.nodeType===Node.ELEMENT_NODE?s.focusNode:s.focusNode.parentElement,g=!!(p&&u.contains(p)),x=!!(w&&u.contains(w));return g||x}return!0};return{handleRowClick:r=>{const s=r?.htmlEvent||r?.event||r?.nativeEvent||(r instanceof Event?r:void 0);if(s&&s.button!==void 0&&s.button!==0)return;const u=s?.target||r?.target;if(u&&v(u,s||r)||R(u))return;const p={dataItem:r.dataItem,rowIndex:r.rowIndex??X(u),field:r.field,event:s||r,triggerType:"click",ctrlKey:!!s?.ctrlKey,shiftKey:!!s?.shiftKey,metaKey:!!s?.metaKey,altKey:!!s?.altKey,coordinates:N(s)??D(u)};k(r.dataItem,p)},handleRowKeyDown:(r,s)=>{if(!E)return;const u=r.key||r.code;if(!(u==="Enter"||u==="NumpadEnter"||u===" "||u==="Space"||r.code==="Enter"||r.code==="NumpadEnter"||r.code==="Space"))return;const w=r.target;if(!w)return;const g=w.closest(".k-table-row[data-grid-row-index], .k-table-row");if(!g||v(w,r))return;r.preventDefault(),r.stopPropagation();const x=g.getAttribute("data-grid-row-index"),S=x!==null?parseInt(x,10):void 0;let L=s;if(L===void 0){const n=(Array.isArray(K)?K:K?.value)||d?.value?.props?.dataItems||d?.value?.dataItems;n&&S!==void 0&&S>=0&&S<n.length&&(L=n[S])}if(L===void 0)return;const e={dataItem:L,rowIndex:S,event:r,triggerType:"keyboard",ctrlKey:!!r.ctrlKey,shiftKey:!!r.shiftKey,metaKey:!!r.metaKey,altKey:!!r.altKey,coordinates:D(g)};k(L,e)},isIgnoredTarget:v}};exports.useGridA11y=P;exports.useGridActiveFilter=O;exports.useGridRowAction=U;
2
+ //# sourceMappingURL=useGridRowAction-Di25yeqb.cjs.map