@geoffcox/sterling-svelte 2.0.1 → 2.0.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/README.md +1 -1
- package/dist/Button.svelte +18 -14
- package/dist/Button.svelte.d.ts +0 -1
- package/dist/Callout.svelte +162 -96
- package/dist/Callout.svelte.d.ts +1 -2
- package/dist/Checkbox.svelte +34 -15
- package/dist/Checkbox.svelte.d.ts +0 -1
- package/dist/Dialog.svelte +121 -71
- package/dist/Dialog.svelte.d.ts +1 -1
- package/dist/Dropdown.svelte +106 -56
- package/dist/Dropdown.svelte.d.ts +8 -3
- package/dist/Input.svelte +54 -29
- package/dist/Input.svelte.d.ts +1 -2
- package/dist/Label.svelte +99 -55
- package/dist/Label.svelte.d.ts +4 -4
- package/dist/Link.svelte +20 -14
- package/dist/Link.svelte.d.ts +0 -1
- package/dist/List.svelte +181 -126
- package/dist/List.svelte.d.ts +0 -1
- package/dist/ListItem.svelte +36 -21
- package/dist/ListItem.svelte.d.ts +0 -1
- package/dist/Menu.svelte +67 -45
- package/dist/Menu.svelte.d.ts +0 -1
- package/dist/MenuBar.svelte +96 -65
- package/dist/MenuBar.svelte.d.ts +0 -1
- package/dist/MenuButton.svelte +102 -62
- package/dist/MenuButton.svelte.d.ts +1 -1
- package/dist/MenuItem.svelte +332 -243
- package/dist/MenuItem.svelte.d.ts +3 -3
- package/dist/MenuSeparator.svelte +7 -7
- package/dist/MenuSeparator.svelte.d.ts +0 -1
- package/dist/Pagination.svelte +267 -0
- package/dist/Pagination.svelte.d.ts +4 -0
- package/dist/Pagination.types.d.ts +24 -0
- package/dist/Pagination.types.js +1 -0
- package/dist/Popover.svelte +114 -60
- package/dist/Popover.svelte.d.ts +1 -2
- package/dist/Portal.types.d.ts +1 -4
- package/dist/Progress.svelte +40 -15
- package/dist/Progress.svelte.d.ts +0 -1
- package/dist/Radio.svelte +37 -25
- package/dist/Radio.svelte.d.ts +0 -1
- package/dist/Select.svelte +191 -125
- package/dist/Select.svelte.d.ts +8 -2
- package/dist/Slider.svelte +120 -71
- package/dist/Slider.svelte.d.ts +0 -1
- package/dist/Switch.svelte +51 -20
- package/dist/Switch.svelte.d.ts +1 -1
- package/dist/Tab.svelte +39 -24
- package/dist/Tab.svelte.d.ts +0 -1
- package/dist/TabList.svelte +176 -125
- package/dist/TabList.svelte.d.ts +0 -1
- package/dist/TextArea.svelte +83 -41
- package/dist/TextArea.svelte.d.ts +2 -3
- package/dist/Tooltip.svelte +68 -36
- package/dist/Tree.svelte +52 -24
- package/dist/Tree.svelte.d.ts +0 -1
- package/dist/TreeChevron.svelte +24 -12
- package/dist/TreeChevron.svelte.d.ts +0 -1
- package/dist/TreeItem.svelte +292 -225
- package/dist/TreeItem.svelte.d.ts +1 -1
- package/dist/actions/extraClass.d.ts +1 -0
- package/dist/actions/extraClass.js +1 -0
- package/dist/idGenerator.d.ts +1 -0
- package/dist/idGenerator.js +1 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -2
- package/dist/mediaQueries/prefersColorSchemeDark.d.ts +0 -1
- package/dist/mediaQueries/prefersReducedMotion.d.ts +0 -1
- package/dist/mediaQueries/usingKeyboard.d.ts +0 -1
- package/package.json +21 -22
package/dist/MenuItem.svelte
CHANGED
|
@@ -1,274 +1,351 @@
|
|
|
1
1
|
<svelte:options runes={true} />
|
|
2
2
|
|
|
3
|
-
<script lang="ts">
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
import
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
3
|
+
<script lang="ts">
|
|
4
|
+
import { getContext, setContext, tick, type Snippet } from 'svelte';
|
|
5
|
+
import type {
|
|
6
|
+
HTMLButtonAttributes,
|
|
7
|
+
KeyboardEventHandler,
|
|
8
|
+
MouseEventHandler
|
|
9
|
+
} from 'svelte/elements';
|
|
10
|
+
import Menu from './Menu.svelte';
|
|
11
|
+
import { MENU_BAR_CONTEXT_KEY } from './MenuBar.constants';
|
|
12
|
+
import type { MenuBarContext } from './MenuBar.types';
|
|
13
|
+
import { MENU_ITEM_CONTEXT_KEY } from './MenuItem.constants';
|
|
14
|
+
import type { MenuItemContext, MenuItemRole } from './MenuItem.types';
|
|
15
|
+
import { isElementEnabledMenuItem } from './MenuItem.utils';
|
|
16
|
+
import Popover from './Popover.svelte';
|
|
17
|
+
import { usingKeyboard } from './mediaQueries/usingKeyboard';
|
|
18
|
+
|
|
19
|
+
const uuid = $props.id();
|
|
20
|
+
|
|
21
|
+
type Props = HTMLButtonAttributes & {
|
|
22
|
+
checked?: boolean | null;
|
|
23
|
+
item?: Snippet;
|
|
24
|
+
menuClass?: string;
|
|
25
|
+
onClose?: (value: string) => void;
|
|
26
|
+
onOpen?: (value: string) => void;
|
|
27
|
+
onSelect?: (value: string) => void;
|
|
28
|
+
role?: MenuItemRole;
|
|
29
|
+
shortcut?: string | Snippet;
|
|
30
|
+
text?: string | Snippet;
|
|
31
|
+
value: string;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
let {
|
|
35
|
+
checked,
|
|
36
|
+
children,
|
|
37
|
+
class: _class,
|
|
38
|
+
disabled,
|
|
39
|
+
item,
|
|
40
|
+
menuClass,
|
|
41
|
+
onClose,
|
|
42
|
+
onOpen,
|
|
43
|
+
onSelect,
|
|
44
|
+
role = 'menuitem',
|
|
45
|
+
text,
|
|
46
|
+
shortcut,
|
|
47
|
+
value,
|
|
48
|
+
...rest
|
|
49
|
+
}: Props = $props();
|
|
50
|
+
|
|
51
|
+
const menuItemContext = getContext<MenuItemContext>(MENU_ITEM_CONTEXT_KEY) || {};
|
|
52
|
+
|
|
53
|
+
const menuBarContext = getContext<MenuBarContext>(MENU_BAR_CONTEXT_KEY) || {};
|
|
54
|
+
|
|
55
|
+
const instanceId = `MenuItem-${uuid}`;
|
|
56
|
+
|
|
57
|
+
let displayId = $derived(`${value}-display-${instanceId}`);
|
|
58
|
+
let open = $derived(menuItemContext.openValues?.includes(value));
|
|
59
|
+
let prevOpen = $state(menuItemContext.openValues?.includes(value));
|
|
60
|
+
let menuId = $derived(`${value}-menu-${instanceId}`);
|
|
61
|
+
|
|
62
|
+
let menuItemRef: HTMLButtonElement | undefined = $state();
|
|
63
|
+
let menuRef: Menu | undefined = $state();
|
|
64
|
+
|
|
65
|
+
//#region methods
|
|
66
|
+
|
|
67
|
+
export const blur = () => {
|
|
23
68
|
menuItemRef?.blur();
|
|
24
|
-
};
|
|
25
|
-
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export const click = () => {
|
|
26
72
|
menuItemRef?.click();
|
|
27
|
-
};
|
|
28
|
-
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export const focus = (options?: FocusOptions) => {
|
|
29
76
|
menuItemRef?.focus(options);
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
//#
|
|
33
|
-
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
//#endregion
|
|
80
|
+
|
|
81
|
+
//#region events
|
|
82
|
+
|
|
83
|
+
const raiseClose = (value: string) => {
|
|
34
84
|
onClose?.(value);
|
|
35
85
|
menuItemContext.onClose?.(value);
|
|
36
|
-
};
|
|
37
|
-
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const raiseOpen = (value: string) => {
|
|
38
89
|
onOpen?.(value);
|
|
39
90
|
menuItemContext.onOpen?.(value);
|
|
40
|
-
};
|
|
41
|
-
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const raiseSelect = (value: string) => {
|
|
42
94
|
onSelect?.(value);
|
|
43
95
|
menuItemContext.onSelect?.(value);
|
|
44
|
-
};
|
|
45
|
-
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
$effect(() => {
|
|
46
99
|
if (open !== prevOpen) {
|
|
47
|
-
|
|
100
|
+
open ? raiseOpen(value) : raiseClose(value);
|
|
48
101
|
}
|
|
49
102
|
prevOpen = open;
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
//#
|
|
53
|
-
|
|
54
|
-
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
//#endregion
|
|
106
|
+
|
|
107
|
+
//#region focus
|
|
108
|
+
|
|
109
|
+
const focusPreviousMenuItem = () => {
|
|
110
|
+
let candidate =
|
|
111
|
+
menuItemRef?.previousElementSibling || menuItemRef?.parentElement?.lastElementChild;
|
|
55
112
|
while (candidate && !isElementEnabledMenuItem(candidate)) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
113
|
+
candidate = candidate.previousElementSibling || menuItemRef?.parentElement?.lastElementChild;
|
|
114
|
+
|
|
115
|
+
if (candidate === menuItemRef) {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
60
118
|
}
|
|
61
|
-
candidate?.focus();
|
|
119
|
+
(candidate as HTMLElement)?.focus();
|
|
62
120
|
return !!candidate;
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const focusNextMenuItem = () => {
|
|
124
|
+
let candidate =
|
|
125
|
+
menuItemRef?.nextElementSibling || menuItemRef?.parentElement?.firstElementChild;
|
|
66
126
|
while (candidate && !isElementEnabledMenuItem(candidate)) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
127
|
+
candidate = candidate.nextElementSibling || menuItemRef?.parentElement?.firstElementChild;
|
|
128
|
+
|
|
129
|
+
if (candidate === menuItemRef) {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
71
132
|
}
|
|
72
|
-
candidate?.focus();
|
|
133
|
+
(candidate as HTMLElement)?.focus();
|
|
73
134
|
return !!candidate;
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
//#
|
|
77
|
-
|
|
78
|
-
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
//#endregion
|
|
138
|
+
|
|
139
|
+
//#region open/close
|
|
140
|
+
|
|
141
|
+
// opens the menu for this menu item
|
|
142
|
+
const openMenu = () => {
|
|
79
143
|
if (!menuItemContext.openValues.includes(value)) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
144
|
+
// slice to depth to close any sibling menus that are open
|
|
145
|
+
menuItemContext.openValues = [
|
|
146
|
+
...menuItemContext.openValues.slice(0, menuItemContext.depth),
|
|
147
|
+
value
|
|
148
|
+
];
|
|
85
149
|
}
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
// closes the menu for this menu item
|
|
153
|
+
const closeMenu = async () => {
|
|
89
154
|
const index = menuItemContext.openValues.indexOf(value);
|
|
90
155
|
if (index !== -1) {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
156
|
+
menuItemContext.openValues = [...menuItemContext.openValues.slice(0, index)];
|
|
157
|
+
await tick();
|
|
158
|
+
menuItemRef?.focus();
|
|
94
159
|
}
|
|
95
|
-
};
|
|
96
|
-
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const closeAllMenus = () => {
|
|
97
163
|
menuItemContext.openValues = [];
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
//#
|
|
101
|
-
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
//#endregion
|
|
167
|
+
|
|
168
|
+
//#region event handlers
|
|
169
|
+
|
|
170
|
+
const onKeyDown: KeyboardEventHandler<HTMLButtonElement> = async (event) => {
|
|
102
171
|
if (!disabled && !event.altKey && !event.ctrlKey && !event.shiftKey) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
event.preventDefault();
|
|
115
|
-
event.stopPropagation();
|
|
116
|
-
return false;
|
|
117
|
-
}
|
|
118
|
-
if (!menuItemContext.isMenuBarItem) {
|
|
119
|
-
// ARIA menuitem:
|
|
120
|
-
// Moves focus to the next item, optionally wrapping from the last to the first.
|
|
121
|
-
focusNextMenuItem();
|
|
122
|
-
event.preventDefault();
|
|
123
|
-
event.stopPropagation();
|
|
124
|
-
return false;
|
|
125
|
-
}
|
|
126
|
-
break;
|
|
127
|
-
case 'ArrowLeft':
|
|
128
|
-
// ARIA menubar/menuitem:
|
|
129
|
-
// Moves focus to the previous item, optionally wrapping from the first to the last.
|
|
130
|
-
if (menuItemContext.isMenuBarItem) {
|
|
131
|
-
focusPreviousMenuItem();
|
|
132
|
-
event.preventDefault();
|
|
133
|
-
event.stopPropagation();
|
|
134
|
-
return false;
|
|
135
|
-
}
|
|
136
|
-
// ARIA menuitem:
|
|
137
|
-
// When focus is in a submenu of an item in a menu,
|
|
138
|
-
// closes the submenu and returns focus to the parent menuitem.
|
|
139
|
-
if (menuItemContext.depth && menuItemContext.depth > 1) {
|
|
140
|
-
menuItemContext.closeContainingMenu?.();
|
|
141
|
-
event.preventDefault();
|
|
142
|
-
event.stopPropagation();
|
|
143
|
-
return false;
|
|
144
|
-
}
|
|
145
|
-
// ARIA menubar/menuitem:
|
|
146
|
-
// When focus is in a submenu of an item in a menubar,
|
|
147
|
-
// closes the submenu,
|
|
148
|
-
// moves focus to the previous item in the menubar,
|
|
149
|
-
// and,
|
|
150
|
-
// if focus is now on a menuitem with a submenu,
|
|
151
|
-
// either opens the submenu of that menuitem without moving focus into the submenu,
|
|
152
|
-
// or opens the submenu of that menuitem and places focus on the first item in the submenu.
|
|
153
|
-
menuBarContext.openPreviousMenuBarItem?.();
|
|
154
|
-
event.preventDefault();
|
|
155
|
-
event.stopPropagation();
|
|
156
|
-
return false;
|
|
157
|
-
case 'ArrowRight':
|
|
158
|
-
// ARIA menubar:
|
|
159
|
-
// Moves focus to the next item, optionally wrapping from the last to the first.
|
|
160
|
-
if (menuItemContext.isMenuBarItem) {
|
|
161
|
-
focusNextMenuItem();
|
|
162
|
-
event.preventDefault();
|
|
163
|
-
event.stopPropagation();
|
|
164
|
-
return false;
|
|
165
|
-
}
|
|
166
|
-
// ARIA menuitem:
|
|
167
|
-
// When focus is in a menu and on a menuitem that has a submenu,
|
|
168
|
-
// opens the submenu and places focus on its first item
|
|
169
|
-
if (children) {
|
|
170
|
-
openMenu();
|
|
171
|
-
setTimeout(async () => {
|
|
172
|
-
await tick();
|
|
173
|
-
menuRef?.focusFirstMenuItem();
|
|
174
|
-
}, 10);
|
|
175
|
-
event.preventDefault();
|
|
176
|
-
event.stopPropagation();
|
|
177
|
-
return false;
|
|
178
|
-
}
|
|
179
|
-
// ARIA menubar/menuitem:
|
|
180
|
-
// When focus is in a menu and on an item that does not have a submenu,
|
|
181
|
-
// closes the submenu and any parent menus,
|
|
182
|
-
// moves focus to the next item in the menubar,
|
|
183
|
-
// and,
|
|
184
|
-
// if focus is now on a menuitem with a submenu,
|
|
185
|
-
// either opens the submenu of that menuitem without moving focus into the submenu,
|
|
186
|
-
// or opens the submenu of that menuitem and places focus on the first item in the submenu.
|
|
187
|
-
if (menuBarContext.openNextMenuBarItem) {
|
|
188
|
-
menuBarContext.openNextMenuBarItem();
|
|
189
|
-
event.preventDefault();
|
|
190
|
-
event.stopPropagation();
|
|
191
|
-
return false;
|
|
192
|
-
}
|
|
193
|
-
break;
|
|
194
|
-
case 'ArrowUp':
|
|
195
|
-
// ARIA menubar/menuitem:
|
|
196
|
-
// If the currently focused menuitem has a submenu,
|
|
197
|
-
// opens the submenu and places focus on the last item in the submenu.
|
|
198
|
-
if (menuItemContext.isMenuBarItem && children) {
|
|
199
|
-
openMenu();
|
|
200
|
-
setTimeout(async () => {
|
|
201
|
-
await tick();
|
|
202
|
-
menuRef?.focusLastMenuItem();
|
|
203
|
-
}, 10);
|
|
204
|
-
event.preventDefault();
|
|
205
|
-
event.stopPropagation();
|
|
206
|
-
return false;
|
|
207
|
-
}
|
|
208
|
-
// ARIA menuitem:
|
|
209
|
-
// Moves focus to the previous item, optionally wrapping from the first to the last.
|
|
210
|
-
if (!menuItemContext.isMenuBarItem) {
|
|
211
|
-
focusPreviousMenuItem();
|
|
212
|
-
event.preventDefault();
|
|
213
|
-
event.stopPropagation();
|
|
214
|
-
return false;
|
|
215
|
-
}
|
|
216
|
-
break;
|
|
217
|
-
case 'Escape':
|
|
218
|
-
// ARIA menuitem:
|
|
219
|
-
// Close the menu that contains focus and return focus to the element or context,
|
|
220
|
-
// e.g., menu button or parent menuitem, from which the menu was opened.
|
|
221
|
-
// open = false;
|
|
222
|
-
closeAllMenus();
|
|
223
|
-
event.preventDefault();
|
|
224
|
-
event.stopPropagation();
|
|
225
|
-
return false;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
rest.onkeydown?.(event);
|
|
229
|
-
};
|
|
230
|
-
const onMouseEnter = (event) => {
|
|
231
|
-
menuItemRef?.focus();
|
|
232
|
-
rest.onmouseenter?.(event);
|
|
233
|
-
};
|
|
234
|
-
const onClick = (event) => {
|
|
235
|
-
if (!disabled) {
|
|
236
|
-
if (children) {
|
|
237
|
-
if (!menuItemContext.openValues.includes(value)) {
|
|
238
|
-
openMenu();
|
|
239
|
-
if ($usingKeyboard) {
|
|
240
|
-
setTimeout(async () => {
|
|
241
|
-
await tick();
|
|
242
|
-
menuRef?.focusFirstMenuItem();
|
|
243
|
-
}, 10);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
else {
|
|
247
|
-
closeMenu();
|
|
248
|
-
}
|
|
172
|
+
switch (event.key) {
|
|
173
|
+
case 'ArrowDown':
|
|
174
|
+
// ARIA menubar/menuitem:
|
|
175
|
+
// If the currently focused menuitem has a submenu,
|
|
176
|
+
// opens the submenu and places focus on the first item in the submenu.
|
|
177
|
+
if (menuItemContext.isMenuBarItem && children) {
|
|
178
|
+
openMenu();
|
|
179
|
+
setTimeout(async () => {
|
|
180
|
+
await tick();
|
|
181
|
+
menuRef?.focusFirstMenuItem();
|
|
182
|
+
}, 10);
|
|
249
183
|
event.preventDefault();
|
|
250
184
|
event.stopPropagation();
|
|
251
185
|
return false;
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (!menuItemContext.isMenuBarItem) {
|
|
189
|
+
// ARIA menuitem:
|
|
190
|
+
// Moves focus to the next item, optionally wrapping from the last to the first.
|
|
191
|
+
focusNextMenuItem();
|
|
192
|
+
event.preventDefault();
|
|
193
|
+
event.stopPropagation();
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
break;
|
|
197
|
+
case 'ArrowLeft':
|
|
198
|
+
// ARIA menubar/menuitem:
|
|
199
|
+
// Moves focus to the previous item, optionally wrapping from the first to the last.
|
|
200
|
+
if (menuItemContext.isMenuBarItem) {
|
|
201
|
+
focusPreviousMenuItem();
|
|
202
|
+
event.preventDefault();
|
|
203
|
+
event.stopPropagation();
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// ARIA menuitem:
|
|
208
|
+
// When focus is in a submenu of an item in a menu,
|
|
209
|
+
// closes the submenu and returns focus to the parent menuitem.
|
|
210
|
+
if (menuItemContext.depth && menuItemContext.depth > 1) {
|
|
211
|
+
menuItemContext.closeContainingMenu?.();
|
|
212
|
+
event.preventDefault();
|
|
213
|
+
event.stopPropagation();
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// ARIA menubar/menuitem:
|
|
218
|
+
// When focus is in a submenu of an item in a menubar,
|
|
219
|
+
// closes the submenu,
|
|
220
|
+
// moves focus to the previous item in the menubar,
|
|
221
|
+
// and,
|
|
222
|
+
// if focus is now on a menuitem with a submenu,
|
|
223
|
+
// either opens the submenu of that menuitem without moving focus into the submenu,
|
|
224
|
+
// or opens the submenu of that menuitem and places focus on the first item in the submenu.
|
|
225
|
+
menuBarContext.openPreviousMenuBarItem?.();
|
|
226
|
+
event.preventDefault();
|
|
227
|
+
event.stopPropagation();
|
|
228
|
+
return false;
|
|
229
|
+
case 'ArrowRight':
|
|
230
|
+
// ARIA menubar:
|
|
231
|
+
// Moves focus to the next item, optionally wrapping from the last to the first.
|
|
232
|
+
if (menuItemContext.isMenuBarItem) {
|
|
233
|
+
focusNextMenuItem();
|
|
234
|
+
event.preventDefault();
|
|
235
|
+
event.stopPropagation();
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// ARIA menuitem:
|
|
240
|
+
// When focus is in a menu and on a menuitem that has a submenu,
|
|
241
|
+
// opens the submenu and places focus on its first item
|
|
242
|
+
if (children) {
|
|
243
|
+
openMenu();
|
|
244
|
+
setTimeout(async () => {
|
|
245
|
+
await tick();
|
|
246
|
+
menuRef?.focusFirstMenuItem();
|
|
247
|
+
}, 10);
|
|
248
|
+
event.preventDefault();
|
|
249
|
+
event.stopPropagation();
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// ARIA menubar/menuitem:
|
|
254
|
+
// When focus is in a menu and on an item that does not have a submenu,
|
|
255
|
+
// closes the submenu and any parent menus,
|
|
256
|
+
// moves focus to the next item in the menubar,
|
|
257
|
+
// and,
|
|
258
|
+
// if focus is now on a menuitem with a submenu,
|
|
259
|
+
// either opens the submenu of that menuitem without moving focus into the submenu,
|
|
260
|
+
// or opens the submenu of that menuitem and places focus on the first item in the submenu.
|
|
261
|
+
if (menuBarContext.openNextMenuBarItem) {
|
|
262
|
+
menuBarContext.openNextMenuBarItem();
|
|
256
263
|
event.preventDefault();
|
|
257
264
|
event.stopPropagation();
|
|
258
265
|
return false;
|
|
266
|
+
}
|
|
267
|
+
break;
|
|
268
|
+
case 'ArrowUp':
|
|
269
|
+
// ARIA menubar/menuitem:
|
|
270
|
+
// If the currently focused menuitem has a submenu,
|
|
271
|
+
// opens the submenu and places focus on the last item in the submenu.
|
|
272
|
+
if (menuItemContext.isMenuBarItem && children) {
|
|
273
|
+
openMenu();
|
|
274
|
+
setTimeout(async () => {
|
|
275
|
+
await tick();
|
|
276
|
+
menuRef?.focusLastMenuItem();
|
|
277
|
+
}, 10);
|
|
278
|
+
event.preventDefault();
|
|
279
|
+
event.stopPropagation();
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// ARIA menuitem:
|
|
284
|
+
// Moves focus to the previous item, optionally wrapping from the first to the last.
|
|
285
|
+
if (!menuItemContext.isMenuBarItem) {
|
|
286
|
+
focusPreviousMenuItem();
|
|
287
|
+
event.preventDefault();
|
|
288
|
+
event.stopPropagation();
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
break;
|
|
292
|
+
case 'Escape':
|
|
293
|
+
// ARIA menuitem:
|
|
294
|
+
// Close the menu that contains focus and return focus to the element or context,
|
|
295
|
+
// e.g., menu button or parent menuitem, from which the menu was opened.
|
|
296
|
+
// open = false;
|
|
297
|
+
closeAllMenus();
|
|
298
|
+
event.preventDefault();
|
|
299
|
+
event.stopPropagation();
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
rest.onkeydown?.(event);
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
const onMouseEnter: MouseEventHandler<HTMLButtonElement> = (event) => {
|
|
307
|
+
menuItemRef?.focus();
|
|
308
|
+
rest.onmouseenter?.(event);
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
const onClick: MouseEventHandler<HTMLButtonElement> = (event) => {
|
|
312
|
+
if (!disabled) {
|
|
313
|
+
if (children) {
|
|
314
|
+
if (!menuItemContext.openValues.includes(value)) {
|
|
315
|
+
openMenu();
|
|
316
|
+
if ($usingKeyboard) {
|
|
317
|
+
setTimeout(async () => {
|
|
318
|
+
await tick();
|
|
319
|
+
menuRef?.focusFirstMenuItem();
|
|
320
|
+
}, 10);
|
|
321
|
+
}
|
|
322
|
+
} else {
|
|
323
|
+
closeMenu();
|
|
259
324
|
}
|
|
325
|
+
event.preventDefault();
|
|
326
|
+
event.stopPropagation();
|
|
327
|
+
return false;
|
|
328
|
+
} else {
|
|
329
|
+
raiseSelect(value);
|
|
330
|
+
closeAllMenus();
|
|
331
|
+
event.preventDefault();
|
|
332
|
+
event.stopPropagation();
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
260
335
|
}
|
|
261
336
|
rest.onclick?.(event);
|
|
262
|
-
};
|
|
263
|
-
|
|
264
|
-
//#
|
|
265
|
-
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
//#endregion
|
|
340
|
+
|
|
341
|
+
//#region set context
|
|
342
|
+
let menuItemChildContext: MenuItemContext = {
|
|
266
343
|
isMenuBarItem: false,
|
|
267
344
|
get openValues() {
|
|
268
|
-
|
|
345
|
+
return menuItemContext.openValues;
|
|
269
346
|
},
|
|
270
|
-
set openValues(value) {
|
|
271
|
-
|
|
347
|
+
set openValues(value: string[]) {
|
|
348
|
+
menuItemContext.openValues = value;
|
|
272
349
|
},
|
|
273
350
|
rootValue: menuItemContext.rootValue || value,
|
|
274
351
|
depth: menuItemContext.depth ? menuItemContext.depth + 1 : 1,
|
|
@@ -276,13 +353,15 @@ let menuItemChildContext = {
|
|
|
276
353
|
onOpen: raiseOpen,
|
|
277
354
|
onClose: raiseClose,
|
|
278
355
|
onSelect: raiseSelect
|
|
279
|
-
};
|
|
280
|
-
|
|
281
|
-
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
setContext<MenuItemContext>(MENU_ITEM_CONTEXT_KEY, menuItemChildContext);
|
|
359
|
+
|
|
360
|
+
//#endregion
|
|
282
361
|
</script>
|
|
283
362
|
|
|
284
363
|
{#snippet renderDefaultItem()}
|
|
285
|
-
<div class="sterling-menu-item-display" class:disabled>
|
|
364
|
+
<div class="default-item sterling-menu-item-display" class:disabled>
|
|
286
365
|
<div
|
|
287
366
|
class="check"
|
|
288
367
|
class:checkmark={role === 'menuitemcheckbox'}
|
|
@@ -290,13 +369,23 @@ setContext(MENU_ITEM_CONTEXT_KEY, menuItemChildContext);
|
|
|
290
369
|
class:checked
|
|
291
370
|
></div>
|
|
292
371
|
<div class="content">
|
|
293
|
-
{text}
|
|
372
|
+
{#if text}
|
|
373
|
+
{#if typeof text === 'string'}
|
|
374
|
+
{text}
|
|
375
|
+
{:else}
|
|
376
|
+
{@render text()}
|
|
377
|
+
{/if}
|
|
378
|
+
{/if}
|
|
379
|
+
</div>
|
|
380
|
+
<div class="shortcut">
|
|
381
|
+
{#if shortcut}
|
|
382
|
+
{#if typeof shortcut === 'string'}
|
|
383
|
+
{shortcut}
|
|
384
|
+
{:else}
|
|
385
|
+
{@render shortcut()}
|
|
386
|
+
{/if}
|
|
387
|
+
{/if}
|
|
294
388
|
</div>
|
|
295
|
-
{#if shortcut}
|
|
296
|
-
<div class="shortcut">
|
|
297
|
-
{shortcut}
|
|
298
|
-
</div>
|
|
299
|
-
{/if}
|
|
300
389
|
<div class="chevron" class:has-children={!menuItemContext.isMenuBarItem && !!children}></div>
|
|
301
390
|
</div>
|
|
302
391
|
{/snippet}
|
|
@@ -308,7 +397,7 @@ setContext(MENU_ITEM_CONTEXT_KEY, menuItemChildContext);
|
|
|
308
397
|
aria-expanded={open}
|
|
309
398
|
aria-haspopup={!!children}
|
|
310
399
|
aria-owns={menuId}
|
|
311
|
-
class={['sterling-menu-item', _class]
|
|
400
|
+
class={['sterling-menu-item', _class]}
|
|
312
401
|
class:using-keyboard={usingKeyboard}
|
|
313
402
|
data-value={value}
|
|
314
403
|
data-root-value={menuItemContext.rootValue}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { MenuItemRole } from './MenuItem.types';
|
|
2
1
|
import { type Snippet } from 'svelte';
|
|
3
2
|
import type { HTMLButtonAttributes } from 'svelte/elements';
|
|
3
|
+
import type { MenuItemRole } from './MenuItem.types';
|
|
4
4
|
type Props = HTMLButtonAttributes & {
|
|
5
5
|
checked?: boolean | null;
|
|
6
6
|
item?: Snippet;
|
|
@@ -9,8 +9,8 @@ type Props = HTMLButtonAttributes & {
|
|
|
9
9
|
onOpen?: (value: string) => void;
|
|
10
10
|
onSelect?: (value: string) => void;
|
|
11
11
|
role?: MenuItemRole;
|
|
12
|
-
shortcut?: string;
|
|
13
|
-
text?: string;
|
|
12
|
+
shortcut?: string | Snippet;
|
|
13
|
+
text?: string | Snippet;
|
|
14
14
|
value: string;
|
|
15
15
|
};
|
|
16
16
|
declare const MenuItem: import("svelte").Component<Props, {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
<svelte:options runes={true} />
|
|
2
2
|
|
|
3
|
-
<script lang="ts">
|
|
4
|
-
|
|
3
|
+
<script lang="ts">
|
|
4
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
5
|
+
|
|
6
|
+
type Props = HTMLAttributes<HTMLDivElement>;
|
|
7
|
+
|
|
8
|
+
let { class: _class, ...rest }: Props = $props();
|
|
5
9
|
</script>
|
|
6
10
|
|
|
7
|
-
<div
|
|
8
|
-
class={['sterling-menu-separator', _class].filter(Boolean).join(' ')}
|
|
9
|
-
role="separator"
|
|
10
|
-
{...rest}
|
|
11
|
-
></div>
|
|
11
|
+
<div class={['sterling-menu-separator', _class]} role="separator" {...rest}></div>
|