@sit-onyx/headless 1.0.0-beta.5 → 1.0.0-beta.7
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/package.json
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
type ListboxValue,
|
|
11
11
|
} from "../listbox/createListbox";
|
|
12
12
|
|
|
13
|
+
/** See https://w3c.github.io/aria/#aria-autocomplete */
|
|
13
14
|
export type ComboboxAutoComplete = "none" | "list" | "both";
|
|
14
15
|
|
|
15
16
|
export const OPENING_KEYS: PressedKey[] = ["ArrowDown", "ArrowUp", " ", "Enter", "Home", "End"];
|
|
@@ -19,11 +20,15 @@ export const CLOSING_KEYS: PressedKey[] = [
|
|
|
19
20
|
"Enter",
|
|
20
21
|
"Tab",
|
|
21
22
|
];
|
|
22
|
-
const SELECTING_KEYS_SINGLE: PressedKey[] = ["Enter", " "];
|
|
23
|
-
const SELECTING_KEYS_MULTIPLE: PressedKey[] = ["Enter"];
|
|
24
23
|
|
|
25
|
-
const
|
|
26
|
-
|
|
24
|
+
const SELECTING_KEYS: PressedKey[] = ["Enter"];
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* if the a search input is included, space should not be used to select
|
|
28
|
+
* TODO: idea for the future: move this distinction to the listbox?
|
|
29
|
+
*/
|
|
30
|
+
const isSelectingKey = (event: KeyboardEvent, withSpace?: boolean) => {
|
|
31
|
+
const selectingKeys = withSpace ? [...SELECTING_KEYS, " "] : SELECTING_KEYS;
|
|
27
32
|
return isKeyOfGroup(event, selectingKeys);
|
|
28
33
|
};
|
|
29
34
|
|
|
@@ -41,6 +46,10 @@ export type CreateComboboxOptions<
|
|
|
41
46
|
* Labels the listbox which displays the available options. E.g. the list label could be "Countries" for a combobox which is labelled "Country".
|
|
42
47
|
*/
|
|
43
48
|
listLabel: MaybeRef<string>;
|
|
49
|
+
/**
|
|
50
|
+
* Provides additional description for the listbox which displays the available options.
|
|
51
|
+
*/
|
|
52
|
+
listDescription?: MaybeRef<string | undefined>;
|
|
44
53
|
/**
|
|
45
54
|
* Controls the opened/visible state of the associated pop-up. When expanded the activeOption can be controlled via the keyboard.
|
|
46
55
|
*/
|
|
@@ -107,6 +116,7 @@ export const createComboBox = createBuilder(
|
|
|
107
116
|
multiple: multipleRef,
|
|
108
117
|
label,
|
|
109
118
|
listLabel,
|
|
119
|
+
listDescription,
|
|
110
120
|
isExpanded: isExpandedRef,
|
|
111
121
|
activeOption,
|
|
112
122
|
onToggle,
|
|
@@ -183,7 +193,7 @@ export const createComboBox = createBuilder(
|
|
|
183
193
|
}
|
|
184
194
|
return onActivateFirst?.();
|
|
185
195
|
}
|
|
186
|
-
if (isSelectingKey(event,
|
|
196
|
+
if (isSelectingKey(event, autocomplete.value === "none")) {
|
|
187
197
|
return handleSelect(activeOption.value!);
|
|
188
198
|
}
|
|
189
199
|
if (isExpanded.value && isKeyOfGroup(event, CLOSING_KEYS)) {
|
|
@@ -213,9 +223,11 @@ export const createComboBox = createBuilder(
|
|
|
213
223
|
internals: { getOptionId },
|
|
214
224
|
} = createListbox({
|
|
215
225
|
label: listLabel,
|
|
226
|
+
description: listDescription,
|
|
216
227
|
multiple,
|
|
217
228
|
controlled: true,
|
|
218
229
|
activeOption,
|
|
230
|
+
isExpanded,
|
|
219
231
|
onSelect: handleSelect,
|
|
220
232
|
});
|
|
221
233
|
|
|
@@ -30,7 +30,9 @@ const {
|
|
|
30
30
|
elements: { listbox, option: headlessOption },
|
|
31
31
|
} = createListbox({
|
|
32
32
|
label: "Test listbox",
|
|
33
|
+
description: "Test description",
|
|
33
34
|
activeOption,
|
|
35
|
+
isExpanded: true,
|
|
34
36
|
onSelect: (id) => {
|
|
35
37
|
selectedOption.value = selectedOption.value === id ? undefined : id;
|
|
36
38
|
},
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { computed, ref, unref, watchEffect, type MaybeRef, type Ref } from "vue";
|
|
1
|
+
import { computed, nextTick, ref, unref, watchEffect, type MaybeRef, type Ref } from "vue";
|
|
2
2
|
import { createId } from "../..";
|
|
3
3
|
import { createBuilder, type VBindAttributes } from "../../utils/builder";
|
|
4
4
|
import { useTypeAhead } from "../helpers/useTypeAhead";
|
|
@@ -10,6 +10,10 @@ export type CreateListboxOptions<TValue extends ListboxValue, TMultiple extends
|
|
|
10
10
|
* Aria label for the listbox.
|
|
11
11
|
*/
|
|
12
12
|
label: MaybeRef<string>;
|
|
13
|
+
/**
|
|
14
|
+
* Aria description for the listbox.
|
|
15
|
+
*/
|
|
16
|
+
description?: MaybeRef<string | undefined>;
|
|
13
17
|
/**
|
|
14
18
|
* Value of currently (visually) active option.
|
|
15
19
|
*/
|
|
@@ -19,6 +23,10 @@ export type CreateListboxOptions<TValue extends ListboxValue, TMultiple extends
|
|
|
19
23
|
* This disables keyboard events and makes the listbox not focusable.
|
|
20
24
|
*/
|
|
21
25
|
controlled?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Controls the opened/visible state of the listbox. When expanded the activeOption can be controlled via the keyboard.
|
|
28
|
+
*/
|
|
29
|
+
isExpanded?: MaybeRef<boolean>;
|
|
22
30
|
/**
|
|
23
31
|
* Whether the listbox is multiselect.
|
|
24
32
|
*/
|
|
@@ -77,6 +85,7 @@ export const createListbox = createBuilder(
|
|
|
77
85
|
options: CreateListboxOptions<TValue, TMultiple>,
|
|
78
86
|
) => {
|
|
79
87
|
const isMultiselect = computed(() => unref(options.multiple) ?? false);
|
|
88
|
+
const isExpanded = computed(() => unref(options.isExpanded) ?? false);
|
|
80
89
|
|
|
81
90
|
/**
|
|
82
91
|
* Map for option IDs. key = option value, key = ID for the HTML element
|
|
@@ -96,11 +105,18 @@ export const createListbox = createBuilder(
|
|
|
96
105
|
const isFocused = ref(false);
|
|
97
106
|
|
|
98
107
|
// scroll currently active option into view if needed
|
|
99
|
-
watchEffect(() => {
|
|
100
|
-
if (
|
|
108
|
+
watchEffect(async () => {
|
|
109
|
+
if (
|
|
110
|
+
!isExpanded.value ||
|
|
111
|
+
options.activeOption.value == undefined ||
|
|
112
|
+
(!isFocused.value && !options.controlled)
|
|
113
|
+
)
|
|
101
114
|
return;
|
|
102
115
|
const id = getOptionId(options.activeOption.value);
|
|
103
|
-
|
|
116
|
+
|
|
117
|
+
await nextTick(() => {
|
|
118
|
+
document.getElementById(id)?.scrollIntoView({ block: "end", inline: "nearest" });
|
|
119
|
+
});
|
|
104
120
|
});
|
|
105
121
|
|
|
106
122
|
const typeAhead = useTypeAhead((inputString) => options.onTypeAhead?.(inputString));
|
|
@@ -158,12 +174,14 @@ export const createListbox = createBuilder(
|
|
|
158
174
|
role: "listbox",
|
|
159
175
|
"aria-multiselectable": isMultiselect.value,
|
|
160
176
|
"aria-label": unref(options.label),
|
|
177
|
+
"aria-description": options.description,
|
|
161
178
|
tabindex: "-1",
|
|
162
179
|
}
|
|
163
180
|
: {
|
|
164
181
|
role: "listbox",
|
|
165
182
|
"aria-multiselectable": isMultiselect.value,
|
|
166
183
|
"aria-label": unref(options.label),
|
|
184
|
+
"aria-description": options.description,
|
|
167
185
|
tabindex: "0",
|
|
168
186
|
"aria-activedescendant":
|
|
169
187
|
options.activeOption.value != undefined
|
package/src/utils/vitest.ts
CHANGED