@sit-onyx/headless 1.0.0-beta.1 → 1.0.0-beta.3
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 +1 -1
- package/src/composables/comboBox/SelectOnlyCombobox.vue +6 -2
- package/src/composables/comboBox/TestCombobox.vue +1 -2
- package/src/composables/comboBox/createComboBox.ts +0 -6
- package/src/composables/menuButton/TestMenuButton.ct.tsx +1 -1
- package/src/composables/menuButton/TestMenuButton.vue +7 -6
- package/src/composables/menuButton/createMenuButton.testing.ts +17 -16
- package/src/composables/menuButton/createMenuButton.ts +117 -99
- package/src/composables/navigationMenu/TestMenu.ct.tsx +12 -0
- package/src/composables/navigationMenu/TestMenu.vue +16 -0
- package/src/composables/navigationMenu/createMenu.testing.ts +37 -0
- package/src/composables/navigationMenu/createMenu.ts +56 -0
- package/src/index.ts +1 -0
- package/src/playwright.ts +1 -0
package/package.json
CHANGED
|
@@ -26,7 +26,6 @@ const onToggle = () => (isExpanded.value = !isExpanded.value);
|
|
|
26
26
|
const onTypeAhead = () => {};
|
|
27
27
|
|
|
28
28
|
const comboBox = createComboBox({
|
|
29
|
-
inputValue: selectedOption,
|
|
30
29
|
autocomplete: "none",
|
|
31
30
|
label: "some label",
|
|
32
31
|
listLabel: "List",
|
|
@@ -51,7 +50,12 @@ defineExpose({ comboBox });
|
|
|
51
50
|
|
|
52
51
|
<template>
|
|
53
52
|
<div ref="comboboxRef">
|
|
54
|
-
<input
|
|
53
|
+
<input
|
|
54
|
+
v-bind="input"
|
|
55
|
+
v-model="selectedOption"
|
|
56
|
+
readonly
|
|
57
|
+
@keydown.arrow-down="isExpanded = true"
|
|
58
|
+
/>
|
|
55
59
|
|
|
56
60
|
<button v-bind="button">
|
|
57
61
|
<template v-if="isExpanded">⬆️</template>
|
|
@@ -30,7 +30,6 @@ const onAutocomplete = (input: string) => (searchTerm.value = input);
|
|
|
30
30
|
const onToggle = () => (isExpanded.value = !isExpanded.value);
|
|
31
31
|
|
|
32
32
|
const comboBox = createComboBox({
|
|
33
|
-
inputValue: searchTerm,
|
|
34
33
|
autocomplete: "list",
|
|
35
34
|
label: "some label",
|
|
36
35
|
listLabel: "List",
|
|
@@ -55,7 +54,7 @@ defineExpose({ comboBox });
|
|
|
55
54
|
|
|
56
55
|
<template>
|
|
57
56
|
<div ref="comboboxRef">
|
|
58
|
-
<input v-bind="input" @keydown.arrow-down="isExpanded = true" />
|
|
57
|
+
<input v-bind="input" v-model="searchTerm" @keydown.arrow-down="isExpanded = true" />
|
|
59
58
|
|
|
60
59
|
<button v-bind="button">
|
|
61
60
|
<template v-if="isExpanded">⬆️</template>
|
|
@@ -41,10 +41,6 @@ export type CreateComboboxOptions<
|
|
|
41
41
|
* Labels the listbox which displays the available options. E.g. the list label could be "Countries" for a combobox which is labelled "Country".
|
|
42
42
|
*/
|
|
43
43
|
listLabel: MaybeRef<string>;
|
|
44
|
-
/**
|
|
45
|
-
* The current value of the combobox. Is updated when an option from the controlled listbox is selected or by typing into it.
|
|
46
|
-
*/
|
|
47
|
-
inputValue: Ref<string | undefined>;
|
|
48
44
|
/**
|
|
49
45
|
* Controls the opened/visible state of the associated pop-up. When expanded the activeOption can be controlled via the keyboard.
|
|
50
46
|
*/
|
|
@@ -105,7 +101,6 @@ export const createComboBox = createBuilder(
|
|
|
105
101
|
TAutoComplete extends ComboboxAutoComplete,
|
|
106
102
|
TMultiple extends boolean = false,
|
|
107
103
|
>({
|
|
108
|
-
inputValue,
|
|
109
104
|
autocomplete: autocompleteRef,
|
|
110
105
|
onAutocomplete,
|
|
111
106
|
onTypeAhead,
|
|
@@ -250,7 +245,6 @@ export const createComboBox = createBuilder(
|
|
|
250
245
|
* 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.
|
|
251
246
|
*/
|
|
252
247
|
input: computed(() => ({
|
|
253
|
-
value: inputValue.value,
|
|
254
248
|
role: "combobox",
|
|
255
249
|
"aria-expanded": isExpanded.value,
|
|
256
250
|
"aria-controls": controlsId,
|
|
@@ -8,18 +8,19 @@ const items = Array.from({ length: 10 }, (_, index) => {
|
|
|
8
8
|
});
|
|
9
9
|
|
|
10
10
|
const activeItem = ref<string>();
|
|
11
|
+
const isExpanded = ref(false);
|
|
12
|
+
const onToggle = () => (isExpanded.value = !isExpanded.value);
|
|
11
13
|
|
|
12
14
|
const {
|
|
13
|
-
elements: { button, menu, menuItem, listItem
|
|
14
|
-
|
|
15
|
-
} = createMenuButton();
|
|
15
|
+
elements: { root, button, menu, menuItem, listItem },
|
|
16
|
+
} = createMenuButton({ isExpanded, onToggle });
|
|
16
17
|
</script>
|
|
17
18
|
|
|
18
19
|
<template>
|
|
19
|
-
<
|
|
20
|
-
|
|
20
|
+
<div v-bind="root">
|
|
21
|
+
<button v-bind="button">Toggle nav menu</button>
|
|
21
22
|
<ul v-show="isExpanded" v-bind="menu">
|
|
22
|
-
<li v-for="item in items" v-bind="listItem" :key="item.value"
|
|
23
|
+
<li v-for="item in items" v-bind="listItem" :key="item.value">
|
|
23
24
|
<a v-bind="menuItem({ active: activeItem === item.value })" href="#">{{ item.label }}</a>
|
|
24
25
|
</li>
|
|
25
26
|
</ul>
|
|
@@ -17,7 +17,7 @@ export type MenuButtonTestingOptions = {
|
|
|
17
17
|
/**
|
|
18
18
|
* List items (at least 3).
|
|
19
19
|
*/
|
|
20
|
-
menuItems: Locator
|
|
20
|
+
menuItems: Locator;
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
/**
|
|
@@ -45,25 +45,26 @@ export const menuButtonTesting = async ({
|
|
|
45
45
|
await expect(button).toBeVisible();
|
|
46
46
|
|
|
47
47
|
// ensure correct navigation menu aria attributes
|
|
48
|
-
await expect(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
await expect(
|
|
56
|
-
button,
|
|
57
|
-
'flyout menu must have an "aria-expanded" attribute set to true',
|
|
58
|
-
).toHaveAttribute("aria-expanded", "true");
|
|
59
|
-
|
|
60
|
-
const firstItem = menuItems[0].getByRole("menuitem");
|
|
61
|
-
const secondItem = menuItems[1].getByRole("menuitem");
|
|
62
|
-
const lastItem = menuItems[menuItems.length - 1].getByRole("menuitem");
|
|
48
|
+
await expect(button, "button must have arial-controls attribute").toHaveAttribute(
|
|
49
|
+
"aria-controls",
|
|
50
|
+
);
|
|
51
|
+
await expect(button, "button must have aria-expanded attribute").toHaveAttribute(
|
|
52
|
+
"aria-expanded",
|
|
53
|
+
"false",
|
|
54
|
+
);
|
|
63
55
|
|
|
64
56
|
await page.keyboard.press("Tab");
|
|
65
57
|
await expect(button, "Button should be focused when pressing tab key").toBeFocused();
|
|
66
58
|
|
|
59
|
+
const firstItem = menuItems.first();
|
|
60
|
+
const secondItem = menuItems.nth(1);
|
|
61
|
+
const lastItem = menuItems.last();
|
|
62
|
+
|
|
63
|
+
await page.keyboard.press("Enter");
|
|
64
|
+
await expect(button, "button must have aria-expanded attribute").toHaveAttribute(
|
|
65
|
+
"aria-expanded",
|
|
66
|
+
"true",
|
|
67
|
+
);
|
|
67
68
|
await button.press("ArrowDown");
|
|
68
69
|
await expect(
|
|
69
70
|
firstItem,
|
|
@@ -1,123 +1,141 @@
|
|
|
1
|
-
import { computed, ref } from "vue";
|
|
1
|
+
import { computed, ref, type Ref } from "vue";
|
|
2
2
|
import { createBuilder } from "../../utils/builder";
|
|
3
3
|
import { createId } from "../../utils/id";
|
|
4
4
|
import { debounce } from "../../utils/timer";
|
|
5
|
+
import { useGlobalEventListener } from "../helpers/useGlobalListener";
|
|
6
|
+
|
|
7
|
+
type CreateMenuButtonOptions = {
|
|
8
|
+
isExpanded: Ref<boolean>;
|
|
9
|
+
onToggle: () => void;
|
|
10
|
+
};
|
|
5
11
|
|
|
6
12
|
/**
|
|
7
13
|
* Based on https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/examples/disclosure-navigation/
|
|
8
14
|
*/
|
|
9
|
-
export const createMenuButton = createBuilder(
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
15
|
+
export const createMenuButton = createBuilder(
|
|
16
|
+
({ isExpanded, onToggle }: CreateMenuButtonOptions) => {
|
|
17
|
+
const rootId = createId("menu-button-root");
|
|
18
|
+
const menuId = createId("menu-button-list");
|
|
19
|
+
const menuRef = ref<HTMLElement>();
|
|
20
|
+
const buttonId = createId("menu-button-button");
|
|
13
21
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
200,
|
|
20
|
-
);
|
|
22
|
+
useGlobalEventListener({
|
|
23
|
+
type: "keydown",
|
|
24
|
+
listener: (e) => e.key === "Escape" && isExpanded.value && onToggle(),
|
|
25
|
+
disabled: computed(() => !isExpanded.value),
|
|
26
|
+
});
|
|
21
27
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
});
|
|
28
|
+
/**
|
|
29
|
+
* Debounced expanded state that will only be toggled after a given timeout.
|
|
30
|
+
*/
|
|
31
|
+
const updateDebouncedExpanded = debounce(
|
|
32
|
+
(expanded: boolean) => isExpanded.value !== expanded && onToggle(),
|
|
33
|
+
200,
|
|
34
|
+
);
|
|
30
35
|
|
|
31
|
-
|
|
32
|
-
|
|
36
|
+
const focusRelativeItem = (next: "next" | "prev" | "first" | "last") => {
|
|
37
|
+
const currentMenuItem = document.activeElement as HTMLElement;
|
|
33
38
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (!currentMenu) return;
|
|
39
|
+
// Either the current focus is on a "menuitem", then we can just get the parent menu.
|
|
40
|
+
// Or the current focus is on the button, then we can get the connected menu using the menuId
|
|
41
|
+
const currentMenu = currentMenuItem?.closest('[role="menu"]') || menuRef.value;
|
|
42
|
+
if (!currentMenu) return;
|
|
39
43
|
|
|
40
|
-
|
|
41
|
-
|
|
44
|
+
const menuItems = [...currentMenu.querySelectorAll<HTMLElement>('[role="menuitem"]')];
|
|
45
|
+
let nextIndex = 0;
|
|
42
46
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
if (currentMenuItem) {
|
|
48
|
+
const currentIndex = menuItems.indexOf(currentMenuItem);
|
|
49
|
+
switch (next) {
|
|
50
|
+
case "next":
|
|
51
|
+
nextIndex = currentIndex + 1;
|
|
52
|
+
break;
|
|
53
|
+
case "prev":
|
|
54
|
+
nextIndex = currentIndex - 1;
|
|
55
|
+
break;
|
|
56
|
+
case "first":
|
|
57
|
+
nextIndex = 0;
|
|
58
|
+
break;
|
|
59
|
+
case "last":
|
|
60
|
+
nextIndex = menuItems.length - 1;
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const nextMenuItem = menuItems[nextIndex];
|
|
66
|
+
nextMenuItem?.focus();
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const handleKeydown = (event: KeyboardEvent) => {
|
|
70
|
+
switch (event.key) {
|
|
71
|
+
case "ArrowDown":
|
|
72
|
+
case "ArrowRight":
|
|
73
|
+
event.preventDefault();
|
|
74
|
+
focusRelativeItem("next");
|
|
75
|
+
break;
|
|
76
|
+
case "ArrowUp":
|
|
77
|
+
case "ArrowLeft":
|
|
78
|
+
event.preventDefault();
|
|
79
|
+
focusRelativeItem("prev");
|
|
80
|
+
break;
|
|
81
|
+
case "Home":
|
|
82
|
+
event.preventDefault();
|
|
83
|
+
focusRelativeItem("first");
|
|
48
84
|
break;
|
|
49
|
-
case "
|
|
50
|
-
|
|
85
|
+
case "End":
|
|
86
|
+
event.preventDefault();
|
|
87
|
+
focusRelativeItem("last");
|
|
51
88
|
break;
|
|
52
|
-
case "
|
|
53
|
-
|
|
89
|
+
case " ":
|
|
90
|
+
event.preventDefault();
|
|
91
|
+
(event.target as HTMLElement).click();
|
|
54
92
|
break;
|
|
55
|
-
case "
|
|
56
|
-
|
|
93
|
+
case "Escape":
|
|
94
|
+
event.preventDefault();
|
|
95
|
+
isExpanded.value && onToggle();
|
|
57
96
|
break;
|
|
58
97
|
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const nextMenuItem = menuItems[nextIndex];
|
|
62
|
-
nextMenuItem?.focus();
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
const handleKeydown = (event: KeyboardEvent) => {
|
|
66
|
-
switch (event.key) {
|
|
67
|
-
case "ArrowDown":
|
|
68
|
-
case "ArrowRight":
|
|
69
|
-
event.preventDefault();
|
|
70
|
-
focusRelativeItem("next");
|
|
71
|
-
break;
|
|
72
|
-
case "ArrowUp":
|
|
73
|
-
case "ArrowLeft":
|
|
74
|
-
event.preventDefault();
|
|
75
|
-
focusRelativeItem("prev");
|
|
76
|
-
break;
|
|
77
|
-
case "Home":
|
|
78
|
-
event.preventDefault();
|
|
79
|
-
focusRelativeItem("first");
|
|
80
|
-
break;
|
|
81
|
-
case "End":
|
|
82
|
-
event.preventDefault();
|
|
83
|
-
focusRelativeItem("last");
|
|
84
|
-
break;
|
|
85
|
-
case " ":
|
|
86
|
-
event.preventDefault();
|
|
87
|
-
(event.target as HTMLElement).click();
|
|
88
|
-
break;
|
|
89
|
-
}
|
|
90
|
-
};
|
|
98
|
+
};
|
|
91
99
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
(
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
100
|
+
return {
|
|
101
|
+
elements: {
|
|
102
|
+
root: {
|
|
103
|
+
id: rootId,
|
|
104
|
+
onKeydown: handleKeydown,
|
|
105
|
+
onMouseover: () => updateDebouncedExpanded(true),
|
|
106
|
+
onMouseout: () => updateDebouncedExpanded(false),
|
|
107
|
+
onFocusout: (event) => {
|
|
108
|
+
// if focus receiving element is not part of the menu button, then close
|
|
109
|
+
if (document.getElementById(rootId)?.contains(event.relatedTarget as HTMLElement)) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
isExpanded.value && onToggle();
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
button: computed(
|
|
116
|
+
() =>
|
|
117
|
+
({
|
|
118
|
+
"aria-controls": menuId,
|
|
119
|
+
"aria-expanded": isExpanded.value,
|
|
120
|
+
"aria-haspopup": true,
|
|
121
|
+
onFocus: () => !isExpanded.value && onToggle(),
|
|
122
|
+
id: buttonId,
|
|
123
|
+
}) as const,
|
|
124
|
+
),
|
|
125
|
+
menu: {
|
|
126
|
+
id: menuId,
|
|
127
|
+
ref: menuRef,
|
|
128
|
+
role: "menu",
|
|
129
|
+
"aria-labelledby": buttonId,
|
|
130
|
+
onClick: () => isExpanded.value && onToggle(),
|
|
131
|
+
},
|
|
132
|
+
...createMenuItems().elements,
|
|
114
133
|
},
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
});
|
|
134
|
+
};
|
|
135
|
+
},
|
|
136
|
+
);
|
|
119
137
|
|
|
120
|
-
export const
|
|
138
|
+
export const createMenuItems = createBuilder(() => {
|
|
121
139
|
return {
|
|
122
140
|
elements: {
|
|
123
141
|
listItem: {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { test } from "@playwright/experimental-ct-vue";
|
|
2
|
+
import { navigationTesting } from "./createMenu.testing";
|
|
3
|
+
import TestMenu from "./TestMenu.vue";
|
|
4
|
+
|
|
5
|
+
test("navigationMenu", async ({ mount, page }) => {
|
|
6
|
+
await mount(<TestMenu />);
|
|
7
|
+
|
|
8
|
+
await navigationTesting({
|
|
9
|
+
buttons: page.getByRole("button"),
|
|
10
|
+
nav: page.getByRole("navigation"),
|
|
11
|
+
});
|
|
12
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import TestMenuButton from "../menuButton/TestMenuButton.vue";
|
|
3
|
+
import { createNavigationMenu } from "./createMenu";
|
|
4
|
+
|
|
5
|
+
const {
|
|
6
|
+
elements: { nav },
|
|
7
|
+
} = createNavigationMenu({ navigationName: "test menu" });
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<template>
|
|
11
|
+
<nav v-bind="nav">
|
|
12
|
+
<TestMenuButton />
|
|
13
|
+
<TestMenuButton />
|
|
14
|
+
<TestMenuButton />
|
|
15
|
+
</nav>
|
|
16
|
+
</template>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { expect } from "@playwright/experimental-ct-vue";
|
|
2
|
+
import type { Locator } from "@playwright/test";
|
|
3
|
+
|
|
4
|
+
export type NavigationMenuTestingOptions = {
|
|
5
|
+
/**
|
|
6
|
+
* Locator for the navigation landmark.
|
|
7
|
+
*/
|
|
8
|
+
nav: Locator;
|
|
9
|
+
/**
|
|
10
|
+
* Locator for the button elements.
|
|
11
|
+
*/
|
|
12
|
+
buttons: Locator;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Playwright utility for executing accessibility testing for a navigation menu.
|
|
17
|
+
* Will check aria attributes and keyboard shortcuts as defined in https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/examples/disclosure-navigation/
|
|
18
|
+
*/
|
|
19
|
+
export const navigationTesting = async ({ nav, buttons }: NavigationMenuTestingOptions) => {
|
|
20
|
+
/**
|
|
21
|
+
* Navigation landmark should have label
|
|
22
|
+
*/
|
|
23
|
+
await expect(nav).toHaveRole("navigation");
|
|
24
|
+
await expect(nav).toHaveAttribute("aria-label");
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Focus first button
|
|
28
|
+
*/
|
|
29
|
+
await buttons.first().focus();
|
|
30
|
+
/**
|
|
31
|
+
* Move keyboard focus among top-level buttons using arrow keys
|
|
32
|
+
*/
|
|
33
|
+
await nav.press("ArrowRight");
|
|
34
|
+
await expect(buttons.nth(1)).toBeFocused();
|
|
35
|
+
await nav.press("ArrowLeft");
|
|
36
|
+
await expect(buttons.nth(0)).toBeFocused();
|
|
37
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { unref, type MaybeRef } from "vue";
|
|
2
|
+
import { createId } from "../..";
|
|
3
|
+
import { createBuilder } from "../../utils/builder";
|
|
4
|
+
import { MathUtils } from "../../utils/math";
|
|
5
|
+
|
|
6
|
+
type CreateNavigationMenu = {
|
|
7
|
+
/**
|
|
8
|
+
* Name of the navigation landmark.
|
|
9
|
+
* Usually this is the name of the website.
|
|
10
|
+
*/
|
|
11
|
+
navigationName?: MaybeRef<string | undefined>;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Based on https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/examples/disclosure-navigation/
|
|
16
|
+
*/
|
|
17
|
+
export const createNavigationMenu = createBuilder(({ navigationName }: CreateNavigationMenu) => {
|
|
18
|
+
const navId = createId("nav");
|
|
19
|
+
|
|
20
|
+
const getMenuButtons = () => {
|
|
21
|
+
const nav = document.getElementById(navId);
|
|
22
|
+
if (!nav) return [];
|
|
23
|
+
return [...nav.querySelectorAll<HTMLElement>("button[aria-expanded][aria-controls]")];
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const focusRelative = (trigger: HTMLElement, next: "next" | "previous") => {
|
|
27
|
+
const menuButtons = getMenuButtons();
|
|
28
|
+
const index = menuButtons.indexOf(trigger);
|
|
29
|
+
if (index === -1) return;
|
|
30
|
+
const nextIndex = MathUtils.clamp(
|
|
31
|
+
index + (next === "next" ? 1 : -1),
|
|
32
|
+
0,
|
|
33
|
+
menuButtons.length - 1,
|
|
34
|
+
);
|
|
35
|
+
menuButtons[nextIndex].focus();
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
elements: {
|
|
40
|
+
nav: {
|
|
41
|
+
"aria-label": unref(navigationName),
|
|
42
|
+
id: navId,
|
|
43
|
+
onKeydown: (event) => {
|
|
44
|
+
switch (event.key) {
|
|
45
|
+
case "ArrowRight":
|
|
46
|
+
focusRelative(event.target as HTMLElement, "next");
|
|
47
|
+
break;
|
|
48
|
+
case "ArrowLeft":
|
|
49
|
+
focusRelative(event.target as HTMLElement, "previous");
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export * from "./composables/comboBox/createComboBox";
|
|
2
2
|
export * from "./composables/listbox/createListbox";
|
|
3
3
|
export * from "./composables/menuButton/createMenuButton";
|
|
4
|
+
export * from "./composables/navigationMenu/createMenu";
|
|
4
5
|
export * from "./composables/tooltip/createTooltip";
|
|
5
6
|
export { createId } from "./utils/id";
|
|
6
7
|
export { isPrintableCharacter, wasKeyPressed } from "./utils/keyboard";
|
package/src/playwright.ts
CHANGED