@sit-onyx/headless 1.0.0-beta.21 → 1.0.0-beta.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/dist/composables/comboBox/SelectOnlyCombobox.d.vue.ts +299 -0
  2. package/dist/composables/comboBox/TestCombobox.ct.d.ts +1 -0
  3. package/dist/composables/comboBox/TestCombobox.d.vue.ts +299 -0
  4. package/dist/composables/comboBox/createComboBox.d.ts +370 -0
  5. package/dist/composables/comboBox/createComboBox.testing.d.ts +10 -0
  6. package/dist/composables/helpers/useDismissible.d.ts +10 -0
  7. package/dist/composables/helpers/useGlobalListener.d.ts +10 -0
  8. package/dist/composables/helpers/useGlobalListener.spec.d.ts +1 -0
  9. package/dist/composables/helpers/useOutsideClick.d.ts +26 -0
  10. package/dist/composables/helpers/useOutsideClick.spec.d.ts +1 -0
  11. package/dist/composables/helpers/useTypeAhead.d.ts +11 -0
  12. package/dist/composables/helpers/useTypeAhead.spec.d.ts +1 -0
  13. package/dist/composables/listbox/TestListbox.ct.d.ts +1 -0
  14. package/dist/composables/listbox/TestListbox.d.vue.ts +2 -0
  15. package/dist/composables/listbox/createListbox.d.ts +102 -0
  16. package/dist/composables/listbox/createListbox.testing.d.ts +24 -0
  17. package/dist/composables/menuButton/TestMenuButton.ct.d.ts +1 -0
  18. package/dist/composables/menuButton/TestMenuButton.d.vue.ts +2 -0
  19. package/dist/composables/menuButton/createMenuButton.d.ts +78 -0
  20. package/dist/composables/menuButton/createMenuButton.testing.d.ts +24 -0
  21. package/dist/composables/navigationMenu/TestMenu.ct.d.ts +1 -0
  22. package/dist/composables/navigationMenu/TestMenu.d.vue.ts +2 -0
  23. package/dist/composables/navigationMenu/createMenu.d.ts +21 -0
  24. package/dist/composables/navigationMenu/createMenu.testing.d.ts +16 -0
  25. package/dist/composables/tabs/TestTabs.ct.d.ts +1 -0
  26. package/dist/composables/tabs/TestTabs.d.vue.ts +2 -0
  27. package/dist/composables/tabs/createTabs.d.ts +48 -0
  28. package/dist/composables/tabs/createTabs.testing.d.ts +13 -0
  29. package/dist/composables/tooltip/createToggletip.d.ts +36 -0
  30. package/dist/composables/tooltip/createTooltip.d.ts +42 -0
  31. package/dist/index.d.ts +11 -0
  32. package/dist/index.js +1088 -0
  33. package/dist/playwright.d.ts +5 -0
  34. package/dist/playwright.js +369 -0
  35. package/dist/utils/builder.d.ts +85 -0
  36. package/dist/utils/keyboard.d.ts +26 -0
  37. package/dist/utils/keyboard.spec.d.ts +1 -0
  38. package/dist/utils/math.d.ts +6 -0
  39. package/dist/utils/math.spec.d.ts +1 -0
  40. package/dist/utils/object.d.ts +5 -0
  41. package/dist/utils/object.spec.d.ts +1 -0
  42. package/dist/utils/timer.d.ts +10 -0
  43. package/{src/utils/types.ts → dist/utils/types.d.ts} +4 -12
  44. package/dist/utils/vitest.d.ts +12 -0
  45. package/package.json +16 -7
  46. package/src/composables/comboBox/SelectOnlyCombobox.vue +0 -90
  47. package/src/composables/comboBox/TestCombobox.ct.tsx +0 -24
  48. package/src/composables/comboBox/TestCombobox.vue +0 -84
  49. package/src/composables/comboBox/createComboBox.testing.ts +0 -168
  50. package/src/composables/comboBox/createComboBox.ts +0 -280
  51. package/src/composables/helpers/useDismissible.ts +0 -19
  52. package/src/composables/helpers/useGlobalListener.spec.ts +0 -93
  53. package/src/composables/helpers/useGlobalListener.ts +0 -64
  54. package/src/composables/helpers/useOutsideClick.spec.ts +0 -117
  55. package/src/composables/helpers/useOutsideClick.ts +0 -69
  56. package/src/composables/helpers/useTypeAhead.spec.ts +0 -29
  57. package/src/composables/helpers/useTypeAhead.ts +0 -26
  58. package/src/composables/listbox/TestListbox.ct.tsx +0 -17
  59. package/src/composables/listbox/TestListbox.vue +0 -92
  60. package/src/composables/listbox/createListbox.testing.ts +0 -141
  61. package/src/composables/listbox/createListbox.ts +0 -234
  62. package/src/composables/menuButton/TestMenuButton.ct.tsx +0 -14
  63. package/src/composables/menuButton/TestMenuButton.vue +0 -29
  64. package/src/composables/menuButton/createMenuButton.testing.ts +0 -91
  65. package/src/composables/menuButton/createMenuButton.ts +0 -206
  66. package/src/composables/navigationMenu/TestMenu.ct.tsx +0 -12
  67. package/src/composables/navigationMenu/TestMenu.vue +0 -16
  68. package/src/composables/navigationMenu/createMenu.testing.ts +0 -37
  69. package/src/composables/navigationMenu/createMenu.ts +0 -55
  70. package/src/composables/tabs/TestTabs.ct.tsx +0 -12
  71. package/src/composables/tabs/TestTabs.vue +0 -28
  72. package/src/composables/tabs/createTabs.testing.ts +0 -151
  73. package/src/composables/tabs/createTabs.ts +0 -129
  74. package/src/composables/tooltip/createToggletip.ts +0 -58
  75. package/src/composables/tooltip/createTooltip.ts +0 -71
  76. package/src/index.ts +0 -11
  77. package/src/playwright.ts +0 -5
  78. package/src/utils/builder.ts +0 -135
  79. package/src/utils/keyboard.spec.ts +0 -53
  80. package/src/utils/keyboard.ts +0 -351
  81. package/src/utils/math.spec.ts +0 -14
  82. package/src/utils/math.ts +0 -6
  83. package/src/utils/object.spec.ts +0 -33
  84. package/src/utils/object.ts +0 -8
  85. package/src/utils/timer.ts +0 -22
  86. package/src/utils/vitest.ts +0 -36
