@vizejs/ui 0.345.0 → 0.350.2

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 (96) hide show
  1. package/dist/{button-D7sM9Xmj.d.mts → button-CVX8UBXj.d.mts} +6 -7
  2. package/dist/{button-8BOlFJNu.mjs → button-DT8AKlds.mjs} +2 -2
  3. package/dist/button.d.mts +1 -1
  4. package/dist/button.mjs +1 -1
  5. package/dist/catalog.d.mts +541 -0
  6. package/dist/catalog.mjs +784 -0
  7. package/dist/{checkbox-B4y9GUen.mjs → checkbox-BgIvwjyC.mjs} +2 -2
  8. package/dist/{checkbox-DkwZFC80.d.mts → checkbox-DP3_ko7D.d.mts} +7 -8
  9. package/dist/checkbox.d.mts +1 -1
  10. package/dist/checkbox.mjs +1 -1
  11. package/dist/collection-B_dFZOHO.d.mts +185 -0
  12. package/dist/collection-eHN150dT.mjs +453 -0
  13. package/dist/collection.d.mts +2 -0
  14. package/dist/collection.mjs +2 -0
  15. package/dist/composite-navigation-QINW9GLy.d.mts +115 -0
  16. package/dist/composite-navigation-Skx3LZsv.mjs +354 -0
  17. package/dist/composite-navigation.d.mts +2 -0
  18. package/dist/composite-navigation.mjs +2 -0
  19. package/dist/controllable-state.d.mts +1 -1
  20. package/dist/dismissable-layer-4icKahVs.d.mts +119 -0
  21. package/dist/dismissable-layer-CwG1gtdz.mjs +488 -0
  22. package/dist/dismissable-layer.d.mts +2 -0
  23. package/dist/dismissable-layer.mjs +2 -0
  24. package/dist/focus-CAWi_I-0.mjs +318 -0
  25. package/dist/focus-K6dOifoZ.d.mts +112 -0
  26. package/dist/focus-guards-Bt0C-0qS.d.mts +81 -0
  27. package/dist/focus-guards-DhMPQAT8.mjs +299 -0
  28. package/dist/focus-guards.d.mts +2 -0
  29. package/dist/focus-guards.mjs +2 -0
  30. package/dist/focus-scope-CavhKWx0.d.mts +100 -0
  31. package/dist/focus-scope-D9U1aj67.mjs +340 -0
  32. package/dist/focus-scope-dom-DBwNEWxG.mjs +264 -0
  33. package/dist/focus-scope.d.mts +2 -0
  34. package/dist/focus-scope.mjs +2 -0
  35. package/dist/focus.d.mts +2 -0
  36. package/dist/focus.mjs +2 -0
  37. package/dist/hover-BXwwbSaO.d.mts +80 -0
  38. package/dist/hover.d.mts +2 -0
  39. package/dist/hover.mjs +197 -0
  40. package/dist/id-5OAUyVPd.d.mts +126 -0
  41. package/dist/id-mziSyAse.mjs +137 -0
  42. package/dist/id.d.mts +2 -0
  43. package/dist/id.mjs +2 -0
  44. package/dist/index.d.mts +23 -6
  45. package/dist/index.mjs +21 -5
  46. package/dist/inert-outside-CyqtoKQ4.mjs +346 -0
  47. package/dist/inert-outside-tuHRmaqJ.d.mts +43 -0
  48. package/dist/inert-outside.d.mts +2 -0
  49. package/dist/inert-outside.mjs +2 -0
  50. package/dist/interaction-modality-DOZK0BpZ.mjs +304 -0
  51. package/dist/interaction-modality-jgHKjPFB.d.mts +89 -0
  52. package/dist/interaction-modality.d.mts +2 -0
  53. package/dist/interaction-modality.mjs +2 -0
  54. package/dist/long-press-Dsul69gk.mjs +340 -0
  55. package/dist/long-press-cll6z2K9.d.mts +105 -0
  56. package/dist/long-press.d.mts +2 -0
  57. package/dist/long-press.mjs +2 -0
  58. package/dist/media-pdf.d.mts +1 -1
  59. package/dist/media-pdf.mjs +1 -1
  60. package/dist/media.d.mts +1 -1
  61. package/dist/media.mjs +1 -1
  62. package/dist/move-BOmXDYbh.d.mts +103 -0
  63. package/dist/move-BVvMHzxr.mjs +386 -0
  64. package/dist/move.d.mts +2 -0
  65. package/dist/move.mjs +2 -0
  66. package/dist/press-N_mE3Mgx.mjs +469 -0
  67. package/dist/press-event-Cd2OPpmw.mjs +149 -0
  68. package/dist/press-types-CNqStzJf.d.mts +112 -0
  69. package/dist/press.d.mts +14 -0
  70. package/dist/press.mjs +2 -0
  71. package/dist/{primitive-DJB7pOf3.mjs → primitive-BX31dogp.mjs} +2 -2
  72. package/dist/{primitive-BtvwikH1.d.mts → primitive-C8jW7DLd.d.mts} +3 -4
  73. package/dist/primitive.d.mts +1 -1
  74. package/dist/primitive.mjs +1 -1
  75. package/dist/scroll-lock-BgVtX4wT.d.mts +49 -0
  76. package/dist/scroll-lock-Ckbmm0-J.mjs +313 -0
  77. package/dist/scroll-lock.d.mts +2 -0
  78. package/dist/scroll-lock.mjs +2 -0
  79. package/dist/spatial-navigation-CHj9mDZH.d.mts +106 -0
  80. package/dist/spatial-navigation-D6-UDavU.mjs +301 -0
  81. package/dist/spatial-navigation.d.mts +2 -0
  82. package/dist/spatial-navigation.mjs +2 -0
  83. package/dist/typeahead-B7KDxE_u.mjs +175 -0
  84. package/dist/typeahead-BbgUB2Bj.d.mts +71 -0
  85. package/dist/typeahead.d.mts +2 -0
  86. package/dist/typeahead.mjs +2 -0
  87. package/dist/visually-hidden-BOqfqBFt.d.mts +17 -0
  88. package/dist/{visually-hidden-QENRapzW.mjs → visually-hidden-BwDOx5yf.mjs} +2 -2
  89. package/dist/visually-hidden.d.mts +1 -1
  90. package/dist/visually-hidden.mjs +1 -1
  91. package/package.json +99 -11
  92. package/dist/visually-hidden-BegtnMng.d.mts +0 -19
  93. /package/dist/{_plugin-vue_export-helper-BVN2DL-U.mjs → _plugin-vue_export-helper-BOaGB7Aw.mjs} +0 -0
  94. /package/dist/{controllable-state-DXsYJ3yl.d.mts → controllable-state-BtoxKahh.d.mts} +0 -0
  95. /package/dist/{pdf-source-COxN3A1l.d.mts → pdf-source-BYFK2Pks.d.mts} +0 -0
  96. /package/dist/{pdf-source-C52YE8tp.mjs → pdf-source-zu1LkCeE.mjs} +0 -0
