@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,453 @@
1
+ import { computed, getCurrentScope, onScopeDispose, shallowRef, toValue, watch } from "vue";
2
+ //#region src/collection-keys.ts
3
+ /** Reject keys that cannot serve as stable, serializable item identities. */
4
+ function validateCollectionKey(key) {
5
+ if (typeof key === "string") {
6
+ if (key.length === 0 || containsAsciiControl(key)) throw new Error("VIZE_UI_COLLECTION_KEY_VALUE: a string key must be non-empty and contain no ASCII controls");
7
+ return;
8
+ }
9
+ if (!Number.isSafeInteger(key) || Object.is(key, -0)) throw new Error("VIZE_UI_COLLECTION_KEY_VALUE: a numeric key must be a safe integer other than negative zero");
10
+ }
11
+ /** Whether a resolved item list still contains a key. */
12
+ function hasCollectionKey(items, key) {
13
+ return items.some((item) => item.key === key);
14
+ }
15
+ /**
16
+ * Pick the logical focus successor for a key that left the navigable set.
17
+ *
18
+ * The next navigable item wins, then the previous one, so removing or
19
+ * disabling the active item never orphans logical focus.
20
+ */
21
+ function recoverCollectionKey(removedKey, previousItems, nextItems) {
22
+ if (nextItems.length === 0) return null;
23
+ const nextKeys = new Set(nextItems.map((item) => item.key));
24
+ const previousIndex = previousItems.findIndex((item) => item.key === removedKey);
25
+ if (previousIndex < 0) return nextItems[0]?.key ?? null;
26
+ for (let index = previousIndex + 1; index < previousItems.length; index++) {
27
+ const key = previousItems[index]?.key;
28
+ if (key !== void 0 && nextKeys.has(key)) return key;
29
+ }
30
+ for (let index = previousIndex - 1; index >= 0; index--) {
31
+ const key = previousItems[index]?.key;
32
+ if (key !== void 0 && nextKeys.has(key)) return key;
33
+ }
34
+ return nextItems[0]?.key ?? null;
35
+ }
36
+ function containsAsciiControl(value) {
37
+ for (let index = 0; index < value.length; index++) {
38
+ const code = value.charCodeAt(index);
39
+ if (code <= 31 || code === 127) return true;
40
+ }
41
+ return false;
42
+ }
43
+ //#endregion
44
+ //#region src/collection-observer.ts
45
+ const orderAndTextMutations = {
46
+ attributeFilter: [
47
+ "alt",
48
+ "aria-hidden",
49
+ "aria-label",
50
+ "aria-labelledby",
51
+ "hidden",
52
+ "inert",
53
+ "title",
54
+ "type",
55
+ "value"
56
+ ],
57
+ attributes: true,
58
+ characterData: true,
59
+ childList: true,
60
+ subtree: true
61
+ };
62
+ const mountMutations = {
63
+ childList: true,
64
+ subtree: true
65
+ };
66
+ /**
67
+ * Observe DOM ordering and accessible text mutations for registered items.
68
+ *
69
+ * One observer is created per owning document. Connected items use their
70
+ * closest common ancestor and disconnected items are observed directly, so
71
+ * attribute and text noise from the rest of the document is never observed.
72
+ * Disconnected items additionally use a structure-only document observer that
73
+ * reports only once one of them mounts, because their insertion point is
74
+ * unknowable beforehand.
75
+ */
76
+ function observeCollectionMutations(items, onMutation) {
77
+ const elementsByDocument = /* @__PURE__ */ new Map();
78
+ for (const { element } of items) {
79
+ if (element === null) continue;
80
+ const elements = elementsByDocument.get(element.ownerDocument) ?? [];
81
+ elements.push(element);
82
+ elementsByDocument.set(element.ownerDocument, elements);
83
+ }
84
+ const observers = [];
85
+ for (const [ownerDocument, elements] of elementsByDocument) {
86
+ const MutationObserverConstructor = ownerDocument.defaultView?.MutationObserver;
87
+ if (MutationObserverConstructor === void 0) continue;
88
+ const observer = new MutationObserverConstructor(onMutation);
89
+ const connectedElements = elements.filter((element) => element.isConnected);
90
+ const disconnectedElements = elements.filter((element) => !element.isConnected);
91
+ const commonAncestor = connectedElements.length === elements.length ? findCollectionObservationRoot(connectedElements) : null;
92
+ const connectedRoot = commonAncestor === null ? findCollectionObservationRoot(connectedElements) : commonAncestor;
93
+ const targets = commonAncestor === null ? new Set([...connectedRoot === null ? connectedElements : [connectedRoot], ...disconnectedElements]) : new Set([commonAncestor]);
94
+ for (const target of targets) observer.observe(target, orderAndTextMutations);
95
+ observers.push(observer);
96
+ if (disconnectedElements.length === 0) continue;
97
+ const mountObserver = new MutationObserverConstructor(() => {
98
+ if (disconnectedElements.some((element) => element.isConnected)) onMutation();
99
+ });
100
+ mountObserver.observe(ownerDocument.documentElement, mountMutations);
101
+ observers.push(mountObserver);
102
+ }
103
+ return observers;
104
+ }
105
+ function findCollectionObservationRoot(elements) {
106
+ const firstElement = elements[0];
107
+ if (firstElement === void 0) return null;
108
+ let candidate = firstElement.parentNode ?? firstElement;
109
+ while (candidate !== null) {
110
+ if (elements.every((element) => candidate === element || candidate?.contains(element) === true)) return candidate;
111
+ candidate = candidate.parentNode;
112
+ }
113
+ return null;
114
+ }
115
+ //#endregion
116
+ //#region src/collection-text.ts
117
+ const ignoredTextTags = new Set([
118
+ "NOSCRIPT",
119
+ "SCRIPT",
120
+ "STYLE",
121
+ "TEMPLATE"
122
+ ]);
123
+ /**
124
+ * Normalize authored or extracted typeahead text without applying a locale.
125
+ *
126
+ * Unicode is normalized to NFC and every whitespace run becomes one ASCII
127
+ * space. Locale-sensitive equality remains the responsibility of a collator.
128
+ */
129
+ function normalizeCollectionTextValue(value) {
130
+ return value.normalize("NFC").trim().replace(/\s+/gu, " ");
131
+ }
132
+ /**
133
+ * Extract a practical accessible text value from an item element.
134
+ *
135
+ * `aria-labelledby` and `aria-label` take precedence, hidden descendants are
136
+ * excluded unless explicitly referenced, and image/input fallbacks are
137
+ * included. Complex widgets should still provide `textValue` explicitly when
138
+ * their typeahead label differs from their accessible name.
139
+ */
140
+ function extractCollectionTextValue(element) {
141
+ return normalizeCollectionTextValue(readElementText(element, /* @__PURE__ */ new Set(), false));
142
+ }
143
+ /** Whether a candidate text starts with a query under locale-aware comparison. */
144
+ function collectionTextStartsWith(candidate, query, collator) {
145
+ let codeUnitLength = 0;
146
+ for (const character of candidate) {
147
+ codeUnitLength += character.length;
148
+ if (collator.compare(candidate.slice(0, codeUnitLength), query) === 0) return true;
149
+ }
150
+ return false;
151
+ }
152
+ function readElementText(element, visited, referenced) {
153
+ if (visited.has(element)) return "";
154
+ visited.add(element);
155
+ if (!referenced && isCollectionTextHidden(element)) return "";
156
+ const labelledBy = element.getAttribute("aria-labelledby");
157
+ if (labelledBy !== null) {
158
+ const root = element.getRootNode();
159
+ const referenceRoot = hasGetElementById(root) ? root : element.ownerDocument;
160
+ const labelledText = labelledBy.split(/\s+/u).filter((id) => id.length > 0).map((id) => referenceRoot.getElementById(id)).filter((label) => label !== null).map((label) => readElementText(label, visited, true)).join(" ");
161
+ if (normalizeCollectionTextValue(labelledText).length > 0) return labelledText;
162
+ }
163
+ const ariaLabel = element.getAttribute("aria-label");
164
+ if (ariaLabel !== null && normalizeCollectionTextValue(ariaLabel).length > 0) return ariaLabel;
165
+ const tagName = element.tagName.toUpperCase();
166
+ if (tagName === "IMG" || tagName === "AREA") {
167
+ const alternative = element.getAttribute("alt");
168
+ if (alternative !== null) return alternative;
169
+ }
170
+ if (tagName === "INPUT") {
171
+ const type = element.getAttribute("type")?.toLowerCase();
172
+ if (type === "button" || type === "reset" || type === "submit") {
173
+ const value = element.value;
174
+ if (value.length > 0) return value;
175
+ }
176
+ }
177
+ if (!ignoredTextTags.has(tagName)) {
178
+ const fragments = [];
179
+ for (const child of element.childNodes) if (child.nodeType === 3) fragments.push(child.nodeValue ?? "");
180
+ else if (child.nodeType === 1) fragments.push(readElementText(child, visited, referenced));
181
+ const content = fragments.join("");
182
+ if (normalizeCollectionTextValue(content).length > 0) return content;
183
+ }
184
+ return element.getAttribute("title") ?? "";
185
+ }
186
+ function hasGetElementById(node) {
187
+ return "getElementById" in node && typeof node.getElementById === "function";
188
+ }
189
+ function isCollectionTextHidden(element) {
190
+ return element.hasAttribute("hidden") || element.hasAttribute("inert") || element.getAttribute("aria-hidden")?.trim().toLowerCase() === "true";
191
+ }
192
+ //#endregion
193
+ //#region src/collection-order.ts
194
+ const documentPositionDisconnected = 1;
195
+ const documentPositionPreceding = 2;
196
+ const documentPositionFollowing = 4;
197
+ /** Resolve every reactive source of one record into an immutable snapshot. */
198
+ function resolveCollectionRecord(record) {
199
+ const { input } = record;
200
+ const element = input.element === void 0 ? null : toValue(input.element) ?? null;
201
+ const explicitText = input.textValue === void 0 ? void 0 : toValue(input.textValue);
202
+ const order = input.order === void 0 ? void 0 : toValue(input.order);
203
+ if (order !== void 0 && !Number.isSafeInteger(order)) throw new Error(`VIZE_UI_COLLECTION_ORDER_VALUE: ${String(input.key)} requires a safe integer order`);
204
+ return {
205
+ sequence: record.sequence,
206
+ item: Object.freeze({
207
+ key: input.key,
208
+ value: input.value,
209
+ element,
210
+ textValue: explicitText === null || explicitText === void 0 ? element === null ? "" : extractCollectionTextValue(element) : normalizeCollectionTextValue(explicitText),
211
+ disabled: input.disabled === void 0 ? false : Boolean(toValue(input.disabled)),
212
+ order
213
+ })
214
+ };
215
+ }
216
+ /**
217
+ * Sort resolved records in place using explicit order, DOM order, or
218
+ * registration order, rejecting ambiguous mixes of explicit orders.
219
+ */
220
+ function sortCollectionRecords(records) {
221
+ const explicitOrderCount = records.filter(({ item }) => item.order !== void 0).length;
222
+ if (explicitOrderCount > 0 && explicitOrderCount !== records.length) throw new Error("VIZE_UI_COLLECTION_ORDER_PARTIAL: every item must provide order when explicit ordering is used");
223
+ if (explicitOrderCount === records.length && records.length > 0) {
224
+ const seenOrders = /* @__PURE__ */ new Set();
225
+ for (const { item } of records) {
226
+ const order = item.order;
227
+ if (order === void 0) continue;
228
+ if (seenOrders.has(order)) throw new Error(`VIZE_UI_COLLECTION_ORDER_DUPLICATE: order ${String(order)} is registered more than once`);
229
+ seenOrders.add(order);
230
+ }
231
+ records.sort((left, right) => (left.item.order ?? 0) - (right.item.order ?? 0));
232
+ return;
233
+ }
234
+ const ownerDocument = records[0]?.item.element?.ownerDocument;
235
+ if (!(ownerDocument !== void 0 && records.every(({ item }) => item.element !== null && item.element.isConnected && item.element.ownerDocument === ownerDocument))) {
236
+ records.sort((left, right) => left.sequence - right.sequence);
237
+ return;
238
+ }
239
+ records.sort((left, right) => {
240
+ const leftElement = left.item.element;
241
+ const rightElement = right.item.element;
242
+ if (leftElement !== null && rightElement !== null && leftElement !== rightElement) {
243
+ const position = leftElement.compareDocumentPosition(rightElement);
244
+ if ((position & documentPositionDisconnected) === 0) {
245
+ if ((position & documentPositionFollowing) !== 0) return -1;
246
+ if ((position & documentPositionPreceding) !== 0) return 1;
247
+ }
248
+ }
249
+ return left.sequence - right.sequence;
250
+ });
251
+ }
252
+ //#endregion
253
+ //#region src/collection-registry.ts
254
+ /**
255
+ * Create an SSR-safe, mutation-aware registry for one compound collection.
256
+ *
257
+ * The registry never moves DOM focus itself. Consumers bind `activeKey` to
258
+ * roving tabindex or `aria-activedescendant`; synchronous recovery guarantees
259
+ * that removing or disabling the active item selects the next navigable item,
260
+ * then the previous one, before the mutation can leave focus state orphaned.
261
+ */
262
+ function createCollectionRegistry(options = {}) {
263
+ const disabledBehavior = options.disabledBehavior ?? "skip";
264
+ const collator = options.collator ?? new Intl.Collator(void 0, {
265
+ sensitivity: "base",
266
+ usage: "search"
267
+ });
268
+ const records = /* @__PURE__ */ new Map();
269
+ const revision = shallowRef(0);
270
+ const activeKeyState = shallowRef(null);
271
+ const activeKey = computed(() => activeKeyState.value);
272
+ let sequence = 0;
273
+ let disposed = false;
274
+ let mutationObservers = [];
275
+ let observedElements;
276
+ let observedConnected = [];
277
+ const items = computed(() => {
278
+ revision.value;
279
+ const resolved = [...records.values()].map(resolveCollectionRecord);
280
+ sortCollectionRecords(resolved);
281
+ return Object.freeze(resolved.map(({ item }) => item));
282
+ });
283
+ const navigableItems = computed(() => {
284
+ const resolved = items.value.filter((item) => disabledBehavior === "focusable" || !item.disabled);
285
+ return Object.freeze(resolved);
286
+ });
287
+ const updateActiveKey = (key, reason) => {
288
+ const previousKey = activeKeyState.value;
289
+ if (previousKey === key) return false;
290
+ activeKeyState.value = key;
291
+ options.onActiveKeyChange?.(Object.freeze({
292
+ key,
293
+ previousKey,
294
+ reason
295
+ }));
296
+ return true;
297
+ };
298
+ const stopRecovery = watch(navigableItems, (nextItems, previousItems) => {
299
+ const activeKey = activeKeyState.value;
300
+ if (activeKey === null || hasCollectionKey(nextItems, activeKey)) return;
301
+ const reason = hasCollectionKey(items.value, activeKey) ? "item-disabled" : "item-removed";
302
+ updateActiveKey(recoverCollectionKey(activeKey, previousItems, nextItems), reason);
303
+ }, { flush: "sync" });
304
+ const stopObservation = watch(items, (nextItems) => {
305
+ const nextElements = nextItems.map(({ element }) => element);
306
+ const nextConnected = nextElements.map((element) => element?.isConnected === true);
307
+ if (observedElements !== void 0 && observedElements.length === nextElements.length && observedElements.every((element, index) => element === nextElements[index] && observedConnected[index] === nextConnected[index])) return;
308
+ for (const observer of mutationObservers) observer.disconnect();
309
+ mutationObservers = observeCollectionMutations(nextItems, () => {
310
+ if (!disposed) revision.value += 1;
311
+ });
312
+ observedElements = nextElements;
313
+ observedConnected = nextConnected;
314
+ }, {
315
+ flush: "sync",
316
+ immediate: true
317
+ });
318
+ const assertMutable = () => {
319
+ if (disposed) throw new Error("VIZE_UI_COLLECTION_DISPOSED: a disposed registry cannot be mutated");
320
+ };
321
+ const refresh = () => {
322
+ assertMutable();
323
+ revision.value += 1;
324
+ };
325
+ const getItem = (key) => items.value.find((item) => item.key === key);
326
+ const setActiveKey = (key) => {
327
+ assertMutable();
328
+ if (key === null) return updateActiveKey(null, "programmatic");
329
+ const item = getItem(key);
330
+ if (item === void 0) throw new Error(`VIZE_UI_COLLECTION_KEY_MISSING: no item is registered for ${String(key)}`);
331
+ if (disabledBehavior === "skip" && item.disabled) throw new Error(`VIZE_UI_COLLECTION_KEY_DISABLED: ${String(key)} is not navigable`);
332
+ return updateActiveKey(key, "programmatic");
333
+ };
334
+ const getNavigationKey = (direction, navigationOptions = {}) => {
335
+ const candidates = navigableItems.value;
336
+ if (candidates.length === 0) return null;
337
+ if (direction === "first") return candidates[0]?.key ?? null;
338
+ if (direction === "last") return candidates.at(-1)?.key ?? null;
339
+ const fromKey = navigationOptions.fromKey === void 0 ? activeKeyState.value : navigationOptions.fromKey;
340
+ if (fromKey === null) return direction === "next" ? candidates[0]?.key ?? null : candidates.at(-1)?.key ?? null;
341
+ const candidateIndex = candidates.findIndex((item) => item.key === fromKey);
342
+ if (candidateIndex >= 0) {
343
+ const target = candidates[candidateIndex + (direction === "next" ? 1 : -1)];
344
+ if (target !== void 0) return target.key;
345
+ if (navigationOptions.loop === true) return direction === "next" ? candidates[0]?.key ?? null : candidates.at(-1)?.key ?? null;
346
+ return fromKey;
347
+ }
348
+ const allItems = items.value;
349
+ const itemIndex = allItems.findIndex((item) => item.key === fromKey);
350
+ if (itemIndex >= 0) {
351
+ const step = direction === "next" ? 1 : -1;
352
+ for (let index = itemIndex + step; index >= 0 && index < allItems.length; index += step) {
353
+ const candidate = allItems[index];
354
+ if (candidate !== void 0 && hasCollectionKey(candidates, candidate.key)) return candidate.key;
355
+ }
356
+ if (navigationOptions.loop !== true) return null;
357
+ }
358
+ return direction === "next" ? candidates[0]?.key ?? null : candidates.at(-1)?.key ?? null;
359
+ };
360
+ const moveActive = (direction, navigationOptions = {}) => {
361
+ assertMutable();
362
+ const target = getNavigationKey(direction, navigationOptions);
363
+ if (target !== null) updateActiveKey(target, "navigation");
364
+ return target;
365
+ };
366
+ const findByTextValue = (query, searchOptions = {}) => {
367
+ const normalizedQuery = normalizeCollectionTextValue(query);
368
+ if (normalizedQuery.length === 0) return null;
369
+ const candidates = navigableItems.value;
370
+ if (candidates.length === 0) return null;
371
+ const fromKey = searchOptions.fromKey === void 0 ? activeKeyState.value : searchOptions.fromKey;
372
+ const fromIndex = fromKey === null ? -1 : candidates.findIndex((candidate) => candidate.key === fromKey);
373
+ const after = candidates.slice(fromIndex + 1);
374
+ const before = searchOptions.loop === false ? [] : candidates.slice(0, fromIndex + 1);
375
+ const searchCandidates = [...after, ...before];
376
+ const searchCollator = searchOptions.collator ?? collator;
377
+ const match = searchOptions.match ?? "prefix";
378
+ for (const candidate of searchCandidates) {
379
+ if (candidate.textValue.length === 0) continue;
380
+ if (match === "exact" ? searchCollator.compare(candidate.textValue, normalizedQuery) === 0 : collectionTextStartsWith(candidate.textValue, normalizedQuery, searchCollator)) return candidate.key;
381
+ }
382
+ return null;
383
+ };
384
+ const moveActiveByTextValue = (query, searchOptions = {}) => {
385
+ assertMutable();
386
+ const target = findByTextValue(query, searchOptions);
387
+ if (target !== null) updateActiveKey(target, "typeahead");
388
+ return target;
389
+ };
390
+ const register = (input) => {
391
+ assertMutable();
392
+ validateCollectionKey(input.key);
393
+ if (records.has(input.key)) throw new Error(`VIZE_UI_COLLECTION_KEY_DUPLICATE: ${String(input.key)} is already registered`);
394
+ const record = {
395
+ input: Object.freeze({ ...input }),
396
+ sequence: sequence++
397
+ };
398
+ records.set(input.key, record);
399
+ try {
400
+ revision.value += 1;
401
+ } catch (error) {
402
+ records.delete(input.key);
403
+ revision.value += 1;
404
+ throw error;
405
+ }
406
+ const unregister = () => {
407
+ if (records.get(input.key) !== record) return false;
408
+ records.delete(input.key);
409
+ revision.value += 1;
410
+ return true;
411
+ };
412
+ const registration = Object.freeze({
413
+ key: input.key,
414
+ registered: computed(() => {
415
+ revision.value;
416
+ return records.get(input.key) === record;
417
+ }),
418
+ unregister
419
+ });
420
+ if (getCurrentScope() !== void 0) onScopeDispose(unregister);
421
+ return registration;
422
+ };
423
+ const dispose = () => {
424
+ if (disposed) return false;
425
+ disposed = true;
426
+ stopRecovery();
427
+ stopObservation();
428
+ for (const observer of mutationObservers) observer.disconnect();
429
+ mutationObservers = [];
430
+ records.clear();
431
+ revision.value += 1;
432
+ updateActiveKey(null, "registry-disposed");
433
+ return true;
434
+ };
435
+ const registry = Object.freeze({
436
+ items,
437
+ navigableItems,
438
+ activeKey,
439
+ register,
440
+ getItem,
441
+ refresh,
442
+ setActiveKey,
443
+ getNavigationKey,
444
+ moveActive,
445
+ findByTextValue,
446
+ moveActiveByTextValue,
447
+ dispose
448
+ });
449
+ if (getCurrentScope() !== void 0) onScopeDispose(dispose);
450
+ return registry;
451
+ }
452
+ //#endregion
453
+ export { extractCollectionTextValue as n, normalizeCollectionTextValue as r, createCollectionRegistry as t };
@@ -0,0 +1,2 @@
1
+ import { a as CollectionActiveChangeReason, c as CollectionItemInput, d as CollectionNavigationOptions, f as CollectionRegistration, h as CollectionTextSearchOptions, i as CollectionActiveChange, l as CollectionKey, m as CollectionRegistryOptions, n as normalizeCollectionTextValue, o as CollectionDisabledBehavior, p as CollectionRegistry, r as createCollectionRegistry, s as CollectionItem, t as extractCollectionTextValue, u as CollectionNavigationDirection } from "./collection-B_dFZOHO.mjs";
2
+ export { type CollectionActiveChange, type CollectionActiveChangeReason, type CollectionDisabledBehavior, type CollectionItem, type CollectionItemInput, type CollectionKey, type CollectionNavigationDirection, type CollectionNavigationOptions, type CollectionRegistration, type CollectionRegistry, type CollectionRegistryOptions, type CollectionTextSearchOptions, createCollectionRegistry, extractCollectionTextValue, normalizeCollectionTextValue };
@@ -0,0 +1,2 @@
1
+ import { n as extractCollectionTextValue, r as normalizeCollectionTextValue, t as createCollectionRegistry } from "./collection-eHN150dT.mjs";
2
+ export { createCollectionRegistry, extractCollectionTextValue, normalizeCollectionTextValue };
@@ -0,0 +1,115 @@
1
+ import { l as CollectionKey, p as CollectionRegistry, s as CollectionItem } from "./collection-B_dFZOHO.mjs";
2
+ import { a as TypeaheadOptions, r as TypeaheadController } from "./typeahead-BbgUB2Bj.mjs";
3
+ import { ComputedRef, MaybeRefOrGetter } from "vue";
4
+
5
+ //#region src/composite-navigation-types.d.ts
6
+ /** Physical axes recognized by composite arrow-key navigation. */
7
+ type CompositeOrientation = "both" | "horizontal" | "vertical";
8
+ /** Reading direction used to map horizontal arrows. */
9
+ type CompositeDirection = "ltr" | "rtl";
10
+ /** DOM focus-management strategy described by the WAI-ARIA APG. */
11
+ type CompositeFocusStrategy = "active-descendant" | "roving";
12
+ /** Logical movement requested from a composite controller. */
13
+ type CompositeNavigationIntent = "first" | "focus" | "last" | "next" | "page-next" | "page-previous" | "pointer" | "previous" | "typeahead";
14
+ /** Imperative and keyboard movement commands accepted by `navigate`. */
15
+ type CompositeNavigationCommand = Exclude<CompositeNavigationIntent, "focus" | "pointer" | "typeahead">;
16
+ /** Immutable snapshot published after logical active state changes. */
17
+ interface CompositeNavigationChange<Key extends CollectionKey> {
18
+ /** Newly active item. */
19
+ readonly key: Key;
20
+ /** Active item before navigation. */
21
+ readonly previousKey: Key | null;
22
+ /** Navigation operation that selected this item. */
23
+ readonly intent: CompositeNavigationIntent;
24
+ /** Native event responsible for navigation, or `null` for imperative calls. */
25
+ readonly originalEvent: Event | null;
26
+ /** Focus-management strategy used while synchronizing the DOM. */
27
+ readonly focusStrategy: CompositeFocusStrategy;
28
+ }
29
+ /** Shared options for both composite focus-management strategies. */
30
+ interface CompositeNavigationBaseOptions<Key extends CollectionKey, Value> {
31
+ /** Ordered, mutation-aware logical item registry. */
32
+ readonly registry: CollectionRegistry<Key, Value>;
33
+ /**
34
+ * Arrow-key axes recognized by this composite.
35
+ *
36
+ * @default "vertical"
37
+ */
38
+ readonly orientation?: MaybeRefOrGetter<CompositeOrientation | undefined>;
39
+ /**
40
+ * Reading direction used by horizontal arrows.
41
+ *
42
+ * @default "ltr"
43
+ */
44
+ readonly direction?: MaybeRefOrGetter<CompositeDirection | undefined>;
45
+ /** Wrap arrow navigation at collection boundaries. */
46
+ readonly loop?: MaybeRefOrGetter<boolean | undefined>;
47
+ /** Suppress keyboard and pointer navigation while retaining active state. */
48
+ readonly isDisabled?: MaybeRefOrGetter<boolean | undefined>;
49
+ /** Number of logical items traversed by PageUp and PageDown. */
50
+ readonly pageSize?: MaybeRefOrGetter<number | undefined>;
51
+ /** Optional buffered typeahead integrated into the same active state. */
52
+ readonly typeahead?: false | Omit<TypeaheadOptions<Key, Value>, "registry">;
53
+ /** Called after the active item and DOM focus representation are synchronized. */
54
+ readonly onNavigate?: (change: CompositeNavigationChange<Key>) => void;
55
+ /**
56
+ * Custom visibility policy for virtualized or scroll-managed collections.
57
+ * The default calls `scrollIntoView({block: "nearest", inline: "nearest"})`
58
+ * when the method exists and DOM focus did not already scroll the item.
59
+ */
60
+ readonly scrollIntoView?: (item: CollectionItem<Key, Value>, originalEvent: Event | null) => void;
61
+ }
62
+ /** Roving-tabindex options. */
63
+ interface RovingNavigationOptions<Key extends CollectionKey, Value> extends CompositeNavigationBaseOptions<Key, Value> {
64
+ /** @default "roving" */
65
+ readonly focusStrategy?: "roving";
66
+ /** Avoid browser scrolling when DOM focus moves; custom scrolling still runs. */
67
+ readonly preventScroll?: MaybeRefOrGetter<boolean | undefined>;
68
+ /** Optional stable ID projection for item props. */
69
+ readonly getItemId?: (item: CollectionItem<Key, Value>) => string | undefined;
70
+ }
71
+ /** `aria-activedescendant` options with a required SSR-stable ID projection. */
72
+ interface ActiveDescendantNavigationOptions<Key extends CollectionKey, Value> extends CompositeNavigationBaseOptions<Key, Value> {
73
+ readonly focusStrategy: "active-descendant";
74
+ /** Resolve the stable DOM ID referenced by `aria-activedescendant`. */
75
+ readonly getItemId: (item: CollectionItem<Key, Value>) => string;
76
+ }
77
+ /** Closed, strategy-discriminated composite configuration. */
78
+ type CompositeNavigationOptions<Key extends CollectionKey, Value> = ActiveDescendantNavigationOptions<Key, Value> | RovingNavigationOptions<Key, Value>;
79
+ /** Dynamic props for the composite container. */
80
+ interface CompositeContainerProps {
81
+ readonly "aria-activedescendant"?: string;
82
+ readonly tabindex?: 0;
83
+ readonly onFocus: (event: FocusEvent) => void;
84
+ readonly onKeydown: (event: KeyboardEvent) => void;
85
+ }
86
+ /** Dynamic props for one registered composite item. */
87
+ interface CompositeItemProps {
88
+ readonly id?: string;
89
+ readonly tabindex?: -1 | 0;
90
+ readonly onFocus: (event: FocusEvent) => void;
91
+ readonly onPointerdown: (event: PointerEvent) => void;
92
+ }
93
+ /** Focus and keyboard adapter layered over a collection registry. */
94
+ interface CompositeNavigationController<Key extends CollectionKey> {
95
+ /** Registry-owned logical active key. */
96
+ readonly activeKey: ComputedRef<Key | null>;
97
+ /** Integrated typeahead controller, or `null` when typeahead is disabled. */
98
+ readonly typeahead: TypeaheadController<Key> | null;
99
+ /** Resolve current dynamic container attributes and stable handlers. */
100
+ readonly getContainerProps: () => Readonly<CompositeContainerProps>;
101
+ /** Resolve current dynamic item attributes and stable key-owned handlers. */
102
+ readonly getItemProps: (key: Key) => Readonly<CompositeItemProps>;
103
+ /** Move logical focus and synchronize the configured DOM representation. */
104
+ readonly navigate: (intent: CompositeNavigationCommand, originalEvent?: Event | null) => Key | null;
105
+ /** Release typeahead timers and cached handlers without disposing the registry. */
106
+ readonly dispose: () => void;
107
+ }
108
+ //#endregion
109
+ //#region src/composite-navigation.d.ts
110
+ /** Create an SSR-safe roving-tabindex or active-descendant collection adapter. */
111
+ declare function createCompositeNavigation<Key extends CollectionKey, Value>(options: CompositeNavigationOptions<Key, Value>): CompositeNavigationController<Key>;
112
+ /** Create a composite adapter disposed with the current Vue effect scope. */
113
+ declare function useCompositeNavigation<Key extends CollectionKey, Value>(options: CompositeNavigationOptions<Key, Value>): CompositeNavigationController<Key>;
114
+ //#endregion
115
+ export { CompositeDirection as a, CompositeNavigationBaseOptions as c, CompositeNavigationController as d, CompositeNavigationIntent as f, RovingNavigationOptions as h, CompositeContainerProps as i, CompositeNavigationChange as l, CompositeOrientation as m, useCompositeNavigation as n, CompositeFocusStrategy as o, CompositeNavigationOptions as p, ActiveDescendantNavigationOptions as r, CompositeItemProps as s, createCompositeNavigation as t, CompositeNavigationCommand as u };