package/dist/index.js ADDED
@@ -0,0 +1,1088 @@
1
+ import { shallowRef, computed, reactive, onBeforeMount, watchEffect, onBeforeUnmount, toValue, unref, ref, nextTick, useId, watch, toRef } from "vue";
2
+ const createBuilder = (builder) => builder;
3
+ function createElRef() {
4
+ const elementRef = shallowRef();
5
+ return computed({
6
+ set: (element) => {
7
+ elementRef.value = element != null && "$el" in element ? element.$el : element;
8
+ },
9
+ get: () => elementRef.value
10
+ });
11
+ }
12
+ const isSubsetMatching = (subset, target) => Object.entries(subset).every(
13
+ ([key, value]) => target[key] === value
14
+ );
15
+ const wasKeyPressed = (event, key) => {
16
+ if (typeof key === "string") {
17
+ return event.key === key;
18
+ }
19
+ return isSubsetMatching(
20
+ { altKey: false, ctrlKey: false, metaKey: false, shiftKey: false, ...key },
21
+ event
22
+ );
23
+ };
24
+ const isPrintableCharacter = (key) => !NAMED_KEYS_SET.has(key);
25
+ const NAMED_KEYS = [
26
+ "Unidentified",
27
+ "Alt",
28
+ "AltGraph",
29
+ "CapsLock",
30
+ "Control",
31
+ "Fn",
32
+ "FnLock",
33
+ "Meta",
34
+ "NumLock",
35
+ "ScrollLock",
36
+ "Shift",
37
+ "Symbol",
38
+ "SymbolLock",
39
+ "Hyper",
40
+ "Super",
41
+ "Enter",
42
+ "Tab",
43
+ "ArrowDown",
44
+ "ArrowLeft",
45
+ "ArrowRight",
46
+ "ArrowUp",
47
+ "End",
48
+ "Home",
49
+ "PageDown",
50
+ "PageUp",
51
+ "Backspace",
52
+ "Clear",
53
+ "Copy",
54
+ "CrSel",
55
+ "Cut",
56
+ "Delete",
57
+ "EraseEof",
58
+ "ExSel",
59
+ "Insert",
60
+ "Paste",
61
+ "Redo",
62
+ "Undo",
63
+ "Accept",
64
+ "Again",
65
+ "Attn",
66
+ "Cancel",
67
+ "ContextMenu",
68
+ "Escape",
69
+ "Execute",
70
+ "Find",
71
+ "Help",
72
+ "Pause",
73
+ "Play",
74
+ "Props",
75
+ "Select",
76
+ "ZoomIn",
77
+ "ZoomOut",
78
+ "BrightnessDown",
79
+ "BrightnessUp",
80
+ "Eject",
81
+ "LogOff",
82
+ "Power",
83
+ "PowerOff",
84
+ "PrintScreen",
85
+ "Hibernate",
86
+ "Standby",
87
+ "WakeUp",
88
+ "AllCandidates",
89
+ "Alphanumeric",
90
+ "CodeInput",
91
+ "Compose",
92
+ "Convert",
93
+ "Dead",
94
+ "FinalMode",
95
+ "GroupFirst",
96
+ "GroupLast",
97
+ "GroupNext",
98
+ "GroupPrevious",
99
+ "ModeChange",
100
+ "NextCandidate",
101
+ "NonConvert",
102
+ "PreviousCandidate",
103
+ "Process",
104
+ "SingleCandidate",
105
+ "HangulMode",
106
+ "HanjaMode",
107
+ "JunjaMode",
108
+ "Eisu",
109
+ "Hankaku",
110
+ "Hiragana",
111
+ "HiraganaKatakana",
112
+ "KanaMode",
113
+ "KanjiMode",
114
+ "Katakana",
115
+ "Romaji",
116
+ "Zenkaku",
117
+ "ZenkakuHankaku",
118
+ "F1",
119
+ "F2",
120
+ "F3",
121
+ "F4",
122
+ "F5",
123
+ "F6",
124
+ "F7",
125
+ "F8",
126
+ "F9",
127
+ "F10",
128
+ "F11",
129
+ "F12",
130
+ "Soft1",
131
+ "Soft2",
132
+ "Soft3",
133
+ "Soft4",
134
+ "ChannelDown",
135
+ "ChannelUp",
136
+ "Close",
137
+ "MailForward",
138
+ "MailReply",
139
+ "MailSend",
140
+ "MediaClose",
141
+ "MediaFastForward",
142
+ "MediaPause",
143
+ "MediaPlay",
144
+ "MediaPlayPause",
145
+ "MediaRecord",
146
+ "MediaRewind",
147
+ "MediaStop",
148
+ "MediaTrackNext",
149
+ "MediaTrackPrevious",
150
+ "New",
151
+ "Open",
152
+ "Print",
153
+ "Save",
154
+ "SpellCheck",
155
+ "Key11",
156
+ "Key12",
157
+ "AudioBalanceLeft",
158
+ "AudioBalanceRight",
159
+ "AudioBassBoostDown",
160
+ "AudioBassBoostToggle",
161
+ "AudioBassBoostUp",
162
+ "AudioFaderFront",
163
+ "AudioFaderRear",
164
+ "AudioSurroundModeNext",
165
+ "AudioTrebleDown",
166
+ "AudioTrebleUp",
167
+ "AudioVolumeDown",
168
+ "AudioVolumeUp",
169
+ "AudioVolumeMute",
170
+ "MicrophoneToggle",
171
+ "MicrophoneVolumeDown",
172
+ "MicrophoneVolumeUp",
173
+ "MicrophoneVolumeMute",
174
+ "SpeechCorrectionList",
175
+ "SpeechInputToggle",
176
+ "LaunchApplication1",
177
+ "LaunchApplication2",
178
+ "LaunchCalendar",
179
+ "LaunchContacts",
180
+ "LaunchMail",
181
+ "LaunchMediaPlayer",
182
+ "LaunchMusicPlayer",
183
+ "LaunchPhone",
184
+ "LaunchScreenSaver",
185
+ "LaunchSpreadsheet",
186
+ "LaunchWebBrowser",
187
+ "LaunchWebCam",
188
+ "LaunchWordProcessor",
189
+ "BrowserBack",
190
+ "BrowserFavorites",
191
+ "BrowserForward",
192
+ "BrowserHome",
193
+ "BrowserRefresh",
194
+ "BrowserSearch",
195
+ "BrowserStop",
196
+ "AppSwitch",
197
+ "Call",
198
+ "Camera",
199
+ "CameraFocus",
200
+ "EndCall",
201
+ "GoBack",
202
+ "GoHome",
203
+ "HeadsetHook",
204
+ "LastNumberRedial",
205
+ "Notification",
206
+ "MannerMode",
207
+ "VoiceDial",
208
+ "TV",
209
+ "TV3DMode",
210
+ "TVAntennaCable",
211
+ "TVAudioDescription",
212
+ "TVAudioDescriptionMixDown",
213
+ "TVAudioDescriptionMixUp",
214
+ "TVContentsMenu",
215
+ "TVDataService",
216
+ "TVInput",
217
+ "TVInputComponent1",
218
+ "TVInputComponent2",
219
+ "TVInputComposite1",
220
+ "TVInputComposite2",
221
+ "TVInputHDMI1",
222
+ "TVInputHDMI2",
223
+ "TVInputHDMI3",
224
+ "TVInputHDMI4",
225
+ "TVInputVGA1",
226
+ "TVMediaContext",
227
+ "TVNetwork",
228
+ "TVNumberEntry",
229
+ "TVPower",
230
+ "TVRadioService",
231
+ "TVSatellite",
232
+ "TVSatelliteBS",
233
+ "TVSatelliteCS",
234
+ "TVSatelliteToggle",
235
+ "TVTerrestrialAnalog",
236
+ "TVTerrestrialDigital",
237
+ "TVTimer",
238
+ "AVRInput",
239
+ "AVRPower",
240
+ "ColorF0Red",
241
+ "ColorF1Green",
242
+ "ColorF2Yellow",
243
+ "ColorF3Blue",
244
+ "ColorF4Grey",
245
+ "ColorF5Brown",
246
+ "ClosedCaptionToggle",
247
+ "Dimmer",
248
+ "DisplaySwap",
249
+ "DVR",
250
+ "Exit",
251
+ "FavoriteClear0",
252
+ "FavoriteClear1",
253
+ "FavoriteClear2",
254
+ "FavoriteClear3",
255
+ "FavoriteRecall0",
256
+ "FavoriteRecall1",
257
+ "FavoriteRecall2",
258
+ "FavoriteRecall3",
259
+ "FavoriteStore0",
260
+ "FavoriteStore1",
261
+ "FavoriteStore2",
262
+ "FavoriteStore3",
263
+ "Guide",
264
+ "GuideNextDay",
265
+ "GuidePreviousDay",
266
+ "Info",
267
+ "InstantReplay",
268
+ "Link",
269
+ "ListProgram",
270
+ "LiveContent",
271
+ "Lock",
272
+ "MediaApps",
273
+ "MediaAudioTrack",
274
+ "MediaLast",
275
+ "MediaSkipBackward",
276
+ "MediaSkipForward",
277
+ "MediaStepBackward",
278
+ "MediaStepForward",
279
+ "MediaTopMenu",
280
+ "NavigateIn",
281
+ "NavigateNext",
282
+ "NavigateOut",
283
+ "NavigatePrevious",
284
+ "NextFavoriteChannel",
285
+ "NextUserProfile",
286
+ "OnDemand",
287
+ "Pairing",
288
+ "PinPDown",
289
+ "PinPMove",
290
+ "PinPToggle",
291
+ "PinPUp",
292
+ "PlaySpeedDown",
293
+ "PlaySpeedReset",
294
+ "PlaySpeedUp",
295
+ "RandomToggle",
296
+ "RcLowBattery",
297
+ "RecordSpeedNext",
298
+ "RfBypass",
299
+ "ScanChannelsToggle",
300
+ "ScreenModeNext",
301
+ "Settings",
302
+ "SplitScreenToggle",
303
+ "STBInput",
304
+ "STBPower",
305
+ "Subtitle",
306
+ "Teletext",
307
+ "VideoModeNext",
308
+ "Wink",
309
+ "ZoomToggle",
310
+ "AudioVolumeDown",
311
+ "AudioVolumeUp",
312
+ "AudioVolumeMute",
313
+ "BrowserBack",
314
+ "BrowserForward",
315
+ "ChannelDown",
316
+ "ChannelUp",
317
+ "ContextMenu",
318
+ "Eject",
319
+ "End",
320
+ "Enter",
321
+ "Home",
322
+ "MediaFastForward",
323
+ "MediaPlay",
324
+ "MediaPlayPause",
325
+ "MediaRecord",
326
+ "MediaRewind",
327
+ "MediaStop",
328
+ "MediaNextTrack",
329
+ "MediaPause",
330
+ "MediaPreviousTrack",
331
+ "Power",
332
+ "Unidentified"
333
+ ];
334
+ const NAMED_KEYS_SET = new Set(NAMED_KEYS);
335
+ const GLOBAL_LISTENERS = reactive(/* @__PURE__ */ new Map());
336
+ const updateRemainingListeners = (type, remaining) => {
337
+ if (remaining?.size) {
338
+ GLOBAL_LISTENERS.set(type, remaining);
339
+ return;
340
+ }
341
+ GLOBAL_LISTENERS.delete(type);
342
+ document.removeEventListener(type, GLOBAL_HANDLER);
343
+ };
344
+ const removeGlobalListener = (type, listener) => {
345
+ const globalListener = GLOBAL_LISTENERS.get(type);
346
+ globalListener?.delete(listener);
347
+ updateRemainingListeners(type, globalListener);
348
+ };
349
+ const addGlobalListener = (type, listener) => {
350
+ const globalListener = GLOBAL_LISTENERS.get(type) ?? /* @__PURE__ */ new Set();
351
+ globalListener.add(listener);
352
+ GLOBAL_LISTENERS.set(type, globalListener);
353
+ document.addEventListener(type, GLOBAL_HANDLER);
354
+ };
355
+ const GLOBAL_HANDLER = (event) => {
356
+ const type = event.type;
357
+ GLOBAL_LISTENERS.get(type)?.forEach((cb) => cb(event));
358
+ };
359
+ const useGlobalEventListener = ({
360
+ type,
361
+ listener,
362
+ disabled
363
+ }) => {
364
+ onBeforeMount(
365
+ () => watchEffect(
366
+ () => disabled?.value ? removeGlobalListener(type, listener) : addGlobalListener(type, listener)
367
+ )
368
+ );
369
+ onBeforeUnmount(() => removeGlobalListener(type, listener));
370
+ };
371
+ const useOutsideClick = ({
372
+ inside,
373
+ onOutsideClick,
374
+ disabled,
375
+ checkOnTab
376
+ }) => {
377
+ const isOutsideClick = (target) => {
378
+ if (!target) return true;
379
+ const raw = toValue(inside);
380
+ const elements = Array.isArray(raw) ? raw : [raw];
381
+ return !elements.some((element) => element?.contains(target));
382
+ };
383
+ const clickListener = (event) => {
384
+ if (isOutsideClick(event.target)) onOutsideClick(event);
385
+ };
386
+ useGlobalEventListener({ type: "mousedown", listener: clickListener, disabled });
387
+ if (checkOnTab) {
388
+ const keydownListener = async (event) => {
389
+ if (event.key !== "Tab") return;
390
+ await new Promise((resolve) => setTimeout(resolve));
391
+ if (isOutsideClick(document.activeElement)) {
392
+ onOutsideClick(event);
393
+ }
394
+ };
395
+ useGlobalEventListener({ type: "keydown", listener: keydownListener, disabled });
396
+ }
397
+ };
398
+ const debounce = (handler, timeout) => {
399
+ let timer;
400
+ const func = (...lastArgs) => {
401
+ clearTimeout(timer);
402
+ timer = setTimeout(() => handler(...lastArgs), toValue(timeout));
403
+ };
404
+ func.abort = () => clearTimeout(timer);
405
+ return func;
406
+ };
407
+ const useTypeAhead = (callback, timeout = 500) => {
408
+ let inputString = "";
409
+ const debouncedReset = debounce(() => inputString = "", timeout);
410
+ return (event) => {
411
+ if (!isPrintableCharacter(event.key)) {
412
+ return;
413
+ }
414
+ debouncedReset();
415
+ inputString = `${inputString}${event.key}`;
416
+ callback(inputString);
417
+ };
418
+ };
419
+ const createListbox = createBuilder(
420
+ (options) => {
421
+ const isMultiselect = computed(() => unref(options.multiple) ?? false);
422
+ const isExpanded = computed(() => unref(options.isExpanded) ?? false);
423
+ const descendantKeyIdMap = /* @__PURE__ */ new Map();
424
+ const getOptionId = (value) => {
425
+ if (!descendantKeyIdMap.has(value)) {
426
+ descendantKeyIdMap.set(value, useId());
427
+ }
428
+ return descendantKeyIdMap.get(value);
429
+ };
430
+ const isFocused = ref(false);
431
+ watchEffect(async () => {
432
+ if (!isExpanded.value || options.activeOption.value == void 0 || !isFocused.value && !options.controlled) {
433
+ return;
434
+ }
435
+ const id = getOptionId(options.activeOption.value);
436
+ await nextTick();
437
+ document.getElementById(id)?.scrollIntoView({ block: "nearest", inline: "nearest" });
438
+ });
439
+ const typeAhead = useTypeAhead((inputString) => options.onTypeAhead?.(inputString));
440
+ const handleKeydown = (event) => {
441
+ switch (event.key) {
442
+ case " ":
443
+ event.preventDefault();
444
+ if (options.activeOption.value != void 0) {
445
+ options.onSelect?.(options.activeOption.value);
446
+ }
447
+ break;
448
+ case "ArrowUp":
449
+ event.preventDefault();
450
+ if (options.activeOption.value == void 0) {
451
+ options.onActivateLast?.();
452
+ return;
453
+ }
454
+ options.onActivatePrevious?.(options.activeOption.value);
455
+ break;
456
+ case "ArrowDown":
457
+ event.preventDefault();
458
+ if (options.activeOption.value == void 0) {
459
+ options.onActivateFirst?.();
460
+ return;
461
+ }
462
+ options.onActivateNext?.(options.activeOption.value);
463
+ break;
464
+ case "Home":
465
+ event.preventDefault();
466
+ options.onActivateFirst?.();
467
+ break;
468
+ case "End":
469
+ event.preventDefault();
470
+ options.onActivateLast?.();
471
+ break;
472
+ default:
473
+ typeAhead(event);
474
+ }
475
+ };
476
+ const listbox = computed(
477
+ () => options.controlled ? {
478
+ role: "listbox",
479
+ "aria-multiselectable": isMultiselect.value,
480
+ "aria-label": unref(options.label),
481
+ "aria-description": options.description,
482
+ tabindex: "-1"
483
+ } : {
484
+ role: "listbox",
485
+ "aria-multiselectable": isMultiselect.value,
486
+ "aria-label": unref(options.label),
487
+ "aria-description": options.description,
488
+ tabindex: "0",
489
+ "aria-activedescendant": options.activeOption.value != void 0 ? getOptionId(options.activeOption.value) : void 0,
490
+ onFocus: () => isFocused.value = true,
491
+ onBlur: () => isFocused.value = false,
492
+ onKeydown: handleKeydown
493
+ }
494
+ );
495
+ return {
496
+ elements: {
497
+ listbox,
498
+ group: computed(() => {
499
+ return (options2) => ({
500
+ role: "group",
501
+ "aria-label": options2.label
502
+ });
503
+ }),
504
+ option: computed(() => {
505
+ return (data) => {
506
+ const selected = data.selected ?? false;
507
+ return {
508
+ id: getOptionId(data.value),
509
+ role: "option",
510
+ "aria-label": data.label,
511
+ "aria-disabled": data.disabled,
512
+ "aria-checked": isMultiselect.value ? selected : void 0,
513
+ "aria-selected": !isMultiselect.value ? selected : void 0,
514
+ onClick: () => !data.disabled && options.onSelect?.(data.value)
515
+ };
516
+ };
517
+ })
518
+ },
519
+ state: {
520
+ isFocused
521
+ },
522
+ internals: {
523
+ getOptionId
524
+ }
525
+ };
526
+ }
527
+ );
528
+ const OPENING_KEYS = ["ArrowDown", "ArrowUp", " ", "Enter", "Home", "End"];
529
+ const CLOSING_KEYS = [
530
+ "Escape",
531
+ { key: "ArrowUp", altKey: true },
532
+ "Enter",
533
+ "Tab"
534
+ ];
535
+ const SELECTING_KEYS = ["Enter"];
536
+ const isSelectingKey = (event, withSpace) => {
537
+ const selectingKeys = withSpace ? [...SELECTING_KEYS, " "] : SELECTING_KEYS;
538
+ return isKeyOfGroup(event, selectingKeys);
539
+ };
540
+ const isKeyOfGroup = (event, group) => group.some((key) => wasKeyPressed(event, key));
541
+ const createComboBox = createBuilder(
542
+ ({
543
+ autocomplete: autocompleteRef,
544
+ onAutocomplete,
545
+ onTypeAhead,
546
+ multiple: multipleRef,
547
+ label,
548
+ listLabel,
549
+ listDescription,
550
+ isExpanded: isExpandedRef,
551
+ activeOption,
552
+ onToggle,
553
+ onSelect,
554
+ onActivateFirst,
555
+ onActivateLast,
556
+ onActivateNext,
557
+ onActivatePrevious,
558
+ templateRef
559
+ }) => {
560
+ const controlsId = useId();
561
+ const autocomplete = computed(() => unref(autocompleteRef));
562
+ const isExpanded = computed(() => unref(isExpandedRef));
563
+ const multiple = computed(() => unref(multipleRef));
564
+ const handleInput = (event) => {
565
+ const inputElement = event.target;
566
+ if (autocomplete.value !== "none") {
567
+ onAutocomplete?.(inputElement.value);
568
+ }
569
+ };
570
+ const typeAhead = useTypeAhead((inputString) => onTypeAhead?.(inputString));
571
+ const handleSelect = (value) => {
572
+ onSelect?.(value);
573
+ if (!unref(multiple)) {
574
+ onToggle?.();
575
+ }
576
+ };
577
+ const handleNavigation = (event) => {
578
+ switch (event.key) {
579
+ case "ArrowUp":
580
+ event.preventDefault();
581
+ if (activeOption.value == void 0) {
582
+ return onActivateLast?.();
583
+ }
584
+ onActivatePrevious?.(activeOption.value);
585
+ break;
586
+ case "ArrowDown":
587
+ event.preventDefault();
588
+ if (activeOption.value == void 0) {
589
+ return onActivateFirst?.();
590
+ }
591
+ onActivateNext?.(activeOption.value);
592
+ break;
593
+ case "Home":
594
+ event.preventDefault();
595
+ onActivateFirst?.();
596
+ break;
597
+ case "End":
598
+ event.preventDefault();
599
+ onActivateLast?.();
600
+ break;
601
+ }
602
+ };
603
+ const handleKeydown = (event) => {
604
+ if (event.key === "Enter") {
605
+ event.preventDefault();
606
+ }
607
+ if (!isExpanded.value && isKeyOfGroup(event, OPENING_KEYS)) {
608
+ onToggle?.();
609
+ if (event.key === " ") {
610
+ event.preventDefault();
611
+ }
612
+ if (event.key === "End") {
613
+ return onActivateLast?.();
614
+ }
615
+ return onActivateFirst?.();
616
+ }
617
+ if (isSelectingKey(event, autocomplete.value === "none")) {
618
+ return handleSelect(activeOption.value);
619
+ }
620
+ if (isExpanded.value && isKeyOfGroup(event, CLOSING_KEYS)) {
621
+ return onToggle?.();
622
+ }
623
+ if (autocomplete.value === "none" && isPrintableCharacter(event.key)) {
624
+ !isExpanded.value && onToggle?.();
625
+ return typeAhead(event);
626
+ }
627
+ if (autocomplete.value !== "none" && isPrintableCharacter(event.key)) {
628
+ !isExpanded.value && onToggle?.();
629
+ return;
630
+ }
631
+ return handleNavigation(event);
632
+ };
633
+ const autocompleteInput = computed(() => {
634
+ if (autocomplete.value === "none") return null;
635
+ return {
636
+ "aria-autocomplete": autocomplete.value,
637
+ type: "text"
638
+ };
639
+ });
640
+ const {
641
+ elements: { option, group, listbox },
642
+ internals: { getOptionId }
643
+ } = createListbox({
644
+ label: listLabel,
645
+ description: listDescription,
646
+ multiple,
647
+ controlled: true,
648
+ activeOption,
649
+ isExpanded,
650
+ onSelect: handleSelect
651
+ });
652
+ useOutsideClick({
653
+ inside: templateRef,
654
+ onOutsideClick() {
655
+ if (!isExpanded.value) return;
656
+ onToggle?.(true);
657
+ }
658
+ });
659
+ return {
660
+ elements: {
661
+ option,
662
+ group,
663
+ /**
664
+ * The listbox associated with the combobox.
665
+ */
666
+ listbox: computed(() => ({
667
+ ...listbox.value,
668
+ id: controlsId,
669
+ // preventDefault to not lose focus of the combobox
670
+ onMousedown: (e) => e.preventDefault()
671
+ })),
672
+ /**
673
+ * An input that controls another element, that can dynamically pop-up to help the user set the value of the input.
674
+ * The input MAY be either a single-line text field that supports editing and typing or an element that only displays the current value of the combobox.
675
+ */
676
+ input: computed(() => ({
677
+ role: "combobox",
678
+ "aria-expanded": isExpanded.value,
679
+ "aria-controls": controlsId,
680
+ "aria-label": unref(label),
681
+ "aria-activedescendant": activeOption.value != void 0 ? getOptionId(activeOption.value) : void 0,
682
+ onInput: handleInput,
683
+ onKeydown: handleKeydown,
684
+ ...autocompleteInput.value
685
+ })),
686
+ /**
687
+ * An optional button to control the visibility of the popup.
688
+ */
689
+ button: computed(() => ({
690
+ tabindex: "-1",
691
+ onClick: () => onToggle?.()
692
+ }))
693
+ }
694
+ };
695
+ }
696
+ );
697
+ const createMenuButton = createBuilder((options) => {
698
+ const rootId = useId();
699
+ const menuId = useId();
700
+ const rootRef = createElRef();
701
+ const menuRef = createElRef();
702
+ const buttonId = useId();
703
+ const position = computed(() => toValue(options.position) ?? "bottom");
704
+ useGlobalEventListener({
705
+ type: "keydown",
706
+ listener: (e) => e.key === "Escape" && setExpanded(false),
707
+ disabled: computed(() => !options.isExpanded.value)
708
+ });
709
+ const updateDebouncedExpanded = debounce(() => options.onToggle(), 200);
710
+ watch(options.isExpanded, () => updateDebouncedExpanded.abort());
711
+ const setExpanded = (expanded, debounced = false) => {
712
+ if (options.disabled?.value) return;
713
+ if (expanded === options.isExpanded.value) {
714
+ updateDebouncedExpanded.abort();
715
+ return;
716
+ }
717
+ if (debounced) {
718
+ updateDebouncedExpanded();
719
+ return;
720
+ }
721
+ options.onToggle();
722
+ };
723
+ const focusRelativeItem = (next) => {
724
+ const currentMenuItem = document.activeElement;
725
+ const currentMenu = currentMenuItem?.closest('[role="menu"]') || menuRef.value;
726
+ if (!currentMenu) return;
727
+ const menuItems = Array.from(currentMenu.querySelectorAll('[role="menuitem"]')).filter((item) => item.closest('[role="menu"]') === currentMenu);
728
+ if (position.value === "top") menuItems.reverse();
729
+ let nextIndex = 0;
730
+ if (currentMenuItem) {
731
+ const currentIndex = menuItems.indexOf(currentMenuItem);
732
+ switch (next) {
733
+ case "next":
734
+ nextIndex = currentIndex + 1;
735
+ break;
736
+ case "prev":
737
+ nextIndex = currentIndex - 1;
738
+ break;
739
+ case "first":
740
+ nextIndex = 0;
741
+ break;
742
+ case "last":
743
+ nextIndex = menuItems.length - 1;
744
+ break;
745
+ }
746
+ }
747
+ const nextMenuItem = menuItems[nextIndex];
748
+ nextMenuItem?.focus();
749
+ };
750
+ const handleKeydown = (event) => {
751
+ switch (event.key) {
752
+ case "ArrowDown":
753
+ event.preventDefault();
754
+ focusRelativeItem(position.value === "bottom" ? "next" : "prev");
755
+ break;
756
+ case "ArrowUp":
757
+ event.preventDefault();
758
+ focusRelativeItem(position.value === "bottom" ? "prev" : "next");
759
+ break;
760
+ case "Home":
761
+ event.preventDefault();
762
+ focusRelativeItem("first");
763
+ break;
764
+ case "End":
765
+ event.preventDefault();
766
+ focusRelativeItem("last");
767
+ break;
768
+ case " ":
769
+ case "Enter":
770
+ if (event.target instanceof HTMLInputElement) break;
771
+ event.preventDefault();
772
+ event.target.click();
773
+ break;
774
+ case "Escape":
775
+ event.preventDefault();
776
+ setExpanded(false);
777
+ break;
778
+ }
779
+ };
780
+ const triggerEvents = computed(() => {
781
+ if (toValue(options.trigger) !== "hover") return;
782
+ return {
783
+ onMouseenter: () => setExpanded(true),
784
+ onMouseleave: () => setExpanded(false, true)
785
+ };
786
+ });
787
+ useOutsideClick({
788
+ inside: rootRef,
789
+ onOutsideClick: () => setExpanded(false),
790
+ disabled: computed(() => !options.isExpanded.value),
791
+ checkOnTab: true
792
+ });
793
+ return {
794
+ elements: {
795
+ root: computed(() => ({
796
+ id: rootId,
797
+ onKeydown: handleKeydown,
798
+ ref: rootRef,
799
+ ...triggerEvents.value
800
+ })),
801
+ button: computed(
802
+ () => ({
803
+ "aria-controls": menuId,
804
+ "aria-expanded": options.isExpanded.value,
805
+ "aria-haspopup": true,
806
+ onFocus: () => setExpanded(true, true),
807
+ onClick: () => toValue(options.trigger) == "click" ? setExpanded(!options.isExpanded.value) : void 0,
808
+ id: buttonId,
809
+ disabled: options.disabled?.value
810
+ })
811
+ ),
812
+ menu: {
813
+ id: menuId,
814
+ ref: menuRef,
815
+ role: "menu",
816
+ "aria-labelledby": buttonId,
817
+ onClick: () => setExpanded(false)
818
+ },
819
+ ...createMenuItems().elements
820
+ }
821
+ };
822
+ });
823
+ const createMenuItems = createBuilder((options) => {
824
+ const onKeydown = (event) => {
825
+ switch (event.key) {
826
+ case "ArrowRight":
827
+ case " ":
828
+ case "Enter":
829
+ event.preventDefault();
830
+ options?.onOpen?.();
831
+ break;
832
+ }
833
+ };
834
+ return {
835
+ elements: {
836
+ listItem: {
837
+ role: "none"
838
+ },
839
+ menuItem: (data) => ({
840
+ "aria-current": data.active ? "page" : void 0,
841
+ "aria-disabled": data.disabled,
842
+ role: "menuitem",
843
+ onKeydown
844
+ })
845
+ }
846
+ };
847
+ });
848
+ const MathUtils = {
849
+ /**
850
+ * Ensures that a given `number` is or is between a given `min` and `max`.
851
+ */
852
+ clamp: (number, min, max) => Math.max(Math.min(number, max), min)
853
+ };
854
+ const createNavigationMenu = createBuilder(({ navigationName }) => {
855
+ const navId = useId();
856
+ const getMenuButtons = () => {
857
+ const nav = navId ? document.getElementById(navId) : void 0;
858
+ if (!nav) return [];
859
+ return Array.from(nav.querySelectorAll("button[aria-expanded][aria-controls]"));
860
+ };
861
+ const focusRelative = (trigger, next) => {
862
+ const menuButtons = getMenuButtons();
863
+ const index = menuButtons.indexOf(trigger);
864
+ if (index === -1) return;
865
+ const nextIndex = MathUtils.clamp(
866
+ index + (next === "next" ? 1 : -1),
867
+ 0,
868
+ menuButtons.length - 1
869
+ );
870
+ menuButtons[nextIndex].focus();
871
+ };
872
+ return {
873
+ elements: {
874
+ nav: {
875
+ "aria-label": unref(navigationName),
876
+ id: navId,
877
+ onKeydown: (event) => {
878
+ switch (event.key) {
879
+ case "ArrowRight":
880
+ focusRelative(event.target, "next");
881
+ break;
882
+ case "ArrowLeft":
883
+ focusRelative(event.target, "previous");
884
+ break;
885
+ }
886
+ }
887
+ }
888
+ }
889
+ };
890
+ });
891
+ const createTabs = createBuilder((options) => {
892
+ const idMap = /* @__PURE__ */ new Map();
893
+ const getId = (value) => {
894
+ if (!idMap.has(value)) {
895
+ idMap.set(value, { tabId: useId(), panelId: useId() });
896
+ }
897
+ return idMap.get(value);
898
+ };
899
+ const handleKeydown = (event) => {
900
+ const tab = event.target;
901
+ const enabledTabs = Array.from(
902
+ tab.parentElement?.querySelectorAll('[role="tab"]') ?? []
903
+ ).filter((tab2) => tab2.ariaDisabled !== "true");
904
+ const currentTabIndex = enabledTabs.indexOf(tab);
905
+ const focusElement = (element) => {
906
+ if (element instanceof HTMLElement) element.focus();
907
+ };
908
+ const focusFirstTab = () => focusElement(enabledTabs.at(0));
909
+ const focusLastTab = () => focusElement(enabledTabs.at(-1));
910
+ const focusTab = (direction) => {
911
+ if (currentTabIndex === -1) return;
912
+ const newIndex = direction === "next" ? currentTabIndex + 1 : currentTabIndex - 1;
913
+ if (newIndex < 0) {
914
+ return focusLastTab();
915
+ } else if (newIndex >= enabledTabs.length) {
916
+ return focusFirstTab();
917
+ }
918
+ return focusElement(enabledTabs.at(newIndex));
919
+ };
920
+ switch (event.key) {
921
+ case "ArrowRight":
922
+ focusTab("next");
923
+ break;
924
+ case "ArrowLeft":
925
+ focusTab("previous");
926
+ break;
927
+ case "Home":
928
+ focusFirstTab();
929
+ break;
930
+ case "End":
931
+ focusLastTab();
932
+ break;
933
+ case "Enter":
934
+ case " ":
935
+ {
936
+ const tabEntry = Array.from(idMap.entries()).find(([, { tabId }]) => tabId === tab.id);
937
+ if (tabEntry) options.onSelect?.(tabEntry[0]);
938
+ }
939
+ break;
940
+ }
941
+ };
942
+ return {
943
+ elements: {
944
+ tablist: computed(() => ({
945
+ role: "tablist",
946
+ "aria-label": unref(options.label),
947
+ onKeydown: handleKeydown
948
+ })),
949
+ tab: computed(() => {
950
+ return (data) => {
951
+ const { tabId: selectedTabId } = getId(unref(options.selectedTab));
952
+ const { tabId, panelId } = getId(data.value);
953
+ const isSelected = tabId === selectedTabId;
954
+ return {
955
+ id: tabId,
956
+ role: "tab",
957
+ "aria-selected": isSelected,
958
+ "aria-controls": panelId,
959
+ "aria-disabled": data.disabled ? true : void 0,
960
+ onClick: () => options.onSelect?.(data.value),
961
+ tabindex: isSelected && !data.disabled ? 0 : -1
962
+ };
963
+ };
964
+ }),
965
+ tabpanel: computed(() => {
966
+ return (data) => {
967
+ const { tabId, panelId } = getId(data.value);
968
+ return {
969
+ id: panelId,
970
+ role: "tabpanel",
971
+ "aria-labelledby": tabId
972
+ };
973
+ };
974
+ })
975
+ }
976
+ };
977
+ });
978
+ const useDismissible = ({ isExpanded }) => useGlobalEventListener({
979
+ type: "keydown",
980
+ listener: (e) => {
981
+ if (e.key === "Escape") {
982
+ isExpanded.value = false;
983
+ }
984
+ },
985
+ disabled: computed(() => !isExpanded.value)
986
+ });
987
+ const createToggletip = createBuilder(
988
+ ({ toggleLabel, isVisible }) => {
989
+ const triggerId = useId();
990
+ const _isVisible = toRef(isVisible ?? false);
991
+ useDismissible({ isExpanded: _isVisible });
992
+ const toggle = () => _isVisible.value = !_isVisible.value;
993
+ return {
994
+ elements: {
995
+ /**
996
+ * The element which controls the toggletip visibility:
997
+ * Preferably a `button` element.
998
+ */
999
+ trigger: computed(() => ({
1000
+ id: triggerId,
1001
+ onClick: toggle,
1002
+ "aria-label": toValue(toggleLabel)
1003
+ })),
1004
+ /**
1005
+ * The element with the relevant toggletip content.
1006
+ * Only simple, textual content is allowed.
1007
+ */
1008
+ tooltip: {
1009
+ onToggle: (e) => {
1010
+ const tooltip = e.target;
1011
+ _isVisible.value = tooltip.matches(":popover-open");
1012
+ },
1013
+ anchor: triggerId,
1014
+ popover: "auto",
1015
+ role: "status",
1016
+ tabindex: "-1"
1017
+ }
1018
+ },
1019
+ state: {
1020
+ isVisible: _isVisible
1021
+ }
1022
+ };
1023
+ }
1024
+ );
1025
+ const createTooltip = createBuilder(({ debounce: debounce2, isVisible }) => {
1026
+ const tooltipId = useId();
1027
+ const _isVisible = toRef(isVisible ?? false);
1028
+ let timeout;
1029
+ const debouncedVisible = computed({
1030
+ get: () => _isVisible.value,
1031
+ set: (newValue) => {
1032
+ clearTimeout(timeout);
1033
+ timeout = setTimeout(() => {
1034
+ _isVisible.value = newValue;
1035
+ }, toValue(debounce2));
1036
+ }
1037
+ });
1038
+ const hoverEvents = {
1039
+ onMouseover: () => debouncedVisible.value = true,
1040
+ onMouseout: () => debouncedVisible.value = false,
1041
+ onFocusin: () => _isVisible.value = true,
1042
+ onFocusout: () => _isVisible.value = false
1043
+ };
1044
+ useDismissible({ isExpanded: _isVisible });
1045
+ return {
1046
+ elements: {
1047
+ /**
1048
+ * The element which controls the tooltip visibility on hover.
1049
+ */
1050
+ trigger: {
1051
+ "aria-describedby": tooltipId,
1052
+ ...hoverEvents
1053
+ },
1054
+ /**
1055
+ * The element describing the tooltip.
1056
+ * Only simple, textual and non-focusable content is allowed.
1057
+ */
1058
+ tooltip: {
1059
+ popover: "manual",
1060
+ role: "tooltip",
1061
+ id: tooltipId,
1062
+ tabindex: "-1",
1063
+ ...hoverEvents
1064
+ }
1065
+ },
1066
+ state: {
1067
+ isVisible: _isVisible
1068
+ }
1069
+ };
1070
+ });
1071
+ export {
1072
+ CLOSING_KEYS,
1073
+ OPENING_KEYS,
1074
+ createBuilder,
1075
+ createComboBox,
1076
+ createElRef,
1077
+ createListbox,
1078
+ createMenuButton,
1079
+ createMenuItems,
1080
+ createNavigationMenu,
1081
+ createTabs,
1082
+ createToggletip,
1083
+ createTooltip,
1084
+ debounce,
1085
+ isPrintableCharacter,
1086
+ useGlobalEventListener,
1087
+ wasKeyPressed
1088
+ };