@@ -0,0 +1,346 @@
1
+ import { getCurrentInstance, getCurrentScope, onMounted, onScopeDispose, shallowReadonly, shallowRef, toValue, watch } from "vue";
2
+ //#region src/inert-outside-internal.ts
3
+ const optionDiagnostic = "VIZE_UI_INERT_OUTSIDE_OPTION";
4
+ const rootDiagnostic = "VIZE_UI_INERT_OUTSIDE_ROOT";
5
+ function readRoot(source) {
6
+ const value = toValue(source);
7
+ if (value === void 0 || value === null) return null;
8
+ const candidate = value;
9
+ if (candidate.nodeType !== 1 || !candidate.ownerDocument) throw new TypeError(`${rootDiagnostic}: root must resolve to an Element or null`);
10
+ return value;
11
+ }
12
+ function readBranches(source, document) {
13
+ const value = toValue(source) ?? [];
14
+ if (!Array.isArray(value)) throw new TypeError(`${optionDiagnostic}: branches must resolve to an array of Elements`);
15
+ const unique = [...new Set(value)];
16
+ for (const branch of unique) {
17
+ const candidate = branch;
18
+ if (candidate.nodeType !== 1 || !candidate.ownerDocument) throw new TypeError(`${optionDiagnostic}: branches must contain only Elements`);
19
+ if (document && candidate.ownerDocument !== document) throw new TypeError(`${optionDiagnostic}: branches must share the root document`);
20
+ }
21
+ return unique;
22
+ }
23
+ function readEnabled(source) {
24
+ const value = toValue(source);
25
+ if (value === void 0) return true;
26
+ if (typeof value !== "boolean") throw new TypeError(`${optionDiagnostic}: enabled must resolve to a boolean`);
27
+ return value;
28
+ }
29
+ function readMode(source) {
30
+ const value = toValue(source) ?? "both";
31
+ if (value !== "aria-hidden" && value !== "both" && value !== "inert") throw new TypeError(`${optionDiagnostic}: mode must resolve to "aria-hidden", "both", or "inert"`);
32
+ return value;
33
+ }
34
+ function validateOptions(options) {
35
+ if (!options || typeof options !== "object" || Array.isArray(options)) throw new TypeError(`${optionDiagnostic}: options must be an object`);
36
+ const root = readRoot(options.root);
37
+ readBranches(options.branches, root?.ownerDocument ?? null);
38
+ readEnabled(options.enabled);
39
+ readMode(options.mode);
40
+ }
41
+ //#endregion
42
+ //#region src/inert-outside-dom.ts
43
+ const ignoredNames = new Set([
44
+ "link",
45
+ "meta",
46
+ "noscript",
47
+ "script",
48
+ "style",
49
+ "template"
50
+ ]);
51
+ function composedParent(element) {
52
+ if (element.assignedSlot) return element.assignedSlot;
53
+ if (element.parentElement) return element.parentElement;
54
+ return element.getRootNode().host ?? null;
55
+ }
56
+ function containsComposed(root, element) {
57
+ let current = element;
58
+ while (current) {
59
+ if (current === root || root.contains(current)) return true;
60
+ current = composedParent(current);
61
+ }
62
+ return false;
63
+ }
64
+ function renderedChildren(element) {
65
+ const slot = element;
66
+ if (slot.localName === "slot" && typeof slot.assignedElements === "function") {
67
+ const assigned = slot.assignedElements({ flatten: true });
68
+ if (assigned.length > 0) return assigned;
69
+ }
70
+ return Array.from(element.shadowRoot?.children ?? element.children);
71
+ }
72
+ /** Collect every currently reachable open shadow root for document-level observation. */
73
+ function collectOpenShadowRoots(root) {
74
+ const roots = [];
75
+ for (const element of root.querySelectorAll("*")) {
76
+ const shadowRoot = element.shadowRoot;
77
+ if (!shadowRoot) continue;
78
+ roots.push(shadowRoot, ...collectOpenShadowRoots(shadowRoot));
79
+ }
80
+ return roots;
81
+ }
82
+ function isInsideAllowed(element, allowed) {
83
+ return allowed.some((root) => root === element || containsComposed(root, element));
84
+ }
85
+ function containsAllowed(element, allowed) {
86
+ return allowed.some((root) => root === element || containsComposed(element, root));
87
+ }
88
+ /** Find the smallest rendered sibling subtrees outside every allowed root. */
89
+ function collectOutside(document, allowed) {
90
+ const body = document.body;
91
+ if (!body || allowed.some((root) => root === body || containsComposed(root, body))) return [];
92
+ const outside = [];
93
+ const visit = (container) => {
94
+ for (const child of renderedChildren(container)) {
95
+ if (ignoredNames.has(child.localName)) continue;
96
+ if (isInsideAllowed(child, allowed)) continue;
97
+ if (containsAllowed(child, allowed)) visit(child);
98
+ else outside.push(child);
99
+ }
100
+ };
101
+ visit(body);
102
+ return outside;
103
+ }
104
+ function maskFor(mode) {
105
+ return {
106
+ ariaHidden: mode === "aria-hidden" || mode === "both",
107
+ inert: mode === "inert" || mode === "both"
108
+ };
109
+ }
110
+ //#endregion
111
+ //#region src/inert-outside-stack.ts
112
+ const states = /* @__PURE__ */ new WeakMap();
113
+ const observationOptions = Object.freeze({
114
+ attributes: true,
115
+ attributeFilter: [
116
+ "aria-hidden",
117
+ "inert",
118
+ "name",
119
+ "slot"
120
+ ],
121
+ childList: true,
122
+ subtree: true
123
+ });
124
+ function stateFor(document) {
125
+ let state = states.get(document);
126
+ if (!state) {
127
+ state = {
128
+ document,
129
+ managed: /* @__PURE__ */ new Map(),
130
+ observer: null,
131
+ queued: false,
132
+ tokens: []
133
+ };
134
+ states.set(document, state);
135
+ }
136
+ return state;
137
+ }
138
+ function restore(state) {
139
+ for (const [element, snapshot] of state.managed) {
140
+ if (snapshot.ariaHidden === null) element.removeAttribute("aria-hidden");
141
+ else element.setAttribute("aria-hidden", snapshot.ariaHidden);
142
+ if (snapshot.inert) element.setAttribute("inert", "");
143
+ else element.removeAttribute("inert");
144
+ }
145
+ state.managed.clear();
146
+ }
147
+ function mergeMask(current, next) {
148
+ return {
149
+ ariaHidden: current?.ariaHidden === true || next.ariaHidden,
150
+ inert: current?.inert === true || next.inert
151
+ };
152
+ }
153
+ function refreshObservationRoots(state) {
154
+ const root = state.document.documentElement;
155
+ if (!state.observer || !root) return;
156
+ state.observer.disconnect();
157
+ state.observer.observe(root, observationOptions);
158
+ for (const shadowRoot of collectOpenShadowRoots(state.document)) state.observer.observe(shadowRoot, observationOptions);
159
+ }
160
+ function connectedRoots(token) {
161
+ if (!token.root?.isConnected || !token.readEnabled()) return [];
162
+ return [...new Set([token.root, ...token.readBranches()])].filter((root) => root.isConnected);
163
+ }
164
+ function recomputeDocument(state) {
165
+ const records = state.tokens.map((token) => ({
166
+ mode: token.readMode(),
167
+ roots: connectedRoots(token),
168
+ token
169
+ })).filter(({ roots }) => roots.length > 0);
170
+ const desired = /* @__PURE__ */ new Map();
171
+ for (let index = 0; index < records.length; index++) {
172
+ const record = records[index];
173
+ if (!record) continue;
174
+ const allowed = records.slice(index).flatMap(({ roots }) => roots);
175
+ const affected = collectOutside(state.document, allowed);
176
+ record.token.setAffected(Object.freeze(affected));
177
+ const mask = maskFor(record.mode);
178
+ for (const element of affected) desired.set(element, mergeMask(desired.get(element), mask));
179
+ }
180
+ for (const token of state.tokens) if (!records.some((record) => record.token === token)) token.setAffected(Object.freeze([]));
181
+ restore(state);
182
+ for (const [element, mask] of desired) {
183
+ state.managed.set(element, {
184
+ ariaHidden: element.getAttribute("aria-hidden"),
185
+ inert: element.hasAttribute("inert")
186
+ });
187
+ if (mask.ariaHidden) element.setAttribute("aria-hidden", "true");
188
+ if (mask.inert) element.setAttribute("inert", "");
189
+ }
190
+ state.observer?.takeRecords();
191
+ refreshObservationRoots(state);
192
+ }
193
+ function observe(state) {
194
+ if (state.observer) return;
195
+ const Observer = state.document.defaultView?.MutationObserver;
196
+ const root = state.document.documentElement;
197
+ if (!Observer || !root) return;
198
+ state.observer = new Observer(() => {
199
+ if (state.queued) return;
200
+ state.queued = true;
201
+ queueMicrotask(() => {
202
+ state.queued = false;
203
+ if (state.tokens.length > 0) recomputeDocument(state);
204
+ });
205
+ });
206
+ refreshObservationRoots(state);
207
+ }
208
+ function attachToken(token, root) {
209
+ if (token.document === root.ownerDocument) {
210
+ token.root = root;
211
+ recomputeDocument(stateFor(root.ownerDocument));
212
+ return;
213
+ }
214
+ detachToken(token);
215
+ token.document = root.ownerDocument;
216
+ token.root = root;
217
+ const state = stateFor(root.ownerDocument);
218
+ state.tokens.push(token);
219
+ observe(state);
220
+ recomputeDocument(state);
221
+ }
222
+ function detachToken(token) {
223
+ const document = token.document;
224
+ if (!document) {
225
+ token.root = null;
226
+ token.setAffected(Object.freeze([]));
227
+ return;
228
+ }
229
+ const state = stateFor(document);
230
+ const index = state.tokens.indexOf(token);
231
+ if (index >= 0) state.tokens.splice(index, 1);
232
+ token.document = null;
233
+ token.root = null;
234
+ token.setAffected(Object.freeze([]));
235
+ if (state.tokens.length === 0) {
236
+ state.observer?.disconnect();
237
+ state.observer = null;
238
+ restore(state);
239
+ states.delete(document);
240
+ } else recomputeDocument(state);
241
+ }
242
+ function refreshToken(token) {
243
+ if (token.document) recomputeDocument(stateFor(token.document));
244
+ }
245
+ //#endregion
246
+ //#region src/inert-outside.ts
247
+ const disposedDiagnostic = "VIZE_UI_INERT_OUTSIDE_DISPOSED";
248
+ const setupDiagnostic = "VIZE_UI_INERT_OUTSIDE_SETUP";
249
+ /** Isolate rendered content outside reactive allowed roots without owning visual styling. */
250
+ function createInertOutside(options) {
251
+ validateOptions(options);
252
+ const activeState = shallowRef(false);
253
+ const affectedState = shallowRef(Object.freeze([]));
254
+ const token = {
255
+ document: null,
256
+ root: null,
257
+ readBranches: () => readBranches(options.branches, token.document),
258
+ readEnabled: () => readEnabled(options.enabled),
259
+ readMode: () => readMode(options.mode),
260
+ setAffected: (elements) => {
261
+ affectedState.value = elements;
262
+ }
263
+ };
264
+ let disposed = false;
265
+ const assertAlive = () => {
266
+ if (disposed) throw new Error(`${disposedDiagnostic}: the controller has been disposed`);
267
+ };
268
+ const refresh = () => {
269
+ assertAlive();
270
+ if (!activeState.value) return;
271
+ const nextRoot = readRoot(options.root);
272
+ if (nextRoot !== token.root) if (nextRoot) {
273
+ readBranches(options.branches, nextRoot.ownerDocument);
274
+ readEnabled(options.enabled);
275
+ readMode(options.mode);
276
+ attachToken(token, nextRoot);
277
+ } else detachToken(token);
278
+ else {
279
+ readBranches(options.branches, token.document);
280
+ readEnabled(options.enabled);
281
+ readMode(options.mode);
282
+ refreshToken(token);
283
+ }
284
+ };
285
+ const activate = () => {
286
+ assertAlive();
287
+ if (activeState.value) return;
288
+ activeState.value = true;
289
+ try {
290
+ refresh();
291
+ } catch (error) {
292
+ activeState.value = false;
293
+ detachToken(token);
294
+ throw error;
295
+ }
296
+ };
297
+ const deactivate = () => {
298
+ assertAlive();
299
+ if (!activeState.value) return;
300
+ try {
301
+ detachToken(token);
302
+ } finally {
303
+ activeState.value = false;
304
+ }
305
+ };
306
+ const stopWatch = watch(() => {
307
+ const root = toValue(options.root);
308
+ const branches = toValue(options.branches) ?? [];
309
+ return [
310
+ root,
311
+ Array.isArray(branches) ? [...branches] : branches,
312
+ toValue(options.enabled),
313
+ toValue(options.mode)
314
+ ];
315
+ }, () => {
316
+ if (activeState.value) refresh();
317
+ }, { flush: "sync" });
318
+ return Object.freeze({
319
+ isActive: shallowReadonly(activeState),
320
+ affectedElements: shallowReadonly(affectedState),
321
+ activate,
322
+ deactivate,
323
+ refresh,
324
+ dispose: () => {
325
+ if (disposed) return;
326
+ try {
327
+ detachToken(token);
328
+ } finally {
329
+ activeState.value = false;
330
+ disposed = true;
331
+ stopWatch();
332
+ }
333
+ }
334
+ });
335
+ }
336
+ /** Create, mount-activate, and scope-dispose an outside-inerting controller. */
337
+ function useInertOutside(options) {
338
+ if (!getCurrentScope()) throw new Error(`${setupDiagnostic}: use inside component setup or an active effect scope`);
339
+ const controller = createInertOutside(options);
340
+ if (getCurrentInstance()) onMounted(controller.activate);
341
+ else controller.activate();
342
+ onScopeDispose(controller.dispose);
343
+ return controller;
344
+ }
345
+ //#endregion
346
+ export { useInertOutside as n, createInertOutside as t };
@@ -0,0 +1,43 @@
1
+ import { MaybeRefOrGetter, ShallowRef } from "vue";
2
+
3
+ //#region src/inert-outside-types.d.ts
4
+ /** Attribute strategy used to isolate content outside an allowed subtree. */
5
+ type InertOutsideMode = "aria-hidden" | "both" | "inert";
6
+ /** Reactive configuration for one document-level inerting layer. */
7
+ interface InertOutsideOptions {
8
+ /** Primary allowed root. It may be `null` during SSR and before mount. */
9
+ readonly root: MaybeRefOrGetter<Element | null | undefined>;
10
+ /** Additional portalled or exceptional roots that remain available. */
11
+ readonly branches?: MaybeRefOrGetter<readonly Element[] | undefined>;
12
+ /** Whether an activated controller currently isolates outside content. @default true */
13
+ readonly enabled?: MaybeRefOrGetter<boolean | undefined>;
14
+ /**
15
+ * Isolation attributes to apply. `aria-hidden` alone does not prevent focus or pointer input;
16
+ * reserve that mode for integrations that provide equivalent interaction blocking separately.
17
+ * @default "both"
18
+ */
19
+ readonly mode?: MaybeRefOrGetter<InertOutsideMode | undefined>;
20
+ }
21
+ /** Lifecycle and inspection interface returned by `createInertOutside`. */
22
+ interface InertOutsideController {
23
+ /** Whether this controller is activated, independently of reactive enablement. */
24
+ readonly isActive: Readonly<ShallowRef<boolean>>;
25
+ /** Elements selected by this layer during the latest recomputation. */
26
+ readonly affectedElements: Readonly<ShallowRef<readonly Element[]>>;
27
+ /** Join the document isolation stack. Safe to call repeatedly. */
28
+ readonly activate: () => void;
29
+ /** Leave the stack and restore every attribute owned by this layer. */
30
+ readonly deactivate: () => void;
31
+ /** Re-read roots and options after imperative DOM movement. */
32
+ readonly refresh: () => void;
33
+ /** Permanently release reactive and document-level ownership. */
34
+ readonly dispose: () => void;
35
+ }
36
+ //#endregion
37
+ //#region src/inert-outside.d.ts
38
+ /** Isolate rendered content outside reactive allowed roots without owning visual styling. */
39
+ declare function createInertOutside(options: InertOutsideOptions): InertOutsideController;
40
+ /** Create, mount-activate, and scope-dispose an outside-inerting controller. */
41
+ declare function useInertOutside(options: InertOutsideOptions): InertOutsideController;
42
+ //#endregion
43
+ export { InertOutsideOptions as a, InertOutsideMode as i, useInertOutside as n, InertOutsideController as r, createInertOutside as t };
@@ -0,0 +1,2 @@
1
+ import { a as InertOutsideOptions, i as InertOutsideMode, n as useInertOutside, r as InertOutsideController, t as createInertOutside } from "./inert-outside-tuHRmaqJ.mjs";
2
+ export { type InertOutsideController, type InertOutsideMode, type InertOutsideOptions, createInertOutside, useInertOutside };
@@ -0,0 +1,2 @@
1
+ import { n as useInertOutside, t as createInertOutside } from "./inert-outside-CyqtoKQ4.mjs";
2
+ export { createInertOutside, useInertOutside };
@@ -0,0 +1,304 @@
1
+ import { computed, getCurrentScope, isRef, onScopeDispose, shallowReadonly, shallowRef, toValue, watch } from "vue";
2
+ //#region src/interaction-modality-events.ts
3
+ const modifierKeys = new Set([
4
+ "Alt",
5
+ "AltGraph",
6
+ "Control",
7
+ "Meta"
8
+ ]);
9
+ /** Classify keyboard intent while excluding composition and modified shortcuts. */
10
+ function classifyKeyboardEvent(event) {
11
+ if (event.isComposing || event.altKey || event.ctrlKey || event.metaKey || modifierKeys.has(event.key)) return null;
12
+ return {
13
+ modality: "keyboard",
14
+ reason: "keyboard",
15
+ originalEvent: event
16
+ };
17
+ }
18
+ /** Map pointer hardware into the stable public modality vocabulary. */
19
+ function classifyPointerEvent(event) {
20
+ if (event.pointerId === -1 && event.pointerType === "") return {
21
+ modality: "virtual",
22
+ reason: "virtual",
23
+ originalEvent: event
24
+ };
25
+ const modality = event.pointerType === "touch" ? "touch" : "pointer";
26
+ return {
27
+ modality,
28
+ reason: modality,
29
+ originalEvent: event
30
+ };
31
+ }
32
+ /** Classify coordinate-free clicks used by keyboards and assistive technology. */
33
+ function classifyVirtualClick(event, current) {
34
+ if (event.detail !== 0 || current === "keyboard") return null;
35
+ return {
36
+ modality: "virtual",
37
+ reason: "virtual",
38
+ originalEvent: event
39
+ };
40
+ }
41
+ //#endregion
42
+ //#region src/interaction-modality-hub.ts
43
+ const hubs = /* @__PURE__ */ new WeakMap();
44
+ /** Install one capture listener set for all consumers in a document. */
45
+ function createHub(document, initialModality) {
46
+ const subscribers = /* @__PURE__ */ new Set();
47
+ const pendingUpdates = [];
48
+ let publishing = false;
49
+ const hub = {
50
+ subscribers,
51
+ modality: initialModality,
52
+ disposeListeners: () => void 0,
53
+ publish: () => false
54
+ };
55
+ hub.publish = (update) => {
56
+ if (hub.modality === update.modality && pendingUpdates.length === 0) return false;
57
+ pendingUpdates.push(update);
58
+ if (publishing) return true;
59
+ publishing = true;
60
+ const errors = [];
61
+ try {
62
+ while (pendingUpdates.length > 0) {
63
+ const nextUpdate = pendingUpdates.shift();
64
+ if (hub.modality === nextUpdate.modality) continue;
65
+ hub.modality = nextUpdate.modality;
66
+ for (const subscriber of Array.from(subscribers)) {
67
+ if (!subscribers.has(subscriber)) continue;
68
+ try {
69
+ subscriber(nextUpdate);
70
+ } catch (error) {
71
+ errors.push(error);
72
+ }
73
+ }
74
+ }
75
+ } finally {
76
+ publishing = false;
77
+ }
78
+ if (errors.length === 1) throw errors[0];
79
+ if (errors.length > 1) throw new AggregateError(errors, "Interaction modality subscribers failed");
80
+ return true;
81
+ };
82
+ let lastTouchTime = Number.NEGATIVE_INFINITY;
83
+ const onKeyDown = (event) => {
84
+ const update = classifyKeyboardEvent(event);
85
+ if (update) hub.publish(update);
86
+ };
87
+ const onPointerDown = (event) => hub.publish(classifyPointerEvent(event));
88
+ const onMouseDown = (event) => {
89
+ const elapsedSinceTouch = event.timeStamp - lastTouchTime;
90
+ if (elapsedSinceTouch >= 0 && elapsedSinceTouch < 800) return;
91
+ hub.publish({
92
+ modality: "pointer",
93
+ reason: "pointer",
94
+ originalEvent: event
95
+ });
96
+ };
97
+ const onTouchStart = (event) => {
98
+ lastTouchTime = event.timeStamp;
99
+ hub.publish({
100
+ modality: "touch",
101
+ reason: "touch",
102
+ originalEvent: event
103
+ });
104
+ };
105
+ const onClick = (event) => {
106
+ const update = classifyVirtualClick(event, hub.modality);
107
+ if (update) hub.publish(update);
108
+ };
109
+ document.addEventListener("keydown", onKeyDown, true);
110
+ document.addEventListener("click", onClick, true);
111
+ const supportsPointerEvents = Boolean(document.defaultView?.PointerEvent);
112
+ if (supportsPointerEvents) document.addEventListener("pointerdown", onPointerDown, true);
113
+ else {
114
+ document.addEventListener("mousedown", onMouseDown, true);
115
+ document.addEventListener("touchstart", onTouchStart, true);
116
+ }
117
+ hub.disposeListeners = () => {
118
+ document.removeEventListener("keydown", onKeyDown, true);
119
+ document.removeEventListener("click", onClick, true);
120
+ if (supportsPointerEvents) document.removeEventListener("pointerdown", onPointerDown, true);
121
+ else {
122
+ document.removeEventListener("mousedown", onMouseDown, true);
123
+ document.removeEventListener("touchstart", onTouchStart, true);
124
+ }
125
+ };
126
+ return hub;
127
+ }
128
+ /** Subscribe to shared document state and return an idempotent release callback. */
129
+ function subscribeToDocumentModality(document, initialModality, subscriber) {
130
+ let hub = hubs.get(document);
131
+ if (!hub) {
132
+ hub = createHub(document, initialModality);
133
+ hubs.set(document, hub);
134
+ }
135
+ hub.subscribers.add(subscriber);
136
+ let released = false;
137
+ return {
138
+ current: hub.modality,
139
+ release: () => {
140
+ if (released) return;
141
+ released = true;
142
+ hub?.subscribers.delete(subscriber);
143
+ if (hub?.subscribers.size === 0) {
144
+ hub.disposeListeners();
145
+ hubs.delete(document);
146
+ }
147
+ }
148
+ };
149
+ }
150
+ /** Synchronize an explicit consumer update with every peer in a document. */
151
+ function publishDocumentModality(document, update) {
152
+ return hubs.get(document)?.publish(update) ?? false;
153
+ }
154
+ //#endregion
155
+ //#region src/interaction-modality.ts
156
+ const disposedDiagnostic = "VIZE_UI_INTERACTION_MODALITY_DISPOSED";
157
+ const invalidDocumentDiagnostic = "VIZE_UI_INTERACTION_MODALITY_DOCUMENT";
158
+ const setupDiagnostic = "VIZE_UI_INTERACTION_MODALITY_SETUP";
159
+ const invalidValueDiagnostic = "VIZE_UI_INTERACTION_MODALITY_VALUE";
160
+ const modalities = new Set([
161
+ "keyboard",
162
+ "pointer",
163
+ "touch",
164
+ "virtual"
165
+ ]);
166
+ function defaultDocument() {
167
+ return typeof globalThis.document === "undefined" ? null : globalThis.document;
168
+ }
169
+ /** Accept cross-realm documents without relying on an unreliable instanceof check. */
170
+ function toDocument(value) {
171
+ if (value == null) return null;
172
+ if (value.nodeType !== 9 || typeof value.addEventListener !== "function" || typeof value.removeEventListener !== "function") throw new TypeError(`${invalidDocumentDiagnostic}: expected a Document, null, or undefined`);
173
+ return value;
174
+ }
175
+ /** Validate JavaScript callers without widening the closed TypeScript union. */
176
+ function toModality(value) {
177
+ if (value === null || modalities.has(value)) return value;
178
+ throw new TypeError(`${invalidValueDiagnostic}: expected keyboard, pointer, touch, virtual, or null`);
179
+ }
180
+ /**
181
+ * Create an SSR-safe, document-scoped interaction-modality observer.
182
+ *
183
+ * Trackers in one document share native capture listeners and state. Separate
184
+ * documents, including iframes, remain isolated. Call {@link InteractionModalityTracker.dispose}
185
+ * when using this factory outside a Vue effect scope.
186
+ */
187
+ function createInteractionModalityTracker(options = {}) {
188
+ const ownerDocument = shallowRef(null);
189
+ const modality = shallowRef(toModality(options.initialModality ?? null));
190
+ let releaseDocument = null;
191
+ let disposed = false;
192
+ const assertActive = () => {
193
+ if (disposed) throw new Error(`${disposedDiagnostic}: the tracker has been disposed`);
194
+ };
195
+ const apply = (update) => {
196
+ if (modality.value === update.modality) return false;
197
+ const previousModality = modality.value;
198
+ modality.value = update.modality;
199
+ const change = Object.freeze({
200
+ modality: update.modality,
201
+ previousModality,
202
+ reason: update.reason,
203
+ originalEvent: update.originalEvent,
204
+ document: ownerDocument.value
205
+ });
206
+ options.onChange?.(change);
207
+ return true;
208
+ };
209
+ const releaseCurrentDocument = () => {
210
+ const changed = ownerDocument.value !== null;
211
+ releaseDocument?.();
212
+ releaseDocument = null;
213
+ ownerDocument.value = null;
214
+ return changed;
215
+ };
216
+ const attach = (nextDocument) => {
217
+ assertActive();
218
+ const resolved = toDocument(nextDocument);
219
+ if (ownerDocument.value === resolved) return false;
220
+ releaseCurrentDocument();
221
+ if (!resolved) return true;
222
+ ownerDocument.value = resolved;
223
+ try {
224
+ const subscription = subscribeToDocumentModality(resolved, modality.value, apply);
225
+ releaseDocument = subscription.release;
226
+ if (subscription.current !== modality.value) apply({
227
+ modality: subscription.current,
228
+ reason: "document",
229
+ originalEvent: null
230
+ });
231
+ } catch (error) {
232
+ releaseCurrentDocument();
233
+ throw error;
234
+ }
235
+ return true;
236
+ };
237
+ const detach = () => {
238
+ assertActive();
239
+ return releaseCurrentDocument();
240
+ };
241
+ const setModality = (nextModality) => {
242
+ assertActive();
243
+ const update = {
244
+ modality: toModality(nextModality),
245
+ reason: "manual",
246
+ originalEvent: null
247
+ };
248
+ if (ownerDocument.value) return publishDocumentModality(ownerDocument.value, update);
249
+ return apply(update);
250
+ };
251
+ let stopDocumentWatch = () => void 0;
252
+ if (options.document === void 0) attach(defaultDocument());
253
+ else if (isRef(options.document) || typeof options.document === "function") stopDocumentWatch = watch(() => toValue(options.document), (nextDocument) => attach(toDocument(nextDocument)), {
254
+ flush: "sync",
255
+ immediate: true
256
+ });
257
+ else attach(toDocument(options.document));
258
+ const dispose = () => {
259
+ if (disposed) return;
260
+ stopDocumentWatch();
261
+ releaseCurrentDocument();
262
+ disposed = true;
263
+ };
264
+ return Object.freeze({
265
+ document: shallowReadonly(ownerDocument),
266
+ modality: shallowReadonly(modality),
267
+ isFocusVisible: computed(() => modality.value === "keyboard" || modality.value === "virtual"),
268
+ attach,
269
+ detach,
270
+ setModality,
271
+ dispose
272
+ });
273
+ }
274
+ /**
275
+ * Create a tracker owned by the current Vue effect scope.
276
+ *
277
+ * The tracker is disposed automatically when its component or effect scope is
278
+ * destroyed, preventing document-listener leaks.
279
+ */
280
+ function useInteractionModality(options = {}) {
281
+ if (!getCurrentScope()) throw new Error(`${setupDiagnostic}: use inside component setup or an active effect scope`);
282
+ const tracker = createInteractionModalityTracker(options);
283
+ onScopeDispose(tracker.dispose);
284
+ return tracker;
285
+ }
286
+ /**
287
+ * Determine whether a focused element should expose its focus indicator.
288
+ *
289
+ * Native `:focus-visible` semantics take precedence. The modality fallback is
290
+ * used only when the selector is unsupported, preserving text-input and
291
+ * platform heuristics implemented by the browser.
292
+ */
293
+ function isElementFocusVisible(element, modality) {
294
+ if (!element) return false;
295
+ const root = element.getRootNode();
296
+ if ((root && "activeElement" in root ? root.activeElement : element.ownerDocument.activeElement) !== element) return false;
297
+ try {
298
+ return element.matches(":focus-visible");
299
+ } catch {
300
+ return modality === "keyboard" || modality === "virtual";
301
+ }
302
+ }
303
+ //#endregion
304
+ export { isElementFocusVisible as n, useInteractionModality as r, createInteractionModalityTracker as t };