@vizejs/ui 0.347.7 → 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-BMIqJ_M5.d.mts → button-CVX8UBXj.d.mts} +6 -7
  2. package/dist/{button-CaLxP2l2.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-sbhaWekB.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.d.mts +2 -2
  12. package/dist/collection.mjs +1 -1
  13. package/dist/composite-navigation-QINW9GLy.d.mts +115 -0
  14. package/dist/composite-navigation-Skx3LZsv.mjs +354 -0
  15. package/dist/composite-navigation.d.mts +2 -0
  16. package/dist/composite-navigation.mjs +2 -0
  17. package/dist/controllable-state.d.mts +1 -1
  18. package/dist/dismissable-layer-4icKahVs.d.mts +119 -0
  19. package/dist/dismissable-layer-CwG1gtdz.mjs +488 -0
  20. package/dist/dismissable-layer.d.mts +2 -0
  21. package/dist/dismissable-layer.mjs +2 -0
  22. package/dist/focus-CAWi_I-0.mjs +318 -0
  23. package/dist/focus-K6dOifoZ.d.mts +112 -0
  24. package/dist/focus-guards-Bt0C-0qS.d.mts +81 -0
  25. package/dist/focus-guards-DhMPQAT8.mjs +299 -0
  26. package/dist/focus-guards.d.mts +2 -0
  27. package/dist/focus-guards.mjs +2 -0
  28. package/dist/focus-scope-CavhKWx0.d.mts +100 -0
  29. package/dist/focus-scope-D9U1aj67.mjs +340 -0
  30. package/dist/focus-scope-dom-DBwNEWxG.mjs +264 -0
  31. package/dist/focus-scope.d.mts +2 -0
  32. package/dist/focus-scope.mjs +2 -0
  33. package/dist/focus.d.mts +2 -0
  34. package/dist/focus.mjs +2 -0
  35. package/dist/hover-BXwwbSaO.d.mts +80 -0
  36. package/dist/hover.d.mts +2 -0
  37. package/dist/hover.mjs +197 -0
  38. package/dist/{id-BSgJwErt.d.mts → id-5OAUyVPd.d.mts} +2 -3
  39. package/dist/{id-CPBdeoL3.mjs → id-mziSyAse.mjs} +2 -2
  40. package/dist/id.d.mts +2 -2
  41. package/dist/id.mjs +1 -1
  42. package/dist/index.d.mts +22 -11
  43. package/dist/index.mjs +21 -10
  44. package/dist/inert-outside-CyqtoKQ4.mjs +346 -0
  45. package/dist/inert-outside-tuHRmaqJ.d.mts +43 -0
  46. package/dist/inert-outside.d.mts +2 -0
  47. package/dist/inert-outside.mjs +2 -0
  48. package/dist/interaction-modality.d.mts +2 -2
  49. package/dist/interaction-modality.mjs +1 -1
  50. package/dist/long-press-Dsul69gk.mjs +340 -0
  51. package/dist/{long-press-CRjjjJK2.d.mts → long-press-cll6z2K9.d.mts} +8 -18
  52. package/dist/long-press.d.mts +2 -2
  53. package/dist/long-press.mjs +1 -245
  54. package/dist/media-pdf.d.mts +1 -1
  55. package/dist/media-pdf.mjs +1 -1
  56. package/dist/media.d.mts +1 -1
  57. package/dist/media.mjs +1 -1
  58. package/dist/move-BOmXDYbh.d.mts +103 -0
  59. package/dist/move-BVvMHzxr.mjs +386 -0
  60. package/dist/move.d.mts +2 -0
  61. package/dist/move.mjs +2 -0
  62. package/dist/{press-Bp57IOe2.mjs → press-N_mE3Mgx.mjs} +3 -149
  63. package/dist/press-event-Cd2OPpmw.mjs +149 -0
  64. package/dist/press.d.mts +1 -1
  65. package/dist/press.mjs +1 -1
  66. package/dist/{primitive-BE28SwD7.mjs → primitive-BX31dogp.mjs} +2 -2
  67. package/dist/{primitive-Bog8qvd8.d.mts → primitive-C8jW7DLd.d.mts} +3 -4
  68. package/dist/primitive.d.mts +1 -1
  69. package/dist/primitive.mjs +1 -1
  70. package/dist/scroll-lock-BgVtX4wT.d.mts +49 -0
  71. package/dist/scroll-lock-Ckbmm0-J.mjs +313 -0
  72. package/dist/scroll-lock.d.mts +2 -0
  73. package/dist/scroll-lock.mjs +2 -0
  74. package/dist/spatial-navigation-CHj9mDZH.d.mts +106 -0
  75. package/dist/spatial-navigation-D6-UDavU.mjs +301 -0
  76. package/dist/spatial-navigation.d.mts +2 -0
  77. package/dist/spatial-navigation.mjs +2 -0
  78. package/dist/typeahead-B7KDxE_u.mjs +175 -0
  79. package/dist/typeahead-BbgUB2Bj.d.mts +71 -0
  80. package/dist/typeahead.d.mts +2 -0
  81. package/dist/typeahead.mjs +2 -0
  82. package/dist/visually-hidden-BOqfqBFt.d.mts +17 -0
  83. package/dist/{visually-hidden-CTSV3ctZ.mjs → visually-hidden-BwDOx5yf.mjs} +2 -2
  84. package/dist/visually-hidden.d.mts +1 -1
  85. package/dist/visually-hidden.mjs +1 -1
  86. package/package.json +67 -6
  87. package/dist/visually-hidden-CekDwEwR.d.mts +0 -19
  88. /package/dist/{_plugin-vue_export-helper-BVN2DL-U.mjs → _plugin-vue_export-helper-BOaGB7Aw.mjs} +0 -0
  89. /package/dist/{collection-aHZ6Pf0e.d.mts → collection-B_dFZOHO.d.mts} +0 -0
  90. /package/dist/{collection-CoOROaho.mjs → collection-eHN150dT.mjs} +0 -0
  91. /package/dist/{controllable-state-B9v6FClp.d.mts → controllable-state-BtoxKahh.d.mts} +0 -0
  92. /package/dist/{interaction-modality-CO-eQzNy.mjs → interaction-modality-DOZK0BpZ.mjs} +0 -0
  93. /package/dist/{interaction-modality-tee79ZBZ.d.mts → interaction-modality-jgHKjPFB.d.mts} +0 -0
  94. /package/dist/{pdf-source-BbZKF_dS.d.mts → pdf-source-BYFK2Pks.d.mts} +0 -0
  95. /package/dist/{pdf-source-C9zNQd9l.mjs → pdf-source-zu1LkCeE.mjs} +0 -0
  96. /package/dist/{press-types-B8Ssxqg4.d.mts → press-types-CNqStzJf.d.mts} +0 -0
@@ -0,0 +1,106 @@
1
+ import { l as CollectionKey, p as CollectionRegistry, s as CollectionItem } from "./collection-B_dFZOHO.mjs";
2
+ import { MaybeRefOrGetter } from "vue";
3
+
4
+ //#region src/spatial-navigation-types.d.ts
5
+ /** Physical direction requested by spatial navigation. */
6
+ type SpatialNavigationDirection = "down" | "left" | "right" | "up";
7
+ /** Candidate ranking behavior aligned with CSS Spatial Navigation Level 1. */
8
+ type SpatialNavigationAlgorithm = "grid" | "normal";
9
+ /** Arrow-key behavior when no candidate exists in the requested direction. */
10
+ type SpatialNavigationBoundaryBehavior = "contain" | "exit";
11
+ /** DOM focus policy after logical state changes. */
12
+ type SpatialNavigationFocusBehavior = "focus" | "logical";
13
+ /** Serializable, axis-aligned CSS pixel rectangle. */
14
+ interface SpatialNavigationRect {
15
+ readonly bottom: number;
16
+ readonly height: number;
17
+ readonly left: number;
18
+ readonly right: number;
19
+ readonly top: number;
20
+ readonly width: number;
21
+ }
22
+ /** Immutable successful navigation snapshot. */
23
+ interface SpatialNavigationChange<Key extends CollectionKey> {
24
+ readonly direction: SpatialNavigationDirection;
25
+ readonly key: Key;
26
+ readonly previousKey: Key | null;
27
+ readonly originalEvent: Event | null;
28
+ readonly algorithm: SpatialNavigationAlgorithm;
29
+ readonly score: number;
30
+ }
31
+ /** Immutable boundary snapshot emitted when no directional candidate exists. */
32
+ interface SpatialNavigationBoundary<Key extends CollectionKey> {
33
+ readonly direction: SpatialNavigationDirection;
34
+ readonly key: Key | null;
35
+ readonly originalEvent: Event | null;
36
+ }
37
+ /** Spatial navigation configuration for one caller-owned collection. */
38
+ interface SpatialNavigationOptions<Key extends CollectionKey, Value> {
39
+ /** Ordered logical items and active-key ownership. */
40
+ readonly registry: CollectionRegistry<Key, Value>;
41
+ /**
42
+ * Candidate scoring algorithm.
43
+ *
44
+ * @default "normal"
45
+ */
46
+ readonly algorithm?: MaybeRefOrGetter<SpatialNavigationAlgorithm | undefined>;
47
+ /**
48
+ * Whether a boundary arrow remains owned by the composite or exits to native behavior.
49
+ *
50
+ * @default "contain"
51
+ */
52
+ readonly boundaryBehavior?: MaybeRefOrGetter<SpatialNavigationBoundaryBehavior | undefined>;
53
+ /** Suppress keyboard and imperative navigation without discarding active state. */
54
+ readonly isDisabled?: MaybeRefOrGetter<boolean | undefined>;
55
+ /**
56
+ * Wrap to the opposite spatial edge when no forward candidate exists.
57
+ *
58
+ * @default false
59
+ */
60
+ readonly loop?: MaybeRefOrGetter<boolean | undefined>;
61
+ /**
62
+ * Whether successful navigation moves DOM focus or only logical active state.
63
+ *
64
+ * @default "focus"
65
+ */
66
+ readonly focusBehavior?: MaybeRefOrGetter<SpatialNavigationFocusBehavior | undefined>;
67
+ /**
68
+ * Avoid browser scrolling while moving DOM focus. Reveal behavior still runs.
69
+ *
70
+ * @default false
71
+ */
72
+ readonly preventScroll?: MaybeRefOrGetter<boolean | undefined>;
73
+ /**
74
+ * Resolve transformed viewport geometry for DOM or virtual items.
75
+ * The default reads `element.getBoundingClientRect()` and skips unmounted candidates.
76
+ */
77
+ readonly getRect?: (item: CollectionItem<Key, Value>) => SpatialNavigationRect | DOMRectReadOnly | null | undefined;
78
+ /** Custom visibility policy for virtualized or scroll-managed collections. */
79
+ readonly scrollIntoView?: (item: CollectionItem<Key, Value>, originalEvent: Event | null) => void;
80
+ /** Called after logical state, DOM focus, and reveal behavior commit. */
81
+ readonly onNavigate?: (change: SpatialNavigationChange<Key>) => void;
82
+ /** Called when a valid origin has no candidate in the requested direction. */
83
+ readonly onBoundary?: (boundary: SpatialNavigationBoundary<Key>) => void;
84
+ }
85
+ /** Stable event adapter for a spatial navigation container. */
86
+ interface SpatialNavigationProps {
87
+ readonly onKeydown: (event: KeyboardEvent) => void;
88
+ }
89
+ /** Geometry-driven navigation controller. */
90
+ interface SpatialNavigationController<Key extends CollectionKey> {
91
+ readonly spatialNavigationProps: Readonly<SpatialNavigationProps>;
92
+ /** Resolve the best candidate without mutating logical or DOM state. */
93
+ readonly findTarget: (direction: SpatialNavigationDirection, fromKey?: Key) => Key | null;
94
+ /** Navigate, returning the committed key or `null` at a boundary. */
95
+ readonly navigate: (direction: SpatialNavigationDirection, originalEvent?: Event | null) => Key | null;
96
+ /** Release scope ownership. The caller-owned registry is not disposed. */
97
+ readonly dispose: () => void;
98
+ }
99
+ //#endregion
100
+ //#region src/spatial-navigation.d.ts
101
+ /** Create a geometry-driven, SSR-safe arrow navigation controller. */
102
+ declare function createSpatialNavigation<Key extends CollectionKey, Value>(options: SpatialNavigationOptions<Key, Value>): SpatialNavigationController<Key>;
103
+ /** Create a spatial controller disposed with the current Vue effect scope. */
104
+ declare function useSpatialNavigation<Key extends CollectionKey, Value>(options: SpatialNavigationOptions<Key, Value>): SpatialNavigationController<Key>;
105
+ //#endregion
106
+ export { SpatialNavigationBoundaryBehavior as a, SpatialNavigationDirection as c, SpatialNavigationProps as d, SpatialNavigationRect as f, SpatialNavigationBoundary as i, SpatialNavigationFocusBehavior as l, useSpatialNavigation as n, SpatialNavigationChange as o, SpatialNavigationAlgorithm as r, SpatialNavigationController as s, createSpatialNavigation as t, SpatialNavigationOptions as u };
@@ -0,0 +1,301 @@
1
+ import { getCurrentScope, onScopeDispose, toValue } from "vue";
2
+ //#region src/spatial-navigation-internal.ts
3
+ const optionDiagnostic = "VIZE_UI_SPATIAL_NAVIGATION_OPTION";
4
+ const rectDiagnostic = "VIZE_UI_SPATIAL_NAVIGATION_RECT";
5
+ function capture(errors, callback) {
6
+ try {
7
+ callback();
8
+ } catch (error) {
9
+ errors.push(error);
10
+ }
11
+ }
12
+ function surfaceErrors(errors, message) {
13
+ if (errors.length === 1) throw errors[0];
14
+ if (errors.length < 2) return;
15
+ const Aggregate = globalThis.AggregateError;
16
+ if (typeof Aggregate === "function") throw new Aggregate(errors, message);
17
+ const fallback = Object.assign(new Error(message), { errors: [...errors] });
18
+ fallback.name = "AggregateError";
19
+ throw fallback;
20
+ }
21
+ function readBoolean(source, name) {
22
+ const value = toValue(source);
23
+ if (value === void 0) return false;
24
+ if (typeof value !== "boolean") throw new TypeError(`${optionDiagnostic}: ${name} must resolve to a boolean`);
25
+ return value;
26
+ }
27
+ function readEnum(source, fallback, values, name) {
28
+ const value = toValue(source) ?? fallback;
29
+ if (!values.includes(value)) throw new TypeError(`${optionDiagnostic}: ${name} must resolve to ${values.join(" or ")}`);
30
+ return value;
31
+ }
32
+ const readAlgorithm = (source) => readEnum(source, "normal", ["normal", "grid"], "algorithm");
33
+ const readBoundary = (source) => readEnum(source, "contain", ["contain", "exit"], "boundaryBehavior");
34
+ const readFocus = (source) => readEnum(source, "focus", ["focus", "logical"], "focusBehavior");
35
+ function readDirection(value) {
36
+ if (value !== "down" && value !== "left" && value !== "right" && value !== "up") throw new TypeError(`${optionDiagnostic}: direction is invalid`);
37
+ return value;
38
+ }
39
+ function validateOptions(options) {
40
+ if (!options || typeof options !== "object" || Array.isArray(options)) throw new TypeError(`${optionDiagnostic}: options must be an object`);
41
+ const registry = options.registry;
42
+ if (!registry || typeof registry.getItem !== "function" || !registry.navigableItems) throw new TypeError(`${optionDiagnostic}: registry must be a CollectionRegistry`);
43
+ for (const name of [
44
+ "getRect",
45
+ "scrollIntoView",
46
+ "onNavigate",
47
+ "onBoundary"
48
+ ]) {
49
+ const callback = options[name];
50
+ if (callback !== void 0 && typeof callback !== "function") throw new TypeError(`${optionDiagnostic}: ${name} must be a function`);
51
+ }
52
+ readAlgorithm(options.algorithm);
53
+ readBoundary(options.boundaryBehavior);
54
+ readFocus(options.focusBehavior);
55
+ readBoolean(options.isDisabled, "isDisabled");
56
+ readBoolean(options.loop, "loop");
57
+ readBoolean(options.preventScroll, "preventScroll");
58
+ }
59
+ function normalizeRect(value) {
60
+ if (!value || typeof value !== "object") throw new TypeError(`${rectDiagnostic}: geometry must be a rectangle object`);
61
+ const input = value;
62
+ if ([
63
+ input.bottom,
64
+ input.height,
65
+ input.left,
66
+ input.right,
67
+ input.top,
68
+ input.width
69
+ ].some((number) => typeof number !== "number" || !Number.isFinite(number))) throw new TypeError(`${rectDiagnostic}: rectangle coordinates must be finite numbers`);
70
+ if (input.width < 0 || input.height < 0 || input.right < input.left || input.bottom < input.top) throw new TypeError(`${rectDiagnostic}: rectangle dimensions must be non-negative and ordered`);
71
+ return Object.freeze({
72
+ bottom: input.bottom,
73
+ height: input.height,
74
+ left: input.left,
75
+ right: input.right,
76
+ top: input.top,
77
+ width: input.width
78
+ });
79
+ }
80
+ function intervalGap(startA, endA, startB, endB) {
81
+ return Math.max(0, startA - endB, startB - endA);
82
+ }
83
+ function intervalOverlap(startA, endA, startB, endB) {
84
+ return Math.max(0, Math.min(endA, endB) - Math.max(startA, startB));
85
+ }
86
+ function directionalGap(origin, candidate, direction) {
87
+ if (direction === "down") return candidate.top >= origin.bottom ? candidate.top - origin.bottom : null;
88
+ if (direction === "up") return candidate.bottom <= origin.top ? origin.top - candidate.bottom : null;
89
+ if (direction === "right") return candidate.left >= origin.right ? candidate.left - origin.right : null;
90
+ return candidate.right <= origin.left ? origin.left - candidate.right : null;
91
+ }
92
+ function rankItem(item, rect, index, origin, direction) {
93
+ const primary = directionalGap(origin, rect, direction);
94
+ if (primary === null) return null;
95
+ const horizontal = direction === "left" || direction === "right";
96
+ const orthogonal = horizontal ? intervalGap(origin.top, origin.bottom, rect.top, rect.bottom) : intervalGap(origin.left, origin.right, rect.left, rect.right);
97
+ const overlap = horizontal ? intervalOverlap(origin.top, origin.bottom, rect.top, rect.bottom) : intervalOverlap(origin.left, origin.right, rect.left, rect.right);
98
+ const orthogonalSize = horizontal ? origin.height : origin.width;
99
+ const orthogonalBias = orthogonalSize / 2;
100
+ const orthogonalWeight = horizontal ? 30 : 2;
101
+ const alignment = orthogonalSize > 0 ? overlap / orthogonalSize * 5 : 0;
102
+ return {
103
+ item,
104
+ rect,
105
+ index,
106
+ primary,
107
+ orthogonal,
108
+ overlap,
109
+ score: Math.hypot(primary, orthogonal) + (orthogonal + orthogonalBias) * orthogonalWeight - alignment
110
+ };
111
+ }
112
+ function selectCandidate(candidates, algorithm) {
113
+ let ranked = [...candidates];
114
+ if (algorithm === "grid" && ranked.some(({ overlap }) => overlap > 0)) ranked = ranked.filter(({ overlap }) => overlap > 0);
115
+ ranked.sort((left, right) => algorithm === "normal" ? left.score - right.score || left.index - right.index : left.primary - right.primary || right.overlap - left.overlap || left.orthogonal - right.orthogonal || left.index - right.index);
116
+ return ranked[0];
117
+ }
118
+ function selectWrappedCandidate(items, origin, direction) {
119
+ const horizontal = direction === "left" || direction === "right";
120
+ const candidates = items.map(({ item, rect, index }) => {
121
+ const orthogonal = horizontal ? intervalGap(origin.top, origin.bottom, rect.top, rect.bottom) : intervalGap(origin.left, origin.right, rect.left, rect.right);
122
+ const overlap = horizontal ? intervalOverlap(origin.top, origin.bottom, rect.top, rect.bottom) : intervalOverlap(origin.left, origin.right, rect.left, rect.right);
123
+ const primary = direction === "right" ? rect.left : direction === "left" ? -rect.right : direction === "down" ? rect.top : -rect.bottom;
124
+ return {
125
+ item,
126
+ rect,
127
+ index,
128
+ primary,
129
+ orthogonal,
130
+ overlap,
131
+ score: primary + orthogonal
132
+ };
133
+ });
134
+ const aligned = candidates.some(({ overlap }) => overlap > 0) ? candidates.filter(({ overlap }) => overlap > 0) : candidates;
135
+ aligned.sort((left, right) => left.primary - right.primary || left.orthogonal - right.orthogonal || right.overlap - left.overlap || left.index - right.index);
136
+ return aligned[0];
137
+ }
138
+ function keyDirection(event) {
139
+ if (event.isComposing || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) return null;
140
+ if (event.key === "ArrowDown") return "down";
141
+ if (event.key === "ArrowLeft") return "left";
142
+ if (event.key === "ArrowRight") return "right";
143
+ if (event.key === "ArrowUp") return "up";
144
+ return null;
145
+ }
146
+ function isEditableDescendant(event) {
147
+ const path = typeof event.composedPath === "function" ? event.composedPath() : [event.target];
148
+ for (const candidate of path) {
149
+ if (candidate === event.currentTarget) break;
150
+ const target = candidate;
151
+ if (target?.localName === "input" || target?.localName === "select" || target?.localName === "textarea" || target?.isContentEditable === true) return true;
152
+ }
153
+ return false;
154
+ }
155
+ //#endregion
156
+ //#region src/spatial-navigation.ts
157
+ const disposedDiagnostic = "VIZE_UI_SPATIAL_NAVIGATION_DISPOSED";
158
+ const originDiagnostic = "VIZE_UI_SPATIAL_NAVIGATION_ORIGIN";
159
+ const setupDiagnostic = "VIZE_UI_SPATIAL_NAVIGATION_SETUP";
160
+ /** Create a geometry-driven, SSR-safe arrow navigation controller. */
161
+ function createSpatialNavigation(options) {
162
+ validateOptions(options);
163
+ const registry = options.registry;
164
+ let disposed = false;
165
+ const assertActive = () => {
166
+ if (disposed) throw new Error(`${disposedDiagnostic}: the controller has been disposed`);
167
+ };
168
+ const rectFor = (item) => {
169
+ const supplied = options.getRect?.(item);
170
+ if (supplied !== void 0 && supplied !== null) return normalizeRect(supplied);
171
+ if (options.getRect) return null;
172
+ const read = item.element?.getBoundingClientRect;
173
+ return typeof read === "function" ? normalizeRect(read.call(item.element)) : null;
174
+ };
175
+ const originItem = (fromKey) => {
176
+ const items = registry.navigableItems.value;
177
+ if (items.length === 0) return null;
178
+ const key = fromKey ?? registry.activeKey.value ?? items[0]?.key;
179
+ const item = items.find((candidate) => candidate.key === key);
180
+ if (!item) throw new Error(`${originDiagnostic}: ${String(key)} is not a navigable collection item`);
181
+ return item;
182
+ };
183
+ const resolve = (directionInput, fromKey) => {
184
+ assertActive();
185
+ const direction = readDirection(directionInput);
186
+ if (readBoolean(options.isDisabled, "isDisabled")) return null;
187
+ const origin = originItem(fromKey);
188
+ if (!origin) return null;
189
+ const originRect = rectFor(origin);
190
+ if (!originRect) throw new Error(`${originDiagnostic}: ${String(origin.key)} has no measurable rectangle`);
191
+ const measured = [];
192
+ const candidates = [];
193
+ for (const [index, item] of registry.navigableItems.value.entries()) {
194
+ if (item.key === origin.key) continue;
195
+ const rect = rectFor(item);
196
+ if (!rect) continue;
197
+ measured.push({
198
+ item,
199
+ rect,
200
+ index
201
+ });
202
+ const candidate = rankItem(item, rect, index, originRect, direction);
203
+ if (candidate) candidates.push(candidate);
204
+ }
205
+ const algorithm = readAlgorithm(options.algorithm);
206
+ let target = selectCandidate(candidates, algorithm) ?? null;
207
+ if (!target && readBoolean(options.loop, "loop")) target = selectWrappedCandidate(measured, originRect, direction) ?? null;
208
+ return {
209
+ algorithm,
210
+ originKey: origin.key,
211
+ target
212
+ };
213
+ };
214
+ const synchronizeDom = (item, originalEvent, errors) => {
215
+ const focusBehavior = readFocus(options.focusBehavior);
216
+ const preventScroll = readBoolean(options.preventScroll, "preventScroll");
217
+ if (focusBehavior === "focus") {
218
+ const focus = item.element?.focus;
219
+ if (typeof focus !== "function") errors.push(/* @__PURE__ */ new Error("VIZE_UI_SPATIAL_NAVIGATION_FOCUS: target item is not focusable"));
220
+ else capture(errors, () => {
221
+ try {
222
+ focus.call(item.element, { preventScroll });
223
+ } catch {
224
+ focus.call(item.element);
225
+ }
226
+ });
227
+ }
228
+ if (!(options.scrollIntoView || focusBehavior === "logical" || preventScroll)) return;
229
+ if (options.scrollIntoView) {
230
+ capture(errors, () => options.scrollIntoView?.(item, originalEvent));
231
+ return;
232
+ }
233
+ const reveal = item.element?.scrollIntoView;
234
+ if (typeof reveal === "function") capture(errors, () => reveal.call(item.element, {
235
+ block: "nearest",
236
+ inline: "nearest"
237
+ }));
238
+ };
239
+ const transition = (direction, resolution, originalEvent) => {
240
+ const errors = [];
241
+ if (!resolution) return null;
242
+ if (!resolution.target) {
243
+ const boundary = Object.freeze({
244
+ direction,
245
+ key: resolution.originKey,
246
+ originalEvent
247
+ });
248
+ capture(errors, () => options.onBoundary?.(boundary));
249
+ surfaceErrors(errors, "Spatial navigation boundary callback failed");
250
+ return null;
251
+ }
252
+ const { item, score } = resolution.target;
253
+ const previousKey = registry.activeKey.value;
254
+ capture(errors, () => {
255
+ registry.setActiveKey(item.key);
256
+ });
257
+ if (registry.activeKey.value === item.key && item.key !== previousKey) {
258
+ capture(errors, () => synchronizeDom(item, originalEvent, errors));
259
+ const change = Object.freeze({
260
+ algorithm: resolution.algorithm,
261
+ direction,
262
+ key: item.key,
263
+ previousKey,
264
+ originalEvent,
265
+ score
266
+ });
267
+ capture(errors, () => options.onNavigate?.(change));
268
+ }
269
+ surfaceErrors(errors, "Spatial navigation transition failed");
270
+ return registry.activeKey.value;
271
+ };
272
+ const navigate = (directionInput, originalEvent = null) => {
273
+ const direction = readDirection(directionInput);
274
+ return transition(direction, resolve(direction), originalEvent);
275
+ };
276
+ const spatialNavigationProps = Object.freeze({ onKeydown(event) {
277
+ if (event.defaultPrevented || isEditableDescendant(event)) return;
278
+ const direction = keyDirection(event);
279
+ if (!direction || readBoolean(options.isDisabled, "isDisabled")) return;
280
+ const resolution = resolve(direction);
281
+ if (resolution && (resolution.target || readBoundary(options.boundaryBehavior) === "contain")) event.preventDefault();
282
+ transition(direction, resolution, event);
283
+ } });
284
+ return Object.freeze({
285
+ spatialNavigationProps,
286
+ findTarget: (direction, fromKey) => resolve(direction, fromKey)?.target?.item.key ?? null,
287
+ navigate,
288
+ dispose: () => {
289
+ disposed = true;
290
+ }
291
+ });
292
+ }
293
+ /** Create a spatial controller disposed with the current Vue effect scope. */
294
+ function useSpatialNavigation(options) {
295
+ if (!getCurrentScope()) throw new Error(`${setupDiagnostic}: use inside component setup or an active effect scope`);
296
+ const controller = createSpatialNavigation(options);
297
+ onScopeDispose(controller.dispose);
298
+ return controller;
299
+ }
300
+ //#endregion
301
+ export { useSpatialNavigation as n, createSpatialNavigation as t };
@@ -0,0 +1,2 @@
1
+ import { a as SpatialNavigationBoundaryBehavior, c as SpatialNavigationDirection, d as SpatialNavigationProps, f as SpatialNavigationRect, i as SpatialNavigationBoundary, l as SpatialNavigationFocusBehavior, n as useSpatialNavigation, o as SpatialNavigationChange, r as SpatialNavigationAlgorithm, s as SpatialNavigationController, t as createSpatialNavigation, u as SpatialNavigationOptions } from "./spatial-navigation-CHj9mDZH.mjs";
2
+ export { type SpatialNavigationAlgorithm, type SpatialNavigationBoundary, type SpatialNavigationBoundaryBehavior, type SpatialNavigationChange, type SpatialNavigationController, type SpatialNavigationDirection, type SpatialNavigationFocusBehavior, type SpatialNavigationOptions, type SpatialNavigationProps, type SpatialNavigationRect, createSpatialNavigation, useSpatialNavigation };
@@ -0,0 +1,2 @@
1
+ import { n as useSpatialNavigation, t as createSpatialNavigation } from "./spatial-navigation-D6-UDavU.mjs";
2
+ export { createSpatialNavigation, useSpatialNavigation };
@@ -0,0 +1,175 @@
1
+ import { getCurrentScope, isRef, onScopeDispose, shallowReadonly, shallowRef, toValue, watch } from "vue";
2
+ //#region src/typeahead-internal.ts
3
+ const optionDiagnostic = "VIZE_UI_TYPEAHEAD_OPTION";
4
+ const inputDiagnostic = "VIZE_UI_TYPEAHEAD_INPUT";
5
+ const graphemeSegmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
6
+ function splitGraphemes(value) {
7
+ return [...graphemeSegmenter.segment(value)].map(({ segment }) => segment);
8
+ }
9
+ function readBoolean(source, name) {
10
+ const value = toValue(source);
11
+ if (value === void 0) return false;
12
+ if (typeof value !== "boolean") throw new TypeError(`${optionDiagnostic}: ${name} must resolve to a boolean`);
13
+ return value;
14
+ }
15
+ function readTimeout(source) {
16
+ const value = toValue(source) ?? 500;
17
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) throw new TypeError(`${optionDiagnostic}: timeout must resolve to a finite number >= 0`);
18
+ return value;
19
+ }
20
+ function readGrapheme(value) {
21
+ if (typeof value !== "string" || value.length === 0) throw new TypeError(`${inputDiagnostic}: input must be exactly one Unicode grapheme`);
22
+ const segments = splitGraphemes(value);
23
+ if (segments.length !== 1 || segments[0] !== value) throw new TypeError(`${inputDiagnostic}: input must be exactly one Unicode grapheme`);
24
+ return value;
25
+ }
26
+ function isCharacterKey(event) {
27
+ if (event.isComposing || event.key === "Dead" || event.key === "Process") return false;
28
+ const altGraph = event.getModifierState?.("AltGraph") === true;
29
+ if (event.metaKey || event.ctrlKey && !altGraph || event.altKey && !altGraph) return false;
30
+ try {
31
+ readGrapheme(event.key);
32
+ return true;
33
+ } catch {
34
+ return false;
35
+ }
36
+ }
37
+ function validateOptions(options) {
38
+ const registry = options.registry;
39
+ if (!registry || typeof registry.moveActiveByTextValue !== "function" || !registry.activeKey) throw new TypeError(`${optionDiagnostic}: registry must be a CollectionRegistry`);
40
+ if (options.allowSpace !== void 0 && typeof options.allowSpace !== "boolean") throw new TypeError(`${optionDiagnostic}: allowSpace must be a boolean`);
41
+ if (options.onMatch !== void 0 && typeof options.onMatch !== "function") throw new TypeError(`${optionDiagnostic}: onMatch must be a function`);
42
+ if (options.collator !== void 0 && typeof options.collator.compare !== "function") throw new TypeError(`${optionDiagnostic}: collator must be an Intl.Collator`);
43
+ readTimeout(options.timeout);
44
+ readBoolean(options.isDisabled, "isDisabled");
45
+ }
46
+ //#endregion
47
+ //#region src/typeahead.ts
48
+ const disposedDiagnostic = "VIZE_UI_TYPEAHEAD_DISPOSED";
49
+ const setupDiagnostic = "VIZE_UI_TYPEAHEAD_SETUP";
50
+ /** Create an SSR-safe, locale-aware typeahead buffer for one collection. */
51
+ function createTypeahead(options) {
52
+ validateOptions(options);
53
+ const query = shallowRef("");
54
+ const collator = options.collator ?? new Intl.Collator(void 0, {
55
+ sensitivity: "base",
56
+ usage: "search"
57
+ });
58
+ let timer = null;
59
+ let disposed = false;
60
+ const clearTimer = () => {
61
+ if (timer !== null) clearTimeout(timer);
62
+ timer = null;
63
+ };
64
+ const reset = () => {
65
+ clearTimer();
66
+ if (query.value.length === 0) return false;
67
+ query.value = "";
68
+ return true;
69
+ };
70
+ const assertActive = () => {
71
+ if (disposed) throw new Error(`${disposedDiagnostic}: the controller has been disposed`);
72
+ };
73
+ const scheduleReset = (timeout) => {
74
+ clearTimer();
75
+ timer = setTimeout(() => {
76
+ query.value = "";
77
+ timer = null;
78
+ }, timeout);
79
+ };
80
+ const input = (value, originalEvent = null) => {
81
+ assertActive();
82
+ const grapheme = readGrapheme(value);
83
+ let timeout;
84
+ try {
85
+ if (readBoolean(options.isDisabled, "isDisabled")) {
86
+ reset();
87
+ return null;
88
+ }
89
+ timeout = readTimeout(options.timeout);
90
+ } catch (error) {
91
+ reset();
92
+ throw error;
93
+ }
94
+ const previousGraphemes = splitGraphemes(query.value);
95
+ const nextQuery = previousGraphemes.length > 0 && previousGraphemes.every((part) => collator.compare(part, grapheme) === 0) ? grapheme : `${query.value}${grapheme}`;
96
+ query.value = nextQuery;
97
+ scheduleReset(timeout);
98
+ const previousKey = options.registry.activeKey.value;
99
+ let key;
100
+ try {
101
+ key = options.registry.moveActiveByTextValue(nextQuery, { collator });
102
+ } catch (error) {
103
+ reset();
104
+ throw error;
105
+ }
106
+ if (key !== null && key !== previousKey) {
107
+ const match = Object.freeze({
108
+ key,
109
+ previousKey,
110
+ query: nextQuery,
111
+ originalEvent
112
+ });
113
+ options.onMatch?.(match);
114
+ }
115
+ return key;
116
+ };
117
+ const typeaheadProps = Object.freeze({ onKeydown(event) {
118
+ if (!isCharacterKey(event)) return;
119
+ if (event.key === " " && query.value.length === 0 && options.allowSpace !== true) return;
120
+ event.preventDefault();
121
+ input(event.key, event);
122
+ } });
123
+ const stopWatches = [];
124
+ const disabledSource = options.isDisabled;
125
+ if (isRef(disabledSource) || typeof disabledSource === "function") stopWatches.push(watch(() => {
126
+ try {
127
+ return isRef(disabledSource) ? disabledSource.value : disabledSource();
128
+ } catch (error) {
129
+ reset();
130
+ throw error;
131
+ }
132
+ }, () => {
133
+ try {
134
+ if (readBoolean(options.isDisabled, "isDisabled")) reset();
135
+ } catch (error) {
136
+ reset();
137
+ throw error;
138
+ }
139
+ }, { flush: "sync" }));
140
+ const timeoutSource = options.timeout;
141
+ if (isRef(timeoutSource) || typeof timeoutSource === "function") stopWatches.push(watch(() => {
142
+ try {
143
+ return readTimeout(timeoutSource);
144
+ } catch (error) {
145
+ reset();
146
+ throw error;
147
+ }
148
+ }, (timeout) => {
149
+ if (query.value.length > 0) scheduleReset(timeout);
150
+ }, { flush: "sync" }));
151
+ return Object.freeze({
152
+ query: shallowReadonly(query),
153
+ typeaheadProps,
154
+ input,
155
+ reset: () => {
156
+ assertActive();
157
+ return reset();
158
+ },
159
+ dispose: () => {
160
+ if (disposed) return;
161
+ disposed = true;
162
+ for (const stop of stopWatches) stop();
163
+ reset();
164
+ }
165
+ });
166
+ }
167
+ /** Create a typeahead controller disposed with the current Vue effect scope. */
168
+ function useTypeahead(options) {
169
+ if (!getCurrentScope()) throw new Error(`${setupDiagnostic}: use inside component setup or an active effect scope`);
170
+ const controller = createTypeahead(options);
171
+ onScopeDispose(controller.dispose);
172
+ return controller;
173
+ }
174
+ //#endregion
175
+ export { useTypeahead as n, createTypeahead as t };
@@ -0,0 +1,71 @@
1
+ import { l as CollectionKey, p as CollectionRegistry } from "./collection-B_dFZOHO.mjs";
2
+ import { MaybeRefOrGetter, ShallowRef } from "vue";
3
+
4
+ //#region src/typeahead-types.d.ts
5
+ /** Immutable snapshot emitted when buffered text matches a collection item. */
6
+ interface TypeaheadMatch<Key extends CollectionKey> {
7
+ /** Item selected by the locale-aware collection search. */
8
+ readonly key: Key;
9
+ /** Active item before the match. */
10
+ readonly previousKey: Key | null;
11
+ /** Normalized buffered query used for this search. */
12
+ readonly query: string;
13
+ /** Native key event responsible for the match, or `null` for manual input. */
14
+ readonly originalEvent: KeyboardEvent | null;
15
+ }
16
+ /** Options for {@link createTypeahead}. */
17
+ interface TypeaheadOptions<Key extends CollectionKey, Value> {
18
+ /** Mutation-aware collection that owns active state and locale matching. */
19
+ readonly registry: CollectionRegistry<Key, Value>;
20
+ /**
21
+ * Idle time before a new grapheme starts a fresh query.
22
+ *
23
+ * @default 500
24
+ */
25
+ readonly timeout?: MaybeRefOrGetter<number | undefined>;
26
+ /**
27
+ * Ignore input and synchronously clear a pending query while true.
28
+ *
29
+ * @default false
30
+ */
31
+ readonly isDisabled?: MaybeRefOrGetter<boolean | undefined>;
32
+ /**
33
+ * Treat Space as the first grapheme of a query. A space is always accepted
34
+ * after another character so multi-word labels remain searchable.
35
+ *
36
+ * @default false
37
+ */
38
+ readonly allowSpace?: boolean;
39
+ /** Comparator used to detect repeated graphemes before cycling matches. */
40
+ readonly collator?: Intl.Collator;
41
+ /** Called after active collection state moves to a text match. */
42
+ readonly onMatch?: (match: TypeaheadMatch<Key>) => void;
43
+ }
44
+ /** Stable keyboard handler to merge onto a composite focus owner. */
45
+ interface TypeaheadProps {
46
+ readonly onKeydown: (event: KeyboardEvent) => void;
47
+ }
48
+ /** Buffered, locale-aware collection typeahead controller. */
49
+ interface TypeaheadController<Key extends CollectionKey> {
50
+ /** Current query, cleared after the configured timeout. */
51
+ readonly query: Readonly<ShallowRef<string>>;
52
+ /** Stable keyboard handler for declarative consumers. */
53
+ readonly typeaheadProps: Readonly<TypeaheadProps>;
54
+ /**
55
+ * Consume exactly one Unicode grapheme and return the matching item.
56
+ * Repeating one grapheme cycles through matching items.
57
+ */
58
+ readonly input: (grapheme: string, originalEvent?: KeyboardEvent | null) => Key | null;
59
+ /** Clear buffered input and report whether any text was pending. */
60
+ readonly reset: () => boolean;
61
+ /** Clear timers and make imperative operations terminal. Safe to repeat. */
62
+ readonly dispose: () => void;
63
+ }
64
+ //#endregion
65
+ //#region src/typeahead.d.ts
66
+ /** Create an SSR-safe, locale-aware typeahead buffer for one collection. */
67
+ declare function createTypeahead<Key extends CollectionKey, Value>(options: TypeaheadOptions<Key, Value>): TypeaheadController<Key>;
68
+ /** Create a typeahead controller disposed with the current Vue effect scope. */
69
+ declare function useTypeahead<Key extends CollectionKey, Value>(options: TypeaheadOptions<Key, Value>): TypeaheadController<Key>;
70
+ //#endregion
71
+ export { TypeaheadOptions as a, TypeaheadMatch as i, useTypeahead as n, TypeaheadProps as o, TypeaheadController as r, createTypeahead as t };
@@ -0,0 +1,2 @@
1
+ import { a as TypeaheadOptions, i as TypeaheadMatch, n as useTypeahead, o as TypeaheadProps, r as TypeaheadController, t as createTypeahead } from "./typeahead-BbgUB2Bj.mjs";
2
+ export { type TypeaheadController, type TypeaheadMatch, type TypeaheadOptions, type TypeaheadProps, createTypeahead, useTypeahead };
@@ -0,0 +1,2 @@
1
+ import { n as useTypeahead, t as createTypeahead } from "./typeahead-B7KDxE_u.mjs";
2
+ export { createTypeahead, useTypeahead };