@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/ListItem.svelte
CHANGED
|
@@ -1,37 +1,52 @@
|
|
|
1
1
|
<svelte:options runes={true} />
|
|
2
2
|
|
|
3
|
-
<script lang="ts">
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
<script lang="ts">
|
|
4
|
+
import { getContext } from 'svelte';
|
|
5
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
6
|
+
import { LIST_CONTEXT_KEY } from './List.constants';
|
|
7
|
+
import type { ListContext } from './List.types';
|
|
8
|
+
|
|
9
|
+
type Props = HTMLAttributes<HTMLDivElement> & {
|
|
10
|
+
disabled?: boolean | null;
|
|
11
|
+
value?: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
let { children, class: _class, disabled, value, ...rest }: Props = $props();
|
|
15
|
+
|
|
16
|
+
const listContext = getContext<ListContext>(LIST_CONTEXT_KEY);
|
|
17
|
+
|
|
18
|
+
let selected = $state(listContext.selectedValue === value);
|
|
19
|
+
|
|
20
|
+
// Using $derived would be preferred, but this helps avoid
|
|
21
|
+
// updates to every list item when selectedValue changes.
|
|
22
|
+
$effect(() => {
|
|
11
23
|
if (listContext.selectedValue === value && !selected) {
|
|
12
|
-
|
|
24
|
+
selected = true;
|
|
25
|
+
} else if (listContext.selectedValue !== value && selected) {
|
|
26
|
+
selected = false;
|
|
13
27
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
export const click = () => {
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
let itemRef: HTMLDivElement;
|
|
31
|
+
|
|
32
|
+
export const click = () => {
|
|
20
33
|
itemRef?.click();
|
|
21
|
-
};
|
|
22
|
-
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const blur = () => {
|
|
23
37
|
itemRef?.blur();
|
|
24
|
-
};
|
|
25
|
-
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const focus = (options?: FocusOptions) => {
|
|
26
41
|
itemRef?.focus(options);
|
|
27
|
-
};
|
|
42
|
+
};
|
|
28
43
|
</script>
|
|
29
44
|
|
|
30
45
|
<!-- svelte-ignore a11y_role_supports_aria_props -->
|
|
31
46
|
<div
|
|
32
47
|
aria-selected={selected}
|
|
33
48
|
bind:this={itemRef}
|
|
34
|
-
class={['sterling-list-item', _class]
|
|
49
|
+
class={['sterling-list-item', _class]}
|
|
35
50
|
class:disabled={disabled || listContext.disabled}
|
|
36
51
|
class:horizontal={listContext.horizontal}
|
|
37
52
|
class:item-disabled={disabled}
|
package/dist/Menu.svelte
CHANGED
|
@@ -1,72 +1,94 @@
|
|
|
1
1
|
<svelte:options runes={true} />
|
|
2
2
|
|
|
3
|
-
<script lang="ts">
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
3
|
+
<script lang="ts">
|
|
4
|
+
import { getContext } from 'svelte';
|
|
5
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
6
|
+
import { slide, type SlideParams, type TransitionConfig } from 'svelte/transition';
|
|
7
|
+
import { MENU_ITEM_CONTEXT_KEY } from './MenuItem.constants';
|
|
8
|
+
import type { MenuItemContext } from './MenuItem.types';
|
|
9
|
+
import { isElementEnabledMenuItem, isElementMenuItem } from './MenuItem.utils';
|
|
10
|
+
import { prefersReducedMotion } from './mediaQueries/prefersReducedMotion';
|
|
11
|
+
|
|
12
|
+
type Props = HTMLAttributes<HTMLDivElement>;
|
|
13
|
+
|
|
14
|
+
let { children, class: _class, ...rest }: Props = $props();
|
|
15
|
+
|
|
16
|
+
let menuRef: HTMLDivElement;
|
|
17
|
+
let menuItemsRef: HTMLDivElement;
|
|
18
|
+
|
|
19
|
+
const noSlide = (node: Element, params?: SlideParams): TransitionConfig => {
|
|
12
20
|
return { delay: 0, duration: 0 };
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
let slideMotion = $derived(!$prefersReducedMotion ? slide : noSlide);
|
|
24
|
+
|
|
25
|
+
const { rootValue = undefined } = getContext<MenuItemContext>(MENU_ITEM_CONTEXT_KEY);
|
|
26
|
+
|
|
27
|
+
const isElementInThisMenu = (candidate: Element) => {
|
|
17
28
|
return candidate && candidate.closest('[role="menu"]') === menuRef;
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
//#region focus
|
|
32
|
+
export const focus = (options?: FocusOptions) => {
|
|
21
33
|
menuRef?.focus(options);
|
|
22
|
-
};
|
|
23
|
-
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const blur = () => {
|
|
24
37
|
menuRef?.blur();
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const focusFirstMenuItem = () => {
|
|
41
|
+
let candidate: Element | undefined | null = menuItemsRef?.firstElementChild;
|
|
28
42
|
while (candidate && !isElementEnabledMenuItem(candidate)) {
|
|
29
|
-
|
|
43
|
+
candidate = candidate.nextElementSibling;
|
|
30
44
|
}
|
|
31
|
-
|
|
45
|
+
|
|
46
|
+
(candidate as HTMLElement)?.focus({ preventScroll: true });
|
|
32
47
|
return !!candidate;
|
|
33
|
-
};
|
|
34
|
-
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export const focusPreviousMenuItem = () => {
|
|
35
51
|
let candidate = document.activeElement;
|
|
52
|
+
|
|
36
53
|
if (candidate && isElementMenuItem(candidate) && isElementInThisMenu(candidate)) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
54
|
+
candidate = menuItemsRef?.previousElementSibling;
|
|
55
|
+
while (candidate && !isElementEnabledMenuItem(candidate)) {
|
|
56
|
+
candidate = candidate.previousElementSibling;
|
|
57
|
+
}
|
|
58
|
+
(candidate as HTMLElement)?.focus();
|
|
42
59
|
}
|
|
43
60
|
return !!candidate;
|
|
44
|
-
};
|
|
45
|
-
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const focusNextMenuItem = () => {
|
|
46
64
|
let candidate = document.activeElement;
|
|
65
|
+
|
|
47
66
|
if (candidate && isElementMenuItem(candidate) && isElementInThisMenu(candidate)) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
67
|
+
candidate = menuItemsRef?.nextElementSibling;
|
|
68
|
+
while (candidate && !isElementEnabledMenuItem(candidate)) {
|
|
69
|
+
candidate = candidate.nextElementSibling;
|
|
70
|
+
}
|
|
71
|
+
(candidate as HTMLElement)?.focus();
|
|
53
72
|
}
|
|
54
73
|
return !!candidate;
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export const focusLastMenuItem = () => {
|
|
77
|
+
let candidate: Element | undefined | null = menuItemsRef?.lastElementChild;
|
|
58
78
|
while (candidate && !isElementEnabledMenuItem(candidate)) {
|
|
59
|
-
|
|
79
|
+
candidate = candidate.previousElementSibling;
|
|
60
80
|
}
|
|
61
|
-
|
|
81
|
+
|
|
82
|
+
(candidate as HTMLElement)?.focus({ preventScroll: true });
|
|
62
83
|
return !!candidate;
|
|
63
|
-
};
|
|
64
|
-
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
//#endregion
|
|
65
87
|
</script>
|
|
66
88
|
|
|
67
89
|
<div
|
|
68
90
|
bind:this={menuRef}
|
|
69
|
-
class={['sterling-menu', _class]
|
|
91
|
+
class={['sterling-menu', _class]}
|
|
70
92
|
role="menu"
|
|
71
93
|
class:open
|
|
72
94
|
data-root-value={rootValue}
|
package/dist/Menu.svelte.d.ts
CHANGED
package/dist/MenuBar.svelte
CHANGED
|
@@ -1,113 +1,144 @@
|
|
|
1
1
|
<svelte:options runes={true} />
|
|
2
2
|
|
|
3
|
-
<script lang="ts">
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
3
|
+
<script lang="ts">
|
|
4
|
+
import { setContext } from 'svelte';
|
|
5
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
6
|
+
import { clickOutside } from './actions/clickOutside';
|
|
7
|
+
import { MENU_BAR_CONTEXT_KEY } from './MenuBar.constants';
|
|
8
|
+
import type { MenuBarContext } from './MenuBar.types';
|
|
9
|
+
import { MENU_ITEM_CONTEXT_KEY } from './MenuItem.constants';
|
|
10
|
+
import type { MenuItemContext } from './MenuItem.types';
|
|
11
|
+
import { isElementEnabledMenuItem } from './MenuItem.utils';
|
|
12
|
+
|
|
13
|
+
const uuid = $props.id();
|
|
14
|
+
|
|
15
|
+
type Props = HTMLAttributes<HTMLDivElement> & {
|
|
16
|
+
onClose?: (value: string) => void;
|
|
17
|
+
onOpen?: (value: string) => void;
|
|
18
|
+
onSelect?: (value: string) => void;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
let { class: _class, children, onClose, onOpen, onSelect, ...rest }: Props = $props();
|
|
22
|
+
|
|
23
|
+
const rootValue = `MenuBar-${uuid}`;
|
|
24
|
+
let openValues: string[] = $state([]);
|
|
25
|
+
let prevOpenValue: string | undefined = $state();
|
|
26
|
+
|
|
27
|
+
let menuBarRef: HTMLDivElement;
|
|
28
|
+
|
|
29
|
+
$effect(() => {
|
|
15
30
|
prevOpenValue = openValues[0];
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Restore focus to the last open menu bar item when it closes
|
|
34
|
+
$effect(() => {
|
|
19
35
|
if (openValues.length === 0 && prevOpenValue !== undefined) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
36
|
+
const candidate = menuBarRef.querySelector(`[data-value="${prevOpenValue}"]`);
|
|
37
|
+
(candidate as HTMLElement)?.focus();
|
|
38
|
+
prevOpenValue = undefined;
|
|
23
39
|
}
|
|
24
|
-
});
|
|
25
|
-
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
export const blur = () => {
|
|
26
43
|
menuBarRef?.blur();
|
|
27
|
-
};
|
|
28
|
-
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const focus = (options?: FocusOptions) => {
|
|
29
47
|
menuBarRef?.focus(options);
|
|
30
|
-
};
|
|
31
|
-
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const getOpenMenuBarItem = () => {
|
|
32
51
|
const value = openValues[0];
|
|
33
52
|
if (value) {
|
|
34
|
-
|
|
53
|
+
return menuBarRef.querySelector(`[data-value="${value}"]`);
|
|
35
54
|
}
|
|
36
55
|
return null;
|
|
37
|
-
};
|
|
38
|
-
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const openPreviousMenuBarItem = () => {
|
|
39
59
|
const openItem = getOpenMenuBarItem() || menuBarRef.firstElementChild;
|
|
40
60
|
let candidate = openItem?.previousElementSibling || menuBarRef.lastElementChild;
|
|
61
|
+
|
|
41
62
|
while (candidate && !isElementEnabledMenuItem(candidate)) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
63
|
+
candidate = candidate.previousElementSibling || menuBarRef.lastElementChild;
|
|
64
|
+
if (candidate === openItem) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
46
67
|
}
|
|
68
|
+
|
|
47
69
|
if (!candidate) {
|
|
48
|
-
|
|
49
|
-
|
|
70
|
+
candidate = menuBarRef.lastElementChild;
|
|
71
|
+
candidate = candidate && isElementEnabledMenuItem(candidate) ? candidate : null;
|
|
50
72
|
}
|
|
51
|
-
|
|
73
|
+
|
|
74
|
+
(candidate as HTMLElement)?.click();
|
|
52
75
|
return !!candidate;
|
|
53
|
-
};
|
|
54
|
-
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const openNextMenuBarItem = () => {
|
|
55
79
|
const openItem = getOpenMenuBarItem() || menuBarRef.lastElementChild;
|
|
56
80
|
let candidate = openItem?.nextElementSibling || menuBarRef.firstElementChild;
|
|
81
|
+
|
|
57
82
|
while (candidate && !isElementEnabledMenuItem(candidate)) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
83
|
+
candidate = candidate.nextElementSibling || menuBarRef.firstElementChild;
|
|
84
|
+
if (candidate === openItem) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
62
87
|
}
|
|
88
|
+
|
|
63
89
|
if (!candidate) {
|
|
64
|
-
|
|
65
|
-
|
|
90
|
+
candidate = menuBarRef.firstElementChild;
|
|
91
|
+
candidate = candidate && isElementEnabledMenuItem(candidate) ? candidate : null;
|
|
66
92
|
}
|
|
67
|
-
|
|
93
|
+
|
|
94
|
+
(candidate as HTMLElement)?.click();
|
|
68
95
|
return !!candidate;
|
|
69
|
-
};
|
|
70
|
-
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const closeAllMenus = () => {
|
|
71
99
|
openValues = [];
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const onClickOutside = (event: MouseEvent) => {
|
|
103
|
+
let element: HTMLElement | null = event.target as HTMLElement;
|
|
75
104
|
while (element) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
105
|
+
if (element.getAttribute('data-root-value') === rootValue) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
element = element.parentElement;
|
|
80
109
|
}
|
|
81
110
|
closeAllMenus?.();
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
let menuBarContext: MenuBarContext = { openPreviousMenuBarItem, openNextMenuBarItem };
|
|
114
|
+
|
|
115
|
+
setContext<MenuBarContext>(MENU_BAR_CONTEXT_KEY, menuBarContext);
|
|
116
|
+
|
|
117
|
+
let menuItemContext: MenuItemContext = {
|
|
86
118
|
isMenuBarItem: true,
|
|
87
119
|
depth: 0,
|
|
88
120
|
get openValues() {
|
|
89
|
-
|
|
121
|
+
return openValues;
|
|
90
122
|
},
|
|
91
|
-
set openValues(value) {
|
|
92
|
-
|
|
123
|
+
set openValues(value: string[]) {
|
|
124
|
+
openValues = value;
|
|
93
125
|
},
|
|
94
126
|
rootValue,
|
|
95
127
|
onClose,
|
|
96
128
|
onOpen,
|
|
97
129
|
onSelect
|
|
98
|
-
};
|
|
99
|
-
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
setContext<MenuItemContext>(MENU_ITEM_CONTEXT_KEY, menuItemContext);
|
|
100
133
|
</script>
|
|
101
134
|
|
|
102
135
|
<div
|
|
103
136
|
bind:this={menuBarRef}
|
|
104
|
-
class={['sterling-menu-bar', _class]
|
|
137
|
+
class={['sterling-menu-bar', _class]}
|
|
105
138
|
role="menubar"
|
|
106
139
|
tabindex="-1"
|
|
107
140
|
{...rest}
|
|
108
141
|
use:clickOutside={{ onclickoutside: onClickOutside }}
|
|
109
142
|
>
|
|
110
|
-
{
|
|
111
|
-
{@render children()}
|
|
112
|
-
{/if}
|
|
143
|
+
{@render children?.()}
|
|
113
144
|
</div>
|
package/dist/MenuBar.svelte.d.ts
CHANGED
package/dist/MenuButton.svelte
CHANGED
|
@@ -1,92 +1,132 @@
|
|
|
1
1
|
<svelte:options runes={true} />
|
|
2
2
|
|
|
3
|
-
<script lang="ts">
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
3
|
+
<script lang="ts">
|
|
4
|
+
import { setContext, tick, type Snippet } from 'svelte';
|
|
5
|
+
import type { HTMLButtonAttributes } from 'svelte/elements';
|
|
6
|
+
import { clickOutside } from './actions/clickOutside';
|
|
7
|
+
import Button from './Button.svelte';
|
|
8
|
+
import Menu from './Menu.svelte';
|
|
9
|
+
import { MENU_ITEM_CONTEXT_KEY } from './MenuItem.constants';
|
|
10
|
+
import type { MenuItemContext } from './MenuItem.types';
|
|
11
|
+
import Popover from './Popover.svelte';
|
|
12
|
+
import type { PopoverPlacement } from './Popover.types';
|
|
13
|
+
|
|
14
|
+
const uuid = $props.id();
|
|
15
|
+
|
|
16
|
+
type Props = HTMLButtonAttributes & {
|
|
17
|
+
items: Snippet;
|
|
18
|
+
menuClass?: string;
|
|
19
|
+
onOpen?: (value: string) => void;
|
|
20
|
+
onClose?: (value: string) => void;
|
|
21
|
+
onSelect?: (value: string) => void;
|
|
22
|
+
open?: boolean | null | undefined;
|
|
23
|
+
popoverPlacement?: PopoverPlacement;
|
|
24
|
+
value?: string;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
let {
|
|
28
|
+
children,
|
|
29
|
+
class: _class,
|
|
30
|
+
items,
|
|
31
|
+
menuClass,
|
|
32
|
+
open = $bindable(false),
|
|
33
|
+
onClose,
|
|
34
|
+
onOpen,
|
|
35
|
+
onSelect,
|
|
36
|
+
popoverPlacement = 'bottom-start',
|
|
37
|
+
value,
|
|
38
|
+
...rest
|
|
39
|
+
}: Props = $props();
|
|
40
|
+
|
|
41
|
+
const instanceId = `MenuButton-${uuid}`;
|
|
42
|
+
|
|
43
|
+
let buttonRef: Button;
|
|
44
|
+
let openValues: string[] = $state([]);
|
|
45
|
+
let menuRef: Menu;
|
|
46
|
+
let menuId = $derived(`${value}-menu-${instanceId}`);
|
|
47
|
+
let prevOpen = $state(open);
|
|
48
|
+
let reference: HTMLDivElement | undefined = $state();
|
|
49
|
+
|
|
50
|
+
export const click = () => {
|
|
19
51
|
buttonRef?.click();
|
|
20
|
-
};
|
|
21
|
-
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const blur = () => {
|
|
22
55
|
buttonRef?.blur();
|
|
23
|
-
};
|
|
24
|
-
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const focus = (options?: FocusOptions) => {
|
|
25
59
|
buttonRef?.focus(options);
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// update open based on openValues
|
|
63
|
+
$effect(() => {
|
|
29
64
|
open = openValues.length > 0;
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// update openValues based on open
|
|
68
|
+
$effect(() => {
|
|
33
69
|
if (open) {
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
openValues = openValues.length === 0 ? openValues : [];
|
|
70
|
+
openValues = openValues.length > 0 ? openValues : ['menu-button'];
|
|
71
|
+
} else {
|
|
72
|
+
openValues = openValues.length === 0 ? openValues : [];
|
|
38
73
|
}
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// focus when closing
|
|
77
|
+
$effect(() => {
|
|
42
78
|
if (!open && open !== prevOpen) {
|
|
43
|
-
|
|
79
|
+
focus();
|
|
44
80
|
}
|
|
45
81
|
prevOpen = open;
|
|
46
|
-
});
|
|
47
|
-
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const onClick = async () => {
|
|
48
85
|
if (!open) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
86
|
+
openValues = ['menu-button'];
|
|
87
|
+
open = true;
|
|
88
|
+
await tick();
|
|
89
|
+
menuRef?.focusFirstMenuItem();
|
|
90
|
+
} else {
|
|
91
|
+
open = false;
|
|
92
|
+
openValues = [];
|
|
53
93
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
};
|
|
59
|
-
const closeAllMenus = () => {
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const closeAllMenus = () => {
|
|
60
97
|
openValues = [];
|
|
61
98
|
open = false;
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const onClickOutside = (event: MouseEvent) => {
|
|
102
|
+
let element: HTMLElement | null = event.target as HTMLElement;
|
|
65
103
|
while (element) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
104
|
+
if (element.getAttribute('data-root-value') === value) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
element = element.parentElement;
|
|
70
108
|
}
|
|
71
109
|
closeAllMenus?.();
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// ----- Context ----- //
|
|
113
|
+
|
|
114
|
+
setContext<MenuItemContext>(MENU_ITEM_CONTEXT_KEY, {
|
|
75
115
|
depth: 1,
|
|
76
116
|
get openValues() {
|
|
77
|
-
|
|
117
|
+
return openValues;
|
|
78
118
|
},
|
|
79
|
-
set openValues(value) {
|
|
80
|
-
|
|
119
|
+
set openValues(value: string[]) {
|
|
120
|
+
openValues = value;
|
|
81
121
|
},
|
|
82
122
|
rootValue: value,
|
|
83
123
|
closeContainingMenu: () => {
|
|
84
|
-
|
|
124
|
+
open = false;
|
|
85
125
|
},
|
|
86
126
|
onOpen,
|
|
87
127
|
onClose,
|
|
88
128
|
onSelect
|
|
89
|
-
});
|
|
129
|
+
});
|
|
90
130
|
</script>
|
|
91
131
|
|
|
92
132
|
<Button
|
|
@@ -95,7 +135,7 @@ setContext(MENU_ITEM_CONTEXT_KEY, {
|
|
|
95
135
|
aria-expanded={!!open}
|
|
96
136
|
aria-haspopup={!!children}
|
|
97
137
|
aria-owns={menuId}
|
|
98
|
-
class={['sterling-menu-button', _class]
|
|
138
|
+
class={['sterling-menu-button', _class]}
|
|
99
139
|
data-value={value}
|
|
100
140
|
data-root-value={value}
|
|
101
141
|
{...rest}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { HTMLButtonAttributes } from 'svelte/elements';
|
|
2
1
|
import { type Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLButtonAttributes } from 'svelte/elements';
|
|
3
3
|
import type { PopoverPlacement } from './Popover.types';
|
|
4
4
|
type Props = HTMLButtonAttributes & {
|
|
5
5
|
items: Snippet;
|