keystra 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/LICENSE +21 -0
- package/README.md +78 -2
- package/dist/chunk-2BLQUBG7.js +816 -0
- package/dist/chunk-77GGU3AX.js +135 -0
- package/dist/chunk-KDOJARJD.js +29 -0
- package/dist/chunk-LYYUOTBP.js +125 -0
- package/dist/chunk-S5YVXIMA.js +61 -0
- package/dist/chunk-SSIKGDD4.js +255 -0
- package/dist/chunk-U7YK6PPT.js +1729 -0
- package/dist/chunk-XFNYIPOW.js +253 -0
- package/dist/chunk-ZOJRXXQN.js +308 -0
- package/dist/core.cjs +2861 -0
- package/dist/core.d.cts +41 -0
- package/dist/core.d.ts +41 -0
- package/dist/core.js +7 -0
- package/dist/engine-COPfXXTp.d.ts +229 -0
- package/dist/engine-DcbkGRgz.d.cts +229 -0
- package/dist/focus.cjs +374 -0
- package/dist/focus.d.cts +74 -0
- package/dist/focus.d.ts +74 -0
- package/dist/focus.js +2 -0
- package/dist/index-IZkcWAXP.d.cts +395 -0
- package/dist/index-IZkcWAXP.d.ts +395 -0
- package/dist/index.cjs +3745 -0
- package/dist/index.d.cts +9 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.global.js +1 -0
- package/dist/index.js +9 -0
- package/dist/layout-C2-n8aUI.d.ts +17 -0
- package/dist/layout-CBzD-87D.d.cts +17 -0
- package/dist/navigation.cjs +293 -0
- package/dist/navigation.d.cts +33 -0
- package/dist/navigation.d.ts +33 -0
- package/dist/navigation.js +2 -0
- package/dist/patterns.cjs +3505 -0
- package/dist/patterns.d.cts +12 -0
- package/dist/patterns.d.ts +12 -0
- package/dist/patterns.js +7 -0
- package/dist/roving-focus-CT7Dm6di.d.ts +63 -0
- package/dist/roving-focus-ZQ0v7hLe.d.cts +63 -0
- package/dist/selection.cjs +266 -0
- package/dist/selection.d.cts +43 -0
- package/dist/selection.d.ts +43 -0
- package/dist/selection.js +2 -0
- package/dist/utils-CkveVeIJ.d.cts +18 -0
- package/dist/utils-JmPZTlys.d.ts +18 -0
- package/dist/utils.cjs +195 -0
- package/dist/utils.d.cts +3 -0
- package/dist/utils.d.ts +3 -0
- package/dist/utils.js +2 -0
- package/package.json +147 -4
|
@@ -0,0 +1,3505 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/utils/dom.ts
|
|
4
|
+
function hasDom() {
|
|
5
|
+
return typeof globalThis !== "undefined" && typeof globalThis.document !== "undefined";
|
|
6
|
+
}
|
|
7
|
+
function resolveElement(target, root = null) {
|
|
8
|
+
if (target == null) return null;
|
|
9
|
+
if (typeof target === "function") {
|
|
10
|
+
return resolveElement(target(), root);
|
|
11
|
+
}
|
|
12
|
+
if (typeof target === "string") {
|
|
13
|
+
const scope = root ?? (hasDom() ? document : null);
|
|
14
|
+
if (!scope) return null;
|
|
15
|
+
try {
|
|
16
|
+
return scope.querySelector(target);
|
|
17
|
+
} catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return target;
|
|
22
|
+
}
|
|
23
|
+
function queryItems(container, selector) {
|
|
24
|
+
if (!container) return [];
|
|
25
|
+
if (typeof selector === "function") {
|
|
26
|
+
const result = selector(container);
|
|
27
|
+
return Array.from(result);
|
|
28
|
+
}
|
|
29
|
+
if (!selector) return [];
|
|
30
|
+
try {
|
|
31
|
+
return Array.from(container.querySelectorAll(selector));
|
|
32
|
+
} catch {
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
var DISABLED_SELECTOR_DEFAULT = "[disabled],[aria-disabled='true']";
|
|
37
|
+
function isDisabled(element, selector = DISABLED_SELECTOR_DEFAULT) {
|
|
38
|
+
if (!selector) return false;
|
|
39
|
+
const html = element;
|
|
40
|
+
if (html.disabled === true) return true;
|
|
41
|
+
if (element.getAttribute("aria-disabled") === "true") return true;
|
|
42
|
+
if (selector === DISABLED_SELECTOR_DEFAULT) return element.hasAttribute("disabled");
|
|
43
|
+
try {
|
|
44
|
+
return element.matches(selector);
|
|
45
|
+
} catch {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function isHidden(element) {
|
|
50
|
+
const html = element;
|
|
51
|
+
const probe = html;
|
|
52
|
+
if (typeof probe.checkVisibility === "function") {
|
|
53
|
+
try {
|
|
54
|
+
return !probe.checkVisibility.call(html, { checkOpacity: false, checkVisibilityCSS: true });
|
|
55
|
+
} catch {
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
let node = html;
|
|
59
|
+
while (node && node.nodeType === 1) {
|
|
60
|
+
if (node.hidden === true) return true;
|
|
61
|
+
if (node.style && node.style.display === "none") return true;
|
|
62
|
+
node = node.parentElement;
|
|
63
|
+
}
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
var EDITABLE_SELECTOR_DEFAULT = "input:not([type='hidden']):not([readonly]),textarea:not([readonly]),select,[contenteditable=''],[contenteditable='true'],[iscontenteditable]";
|
|
67
|
+
function isEditableTarget(target, extraSelector) {
|
|
68
|
+
if (!target || typeof target.matches !== "function") return false;
|
|
69
|
+
const element = target;
|
|
70
|
+
const selector = extraSelector ? `${EDITABLE_SELECTOR_DEFAULT},${extraSelector}` : EDITABLE_SELECTOR_DEFAULT;
|
|
71
|
+
try {
|
|
72
|
+
if (element.matches(selector)) return true;
|
|
73
|
+
} catch {
|
|
74
|
+
}
|
|
75
|
+
const html = element;
|
|
76
|
+
return html.isContentEditable === true;
|
|
77
|
+
}
|
|
78
|
+
function getText(element) {
|
|
79
|
+
const label = element.getAttribute("aria-label") ?? element.textContent ?? "";
|
|
80
|
+
return label.replace(/\s+/g, " ").trim();
|
|
81
|
+
}
|
|
82
|
+
function setAttribute(element, name, value) {
|
|
83
|
+
if (value === null) element.removeAttribute(name);
|
|
84
|
+
else if (element.getAttribute(name) !== value) element.setAttribute(name, value);
|
|
85
|
+
}
|
|
86
|
+
function toggleClass(element, name, on) {
|
|
87
|
+
if (!name) return;
|
|
88
|
+
const html = element;
|
|
89
|
+
if (typeof html.classList?.toggle === "function") html.classList.toggle(name, on);
|
|
90
|
+
}
|
|
91
|
+
function focusElement(element, preventScroll = true) {
|
|
92
|
+
if (typeof element.focus !== "function") return false;
|
|
93
|
+
try {
|
|
94
|
+
element.focus({ preventScroll });
|
|
95
|
+
} catch {
|
|
96
|
+
try {
|
|
97
|
+
element.focus();
|
|
98
|
+
} catch {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
function contains(parent, child) {
|
|
105
|
+
if (!parent || !child) return false;
|
|
106
|
+
if (parent === child) return true;
|
|
107
|
+
if (typeof parent.contains === "function") return parent.contains(child);
|
|
108
|
+
let node = child;
|
|
109
|
+
while (node) {
|
|
110
|
+
if (node === parent) return true;
|
|
111
|
+
node = node.parentNode;
|
|
112
|
+
}
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
function resolveEventTarget(target) {
|
|
116
|
+
if (target == null) return null;
|
|
117
|
+
if (typeof target === "string") return resolveElement(target);
|
|
118
|
+
if (typeof target === "function") return resolveEventTarget(target());
|
|
119
|
+
return target;
|
|
120
|
+
}
|
|
121
|
+
var idCounter = 0;
|
|
122
|
+
function ensureId(element, prefix) {
|
|
123
|
+
const existing = element.id;
|
|
124
|
+
if (existing) return existing;
|
|
125
|
+
idCounter += 1;
|
|
126
|
+
const generated = `${prefix}-${idCounter}`;
|
|
127
|
+
element.id = generated;
|
|
128
|
+
return generated;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// src/focus/roving-focus.ts
|
|
132
|
+
var RovingTabindex = class {
|
|
133
|
+
constructor(options = {}) {
|
|
134
|
+
this.current = /* @__PURE__ */ new WeakMap();
|
|
135
|
+
this.originals = /* @__PURE__ */ new Map();
|
|
136
|
+
this.active = null;
|
|
137
|
+
this.respectNegative = options.respectNegative !== false;
|
|
138
|
+
this.disabled = options.disabled === true;
|
|
139
|
+
}
|
|
140
|
+
get activeElement() {
|
|
141
|
+
return this.active;
|
|
142
|
+
}
|
|
143
|
+
sync(element, others) {
|
|
144
|
+
if (this.disabled) return;
|
|
145
|
+
const desired = element ? this.desiredFor(element) : null;
|
|
146
|
+
for (let i = 0; i < others.length; i++) {
|
|
147
|
+
const item = others[i];
|
|
148
|
+
if (item === element) continue;
|
|
149
|
+
this.write(item, "-1");
|
|
150
|
+
}
|
|
151
|
+
this.active = element;
|
|
152
|
+
if (element && desired !== null) this.write(element, desired);
|
|
153
|
+
this.forgetDetached(others.length);
|
|
154
|
+
}
|
|
155
|
+
move(element) {
|
|
156
|
+
if (this.disabled) return;
|
|
157
|
+
const desired = this.desiredFor(element);
|
|
158
|
+
if (this.active && this.active !== element) this.write(this.active, "-1");
|
|
159
|
+
this.active = element;
|
|
160
|
+
this.write(element, desired);
|
|
161
|
+
}
|
|
162
|
+
forgetDetached(live) {
|
|
163
|
+
if (this.originals.size <= live) return;
|
|
164
|
+
for (const element of this.originals.keys()) {
|
|
165
|
+
if (!element.isConnected) this.originals.delete(element);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
authoredTabStop(items) {
|
|
169
|
+
for (let i = 0; i < items.length; i++) {
|
|
170
|
+
const item = items[i];
|
|
171
|
+
const authored = this.originals.has(item) ? this.originals.get(item) : item.getAttribute("tabindex");
|
|
172
|
+
if (authored === "0") return item;
|
|
173
|
+
}
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
apply(element) {
|
|
177
|
+
if (this.disabled) return;
|
|
178
|
+
this.active = element;
|
|
179
|
+
if (element) this.write(element, this.desiredFor(element));
|
|
180
|
+
}
|
|
181
|
+
desiredFor(element) {
|
|
182
|
+
if (this.respectNegative) {
|
|
183
|
+
const authored = this.originals.has(element) ? this.originals.get(element) : element.getAttribute("tabindex");
|
|
184
|
+
if (authored === "-1") return "-1";
|
|
185
|
+
}
|
|
186
|
+
return "0";
|
|
187
|
+
}
|
|
188
|
+
write(element, value) {
|
|
189
|
+
if (this.current.get(element) === value) return;
|
|
190
|
+
if (!this.originals.has(element)) this.originals.set(element, element.getAttribute("tabindex"));
|
|
191
|
+
element.setAttribute("tabindex", value);
|
|
192
|
+
this.current.set(element, value);
|
|
193
|
+
}
|
|
194
|
+
restore() {
|
|
195
|
+
for (const [element, value] of this.originals) {
|
|
196
|
+
if (value === null) element.removeAttribute("tabindex");
|
|
197
|
+
else element.setAttribute("tabindex", value);
|
|
198
|
+
this.current.delete(element);
|
|
199
|
+
}
|
|
200
|
+
this.originals.clear();
|
|
201
|
+
this.active = null;
|
|
202
|
+
}
|
|
203
|
+
get touched() {
|
|
204
|
+
return this.originals.size;
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
// src/focus/focus-manager.ts
|
|
209
|
+
var FocusManager = class {
|
|
210
|
+
constructor(options = {}) {
|
|
211
|
+
this.items = [];
|
|
212
|
+
this.activeElement = null;
|
|
213
|
+
this.activeIndex = -1;
|
|
214
|
+
this.outsideElement = null;
|
|
215
|
+
this.lastInsideElement = null;
|
|
216
|
+
this.lastKnownIndex = 0;
|
|
217
|
+
this.cleared = false;
|
|
218
|
+
this.started = false;
|
|
219
|
+
this.container = null;
|
|
220
|
+
this.markedOption = null;
|
|
221
|
+
this.options = options;
|
|
222
|
+
this.virtual = options.virtual === true;
|
|
223
|
+
const roving = options.roving;
|
|
224
|
+
this.rovingEnabled = roving !== false && (typeof roving !== "object" || roving.enabled !== false);
|
|
225
|
+
this.roving = new RovingTabindex({
|
|
226
|
+
disabled: !this.rovingEnabled,
|
|
227
|
+
respectNegative: typeof roving === "object" ? roving.force !== true : true
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
get element() {
|
|
231
|
+
return this.activeElement;
|
|
232
|
+
}
|
|
233
|
+
get index() {
|
|
234
|
+
return this.activeIndex;
|
|
235
|
+
}
|
|
236
|
+
get focusedIndex() {
|
|
237
|
+
if (this.activeIndex < 0) return -1;
|
|
238
|
+
if (this.virtual) return this.activeIndex;
|
|
239
|
+
if (typeof document === "undefined") return this.activeIndex;
|
|
240
|
+
const active = document.activeElement;
|
|
241
|
+
const current = this.activeElement;
|
|
242
|
+
if (!current) return -1;
|
|
243
|
+
if (active === current) return this.activeIndex;
|
|
244
|
+
if (active && contains(current, active)) return this.activeIndex;
|
|
245
|
+
return -1;
|
|
246
|
+
}
|
|
247
|
+
get previousElement() {
|
|
248
|
+
return this.outsideElement;
|
|
249
|
+
}
|
|
250
|
+
get lastFocused() {
|
|
251
|
+
return this.lastInsideElement;
|
|
252
|
+
}
|
|
253
|
+
sync(items, container) {
|
|
254
|
+
const previous = this.activeElement;
|
|
255
|
+
if (this.activeIndex >= 0) this.lastKnownIndex = this.activeIndex;
|
|
256
|
+
this.items = items;
|
|
257
|
+
if (container !== void 0) this.container = container;
|
|
258
|
+
if (previous) {
|
|
259
|
+
const index = items.indexOf(previous);
|
|
260
|
+
if (index !== -1 && this.isUsable(previous)) {
|
|
261
|
+
this.activeIndex = index;
|
|
262
|
+
this.promote(previous, items);
|
|
263
|
+
return index;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
const fromDocument = previous || !this.cleared ? this.readActiveFromDocument(container) : -1;
|
|
267
|
+
if (fromDocument >= 0) {
|
|
268
|
+
const element = items[fromDocument];
|
|
269
|
+
this.activeElement = element;
|
|
270
|
+
this.activeIndex = fromDocument;
|
|
271
|
+
this.lastInsideElement = element;
|
|
272
|
+
this.lastKnownIndex = fromDocument;
|
|
273
|
+
this.promote(element, items);
|
|
274
|
+
return fromDocument;
|
|
275
|
+
}
|
|
276
|
+
this.activeElement = null;
|
|
277
|
+
this.activeIndex = -1;
|
|
278
|
+
let candidate = null;
|
|
279
|
+
if (previous && items.length > 0) {
|
|
280
|
+
const previousIndex = this.lastKnownIndex;
|
|
281
|
+
candidate = items[Math.min(previousIndex, items.length - 1)] ?? items[0] ?? null;
|
|
282
|
+
} else if (!this.started && items.length > 0) {
|
|
283
|
+
candidate = this.roving.authoredTabStop(items) ?? items[0] ?? null;
|
|
284
|
+
const index = candidate ? items.indexOf(candidate) : -1;
|
|
285
|
+
if (index > 0 && candidate && this.isUsable(candidate)) {
|
|
286
|
+
this.activeElement = candidate;
|
|
287
|
+
this.activeIndex = index;
|
|
288
|
+
this.lastInsideElement = candidate;
|
|
289
|
+
this.lastKnownIndex = index;
|
|
290
|
+
}
|
|
291
|
+
} else {
|
|
292
|
+
candidate = items[0] ?? null;
|
|
293
|
+
}
|
|
294
|
+
this.started = true;
|
|
295
|
+
this.promote(candidate, items);
|
|
296
|
+
return this.activeIndex;
|
|
297
|
+
}
|
|
298
|
+
promote(element, items) {
|
|
299
|
+
this.roving.sync(this.virtual ? null : element, items);
|
|
300
|
+
this.writeActiveDescendant(this.virtual ? element : null);
|
|
301
|
+
}
|
|
302
|
+
writeActiveDescendant(element) {
|
|
303
|
+
if (!this.virtual) return;
|
|
304
|
+
this.markOption(element);
|
|
305
|
+
const owner = this.options.descendantOwner?.() ?? null;
|
|
306
|
+
if (!owner) return;
|
|
307
|
+
if (!element) {
|
|
308
|
+
if (owner.hasAttribute("aria-activedescendant")) owner.removeAttribute("aria-activedescendant");
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
const id = ensureId(element, "keystra-item");
|
|
312
|
+
if (owner.getAttribute("aria-activedescendant") !== id) {
|
|
313
|
+
owner.setAttribute("aria-activedescendant", id);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
markOption(element) {
|
|
317
|
+
if (this.markedOption === element) return;
|
|
318
|
+
if (this.markedOption) this.markedOption.setAttribute("aria-selected", "false");
|
|
319
|
+
this.markedOption = element?.getAttribute("role") === "option" ? element : null;
|
|
320
|
+
if (this.markedOption) this.markedOption.setAttribute("aria-selected", "true");
|
|
321
|
+
}
|
|
322
|
+
observe(element, container) {
|
|
323
|
+
if (!element) return;
|
|
324
|
+
if (container && !contains(container, element)) return;
|
|
325
|
+
const index = this.items.indexOf(element);
|
|
326
|
+
if (index === -1) return;
|
|
327
|
+
const item = this.items[index];
|
|
328
|
+
this.activeElement = item;
|
|
329
|
+
this.activeIndex = index;
|
|
330
|
+
this.lastInsideElement = item;
|
|
331
|
+
this.lastKnownIndex = index;
|
|
332
|
+
if (!this.virtual) this.roving.apply(item);
|
|
333
|
+
this.writeActiveDescendant(item);
|
|
334
|
+
}
|
|
335
|
+
focusIndex(index, action = null, preventScroll, moveFocus = true) {
|
|
336
|
+
const items = this.items;
|
|
337
|
+
if (index < 0 || index >= items.length) return null;
|
|
338
|
+
const element = items[index];
|
|
339
|
+
if (!this.isUsable(element)) return null;
|
|
340
|
+
const previous = this.activeElement;
|
|
341
|
+
if (previous && previous !== element) {
|
|
342
|
+
const previousIndex = items.indexOf(previous);
|
|
343
|
+
if (previousIndex !== -1) this.options.onBlur?.(previous, previousIndex);
|
|
344
|
+
}
|
|
345
|
+
this.captureOutside(previous);
|
|
346
|
+
this.activeElement = element;
|
|
347
|
+
this.activeIndex = index;
|
|
348
|
+
this.cleared = false;
|
|
349
|
+
this.lastInsideElement = element;
|
|
350
|
+
this.lastKnownIndex = index;
|
|
351
|
+
if (this.virtual) this.writeActiveDescendant(element);
|
|
352
|
+
else this.roving.move(element);
|
|
353
|
+
if (moveFocus && !this.virtual) {
|
|
354
|
+
focusElement(element, preventScroll ?? this.options.preventScroll !== false);
|
|
355
|
+
}
|
|
356
|
+
this.options.onFocus?.(element, index, action);
|
|
357
|
+
return element;
|
|
358
|
+
}
|
|
359
|
+
setActiveIndex(index) {
|
|
360
|
+
const element = index >= 0 ? this.items[index] ?? null : null;
|
|
361
|
+
this.activeElement = element;
|
|
362
|
+
this.activeIndex = element ? index : -1;
|
|
363
|
+
if (element) {
|
|
364
|
+
this.cleared = false;
|
|
365
|
+
this.lastInsideElement = element;
|
|
366
|
+
this.lastKnownIndex = index;
|
|
367
|
+
} else {
|
|
368
|
+
this.cleared = index < 0;
|
|
369
|
+
}
|
|
370
|
+
this.promote(element ?? this.items[0] ?? null, this.items);
|
|
371
|
+
}
|
|
372
|
+
focusElement(element, action = null) {
|
|
373
|
+
if (!element) return false;
|
|
374
|
+
const index = this.items.indexOf(element);
|
|
375
|
+
if (index === -1) return false;
|
|
376
|
+
return this.focusIndex(index, action) !== null;
|
|
377
|
+
}
|
|
378
|
+
restore() {
|
|
379
|
+
const target = this.outsideElement;
|
|
380
|
+
if (target && typeof target.focus === "function" && !isHidden(target) && !isDisabled(target)) {
|
|
381
|
+
focusElement(target, this.options.preventScroll !== false);
|
|
382
|
+
return true;
|
|
383
|
+
}
|
|
384
|
+
if (this.virtual) return false;
|
|
385
|
+
if (this.lastInsideElement && this.items.indexOf(this.lastInsideElement) !== -1) {
|
|
386
|
+
return this.focusElement(this.lastInsideElement);
|
|
387
|
+
}
|
|
388
|
+
return this.focusIndex(this.activeIndex) !== null;
|
|
389
|
+
}
|
|
390
|
+
remember(element) {
|
|
391
|
+
if (element) this.outsideElement = element;
|
|
392
|
+
}
|
|
393
|
+
clear() {
|
|
394
|
+
this.activeElement = null;
|
|
395
|
+
this.activeIndex = -1;
|
|
396
|
+
this.items = [];
|
|
397
|
+
this.lastInsideElement = null;
|
|
398
|
+
this.lastKnownIndex = 0;
|
|
399
|
+
}
|
|
400
|
+
dispose() {
|
|
401
|
+
this.writeActiveDescendant(null);
|
|
402
|
+
this.roving.restore();
|
|
403
|
+
this.clear();
|
|
404
|
+
this.outsideElement = null;
|
|
405
|
+
}
|
|
406
|
+
isUsable(element) {
|
|
407
|
+
if (!element) return false;
|
|
408
|
+
if (isDisabled(element, this.options.disabledSelector ?? null)) return false;
|
|
409
|
+
if (this.options.skipHidden !== false && isHidden(element)) return false;
|
|
410
|
+
return true;
|
|
411
|
+
}
|
|
412
|
+
captureOutside(current) {
|
|
413
|
+
if (typeof document === "undefined") return;
|
|
414
|
+
const active = document.activeElement;
|
|
415
|
+
if (!active || active === document.body || active === document.documentElement) return;
|
|
416
|
+
if (current && active === current) return;
|
|
417
|
+
if (this.items.indexOf(active) !== -1) return;
|
|
418
|
+
if (this.container && contains(this.container, active)) return;
|
|
419
|
+
this.outsideElement = active;
|
|
420
|
+
}
|
|
421
|
+
readActiveFromDocument(container) {
|
|
422
|
+
if (typeof document === "undefined") return -1;
|
|
423
|
+
const active = document.activeElement;
|
|
424
|
+
if (!active || active === document.body) return -1;
|
|
425
|
+
const index = this.items.indexOf(active);
|
|
426
|
+
if (index !== -1) return index;
|
|
427
|
+
if (container) {
|
|
428
|
+
for (let i = 0; i < this.items.length; i++) {
|
|
429
|
+
if (contains(this.items[i], active)) return i;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
return -1;
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
// src/selection/selection-manager.ts
|
|
437
|
+
var SelectionManager = class {
|
|
438
|
+
constructor(options = {}) {
|
|
439
|
+
this.selectedItems = [];
|
|
440
|
+
this.itemsValue = [];
|
|
441
|
+
this.provider = null;
|
|
442
|
+
this.anchorElement = null;
|
|
443
|
+
this.touched = /* @__PURE__ */ new Set();
|
|
444
|
+
this.onDiff = null;
|
|
445
|
+
this.needsInitial = true;
|
|
446
|
+
this.options = options;
|
|
447
|
+
this.modeValue = options.mode ?? "single";
|
|
448
|
+
this.attribute = options.attribute ?? null;
|
|
449
|
+
this.selectedClass = options.selectedClass;
|
|
450
|
+
this.aria = options.aria === true;
|
|
451
|
+
}
|
|
452
|
+
get mode() {
|
|
453
|
+
return this.modeValue;
|
|
454
|
+
}
|
|
455
|
+
get selected() {
|
|
456
|
+
return this.selectedItems;
|
|
457
|
+
}
|
|
458
|
+
get items() {
|
|
459
|
+
return this.provider ? this.provider() : this.itemsValue;
|
|
460
|
+
}
|
|
461
|
+
get selectedIndexes() {
|
|
462
|
+
const result = [];
|
|
463
|
+
for (let i = 0; i < this.selectedItems.length; i++) {
|
|
464
|
+
const index = this.items.indexOf(this.selectedItems[i]);
|
|
465
|
+
if (index !== -1) result.push(index);
|
|
466
|
+
}
|
|
467
|
+
return result.sort((a, b) => a - b);
|
|
468
|
+
}
|
|
469
|
+
get anchor() {
|
|
470
|
+
return this.anchorElement;
|
|
471
|
+
}
|
|
472
|
+
get size() {
|
|
473
|
+
return this.selectedItems.length;
|
|
474
|
+
}
|
|
475
|
+
bindItems(provider) {
|
|
476
|
+
this.provider = provider;
|
|
477
|
+
}
|
|
478
|
+
setDiffHandler(handler) {
|
|
479
|
+
this.onDiff = handler;
|
|
480
|
+
}
|
|
481
|
+
sync(items) {
|
|
482
|
+
if (this.itemsValue === items && !this.needsInitial) return;
|
|
483
|
+
this.itemsValue = items;
|
|
484
|
+
const current = this.items;
|
|
485
|
+
if (this.needsInitial) {
|
|
486
|
+
this.needsInitial = false;
|
|
487
|
+
this.applyInitial(this.options.initial);
|
|
488
|
+
}
|
|
489
|
+
if (this.aria || this.attribute || this.selectedClass) {
|
|
490
|
+
for (let i = 0; i < current.length; i++) {
|
|
491
|
+
const element = current[i];
|
|
492
|
+
this.paint(element, this.selectedItems.indexOf(element) !== -1);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
if (this.touched.size > current.length) {
|
|
496
|
+
for (const element of this.touched) {
|
|
497
|
+
if (!element.isConnected && this.selectedItems.indexOf(element) === -1) this.touched.delete(element);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
if (this.selectedItems.length === 0 && this.anchorElement === null) return;
|
|
501
|
+
let changed = false;
|
|
502
|
+
const kept = [];
|
|
503
|
+
for (const element of this.selectedItems) {
|
|
504
|
+
if (current.indexOf(element) !== -1) kept.push(element);
|
|
505
|
+
else {
|
|
506
|
+
this.paint(element, false);
|
|
507
|
+
changed = true;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
if (this.anchorElement && current.indexOf(this.anchorElement) === -1) {
|
|
511
|
+
this.anchorElement = kept[0] ?? null;
|
|
512
|
+
changed = true;
|
|
513
|
+
}
|
|
514
|
+
if (changed) {
|
|
515
|
+
const previous = this.selectedItems;
|
|
516
|
+
this.selectedItems = kept;
|
|
517
|
+
this.options.onChange?.(this.selectedItems);
|
|
518
|
+
this.onDiff?.(this.selectedItems, [], previous.filter((element) => kept.indexOf(element) === -1));
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
applyInitial(initial) {
|
|
522
|
+
if (initial == null) return;
|
|
523
|
+
const current = this.items;
|
|
524
|
+
if (typeof initial === "number") {
|
|
525
|
+
const element = current[initial];
|
|
526
|
+
if (element) this.select(element);
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
const list = Array.isArray(initial) ? initial : [initial];
|
|
530
|
+
for (const element of list) if (element) this.select(element, this.modeValue !== "single");
|
|
531
|
+
}
|
|
532
|
+
select(elementOrIndex, extend = false) {
|
|
533
|
+
if (this.modeValue === "none") return;
|
|
534
|
+
const element = this.resolve(elementOrIndex);
|
|
535
|
+
if (!element) return;
|
|
536
|
+
if (this.modeValue === "single" || !extend && this.modeValue !== "multiple") {
|
|
537
|
+
if (this.selectedItems.length === 1 && this.selectedItems[0] === element) {
|
|
538
|
+
this.anchorElement = element;
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
const removed = this.selectedItems.slice();
|
|
542
|
+
this.selectedItems = [element];
|
|
543
|
+
this.anchorElement = element;
|
|
544
|
+
for (const item of removed) this.paint(item, false);
|
|
545
|
+
this.paint(element, true);
|
|
546
|
+
this.emit(removed);
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
if (this.modeValue === "range" && extend && this.anchorElement) {
|
|
550
|
+
this.selectRange(this.anchorElement, element);
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
if (this.selectedItems.indexOf(element) === -1) {
|
|
554
|
+
const previous = this.selectedItems.slice();
|
|
555
|
+
this.selectedItems.push(element);
|
|
556
|
+
this.paint(element, true);
|
|
557
|
+
this.emit(previous);
|
|
558
|
+
}
|
|
559
|
+
if (!this.anchorElement) this.anchorElement = element;
|
|
560
|
+
}
|
|
561
|
+
toggle(elementOrIndex) {
|
|
562
|
+
if (this.modeValue === "none") return;
|
|
563
|
+
const element = this.resolve(elementOrIndex);
|
|
564
|
+
if (!element) return;
|
|
565
|
+
if (this.modeValue === "single") {
|
|
566
|
+
if (this.selectedItems[0] === element) this.clear();
|
|
567
|
+
else this.select(element);
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
const previous = this.selectedItems.slice();
|
|
571
|
+
const index = previous.indexOf(element);
|
|
572
|
+
if (index === -1) {
|
|
573
|
+
this.selectedItems.push(element);
|
|
574
|
+
this.paint(element, true);
|
|
575
|
+
} else {
|
|
576
|
+
this.selectedItems.splice(index, 1);
|
|
577
|
+
this.paint(element, false);
|
|
578
|
+
if (this.anchorElement === element) this.anchorElement = this.selectedItems[0] ?? null;
|
|
579
|
+
}
|
|
580
|
+
this.emit(previous);
|
|
581
|
+
if (!this.anchorElement && index === -1) this.anchorElement = element;
|
|
582
|
+
}
|
|
583
|
+
deselect(elementOrIndex) {
|
|
584
|
+
const element = this.resolve(elementOrIndex);
|
|
585
|
+
if (!element) return;
|
|
586
|
+
const index = this.selectedItems.indexOf(element);
|
|
587
|
+
if (index === -1) return;
|
|
588
|
+
const previous = this.selectedItems.slice();
|
|
589
|
+
this.selectedItems.splice(index, 1);
|
|
590
|
+
this.paint(element, false);
|
|
591
|
+
if (this.anchorElement === element) this.anchorElement = this.selectedItems[0] ?? null;
|
|
592
|
+
this.emit(previous);
|
|
593
|
+
}
|
|
594
|
+
selectRange(from, to) {
|
|
595
|
+
if (this.modeValue === "none") return;
|
|
596
|
+
const start = this.resolve(from);
|
|
597
|
+
const end = this.resolve(to);
|
|
598
|
+
if (!start || !end) return;
|
|
599
|
+
let a = this.items.indexOf(start);
|
|
600
|
+
let b = this.items.indexOf(end);
|
|
601
|
+
if (a === -1 || b === -1) return;
|
|
602
|
+
if (a > b) {
|
|
603
|
+
const swap = a;
|
|
604
|
+
a = b;
|
|
605
|
+
b = swap;
|
|
606
|
+
}
|
|
607
|
+
const previous = this.selectedItems.slice();
|
|
608
|
+
const next = [];
|
|
609
|
+
for (let i = a; i <= b; i++) {
|
|
610
|
+
const element = this.items[i];
|
|
611
|
+
if (element) next.push(element);
|
|
612
|
+
}
|
|
613
|
+
this.selectedItems = next;
|
|
614
|
+
this.anchorElement = start;
|
|
615
|
+
for (const element of previous) if (next.indexOf(element) === -1) this.paint(element, false);
|
|
616
|
+
for (const element of next) this.paint(element, true);
|
|
617
|
+
this.emit(previous);
|
|
618
|
+
}
|
|
619
|
+
selectAll() {
|
|
620
|
+
if (this.modeValue === "none" || this.modeValue === "single") {
|
|
621
|
+
if (this.modeValue === "single" && this.items[0]) this.select(this.items[0]);
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
const previous = this.selectedItems.slice();
|
|
625
|
+
this.selectedItems = this.items.slice();
|
|
626
|
+
for (const element of this.selectedItems) this.paint(element, true);
|
|
627
|
+
this.anchorElement = this.items[0] ?? null;
|
|
628
|
+
this.emit(previous);
|
|
629
|
+
}
|
|
630
|
+
clear() {
|
|
631
|
+
if (this.selectedItems.length === 0 && !this.anchorElement) return;
|
|
632
|
+
const previous = this.selectedItems.slice();
|
|
633
|
+
this.selectedItems = [];
|
|
634
|
+
this.anchorElement = null;
|
|
635
|
+
for (const element of previous) this.paint(element, false);
|
|
636
|
+
this.emit(previous);
|
|
637
|
+
}
|
|
638
|
+
isSelected(elementOrIndex) {
|
|
639
|
+
const element = this.resolve(elementOrIndex);
|
|
640
|
+
return element !== null && this.selectedItems.indexOf(element) !== -1;
|
|
641
|
+
}
|
|
642
|
+
indexOfSelected(element) {
|
|
643
|
+
return this.selectedItems.indexOf(element);
|
|
644
|
+
}
|
|
645
|
+
setMode(mode) {
|
|
646
|
+
if (mode === this.modeValue) return;
|
|
647
|
+
this.modeValue = mode;
|
|
648
|
+
if (mode === "single" && this.selectedItems.length > 1) {
|
|
649
|
+
const previous = this.selectedItems.slice();
|
|
650
|
+
this.selectedItems = previous.slice(0, 1);
|
|
651
|
+
for (const element of previous) if (this.selectedItems.indexOf(element) === -1) this.paint(element, false);
|
|
652
|
+
this.emit(previous);
|
|
653
|
+
}
|
|
654
|
+
if (mode === "none") this.clear();
|
|
655
|
+
}
|
|
656
|
+
dispose() {
|
|
657
|
+
this.provider = null;
|
|
658
|
+
for (const element of this.touched) {
|
|
659
|
+
if (this.aria) setAttribute(element, "aria-selected", null);
|
|
660
|
+
if (this.attribute) element.removeAttribute(this.attribute);
|
|
661
|
+
toggleClass(element, this.selectedClass, false);
|
|
662
|
+
}
|
|
663
|
+
this.touched.clear();
|
|
664
|
+
this.selectedItems = [];
|
|
665
|
+
this.anchorElement = null;
|
|
666
|
+
this.itemsValue = [];
|
|
667
|
+
}
|
|
668
|
+
resolve(elementOrIndex) {
|
|
669
|
+
if (elementOrIndex === void 0 || elementOrIndex === null) return null;
|
|
670
|
+
if (typeof elementOrIndex === "number") return this.items[elementOrIndex] ?? null;
|
|
671
|
+
return elementOrIndex;
|
|
672
|
+
}
|
|
673
|
+
paint(element, on) {
|
|
674
|
+
this.touched.add(element);
|
|
675
|
+
if (this.aria) setAttribute(element, "aria-selected", on ? "true" : "false");
|
|
676
|
+
if (this.attribute) setAttribute(element, this.attribute, on ? "true" : null);
|
|
677
|
+
toggleClass(element, this.selectedClass, on);
|
|
678
|
+
}
|
|
679
|
+
emit(previous) {
|
|
680
|
+
const added = this.selectedItems.filter((element) => previous.indexOf(element) === -1);
|
|
681
|
+
const removed = previous.filter((element) => this.selectedItems.indexOf(element) === -1);
|
|
682
|
+
if (added.length === 0 && removed.length === 0) return;
|
|
683
|
+
this.options.onChange?.(this.selectedItems);
|
|
684
|
+
this.onDiff?.(this.selectedItems, added, removed);
|
|
685
|
+
}
|
|
686
|
+
};
|
|
687
|
+
|
|
688
|
+
// src/navigation/directional.ts
|
|
689
|
+
function actionToDirection(action) {
|
|
690
|
+
switch (action) {
|
|
691
|
+
case "up":
|
|
692
|
+
return "up";
|
|
693
|
+
case "down":
|
|
694
|
+
return "down";
|
|
695
|
+
case "left":
|
|
696
|
+
return "left";
|
|
697
|
+
case "right":
|
|
698
|
+
return "right";
|
|
699
|
+
default:
|
|
700
|
+
return null;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
function createDirectionalStrategy(options = {}) {
|
|
704
|
+
const crossWeight = options.crossAxisWeight ?? 2;
|
|
705
|
+
return (context) => {
|
|
706
|
+
const direction = context.direction ?? actionToDirection(context.action);
|
|
707
|
+
if (!direction) return null;
|
|
708
|
+
const items = context.items;
|
|
709
|
+
const total = items.length;
|
|
710
|
+
if (total === 0) return -1;
|
|
711
|
+
const from = context.index;
|
|
712
|
+
if (from < 0 || from >= total) {
|
|
713
|
+
return direction === "down" || direction === "right" ? 0 : total - 1;
|
|
714
|
+
}
|
|
715
|
+
const current = items[from];
|
|
716
|
+
if (!current) return null;
|
|
717
|
+
const origin = context.rectOf(current);
|
|
718
|
+
const ox = origin.left + origin.width / 2;
|
|
719
|
+
const oy = origin.top + origin.height / 2;
|
|
720
|
+
const horizontal = direction === "left" || direction === "right";
|
|
721
|
+
const sign = direction === "right" || direction === "down" ? 1 : -1;
|
|
722
|
+
let bestIndex = -1;
|
|
723
|
+
let bestScore = Infinity;
|
|
724
|
+
let nearestIndex = -1;
|
|
725
|
+
let nearestDistance = Infinity;
|
|
726
|
+
for (let i = 0; i < total; i++) {
|
|
727
|
+
if (i === from) continue;
|
|
728
|
+
const element = items[i];
|
|
729
|
+
const rect = context.rectOf(element);
|
|
730
|
+
if (rect.width === 0 && rect.height === 0) continue;
|
|
731
|
+
const dx = rect.left + rect.width / 2 - ox;
|
|
732
|
+
const dy = rect.top + rect.height / 2 - oy;
|
|
733
|
+
const distance = Math.hypot(dx, dy);
|
|
734
|
+
if (distance < nearestDistance) {
|
|
735
|
+
nearestDistance = distance;
|
|
736
|
+
nearestIndex = i;
|
|
737
|
+
}
|
|
738
|
+
const primary = (horizontal ? dx : dy) * sign;
|
|
739
|
+
if (primary <= 0) continue;
|
|
740
|
+
const cross = Math.abs(horizontal ? dy : dx);
|
|
741
|
+
if (cross > primary) continue;
|
|
742
|
+
const score = primary + cross * crossWeight;
|
|
743
|
+
if (score < bestScore) {
|
|
744
|
+
bestScore = score;
|
|
745
|
+
bestIndex = i;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
if (bestIndex !== -1) return bestIndex;
|
|
749
|
+
const allowFallback = options.wrapFallback ?? context.loop;
|
|
750
|
+
return allowFallback ? nearestIndex : null;
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
// src/navigation/grid.ts
|
|
755
|
+
function computeGridInfo(context) {
|
|
756
|
+
const items = context.items;
|
|
757
|
+
const total = items.length;
|
|
758
|
+
if (total === 0) return { starts: [0], columns: 1 };
|
|
759
|
+
const starts = [0];
|
|
760
|
+
let rowTop = context.rectOf(items[0]).top;
|
|
761
|
+
let currentColumns = 1;
|
|
762
|
+
let maxColumns = 1;
|
|
763
|
+
for (let i = 1; i < total; i++) {
|
|
764
|
+
const rect = context.rectOf(items[i]);
|
|
765
|
+
if (Math.abs(rect.top - rowTop) < 2) {
|
|
766
|
+
currentColumns++;
|
|
767
|
+
continue;
|
|
768
|
+
}
|
|
769
|
+
maxColumns = Math.max(maxColumns, currentColumns);
|
|
770
|
+
currentColumns = 1;
|
|
771
|
+
rowTop = rect.top;
|
|
772
|
+
starts.push(i);
|
|
773
|
+
}
|
|
774
|
+
return { starts, columns: Math.max(maxColumns, currentColumns) };
|
|
775
|
+
}
|
|
776
|
+
function declaredInfo(total, columns) {
|
|
777
|
+
const starts = [];
|
|
778
|
+
for (let start = 0; start < total; start += columns) starts.push(start);
|
|
779
|
+
if (starts.length === 0) starts.push(0);
|
|
780
|
+
return { starts, columns };
|
|
781
|
+
}
|
|
782
|
+
function rowOf(info, index) {
|
|
783
|
+
const starts = info.starts;
|
|
784
|
+
let row = 0;
|
|
785
|
+
for (let i = 0; i < starts.length; i++) {
|
|
786
|
+
if (starts[i] <= index) row = i;
|
|
787
|
+
else break;
|
|
788
|
+
}
|
|
789
|
+
return row;
|
|
790
|
+
}
|
|
791
|
+
function rowStart(info, row) {
|
|
792
|
+
return info.starts[row] ?? 0;
|
|
793
|
+
}
|
|
794
|
+
function rowEnd(info, row, total) {
|
|
795
|
+
return row + 1 < info.starts.length ? info.starts[row + 1] - 1 : total - 1;
|
|
796
|
+
}
|
|
797
|
+
function createGridStrategy(options = {}) {
|
|
798
|
+
const maintainColumn = options.maintainColumn !== false;
|
|
799
|
+
const rowHomeEnd = options.rowHomeEnd === true;
|
|
800
|
+
const declared = options.columns && options.columns > 0 ? options.columns : 0;
|
|
801
|
+
let cachedVersion = -1;
|
|
802
|
+
let cachedItems = null;
|
|
803
|
+
let cached = { starts: [0], columns: 1 };
|
|
804
|
+
return (context) => {
|
|
805
|
+
const items = context.items;
|
|
806
|
+
const total = items.length;
|
|
807
|
+
if (total === 0) return -1;
|
|
808
|
+
const columns = declared || context.columns;
|
|
809
|
+
const index = context.index;
|
|
810
|
+
const direction = context.direction ?? directionFromAction(context.action);
|
|
811
|
+
if (!direction) {
|
|
812
|
+
if (context.action === "first") {
|
|
813
|
+
if (!rowHomeEnd || index < 0) return 0;
|
|
814
|
+
const info2 = infoFor(context, columns, total);
|
|
815
|
+
return rowStart(info2, rowOf(info2, index));
|
|
816
|
+
}
|
|
817
|
+
if (context.action === "last") {
|
|
818
|
+
if (!rowHomeEnd || index < 0) return total - 1;
|
|
819
|
+
const info2 = infoFor(context, columns, total);
|
|
820
|
+
return rowEnd(info2, rowOf(info2, index), total);
|
|
821
|
+
}
|
|
822
|
+
return null;
|
|
823
|
+
}
|
|
824
|
+
if (index < 0 || index >= total) {
|
|
825
|
+
return direction === "down" || direction === "right" ? 0 : total - 1;
|
|
826
|
+
}
|
|
827
|
+
const info = infoFor(context, columns, total);
|
|
828
|
+
const row = rowOf(info, index);
|
|
829
|
+
const start = rowStart(info, row);
|
|
830
|
+
const end = rowEnd(info, row, total);
|
|
831
|
+
if (direction === "left" || direction === "right") {
|
|
832
|
+
const next = index + (direction === "right" ? 1 : -1);
|
|
833
|
+
if (next >= start && next <= end) return next;
|
|
834
|
+
if (!context.loop) return index;
|
|
835
|
+
return direction === "right" ? start : end;
|
|
836
|
+
}
|
|
837
|
+
const rowDelta = direction === "down" ? 1 : -1;
|
|
838
|
+
let targetRow = row + rowDelta;
|
|
839
|
+
if (targetRow < 0 || targetRow >= info.starts.length) {
|
|
840
|
+
if (!context.loop) return index;
|
|
841
|
+
targetRow = targetRow < 0 ? info.starts.length - 1 : 0;
|
|
842
|
+
}
|
|
843
|
+
const targetStart = rowStart(info, targetRow);
|
|
844
|
+
const targetEnd = rowEnd(info, targetRow, total);
|
|
845
|
+
if (columns > 0 && !maintainColumn) return targetStart;
|
|
846
|
+
if (columns > 0) {
|
|
847
|
+
const column = index - start;
|
|
848
|
+
const candidate = targetStart + column;
|
|
849
|
+
return candidate <= targetEnd ? candidate : targetEnd;
|
|
850
|
+
}
|
|
851
|
+
const current = context.rectOf(items[index]);
|
|
852
|
+
return nearestInRow(context, targetStart, targetEnd, current.left + current.width / 2);
|
|
853
|
+
function infoFor(ctx, cols, count) {
|
|
854
|
+
if (cols > 0) return declaredInfo(count, cols);
|
|
855
|
+
if (cachedVersion === ctx.version && cachedItems === ctx.items) return cached;
|
|
856
|
+
cached = computeGridInfo(ctx);
|
|
857
|
+
cachedVersion = ctx.version;
|
|
858
|
+
cachedItems = ctx.items;
|
|
859
|
+
return cached;
|
|
860
|
+
}
|
|
861
|
+
};
|
|
862
|
+
}
|
|
863
|
+
function nearestInRow(context, start, end, targetX) {
|
|
864
|
+
let best = start;
|
|
865
|
+
let bestDistance = Infinity;
|
|
866
|
+
for (let i = start; i <= end; i++) {
|
|
867
|
+
const element = context.items[i];
|
|
868
|
+
if (!element) continue;
|
|
869
|
+
const rect = context.rectOf(element);
|
|
870
|
+
const distance = Math.abs(rect.left + rect.width / 2 - targetX);
|
|
871
|
+
if (distance < bestDistance) {
|
|
872
|
+
bestDistance = distance;
|
|
873
|
+
best = i;
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
return best;
|
|
877
|
+
}
|
|
878
|
+
function directionFromAction(action) {
|
|
879
|
+
switch (action) {
|
|
880
|
+
case "up":
|
|
881
|
+
case "previous":
|
|
882
|
+
return "up";
|
|
883
|
+
case "down":
|
|
884
|
+
case "next":
|
|
885
|
+
return "down";
|
|
886
|
+
case "left":
|
|
887
|
+
return "left";
|
|
888
|
+
case "right":
|
|
889
|
+
return "right";
|
|
890
|
+
default:
|
|
891
|
+
return null;
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
// src/navigation/sequential.ts
|
|
896
|
+
function findNextIndex(total, from, step, loop, clamp = true) {
|
|
897
|
+
if (total === 0 || step === 0) return -1;
|
|
898
|
+
if (from < 0 || from >= total) {
|
|
899
|
+
return step > 0 ? 0 : total - 1;
|
|
900
|
+
}
|
|
901
|
+
const next = from + step;
|
|
902
|
+
if (loop) return (next % total + total) % total;
|
|
903
|
+
if (next < 0) return clamp ? 0 : -1;
|
|
904
|
+
if (next >= total) return clamp ? total - 1 : -1;
|
|
905
|
+
return next;
|
|
906
|
+
}
|
|
907
|
+
function actionDelta(action, direction) {
|
|
908
|
+
if (direction) {
|
|
909
|
+
if (direction === "down" || direction === "right") return 1;
|
|
910
|
+
if (direction === "up" || direction === "left") return -1;
|
|
911
|
+
return 0;
|
|
912
|
+
}
|
|
913
|
+
switch (action) {
|
|
914
|
+
case "next":
|
|
915
|
+
case "down":
|
|
916
|
+
case "right":
|
|
917
|
+
return 1;
|
|
918
|
+
case "previous":
|
|
919
|
+
case "up":
|
|
920
|
+
case "left":
|
|
921
|
+
return -1;
|
|
922
|
+
default:
|
|
923
|
+
return 0;
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
function createSequentialStrategy(axis = "both") {
|
|
927
|
+
return (context) => {
|
|
928
|
+
const { action, direction, index, items, loop } = context;
|
|
929
|
+
const delta = actionDelta(action, direction);
|
|
930
|
+
if (delta === 0) return null;
|
|
931
|
+
const isVerticalAction = direction !== null ? direction === "up" || direction === "down" : action === "up" || action === "down";
|
|
932
|
+
if (axis === "vertical" && !isVerticalAction && direction !== null) return null;
|
|
933
|
+
if (axis === "horizontal" && isVerticalAction) return null;
|
|
934
|
+
return findNextIndex(items.length, index, delta, loop);
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
// src/utils/emitter.ts
|
|
939
|
+
var Emitter = class {
|
|
940
|
+
constructor() {
|
|
941
|
+
this.handlers = null;
|
|
942
|
+
}
|
|
943
|
+
on(event, handler) {
|
|
944
|
+
if (!this.handlers) this.handlers = /* @__PURE__ */ new Map();
|
|
945
|
+
let set = this.handlers.get(event);
|
|
946
|
+
if (!set) {
|
|
947
|
+
set = /* @__PURE__ */ new Set();
|
|
948
|
+
this.handlers.set(event, set);
|
|
949
|
+
}
|
|
950
|
+
set.add(handler);
|
|
951
|
+
let disposed = false;
|
|
952
|
+
return () => {
|
|
953
|
+
if (disposed) return;
|
|
954
|
+
disposed = true;
|
|
955
|
+
this.off(event, handler);
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
off(event, handler) {
|
|
959
|
+
const set = this.handlers?.get(event);
|
|
960
|
+
if (!set) return;
|
|
961
|
+
if (handler) set.delete(handler);
|
|
962
|
+
else set.clear();
|
|
963
|
+
if (set.size === 0) this.handlers?.delete(event);
|
|
964
|
+
}
|
|
965
|
+
emit(event, payload) {
|
|
966
|
+
const set = this.handlers?.get(event);
|
|
967
|
+
if (!set || set.size === 0) return;
|
|
968
|
+
for (const handler of Array.from(set)) {
|
|
969
|
+
handler(payload);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
clear() {
|
|
973
|
+
this.handlers?.clear();
|
|
974
|
+
this.handlers = null;
|
|
975
|
+
}
|
|
976
|
+
get size() {
|
|
977
|
+
if (!this.handlers) return 0;
|
|
978
|
+
let total = 0;
|
|
979
|
+
for (const set of this.handlers.values()) total += set.size;
|
|
980
|
+
return total;
|
|
981
|
+
}
|
|
982
|
+
};
|
|
983
|
+
|
|
984
|
+
// src/core/default-keys.ts
|
|
985
|
+
var NAVIGATION = {
|
|
986
|
+
first: ["Home"],
|
|
987
|
+
last: ["End"],
|
|
988
|
+
pageUp: ["PageUp"],
|
|
989
|
+
pageDown: ["PageDown"],
|
|
990
|
+
escape: ["Escape"],
|
|
991
|
+
selectAll: ["Ctrl+A", "Meta+A"]
|
|
992
|
+
};
|
|
993
|
+
var VERTICAL_KEYS = {
|
|
994
|
+
up: ["ArrowUp"],
|
|
995
|
+
down: ["ArrowDown"],
|
|
996
|
+
next: ["ArrowDown"],
|
|
997
|
+
previous: ["ArrowUp"],
|
|
998
|
+
toggle: ["Space"],
|
|
999
|
+
select: ["Enter", "Space"],
|
|
1000
|
+
...NAVIGATION
|
|
1001
|
+
};
|
|
1002
|
+
var HORIZONTAL_KEYS = {
|
|
1003
|
+
left: ["ArrowLeft"],
|
|
1004
|
+
right: ["ArrowRight"],
|
|
1005
|
+
next: ["ArrowRight"],
|
|
1006
|
+
previous: ["ArrowLeft"],
|
|
1007
|
+
toggle: ["Space"],
|
|
1008
|
+
select: ["Enter", "Space"],
|
|
1009
|
+
...NAVIGATION
|
|
1010
|
+
};
|
|
1011
|
+
var ROVING_KEYS = {
|
|
1012
|
+
up: ["ArrowUp"],
|
|
1013
|
+
down: ["ArrowDown"],
|
|
1014
|
+
left: ["ArrowLeft"],
|
|
1015
|
+
right: ["ArrowRight"],
|
|
1016
|
+
next: ["ArrowDown", "ArrowRight"],
|
|
1017
|
+
previous: ["ArrowUp", "ArrowLeft"],
|
|
1018
|
+
toggle: ["Space"],
|
|
1019
|
+
select: ["Enter", "Space"],
|
|
1020
|
+
...NAVIGATION
|
|
1021
|
+
};
|
|
1022
|
+
var TABS_KEYS_HORIZONTAL = {
|
|
1023
|
+
left: ["ArrowLeft"],
|
|
1024
|
+
right: ["ArrowRight"],
|
|
1025
|
+
next: ["ArrowRight"],
|
|
1026
|
+
previous: ["ArrowLeft"],
|
|
1027
|
+
first: ["Home"],
|
|
1028
|
+
last: ["End"],
|
|
1029
|
+
select: ["Enter", "Space"],
|
|
1030
|
+
escape: ["Escape"]
|
|
1031
|
+
};
|
|
1032
|
+
var TABS_KEYS_VERTICAL = {
|
|
1033
|
+
up: ["ArrowUp"],
|
|
1034
|
+
down: ["ArrowDown"],
|
|
1035
|
+
next: ["ArrowDown"],
|
|
1036
|
+
previous: ["ArrowUp"],
|
|
1037
|
+
first: ["Home"],
|
|
1038
|
+
last: ["End"],
|
|
1039
|
+
select: ["Enter", "Space"],
|
|
1040
|
+
escape: ["Escape"]
|
|
1041
|
+
};
|
|
1042
|
+
var GRID_KEYS = {
|
|
1043
|
+
up: ["ArrowUp"],
|
|
1044
|
+
down: ["ArrowDown"],
|
|
1045
|
+
left: ["ArrowLeft"],
|
|
1046
|
+
right: ["ArrowRight"],
|
|
1047
|
+
next: ["ArrowDown"],
|
|
1048
|
+
previous: ["ArrowUp"],
|
|
1049
|
+
first: ["Home"],
|
|
1050
|
+
last: ["End"],
|
|
1051
|
+
pageUp: ["PageUp"],
|
|
1052
|
+
pageDown: ["PageDown"],
|
|
1053
|
+
select: ["Enter", "Space"],
|
|
1054
|
+
escape: ["Escape"]
|
|
1055
|
+
};
|
|
1056
|
+
var MENU_KEYS = {
|
|
1057
|
+
up: ["ArrowUp"],
|
|
1058
|
+
down: ["ArrowDown"],
|
|
1059
|
+
next: ["ArrowDown"],
|
|
1060
|
+
previous: ["ArrowUp"],
|
|
1061
|
+
first: ["Home"],
|
|
1062
|
+
last: ["End"],
|
|
1063
|
+
select: ["Enter", "Space"],
|
|
1064
|
+
escape: ["Escape"]
|
|
1065
|
+
};
|
|
1066
|
+
var TREE_KEYS = {
|
|
1067
|
+
up: ["ArrowUp"],
|
|
1068
|
+
down: ["ArrowDown"],
|
|
1069
|
+
next: ["ArrowDown"],
|
|
1070
|
+
previous: ["ArrowUp"],
|
|
1071
|
+
expand: ["ArrowRight"],
|
|
1072
|
+
collapse: ["ArrowLeft"],
|
|
1073
|
+
first: ["Home"],
|
|
1074
|
+
last: ["End"],
|
|
1075
|
+
select: ["Enter", "Space"],
|
|
1076
|
+
escape: ["Escape"]
|
|
1077
|
+
};
|
|
1078
|
+
var LIST_KEYS = {
|
|
1079
|
+
up: ["ArrowUp"],
|
|
1080
|
+
down: ["ArrowDown"],
|
|
1081
|
+
next: ["ArrowDown"],
|
|
1082
|
+
previous: ["ArrowUp"],
|
|
1083
|
+
first: ["Home"],
|
|
1084
|
+
last: ["End"],
|
|
1085
|
+
pageUp: ["PageUp"],
|
|
1086
|
+
pageDown: ["PageDown"],
|
|
1087
|
+
toggle: ["Space"],
|
|
1088
|
+
select: ["Enter"],
|
|
1089
|
+
selectAll: ["Ctrl+A", "Meta+A"],
|
|
1090
|
+
escape: ["Escape"]
|
|
1091
|
+
};
|
|
1092
|
+
var COMMAND_PALETTE_KEYS = {
|
|
1093
|
+
up: ["ArrowUp"],
|
|
1094
|
+
down: ["ArrowDown"],
|
|
1095
|
+
next: ["ArrowDown"],
|
|
1096
|
+
previous: ["ArrowUp"],
|
|
1097
|
+
pageUp: ["PageUp"],
|
|
1098
|
+
pageDown: ["PageDown"],
|
|
1099
|
+
select: ["Enter"],
|
|
1100
|
+
escape: ["Escape"]
|
|
1101
|
+
};
|
|
1102
|
+
function defaultKeysFor(mode) {
|
|
1103
|
+
switch (mode) {
|
|
1104
|
+
case "horizontal":
|
|
1105
|
+
return HORIZONTAL_KEYS;
|
|
1106
|
+
case "grid":
|
|
1107
|
+
case "directional":
|
|
1108
|
+
return GRID_KEYS;
|
|
1109
|
+
case "sequential":
|
|
1110
|
+
case "vertical":
|
|
1111
|
+
case "custom":
|
|
1112
|
+
default:
|
|
1113
|
+
return VERTICAL_KEYS;
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
function mergeKeys(defaults, user, useDefaults) {
|
|
1117
|
+
if (!useDefaults) return { ...user ?? {} };
|
|
1118
|
+
const merged = { ...defaults };
|
|
1119
|
+
if (user) {
|
|
1120
|
+
for (const action of Object.keys(user)) {
|
|
1121
|
+
const value = user[action];
|
|
1122
|
+
if (value === void 0) continue;
|
|
1123
|
+
if (value === false || value === null) delete merged[action];
|
|
1124
|
+
else merged[action] = value;
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
return merged;
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
// src/core/event-manager.ts
|
|
1131
|
+
var EventManager = class {
|
|
1132
|
+
constructor() {
|
|
1133
|
+
this.records = [];
|
|
1134
|
+
}
|
|
1135
|
+
add(target, type, listener, options) {
|
|
1136
|
+
if (!target || typeof target.addEventListener !== "function") return () => {
|
|
1137
|
+
};
|
|
1138
|
+
target.addEventListener(type, listener, options);
|
|
1139
|
+
const record = { target, type, listener, options: options ?? false };
|
|
1140
|
+
this.records.push(record);
|
|
1141
|
+
return () => this.removeRecord(record);
|
|
1142
|
+
}
|
|
1143
|
+
removeRecord(record) {
|
|
1144
|
+
const index = this.records.indexOf(record);
|
|
1145
|
+
if (index === -1) return;
|
|
1146
|
+
this.records.splice(index, 1);
|
|
1147
|
+
record.target.removeEventListener(record.type, record.listener, record.options);
|
|
1148
|
+
}
|
|
1149
|
+
dispose() {
|
|
1150
|
+
for (let i = this.records.length - 1; i >= 0; i--) {
|
|
1151
|
+
const record = this.records[i];
|
|
1152
|
+
record.target.removeEventListener(record.type, record.listener, record.options);
|
|
1153
|
+
}
|
|
1154
|
+
this.records.length = 0;
|
|
1155
|
+
}
|
|
1156
|
+
get count() {
|
|
1157
|
+
return this.records.length;
|
|
1158
|
+
}
|
|
1159
|
+
};
|
|
1160
|
+
|
|
1161
|
+
// src/utils/layout.ts
|
|
1162
|
+
var LayoutCache = class {
|
|
1163
|
+
constructor() {
|
|
1164
|
+
this.rects = /* @__PURE__ */ new WeakMap();
|
|
1165
|
+
}
|
|
1166
|
+
rect(element, version) {
|
|
1167
|
+
const cached = this.rects.get(element);
|
|
1168
|
+
if (cached && cached.version === version) return cached.rect;
|
|
1169
|
+
const rect = element.getBoundingClientRect();
|
|
1170
|
+
this.rects.set(element, { version, rect });
|
|
1171
|
+
return rect;
|
|
1172
|
+
}
|
|
1173
|
+
};
|
|
1174
|
+
|
|
1175
|
+
// src/core/item-collection.ts
|
|
1176
|
+
var groups = /* @__PURE__ */ new Map();
|
|
1177
|
+
function claimGroup(container) {
|
|
1178
|
+
if (container) groups.set(container, (groups.get(container) ?? 0) + 1);
|
|
1179
|
+
}
|
|
1180
|
+
function releaseGroup(container) {
|
|
1181
|
+
if (!container) return;
|
|
1182
|
+
const count = groups.get(container) ?? 0;
|
|
1183
|
+
if (count <= 1) groups.delete(container);
|
|
1184
|
+
else groups.set(container, count - 1);
|
|
1185
|
+
}
|
|
1186
|
+
function groupsInside(container) {
|
|
1187
|
+
const inside = [];
|
|
1188
|
+
if (!container) return inside;
|
|
1189
|
+
for (const group of groups.keys()) {
|
|
1190
|
+
if (group !== container && container.contains(group)) inside.push(group);
|
|
1191
|
+
}
|
|
1192
|
+
return inside;
|
|
1193
|
+
}
|
|
1194
|
+
var ItemCollection = class {
|
|
1195
|
+
constructor(container, options, onInvalidate) {
|
|
1196
|
+
this.container = container;
|
|
1197
|
+
this.options = options;
|
|
1198
|
+
this.onInvalidate = onInvalidate;
|
|
1199
|
+
this.list = [];
|
|
1200
|
+
this.extra = [];
|
|
1201
|
+
this.removed = /* @__PURE__ */ new WeakSet();
|
|
1202
|
+
this.explicit = null;
|
|
1203
|
+
this.built = false;
|
|
1204
|
+
this.observer = null;
|
|
1205
|
+
this.observerKey = "";
|
|
1206
|
+
this.layout = new LayoutCache();
|
|
1207
|
+
this._version = 0;
|
|
1208
|
+
claimGroup(container);
|
|
1209
|
+
}
|
|
1210
|
+
get version() {
|
|
1211
|
+
return this._version;
|
|
1212
|
+
}
|
|
1213
|
+
get items() {
|
|
1214
|
+
if (!this.built) this.build();
|
|
1215
|
+
return this.list;
|
|
1216
|
+
}
|
|
1217
|
+
get size() {
|
|
1218
|
+
return this.items.length;
|
|
1219
|
+
}
|
|
1220
|
+
setContainer(container) {
|
|
1221
|
+
if (this.container === container) return;
|
|
1222
|
+
releaseGroup(this.container);
|
|
1223
|
+
claimGroup(container);
|
|
1224
|
+
this.container = container;
|
|
1225
|
+
this.invalidate();
|
|
1226
|
+
}
|
|
1227
|
+
setItems(elements) {
|
|
1228
|
+
this.explicit = elements ? elements.slice() : null;
|
|
1229
|
+
this.invalidate();
|
|
1230
|
+
}
|
|
1231
|
+
add(element) {
|
|
1232
|
+
const additions = Array.isArray(element) ? element : [element];
|
|
1233
|
+
for (const item of additions) {
|
|
1234
|
+
if (!item || this.extra.includes(item)) continue;
|
|
1235
|
+
this.removed.delete(item);
|
|
1236
|
+
this.extra.push(item);
|
|
1237
|
+
}
|
|
1238
|
+
this.invalidate();
|
|
1239
|
+
}
|
|
1240
|
+
remove(element) {
|
|
1241
|
+
const removals = Array.isArray(element) ? element : [element];
|
|
1242
|
+
for (const item of removals) {
|
|
1243
|
+
const index = this.extra.indexOf(item);
|
|
1244
|
+
if (index !== -1) this.extra.splice(index, 1);
|
|
1245
|
+
this.removed.add(item);
|
|
1246
|
+
}
|
|
1247
|
+
this.invalidate();
|
|
1248
|
+
}
|
|
1249
|
+
invalidate() {
|
|
1250
|
+
this.built = false;
|
|
1251
|
+
this.onInvalidate?.();
|
|
1252
|
+
}
|
|
1253
|
+
refresh() {
|
|
1254
|
+
this.build();
|
|
1255
|
+
return this.list;
|
|
1256
|
+
}
|
|
1257
|
+
revalidate() {
|
|
1258
|
+
if (this.observer) return;
|
|
1259
|
+
const before = this.list;
|
|
1260
|
+
this.build();
|
|
1261
|
+
if (this.list !== before) this.onInvalidate?.();
|
|
1262
|
+
}
|
|
1263
|
+
indexOf(element) {
|
|
1264
|
+
if (!element) return -1;
|
|
1265
|
+
return this.items.indexOf(element);
|
|
1266
|
+
}
|
|
1267
|
+
at(index) {
|
|
1268
|
+
return this.items[index] ?? null;
|
|
1269
|
+
}
|
|
1270
|
+
startObserving() {
|
|
1271
|
+
if (!this.options.observe) {
|
|
1272
|
+
this.stopObserving();
|
|
1273
|
+
return;
|
|
1274
|
+
}
|
|
1275
|
+
const key = `${this.container ? "c" : "n"}:${JSON.stringify(this.options.observe)}`;
|
|
1276
|
+
if (this.observer && this.observerKey === key) return;
|
|
1277
|
+
this.stopObserving();
|
|
1278
|
+
if (!this.container || typeof MutationObserver !== "function") return;
|
|
1279
|
+
const init = typeof this.options.observe === "object" ? this.options.observe : { childList: true, subtree: true, attributes: true, attributeFilter: ["disabled", "aria-disabled", "hidden", "style", "class"] };
|
|
1280
|
+
this.observer = new MutationObserver(() => this.invalidate());
|
|
1281
|
+
this.observer.observe(this.container, init);
|
|
1282
|
+
this.observerKey = key;
|
|
1283
|
+
}
|
|
1284
|
+
stopObserving() {
|
|
1285
|
+
if (this.observer) {
|
|
1286
|
+
this.observer.disconnect();
|
|
1287
|
+
this.observer = null;
|
|
1288
|
+
this.observerKey = "";
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
dispose() {
|
|
1292
|
+
this.stopObserving();
|
|
1293
|
+
releaseGroup(this.container);
|
|
1294
|
+
this.list = [];
|
|
1295
|
+
this.extra = [];
|
|
1296
|
+
this.removed = /* @__PURE__ */ new WeakSet();
|
|
1297
|
+
this.explicit = null;
|
|
1298
|
+
this.built = false;
|
|
1299
|
+
this.container = null;
|
|
1300
|
+
this.onInvalidate = void 0;
|
|
1301
|
+
}
|
|
1302
|
+
build() {
|
|
1303
|
+
const { selector, disabledSelector, skipHidden, filter } = this.options;
|
|
1304
|
+
let source;
|
|
1305
|
+
let nested = [];
|
|
1306
|
+
if (this.explicit) {
|
|
1307
|
+
source = this.explicit.slice();
|
|
1308
|
+
} else {
|
|
1309
|
+
source = queryItems(this.container, selector);
|
|
1310
|
+
nested = groupsInside(this.container);
|
|
1311
|
+
if (this.extra.length > 0) {
|
|
1312
|
+
const merged = source.slice();
|
|
1313
|
+
for (const item of this.extra) if (!merged.includes(item)) merged.push(item);
|
|
1314
|
+
source = sortInDomOrder(merged);
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
const result = [];
|
|
1318
|
+
for (let i = 0; i < source.length; i++) {
|
|
1319
|
+
const element = source[i];
|
|
1320
|
+
if (!element || this.removed.has(element)) continue;
|
|
1321
|
+
if (nested.length > 0 && nested.some((group) => group !== element && group.contains(element))) continue;
|
|
1322
|
+
if (disabledSelector && isDisabled(element, disabledSelector)) continue;
|
|
1323
|
+
if (skipHidden && isHidden(element)) continue;
|
|
1324
|
+
if (filter && !filter(element, result.length)) continue;
|
|
1325
|
+
result.push(element);
|
|
1326
|
+
}
|
|
1327
|
+
if (sameList(this.list, result)) {
|
|
1328
|
+
this.built = true;
|
|
1329
|
+
this.startObserving();
|
|
1330
|
+
return;
|
|
1331
|
+
}
|
|
1332
|
+
this._version++;
|
|
1333
|
+
this.list = result;
|
|
1334
|
+
this.built = true;
|
|
1335
|
+
this.startObserving();
|
|
1336
|
+
}
|
|
1337
|
+
};
|
|
1338
|
+
function sameList(a, b) {
|
|
1339
|
+
if (a.length !== b.length) return false;
|
|
1340
|
+
for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
|
|
1341
|
+
return true;
|
|
1342
|
+
}
|
|
1343
|
+
function sortInDomOrder(elements) {
|
|
1344
|
+
if (elements.length < 2) return elements;
|
|
1345
|
+
if (typeof document === "undefined" || elements[0]?.compareDocumentPosition == null) return elements;
|
|
1346
|
+
return elements.slice().sort((a, b) => {
|
|
1347
|
+
if (a === b) return 0;
|
|
1348
|
+
const position = a.compareDocumentPosition(b);
|
|
1349
|
+
if (position & Node.DOCUMENT_POSITION_FOLLOWING) return -1;
|
|
1350
|
+
if (position & Node.DOCUMENT_POSITION_PRECEDING) return 1;
|
|
1351
|
+
return 0;
|
|
1352
|
+
});
|
|
1353
|
+
}
|
|
1354
|
+
function createRectOf(collection) {
|
|
1355
|
+
return (element) => collection.layout.rect(element, collection.version);
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
// src/core/keyboard.ts
|
|
1359
|
+
var KEY_ALIASES = {
|
|
1360
|
+
spacebar: "Space",
|
|
1361
|
+
" ": "Space",
|
|
1362
|
+
esc: "Escape",
|
|
1363
|
+
del: "Delete",
|
|
1364
|
+
left: "ArrowLeft",
|
|
1365
|
+
right: "ArrowRight",
|
|
1366
|
+
up: "ArrowUp",
|
|
1367
|
+
down: "ArrowDown",
|
|
1368
|
+
multiply: "*",
|
|
1369
|
+
add: "+",
|
|
1370
|
+
subtract: "-",
|
|
1371
|
+
divide: "/",
|
|
1372
|
+
decimal: ".",
|
|
1373
|
+
windows: "Meta",
|
|
1374
|
+
cmd: "Meta",
|
|
1375
|
+
command: "Meta",
|
|
1376
|
+
option: "Alt",
|
|
1377
|
+
control: "Control",
|
|
1378
|
+
return: "Enter",
|
|
1379
|
+
cr: "Enter",
|
|
1380
|
+
pageup: "PageUp",
|
|
1381
|
+
pagedown: "PageDown",
|
|
1382
|
+
"page up": "PageUp",
|
|
1383
|
+
"page down": "PageDown"
|
|
1384
|
+
};
|
|
1385
|
+
var CANONICAL = /* @__PURE__ */ new Set([
|
|
1386
|
+
"arrowup",
|
|
1387
|
+
"arrowdown",
|
|
1388
|
+
"arrowleft",
|
|
1389
|
+
"arrowright",
|
|
1390
|
+
"enter",
|
|
1391
|
+
"escape",
|
|
1392
|
+
"tab",
|
|
1393
|
+
"space",
|
|
1394
|
+
"home",
|
|
1395
|
+
"end",
|
|
1396
|
+
"pageup",
|
|
1397
|
+
"pagedown",
|
|
1398
|
+
"backspace",
|
|
1399
|
+
"delete",
|
|
1400
|
+
"insert",
|
|
1401
|
+
"shift",
|
|
1402
|
+
"control",
|
|
1403
|
+
"alt",
|
|
1404
|
+
"meta",
|
|
1405
|
+
"capslock",
|
|
1406
|
+
"contextmenu",
|
|
1407
|
+
"f1",
|
|
1408
|
+
"f2",
|
|
1409
|
+
"f3",
|
|
1410
|
+
"f4",
|
|
1411
|
+
"f5",
|
|
1412
|
+
"f6",
|
|
1413
|
+
"f7",
|
|
1414
|
+
"f8",
|
|
1415
|
+
"f9",
|
|
1416
|
+
"f10",
|
|
1417
|
+
"f11",
|
|
1418
|
+
"f12",
|
|
1419
|
+
"f13",
|
|
1420
|
+
"f14",
|
|
1421
|
+
"f15",
|
|
1422
|
+
"f16",
|
|
1423
|
+
"f17",
|
|
1424
|
+
"f18",
|
|
1425
|
+
"f19",
|
|
1426
|
+
"f20",
|
|
1427
|
+
"f21",
|
|
1428
|
+
"f22",
|
|
1429
|
+
"f23",
|
|
1430
|
+
"f24",
|
|
1431
|
+
"printscreen",
|
|
1432
|
+
"scrolllock",
|
|
1433
|
+
"pause",
|
|
1434
|
+
"unidentified"
|
|
1435
|
+
]);
|
|
1436
|
+
function normalizeKeyName(key) {
|
|
1437
|
+
if (!key) return "";
|
|
1438
|
+
const lower = key.toLowerCase();
|
|
1439
|
+
const alias = KEY_ALIASES[lower];
|
|
1440
|
+
if (alias !== void 0) return alias;
|
|
1441
|
+
if (key.length === 1) return key;
|
|
1442
|
+
if (CANONICAL.has(lower)) return canonicalCase(lower);
|
|
1443
|
+
return key;
|
|
1444
|
+
}
|
|
1445
|
+
function canonicalCase(lower) {
|
|
1446
|
+
switch (lower) {
|
|
1447
|
+
case "arrowup":
|
|
1448
|
+
return "ArrowUp";
|
|
1449
|
+
case "arrowdown":
|
|
1450
|
+
return "ArrowDown";
|
|
1451
|
+
case "arrowleft":
|
|
1452
|
+
return "ArrowLeft";
|
|
1453
|
+
case "arrowright":
|
|
1454
|
+
return "ArrowRight";
|
|
1455
|
+
case "pageup":
|
|
1456
|
+
return "PageUp";
|
|
1457
|
+
case "pagedown":
|
|
1458
|
+
return "PageDown";
|
|
1459
|
+
case "capslock":
|
|
1460
|
+
return "CapsLock";
|
|
1461
|
+
case "contextmenu":
|
|
1462
|
+
return "ContextMenu";
|
|
1463
|
+
case "printscreen":
|
|
1464
|
+
return "PrintScreen";
|
|
1465
|
+
case "scrolllock":
|
|
1466
|
+
return "ScrollLock";
|
|
1467
|
+
case "unidentified":
|
|
1468
|
+
return "Unidentified";
|
|
1469
|
+
default:
|
|
1470
|
+
return lower.charAt(0).toUpperCase() + lower.slice(1);
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
var NON_PRINTABLE = /* @__PURE__ */ new Set([
|
|
1474
|
+
"Shift",
|
|
1475
|
+
"Control",
|
|
1476
|
+
"Alt",
|
|
1477
|
+
"Meta",
|
|
1478
|
+
"CapsLock",
|
|
1479
|
+
"Tab",
|
|
1480
|
+
"Enter",
|
|
1481
|
+
"Escape",
|
|
1482
|
+
"Backspace",
|
|
1483
|
+
"Delete",
|
|
1484
|
+
"Insert",
|
|
1485
|
+
"Home",
|
|
1486
|
+
"End",
|
|
1487
|
+
"PageUp",
|
|
1488
|
+
"PageDown",
|
|
1489
|
+
"ArrowUp",
|
|
1490
|
+
"ArrowDown",
|
|
1491
|
+
"ArrowLeft",
|
|
1492
|
+
"ArrowRight",
|
|
1493
|
+
"ContextMenu",
|
|
1494
|
+
"PrintScreen",
|
|
1495
|
+
"ScrollLock",
|
|
1496
|
+
"Pause",
|
|
1497
|
+
"Unidentified"
|
|
1498
|
+
]);
|
|
1499
|
+
function isPrintableKey(key) {
|
|
1500
|
+
if (!key) return false;
|
|
1501
|
+
if (key.length === 1) return true;
|
|
1502
|
+
if (NON_PRINTABLE.has(key)) return false;
|
|
1503
|
+
if (/^F\d{1,2}$/.test(key)) return false;
|
|
1504
|
+
if (/^(Numpad|NumLock)/.test(key)) return false;
|
|
1505
|
+
return false;
|
|
1506
|
+
}
|
|
1507
|
+
function isModifierKey(key) {
|
|
1508
|
+
return key === "Shift" || key === "Control" || key === "Alt" || key === "Meta";
|
|
1509
|
+
}
|
|
1510
|
+
function normalizeKeyEvent(event) {
|
|
1511
|
+
let key = normalizeKeyName(event.key ?? "");
|
|
1512
|
+
if (key === "Unidentified" || key === "") {
|
|
1513
|
+
const fromCode = codeToKey(event.code);
|
|
1514
|
+
if (fromCode) key = fromCode;
|
|
1515
|
+
}
|
|
1516
|
+
const ctrl = event.ctrlKey === true;
|
|
1517
|
+
const alt = event.altKey === true;
|
|
1518
|
+
const shift = event.shiftKey === true;
|
|
1519
|
+
const meta = event.metaKey === true;
|
|
1520
|
+
return {
|
|
1521
|
+
key,
|
|
1522
|
+
code: event.code ?? "",
|
|
1523
|
+
ctrl,
|
|
1524
|
+
alt,
|
|
1525
|
+
shift,
|
|
1526
|
+
meta,
|
|
1527
|
+
isPlain: !ctrl && !alt && !meta,
|
|
1528
|
+
isPrintable: isPrintableKey(key) && !ctrl && !alt && !meta,
|
|
1529
|
+
repeat: event.repeat === true,
|
|
1530
|
+
shiftKey: shift,
|
|
1531
|
+
originalEvent: event
|
|
1532
|
+
};
|
|
1533
|
+
}
|
|
1534
|
+
function codeToKey(code) {
|
|
1535
|
+
if (!code) return "";
|
|
1536
|
+
if (code.startsWith("Key")) return code.slice(3);
|
|
1537
|
+
if (code.startsWith("Digit")) return code.slice(5);
|
|
1538
|
+
if (code.startsWith("Arrow")) return code;
|
|
1539
|
+
if (code === "NumpadEnter") return "Enter";
|
|
1540
|
+
if (code.startsWith("Numpad")) {
|
|
1541
|
+
const rest = code.slice(6);
|
|
1542
|
+
if (/^\d$/.test(rest)) return rest;
|
|
1543
|
+
if (rest === "Add") return "+";
|
|
1544
|
+
if (rest === "Subtract") return "-";
|
|
1545
|
+
if (rest === "Multiply") return "*";
|
|
1546
|
+
if (rest === "Divide") return "/";
|
|
1547
|
+
if (rest === "Decimal") return ".";
|
|
1548
|
+
return `Numpad${rest}`;
|
|
1549
|
+
}
|
|
1550
|
+
switch (code) {
|
|
1551
|
+
case "Space":
|
|
1552
|
+
return "Space";
|
|
1553
|
+
case "Enter":
|
|
1554
|
+
case "NumpadEnter":
|
|
1555
|
+
return "Enter";
|
|
1556
|
+
case "Escape":
|
|
1557
|
+
return "Escape";
|
|
1558
|
+
case "Tab":
|
|
1559
|
+
return "Tab";
|
|
1560
|
+
case "Backspace":
|
|
1561
|
+
return "Backspace";
|
|
1562
|
+
case "Delete":
|
|
1563
|
+
return "Delete";
|
|
1564
|
+
case "Minus":
|
|
1565
|
+
return "-";
|
|
1566
|
+
case "Equal":
|
|
1567
|
+
return "=";
|
|
1568
|
+
case "Comma":
|
|
1569
|
+
return ",";
|
|
1570
|
+
case "Period":
|
|
1571
|
+
return ".";
|
|
1572
|
+
case "Slash":
|
|
1573
|
+
return "/";
|
|
1574
|
+
case "Backslash":
|
|
1575
|
+
return "\\";
|
|
1576
|
+
case "Semicolon":
|
|
1577
|
+
return ";";
|
|
1578
|
+
case "Quote":
|
|
1579
|
+
return "'";
|
|
1580
|
+
case "BracketLeft":
|
|
1581
|
+
return "[";
|
|
1582
|
+
case "BracketRight":
|
|
1583
|
+
return "]";
|
|
1584
|
+
case "Backquote":
|
|
1585
|
+
return "`";
|
|
1586
|
+
default:
|
|
1587
|
+
return "";
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
// src/core/key-matcher.ts
|
|
1592
|
+
var MODIFIER_ALIASES = {
|
|
1593
|
+
ctrl: "ctrl",
|
|
1594
|
+
ctl: "ctrl",
|
|
1595
|
+
control: "ctrl",
|
|
1596
|
+
alt: "alt",
|
|
1597
|
+
option: "alt",
|
|
1598
|
+
opt: "alt",
|
|
1599
|
+
shift: "shift",
|
|
1600
|
+
meta: "meta",
|
|
1601
|
+
cmd: "meta",
|
|
1602
|
+
command: "meta",
|
|
1603
|
+
super: "meta",
|
|
1604
|
+
win: "meta",
|
|
1605
|
+
windows: "meta"
|
|
1606
|
+
};
|
|
1607
|
+
function tokenize(chord) {
|
|
1608
|
+
const tokens = [];
|
|
1609
|
+
let current = "";
|
|
1610
|
+
let quoted = false;
|
|
1611
|
+
for (let i = 0; i < chord.length; i++) {
|
|
1612
|
+
const char = chord[i];
|
|
1613
|
+
if (char === '"' || char === "'") {
|
|
1614
|
+
quoted = !quoted;
|
|
1615
|
+
continue;
|
|
1616
|
+
}
|
|
1617
|
+
if (char === "+" && !quoted) {
|
|
1618
|
+
if (i === chord.length - 1) {
|
|
1619
|
+
tokens.push("+");
|
|
1620
|
+
current = "";
|
|
1621
|
+
continue;
|
|
1622
|
+
}
|
|
1623
|
+
if (current !== "") tokens.push(current);
|
|
1624
|
+
current = "";
|
|
1625
|
+
continue;
|
|
1626
|
+
}
|
|
1627
|
+
current += char;
|
|
1628
|
+
}
|
|
1629
|
+
if (current !== "") tokens.push(current);
|
|
1630
|
+
return tokens;
|
|
1631
|
+
}
|
|
1632
|
+
function parseKeyBinding(binding) {
|
|
1633
|
+
if (binding == null) return null;
|
|
1634
|
+
let key = null;
|
|
1635
|
+
let code = null;
|
|
1636
|
+
let ctrl = false;
|
|
1637
|
+
let alt = false;
|
|
1638
|
+
let shift = false;
|
|
1639
|
+
let meta = false;
|
|
1640
|
+
let allowWithoutModifiers = false;
|
|
1641
|
+
if (typeof binding === "object") {
|
|
1642
|
+
if (binding.key) key = normalizeKeyName(String(binding.key));
|
|
1643
|
+
if (binding.code) code = String(binding.code);
|
|
1644
|
+
ctrl = binding.ctrl === true;
|
|
1645
|
+
alt = binding.alt === true;
|
|
1646
|
+
shift = binding.shift === true;
|
|
1647
|
+
meta = binding.meta === true;
|
|
1648
|
+
allowWithoutModifiers = binding.allowWithoutModifiers === true;
|
|
1649
|
+
if (!key && !code) return null;
|
|
1650
|
+
} else {
|
|
1651
|
+
const raw = String(binding);
|
|
1652
|
+
const trimmed = raw.trim();
|
|
1653
|
+
if (trimmed === "" && raw !== " ") return null;
|
|
1654
|
+
const chord = raw === " " ? " " : trimmed;
|
|
1655
|
+
if (!chord) return null;
|
|
1656
|
+
const tokens = tokenize(chord);
|
|
1657
|
+
if (tokens.length === 0) return null;
|
|
1658
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
1659
|
+
const token = tokens[i];
|
|
1660
|
+
const modifier = MODIFIER_ALIASES[token.toLowerCase()];
|
|
1661
|
+
if (modifier && i < tokens.length - 1) {
|
|
1662
|
+
if (modifier === "ctrl") ctrl = true;
|
|
1663
|
+
else if (modifier === "alt") alt = true;
|
|
1664
|
+
else if (modifier === "shift") shift = true;
|
|
1665
|
+
else meta = true;
|
|
1666
|
+
continue;
|
|
1667
|
+
}
|
|
1668
|
+
key = normalizeKeyName(token);
|
|
1669
|
+
}
|
|
1670
|
+
if (!key) return null;
|
|
1671
|
+
if (shift && key.length === 1) key = key.toLowerCase();
|
|
1672
|
+
}
|
|
1673
|
+
return {
|
|
1674
|
+
key,
|
|
1675
|
+
code,
|
|
1676
|
+
ctrl,
|
|
1677
|
+
alt,
|
|
1678
|
+
shift,
|
|
1679
|
+
meta,
|
|
1680
|
+
allowWithoutModifiers,
|
|
1681
|
+
id: bindingId(key, code, ctrl, alt, shift, meta)
|
|
1682
|
+
};
|
|
1683
|
+
}
|
|
1684
|
+
function bindingId(key, code, ctrl, alt, shift, meta) {
|
|
1685
|
+
const flags = (ctrl ? "c" : "") + (alt ? "a" : "") + (shift ? "s" : "") + (meta ? "m" : "");
|
|
1686
|
+
const main = code ? `#${code.toLowerCase()}` : (key ?? "").toLowerCase();
|
|
1687
|
+
return flags ? `${flags}:${main}` : main;
|
|
1688
|
+
}
|
|
1689
|
+
function toArray(input) {
|
|
1690
|
+
if (input == null || input === false) return [];
|
|
1691
|
+
return Array.isArray(input) ? input : [input];
|
|
1692
|
+
}
|
|
1693
|
+
var KeyMatcher = class {
|
|
1694
|
+
constructor(map = {}, sequences = []) {
|
|
1695
|
+
this.byId = /* @__PURE__ */ new Map();
|
|
1696
|
+
this.loose = [];
|
|
1697
|
+
this.actions = /* @__PURE__ */ new Set();
|
|
1698
|
+
this.sequences = [];
|
|
1699
|
+
this.sequenceIndex = 0;
|
|
1700
|
+
this.sequenceOwner = null;
|
|
1701
|
+
this.sequenceTimer = null;
|
|
1702
|
+
this.sequenceCandidates = [];
|
|
1703
|
+
this.loadMap(map);
|
|
1704
|
+
this.loadSequences(sequences);
|
|
1705
|
+
}
|
|
1706
|
+
loadMap(map) {
|
|
1707
|
+
this.byId.clear();
|
|
1708
|
+
this.loose.length = 0;
|
|
1709
|
+
this.actions.clear();
|
|
1710
|
+
for (const action of Object.keys(map)) {
|
|
1711
|
+
for (const raw of toArray(map[action])) {
|
|
1712
|
+
const binding = parseKeyBinding(raw);
|
|
1713
|
+
if (!binding) continue;
|
|
1714
|
+
this.actions.add(action);
|
|
1715
|
+
let entry = this.byId.get(binding.id);
|
|
1716
|
+
if (!entry || entry.binding.id !== binding.id) {
|
|
1717
|
+
entry = { actions: [], binding };
|
|
1718
|
+
this.byId.set(binding.id, entry);
|
|
1719
|
+
if (binding.allowWithoutModifiers || binding.code) this.loose.push(entry);
|
|
1720
|
+
}
|
|
1721
|
+
if (entry.actions.indexOf(action) === -1) entry.actions.push(action);
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
loadSequences(sequences) {
|
|
1726
|
+
this.resetSequence();
|
|
1727
|
+
this.sequences.length = 0;
|
|
1728
|
+
for (const options of sequences) {
|
|
1729
|
+
const normalized = normalizeSequence(options);
|
|
1730
|
+
if (!normalized) continue;
|
|
1731
|
+
const steps = [];
|
|
1732
|
+
let valid = true;
|
|
1733
|
+
for (const raw of normalized.binding) {
|
|
1734
|
+
const parsed = parseKeyBinding(raw);
|
|
1735
|
+
if (!parsed) {
|
|
1736
|
+
valid = false;
|
|
1737
|
+
break;
|
|
1738
|
+
}
|
|
1739
|
+
steps.push(parsed);
|
|
1740
|
+
}
|
|
1741
|
+
if (!valid) continue;
|
|
1742
|
+
this.sequences.push({
|
|
1743
|
+
steps,
|
|
1744
|
+
action: normalized.action,
|
|
1745
|
+
timeout: normalized.timeout ?? 1e3,
|
|
1746
|
+
preventDefault: normalized.preventDefault !== false
|
|
1747
|
+
});
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
has(action) {
|
|
1751
|
+
return this.actions.has(action);
|
|
1752
|
+
}
|
|
1753
|
+
get boundActions() {
|
|
1754
|
+
return Array.from(this.actions);
|
|
1755
|
+
}
|
|
1756
|
+
get size() {
|
|
1757
|
+
return this.byId.size + this.sequences.length;
|
|
1758
|
+
}
|
|
1759
|
+
match(event, owner) {
|
|
1760
|
+
if (this.sequences.length > 0) {
|
|
1761
|
+
const sequence = this.matchSequence(event, owner);
|
|
1762
|
+
if (sequence) return sequence;
|
|
1763
|
+
}
|
|
1764
|
+
const ids = candidateIds(event);
|
|
1765
|
+
for (let i = 0; i < ids.length; i++) {
|
|
1766
|
+
const entry = this.byId.get(ids[i]);
|
|
1767
|
+
if (entry && matchesBinding(entry.binding, event)) {
|
|
1768
|
+
return { actions: entry.actions, binding: entry.binding, fromSequence: false, complete: true, preventDefault: true };
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
for (let i = 0; i < this.loose.length; i++) {
|
|
1772
|
+
const entry = this.loose[i];
|
|
1773
|
+
if (matchesBinding(entry.binding, event)) {
|
|
1774
|
+
return { actions: entry.actions, binding: entry.binding, fromSequence: false, complete: true, preventDefault: true };
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
return null;
|
|
1778
|
+
}
|
|
1779
|
+
resetSequence() {
|
|
1780
|
+
this.sequenceIndex = 0;
|
|
1781
|
+
this.sequenceCandidates = [];
|
|
1782
|
+
this.clearSequenceTimer();
|
|
1783
|
+
}
|
|
1784
|
+
dispose() {
|
|
1785
|
+
this.resetSequence();
|
|
1786
|
+
this.byId.clear();
|
|
1787
|
+
this.loose.length = 0;
|
|
1788
|
+
this.actions.clear();
|
|
1789
|
+
this.sequences.length = 0;
|
|
1790
|
+
}
|
|
1791
|
+
matchSequence(event, owner) {
|
|
1792
|
+
if (owner && this.sequenceOwner && this.sequenceOwner !== owner) this.resetSequence();
|
|
1793
|
+
if (owner) this.sequenceOwner = owner;
|
|
1794
|
+
const pending = this.sequenceCandidates.length > 0;
|
|
1795
|
+
const step = this.sequenceIndex;
|
|
1796
|
+
const pool = pending ? this.sequenceCandidates : this.sequences;
|
|
1797
|
+
const fitting = pool.filter((sequence) => {
|
|
1798
|
+
const binding = sequence.steps[step];
|
|
1799
|
+
return binding !== void 0 && matchesBinding(binding, event);
|
|
1800
|
+
});
|
|
1801
|
+
if (fitting.length === 0) {
|
|
1802
|
+
if (!pending) return null;
|
|
1803
|
+
this.resetSequence();
|
|
1804
|
+
return this.matchSequence(event, owner);
|
|
1805
|
+
}
|
|
1806
|
+
const done = fitting.find((sequence) => sequence.steps.length === step + 1);
|
|
1807
|
+
const lead = done ?? fitting[0];
|
|
1808
|
+
const result = {
|
|
1809
|
+
actions: [lead.action],
|
|
1810
|
+
binding: lead.steps[step],
|
|
1811
|
+
fromSequence: true,
|
|
1812
|
+
complete: done !== void 0,
|
|
1813
|
+
preventDefault: lead.preventDefault
|
|
1814
|
+
};
|
|
1815
|
+
if (done) {
|
|
1816
|
+
this.resetSequence();
|
|
1817
|
+
} else {
|
|
1818
|
+
this.sequenceCandidates = fitting;
|
|
1819
|
+
this.sequenceIndex = step + 1;
|
|
1820
|
+
this.armSequenceTimer(lead.timeout);
|
|
1821
|
+
}
|
|
1822
|
+
return result;
|
|
1823
|
+
}
|
|
1824
|
+
armSequenceTimer(timeout) {
|
|
1825
|
+
this.clearSequenceTimer();
|
|
1826
|
+
if (typeof setTimeout !== "function") return;
|
|
1827
|
+
this.sequenceTimer = setTimeout(() => this.resetSequence(), timeout);
|
|
1828
|
+
}
|
|
1829
|
+
clearSequenceTimer() {
|
|
1830
|
+
if (this.sequenceTimer !== null) {
|
|
1831
|
+
clearTimeout(this.sequenceTimer);
|
|
1832
|
+
this.sequenceTimer = null;
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
};
|
|
1836
|
+
var SHIFT_DISTINCT_KEYS = /* @__PURE__ */ new Set(["tab"]);
|
|
1837
|
+
function shiftDistinct(key) {
|
|
1838
|
+
return SHIFT_DISTINCT_KEYS.has(key.toLowerCase());
|
|
1839
|
+
}
|
|
1840
|
+
function makeId(key, ctrl, alt, shift, meta) {
|
|
1841
|
+
const flags = (ctrl ? "c" : "") + (alt ? "a" : "") + (shift ? "s" : "") + (meta ? "m" : "");
|
|
1842
|
+
return flags ? `${flags}:${key}` : key;
|
|
1843
|
+
}
|
|
1844
|
+
function candidateIds(event) {
|
|
1845
|
+
const { ctrl, alt, shift, meta } = event;
|
|
1846
|
+
const key = event.key;
|
|
1847
|
+
const lower = key.toLowerCase();
|
|
1848
|
+
const ids = [makeId(lower, ctrl, alt, shift, meta)];
|
|
1849
|
+
const singleChar = key.length === 1;
|
|
1850
|
+
const noShift = shift && (singleChar || !shiftDistinct(lower)) ? makeId(lower, ctrl, alt, false, meta) : null;
|
|
1851
|
+
if (noShift) ids.push(noShift);
|
|
1852
|
+
if (singleChar && lower !== key) {
|
|
1853
|
+
ids.push(makeId(key, ctrl, alt, shift, meta));
|
|
1854
|
+
if (noShift) ids.push(makeId(key, ctrl, alt, false, meta));
|
|
1855
|
+
}
|
|
1856
|
+
return ids;
|
|
1857
|
+
}
|
|
1858
|
+
function matchesBinding(binding, event) {
|
|
1859
|
+
if (binding.code) {
|
|
1860
|
+
if (binding.code.toLowerCase() !== event.code.toLowerCase()) return false;
|
|
1861
|
+
} else if (binding.key) {
|
|
1862
|
+
if (!keyEquals(binding.key, event.key)) return false;
|
|
1863
|
+
} else {
|
|
1864
|
+
return false;
|
|
1865
|
+
}
|
|
1866
|
+
if (binding.allowWithoutModifiers && !event.ctrl && !event.alt && !event.meta) return true;
|
|
1867
|
+
if (binding.ctrl !== event.ctrl) return false;
|
|
1868
|
+
if (binding.alt !== event.alt) return false;
|
|
1869
|
+
if (binding.meta !== event.meta) return false;
|
|
1870
|
+
if (binding.shift !== event.shift) {
|
|
1871
|
+
const named = binding.key ?? "";
|
|
1872
|
+
const singleChar = named.length === 1;
|
|
1873
|
+
if (event.shift && !binding.shift) {
|
|
1874
|
+
if (shiftDistinct(named)) return false;
|
|
1875
|
+
if (/^[a-z]$/i.test(named) && (event.ctrl || event.alt || event.meta)) return false;
|
|
1876
|
+
} else {
|
|
1877
|
+
if (!singleChar) return false;
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
return true;
|
|
1881
|
+
}
|
|
1882
|
+
function keyEquals(bindingKey, eventKey) {
|
|
1883
|
+
if (bindingKey === eventKey) return true;
|
|
1884
|
+
return bindingKey.toLowerCase() === eventKey.toLowerCase();
|
|
1885
|
+
}
|
|
1886
|
+
function normalizeSequence(input) {
|
|
1887
|
+
if (!input || !input.binding || input.binding.length < 2 || !input.action) return null;
|
|
1888
|
+
return input;
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1891
|
+
// src/core/typeahead.ts
|
|
1892
|
+
function createTypeahead(options = {}) {
|
|
1893
|
+
const resetDelay = options.resetDelay ?? 600;
|
|
1894
|
+
const getTextFor = options.getText ?? ((element) => getText(element));
|
|
1895
|
+
let buffer = "";
|
|
1896
|
+
let timer = null;
|
|
1897
|
+
let lastMatch = -1;
|
|
1898
|
+
function clearTimer() {
|
|
1899
|
+
if (timer !== null) {
|
|
1900
|
+
clearTimeout(timer);
|
|
1901
|
+
timer = null;
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
function reset() {
|
|
1905
|
+
buffer = "";
|
|
1906
|
+
lastMatch = -1;
|
|
1907
|
+
clearTimer();
|
|
1908
|
+
}
|
|
1909
|
+
return {
|
|
1910
|
+
get query() {
|
|
1911
|
+
return buffer;
|
|
1912
|
+
},
|
|
1913
|
+
reset,
|
|
1914
|
+
dispose: reset,
|
|
1915
|
+
handle(character, items, currentIndex) {
|
|
1916
|
+
if (!character) return null;
|
|
1917
|
+
const total = items.length;
|
|
1918
|
+
if (total === 0) return null;
|
|
1919
|
+
const previousBuffer = buffer;
|
|
1920
|
+
buffer += character.toLowerCase();
|
|
1921
|
+
if (buffer.length > 12) buffer = buffer.slice(-12);
|
|
1922
|
+
clearTimer();
|
|
1923
|
+
if (typeof setTimeout === "function") timer = setTimeout(reset, resetDelay);
|
|
1924
|
+
const repeating = buffer.length > 1 && previousBuffer.length === 1 && buffer === previousBuffer + previousBuffer;
|
|
1925
|
+
let query = repeating ? previousBuffer : buffer;
|
|
1926
|
+
const collect = (prefix) => {
|
|
1927
|
+
const found = [];
|
|
1928
|
+
for (let i = 0; i < total; i++) {
|
|
1929
|
+
const text = getTextFor(items[i]);
|
|
1930
|
+
if (text && text.toLowerCase().startsWith(prefix)) found.push(i);
|
|
1931
|
+
}
|
|
1932
|
+
return found;
|
|
1933
|
+
};
|
|
1934
|
+
let matches = collect(query);
|
|
1935
|
+
if (matches.length === 0 && query.length > 1) {
|
|
1936
|
+
query = character.toLowerCase();
|
|
1937
|
+
buffer = query;
|
|
1938
|
+
matches = collect(query);
|
|
1939
|
+
}
|
|
1940
|
+
if (matches.length === 0) {
|
|
1941
|
+
options.onMatch?.(null, query);
|
|
1942
|
+
return null;
|
|
1943
|
+
}
|
|
1944
|
+
let pick;
|
|
1945
|
+
if (repeating) {
|
|
1946
|
+
const position = matches.indexOf(lastMatch);
|
|
1947
|
+
pick = matches[(position + 1) % matches.length];
|
|
1948
|
+
} else if (query === previousBuffer && previousBuffer.length > 0) {
|
|
1949
|
+
const position = matches.indexOf(lastMatch);
|
|
1950
|
+
pick = matches[(position + 1) % matches.length];
|
|
1951
|
+
} else {
|
|
1952
|
+
pick = matches[0];
|
|
1953
|
+
for (let i = 0; i < matches.length; i++) {
|
|
1954
|
+
if (matches[i] > currentIndex) {
|
|
1955
|
+
pick = matches[i];
|
|
1956
|
+
break;
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
lastMatch = pick;
|
|
1961
|
+
const element = items[pick] ?? null;
|
|
1962
|
+
options.onMatch?.(element, query);
|
|
1963
|
+
return element;
|
|
1964
|
+
}
|
|
1965
|
+
};
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1968
|
+
// src/core/engine.ts
|
|
1969
|
+
var DEFAULT_ITEM_SELECTOR = "[data-keystra-item]";
|
|
1970
|
+
var DEFAULT_DISABLED_SELECTOR = "[disabled],[aria-disabled='true']";
|
|
1971
|
+
var DEFAULT_PAGE_SIZE = 10;
|
|
1972
|
+
var MOVEMENT_ACTIONS = /* @__PURE__ */ new Set([
|
|
1973
|
+
"next",
|
|
1974
|
+
"previous",
|
|
1975
|
+
"up",
|
|
1976
|
+
"down",
|
|
1977
|
+
"left",
|
|
1978
|
+
"right",
|
|
1979
|
+
"first",
|
|
1980
|
+
"last",
|
|
1981
|
+
"pageUp",
|
|
1982
|
+
"pageDown"
|
|
1983
|
+
]);
|
|
1984
|
+
function resolveOptions(options = {}, overrides = {}) {
|
|
1985
|
+
const mode = overrides.mode ?? options.mode ?? "vertical";
|
|
1986
|
+
const container = overrides.container ?? resolveElement(options.container) ?? null;
|
|
1987
|
+
const keys = overrides.keys ?? mergeKeys(
|
|
1988
|
+
overrides.defaultKeys ?? defaultKeysFor(mode),
|
|
1989
|
+
options.keys,
|
|
1990
|
+
options.useDefaultKeys !== false
|
|
1991
|
+
);
|
|
1992
|
+
const userStrategy = options.strategy ?? null;
|
|
1993
|
+
const listenTarget = overrides.listenTarget ?? resolveEventTarget(options.target) ?? container ?? (typeof document !== "undefined" ? document : null);
|
|
1994
|
+
const resolved = {
|
|
1995
|
+
container,
|
|
1996
|
+
pinnedTarget: options.target !== void 0 && options.target !== null,
|
|
1997
|
+
listenTarget,
|
|
1998
|
+
selector: options.items ?? options.itemSelector ?? DEFAULT_ITEM_SELECTOR,
|
|
1999
|
+
disabledSelector: options.disabledSelector === false || options.disabledSelector === null ? null : options.disabledSelector ?? DEFAULT_DISABLED_SELECTOR,
|
|
2000
|
+
skipHidden: options.skipHidden !== false,
|
|
2001
|
+
filter: options.filter ?? null,
|
|
2002
|
+
mode,
|
|
2003
|
+
loop: overrides.loop ?? options.loop === true,
|
|
2004
|
+
columns: overrides.columns ?? options.columns ?? 0,
|
|
2005
|
+
pageSize: options.pageSize ?? DEFAULT_PAGE_SIZE,
|
|
2006
|
+
strategy: userStrategy ?? overrides.strategy ?? strategyForMode(mode),
|
|
2007
|
+
userStrategy,
|
|
2008
|
+
onBeforeNavigate: options.onBeforeNavigate ?? null,
|
|
2009
|
+
keys,
|
|
2010
|
+
defaultKeys: overrides.defaultKeys ?? null,
|
|
2011
|
+
userKeys: options.keys ?? null,
|
|
2012
|
+
useDefaultKeys: options.useDefaultKeys !== false,
|
|
2013
|
+
sequences: options.sequences ?? [],
|
|
2014
|
+
typeahead: normalizeTypeahead(options.typeahead),
|
|
2015
|
+
roving: normalizeRoving(options.roving),
|
|
2016
|
+
activeDescendant: options.activeDescendant === true || options.activeDescendant !== void 0 && options.activeDescendant !== false && options.activeDescendant !== null,
|
|
2017
|
+
descendantOwner: options.activeDescendant === void 0 || options.activeDescendant === false || options.activeDescendant === null ? null : options.activeDescendant === true ? () => resolveActiveDescendantOwner(container, listenTarget) : () => resolveElement(options.activeDescendant, container?.ownerDocument ?? null),
|
|
2018
|
+
autoFocus: normalizeAutoFocus(options.autoFocus),
|
|
2019
|
+
selectionOptions: normalizeSelection(options.selection),
|
|
2020
|
+
observe: options.observe ?? false,
|
|
2021
|
+
onlyWhenFocusedInside: options.onlyWhenFocusedInside !== false,
|
|
2022
|
+
when: options.when ?? null,
|
|
2023
|
+
ignoreEditable: options.ignoreEditable !== false,
|
|
2024
|
+
editableSelector: options.editableSelector ?? null,
|
|
2025
|
+
stopPropagation: options.stopPropagation !== false,
|
|
2026
|
+
preventDefault: options.preventDefault !== false,
|
|
2027
|
+
capture: options.capture === true,
|
|
2028
|
+
pointerSync: options.pointerSync === true,
|
|
2029
|
+
handlers: {
|
|
2030
|
+
onFocus: options.onFocus,
|
|
2031
|
+
onBlur: options.onBlur,
|
|
2032
|
+
onSelect: options.onSelect,
|
|
2033
|
+
onAction: options.onAction,
|
|
2034
|
+
onEscape: options.onEscape,
|
|
2035
|
+
onRefresh: options.onRefresh
|
|
2036
|
+
}
|
|
2037
|
+
};
|
|
2038
|
+
return resolved;
|
|
2039
|
+
}
|
|
2040
|
+
function strategyForMode(mode) {
|
|
2041
|
+
switch (mode) {
|
|
2042
|
+
case "horizontal":
|
|
2043
|
+
return createSequentialStrategy("horizontal");
|
|
2044
|
+
case "grid":
|
|
2045
|
+
return createGridStrategy();
|
|
2046
|
+
case "directional":
|
|
2047
|
+
return createDirectionalStrategy();
|
|
2048
|
+
case "custom":
|
|
2049
|
+
return () => null;
|
|
2050
|
+
case "sequential":
|
|
2051
|
+
case "vertical":
|
|
2052
|
+
default:
|
|
2053
|
+
return createSequentialStrategy("vertical");
|
|
2054
|
+
}
|
|
2055
|
+
}
|
|
2056
|
+
function normalizeTypeahead(input) {
|
|
2057
|
+
if (!input) return null;
|
|
2058
|
+
if (input === true) return { enabled: true };
|
|
2059
|
+
return input.enabled === false ? null : { ...input, enabled: true };
|
|
2060
|
+
}
|
|
2061
|
+
function resolveActiveDescendantOwner(container, listenTarget) {
|
|
2062
|
+
if (listenTarget && typeof listenTarget.nodeType === "number") {
|
|
2063
|
+
const element = listenTarget;
|
|
2064
|
+
if (!container || contains(container, element) || element === container) return element;
|
|
2065
|
+
}
|
|
2066
|
+
if (typeof document === "undefined") return null;
|
|
2067
|
+
const active = document.activeElement;
|
|
2068
|
+
if (active && container && contains(container, active)) return active;
|
|
2069
|
+
return null;
|
|
2070
|
+
}
|
|
2071
|
+
function normalizeRoving(input) {
|
|
2072
|
+
if (input === false) return { enabled: false };
|
|
2073
|
+
if (input === true || input === void 0) return { enabled: true };
|
|
2074
|
+
return input;
|
|
2075
|
+
}
|
|
2076
|
+
function normalizeAutoFocus(input) {
|
|
2077
|
+
if (!input) return { enabled: false };
|
|
2078
|
+
if (input === true) return { enabled: true };
|
|
2079
|
+
return input;
|
|
2080
|
+
}
|
|
2081
|
+
function normalizeSelection(input) {
|
|
2082
|
+
if (!input) return null;
|
|
2083
|
+
if (input === true) return { enabled: true, mode: "single" };
|
|
2084
|
+
return input.enabled === false ? null : { enabled: true, ...input };
|
|
2085
|
+
}
|
|
2086
|
+
var NavigationController = class {
|
|
2087
|
+
constructor(options = {}, overrides = {}) {
|
|
2088
|
+
this.events = new EventManager();
|
|
2089
|
+
this.emitter = new Emitter();
|
|
2090
|
+
this.keydownDisposer = null;
|
|
2091
|
+
this.auxDisposers = [];
|
|
2092
|
+
this.syncPending = true;
|
|
2093
|
+
this.destroyed = false;
|
|
2094
|
+
this.resolved = resolveOptions(options, overrides);
|
|
2095
|
+
this.collection = new ItemCollection(
|
|
2096
|
+
this.resolved.container,
|
|
2097
|
+
{
|
|
2098
|
+
selector: this.resolved.selector,
|
|
2099
|
+
disabledSelector: this.resolved.disabledSelector,
|
|
2100
|
+
skipHidden: this.resolved.skipHidden,
|
|
2101
|
+
filter: this.resolved.filter,
|
|
2102
|
+
observe: this.resolved.observe
|
|
2103
|
+
},
|
|
2104
|
+
() => {
|
|
2105
|
+
this.syncPending = true;
|
|
2106
|
+
}
|
|
2107
|
+
);
|
|
2108
|
+
this.focus = new FocusManager({
|
|
2109
|
+
roving: this.resolved.roving,
|
|
2110
|
+
virtual: this.resolved.activeDescendant,
|
|
2111
|
+
descendantOwner: this.resolved.descendantOwner,
|
|
2112
|
+
disabledSelector: this.resolved.disabledSelector,
|
|
2113
|
+
skipHidden: this.resolved.skipHidden,
|
|
2114
|
+
onFocus: (element, index, action) => {
|
|
2115
|
+
const selection = this.resolved.selectionOptions;
|
|
2116
|
+
if (selection?.followFocus && selection.mode !== "multiple") {
|
|
2117
|
+
this.selectionManager?.select(index);
|
|
2118
|
+
}
|
|
2119
|
+
this.resolved.handlers.onFocus?.(element, index);
|
|
2120
|
+
this.emitter.emit("focus", { element, index, action });
|
|
2121
|
+
},
|
|
2122
|
+
onBlur: (element, index) => {
|
|
2123
|
+
this.resolved.handlers.onBlur?.(element, index);
|
|
2124
|
+
this.emitter.emit("blur", { element, index });
|
|
2125
|
+
}
|
|
2126
|
+
});
|
|
2127
|
+
if (options.elements) this.collection.setItems(options.elements);
|
|
2128
|
+
this.matcher = new KeyMatcher(this.resolved.keys, this.resolved.sequences ?? []);
|
|
2129
|
+
this.typeaheadController = this.resolved.typeahead ? createTypeahead(this.resolved.typeahead) : null;
|
|
2130
|
+
this.selectionManager = this.resolved.selectionOptions ? new SelectionManager(this.resolved.selectionOptions) : null;
|
|
2131
|
+
this.selectionManager?.setDiffHandler((selected, added, removed) => {
|
|
2132
|
+
this.emitter.emit("selectionchange", { selected, added, removed });
|
|
2133
|
+
});
|
|
2134
|
+
this.selectionManager?.bindItems(() => this.collection.items);
|
|
2135
|
+
if (options.enabled !== false) this.attach();
|
|
2136
|
+
if (this.resolved.autoFocus.enabled) this.applyAutoFocus();
|
|
2137
|
+
}
|
|
2138
|
+
get container() {
|
|
2139
|
+
return this.resolved.container;
|
|
2140
|
+
}
|
|
2141
|
+
get items() {
|
|
2142
|
+
this.sync();
|
|
2143
|
+
return this.collection.items;
|
|
2144
|
+
}
|
|
2145
|
+
get activeElement() {
|
|
2146
|
+
this.sync();
|
|
2147
|
+
return this.focus.element;
|
|
2148
|
+
}
|
|
2149
|
+
get activeIndex() {
|
|
2150
|
+
this.sync();
|
|
2151
|
+
return this.focus.focusedIndex;
|
|
2152
|
+
}
|
|
2153
|
+
get currentIndex() {
|
|
2154
|
+
this.sync();
|
|
2155
|
+
return this.focus.index;
|
|
2156
|
+
}
|
|
2157
|
+
get mode() {
|
|
2158
|
+
return this.resolved.mode;
|
|
2159
|
+
}
|
|
2160
|
+
get loop() {
|
|
2161
|
+
return this.resolved.loop;
|
|
2162
|
+
}
|
|
2163
|
+
get enabled() {
|
|
2164
|
+
return this.keydownDisposer !== null;
|
|
2165
|
+
}
|
|
2166
|
+
get selection() {
|
|
2167
|
+
this.sync();
|
|
2168
|
+
return this.selectionManager;
|
|
2169
|
+
}
|
|
2170
|
+
next(count = 1) {
|
|
2171
|
+
return this.step(count, "next");
|
|
2172
|
+
}
|
|
2173
|
+
previous(count = 1) {
|
|
2174
|
+
return this.step(-count, "previous");
|
|
2175
|
+
}
|
|
2176
|
+
up() {
|
|
2177
|
+
return this.move("up");
|
|
2178
|
+
}
|
|
2179
|
+
down() {
|
|
2180
|
+
return this.move("down");
|
|
2181
|
+
}
|
|
2182
|
+
left() {
|
|
2183
|
+
return this.move("left");
|
|
2184
|
+
}
|
|
2185
|
+
right() {
|
|
2186
|
+
return this.move("right");
|
|
2187
|
+
}
|
|
2188
|
+
move(direction) {
|
|
2189
|
+
this.collection.revalidate();
|
|
2190
|
+
this.sync();
|
|
2191
|
+
const index = this.resolveNavigation(direction, direction, null);
|
|
2192
|
+
if (index === null) return null;
|
|
2193
|
+
const focused = this.focus.focusIndex(index, direction);
|
|
2194
|
+
if (focused) this.afterMove(index, direction, null);
|
|
2195
|
+
return focused;
|
|
2196
|
+
}
|
|
2197
|
+
first() {
|
|
2198
|
+
this.collection.revalidate();
|
|
2199
|
+
this.sync();
|
|
2200
|
+
if (this.collection.items.length === 0) return null;
|
|
2201
|
+
const focused = this.focus.focusIndex(0, "first");
|
|
2202
|
+
if (focused) this.afterMove(0, "first", null);
|
|
2203
|
+
return focused;
|
|
2204
|
+
}
|
|
2205
|
+
last() {
|
|
2206
|
+
this.collection.revalidate();
|
|
2207
|
+
this.sync();
|
|
2208
|
+
const total = this.collection.items.length;
|
|
2209
|
+
if (total === 0) return null;
|
|
2210
|
+
const focused = this.focus.focusIndex(total - 1, "last");
|
|
2211
|
+
if (focused) this.afterMove(total - 1, "last", null);
|
|
2212
|
+
return focused;
|
|
2213
|
+
}
|
|
2214
|
+
focusFirst() {
|
|
2215
|
+
return this.first();
|
|
2216
|
+
}
|
|
2217
|
+
focusLast() {
|
|
2218
|
+
return this.last();
|
|
2219
|
+
}
|
|
2220
|
+
pageUp() {
|
|
2221
|
+
return this.step(-this.resolved.pageSize, "pageUp");
|
|
2222
|
+
}
|
|
2223
|
+
pageDown() {
|
|
2224
|
+
return this.step(this.resolved.pageSize, "pageDown");
|
|
2225
|
+
}
|
|
2226
|
+
focusIndex(index) {
|
|
2227
|
+
this.collection.revalidate();
|
|
2228
|
+
this.sync();
|
|
2229
|
+
return this.focus.focusIndex(index, null);
|
|
2230
|
+
}
|
|
2231
|
+
setActiveIndex(index, options = {}) {
|
|
2232
|
+
this.collection.revalidate();
|
|
2233
|
+
this.sync();
|
|
2234
|
+
if (options.focus === false) {
|
|
2235
|
+
this.focus.setActiveIndex(index);
|
|
2236
|
+
return this.collection.items[index] ?? null;
|
|
2237
|
+
}
|
|
2238
|
+
return this.focus.focusIndex(index, null);
|
|
2239
|
+
}
|
|
2240
|
+
focusElement(element) {
|
|
2241
|
+
this.sync();
|
|
2242
|
+
return this.focus.focusElement(element ?? null);
|
|
2243
|
+
}
|
|
2244
|
+
restoreFocus() {
|
|
2245
|
+
return this.focus.restore();
|
|
2246
|
+
}
|
|
2247
|
+
rememberFocus() {
|
|
2248
|
+
if (typeof document === "undefined") return;
|
|
2249
|
+
const active = document.activeElement;
|
|
2250
|
+
if (!active || active === document.body || active === document.documentElement) return;
|
|
2251
|
+
if (this.resolved.container && contains(this.resolved.container, active)) return;
|
|
2252
|
+
this.focus.remember(active);
|
|
2253
|
+
}
|
|
2254
|
+
select(elementOrIndex) {
|
|
2255
|
+
this.sync();
|
|
2256
|
+
const index = this.resolveIndex(elementOrIndex);
|
|
2257
|
+
const element = index >= 0 ? this.collection.items[index] : void 0;
|
|
2258
|
+
if (!element) return;
|
|
2259
|
+
this.applySelection(index, false);
|
|
2260
|
+
this.resolved.handlers.onSelect?.(element, index);
|
|
2261
|
+
this.emitter.emit("select", { element, index });
|
|
2262
|
+
this.emitAction("select", null, element, index);
|
|
2263
|
+
}
|
|
2264
|
+
perform(action, event = null) {
|
|
2265
|
+
if (this.destroyed) return false;
|
|
2266
|
+
return this.runAction(action, event, null);
|
|
2267
|
+
}
|
|
2268
|
+
setContainer(container) {
|
|
2269
|
+
const next = resolveElement(container, this.resolved.container?.ownerDocument ?? null);
|
|
2270
|
+
if (next === this.resolved.container) return;
|
|
2271
|
+
const attached = this.keydownDisposer !== null;
|
|
2272
|
+
if (attached) this.detach();
|
|
2273
|
+
this.resolved.container = next;
|
|
2274
|
+
if (!this.resolved.pinnedTarget) {
|
|
2275
|
+
this.resolved.listenTarget = next ?? (typeof document !== "undefined" ? document : null);
|
|
2276
|
+
}
|
|
2277
|
+
this.collection.setContainer(next);
|
|
2278
|
+
if (attached) this.attach();
|
|
2279
|
+
this.syncPending = true;
|
|
2280
|
+
this.emitter.emit("refresh", { items: this.collection.items });
|
|
2281
|
+
}
|
|
2282
|
+
refresh() {
|
|
2283
|
+
const items = this.collection.refresh();
|
|
2284
|
+
this.sync(true);
|
|
2285
|
+
this.resolved.handlers.onRefresh?.(items);
|
|
2286
|
+
this.emitter.emit("refresh", { items });
|
|
2287
|
+
return items;
|
|
2288
|
+
}
|
|
2289
|
+
add(element) {
|
|
2290
|
+
this.collection.add(element);
|
|
2291
|
+
}
|
|
2292
|
+
remove(element) {
|
|
2293
|
+
this.collection.remove(element);
|
|
2294
|
+
}
|
|
2295
|
+
setItems(elements) {
|
|
2296
|
+
this.collection.setItems(elements);
|
|
2297
|
+
}
|
|
2298
|
+
enable() {
|
|
2299
|
+
if (this.destroyed || this.enabled) return;
|
|
2300
|
+
this.attach();
|
|
2301
|
+
}
|
|
2302
|
+
disable() {
|
|
2303
|
+
this.detach();
|
|
2304
|
+
this.matcher.resetSequence();
|
|
2305
|
+
this.typeaheadController?.reset();
|
|
2306
|
+
}
|
|
2307
|
+
update(options) {
|
|
2308
|
+
if (this.destroyed) return;
|
|
2309
|
+
if (options.container !== void 0) this.setContainer(options.container);
|
|
2310
|
+
if (options.mode !== void 0 && options.mode !== this.resolved.mode) {
|
|
2311
|
+
this.resolved.mode = options.mode;
|
|
2312
|
+
if (!this.resolved.userStrategy) {
|
|
2313
|
+
this.resolved.strategy = strategyForMode(options.mode);
|
|
2314
|
+
}
|
|
2315
|
+
}
|
|
2316
|
+
if (options.loop !== void 0) this.resolved.loop = options.loop === true;
|
|
2317
|
+
if (options.columns !== void 0) this.resolved.columns = options.columns ?? 0;
|
|
2318
|
+
if (options.pageSize !== void 0) this.resolved.pageSize = options.pageSize ?? DEFAULT_PAGE_SIZE;
|
|
2319
|
+
if (options.keys !== void 0) this.resolved.userKeys = options.keys;
|
|
2320
|
+
if (options.keys !== void 0 || options.mode !== void 0) {
|
|
2321
|
+
this.resolved.keys = mergeKeys(
|
|
2322
|
+
this.resolved.defaultKeys ?? defaultKeysFor(this.resolved.mode),
|
|
2323
|
+
this.resolved.userKeys,
|
|
2324
|
+
this.resolved.useDefaultKeys
|
|
2325
|
+
);
|
|
2326
|
+
this.matcher.loadMap(this.resolved.keys);
|
|
2327
|
+
}
|
|
2328
|
+
if (options.enabled === true) this.enable();
|
|
2329
|
+
else if (options.enabled === false) this.disable();
|
|
2330
|
+
this.syncPending = true;
|
|
2331
|
+
}
|
|
2332
|
+
on(event, handler) {
|
|
2333
|
+
return this.emitter.on(event, handler);
|
|
2334
|
+
}
|
|
2335
|
+
off(event, handler) {
|
|
2336
|
+
this.emitter.off(event, handler);
|
|
2337
|
+
}
|
|
2338
|
+
destroy() {
|
|
2339
|
+
if (this.destroyed) return;
|
|
2340
|
+
this.destroyed = true;
|
|
2341
|
+
this.detach();
|
|
2342
|
+
this.events.dispose();
|
|
2343
|
+
this.collection.dispose();
|
|
2344
|
+
this.matcher.dispose();
|
|
2345
|
+
this.typeaheadController?.dispose();
|
|
2346
|
+
this.typeaheadController = null;
|
|
2347
|
+
this.selectionManager?.dispose();
|
|
2348
|
+
this.selectionManager = null;
|
|
2349
|
+
this.focus.dispose();
|
|
2350
|
+
this.emitter.emit("destroy", {});
|
|
2351
|
+
this.emitter.clear();
|
|
2352
|
+
}
|
|
2353
|
+
attach() {
|
|
2354
|
+
const target = this.resolved.listenTarget;
|
|
2355
|
+
if (!target || this.keydownDisposer) return;
|
|
2356
|
+
const listenerOptions = { capture: this.resolved.capture, passive: false };
|
|
2357
|
+
this.keydownDisposer = this.events.add(target, "keydown", (event) => this.onKeydown(event), listenerOptions);
|
|
2358
|
+
this.auxDisposers.push(
|
|
2359
|
+
this.events.add(target, "focusin", (event) => {
|
|
2360
|
+
this.focus.observe(event.target, this.resolved.container);
|
|
2361
|
+
})
|
|
2362
|
+
);
|
|
2363
|
+
if (this.resolved.pointerSync && this.resolved.container) {
|
|
2364
|
+
this.auxDisposers.push(
|
|
2365
|
+
this.events.add(this.resolved.container, "click", (event) => this.onPointer(event)),
|
|
2366
|
+
this.events.add(this.resolved.container, "pointerover", (event) => this.onPointerOver(event))
|
|
2367
|
+
);
|
|
2368
|
+
}
|
|
2369
|
+
if (this.resolved.observe) this.collection.startObserving();
|
|
2370
|
+
}
|
|
2371
|
+
detach() {
|
|
2372
|
+
if (this.keydownDisposer) {
|
|
2373
|
+
this.keydownDisposer();
|
|
2374
|
+
this.keydownDisposer = null;
|
|
2375
|
+
}
|
|
2376
|
+
for (const dispose of this.auxDisposers) dispose();
|
|
2377
|
+
this.auxDisposers.length = 0;
|
|
2378
|
+
this.collection.stopObserving();
|
|
2379
|
+
}
|
|
2380
|
+
applyAutoFocus() {
|
|
2381
|
+
const { target } = this.resolved.autoFocus;
|
|
2382
|
+
this.sync(true);
|
|
2383
|
+
const items = this.collection.items;
|
|
2384
|
+
if (items.length === 0) return;
|
|
2385
|
+
let index = 0;
|
|
2386
|
+
if (target === "last") index = items.length - 1;
|
|
2387
|
+
else if (typeof target === "function") index = target(items);
|
|
2388
|
+
this.focus.focusIndex(clampIndex(index, items.length, false), null);
|
|
2389
|
+
}
|
|
2390
|
+
onKeydown(event) {
|
|
2391
|
+
if (this.destroyed) return;
|
|
2392
|
+
if (this.resolved.when && !this.resolved.when()) return;
|
|
2393
|
+
this.collection.revalidate();
|
|
2394
|
+
const normalized = normalizeKeyEvent(event);
|
|
2395
|
+
const match = this.matcher.match(normalized, this);
|
|
2396
|
+
if (this.reservedTarget(event) && !(match?.complete ?? false)) return;
|
|
2397
|
+
if (!match) {
|
|
2398
|
+
if (this.handleTypeahead(normalized, event)) return;
|
|
2399
|
+
if (!isModifierKey(normalized.key)) this.emitter.emit("keydown", { event, handled: false });
|
|
2400
|
+
return;
|
|
2401
|
+
}
|
|
2402
|
+
if (!match.complete) {
|
|
2403
|
+
if (match.preventDefault && this.resolved.preventDefault) event.preventDefault();
|
|
2404
|
+
return;
|
|
2405
|
+
}
|
|
2406
|
+
if (this.reservedTarget(event)) return;
|
|
2407
|
+
if (this.resolved.onlyWhenFocusedInside && !this.focusIsInside()) return;
|
|
2408
|
+
const actions = match.actions;
|
|
2409
|
+
for (let i = 0; i < actions.length; i++) {
|
|
2410
|
+
if (this.runAction(actions[i], event, match)) {
|
|
2411
|
+
if (this.resolved.preventDefault) event.preventDefault();
|
|
2412
|
+
if (this.resolved.stopPropagation) event.stopPropagation();
|
|
2413
|
+
this.emitter.emit("keydown", { event, handled: true });
|
|
2414
|
+
return;
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
2417
|
+
this.emitter.emit("keydown", { event, handled: false });
|
|
2418
|
+
}
|
|
2419
|
+
reservedTarget(event) {
|
|
2420
|
+
if (!this.resolved.ignoreEditable) return false;
|
|
2421
|
+
if (event.target === this.resolved.listenTarget) return false;
|
|
2422
|
+
return isEditableTarget(event.target, this.resolved.editableSelector);
|
|
2423
|
+
}
|
|
2424
|
+
focusIsInside() {
|
|
2425
|
+
if (typeof document === "undefined") return true;
|
|
2426
|
+
const active = document.activeElement;
|
|
2427
|
+
const container = this.resolved.container;
|
|
2428
|
+
const nothingFocused = !active || active === document.body || active === document.documentElement;
|
|
2429
|
+
if (nothingFocused) {
|
|
2430
|
+
return this.focus.element !== null || container === null;
|
|
2431
|
+
}
|
|
2432
|
+
if (!container) return true;
|
|
2433
|
+
if (contains(container, active)) return true;
|
|
2434
|
+
return this.focus.element !== null && contains(container, this.focus.element);
|
|
2435
|
+
}
|
|
2436
|
+
handleTypeahead(normalized, event) {
|
|
2437
|
+
const typeahead = this.typeaheadController;
|
|
2438
|
+
if (!typeahead) return false;
|
|
2439
|
+
if (!normalized.isPrintable || normalized.key.length !== 1 || normalized.repeat) return false;
|
|
2440
|
+
this.sync();
|
|
2441
|
+
const items = this.collection.items;
|
|
2442
|
+
if (items.length === 0) return false;
|
|
2443
|
+
const match = typeahead.handle(normalized.key, items, this.focus.index);
|
|
2444
|
+
if (!match) return false;
|
|
2445
|
+
const index = items.indexOf(match);
|
|
2446
|
+
if (index === -1) return false;
|
|
2447
|
+
const focused = this.focus.focusIndex(index, "typeahead");
|
|
2448
|
+
if (!focused) return false;
|
|
2449
|
+
this.afterMove(index, "typeahead", event);
|
|
2450
|
+
if (this.resolved.preventDefault) event.preventDefault();
|
|
2451
|
+
if (this.resolved.stopPropagation) event.stopPropagation();
|
|
2452
|
+
this.emitter.emit("keydown", { event, handled: true });
|
|
2453
|
+
return true;
|
|
2454
|
+
}
|
|
2455
|
+
runAction(action, event, match) {
|
|
2456
|
+
const fromSequence = match?.fromSequence === true;
|
|
2457
|
+
this.sync();
|
|
2458
|
+
const items = this.collection.items;
|
|
2459
|
+
const index = this.focus.index;
|
|
2460
|
+
const element = index >= 0 ? items[index] ?? null : null;
|
|
2461
|
+
if (MOVEMENT_ACTIONS.has(action)) {
|
|
2462
|
+
if (items.length === 0) return false;
|
|
2463
|
+
return this.runMovement(action, event, items, fromSequence);
|
|
2464
|
+
}
|
|
2465
|
+
switch (action) {
|
|
2466
|
+
case "select": {
|
|
2467
|
+
if (index < 0) return false;
|
|
2468
|
+
const item = items[index];
|
|
2469
|
+
if (this.resolved.selectionOptions?.selectOnActivate !== false) this.applySelection(index, false);
|
|
2470
|
+
this.resolved.handlers.onSelect?.(item, index);
|
|
2471
|
+
this.emitter.emit("select", { element: item, index });
|
|
2472
|
+
this.emitAction(action, event, item, index, fromSequence);
|
|
2473
|
+
return this.selectionManager !== null || this.isHeard(action) || !activatesNatively(item);
|
|
2474
|
+
}
|
|
2475
|
+
case "toggle": {
|
|
2476
|
+
const mode = this.selectionManager?.mode;
|
|
2477
|
+
if (!this.selectionManager || mode === "single" || mode === "none") return false;
|
|
2478
|
+
if (index < 0) return false;
|
|
2479
|
+
this.selectionManager.toggle(index);
|
|
2480
|
+
this.emitAction(action, event, items[index], index, fromSequence);
|
|
2481
|
+
return true;
|
|
2482
|
+
}
|
|
2483
|
+
case "selectAll": {
|
|
2484
|
+
if (!this.selectionManager) return false;
|
|
2485
|
+
this.selectionManager.selectAll();
|
|
2486
|
+
this.emitAction(action, event, element, index, fromSequence);
|
|
2487
|
+
return true;
|
|
2488
|
+
}
|
|
2489
|
+
case "escape": {
|
|
2490
|
+
this.typeaheadController?.reset();
|
|
2491
|
+
this.matcher.resetSequence();
|
|
2492
|
+
this.emitAction(action, event, element, index, fromSequence);
|
|
2493
|
+
this.resolved.handlers.onEscape?.(event);
|
|
2494
|
+
return this.isHeard(action);
|
|
2495
|
+
}
|
|
2496
|
+
default: {
|
|
2497
|
+
this.emitAction(action, event, element, index, fromSequence);
|
|
2498
|
+
return true;
|
|
2499
|
+
}
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
2502
|
+
runMovement(action, event, items, fromSequence = false) {
|
|
2503
|
+
const direction = action === "up" || action === "down" || action === "left" || action === "right" ? action : null;
|
|
2504
|
+
let target;
|
|
2505
|
+
switch (action) {
|
|
2506
|
+
case "first":
|
|
2507
|
+
target = this.resolveNavigation(action, null, event) ?? 0;
|
|
2508
|
+
break;
|
|
2509
|
+
case "last":
|
|
2510
|
+
target = this.resolveNavigation(action, null, event) ?? items.length - 1;
|
|
2511
|
+
break;
|
|
2512
|
+
case "pageUp":
|
|
2513
|
+
case "pageDown": {
|
|
2514
|
+
const delta = (action === "pageDown" ? 1 : -1) * this.resolved.pageSize;
|
|
2515
|
+
const current = this.focus.index;
|
|
2516
|
+
const raw = current < 0 ? delta > 0 ? 0 : items.length - 1 : current + delta;
|
|
2517
|
+
target = clampIndex(raw, items.length, this.resolved.loop);
|
|
2518
|
+
break;
|
|
2519
|
+
}
|
|
2520
|
+
default: {
|
|
2521
|
+
const resolved = this.resolveNavigation(action, direction, event);
|
|
2522
|
+
if (resolved === null) return false;
|
|
2523
|
+
target = resolved;
|
|
2524
|
+
}
|
|
2525
|
+
}
|
|
2526
|
+
const focused = this.focus.focusIndex(target, action);
|
|
2527
|
+
if (!focused) return false;
|
|
2528
|
+
this.afterMove(target, action, event, fromSequence);
|
|
2529
|
+
return true;
|
|
2530
|
+
}
|
|
2531
|
+
isHeard(action) {
|
|
2532
|
+
const { onSelect, onEscape, onAction } = this.resolved.handlers;
|
|
2533
|
+
if (onAction || this.ownsAction(action)) return true;
|
|
2534
|
+
return action === "escape" ? onEscape !== void 0 : onSelect !== void 0;
|
|
2535
|
+
}
|
|
2536
|
+
ownsAction(_action) {
|
|
2537
|
+
return false;
|
|
2538
|
+
}
|
|
2539
|
+
emitAction(action, event, element, index, fromSequence = false) {
|
|
2540
|
+
this.resolved.handlers.onAction?.(action, event, element, index);
|
|
2541
|
+
this.emitter.emit("action", { action, event, element, index, fromSequence });
|
|
2542
|
+
}
|
|
2543
|
+
afterMove(index, action, event, fromSequence = false) {
|
|
2544
|
+
this.emitAction(action, event, this.collection.items[index] ?? null, index, fromSequence);
|
|
2545
|
+
const selection = this.resolved.selectionOptions;
|
|
2546
|
+
if (!selection) return;
|
|
2547
|
+
if (!selection.followFocus && selection.mode === "range" && selection.shiftExtendsRange !== false && event?.shiftKey) {
|
|
2548
|
+
this.extendRangeTo(index);
|
|
2549
|
+
}
|
|
2550
|
+
}
|
|
2551
|
+
resolveNavigation(action, direction, event) {
|
|
2552
|
+
const items = this.collection.items;
|
|
2553
|
+
if (items.length === 0) return null;
|
|
2554
|
+
const context = this.createContext(action, direction, event);
|
|
2555
|
+
if (this.resolved.onBeforeNavigate) {
|
|
2556
|
+
const override = this.resolved.onBeforeNavigate(context);
|
|
2557
|
+
if (typeof override === "number" && override >= 0 && override < items.length) return override;
|
|
2558
|
+
}
|
|
2559
|
+
const result = this.resolved.strategy(context);
|
|
2560
|
+
if (typeof result !== "number" || result < 0) return null;
|
|
2561
|
+
return result >= items.length ? items.length - 1 : result;
|
|
2562
|
+
}
|
|
2563
|
+
createContext(action, direction, event) {
|
|
2564
|
+
return {
|
|
2565
|
+
items: this.collection.items,
|
|
2566
|
+
container: this.resolved.container,
|
|
2567
|
+
index: this.focus.index,
|
|
2568
|
+
direction,
|
|
2569
|
+
action,
|
|
2570
|
+
event,
|
|
2571
|
+
shiftKey: event?.shiftKey === true,
|
|
2572
|
+
altKey: event?.altKey === true,
|
|
2573
|
+
ctrlKey: event?.ctrlKey === true,
|
|
2574
|
+
metaKey: event?.metaKey === true,
|
|
2575
|
+
columns: this.resolved.columns,
|
|
2576
|
+
loop: this.resolved.loop,
|
|
2577
|
+
version: this.collection.version,
|
|
2578
|
+
rectOf: createRectOf(this.collection)
|
|
2579
|
+
};
|
|
2580
|
+
}
|
|
2581
|
+
step(delta, action) {
|
|
2582
|
+
this.collection.revalidate();
|
|
2583
|
+
this.focus.sync(this.collection.items, this.resolved.container);
|
|
2584
|
+
this.sync();
|
|
2585
|
+
const total = this.collection.items.length;
|
|
2586
|
+
if (total === 0 || delta === 0) return null;
|
|
2587
|
+
const current = this.focus.index;
|
|
2588
|
+
const raw = current < 0 ? delta > 0 ? 0 : total - 1 : current + delta;
|
|
2589
|
+
const target = clampIndex(raw, total, this.resolved.loop);
|
|
2590
|
+
const focused = this.focus.focusIndex(target, action);
|
|
2591
|
+
if (focused && action) this.afterMove(target, action, null);
|
|
2592
|
+
return focused;
|
|
2593
|
+
}
|
|
2594
|
+
resolveIndex(elementOrIndex) {
|
|
2595
|
+
if (elementOrIndex === void 0) return this.focus.index;
|
|
2596
|
+
if (typeof elementOrIndex === "number") return elementOrIndex;
|
|
2597
|
+
return this.collection.indexOf(elementOrIndex);
|
|
2598
|
+
}
|
|
2599
|
+
applySelection(index, extend = false) {
|
|
2600
|
+
const manager = this.selectionManager;
|
|
2601
|
+
if (!manager) return;
|
|
2602
|
+
manager.select(index, extend);
|
|
2603
|
+
}
|
|
2604
|
+
extendRangeTo(index) {
|
|
2605
|
+
const manager = this.selectionManager;
|
|
2606
|
+
if (!manager) return;
|
|
2607
|
+
const items = this.collection.items;
|
|
2608
|
+
const anchor = manager.anchor;
|
|
2609
|
+
const from = anchor ? items.indexOf(anchor) : index;
|
|
2610
|
+
if (from < 0) return;
|
|
2611
|
+
manager.selectRange(from, index);
|
|
2612
|
+
}
|
|
2613
|
+
onPointer(event) {
|
|
2614
|
+
const item = this.itemFromEvent(event);
|
|
2615
|
+
if (!item) return;
|
|
2616
|
+
const index = this.collection.indexOf(item);
|
|
2617
|
+
if (index === -1) return;
|
|
2618
|
+
this.focus.focusIndex(index, "select");
|
|
2619
|
+
const manager = this.selectionManager;
|
|
2620
|
+
if (!manager) return;
|
|
2621
|
+
const mode = manager.mode;
|
|
2622
|
+
if (mode === "none") return;
|
|
2623
|
+
if (mode === "multiple") {
|
|
2624
|
+
if (event.ctrlKey || event.metaKey) manager.toggle(index);
|
|
2625
|
+
else manager.select(index);
|
|
2626
|
+
return;
|
|
2627
|
+
}
|
|
2628
|
+
if (mode === "range" && event.shiftKey) {
|
|
2629
|
+
this.extendRangeTo(index);
|
|
2630
|
+
return;
|
|
2631
|
+
}
|
|
2632
|
+
this.applySelection(index, false);
|
|
2633
|
+
}
|
|
2634
|
+
onPointerOver(event) {
|
|
2635
|
+
const item = this.itemFromEvent(event);
|
|
2636
|
+
if (item) this.focus.observe(item, this.resolved.container);
|
|
2637
|
+
}
|
|
2638
|
+
itemFromEvent(event) {
|
|
2639
|
+
const target = event.target;
|
|
2640
|
+
if (!target) return null;
|
|
2641
|
+
this.collection.revalidate();
|
|
2642
|
+
this.sync();
|
|
2643
|
+
const items = this.collection.items;
|
|
2644
|
+
const selector = this.resolved.selector;
|
|
2645
|
+
if (typeof selector === "string" && typeof target.closest === "function") {
|
|
2646
|
+
try {
|
|
2647
|
+
const closest = target.closest(selector);
|
|
2648
|
+
if (closest && items.indexOf(closest) !== -1) return closest;
|
|
2649
|
+
} catch {
|
|
2650
|
+
}
|
|
2651
|
+
}
|
|
2652
|
+
let node = target;
|
|
2653
|
+
while (node) {
|
|
2654
|
+
if (items.indexOf(node) !== -1) return node;
|
|
2655
|
+
node = node.parentElement;
|
|
2656
|
+
}
|
|
2657
|
+
return null;
|
|
2658
|
+
}
|
|
2659
|
+
sync(force = false) {
|
|
2660
|
+
if (this.destroyed) return;
|
|
2661
|
+
if (!force && !this.syncPending) return;
|
|
2662
|
+
this.syncPending = false;
|
|
2663
|
+
const items = this.collection.items;
|
|
2664
|
+
this.focus.sync(items, this.resolved.container);
|
|
2665
|
+
this.selectionManager?.sync(items);
|
|
2666
|
+
}
|
|
2667
|
+
};
|
|
2668
|
+
function activatesNatively(element) {
|
|
2669
|
+
return element.matches("a[href],button,summary,input,select,textarea");
|
|
2670
|
+
}
|
|
2671
|
+
function clampIndex(index, total, loop) {
|
|
2672
|
+
if (total === 0) return -1;
|
|
2673
|
+
if (loop) return (index % total + total) % total;
|
|
2674
|
+
if (index < 0) return 0;
|
|
2675
|
+
if (index >= total) return total - 1;
|
|
2676
|
+
return index;
|
|
2677
|
+
}
|
|
2678
|
+
function createKeystra(options = {}) {
|
|
2679
|
+
return new NavigationController(options);
|
|
2680
|
+
}
|
|
2681
|
+
|
|
2682
|
+
// src/patterns/shared.ts
|
|
2683
|
+
var LANDMARKS = /* @__PURE__ */ new Set(["NAV", "MAIN", "ASIDE", "HEADER", "FOOTER", "FORM", "SECTION", "ARTICLE", "DIALOG"]);
|
|
2684
|
+
function acceptsRole(element) {
|
|
2685
|
+
return !element.hasAttribute("role") && !LANDMARKS.has(element.tagName);
|
|
2686
|
+
}
|
|
2687
|
+
function ensureRole(element, role, enabled) {
|
|
2688
|
+
if (enabled && acceptsRole(element)) element.setAttribute("role", role);
|
|
2689
|
+
}
|
|
2690
|
+
function ensureItemRoles(container, items, containerRole, itemRole, enabled) {
|
|
2691
|
+
if (!enabled || !container) return;
|
|
2692
|
+
if (container.getAttribute("role") !== containerRole) return;
|
|
2693
|
+
for (let i = 0; i < items.length; i++) ensureRole(items[i], itemRole, true);
|
|
2694
|
+
}
|
|
2695
|
+
function setAria(element, name, value, enabled) {
|
|
2696
|
+
if (!enabled || !element) return;
|
|
2697
|
+
setAttribute(element, name, value);
|
|
2698
|
+
}
|
|
2699
|
+
|
|
2700
|
+
// src/patterns/tabs.ts
|
|
2701
|
+
var TabsController = class extends NavigationController {
|
|
2702
|
+
constructor(options) {
|
|
2703
|
+
const orientation = options.orientation ?? "horizontal";
|
|
2704
|
+
super(options, {
|
|
2705
|
+
mode: options.mode ?? (orientation === "vertical" ? "vertical" : "horizontal"),
|
|
2706
|
+
loop: options.loop ?? true,
|
|
2707
|
+
defaultKeys: orientation === "vertical" ? TABS_KEYS_VERTICAL : TABS_KEYS_HORIZONTAL
|
|
2708
|
+
});
|
|
2709
|
+
this.panels = [];
|
|
2710
|
+
this.activeValue = -1;
|
|
2711
|
+
this.applying = false;
|
|
2712
|
+
this.initializing = true;
|
|
2713
|
+
this.orientationValue = orientation;
|
|
2714
|
+
this.activationValue = options.activation ?? "automatic";
|
|
2715
|
+
this.manageAria = options.aria !== false;
|
|
2716
|
+
this.activeAttribute = options.activeAttribute ?? "aria-selected";
|
|
2717
|
+
this.panelActiveAttribute = options.panelActiveAttribute ?? "hidden";
|
|
2718
|
+
this.panelSelector = options.panels;
|
|
2719
|
+
this.userOnActivate = options.onActivate;
|
|
2720
|
+
if (this.manageAria && this.container) {
|
|
2721
|
+
ensureRole(this.container, "tablist", true);
|
|
2722
|
+
if (this.container.getAttribute("role") === "tablist") {
|
|
2723
|
+
setAria(this.container, "aria-orientation", this.orientationValue, true);
|
|
2724
|
+
}
|
|
2725
|
+
}
|
|
2726
|
+
this.on("focus", ({ index }) => {
|
|
2727
|
+
if (this.activationValue === "automatic") this.activate(index);
|
|
2728
|
+
});
|
|
2729
|
+
this.on("action", ({ action, index }) => {
|
|
2730
|
+
if (this.activationValue === "manual" && action === "select" && index >= 0) this.activate(index);
|
|
2731
|
+
});
|
|
2732
|
+
this.on("refresh", () => this.refreshPanels());
|
|
2733
|
+
this.refreshPanels();
|
|
2734
|
+
this.decorate();
|
|
2735
|
+
const initial = this.detectInitialIndex();
|
|
2736
|
+
this.applyActive(initial);
|
|
2737
|
+
this.initializing = false;
|
|
2738
|
+
}
|
|
2739
|
+
get activeTab() {
|
|
2740
|
+
return this.activeValue >= 0 ? this.items[this.activeValue] ?? null : null;
|
|
2741
|
+
}
|
|
2742
|
+
get activeIndex() {
|
|
2743
|
+
return this.activeValue;
|
|
2744
|
+
}
|
|
2745
|
+
get orientation() {
|
|
2746
|
+
return this.orientationValue;
|
|
2747
|
+
}
|
|
2748
|
+
get activation() {
|
|
2749
|
+
return this.activationValue;
|
|
2750
|
+
}
|
|
2751
|
+
activate(indexOrElement) {
|
|
2752
|
+
if (indexOrElement === null) {
|
|
2753
|
+
this.applyActive(-1);
|
|
2754
|
+
return;
|
|
2755
|
+
}
|
|
2756
|
+
const index = typeof indexOrElement === "number" ? indexOrElement : this.items.indexOf(indexOrElement);
|
|
2757
|
+
if (index < 0 || index >= this.items.length) return;
|
|
2758
|
+
this.applyActive(index, true);
|
|
2759
|
+
}
|
|
2760
|
+
refresh() {
|
|
2761
|
+
const items = super.refresh();
|
|
2762
|
+
this.refreshPanels();
|
|
2763
|
+
this.decorate();
|
|
2764
|
+
if (this.activeValue >= items.length) this.applyActive(items.length > 0 ? 0 : -1);
|
|
2765
|
+
else this.applyActive(this.activeValue);
|
|
2766
|
+
return items;
|
|
2767
|
+
}
|
|
2768
|
+
ownsAction(action) {
|
|
2769
|
+
return action === "select";
|
|
2770
|
+
}
|
|
2771
|
+
refreshPanels() {
|
|
2772
|
+
if (!this.panelSelector || !this.container) {
|
|
2773
|
+
this.panels = [];
|
|
2774
|
+
return;
|
|
2775
|
+
}
|
|
2776
|
+
let panels = queryItems(this.container, this.panelSelector);
|
|
2777
|
+
if (panels.length === 0) {
|
|
2778
|
+
const scope = this.container.parentElement;
|
|
2779
|
+
if (scope && scope !== this.container.ownerDocument?.documentElement) {
|
|
2780
|
+
panels = queryItems(scope, this.panelSelector);
|
|
2781
|
+
}
|
|
2782
|
+
}
|
|
2783
|
+
this.panels = panels;
|
|
2784
|
+
}
|
|
2785
|
+
decorate() {
|
|
2786
|
+
const items = this.items;
|
|
2787
|
+
for (let i = 0; i < items.length; i++) {
|
|
2788
|
+
const tab = items[i];
|
|
2789
|
+
ensureRole(tab, "tab", this.manageAria);
|
|
2790
|
+
if (!this.manageAria) continue;
|
|
2791
|
+
const tabId = ensureId(tab, "keystra-tab");
|
|
2792
|
+
const panel = this.panels[i];
|
|
2793
|
+
if (!panel) continue;
|
|
2794
|
+
const panelId = ensureId(panel, "keystra-panel");
|
|
2795
|
+
ensureRole(panel, "tabpanel", true);
|
|
2796
|
+
setAria(tab, "aria-controls", panelId, true);
|
|
2797
|
+
setAria(panel, "aria-labelledby", tabId, true);
|
|
2798
|
+
}
|
|
2799
|
+
}
|
|
2800
|
+
detectInitialIndex() {
|
|
2801
|
+
const items = this.items;
|
|
2802
|
+
for (let i = 0; i < items.length; i++) {
|
|
2803
|
+
if (items[i].getAttribute(this.activeAttribute) === "true") return i;
|
|
2804
|
+
}
|
|
2805
|
+
return items.length > 0 ? 0 : -1;
|
|
2806
|
+
}
|
|
2807
|
+
applyActive(index, moveFocus = false) {
|
|
2808
|
+
if (this.applying) return;
|
|
2809
|
+
this.applying = true;
|
|
2810
|
+
try {
|
|
2811
|
+
this.applyActiveNow(index, moveFocus);
|
|
2812
|
+
} finally {
|
|
2813
|
+
this.applying = false;
|
|
2814
|
+
}
|
|
2815
|
+
}
|
|
2816
|
+
applyActiveNow(index, moveFocus) {
|
|
2817
|
+
const items = this.items;
|
|
2818
|
+
const previous = this.activeValue;
|
|
2819
|
+
this.activeValue = index;
|
|
2820
|
+
const writeMarker = this.manageAria || this.activeAttribute !== "aria-selected";
|
|
2821
|
+
const writePanels = this.manageAria;
|
|
2822
|
+
for (let i = 0; i < items.length; i++) {
|
|
2823
|
+
const tab = items[i];
|
|
2824
|
+
const on = i === index;
|
|
2825
|
+
if (writeMarker && tab.getAttribute(this.activeAttribute) !== (on ? "true" : "false")) {
|
|
2826
|
+
tab.setAttribute(this.activeAttribute, on ? "true" : "false");
|
|
2827
|
+
}
|
|
2828
|
+
if (this.manageAria && this.activeAttribute !== "aria-selected") {
|
|
2829
|
+
setAria(tab, "aria-selected", on ? "true" : "false", true);
|
|
2830
|
+
}
|
|
2831
|
+
if (!writePanels) continue;
|
|
2832
|
+
const panel = this.panels[i];
|
|
2833
|
+
if (!panel) continue;
|
|
2834
|
+
if (this.panelActiveAttribute === "hidden") {
|
|
2835
|
+
if (panel.hasAttribute("hidden") === on) panel.toggleAttribute("hidden", !on);
|
|
2836
|
+
} else {
|
|
2837
|
+
panel.setAttribute(this.panelActiveAttribute, on ? "true" : "false");
|
|
2838
|
+
}
|
|
2839
|
+
}
|
|
2840
|
+
if (index >= 0) {
|
|
2841
|
+
if (moveFocus && items[index] !== this.activeElement) this.focusIndex(index);
|
|
2842
|
+
else if (!moveFocus) this.setActiveIndex(index, { focus: false });
|
|
2843
|
+
}
|
|
2844
|
+
if (previous !== index && index >= 0 && !this.initializing) {
|
|
2845
|
+
const element = items[index];
|
|
2846
|
+
if (element) this.userOnActivate?.(element, index);
|
|
2847
|
+
}
|
|
2848
|
+
}
|
|
2849
|
+
};
|
|
2850
|
+
function createTabs(options = {}) {
|
|
2851
|
+
return new TabsController(options);
|
|
2852
|
+
}
|
|
2853
|
+
|
|
2854
|
+
// src/patterns/menu.ts
|
|
2855
|
+
var MenuController = class extends NavigationController {
|
|
2856
|
+
constructor(options) {
|
|
2857
|
+
super(
|
|
2858
|
+
{
|
|
2859
|
+
typeahead: true,
|
|
2860
|
+
...options,
|
|
2861
|
+
enabled: false
|
|
2862
|
+
},
|
|
2863
|
+
{
|
|
2864
|
+
mode: options.mode ?? "vertical",
|
|
2865
|
+
loop: options.loop ?? true,
|
|
2866
|
+
defaultKeys: MENU_KEYS
|
|
2867
|
+
}
|
|
2868
|
+
);
|
|
2869
|
+
this.openValue = false;
|
|
2870
|
+
this.unhidden = false;
|
|
2871
|
+
this.closeOnSelect = options.closeOnSelect !== false;
|
|
2872
|
+
this.returnFocusTarget = options.returnFocusTo;
|
|
2873
|
+
this.manageAria = options.aria !== false;
|
|
2874
|
+
this.toggleHiddenValue = options.toggleHidden !== false;
|
|
2875
|
+
this.userOnOpen = options.onOpen;
|
|
2876
|
+
this.userOnClose = options.onClose;
|
|
2877
|
+
if (this.manageAria && this.container) ensureRole(this.container, "menu", true);
|
|
2878
|
+
this.decorateItems();
|
|
2879
|
+
this.on("select", () => {
|
|
2880
|
+
if (this.closeOnSelect) this.close();
|
|
2881
|
+
});
|
|
2882
|
+
this.on("action", ({ action }) => {
|
|
2883
|
+
if (action === "escape") this.close();
|
|
2884
|
+
});
|
|
2885
|
+
this.on("refresh", () => this.decorateItems());
|
|
2886
|
+
}
|
|
2887
|
+
decorateItems() {
|
|
2888
|
+
ensureItemRoles(this.container, this.items, "menu", "menuitem", this.manageAria);
|
|
2889
|
+
}
|
|
2890
|
+
get isOpen() {
|
|
2891
|
+
return this.openValue;
|
|
2892
|
+
}
|
|
2893
|
+
open(options = {}) {
|
|
2894
|
+
if (this.openValue) return;
|
|
2895
|
+
this.openValue = true;
|
|
2896
|
+
this.enable();
|
|
2897
|
+
this.rememberFocus();
|
|
2898
|
+
if (this.toggleHiddenValue && this.container?.hasAttribute("hidden")) {
|
|
2899
|
+
this.container.removeAttribute("hidden");
|
|
2900
|
+
this.unhidden = true;
|
|
2901
|
+
}
|
|
2902
|
+
this.refresh();
|
|
2903
|
+
if (this.manageAria && this.container) this.container.setAttribute("data-keystra-open", "true");
|
|
2904
|
+
const target = options.focus ?? "first";
|
|
2905
|
+
const items = this.items;
|
|
2906
|
+
if (items.length === 0) {
|
|
2907
|
+
this.userOnOpen?.(this);
|
|
2908
|
+
return;
|
|
2909
|
+
}
|
|
2910
|
+
if (target === "current" && this.currentIndex >= 0) {
|
|
2911
|
+
this.focusIndex(this.currentIndex);
|
|
2912
|
+
} else if (target === "last") {
|
|
2913
|
+
this.focusIndex(items.length - 1);
|
|
2914
|
+
} else if (typeof target === "number") {
|
|
2915
|
+
this.focusIndex(Math.max(0, Math.min(target, items.length - 1)));
|
|
2916
|
+
} else {
|
|
2917
|
+
this.focusIndex(0);
|
|
2918
|
+
}
|
|
2919
|
+
this.userOnOpen?.(this);
|
|
2920
|
+
}
|
|
2921
|
+
close(options = {}) {
|
|
2922
|
+
if (!this.openValue) return;
|
|
2923
|
+
this.openValue = false;
|
|
2924
|
+
this.disable();
|
|
2925
|
+
if (this.manageAria && this.container) this.container.removeAttribute("data-keystra-open");
|
|
2926
|
+
if (options.restoreFocus !== false) {
|
|
2927
|
+
const explicit = resolveElement(this.returnFocusTarget, this.container?.ownerDocument ?? null);
|
|
2928
|
+
if (explicit) focusElement(explicit);
|
|
2929
|
+
else this.restoreFocus();
|
|
2930
|
+
}
|
|
2931
|
+
if (this.unhidden) {
|
|
2932
|
+
this.container?.setAttribute("hidden", "");
|
|
2933
|
+
this.unhidden = false;
|
|
2934
|
+
}
|
|
2935
|
+
this.userOnClose?.(this);
|
|
2936
|
+
}
|
|
2937
|
+
toggle() {
|
|
2938
|
+
if (this.openValue) this.close();
|
|
2939
|
+
else this.open();
|
|
2940
|
+
}
|
|
2941
|
+
destroy() {
|
|
2942
|
+
this.openValue = false;
|
|
2943
|
+
super.destroy();
|
|
2944
|
+
}
|
|
2945
|
+
ownsAction(action) {
|
|
2946
|
+
return action === "select" || action === "escape";
|
|
2947
|
+
}
|
|
2948
|
+
};
|
|
2949
|
+
function createMenu(options = {}) {
|
|
2950
|
+
return new MenuController(options);
|
|
2951
|
+
}
|
|
2952
|
+
|
|
2953
|
+
// src/patterns/dropdown.ts
|
|
2954
|
+
var DEFAULT_OPEN_KEYS = ["Enter", "Space", "ArrowDown", "ArrowUp"];
|
|
2955
|
+
var DropdownController = class extends MenuController {
|
|
2956
|
+
constructor(options) {
|
|
2957
|
+
super({ typeahead: true, ...options });
|
|
2958
|
+
this.extra = new EventManager();
|
|
2959
|
+
const doc = this.container?.ownerDocument ?? (typeof document !== "undefined" ? document : null);
|
|
2960
|
+
this.triggerElement = resolveElement(options.trigger, doc);
|
|
2961
|
+
this.manageTriggerAria = options.aria !== false;
|
|
2962
|
+
this.closeOnBlurValue = options.closeOnBlur === true;
|
|
2963
|
+
this.closeOnOutsidePointerValue = options.closeOnOutsidePointer !== false;
|
|
2964
|
+
const openKeys = options.openKeys === void 0 || options.openKeys === null ? DEFAULT_OPEN_KEYS : Array.isArray(options.openKeys) ? options.openKeys : [options.openKeys];
|
|
2965
|
+
this.openMatcher = new KeyMatcher({ open: openKeys });
|
|
2966
|
+
this.menuId = this.container ? ensureId(this.container, "keystra-menu") : null;
|
|
2967
|
+
this.decorateTrigger();
|
|
2968
|
+
this.wireTrigger();
|
|
2969
|
+
this.on("action", ({ action, event }) => {
|
|
2970
|
+
if (action !== "escape") return;
|
|
2971
|
+
event?.preventDefault();
|
|
2972
|
+
this.close();
|
|
2973
|
+
});
|
|
2974
|
+
}
|
|
2975
|
+
get trigger() {
|
|
2976
|
+
return this.triggerElement;
|
|
2977
|
+
}
|
|
2978
|
+
open(options = {}) {
|
|
2979
|
+
const wasOpen = this.isOpen;
|
|
2980
|
+
super.open(options);
|
|
2981
|
+
if (!wasOpen && this.isOpen) this.syncTriggerState();
|
|
2982
|
+
}
|
|
2983
|
+
close(options = {}) {
|
|
2984
|
+
const wasOpen = this.isOpen;
|
|
2985
|
+
super.close({ restoreFocus: false });
|
|
2986
|
+
if (wasOpen && options.restoreFocus !== false && this.triggerElement) {
|
|
2987
|
+
focusElement(this.triggerElement);
|
|
2988
|
+
}
|
|
2989
|
+
if (wasOpen) this.syncTriggerState();
|
|
2990
|
+
}
|
|
2991
|
+
destroy() {
|
|
2992
|
+
this.extra.dispose();
|
|
2993
|
+
this.openMatcher.dispose();
|
|
2994
|
+
if (this.triggerElement && this.manageTriggerAria) {
|
|
2995
|
+
this.triggerElement.removeAttribute("aria-expanded");
|
|
2996
|
+
if (this.menuId) this.triggerElement.removeAttribute("aria-controls");
|
|
2997
|
+
}
|
|
2998
|
+
super.destroy();
|
|
2999
|
+
}
|
|
3000
|
+
decorateTrigger() {
|
|
3001
|
+
const trigger = this.triggerElement;
|
|
3002
|
+
if (!trigger || !this.manageTriggerAria) return;
|
|
3003
|
+
if (!trigger.hasAttribute("aria-haspopup")) trigger.setAttribute("aria-haspopup", "menu");
|
|
3004
|
+
setAria(trigger, "aria-controls", this.menuId, true);
|
|
3005
|
+
this.syncTriggerState();
|
|
3006
|
+
}
|
|
3007
|
+
syncTriggerState() {
|
|
3008
|
+
if (!this.triggerElement || !this.manageTriggerAria) return;
|
|
3009
|
+
this.triggerElement.setAttribute("aria-expanded", this.isOpen ? "true" : "false");
|
|
3010
|
+
}
|
|
3011
|
+
wireTrigger() {
|
|
3012
|
+
const trigger = this.triggerElement;
|
|
3013
|
+
if (!trigger) return;
|
|
3014
|
+
this.extra.add(trigger, "click", () => {
|
|
3015
|
+
this.toggle();
|
|
3016
|
+
});
|
|
3017
|
+
this.extra.add(trigger, "keydown", (rawEvent) => {
|
|
3018
|
+
const event = rawEvent;
|
|
3019
|
+
if (this.isOpen) {
|
|
3020
|
+
if (event.key === "Tab") this.close({ restoreFocus: false });
|
|
3021
|
+
return;
|
|
3022
|
+
}
|
|
3023
|
+
const normalized = normalizeKeyEvent(event);
|
|
3024
|
+
const match = this.openMatcher.match(normalized, this);
|
|
3025
|
+
if (!match?.complete) return;
|
|
3026
|
+
event.preventDefault();
|
|
3027
|
+
this.open({ focus: normalized.key === "ArrowUp" ? "last" : "first" });
|
|
3028
|
+
});
|
|
3029
|
+
if (typeof document !== "undefined") {
|
|
3030
|
+
this.extra.add(document, "keydown", (rawEvent) => {
|
|
3031
|
+
const event = rawEvent;
|
|
3032
|
+
if (event.key !== "Tab" || event.defaultPrevented || !this.isOpen) return;
|
|
3033
|
+
if (contains(this.container, event.target)) this.close();
|
|
3034
|
+
});
|
|
3035
|
+
}
|
|
3036
|
+
if (this.closeOnOutsidePointerValue && typeof document !== "undefined") {
|
|
3037
|
+
this.extra.add(document, "pointerdown", (rawEvent) => {
|
|
3038
|
+
if (!this.isOpen) return;
|
|
3039
|
+
const target = rawEvent.target;
|
|
3040
|
+
if (contains(this.container, target) || contains(this.triggerElement, target)) return;
|
|
3041
|
+
this.close({ restoreFocus: false });
|
|
3042
|
+
});
|
|
3043
|
+
}
|
|
3044
|
+
if (this.closeOnBlurValue && typeof document !== "undefined") {
|
|
3045
|
+
this.extra.add(document, "focusin", (rawEvent) => {
|
|
3046
|
+
if (!this.isOpen) return;
|
|
3047
|
+
const target = rawEvent.target;
|
|
3048
|
+
if (contains(this.container, target) || contains(this.triggerElement, target)) return;
|
|
3049
|
+
this.close({ restoreFocus: false });
|
|
3050
|
+
});
|
|
3051
|
+
}
|
|
3052
|
+
}
|
|
3053
|
+
};
|
|
3054
|
+
function createDropdown(options = {}) {
|
|
3055
|
+
return new DropdownController(options);
|
|
3056
|
+
}
|
|
3057
|
+
|
|
3058
|
+
// src/patterns/list.ts
|
|
3059
|
+
var ListController = class extends NavigationController {
|
|
3060
|
+
constructor(options) {
|
|
3061
|
+
super(options, {
|
|
3062
|
+
mode: options.mode ?? "vertical",
|
|
3063
|
+
loop: options.loop ?? false,
|
|
3064
|
+
defaultKeys: LIST_KEYS
|
|
3065
|
+
});
|
|
3066
|
+
this.manageAria = options.aria !== false;
|
|
3067
|
+
if (this.manageAria && this.container) {
|
|
3068
|
+
ensureRole(this.container, "listbox", true);
|
|
3069
|
+
if (this.container.getAttribute("role") === "listbox" && this.selectionManager) {
|
|
3070
|
+
const multiple = this.selectionManager.mode === "multiple" || this.selectionManager.mode === "range";
|
|
3071
|
+
setAria(this.container, "aria-multiselectable", multiple ? "true" : null, true);
|
|
3072
|
+
}
|
|
3073
|
+
}
|
|
3074
|
+
this.decorateItems();
|
|
3075
|
+
this.on("refresh", () => this.decorateItems());
|
|
3076
|
+
}
|
|
3077
|
+
decorateItems() {
|
|
3078
|
+
ensureItemRoles(this.container, this.items, "listbox", "option", this.manageAria);
|
|
3079
|
+
}
|
|
3080
|
+
};
|
|
3081
|
+
function createList(options = {}) {
|
|
3082
|
+
const container = resolveElement(options.container);
|
|
3083
|
+
const aria = options.aria !== false && container !== null && (container.getAttribute("role") === "listbox" || acceptsRole(container));
|
|
3084
|
+
const selection = options.selection ?? { enabled: true, mode: "single", followFocus: true };
|
|
3085
|
+
return new ListController({
|
|
3086
|
+
typeahead: true,
|
|
3087
|
+
...options,
|
|
3088
|
+
selection: typeof selection === "object" ? { aria, ...selection } : selection && { mode: "single", aria }
|
|
3089
|
+
});
|
|
3090
|
+
}
|
|
3091
|
+
|
|
3092
|
+
// src/patterns/grid.ts
|
|
3093
|
+
var GridController = class extends NavigationController {
|
|
3094
|
+
constructor(options) {
|
|
3095
|
+
super(options, {
|
|
3096
|
+
mode: "grid",
|
|
3097
|
+
loop: options.loop ?? false,
|
|
3098
|
+
columns: options.columns ?? 0,
|
|
3099
|
+
strategy: createGridStrategy({
|
|
3100
|
+
maintainColumn: options.maintainColumn,
|
|
3101
|
+
rowHomeEnd: options.rowHomeEnd
|
|
3102
|
+
})
|
|
3103
|
+
});
|
|
3104
|
+
this.manageAria = options.aria !== false;
|
|
3105
|
+
this.decorateRoles();
|
|
3106
|
+
this.on("refresh", () => this.decorateRoles());
|
|
3107
|
+
}
|
|
3108
|
+
decorateRoles() {
|
|
3109
|
+
const container = this.container;
|
|
3110
|
+
if (!this.manageAria || !container) return;
|
|
3111
|
+
if (container.querySelector("[role='row']") === null) return;
|
|
3112
|
+
ensureRole(container, "grid", true);
|
|
3113
|
+
const items = this.items;
|
|
3114
|
+
for (let i = 0; i < items.length; i++) {
|
|
3115
|
+
const item = items[i];
|
|
3116
|
+
if (item.parentElement?.getAttribute("role") === "row") ensureRole(item, "gridcell", true);
|
|
3117
|
+
}
|
|
3118
|
+
}
|
|
3119
|
+
get columns() {
|
|
3120
|
+
if (this.resolved.columns > 0) return this.resolved.columns;
|
|
3121
|
+
const items = this.items;
|
|
3122
|
+
if (items.length === 0) return 0;
|
|
3123
|
+
let columns = 1;
|
|
3124
|
+
const first = items[0];
|
|
3125
|
+
const top = this.rectOf(first).top;
|
|
3126
|
+
for (let i = 1; i < items.length; i++) {
|
|
3127
|
+
if (Math.abs(this.rectOf(items[i]).top - top) < 2) columns++;
|
|
3128
|
+
else break;
|
|
3129
|
+
}
|
|
3130
|
+
return columns;
|
|
3131
|
+
}
|
|
3132
|
+
get rows() {
|
|
3133
|
+
const columns = this.columns;
|
|
3134
|
+
if (columns <= 0) return 0;
|
|
3135
|
+
return Math.ceil(this.items.length / columns);
|
|
3136
|
+
}
|
|
3137
|
+
get position() {
|
|
3138
|
+
const index = this.activeIndex;
|
|
3139
|
+
const columns = this.columns;
|
|
3140
|
+
if (index < 0 || columns <= 0) return { row: -1, column: -1 };
|
|
3141
|
+
return { row: Math.floor(index / columns), column: index % columns };
|
|
3142
|
+
}
|
|
3143
|
+
focusCell(row, column) {
|
|
3144
|
+
const columns = this.columns;
|
|
3145
|
+
if (columns <= 0) return null;
|
|
3146
|
+
const index = row * columns + column;
|
|
3147
|
+
if (index < 0 || index >= this.items.length) return null;
|
|
3148
|
+
return this.focusIndex(index);
|
|
3149
|
+
}
|
|
3150
|
+
rectOf(element) {
|
|
3151
|
+
return this.collection.layout.rect(element, this.collection.version);
|
|
3152
|
+
}
|
|
3153
|
+
};
|
|
3154
|
+
function createGrid(options = {}) {
|
|
3155
|
+
return new GridController(options);
|
|
3156
|
+
}
|
|
3157
|
+
|
|
3158
|
+
// src/patterns/tree.ts
|
|
3159
|
+
var DEFAULT_ITEM_SELECTOR2 = "[role='treeitem']";
|
|
3160
|
+
var DEFAULT_GROUP_SELECTOR = "[role='group']";
|
|
3161
|
+
var TreeController = class extends NavigationController {
|
|
3162
|
+
constructor(options) {
|
|
3163
|
+
const hiddenClass = options.hiddenClass;
|
|
3164
|
+
super(
|
|
3165
|
+
{
|
|
3166
|
+
typeahead: true,
|
|
3167
|
+
items: options.items ?? DEFAULT_ITEM_SELECTOR2,
|
|
3168
|
+
...options,
|
|
3169
|
+
filter: hiddenClass ? (element, index) => {
|
|
3170
|
+
if (options.filter && !options.filter(element, index)) return false;
|
|
3171
|
+
const group = element.parentElement?.closest(`.${hiddenClass}`);
|
|
3172
|
+
return !group;
|
|
3173
|
+
} : options.filter
|
|
3174
|
+
},
|
|
3175
|
+
{
|
|
3176
|
+
mode: options.mode ?? "vertical",
|
|
3177
|
+
loop: options.loop ?? false,
|
|
3178
|
+
defaultKeys: TREE_KEYS
|
|
3179
|
+
}
|
|
3180
|
+
);
|
|
3181
|
+
this.groupSelector = options.groupSelector ?? DEFAULT_GROUP_SELECTOR;
|
|
3182
|
+
this.manageAria = options.aria !== false;
|
|
3183
|
+
this.collapseBehavior = options.collapseBehavior ?? "attribute";
|
|
3184
|
+
this.toggleHiddenValue = options.toggleHidden === true || this.collapseBehavior === "hidden";
|
|
3185
|
+
this.hiddenClassValue = options.hiddenClass;
|
|
3186
|
+
this.userOnExpand = options.onExpand;
|
|
3187
|
+
this.userOnCollapse = options.onCollapse;
|
|
3188
|
+
if (this.manageAria && this.container) ensureRole(this.container, "tree", true);
|
|
3189
|
+
this.decorateItems();
|
|
3190
|
+
this.on("action", ({ action, element }) => {
|
|
3191
|
+
if (!element) return;
|
|
3192
|
+
if (action === "expand") this.handleExpandKey(element);
|
|
3193
|
+
else if (action === "collapse") this.handleCollapseKey(element);
|
|
3194
|
+
});
|
|
3195
|
+
}
|
|
3196
|
+
decorateItems() {
|
|
3197
|
+
ensureItemRoles(this.container, this.items, "tree", "treeitem", this.manageAria);
|
|
3198
|
+
this.on("refresh", () => {
|
|
3199
|
+
ensureItemRoles(this.container, this.items, "tree", "treeitem", this.manageAria);
|
|
3200
|
+
});
|
|
3201
|
+
}
|
|
3202
|
+
isExpandable(element) {
|
|
3203
|
+
if (element.hasAttribute("aria-expanded")) return true;
|
|
3204
|
+
if (element.hasAttribute("data-expandable")) return true;
|
|
3205
|
+
return element.querySelector(this.groupSelector) !== null;
|
|
3206
|
+
}
|
|
3207
|
+
isExpanded(element) {
|
|
3208
|
+
const value = element.getAttribute("aria-expanded");
|
|
3209
|
+
if (value !== null) return value === "true";
|
|
3210
|
+
const group = element.querySelector(this.groupSelector);
|
|
3211
|
+
if (!group) return false;
|
|
3212
|
+
if (this.hiddenClassValue) return !group.classList.contains(this.hiddenClassValue);
|
|
3213
|
+
return !group.hasAttribute("hidden");
|
|
3214
|
+
}
|
|
3215
|
+
expand(elementOrIndex) {
|
|
3216
|
+
const element = this.elementOf(elementOrIndex);
|
|
3217
|
+
if (!element || !this.isExpandable(element)) return;
|
|
3218
|
+
if (this.isExpanded(element)) return;
|
|
3219
|
+
this.setExpanded(element, true);
|
|
3220
|
+
}
|
|
3221
|
+
collapse(elementOrIndex) {
|
|
3222
|
+
const element = this.elementOf(elementOrIndex);
|
|
3223
|
+
if (!element || !this.isExpandable(element)) return;
|
|
3224
|
+
if (!this.isExpanded(element)) return;
|
|
3225
|
+
this.setExpanded(element, false);
|
|
3226
|
+
}
|
|
3227
|
+
toggleExpanded(elementOrIndex) {
|
|
3228
|
+
const element = this.elementOf(elementOrIndex);
|
|
3229
|
+
if (!element || !this.isExpandable(element)) return;
|
|
3230
|
+
this.setExpanded(element, !this.isExpanded(element));
|
|
3231
|
+
}
|
|
3232
|
+
expandAll() {
|
|
3233
|
+
const visited = /* @__PURE__ */ new Set();
|
|
3234
|
+
for (let pending = true; pending; ) {
|
|
3235
|
+
pending = false;
|
|
3236
|
+
for (const element of this.items) {
|
|
3237
|
+
if (visited.has(element)) continue;
|
|
3238
|
+
visited.add(element);
|
|
3239
|
+
if (!this.isExpandable(element) || this.isExpanded(element)) continue;
|
|
3240
|
+
this.setExpanded(element, true);
|
|
3241
|
+
pending = true;
|
|
3242
|
+
}
|
|
3243
|
+
}
|
|
3244
|
+
}
|
|
3245
|
+
collapseAll() {
|
|
3246
|
+
for (const element of this.items) if (this.isExpandable(element)) this.setExpanded(element, false);
|
|
3247
|
+
}
|
|
3248
|
+
level(element) {
|
|
3249
|
+
let depth = 0;
|
|
3250
|
+
let node = element.parentElement;
|
|
3251
|
+
while (node && node !== this.container) {
|
|
3252
|
+
if (this.matchesGroup(node)) depth++;
|
|
3253
|
+
node = node.parentElement;
|
|
3254
|
+
}
|
|
3255
|
+
return depth;
|
|
3256
|
+
}
|
|
3257
|
+
parent(element) {
|
|
3258
|
+
const items = this.items;
|
|
3259
|
+
let node = element.parentElement;
|
|
3260
|
+
while (node && node !== this.container) {
|
|
3261
|
+
if (items.indexOf(node) !== -1) return node;
|
|
3262
|
+
node = node.parentElement;
|
|
3263
|
+
}
|
|
3264
|
+
return null;
|
|
3265
|
+
}
|
|
3266
|
+
elementOf(elementOrIndex) {
|
|
3267
|
+
if (elementOrIndex === void 0) return this.activeElement;
|
|
3268
|
+
if (typeof elementOrIndex === "number") return this.items[elementOrIndex] ?? null;
|
|
3269
|
+
return elementOrIndex;
|
|
3270
|
+
}
|
|
3271
|
+
matchesGroup(node) {
|
|
3272
|
+
if (typeof node.matches === "function") {
|
|
3273
|
+
try {
|
|
3274
|
+
return node.matches(this.groupSelector);
|
|
3275
|
+
} catch {
|
|
3276
|
+
return false;
|
|
3277
|
+
}
|
|
3278
|
+
}
|
|
3279
|
+
return false;
|
|
3280
|
+
}
|
|
3281
|
+
setExpanded(element, expanded) {
|
|
3282
|
+
if (this.collapseBehavior !== "none") {
|
|
3283
|
+
setAria(element, "aria-expanded", expanded ? "true" : "false", this.manageAria);
|
|
3284
|
+
}
|
|
3285
|
+
const group = element.querySelector(this.groupSelector);
|
|
3286
|
+
if (group) {
|
|
3287
|
+
if (this.toggleHiddenValue) {
|
|
3288
|
+
if (group.hasAttribute("hidden") === expanded) group.toggleAttribute("hidden", !expanded);
|
|
3289
|
+
}
|
|
3290
|
+
if (this.hiddenClassValue) group.classList.toggle(this.hiddenClassValue, !expanded);
|
|
3291
|
+
}
|
|
3292
|
+
this.refresh();
|
|
3293
|
+
if (expanded) this.userOnExpand?.(element, this.items.indexOf(element));
|
|
3294
|
+
else this.userOnCollapse?.(element, this.items.indexOf(element));
|
|
3295
|
+
}
|
|
3296
|
+
handleExpandKey(element) {
|
|
3297
|
+
if (!this.isExpandable(element)) return;
|
|
3298
|
+
if (!this.isExpanded(element)) {
|
|
3299
|
+
this.setExpanded(element, true);
|
|
3300
|
+
return;
|
|
3301
|
+
}
|
|
3302
|
+
const index = this.items.indexOf(element);
|
|
3303
|
+
if (index !== -1 && index + 1 < this.items.length) this.focusIndex(index + 1);
|
|
3304
|
+
}
|
|
3305
|
+
handleCollapseKey(element) {
|
|
3306
|
+
if (this.isExpandable(element) && this.isExpanded(element)) {
|
|
3307
|
+
this.setExpanded(element, false);
|
|
3308
|
+
return;
|
|
3309
|
+
}
|
|
3310
|
+
const parent = this.parent(element);
|
|
3311
|
+
if (parent) this.focusElement(parent);
|
|
3312
|
+
}
|
|
3313
|
+
};
|
|
3314
|
+
function createTree(options = {}) {
|
|
3315
|
+
return new TreeController(options);
|
|
3316
|
+
}
|
|
3317
|
+
|
|
3318
|
+
// src/patterns/command-palette.ts
|
|
3319
|
+
var CommandPaletteController = class extends NavigationController {
|
|
3320
|
+
constructor(options) {
|
|
3321
|
+
const doc = typeof document !== "undefined" ? document : null;
|
|
3322
|
+
const containerElement = resolveElement(options.container, doc);
|
|
3323
|
+
const input = resolveElement(options.input, doc) ?? containerElement?.querySelector(
|
|
3324
|
+
"input:not([type='hidden']), textarea"
|
|
3325
|
+
) ?? null;
|
|
3326
|
+
super(
|
|
3327
|
+
{
|
|
3328
|
+
activeDescendant: input ?? true,
|
|
3329
|
+
...options
|
|
3330
|
+
},
|
|
3331
|
+
{
|
|
3332
|
+
mode: options.mode ?? "vertical",
|
|
3333
|
+
loop: options.loop ?? true,
|
|
3334
|
+
defaultKeys: COMMAND_PALETTE_KEYS,
|
|
3335
|
+
listenTarget: input ?? containerElement ?? doc
|
|
3336
|
+
}
|
|
3337
|
+
);
|
|
3338
|
+
this.extra = new EventManager();
|
|
3339
|
+
this.openValue = false;
|
|
3340
|
+
this.listId = null;
|
|
3341
|
+
this.listElement = null;
|
|
3342
|
+
this.inputElement = input;
|
|
3343
|
+
this.manageAria = options.aria !== false;
|
|
3344
|
+
this.toggleHiddenValue = options.toggleHidden !== false;
|
|
3345
|
+
this.userOnQuery = options.onQuery;
|
|
3346
|
+
this.userOnSubmit = options.onSubmit;
|
|
3347
|
+
this.userOnCancel = options.onCancel;
|
|
3348
|
+
if (options.openKeys && typeof document !== "undefined") {
|
|
3349
|
+
const bindings = Array.isArray(options.openKeys) ? options.openKeys : [options.openKeys];
|
|
3350
|
+
this.openMatcher = new KeyMatcher({ open: bindings });
|
|
3351
|
+
this.extra.add(document, "keydown", (rawEvent) => {
|
|
3352
|
+
const event = rawEvent;
|
|
3353
|
+
const match = this.openMatcher.match(normalizeKeyEvent(event), this);
|
|
3354
|
+
if (!match?.complete) return;
|
|
3355
|
+
event.preventDefault();
|
|
3356
|
+
this.toggle();
|
|
3357
|
+
});
|
|
3358
|
+
} else {
|
|
3359
|
+
this.openMatcher = null;
|
|
3360
|
+
}
|
|
3361
|
+
this.listElement = resolveElement(options.list, doc) ?? this.container;
|
|
3362
|
+
if (this.manageAria && this.listElement) {
|
|
3363
|
+
ensureRole(this.listElement, "listbox", true);
|
|
3364
|
+
this.listId = ensureId(this.listElement, "keystra-palette");
|
|
3365
|
+
}
|
|
3366
|
+
if (this.manageAria && this.inputElement) {
|
|
3367
|
+
ensureRole(this.inputElement, "combobox", true);
|
|
3368
|
+
setAria(this.inputElement, "aria-controls", this.listId, true);
|
|
3369
|
+
setAria(this.inputElement, "aria-expanded", "false", true);
|
|
3370
|
+
if (!this.inputElement.hasAttribute("aria-autocomplete")) {
|
|
3371
|
+
this.inputElement.setAttribute("aria-autocomplete", "list");
|
|
3372
|
+
}
|
|
3373
|
+
if (options.placeholder && !this.inputElement.hasAttribute("placeholder")) {
|
|
3374
|
+
this.inputElement.setAttribute("placeholder", options.placeholder);
|
|
3375
|
+
}
|
|
3376
|
+
}
|
|
3377
|
+
this.wireInput();
|
|
3378
|
+
this.decorateItems();
|
|
3379
|
+
this.on("refresh", () => this.decorateItems());
|
|
3380
|
+
this.on("select", ({ element }) => {
|
|
3381
|
+
this.userOnSubmit?.(element ?? null, this.query);
|
|
3382
|
+
});
|
|
3383
|
+
this.on("action", ({ action, event }) => {
|
|
3384
|
+
if (action !== "escape") return;
|
|
3385
|
+
event?.preventDefault();
|
|
3386
|
+
this.userOnCancel?.();
|
|
3387
|
+
this.close();
|
|
3388
|
+
});
|
|
3389
|
+
}
|
|
3390
|
+
get isOpen() {
|
|
3391
|
+
return this.openValue;
|
|
3392
|
+
}
|
|
3393
|
+
get query() {
|
|
3394
|
+
return this.inputElement?.value ?? "";
|
|
3395
|
+
}
|
|
3396
|
+
get input() {
|
|
3397
|
+
return this.inputElement;
|
|
3398
|
+
}
|
|
3399
|
+
open() {
|
|
3400
|
+
if (this.openValue) return;
|
|
3401
|
+
this.openValue = true;
|
|
3402
|
+
this.enable();
|
|
3403
|
+
this.rememberFocus();
|
|
3404
|
+
if (this.container && this.toggleHiddenValue && this.container.hasAttribute("hidden")) {
|
|
3405
|
+
this.container.removeAttribute("hidden");
|
|
3406
|
+
}
|
|
3407
|
+
if (this.inputElement) {
|
|
3408
|
+
focusElement(this.inputElement);
|
|
3409
|
+
if (typeof this.inputElement.select === "function") this.inputElement.select();
|
|
3410
|
+
}
|
|
3411
|
+
this.refresh();
|
|
3412
|
+
this.setActiveIndex(0, { focus: false });
|
|
3413
|
+
setAria(this.inputElement, "aria-expanded", "true", this.manageAria);
|
|
3414
|
+
}
|
|
3415
|
+
close(options = {}) {
|
|
3416
|
+
if (!this.openValue) return;
|
|
3417
|
+
this.openValue = false;
|
|
3418
|
+
this.disable();
|
|
3419
|
+
if (this.container && this.toggleHiddenValue && !this.container.hasAttribute("hidden")) {
|
|
3420
|
+
this.container.setAttribute("hidden", "");
|
|
3421
|
+
}
|
|
3422
|
+
setAria(this.inputElement, "aria-expanded", "false", this.manageAria);
|
|
3423
|
+
if (options.restoreFocus !== false) this.restoreFocus();
|
|
3424
|
+
}
|
|
3425
|
+
toggle() {
|
|
3426
|
+
if (this.openValue) this.close();
|
|
3427
|
+
else this.open();
|
|
3428
|
+
}
|
|
3429
|
+
setQuery(query) {
|
|
3430
|
+
if (!this.inputElement) return;
|
|
3431
|
+
if (this.inputElement.value === query) return;
|
|
3432
|
+
this.inputElement.value = query;
|
|
3433
|
+
this.userOnQuery?.(query);
|
|
3434
|
+
}
|
|
3435
|
+
destroy() {
|
|
3436
|
+
this.extra.dispose();
|
|
3437
|
+
this.openMatcher?.dispose();
|
|
3438
|
+
if (this.inputElement && this.manageAria) {
|
|
3439
|
+
this.inputElement.removeAttribute("aria-expanded");
|
|
3440
|
+
if (this.listId) this.inputElement.removeAttribute("aria-controls");
|
|
3441
|
+
}
|
|
3442
|
+
super.destroy();
|
|
3443
|
+
}
|
|
3444
|
+
ownsAction(action) {
|
|
3445
|
+
return action === "select" || action === "escape";
|
|
3446
|
+
}
|
|
3447
|
+
runAction(action, event, match) {
|
|
3448
|
+
if (super.runAction(action, event, match)) return true;
|
|
3449
|
+
if (action !== "select") return false;
|
|
3450
|
+
this.userOnSubmit?.(null, this.query);
|
|
3451
|
+
return true;
|
|
3452
|
+
}
|
|
3453
|
+
decorateItems() {
|
|
3454
|
+
ensureItemRoles(this.listElement, this.items, "listbox", "option", this.manageAria);
|
|
3455
|
+
}
|
|
3456
|
+
wireInput() {
|
|
3457
|
+
const input = this.inputElement;
|
|
3458
|
+
if (!input) return;
|
|
3459
|
+
this.extra.add(input, "input", () => {
|
|
3460
|
+
this.userOnQuery?.(input.value);
|
|
3461
|
+
this.refresh();
|
|
3462
|
+
this.setActiveIndex(0, { focus: false });
|
|
3463
|
+
});
|
|
3464
|
+
}
|
|
3465
|
+
};
|
|
3466
|
+
function createCommandPalette(options = {}) {
|
|
3467
|
+
return new CommandPaletteController(options);
|
|
3468
|
+
}
|
|
3469
|
+
|
|
3470
|
+
// src/patterns/roving-focus.ts
|
|
3471
|
+
function createRovingFocus(options = {}) {
|
|
3472
|
+
const instance = createKeystra({
|
|
3473
|
+
keys: options.mode === void 0 ? ROVING_KEYS : void 0,
|
|
3474
|
+
loop: true,
|
|
3475
|
+
roving: true,
|
|
3476
|
+
...options
|
|
3477
|
+
});
|
|
3478
|
+
const items = instance.items;
|
|
3479
|
+
if (items.length === 0) return instance;
|
|
3480
|
+
let initial = options.initialIndex ?? -1;
|
|
3481
|
+
if (initial < 0) {
|
|
3482
|
+
for (let i = 0; i < items.length; i++) {
|
|
3483
|
+
if (items[i].getAttribute("tabindex") === "0") {
|
|
3484
|
+
initial = i;
|
|
3485
|
+
break;
|
|
3486
|
+
}
|
|
3487
|
+
}
|
|
3488
|
+
}
|
|
3489
|
+
if (initial > 0) instance.setActiveIndex(initial, { focus: false });
|
|
3490
|
+
return instance;
|
|
3491
|
+
}
|
|
3492
|
+
|
|
3493
|
+
exports.DropdownController = DropdownController;
|
|
3494
|
+
exports.MenuController = MenuController;
|
|
3495
|
+
exports.createCommandPalette = createCommandPalette;
|
|
3496
|
+
exports.createDropdown = createDropdown;
|
|
3497
|
+
exports.createGrid = createGrid;
|
|
3498
|
+
exports.createList = createList;
|
|
3499
|
+
exports.createMenu = createMenu;
|
|
3500
|
+
exports.createRovingFocus = createRovingFocus;
|
|
3501
|
+
exports.createTabs = createTabs;
|
|
3502
|
+
exports.createTree = createTree;
|
|
3503
|
+
exports.ensureId = ensureId;
|
|
3504
|
+
exports.ensureRole = ensureRole;
|
|
3505
|
+
exports.setAria = setAria;
|