@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.
- package/dist/{button-D7sM9Xmj.d.mts → button-CVX8UBXj.d.mts} +6 -7
- package/dist/{button-8BOlFJNu.mjs → button-DT8AKlds.mjs} +2 -2
- package/dist/button.d.mts +1 -1
- package/dist/button.mjs +1 -1
- package/dist/catalog.d.mts +541 -0
- package/dist/catalog.mjs +784 -0
- package/dist/{checkbox-B4y9GUen.mjs → checkbox-BgIvwjyC.mjs} +2 -2
- package/dist/{checkbox-DkwZFC80.d.mts → checkbox-DP3_ko7D.d.mts} +7 -8
- package/dist/checkbox.d.mts +1 -1
- package/dist/checkbox.mjs +1 -1
- package/dist/collection-B_dFZOHO.d.mts +185 -0
- package/dist/collection-eHN150dT.mjs +453 -0
- package/dist/collection.d.mts +2 -0
- package/dist/collection.mjs +2 -0
- package/dist/composite-navigation-QINW9GLy.d.mts +115 -0
- package/dist/composite-navigation-Skx3LZsv.mjs +354 -0
- package/dist/composite-navigation.d.mts +2 -0
- package/dist/composite-navigation.mjs +2 -0
- package/dist/controllable-state.d.mts +1 -1
- package/dist/dismissable-layer-4icKahVs.d.mts +119 -0
- package/dist/dismissable-layer-CwG1gtdz.mjs +488 -0
- package/dist/dismissable-layer.d.mts +2 -0
- package/dist/dismissable-layer.mjs +2 -0
- package/dist/focus-CAWi_I-0.mjs +318 -0
- package/dist/focus-K6dOifoZ.d.mts +112 -0
- package/dist/focus-guards-Bt0C-0qS.d.mts +81 -0
- package/dist/focus-guards-DhMPQAT8.mjs +299 -0
- package/dist/focus-guards.d.mts +2 -0
- package/dist/focus-guards.mjs +2 -0
- package/dist/focus-scope-CavhKWx0.d.mts +100 -0
- package/dist/focus-scope-D9U1aj67.mjs +340 -0
- package/dist/focus-scope-dom-DBwNEWxG.mjs +264 -0
- package/dist/focus-scope.d.mts +2 -0
- package/dist/focus-scope.mjs +2 -0
- package/dist/focus.d.mts +2 -0
- package/dist/focus.mjs +2 -0
- package/dist/hover-BXwwbSaO.d.mts +80 -0
- package/dist/hover.d.mts +2 -0
- package/dist/hover.mjs +197 -0
- package/dist/id-5OAUyVPd.d.mts +126 -0
- package/dist/id-mziSyAse.mjs +137 -0
- package/dist/id.d.mts +2 -0
- package/dist/id.mjs +2 -0
- package/dist/index.d.mts +23 -6
- package/dist/index.mjs +21 -5
- package/dist/inert-outside-CyqtoKQ4.mjs +346 -0
- package/dist/inert-outside-tuHRmaqJ.d.mts +43 -0
- package/dist/inert-outside.d.mts +2 -0
- package/dist/inert-outside.mjs +2 -0
- package/dist/interaction-modality-DOZK0BpZ.mjs +304 -0
- package/dist/interaction-modality-jgHKjPFB.d.mts +89 -0
- package/dist/interaction-modality.d.mts +2 -0
- package/dist/interaction-modality.mjs +2 -0
- package/dist/long-press-Dsul69gk.mjs +340 -0
- package/dist/long-press-cll6z2K9.d.mts +105 -0
- package/dist/long-press.d.mts +2 -0
- package/dist/long-press.mjs +2 -0
- package/dist/media-pdf.d.mts +1 -1
- package/dist/media-pdf.mjs +1 -1
- package/dist/media.d.mts +1 -1
- package/dist/media.mjs +1 -1
- package/dist/move-BOmXDYbh.d.mts +103 -0
- package/dist/move-BVvMHzxr.mjs +386 -0
- package/dist/move.d.mts +2 -0
- package/dist/move.mjs +2 -0
- package/dist/press-N_mE3Mgx.mjs +469 -0
- package/dist/press-event-Cd2OPpmw.mjs +149 -0
- package/dist/press-types-CNqStzJf.d.mts +112 -0
- package/dist/press.d.mts +14 -0
- package/dist/press.mjs +2 -0
- package/dist/{primitive-DJB7pOf3.mjs → primitive-BX31dogp.mjs} +2 -2
- package/dist/{primitive-BtvwikH1.d.mts → primitive-C8jW7DLd.d.mts} +3 -4
- package/dist/primitive.d.mts +1 -1
- package/dist/primitive.mjs +1 -1
- package/dist/scroll-lock-BgVtX4wT.d.mts +49 -0
- package/dist/scroll-lock-Ckbmm0-J.mjs +313 -0
- package/dist/scroll-lock.d.mts +2 -0
- package/dist/scroll-lock.mjs +2 -0
- package/dist/spatial-navigation-CHj9mDZH.d.mts +106 -0
- package/dist/spatial-navigation-D6-UDavU.mjs +301 -0
- package/dist/spatial-navigation.d.mts +2 -0
- package/dist/spatial-navigation.mjs +2 -0
- package/dist/typeahead-B7KDxE_u.mjs +175 -0
- package/dist/typeahead-BbgUB2Bj.d.mts +71 -0
- package/dist/typeahead.d.mts +2 -0
- package/dist/typeahead.mjs +2 -0
- package/dist/visually-hidden-BOqfqBFt.d.mts +17 -0
- package/dist/{visually-hidden-QENRapzW.mjs → visually-hidden-BwDOx5yf.mjs} +2 -2
- package/dist/visually-hidden.d.mts +1 -1
- package/dist/visually-hidden.mjs +1 -1
- package/package.json +99 -11
- package/dist/visually-hidden-BegtnMng.d.mts +0 -19
- /package/dist/{_plugin-vue_export-helper-BVN2DL-U.mjs → _plugin-vue_export-helper-BOaGB7Aw.mjs} +0 -0
- /package/dist/{controllable-state-DXsYJ3yl.d.mts → controllable-state-BtoxKahh.d.mts} +0 -0
- /package/dist/{pdf-source-COxN3A1l.d.mts → pdf-source-BYFK2Pks.d.mts} +0 -0
- /package/dist/{pdf-source-C52YE8tp.mjs → pdf-source-zu1LkCeE.mjs} +0 -0
|
@@ -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,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,17 @@
|
|
|
1
|
+
//#region src/VisuallyHidden.vue.d.ts
|
|
2
|
+
declare var __VLS_1: {};
|
|
3
|
+
type __VLS_Slots = {} & {
|
|
4
|
+
default?: (props: typeof __VLS_1) => any;
|
|
5
|
+
};
|
|
6
|
+
declare const __VLS_base: import("vue").DefineComponent<{}, {
|
|
7
|
+
element: Readonly<import("vue").ShallowRef<HTMLSpanElement | null>>;
|
|
8
|
+
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
9
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
10
|
+
declare const _default: typeof __VLS_export;
|
|
11
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
12
|
+
new (): {
|
|
13
|
+
$slots: S;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
//#endregion
|
|
17
|
+
export { _default as t };
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import './style.css';
|
|
2
|
-
import { t as _plugin_vue_export_helper_default } from "./_plugin-vue_export-helper-
|
|
2
|
+
import { t as _plugin_vue_export_helper_default } from "./_plugin-vue_export-helper-BOaGB7Aw.mjs";
|
|
3
3
|
import { createElementBlock, defineComponent, openBlock, renderSlot, useTemplateRef } from "vue";
|
|
4
4
|
//#endregion
|
|
5
5
|
//#region src/VisuallyHidden.vue
|
|
6
|
-
var VisuallyHidden_default =
|
|
6
|
+
var VisuallyHidden_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
|
|
7
7
|
__name: "VisuallyHidden",
|
|
8
8
|
setup(__props, { expose: __expose }) {
|
|
9
9
|
const element = useTemplateRef("element");
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as _default } from "./visually-hidden-
|
|
1
|
+
import { t as _default } from "./visually-hidden-BOqfqBFt.mjs";
|
|
2
2
|
export { _default as VisuallyHidden };
|
package/dist/visually-hidden.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as VisuallyHidden_default } from "./visually-hidden-
|
|
1
|
+
import { t as VisuallyHidden_default } from "./visually-hidden-BwDOx5yf.mjs";
|
|
2
2
|
export { VisuallyHidden_default as VisuallyHidden };
|