@sit-onyx/headless 0.9.0 → 0.10.0-dev-20260505160733

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.
@@ -12,7 +12,7 @@ declare const _default: import('@vue/runtime-core').DefineComponent<{}, {
12
12
  readonly "aria-disabled": boolean | undefined;
13
13
  readonly "aria-checked": boolean | undefined;
14
14
  readonly "aria-selected": boolean | undefined;
15
- readonly onClick: () => false | void | undefined;
15
+ readonly onClick: (event: PointerEvent) => false | void | undefined;
16
16
  }>;
17
17
  group: import('@vue/reactivity').ComputedRef<(options: {
18
18
  label: string;
@@ -228,6 +228,7 @@ declare const _default: import('@vue/runtime-core').DefineComponent<{}, {
228
228
  }, undefined, {
229
229
  getOptionId: (value: string) => string;
230
230
  getOptionValueById: (id: string) => string | undefined;
231
+ getOption: (value: string) => HTMLElement | null;
231
232
  }>;
232
233
  }, {}, {}, {}, import('@vue/runtime-core').ComponentOptionsMixin, import('@vue/runtime-core').ComponentOptionsMixin, {}, string, import('@vue/runtime-core').PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import('@vue/runtime-core').ComponentProvideOptions, true, {
233
234
  combobox: HTMLDivElement;
@@ -12,7 +12,7 @@ declare const _default: import('@vue/runtime-core').DefineComponent<{}, {
12
12
  readonly "aria-disabled": boolean | undefined;
13
13
  readonly "aria-checked": boolean | undefined;
14
14
  readonly "aria-selected": boolean | undefined;
15
- readonly onClick: () => false | void | undefined;
15
+ readonly onClick: (event: PointerEvent) => false | void | undefined;
16
16
  }>;
17
17
  group: import('@vue/reactivity').ComputedRef<(options: {
18
18
  label: string;
@@ -228,6 +228,7 @@ declare const _default: import('@vue/runtime-core').DefineComponent<{}, {
228
228
  }, undefined, {
229
229
  getOptionId: (value: string) => string;
230
230
  getOptionValueById: (id: string) => string | undefined;
231
+ getOption: (value: string) => HTMLElement | null;
231
232
  }>;
232
233
  }, {}, {}, {}, import('@vue/runtime-core').ComponentOptionsMixin, import('@vue/runtime-core').ComponentOptionsMixin, {}, string, import('@vue/runtime-core').PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import('@vue/runtime-core').ComponentProvideOptions, true, {
233
234
  combobox: HTMLDivElement;
@@ -38,7 +38,7 @@ export type CreateComboboxOptions<TValue extends ListboxValue, TAutoComplete ext
38
38
  /**
39
39
  * Hook when an option is (un-)selected.
40
40
  */
41
- onSelect?: (value: TValue) => void;
41
+ onSelect?: (value: TValue, event?: Event) => void;
42
42
  /**
43
43
  * Hook when the first option should be activated.
44
44
  */
@@ -77,7 +77,7 @@ export declare const createComboBox: <TValue extends ListboxValue, TAutoComplete
77
77
  readonly "aria-disabled": boolean | undefined;
78
78
  readonly "aria-checked": boolean | undefined;
79
79
  readonly "aria-selected": boolean | undefined;
80
- readonly onClick: () => false | void | undefined;
80
+ readonly onClick: (event: PointerEvent) => false | void | undefined;
81
81
  }>;
82
82
  group: import('vue').ComputedRef<(options: {
83
83
  label: string;
@@ -303,4 +303,5 @@ export declare const createComboBox: <TValue extends ListboxValue, TAutoComplete
303
303
  }, undefined, {
304
304
  getOptionId: (value: TValue) => string;
305
305
  getOptionValueById: (id: string) => TValue | undefined;
306
+ getOption: (value: TValue) => HTMLElement | null;
306
307
  }>;
@@ -31,7 +31,7 @@ export type CreateListboxOptions<TValue extends ListboxValue, TMultiple extends
31
31
  /**
32
32
  * Hook when an option is selected.
33
33
  */
34
- onSelect?: (value: TValue) => void;
34
+ onSelect?: (value: TValue, event?: Event) => void;
35
35
  /**
36
36
  * Hook when the first option should be activated.
37
37
  */
@@ -93,11 +93,12 @@ export declare const createListbox: <TValue extends ListboxValue, TMultiple exte
93
93
  readonly "aria-disabled": boolean | undefined;
94
94
  readonly "aria-checked": boolean | undefined;
95
95
  readonly "aria-selected": boolean | undefined;
96
- readonly onClick: () => false | void | undefined;
96
+ readonly onClick: (event: PointerEvent) => false | void | undefined;
97
97
  }>;
98
98
  }, {
99
99
  isFocused: Ref<boolean, boolean>;
100
100
  }, {
101
101
  getOptionId: (value: TValue) => string;
102
102
  getOptionValueById: (id: string) => TValue | undefined;
103
+ getOption: (value: TValue) => HTMLElement | null;
103
104
  }>;
@@ -18,6 +18,10 @@ export declare const createMenuButton: (options: CreateMenuButtonOptions) => {
18
18
  elements: {
19
19
  listItem: {
20
20
  role: string;
21
+ onMouseenter: (event?: Event) => void;
22
+ onMouseleave: () => void;
23
+ onFocusin: (event?: Event) => void;
24
+ onFocusout: () => void;
21
25
  };
22
26
  menuItem: (data: {
23
27
  active?: boolean;
@@ -28,6 +32,22 @@ export declare const createMenuButton: (options: CreateMenuButtonOptions) => {
28
32
  role: string;
29
33
  onKeydown: (event: KeyboardEvent) => void;
30
34
  };
35
+ internalChildren: {
36
+ role: string;
37
+ };
38
+ backButton: {
39
+ onKeydown: (event: KeyboardEvent) => void;
40
+ onClick: (event: Event) => void;
41
+ };
42
+ externalChildren: {
43
+ role: string;
44
+ tabindex: number;
45
+ onKeydown: (event: KeyboardEvent) => void;
46
+ onMouseenter: (event?: Event) => void;
47
+ onMouseleave: () => void;
48
+ onFocusin: (event?: Event) => void;
49
+ onFocusout: () => void;
50
+ };
31
51
  root: import('vue').ComputedRef<{
32
52
  onMouseenter?: (() => void) | undefined;
33
53
  onMouseleave?: (() => void) | undefined;
@@ -54,26 +74,61 @@ export declare const createMenuButton: (options: CreateMenuButtonOptions) => {
54
74
  };
55
75
  };
56
76
  type CreateMenuItemOptions = {
57
- /**
58
- * Called when the menu item should be opened (if it has nested children).
59
- */
77
+ /** Current expanded state of the menu item (for nested children). */
78
+ isExpanded?: Ref<boolean>;
79
+ /** Whether the menu item renders its children in an external flyout. */
80
+ isExternal?: MaybeRefOrGetter<boolean>;
81
+ /** Whether the menu item is disabled. */
82
+ disabled?: MaybeRefOrGetter<boolean>;
83
+ /** DOM element ref of the external children wrapper (used for focus checks). */
84
+ externalChildrenRef?: Readonly<Ref<HTMLElement | null>>;
85
+ /** Called when the menu item should be opened (if it has nested children). */
60
86
  onOpen?: () => void;
87
+ /** Called when the menu item should be closed.*/
88
+ onClose?: () => void;
89
+ /** Called when the external children should close and focus the trigger. */
90
+ onFocusTrigger?: () => void;
91
+ /** Called to notify a parent menu of a hover enter event. */
92
+ onHoverEnterParent?: () => void;
93
+ /** Called to notify a parent menu of a hover leave event. */
94
+ onHoverLeaveParent?: () => void;
61
95
  openingArrowDirection?: MaybeRefOrGetter<"ArrowRight" | "ArrowLeft">;
62
96
  };
63
- export declare const createMenuItems: (options?: CreateMenuItemOptions | undefined) => {
64
- elements: {
65
- listItem: {
66
- role: string;
67
- };
68
- menuItem: (data: {
69
- active?: boolean;
70
- disabled?: boolean;
71
- }) => {
72
- "aria-current": "page" | undefined;
73
- "aria-disabled": boolean | undefined;
74
- role: string;
75
- onKeydown: (event: KeyboardEvent) => void;
76
- };
97
+ export declare const createMenuItems: (options?: CreateMenuItemOptions | undefined) => import('../../utils/builder.js').HeadlessComposable<{
98
+ listItem: {
99
+ role: string;
100
+ onMouseenter: (event?: Event) => void;
101
+ onMouseleave: () => void;
102
+ onFocusin: (event?: Event) => void;
103
+ onFocusout: () => void;
77
104
  };
78
- };
105
+ menuItem: (data: {
106
+ active?: boolean;
107
+ disabled?: boolean;
108
+ }) => {
109
+ "aria-current": "page" | undefined;
110
+ "aria-disabled": boolean | undefined;
111
+ role: string;
112
+ onKeydown: (event: KeyboardEvent) => void;
113
+ };
114
+ internalChildren: {
115
+ role: string;
116
+ };
117
+ backButton: {
118
+ onKeydown: (event: KeyboardEvent) => void;
119
+ onClick: (event: Event) => void;
120
+ };
121
+ externalChildren: {
122
+ role: string;
123
+ tabindex: number;
124
+ onKeydown: (event: KeyboardEvent) => void;
125
+ onMouseenter: (event?: Event) => void;
126
+ onMouseleave: () => void;
127
+ onFocusin: (event?: Event) => void;
128
+ onFocusout: () => void;
129
+ };
130
+ }, undefined, {
131
+ handlePopoverMouseEnter: (event?: Event) => void;
132
+ handlePopoverMouseLeave: () => void;
133
+ }>;
79
134
  export {};
package/dist/index.js CHANGED
@@ -564,15 +564,18 @@ var createListbox = createBuilder((options) => {
564
564
  const getOptionValueById = (id) => {
565
565
  return Array.from(descendantKeyIdMap.entries()).find(([_value, key]) => key === id)?.[0];
566
566
  };
567
+ const getOption = (value) => {
568
+ const id = getOptionId(value);
569
+ return document.getElementById(id);
570
+ };
567
571
  /**
568
572
  * Whether the listbox element is focused.
569
573
  */
570
574
  const isFocused = ref(false);
571
575
  watchEffect(async () => {
572
576
  if (!isExpanded.value || options.activeOption.value == void 0 || !isFocused.value && !options.controlled) return;
573
- const id = getOptionId(options.activeOption.value);
574
577
  await nextTick();
575
- document.getElementById(id)?.scrollIntoView({
578
+ getOption(options.activeOption.value)?.scrollIntoView({
576
579
  block: "nearest",
577
580
  inline: "nearest"
578
581
  });
@@ -582,7 +585,7 @@ var createListbox = createBuilder((options) => {
582
585
  switch (event.key) {
583
586
  case " ":
584
587
  event.preventDefault();
585
- if (options.activeOption.value != void 0) options.onSelect?.(options.activeOption.value);
588
+ if (options.activeOption.value != void 0) options.onSelect?.(options.activeOption.value, event);
586
589
  break;
587
590
  case "ArrowUp":
588
591
  event.preventDefault();
@@ -646,7 +649,7 @@ var createListbox = createBuilder((options) => {
646
649
  "aria-disabled": data.disabled,
647
650
  "aria-checked": isMultiselect.value ? selected : void 0,
648
651
  "aria-selected": !isMultiselect.value ? selected : void 0,
649
- onClick: () => !data.disabled && options.onSelect?.(data.value)
652
+ onClick: (event) => !data.disabled && options.onSelect?.(data.value, event)
650
653
  };
651
654
  };
652
655
  })
@@ -654,7 +657,8 @@ var createListbox = createBuilder((options) => {
654
657
  state: { isFocused },
655
658
  internals: {
656
659
  getOptionId,
657
- getOptionValueById
660
+ getOptionValueById,
661
+ getOption
658
662
  }
659
663
  };
660
664
  });
@@ -696,9 +700,9 @@ var createComboBox = createBuilder(({ autocomplete: autocompleteRef, onAutocompl
696
700
  if (autocomplete.value !== "none") onAutocomplete?.(inputElement.value);
697
701
  };
698
702
  const typeAhead = useTypeAhead((inputString) => onTypeAhead?.(inputString));
699
- const handleSelect = (value) => {
700
- onSelect?.(value);
701
- if (!unref(multiple)) onToggle?.();
703
+ const handleSelect = (value, event) => {
704
+ onSelect?.(value, event);
705
+ if (!toValue(multiple)) onToggle?.();
702
706
  };
703
707
  const handleNavigation = (event) => {
704
708
  switch (event.key) {
@@ -749,7 +753,7 @@ var createComboBox = createBuilder(({ autocomplete: autocompleteRef, onAutocompl
749
753
  type: "text"
750
754
  };
751
755
  });
752
- const { elements: { option, group, listbox }, internals: { getOptionId, getOptionValueById } } = createListbox({
756
+ const { elements: { option, group, listbox }, internals: { getOptionId, getOptionValueById, getOption } } = createListbox({
753
757
  label: listLabel,
754
758
  description: listDescription,
755
759
  multiple,
@@ -791,7 +795,8 @@ var createComboBox = createBuilder(({ autocomplete: autocompleteRef, onAutocompl
791
795
  },
792
796
  internals: {
793
797
  getOptionId,
794
- getOptionValueById
798
+ getOptionValueById,
799
+ getOption
795
800
  }
796
801
  };
797
802
  });
@@ -1154,7 +1159,48 @@ var createMenuButton = createBuilder((options) => {
1154
1159
  } };
1155
1160
  });
1156
1161
  var createMenuItems = createBuilder((options) => {
1157
- const { onOpen, openingArrowDirection = "ArrowRight" } = options || {};
1162
+ const { isExpanded, isExternal, disabled, externalChildrenRef, onOpen, onClose, onFocusTrigger, onHoverEnterParent, onHoverLeaveParent, openingArrowDirection = "ArrowRight" } = options || {};
1163
+ const debouncedClose = debounce(() => {
1164
+ if (isExpanded) isExpanded.value = false;
1165
+ }, 300);
1166
+ onBeforeUnmount(() => {
1167
+ debouncedClose.abort();
1168
+ });
1169
+ const handleClose = () => {
1170
+ debouncedClose.abort();
1171
+ if (isExpanded) isExpanded.value = false;
1172
+ onClose?.();
1173
+ };
1174
+ const handleTriggerMouseEnter = (event) => {
1175
+ if (toValue(isExternal) && !toValue(disabled)) {
1176
+ if (event?.type === "focusin") {
1177
+ const focusEvent = event;
1178
+ const relatedTarget = focusEvent.relatedTarget;
1179
+ const target = focusEvent.target;
1180
+ const externalNode = toValue(externalChildrenRef);
1181
+ if (!externalNode?.contains(target) && relatedTarget && externalNode?.contains(relatedTarget)) {
1182
+ debouncedClose.abort();
1183
+ return;
1184
+ }
1185
+ }
1186
+ debouncedClose.abort();
1187
+ if (isExpanded) isExpanded.value = true;
1188
+ }
1189
+ };
1190
+ const handleTriggerMouseLeave = () => {
1191
+ if (toValue(isExternal)) {
1192
+ debouncedClose.abort();
1193
+ debouncedClose();
1194
+ }
1195
+ };
1196
+ const handlePopoverMouseEnter = (event) => {
1197
+ handleTriggerMouseEnter(event);
1198
+ onHoverEnterParent?.();
1199
+ };
1200
+ const handlePopoverMouseLeave = () => {
1201
+ handleTriggerMouseLeave();
1202
+ onHoverLeaveParent?.();
1203
+ };
1158
1204
  const onKeydown = (event) => {
1159
1205
  const resolvedKey = toValue(openingArrowDirection);
1160
1206
  switch (event.key) {
@@ -1166,15 +1212,66 @@ var createMenuItems = createBuilder((options) => {
1166
1212
  break;
1167
1213
  }
1168
1214
  };
1169
- return { elements: {
1170
- listItem: { role: "none" },
1171
- menuItem: (data) => ({
1172
- "aria-current": data.active ? "page" : void 0,
1173
- "aria-disabled": data.disabled,
1174
- role: "menuitem",
1175
- onKeydown
1176
- })
1177
- } };
1215
+ const getClosingArrowDirection = () => {
1216
+ return toValue(openingArrowDirection) === "ArrowRight" ? "ArrowLeft" : "ArrowRight";
1217
+ };
1218
+ const onBackButtonKeydown = (event) => {
1219
+ if ([
1220
+ getClosingArrowDirection(),
1221
+ " ",
1222
+ "Enter"
1223
+ ].includes(event.key)) {
1224
+ event.preventDefault();
1225
+ event.stopPropagation();
1226
+ handleClose();
1227
+ }
1228
+ };
1229
+ const onExternalChildrenKeydown = (event) => {
1230
+ const closeKey = getClosingArrowDirection();
1231
+ if (event.key === closeKey) {
1232
+ event.preventDefault();
1233
+ event.stopPropagation();
1234
+ onFocusTrigger?.();
1235
+ }
1236
+ };
1237
+ return {
1238
+ elements: {
1239
+ listItem: {
1240
+ role: "none",
1241
+ onMouseenter: handleTriggerMouseEnter,
1242
+ onMouseleave: handleTriggerMouseLeave,
1243
+ onFocusin: handleTriggerMouseEnter,
1244
+ onFocusout: handleTriggerMouseLeave
1245
+ },
1246
+ menuItem: (data) => ({
1247
+ "aria-current": data.active ? "page" : void 0,
1248
+ "aria-disabled": data.disabled,
1249
+ role: "menuitem",
1250
+ onKeydown
1251
+ }),
1252
+ internalChildren: { role: "menu" },
1253
+ backButton: {
1254
+ onKeydown: onBackButtonKeydown,
1255
+ onClick: (event) => {
1256
+ event.stopPropagation();
1257
+ handleClose();
1258
+ }
1259
+ },
1260
+ externalChildren: {
1261
+ role: "presentation",
1262
+ tabindex: -1,
1263
+ onKeydown: onExternalChildrenKeydown,
1264
+ onMouseenter: handlePopoverMouseEnter,
1265
+ onMouseleave: handlePopoverMouseLeave,
1266
+ onFocusin: handlePopoverMouseEnter,
1267
+ onFocusout: handlePopoverMouseLeave
1268
+ }
1269
+ },
1270
+ internals: {
1271
+ handlePopoverMouseEnter,
1272
+ handlePopoverMouseLeave
1273
+ }
1274
+ };
1178
1275
  });
1179
1276
  //#endregion
1180
1277
  //#region src/utils/math.ts
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sit-onyx/headless",
3
3
  "description": "Headless composables for Vue",
4
- "version": "0.9.0",
4
+ "version": "0.10.0-dev-20260505160733",
5
5
  "type": "module",
6
6
  "author": "Schwarz IT KG",
7
7
  "license": "Apache-2.0",