@featherk/composables 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.
- package/README.md +62 -15
- package/dist/address.cjs +2 -0
- package/dist/address.cjs.map +1 -0
- package/dist/address.mjs +6 -0
- package/dist/address.mjs.map +1 -0
- package/dist/chunks/useActiveIdRegistry-BG8VR__Z.mjs +43 -0
- package/dist/chunks/useActiveIdRegistry-BG8VR__Z.mjs.map +1 -0
- package/dist/chunks/useActiveIdRegistry-C1b00tqW.cjs +2 -0
- package/dist/chunks/useActiveIdRegistry-C1b00tqW.cjs.map +1 -0
- package/dist/chunks/useCompositeId-DyrJ0Fqj.cjs +2 -0
- package/dist/chunks/useCompositeId-DyrJ0Fqj.cjs.map +1 -0
- package/dist/chunks/useCompositeId-xg87ehXQ.mjs +9 -0
- package/dist/chunks/useCompositeId-xg87ehXQ.mjs.map +1 -0
- package/dist/chunks/useFieldsetValidationKit-7W1XDbbr.cjs +2 -0
- package/dist/chunks/useFieldsetValidationKit-7W1XDbbr.cjs.map +1 -0
- package/dist/chunks/useFieldsetValidationKit-x0Rh48mE.mjs +64 -0
- package/dist/chunks/useFieldsetValidationKit-x0Rh48mE.mjs.map +1 -0
- package/dist/chunks/useGridRowAction-Di25yeqb.cjs +2 -0
- package/dist/chunks/useGridRowAction-Di25yeqb.cjs.map +1 -0
- package/dist/chunks/useGridRowAction-akjwkV_F.mjs +532 -0
- package/dist/chunks/useGridRowAction-akjwkV_F.mjs.map +1 -0
- package/dist/chunks/useIntersectionObserver-A4xQV_pl.mjs +23 -0
- package/dist/chunks/useIntersectionObserver-A4xQV_pl.mjs.map +1 -0
- package/dist/chunks/useIntersectionObserver-CfgNAo2N.cjs +2 -0
- package/dist/chunks/useIntersectionObserver-CfgNAo2N.cjs.map +1 -0
- package/dist/chunks/useMaskedDateInput-COOksUM_.mjs +276 -0
- package/dist/chunks/useMaskedDateInput-COOksUM_.mjs.map +1 -0
- package/dist/chunks/useMaskedDateInput-wRnYlR3L.cjs +2 -0
- package/dist/chunks/useMaskedDateInput-wRnYlR3L.cjs.map +1 -0
- package/dist/chunks/useMaskedDateRangeInput-DST5aQe0.cjs +2 -0
- package/dist/chunks/useMaskedDateRangeInput-DST5aQe0.cjs.map +1 -0
- package/dist/chunks/useMaskedDateRangeInput-owYSg36r.mjs +328 -0
- package/dist/chunks/useMaskedDateRangeInput-owYSg36r.mjs.map +1 -0
- package/dist/chunks/useMaskedTimeInput-DPd2lysd.cjs +2 -0
- package/dist/chunks/useMaskedTimeInput-DPd2lysd.cjs.map +1 -0
- package/dist/chunks/useMaskedTimeInput-DjtBdvsf.mjs +363 -0
- package/dist/chunks/useMaskedTimeInput-DjtBdvsf.mjs.map +1 -0
- package/dist/chunks/usePopupMenu-CZLtgrXk.mjs +296 -0
- package/dist/chunks/usePopupMenu-CZLtgrXk.mjs.map +1 -0
- package/dist/chunks/usePopupMenu-DeTWakUH.cjs +2 -0
- package/dist/chunks/usePopupMenu-DeTWakUH.cjs.map +1 -0
- package/dist/chunks/usePopupTrap-DQudM6YA.cjs +2 -0
- package/dist/chunks/usePopupTrap-DQudM6YA.cjs.map +1 -0
- package/dist/chunks/usePopupTrap-DeuXHLZR.mjs +105 -0
- package/dist/chunks/usePopupTrap-DeuXHLZR.mjs.map +1 -0
- package/dist/chunks/useUSAddress-BYZZfbHc.cjs +2 -0
- package/dist/chunks/useUSAddress-BYZZfbHc.cjs.map +1 -0
- package/dist/chunks/useUSAddress-CRcqdqrZ.mjs +291 -0
- package/dist/chunks/useUSAddress-CRcqdqrZ.mjs.map +1 -0
- package/dist/chunks/useWindow-C4XFBuJA.mjs +210 -0
- package/dist/chunks/useWindow-C4XFBuJA.mjs.map +1 -0
- package/dist/chunks/useWindow-Yia2I6zX.cjs +2 -0
- package/dist/chunks/useWindow-Yia2I6zX.cjs.map +1 -0
- package/dist/date/useMaskedDateInput.d.ts +4 -0
- package/dist/date.cjs +2 -0
- package/dist/date.cjs.map +1 -0
- package/dist/date.mjs +5 -0
- package/dist/date.mjs.map +1 -0
- package/dist/{docs-manifest.js → docs-manifest.mjs} +24 -24
- package/dist/form.cjs +2 -0
- package/dist/form.cjs.map +1 -0
- package/dist/form.mjs +6 -0
- package/dist/form.mjs.map +1 -0
- package/dist/geometry.cjs +2 -0
- package/dist/geometry.cjs.map +1 -0
- package/dist/geometry.mjs +60 -0
- package/dist/geometry.mjs.map +1 -0
- package/dist/grid.cjs +2 -0
- package/dist/grid.cjs.map +1 -0
- package/dist/grid.mjs +7 -0
- package/dist/grid.mjs.map +1 -0
- package/dist/id.cjs +2 -0
- package/dist/id.cjs.map +1 -0
- package/dist/id.mjs +5 -0
- package/dist/id.mjs.map +1 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.mjs +37 -0
- package/dist/index.mjs.map +1 -0
- package/dist/menu.cjs +2 -0
- package/dist/menu.cjs.map +1 -0
- package/dist/menu.mjs +5 -0
- package/dist/menu.mjs.map +1 -0
- package/dist/observer.cjs +2 -0
- package/dist/observer.cjs.map +1 -0
- package/dist/observer.mjs +5 -0
- package/dist/observer.mjs.map +1 -0
- package/dist/range/useMaskedDateRangeInput.d.ts +4 -0
- package/dist/range.cjs +2 -0
- package/dist/range.cjs.map +1 -0
- package/dist/range.mjs +5 -0
- package/dist/range.mjs.map +1 -0
- package/dist/registry.cjs +2 -0
- package/dist/registry.cjs.map +1 -0
- package/dist/registry.mjs +6 -0
- package/dist/registry.mjs.map +1 -0
- package/dist/time/useMaskedTimeInput.d.ts +4 -0
- package/dist/time.cjs +2 -0
- package/dist/time.cjs.map +1 -0
- package/dist/time.mjs +5 -0
- package/dist/time.mjs.map +1 -0
- package/dist/trap.cjs +2 -0
- package/dist/trap.cjs.map +1 -0
- package/dist/trap.mjs +7 -0
- package/dist/trap.mjs.map +1 -0
- package/dist/window.cjs +2 -0
- package/dist/window.cjs.map +1 -0
- package/dist/window.mjs +6 -0
- package/dist/window.mjs.map +1 -0
- package/docs/date/useMaskedDateInput.md +3 -0
- package/docs/range/useMaskedDateRangeInput.md +6 -7
- package/docs/time/useMaskedTimeInput.md +3 -0
- package/package.json +38 -36
- package/dist/featherk-composables.es.js +0 -2628
- package/dist/featherk-composables.es.js.map +0 -1
- package/dist/featherk-composables.umd.js +0 -2
- package/dist/featherk-composables.umd.js.map +0 -1
- /package/dist/{docs-manifest.d.ts → docs-manifest.d.mts} +0 -0
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
|
214
|
+
npm run build -- --filter=@featherk/composables
|
|
176
215
|
```
|
|
177
216
|
|
|
178
|
-
|
|
217
|
+
Or run package-local:
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
npm run build
|
|
221
|
+
```
|
|
179
222
|
|
|
180
|
-
## Notes
|
|
223
|
+
## Maintainer Notes
|
|
181
224
|
|
|
182
|
-
- Every
|
|
183
|
-
-
|
|
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
|
|
package/dist/address.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"address.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":""}
|
package/dist/address.mjs
ADDED
|
@@ -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 @@
|
|
|
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
|