@geoffcox/sterling-svelte 0.0.24 → 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 +26 -39
- package/Field.svelte +3 -15
- package/List.svelte +3 -15
- 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 +124 -0
- package/Popover.svelte.d.ts +51 -0
- package/Progress.svelte +13 -2
- package/Progress.svelte.d.ts +1 -0
- package/Radio.svelte +39 -22
- package/Select.svelte +17 -48
- package/Switch.svelte +7 -3
- package/Tab.svelte +1 -1
- package/TabList.svelte +2 -18
- package/TabList.types.d.ts +0 -1
- package/TextArea.svelte +10 -8
- package/TextArea.svelte.d.ts +1 -1
- package/Tree.svelte +3 -14
- package/TreeItem.svelte +7 -1
- package/actions/clickOutside.d.ts +12 -1
- package/actions/clickOutside.js +13 -2
- package/index.d.ts +4 -3
- package/index.js +3 -2
- package/package.json +4 -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
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
<script>import {
|
|
2
|
-
import
|
|
1
|
+
<script>import { createEventDispatcher } from "svelte";
|
|
2
|
+
import Popup from "./Popover.svelte";
|
|
3
3
|
import { clickOutside } from "./actions/clickOutside";
|
|
4
4
|
import { idGenerator } from "./idGenerator";
|
|
5
5
|
const popupId = idGenerator.nextId("Dropdown-popup");
|
|
@@ -8,11 +8,7 @@ export let disabled = false;
|
|
|
8
8
|
export let open = false;
|
|
9
9
|
export let stayOpenOnClickAway = false;
|
|
10
10
|
let dropdownRef;
|
|
11
|
-
let
|
|
12
|
-
let popupPosition = {
|
|
13
|
-
x: void 0,
|
|
14
|
-
y: void 0
|
|
15
|
-
};
|
|
11
|
+
let popupContentRef;
|
|
16
12
|
const dispatch = createEventDispatcher();
|
|
17
13
|
const raiseOpen = (open2) => {
|
|
18
14
|
dispatch("open", { open: open2 });
|
|
@@ -29,29 +25,11 @@ export const blur = () => {
|
|
|
29
25
|
export const focus = (options) => {
|
|
30
26
|
dropdownRef?.focus(options);
|
|
31
27
|
};
|
|
32
|
-
let mounted = false;
|
|
33
|
-
onMount(() => {
|
|
34
|
-
mounted = true;
|
|
35
|
-
const cleanup = autoUpdate(dropdownRef, popupRef, async () => {
|
|
36
|
-
const { x, y } = await computePosition(dropdownRef, popupRef, {
|
|
37
|
-
placement: "bottom-end",
|
|
38
|
-
middleware: [offset({ mainAxis: 2 }), flip(), shift({ padding: 0 })]
|
|
39
|
-
});
|
|
40
|
-
if (open) {
|
|
41
|
-
popupPosition = { x, y };
|
|
42
|
-
}
|
|
43
|
-
});
|
|
44
|
-
return cleanup;
|
|
45
|
-
});
|
|
46
28
|
const onClick = (event) => {
|
|
47
|
-
if (!disabled
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
open = !open;
|
|
52
|
-
event.preventDefault();
|
|
53
|
-
event.stopPropagation();
|
|
54
|
-
}
|
|
29
|
+
if (!disabled) {
|
|
30
|
+
open = !open;
|
|
31
|
+
event.preventDefault();
|
|
32
|
+
event.stopPropagation();
|
|
55
33
|
}
|
|
56
34
|
};
|
|
57
35
|
const onKeydown = (event) => {
|
|
@@ -81,7 +59,7 @@ const onClickOutside = (event) => {
|
|
|
81
59
|
class:disabled
|
|
82
60
|
role="combobox"
|
|
83
61
|
tabindex="0"
|
|
84
|
-
use:clickOutside
|
|
62
|
+
use:clickOutside={{ ignoreOthers: [popupContentRef] }}
|
|
85
63
|
on:blur
|
|
86
64
|
on:click
|
|
87
65
|
on:click={onClick}
|
|
@@ -120,15 +98,11 @@ const onClickOutside = (event) => {
|
|
|
120
98
|
</div>
|
|
121
99
|
</slot>
|
|
122
100
|
|
|
123
|
-
<
|
|
124
|
-
bind:this={
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
style="left:{popupPosition.x}px; top:{popupPosition.y}px"
|
|
129
|
-
>
|
|
130
|
-
<slot {composed} {disabled} {open} />
|
|
131
|
-
</div>
|
|
101
|
+
<Popup reference={dropdownRef} open={!disabled && open}>
|
|
102
|
+
<div class="popup-content" bind:this={popupContentRef}>
|
|
103
|
+
<slot {composed} {disabled} {open} />
|
|
104
|
+
</div>
|
|
105
|
+
</Popup>
|
|
132
106
|
</div>
|
|
133
107
|
|
|
134
108
|
<style>
|
|
@@ -266,6 +240,19 @@ const onClickOutside = (event) => {
|
|
|
266
240
|
grid-template-rows: 1fr;
|
|
267
241
|
}
|
|
268
242
|
|
|
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);
|
|
249
|
+
padding: 0.25em;
|
|
250
|
+
display: grid;
|
|
251
|
+
grid-template-columns: 1fr;
|
|
252
|
+
grid-template-rows: 1fr;
|
|
253
|
+
overflow: hidden;
|
|
254
|
+
}
|
|
255
|
+
|
|
269
256
|
@media (prefers-reduced-motion) {
|
|
270
257
|
.sterling-dropdown,
|
|
271
258
|
.sterling-dropdown::after {
|
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
|
|
@@ -296,7 +285,6 @@ A list of items where a single item can be selected.
|
|
|
296
285
|
position: relative;
|
|
297
286
|
flex-direction: column;
|
|
298
287
|
flex-wrap: nowrap;
|
|
299
|
-
width: fit-content;
|
|
300
288
|
}
|
|
301
289
|
|
|
302
290
|
.sterling-list.horizontal .container {
|
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