@geoffcox/sterling-svelte 0.0.25 → 0.0.26
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/Dropdown.svelte +9 -0
- package/Field.svelte +3 -15
- package/List.svelte +3 -14
- package/Menu.svelte +86 -115
- package/Menu.svelte.d.ts +8 -2
- package/MenuBar.svelte +76 -31
- package/MenuBar.types.d.ts +2 -2
- package/MenuButton.svelte +50 -28
- package/MenuItem.constants.d.ts +1 -0
- package/MenuItem.constants.js +1 -0
- package/MenuItem.svelte +169 -128
- package/MenuItem.svelte.d.ts +6 -3
- package/MenuItem.types.d.ts +14 -5
- package/MenuItem.utils.d.ts +2 -0
- package/MenuItem.utils.js +16 -0
- package/MenuItemDisplay.svelte +2 -1
- package/MenuItemDisplay.svelte.d.ts +1 -0
- package/Popover.svelte +3 -14
- package/Select.svelte +25 -13
- package/Tab.svelte +1 -1
- package/TabList.svelte +2 -18
- package/TabList.types.d.ts +0 -1
- package/Tree.svelte +3 -14
- package/TreeItem.svelte +7 -1
- package/index.d.ts +2 -2
- package/index.js +1 -1
- package/package.json +3 -1
- package/stores/prefersReducedMotion.d.ts +1 -0
- package/stores/prefersReducedMotion.js +10 -0
- package/stores/usingKeyboard.d.ts +1 -0
- package/stores/usingKeyboard.js +13 -0
package/Dropdown.svelte
CHANGED
|
@@ -241,7 +241,16 @@ const onClickOutside = (event) => {
|
|
|
241
241
|
}
|
|
242
242
|
|
|
243
243
|
.popup-content {
|
|
244
|
+
background-color: var(--stsv-Common__background-color);
|
|
245
|
+
border-color: var(--stsv-Common__border-color);
|
|
246
|
+
border-radius: var(--stsv-Common__border-radius);
|
|
247
|
+
border-style: var(--stsv-Common__border-style);
|
|
248
|
+
border-width: var(--stsv-Common__border-width);
|
|
244
249
|
padding: 0.25em;
|
|
250
|
+
display: grid;
|
|
251
|
+
grid-template-columns: 1fr;
|
|
252
|
+
grid-template-rows: 1fr;
|
|
253
|
+
overflow: hidden;
|
|
245
254
|
}
|
|
246
255
|
|
|
247
256
|
@media (prefers-reduced-motion) {
|
package/Field.svelte
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
<script>import
|
|
2
|
-
import {
|
|
3
|
-
import Tooltip from "./Tooltip.svelte";
|
|
1
|
+
<script>import Tooltip from "./Tooltip.svelte";
|
|
2
|
+
import { usingKeyboard } from "./stores/usingKeyboard";
|
|
4
3
|
export let forwardClick = false;
|
|
5
4
|
let htmlFor = void 0;
|
|
6
5
|
export { htmlFor as for };
|
|
@@ -62,11 +61,6 @@ $: {
|
|
|
62
61
|
});
|
|
63
62
|
}
|
|
64
63
|
}
|
|
65
|
-
let keyborg = createKeyborg(window);
|
|
66
|
-
let usingKeyboard = keyborg.isNavigatingWithKeyboard();
|
|
67
|
-
const keyborgHandler = (value) => {
|
|
68
|
-
usingKeyboard = value;
|
|
69
|
-
};
|
|
70
64
|
export const click = () => {
|
|
71
65
|
fieldRef?.click();
|
|
72
66
|
};
|
|
@@ -76,12 +70,6 @@ export const blur = () => {
|
|
|
76
70
|
export const focus = (options) => {
|
|
77
71
|
fieldRef?.focus(options);
|
|
78
72
|
};
|
|
79
|
-
onMount(() => {
|
|
80
|
-
keyborg.subscribe(keyborgHandler);
|
|
81
|
-
return () => {
|
|
82
|
-
keyborg.unsubscribe(keyborgHandler);
|
|
83
|
-
};
|
|
84
|
-
});
|
|
85
73
|
const onClick = () => {
|
|
86
74
|
if (forwardClick) {
|
|
87
75
|
targetRef?.click();
|
|
@@ -94,7 +82,7 @@ const onClick = () => {
|
|
|
94
82
|
aria-disabled={targetDisabled}
|
|
95
83
|
class="sterling-field"
|
|
96
84
|
class:disabled={targetDisabled}
|
|
97
|
-
class:using-keyboard={usingKeyboard}
|
|
85
|
+
class:using-keyboard={$usingKeyboard}
|
|
98
86
|
for={htmlFor}
|
|
99
87
|
on:blur
|
|
100
88
|
on:click
|
package/List.svelte
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
<script>import {
|
|
2
|
-
import { createEventDispatcher, onMount, setContext } from "svelte";
|
|
1
|
+
<script>import { createEventDispatcher, setContext } from "svelte";
|
|
3
2
|
import { writable } from "svelte/store";
|
|
4
3
|
import { LIST_CONTEXT_KEY } from "./List.constants";
|
|
4
|
+
import { usingKeyboard } from "./stores/usingKeyboard";
|
|
5
5
|
export let composed = false;
|
|
6
6
|
export let disabled = false;
|
|
7
7
|
export let horizontal = false;
|
|
@@ -43,11 +43,6 @@ export const scrollToSelectedItem = () => {
|
|
|
43
43
|
const element = getSelectedItemElement();
|
|
44
44
|
element?.scrollIntoView({ block: "nearest", inline: "nearest" });
|
|
45
45
|
};
|
|
46
|
-
let keyborg = createKeyborg(window);
|
|
47
|
-
let usingKeyboard = keyborg.isNavigatingWithKeyboard();
|
|
48
|
-
const keyborgHandler = (value) => {
|
|
49
|
-
usingKeyboard = value;
|
|
50
|
-
};
|
|
51
46
|
const isElementListItem = (candidate) => {
|
|
52
47
|
return candidate && candidate.getAttribute("data-value") !== null && candidate.getAttribute("data-value") !== void 0 && candidate.getAttribute("role") === "listitem";
|
|
53
48
|
};
|
|
@@ -113,12 +108,6 @@ export const selectLastItem = () => {
|
|
|
113
108
|
}
|
|
114
109
|
return false;
|
|
115
110
|
};
|
|
116
|
-
onMount(() => {
|
|
117
|
-
keyborg.subscribe(keyborgHandler);
|
|
118
|
-
return () => {
|
|
119
|
-
keyborg.unsubscribe(keyborgHandler);
|
|
120
|
-
};
|
|
121
|
-
});
|
|
122
111
|
const onClick = (event) => {
|
|
123
112
|
if (!disabled) {
|
|
124
113
|
let candidate = event.target;
|
|
@@ -197,7 +186,7 @@ A list of items where a single item can be selected.
|
|
|
197
186
|
class:composed
|
|
198
187
|
class:disabled
|
|
199
188
|
class:horizontal
|
|
200
|
-
class:using-keyboard={usingKeyboard}
|
|
189
|
+
class:using-keyboard={$usingKeyboard}
|
|
201
190
|
role="list"
|
|
202
191
|
tabindex={0}
|
|
203
192
|
on:blur
|
package/Menu.svelte
CHANGED
|
@@ -1,155 +1,126 @@
|
|
|
1
|
-
<script>import { getContext
|
|
2
|
-
import {
|
|
3
|
-
import { portal } from "./actions/portal";
|
|
1
|
+
<script>import { getContext } from "svelte";
|
|
2
|
+
import { slide } from "svelte/transition";
|
|
4
3
|
import { MENU_ITEM_CONTEXT_KEY } from "./MenuItem.constants";
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
import { isElementEnabledMenuItem, isElementMenuItem } from "./MenuItem.utils";
|
|
5
|
+
import { prefersReducedMotion } from "./stores/prefersReducedMotion";
|
|
7
6
|
let menuRef;
|
|
8
|
-
let
|
|
9
|
-
const
|
|
7
|
+
let menuItemsRef;
|
|
8
|
+
const slidNoOp = (node, params) => {
|
|
9
|
+
return { delay: 0, duration: 0 };
|
|
10
|
+
};
|
|
11
|
+
$:
|
|
12
|
+
slideMotion = !$prefersReducedMotion ? slide : slidNoOp;
|
|
13
|
+
const { rootValue } = getContext(MENU_ITEM_CONTEXT_KEY) || {};
|
|
10
14
|
export const blur = () => {
|
|
11
15
|
menuRef?.blur();
|
|
12
16
|
};
|
|
13
17
|
export const focus = (options) => {
|
|
14
18
|
menuRef?.focus(options);
|
|
15
19
|
};
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
const isElementInThisMenu = (candidate) => {
|
|
21
|
+
return candidate && candidate.closest('[role="menu"]') === menuRef;
|
|
22
|
+
};
|
|
23
|
+
export const focusFirstMenuItem = () => {
|
|
24
|
+
let candidate = menuItemsRef?.firstElementChild;
|
|
25
|
+
while (candidate && !isElementEnabledMenuItem(candidate)) {
|
|
26
|
+
candidate = candidate.nextElementSibling;
|
|
23
27
|
}
|
|
24
|
-
|
|
28
|
+
candidate?.focus({ preventScroll: true });
|
|
29
|
+
return !!candidate;
|
|
25
30
|
};
|
|
26
|
-
const
|
|
27
|
-
let
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
flip(),
|
|
35
|
-
shift({ padding: 0, mainAxis: true, crossAxis: true })
|
|
36
|
-
];
|
|
37
|
-
const computeMenuPosition = async () => {
|
|
38
|
-
if (reference && menuRef) {
|
|
39
|
-
menuPosition = await computePosition(reference, menuRef, {
|
|
40
|
-
placement: menuPlacement,
|
|
41
|
-
middleware
|
|
42
|
-
});
|
|
43
|
-
} else {
|
|
44
|
-
menuPosition = { x: 0, y: 0 };
|
|
31
|
+
export const focusPreviousMenuItem = () => {
|
|
32
|
+
let candidate = document.activeElement;
|
|
33
|
+
if (candidate && isElementMenuItem(candidate) && isElementInThisMenu(candidate)) {
|
|
34
|
+
candidate = menuItemsRef?.previousElementSibling;
|
|
35
|
+
while (candidate && !isElementEnabledMenuItem(candidate)) {
|
|
36
|
+
candidate = candidate.previousElementSibling;
|
|
37
|
+
}
|
|
38
|
+
candidate?.focus();
|
|
45
39
|
}
|
|
40
|
+
return !!candidate;
|
|
46
41
|
};
|
|
47
|
-
|
|
42
|
+
export const focusNextMenuItem = () => {
|
|
43
|
+
let candidate = document.activeElement;
|
|
44
|
+
if (candidate && isElementMenuItem(candidate) && isElementInThisMenu(candidate)) {
|
|
45
|
+
candidate = menuItemsRef?.nextElementSibling;
|
|
46
|
+
while (candidate && !isElementEnabledMenuItem(candidate)) {
|
|
47
|
+
candidate = candidate.nextElementSibling;
|
|
48
|
+
}
|
|
49
|
+
candidate?.focus();
|
|
50
|
+
}
|
|
51
|
+
return !!candidate;
|
|
48
52
|
};
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
+
export const focusLastMenuItem = () => {
|
|
54
|
+
let candidate = menuItemsRef?.lastElementChild;
|
|
55
|
+
while (candidate && !isElementEnabledMenuItem(candidate)) {
|
|
56
|
+
candidate = candidate.previousElementSibling;
|
|
53
57
|
}
|
|
58
|
+
candidate?.focus({ preventScroll: true });
|
|
59
|
+
return !!candidate;
|
|
54
60
|
};
|
|
55
|
-
$:
|
|
56
|
-
menuRef, reference, autoUpdateMenuPosition();
|
|
57
|
-
$:
|
|
58
|
-
open, bodyHeight, computeMenuPosition();
|
|
59
|
-
onMount(() => {
|
|
60
|
-
resizeObserver.observe(document.body);
|
|
61
|
-
return () => {
|
|
62
|
-
resizeObserver.unobserve(document.body);
|
|
63
|
-
};
|
|
64
|
-
});
|
|
65
61
|
</script>
|
|
66
62
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
style="left:{menuPosition.x}px; top:{menuPosition.y}px"
|
|
107
|
-
>
|
|
108
|
-
<div class="sterling-menu-content">
|
|
109
|
-
<slot />
|
|
110
|
-
</div>
|
|
111
|
-
</div>
|
|
63
|
+
<div
|
|
64
|
+
bind:this={menuRef}
|
|
65
|
+
class="sterling-menu"
|
|
66
|
+
role="menu"
|
|
67
|
+
class:open
|
|
68
|
+
data-root-value={rootValue}
|
|
69
|
+
in:slideMotion={{ duration: 300 }}
|
|
70
|
+
out:slideMotion={{ duration: 100 }}
|
|
71
|
+
on:blur
|
|
72
|
+
on:click
|
|
73
|
+
on:copy
|
|
74
|
+
on:cut
|
|
75
|
+
on:dblclick
|
|
76
|
+
on:dragend
|
|
77
|
+
on:dragenter
|
|
78
|
+
on:dragleave
|
|
79
|
+
on:dragover
|
|
80
|
+
on:dragstart
|
|
81
|
+
on:drop
|
|
82
|
+
on:focus
|
|
83
|
+
on:focusin
|
|
84
|
+
on:focusout
|
|
85
|
+
on:keydown
|
|
86
|
+
on:keypress
|
|
87
|
+
on:keyup
|
|
88
|
+
on:mousedown
|
|
89
|
+
on:mouseenter
|
|
90
|
+
on:mouseleave
|
|
91
|
+
on:mousemove
|
|
92
|
+
on:mouseover
|
|
93
|
+
on:mouseout
|
|
94
|
+
on:mouseup
|
|
95
|
+
on:scroll
|
|
96
|
+
on:wheel
|
|
97
|
+
on:paste
|
|
98
|
+
{...$$restProps}
|
|
99
|
+
>
|
|
100
|
+
<div bind:this={menuItemsRef} class="menu-items">
|
|
101
|
+
<slot />
|
|
112
102
|
</div>
|
|
113
|
-
|
|
103
|
+
</div>
|
|
114
104
|
|
|
115
105
|
<style>
|
|
116
|
-
.sterling-menu-portal {
|
|
117
|
-
position: relative;
|
|
118
|
-
overflow: visible;
|
|
119
|
-
background: rgba(255, 0, 0, 0.1);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
106
|
.sterling-menu {
|
|
123
107
|
background-color: var(--stsv-Common__background-color);
|
|
124
108
|
border-color: var(--stsv-Common__border-color);
|
|
125
109
|
border-radius: var(--stsv-Common__border-radius);
|
|
126
110
|
border-style: var(--stsv-Common__border-style);
|
|
127
111
|
border-width: var(--stsv-Common__border-width);
|
|
128
|
-
box-shadow: rgba(0, 0, 0, 0.4) 2px 2px 4px -1px;
|
|
129
112
|
box-sizing: border-box;
|
|
130
113
|
display: grid;
|
|
131
114
|
grid-template-columns: 1fr;
|
|
132
115
|
grid-template-rows: 1fr;
|
|
133
116
|
height: fit-content;
|
|
134
|
-
left: 0;
|
|
135
|
-
max-height: calc(50vh);
|
|
136
|
-
overflow: auto;
|
|
137
|
-
overscroll-behavior: contain;
|
|
138
117
|
padding: 0.25em;
|
|
139
|
-
position: absolute;
|
|
140
|
-
top: 0;
|
|
141
|
-
width: max-content;
|
|
142
|
-
z-index: 1;
|
|
143
118
|
}
|
|
144
119
|
|
|
145
|
-
.
|
|
120
|
+
.menu-items {
|
|
146
121
|
display: grid;
|
|
147
122
|
grid-template-columns: 1fr;
|
|
148
123
|
grid-template-rows: auto;
|
|
149
124
|
row-gap: calc(2 * var(--stsv-Common__outline-width));
|
|
150
125
|
}
|
|
151
|
-
|
|
152
|
-
.sterling-menu.open {
|
|
153
|
-
display: grid;
|
|
154
|
-
}
|
|
155
126
|
</style>
|
package/Menu.svelte.d.ts
CHANGED
|
@@ -2,10 +2,12 @@ import { SvelteComponentTyped } from "svelte";
|
|
|
2
2
|
declare const __propDef: {
|
|
3
3
|
props: {
|
|
4
4
|
[x: string]: any;
|
|
5
|
-
reference: HTMLElement;
|
|
6
|
-
open?: boolean | undefined;
|
|
7
5
|
blur?: (() => void) | undefined;
|
|
8
6
|
focus?: ((options?: FocusOptions) => void) | undefined;
|
|
7
|
+
focusFirstMenuItem?: (() => boolean) | undefined;
|
|
8
|
+
focusPreviousMenuItem?: (() => boolean) | undefined;
|
|
9
|
+
focusNextMenuItem?: (() => boolean) | undefined;
|
|
10
|
+
focusLastMenuItem?: (() => boolean) | undefined;
|
|
9
11
|
};
|
|
10
12
|
events: {
|
|
11
13
|
blur: FocusEvent;
|
|
@@ -48,5 +50,9 @@ export type MenuSlots = typeof __propDef.slots;
|
|
|
48
50
|
export default class Menu extends SvelteComponentTyped<MenuProps, MenuEvents, MenuSlots> {
|
|
49
51
|
get blur(): () => void;
|
|
50
52
|
get focus(): (options?: FocusOptions | undefined) => void;
|
|
53
|
+
get focusFirstMenuItem(): () => boolean;
|
|
54
|
+
get focusPreviousMenuItem(): () => boolean;
|
|
55
|
+
get focusNextMenuItem(): () => boolean;
|
|
56
|
+
get focusLastMenuItem(): () => boolean;
|
|
51
57
|
}
|
|
52
58
|
export {};
|
package/MenuBar.svelte
CHANGED
|
@@ -2,7 +2,25 @@
|
|
|
2
2
|
import { MENU_BAR_CONTEXT_KEY } from "./MenuBar.constants";
|
|
3
3
|
import { MENU_ITEM_CONTEXT_KEY } from "./MenuItem.constants";
|
|
4
4
|
import { writable } from "svelte/store";
|
|
5
|
+
import { isElementEnabledMenuItem } from "./MenuItem.utils";
|
|
6
|
+
import { idGenerator } from "./idGenerator";
|
|
7
|
+
import { clickOutside } from "./actions/clickOutside";
|
|
8
|
+
const openValues = writable([]);
|
|
9
|
+
const rootValue = idGenerator.nextId("MenuBar");
|
|
5
10
|
let menuBarRef;
|
|
11
|
+
let prevOpenValue = void 0;
|
|
12
|
+
$: {
|
|
13
|
+
if ($openValues.length > 0) {
|
|
14
|
+
prevOpenValue = $openValues[0];
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
$: {
|
|
18
|
+
if ($openValues.length === 0 && prevOpenValue !== void 0) {
|
|
19
|
+
const candidate = menuBarRef.querySelector(`[data-value="${prevOpenValue}"]`);
|
|
20
|
+
candidate?.focus();
|
|
21
|
+
prevOpenValue = void 0;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
6
24
|
const dispatch = createEventDispatcher();
|
|
7
25
|
const raiseClose = (value) => {
|
|
8
26
|
dispatch("close", { value });
|
|
@@ -19,48 +37,73 @@ export const blur = () => {
|
|
|
19
37
|
export const focus = (options) => {
|
|
20
38
|
menuBarRef?.focus(options);
|
|
21
39
|
};
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const focusIndex = index === 0 ? $children.length - 1 : index - 1;
|
|
27
|
-
$children[focusIndex].focus();
|
|
28
|
-
$children[focusIndex].open();
|
|
40
|
+
const getOpenMenuBarItem = () => {
|
|
41
|
+
const value = $openValues[0];
|
|
42
|
+
if (value) {
|
|
43
|
+
return menuBarRef.querySelector(`[data-value="${value}"]`);
|
|
29
44
|
}
|
|
45
|
+
return null;
|
|
30
46
|
};
|
|
31
|
-
const
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
47
|
+
const openPreviousMenuBarItem = () => {
|
|
48
|
+
const openItem = getOpenMenuBarItem() || menuBarRef.firstElementChild;
|
|
49
|
+
let candidate = openItem?.previousElementSibling || menuBarRef.lastElementChild;
|
|
50
|
+
while (candidate && !isElementEnabledMenuItem(candidate)) {
|
|
51
|
+
candidate = candidate.previousElementSibling || menuBarRef.lastElementChild;
|
|
52
|
+
if (candidate === openItem) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (!candidate) {
|
|
57
|
+
candidate = menuBarRef.lastElementChild;
|
|
58
|
+
candidate = candidate && isElementEnabledMenuItem(candidate) ? candidate : null;
|
|
37
59
|
}
|
|
60
|
+
candidate?.click();
|
|
61
|
+
return !!candidate;
|
|
38
62
|
};
|
|
39
|
-
const
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
63
|
+
const openNextMenuBarItem = () => {
|
|
64
|
+
const openItem = getOpenMenuBarItem() || menuBarRef.lastElementChild;
|
|
65
|
+
let candidate = openItem?.nextElementSibling || menuBarRef.firstElementChild;
|
|
66
|
+
while (candidate && !isElementEnabledMenuItem(candidate)) {
|
|
67
|
+
candidate = candidate.nextElementSibling || menuBarRef.firstElementChild;
|
|
68
|
+
if (candidate === openItem) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
43
71
|
}
|
|
72
|
+
if (!candidate) {
|
|
73
|
+
candidate = menuBarRef.firstElementChild;
|
|
74
|
+
candidate = candidate && isElementEnabledMenuItem(candidate) ? candidate : null;
|
|
75
|
+
}
|
|
76
|
+
candidate?.click();
|
|
77
|
+
return !!candidate;
|
|
78
|
+
};
|
|
79
|
+
const closeAllMenus = () => {
|
|
80
|
+
openValues.set([]);
|
|
44
81
|
};
|
|
82
|
+
const onClickOutside = (event) => {
|
|
83
|
+
const {
|
|
84
|
+
detail: { mouseEvent }
|
|
85
|
+
} = event;
|
|
86
|
+
let element = mouseEvent.target;
|
|
87
|
+
while (element) {
|
|
88
|
+
if (element.getAttribute("data-root-value") === rootValue) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
element = element.parentElement;
|
|
92
|
+
}
|
|
93
|
+
closeAllMenus?.();
|
|
94
|
+
};
|
|
95
|
+
setContext(MENU_BAR_CONTEXT_KEY, {
|
|
96
|
+
openPreviousMenuBarItem,
|
|
97
|
+
openNextMenuBarItem
|
|
98
|
+
});
|
|
45
99
|
setContext(MENU_ITEM_CONTEXT_KEY, {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
unregister: (menuItem) => {
|
|
50
|
-
children.set($children.filter((x) => x.value !== menuItem.value));
|
|
51
|
-
},
|
|
52
|
-
closeMenu: (recursive) => {
|
|
53
|
-
},
|
|
54
|
-
focusPrevious: openPreviousChild,
|
|
55
|
-
focusNext: openNextChild,
|
|
100
|
+
isMenuBarItem: true,
|
|
101
|
+
openValues,
|
|
102
|
+
rootValue,
|
|
56
103
|
onClose: raiseClose,
|
|
57
104
|
onOpen: raiseOpen,
|
|
58
105
|
onSelect: raiseSelect
|
|
59
106
|
});
|
|
60
|
-
setContext(MENU_BAR_CONTEXT_KEY, {
|
|
61
|
-
openPreviousMenu: openPreviousChild,
|
|
62
|
-
openNextMenu: openNextChild
|
|
63
|
-
});
|
|
64
107
|
</script>
|
|
65
108
|
|
|
66
109
|
<div
|
|
@@ -95,6 +138,8 @@ setContext(MENU_BAR_CONTEXT_KEY, {
|
|
|
95
138
|
on:wheel
|
|
96
139
|
on:paste
|
|
97
140
|
{...$$restProps}
|
|
141
|
+
use:clickOutside
|
|
142
|
+
on:click_outside={onClickOutside}
|
|
98
143
|
>
|
|
99
144
|
<slot />
|
|
100
145
|
</div>
|
package/MenuBar.types.d.ts
CHANGED
package/MenuButton.svelte
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
<script>import { createEventDispatcher,
|
|
1
|
+
<script>import { createEventDispatcher, setContext, tick } from "svelte";
|
|
2
2
|
import { writable } from "svelte/store";
|
|
3
3
|
import Button from "./Button.svelte";
|
|
4
4
|
import Menu from "./Menu.svelte";
|
|
5
5
|
import { MENU_ITEM_CONTEXT_KEY } from "./MenuItem.constants";
|
|
6
|
-
import { focusFirstChild, focusNextChild, focusPreviousChild } from "./MenuItem.utils";
|
|
7
6
|
import { idGenerator } from "./idGenerator";
|
|
7
|
+
import Popover from "./Popover.svelte";
|
|
8
|
+
import { clickOutside } from "./actions/clickOutside";
|
|
9
|
+
import { usingKeyboard } from "./stores/usingKeyboard";
|
|
8
10
|
export let open = false;
|
|
9
11
|
export let shape = "rounded";
|
|
10
12
|
export let value;
|
|
@@ -12,12 +14,16 @@ export let variant = "regular";
|
|
|
12
14
|
const instanceId = idGenerator.nextId("MenuButton");
|
|
13
15
|
let buttonRef;
|
|
14
16
|
let reference;
|
|
17
|
+
let menuRef;
|
|
15
18
|
let prevOpen = open;
|
|
16
19
|
$:
|
|
17
20
|
menuId = `${value}-menu-${instanceId}`;
|
|
18
21
|
$:
|
|
19
22
|
hasChildren = $$slots.items;
|
|
20
|
-
const
|
|
23
|
+
const openValues = writable([]);
|
|
24
|
+
$: {
|
|
25
|
+
open = $openValues.length > 0;
|
|
26
|
+
}
|
|
21
27
|
const dispatch = createEventDispatcher();
|
|
22
28
|
const raiseClose = (value2) => {
|
|
23
29
|
dispatch("close", { value: value2 });
|
|
@@ -28,10 +34,25 @@ const raiseOpen = (value2) => {
|
|
|
28
34
|
const raiseSelect = (value2) => {
|
|
29
35
|
dispatch("select", { value: value2 });
|
|
30
36
|
};
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
37
|
+
export const click = () => {
|
|
38
|
+
buttonRef?.click();
|
|
39
|
+
};
|
|
40
|
+
export const blur = () => {
|
|
41
|
+
buttonRef?.blur();
|
|
42
|
+
};
|
|
43
|
+
export const focus = (options) => {
|
|
44
|
+
buttonRef?.focus(options);
|
|
45
|
+
};
|
|
46
|
+
const onClick = async () => {
|
|
47
|
+
if (!open) {
|
|
48
|
+
openValues.set(["menu-button"]);
|
|
49
|
+
if ($usingKeyboard) {
|
|
50
|
+
await tick();
|
|
51
|
+
menuRef?.focusFirstMenuItem();
|
|
52
|
+
}
|
|
53
|
+
} else {
|
|
54
|
+
open = false;
|
|
55
|
+
openValues.set([]);
|
|
35
56
|
}
|
|
36
57
|
};
|
|
37
58
|
$: {
|
|
@@ -40,29 +61,28 @@ $: {
|
|
|
40
61
|
}
|
|
41
62
|
prevOpen = open;
|
|
42
63
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
};
|
|
46
|
-
export const blur = () => {
|
|
47
|
-
buttonRef?.blur();
|
|
64
|
+
const closeAllMenus = () => {
|
|
65
|
+
openValues.set([]);
|
|
48
66
|
};
|
|
49
|
-
|
|
50
|
-
|
|
67
|
+
const onClickOutside = (event) => {
|
|
68
|
+
const {
|
|
69
|
+
detail: { mouseEvent }
|
|
70
|
+
} = event;
|
|
71
|
+
let element = mouseEvent.target;
|
|
72
|
+
while (element) {
|
|
73
|
+
if (element.getAttribute("data-root-value") === value) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
element = element.parentElement;
|
|
77
|
+
}
|
|
78
|
+
closeAllMenus?.();
|
|
51
79
|
};
|
|
52
80
|
setContext(MENU_ITEM_CONTEXT_KEY, {
|
|
81
|
+
openValues,
|
|
53
82
|
rootValue: value,
|
|
54
|
-
|
|
55
|
-
register: (menuItem) => {
|
|
56
|
-
children.set([...$children, menuItem]);
|
|
57
|
-
},
|
|
58
|
-
unregister: (menuItem) => {
|
|
59
|
-
children.set($children.filter((x) => x.value !== menuItem.value));
|
|
60
|
-
},
|
|
61
|
-
closeMenu: (recursive) => {
|
|
83
|
+
closeContainingMenu: () => {
|
|
62
84
|
open = false;
|
|
63
85
|
},
|
|
64
|
-
focusPrevious: (currentValue) => focusPreviousChild($children, currentValue),
|
|
65
|
-
focusNext: (currentValue) => focusNextChild($children, currentValue),
|
|
66
86
|
onOpen: raiseOpen,
|
|
67
87
|
onClose: raiseClose,
|
|
68
88
|
onSelect: raiseSelect
|
|
@@ -117,12 +137,14 @@ setContext(MENU_ITEM_CONTEXT_KEY, {
|
|
|
117
137
|
on:wheel
|
|
118
138
|
{...$$restProps}
|
|
119
139
|
>
|
|
120
|
-
<div class="reference" bind:this={reference}>
|
|
140
|
+
<div class="reference" bind:this={reference} use:clickOutside on:click_outside={onClickOutside}>
|
|
121
141
|
<slot {shape} {variant} />
|
|
122
142
|
</div>
|
|
123
|
-
<
|
|
124
|
-
<
|
|
125
|
-
|
|
143
|
+
<Popover {reference} placement="bottom-start" {open}>
|
|
144
|
+
<Menu bind:this={menuRef} id={menuId} {reference} {open}>
|
|
145
|
+
<slot name="items" />
|
|
146
|
+
</Menu>
|
|
147
|
+
</Popover>
|
|
126
148
|
</Button>
|
|
127
149
|
|
|
128
150
|
<style>
|
package/MenuItem.constants.d.ts
CHANGED
package/MenuItem.constants.js
CHANGED