@dorsk/tsumikit 0.72.0 → 0.73.1
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 +25 -0
- package/dist/components/atoms/Button.svelte +12 -1
- package/dist/components/atoms/Checkbox.svelte +12 -0
- package/dist/components/atoms/Icon.svelte +5 -0
- package/dist/components/atoms/Icon.svelte.d.ts +5 -0
- package/dist/components/atoms/TimeDisplay.svelte +98 -0
- package/dist/components/atoms/TimeDisplay.svelte.d.ts +24 -0
- package/dist/components/layouts/AppShell.svelte +3 -2
- package/dist/components/layouts/ResizablePanel.svelte +5 -4
- package/dist/components/layouts/TabBar.svelte +3 -1
- package/dist/components/molecules/Accordion.svelte +2 -1
- package/dist/components/molecules/AttachmentList.svelte +1 -0
- package/dist/components/molecules/Breadcrumb.svelte +7 -6
- package/dist/components/molecules/Breadcrumb.svelte.d.ts +2 -1
- package/dist/components/molecules/CapBar.svelte +3 -1
- package/dist/components/molecules/Composer.svelte +27 -5
- package/dist/components/molecules/Drawer.svelte +10 -1
- package/dist/components/molecules/Dropzone.svelte +2 -1
- package/dist/components/molecules/FilterInput.svelte +10 -1
- package/dist/components/molecules/Metric.svelte +28 -3
- package/dist/components/molecules/Modal.svelte +20 -4
- package/dist/components/molecules/Popover.svelte +7 -3
- package/dist/components/molecules/SectionHeader.svelte +29 -14
- package/dist/components/molecules/SeekSlider.svelte +370 -0
- package/dist/components/molecules/SeekSlider.svelte.d.ts +37 -0
- package/dist/components/molecules/SegmentedControl.svelte +54 -6
- package/dist/components/molecules/SelectButton.svelte +33 -22
- package/dist/components/molecules/SwipeRow.svelte +6 -2
- package/dist/components/molecules/SwipeRow.svelte.d.ts +4 -0
- package/dist/components/molecules/Tabs.svelte +11 -5
- package/dist/components/molecules/Timestamp.svelte +1 -1
- package/dist/components/molecules/Toaster.svelte +70 -13
- package/dist/components/molecules/Tooltip.svelte +19 -10
- package/dist/components/molecules/VolumeSlider.svelte +367 -0
- package/dist/components/molecules/VolumeSlider.svelte.d.ts +30 -0
- package/dist/components/molecules/combobox-keyboard.js +2 -1
- package/dist/components/organisms/DataTable.svelte +12 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/media-time.d.ts +6 -0
- package/dist/media-time.js +25 -0
- package/dist/query/edit.js +1 -16
- package/dist/query/parser.js +68 -28
- package/dist/query/query.js +9 -5
- package/dist/query/quotes.d.ts +24 -0
- package/dist/query/quotes.js +78 -0
- package/dist/query/single.js +1 -3
- package/dist/query/suggest.js +25 -32
- package/dist/stores/fontscale.svelte.js +3 -2
- package/dist/stores/storage.d.ts +2 -0
- package/dist/stores/storage.js +16 -0
- package/dist/stores/theme.svelte.js +4 -7
- package/package.json +1 -1
|
@@ -66,7 +66,8 @@
|
|
|
66
66
|
</script>
|
|
67
67
|
|
|
68
68
|
<!-- The wrapper (owned here, so styled scoped) is the positioning context that
|
|
69
|
-
clips the transparent overlaid <select>; the icon Button
|
|
69
|
+
clips the transparent overlaid <select>; the inert icon Button is the face
|
|
70
|
+
behind it, so the select is the only focusable control. -->
|
|
70
71
|
<span
|
|
71
72
|
class="select-button {klass}"
|
|
72
73
|
class:row={variant === 'row'}
|
|
@@ -77,32 +78,33 @@
|
|
|
77
78
|
class:no-shrink={!shrink}
|
|
78
79
|
class:block={block || variant === 'row'}
|
|
79
80
|
>
|
|
80
|
-
<Button variant="ghost" icon={variant === 'square'} block={variant === 'row'} {box} {hitArea}
|
|
81
|
+
<Button variant="ghost" icon={variant === 'square'} block={variant === 'row'} {box} {hitArea} inert tabindex={-1}>
|
|
81
82
|
<span aria-hidden="true" class="sb-glyph">
|
|
82
83
|
{#if iconChildren}<Icon>{@render iconChildren()}</Icon>{:else if icon}<Icon name={icon} />{:else}{glyph}{/if}
|
|
83
84
|
</span>
|
|
84
85
|
{#if variant === 'row'}<span aria-hidden="true" class="sb-label">{label}</span>{/if}
|
|
85
|
-
<Select
|
|
86
|
-
variant="ghost"
|
|
87
|
-
aria-label={label}
|
|
88
|
-
{value}
|
|
89
|
-
onchange={(e) => onchange((e.currentTarget as HTMLSelectElement).value)}
|
|
90
|
-
>
|
|
91
|
-
{#if groups}
|
|
92
|
-
{#each groups as g (g.label)}
|
|
93
|
-
<optgroup label={g.label}>
|
|
94
|
-
{#each g.options as o (o.value)}
|
|
95
|
-
<option value={o.value}>{o.label}</option>
|
|
96
|
-
{/each}
|
|
97
|
-
</optgroup>
|
|
98
|
-
{/each}
|
|
99
|
-
{:else}
|
|
100
|
-
{#each options ?? [] as o (o.value)}
|
|
101
|
-
<option value={o.value}>{o.label}</option>
|
|
102
|
-
{/each}
|
|
103
|
-
{/if}
|
|
104
|
-
</Select>
|
|
105
86
|
</Button>
|
|
87
|
+
<Select
|
|
88
|
+
variant="ghost"
|
|
89
|
+
aria-label={label}
|
|
90
|
+
{title}
|
|
91
|
+
{value}
|
|
92
|
+
onchange={(e) => onchange((e.currentTarget as HTMLSelectElement).value)}
|
|
93
|
+
>
|
|
94
|
+
{#if groups}
|
|
95
|
+
{#each groups as g (g.label)}
|
|
96
|
+
<optgroup label={g.label}>
|
|
97
|
+
{#each g.options as o (o.value)}
|
|
98
|
+
<option value={o.value}>{o.label}</option>
|
|
99
|
+
{/each}
|
|
100
|
+
</optgroup>
|
|
101
|
+
{/each}
|
|
102
|
+
{:else}
|
|
103
|
+
{#each options ?? [] as o (o.value)}
|
|
104
|
+
<option value={o.value}>{o.label}</option>
|
|
105
|
+
{/each}
|
|
106
|
+
{/if}
|
|
107
|
+
</Select>
|
|
106
108
|
</span>
|
|
107
109
|
|
|
108
110
|
<style>
|
|
@@ -127,6 +129,15 @@
|
|
|
127
129
|
.select-button.active {
|
|
128
130
|
color: var(--accent);
|
|
129
131
|
}
|
|
132
|
+
/* The inert face takes no pointer events: mirror ghost hover and the
|
|
133
|
+
focus ring from the select overlaying it. */
|
|
134
|
+
.select-button:hover :global(.btn-ghost) {
|
|
135
|
+
background: var(--bg-elevated-2);
|
|
136
|
+
}
|
|
137
|
+
.select-button:has(:focus-visible) {
|
|
138
|
+
outline: 2px solid var(--accent);
|
|
139
|
+
outline-offset: 2px;
|
|
140
|
+
}
|
|
130
141
|
.select-button.row :global(.btn) {
|
|
131
142
|
justify-content: flex-start;
|
|
132
143
|
gap: var(--sp-2);
|
|
@@ -18,6 +18,10 @@
|
|
|
18
18
|
* a drag that is more vertical than horizontal is left to the scroller.
|
|
19
19
|
* The same actions are reachable as visually hidden buttons for keyboard
|
|
20
20
|
* and screen-reader users.
|
|
21
|
+
*
|
|
22
|
+
* On touch the browser implicitly captures the pointer on the element under
|
|
23
|
+
* the finger; taking capture on the row makes that child fire a bubbling
|
|
24
|
+
* `lostpointercapture`, which must not be read as a cancel.
|
|
21
25
|
*/
|
|
22
26
|
import type { Snippet } from 'svelte';
|
|
23
27
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
@@ -162,8 +166,8 @@
|
|
|
162
166
|
e.preventDefault();
|
|
163
167
|
}
|
|
164
168
|
|
|
165
|
-
function onlostpointercapture() {
|
|
166
|
-
if (pointerId === null) return;
|
|
169
|
+
function onlostpointercapture(e: PointerEvent) {
|
|
170
|
+
if (pointerId === null || e.pointerId !== pointerId || e.target !== e.currentTarget) return;
|
|
167
171
|
pointerId = null;
|
|
168
172
|
locked = null;
|
|
169
173
|
dragging = false;
|
|
@@ -13,6 +13,10 @@ export interface SwipeAction {
|
|
|
13
13
|
* a drag that is more vertical than horizontal is left to the scroller.
|
|
14
14
|
* The same actions are reachable as visually hidden buttons for keyboard
|
|
15
15
|
* and screen-reader users.
|
|
16
|
+
*
|
|
17
|
+
* On touch the browser implicitly captures the pointer on the element under
|
|
18
|
+
* the finger; taking capture on the row makes that child fire a bubbling
|
|
19
|
+
* `lostpointercapture`, which must not be read as a cancel.
|
|
16
20
|
*/
|
|
17
21
|
import type { Snippet } from 'svelte';
|
|
18
22
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
@@ -86,6 +86,12 @@
|
|
|
86
86
|
let listEl = $state<HTMLDivElement | null>(null);
|
|
87
87
|
const baseId = `tabs-${Math.random().toString(36).slice(2, 8)}`;
|
|
88
88
|
const panelId = $derived(panel ? `${baseId}-panel` : undefined);
|
|
89
|
+
// The roving tab stop: the selected tab, or the first enabled one when the
|
|
90
|
+
// selection is unknown or disabled, so the strip never drops out of the Tab order.
|
|
91
|
+
const stop = $derived(
|
|
92
|
+
tabs.find((t) => t.id === value && !t.disabled) ?? tabs.find((t) => !t.disabled)
|
|
93
|
+
);
|
|
94
|
+
const tabId = (id: string) => `${baseId}-tab-${id}`;
|
|
89
95
|
|
|
90
96
|
function canClose(t: TabItem): boolean {
|
|
91
97
|
return t.closable ?? closable;
|
|
@@ -95,7 +101,7 @@
|
|
|
95
101
|
if (tabs.find((t) => t.id === id)?.disabled) return;
|
|
96
102
|
value = id;
|
|
97
103
|
queueMicrotask(() => {
|
|
98
|
-
const el = listEl?.
|
|
104
|
+
const el = listEl?.ownerDocument.getElementById(tabId(id));
|
|
99
105
|
// The list scrolls when it is too narrow, so keyboard selection has to
|
|
100
106
|
// bring the tab back into view or it moves somewhere unseen.
|
|
101
107
|
el?.scrollIntoView({ block: 'nearest', inline: 'nearest' });
|
|
@@ -129,7 +135,7 @@
|
|
|
129
135
|
close(id);
|
|
130
136
|
}
|
|
131
137
|
function onkeydown(e: KeyboardEvent) {
|
|
132
|
-
const i = tabs.
|
|
138
|
+
const i = stop ? tabs.indexOf(stop) : -1;
|
|
133
139
|
if (i < 0) return;
|
|
134
140
|
if (e.key === 'Delete') {
|
|
135
141
|
if (!canClose(tabs[i])) return;
|
|
@@ -169,7 +175,7 @@
|
|
|
169
175
|
aria-selected={value === t.id}
|
|
170
176
|
aria-controls={panelId}
|
|
171
177
|
aria-disabled={t.disabled || undefined}
|
|
172
|
-
tabindex={
|
|
178
|
+
tabindex={stop?.id === t.id ? 0 : -1}
|
|
173
179
|
class="tab closable"
|
|
174
180
|
class:selected={value === t.id}
|
|
175
181
|
class:disabled={t.disabled}
|
|
@@ -202,7 +208,7 @@
|
|
|
202
208
|
aria-selected={value === t.id}
|
|
203
209
|
aria-controls={panelId}
|
|
204
210
|
disabled={t.disabled}
|
|
205
|
-
tabindex={
|
|
211
|
+
tabindex={stop?.id === t.id ? 0 : -1}
|
|
206
212
|
class="tab"
|
|
207
213
|
class:selected={value === t.id}
|
|
208
214
|
onclick={() => select(t.id)}
|
|
@@ -217,7 +223,7 @@
|
|
|
217
223
|
{/if}
|
|
218
224
|
</div>
|
|
219
225
|
{#if panel}
|
|
220
|
-
<div role="tabpanel" id={panelId} tabindex="0" class="tabpanel {panelClass}" class:pad-none={panelPadding === 'none'} class:pad-sm={panelPadding === 'sm'}>
|
|
226
|
+
<div role="tabpanel" id={panelId} aria-labelledby={value === undefined ? undefined : tabId(value)} tabindex="0" class="tabpanel {panelClass}" class:pad-none={panelPadding === 'none'} class:pad-sm={panelPadding === 'sm'}>
|
|
221
227
|
{#if value !== undefined}{@render panel(value)}{/if}
|
|
222
228
|
</div>
|
|
223
229
|
{/if}
|
|
@@ -130,7 +130,7 @@
|
|
|
130
130
|
<!-- Unparseable input: degrade to an inert dash rather than empty text. -->
|
|
131
131
|
<time class="{cls} ts-invalid" data-tsu="Timestamp" style={styleProp} style:font-size={sizeVar}>—</time>
|
|
132
132
|
{:else if showPopover}
|
|
133
|
-
<Popover label="
|
|
133
|
+
<Popover label="{label}, details" placement="bottom-start" bare>
|
|
134
134
|
{#snippet trigger()}
|
|
135
135
|
<time class="{cls} ts-trigger" data-tsu="Timestamp" style={styleProp} style:font-size={sizeVar} datetime={machine}>{label}</time>
|
|
136
136
|
{/snippet}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
// Renders the toast queue bottom-centered in two live regions: polite for
|
|
3
3
|
// ok/info/neutral, assertive (role=alert) for errors. Mount once near the
|
|
4
|
-
// app root. The stack lives in a manual popover so it paints in the
|
|
5
|
-
//
|
|
6
|
-
//
|
|
4
|
+
// app root. The stack lives in a manual popover so it paints in the top
|
|
5
|
+
// layer; without the Popover API the attribute is ignored and the
|
|
6
|
+
// fixed/z-index positioning applies instead. While a modal <dialog> is open
|
|
7
|
+
// everything outside it is inert, top layer included, so the stack moves
|
|
8
|
+
// into the topmost modal dialog and back to its host when it closes.
|
|
7
9
|
import { toasts } from '../../stores/toast.svelte';
|
|
8
10
|
import Button from '../atoms/Button.svelte';
|
|
9
11
|
import Card from '../atoms/Card.svelte';
|
|
@@ -11,16 +13,61 @@
|
|
|
11
13
|
|
|
12
14
|
let { class: klass = '', style: styleProp = '' }: { class?: string; style?: string } = $props();
|
|
13
15
|
|
|
16
|
+
let host: HTMLDivElement | undefined = $state();
|
|
14
17
|
let el: HTMLDivElement | undefined = $state();
|
|
15
18
|
const polite = $derived(toasts.items.filter((t) => t.tone !== 'error'));
|
|
16
19
|
const assertive = $derived(toasts.items.filter((t) => t.tone === 'error'));
|
|
17
20
|
|
|
18
|
-
|
|
19
|
-
|
|
21
|
+
function topModal(): HTMLDialogElement | null {
|
|
22
|
+
let found: HTMLDialogElement | null = null;
|
|
23
|
+
for (const d of document.querySelectorAll<HTMLDialogElement>('dialog[open]')) {
|
|
24
|
+
try {
|
|
25
|
+
if (d.matches(':modal')) found = d;
|
|
26
|
+
} catch {}
|
|
27
|
+
}
|
|
28
|
+
return found;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let shown = false;
|
|
32
|
+
function show(stack: HTMLDivElement) {
|
|
33
|
+
if (typeof stack.showPopover !== 'function') return;
|
|
20
34
|
try {
|
|
21
|
-
|
|
22
|
-
|
|
35
|
+
stack.showPopover();
|
|
36
|
+
shown = true;
|
|
23
37
|
} catch {}
|
|
38
|
+
}
|
|
39
|
+
function hide(stack: HTMLDivElement) {
|
|
40
|
+
if (!shown || typeof stack.hidePopover !== 'function') return;
|
|
41
|
+
shown = false;
|
|
42
|
+
try {
|
|
43
|
+
stack.hidePopover();
|
|
44
|
+
} catch {}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
$effect(() => {
|
|
48
|
+
const stack = el;
|
|
49
|
+
const home = host;
|
|
50
|
+
if (!stack || !home) return;
|
|
51
|
+
if (!toasts.items.length) {
|
|
52
|
+
hide(stack);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const place = () => {
|
|
56
|
+
const target = topModal() ?? home;
|
|
57
|
+
if (stack.parentNode !== target) {
|
|
58
|
+
hide(stack);
|
|
59
|
+
target.appendChild(stack);
|
|
60
|
+
}
|
|
61
|
+
show(stack);
|
|
62
|
+
};
|
|
63
|
+
place();
|
|
64
|
+
const observer = new MutationObserver(place);
|
|
65
|
+
observer.observe(document.body, { attributes: true, attributeFilter: ['open'], subtree: true });
|
|
66
|
+
return () => observer.disconnect();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
$effect(() => () => {
|
|
70
|
+
if (el && host && el.parentNode !== host) host.appendChild(el);
|
|
24
71
|
});
|
|
25
72
|
</script>
|
|
26
73
|
|
|
@@ -62,16 +109,21 @@
|
|
|
62
109
|
{/if}
|
|
63
110
|
{/snippet}
|
|
64
111
|
|
|
65
|
-
<div class="toaster
|
|
66
|
-
<div class="
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
112
|
+
<div class="toaster-host" bind:this={host}>
|
|
113
|
+
<div class="toaster {klass}" style={styleProp} bind:this={el} popover="manual" data-tsu="Toaster">
|
|
114
|
+
<div class="region" role="status" aria-live="polite">
|
|
115
|
+
{#each polite as t (t.id)}{@render toast(t)}{/each}
|
|
116
|
+
</div>
|
|
117
|
+
<div class="region" role="alert" aria-live="assertive">
|
|
118
|
+
{#each assertive as t (t.id)}{@render toast(t)}{/each}
|
|
119
|
+
</div>
|
|
71
120
|
</div>
|
|
72
121
|
</div>
|
|
73
122
|
|
|
74
123
|
<style>
|
|
124
|
+
.toaster-host {
|
|
125
|
+
display: contents;
|
|
126
|
+
}
|
|
75
127
|
.toaster {
|
|
76
128
|
position: fixed;
|
|
77
129
|
inset: auto;
|
|
@@ -142,4 +194,9 @@
|
|
|
142
194
|
opacity: 0;
|
|
143
195
|
}
|
|
144
196
|
}
|
|
197
|
+
@media (prefers-reduced-motion: reduce) {
|
|
198
|
+
.toaster :global(.toast) {
|
|
199
|
+
animation: none;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
145
202
|
</style>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
// Lightweight tooltip. Shows on hover (after a short delay) and on keyboard
|
|
3
|
-
// focus of the trigger; hides on blur / mouseleave / Escape. The trigger is
|
|
3
|
+
// focus of the trigger; hides on blur / mouseleave / Escape (anywhere). The trigger is
|
|
4
4
|
// any snippet; an action wires `aria-describedby` onto its first focusable
|
|
5
5
|
// element so screen readers announce the text.
|
|
6
6
|
//
|
|
@@ -63,6 +63,11 @@
|
|
|
63
63
|
if (wrapEl && tipEl) place(wrapEl, tipEl, `${placement}-center`, 6);
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
// Escape must dismiss a hover-opened bubble even while focus sits elsewhere.
|
|
67
|
+
function onEscape(e: KeyboardEvent) {
|
|
68
|
+
if (e.key === 'Escape') hideNow();
|
|
69
|
+
}
|
|
70
|
+
|
|
66
71
|
function show() {
|
|
67
72
|
clearTimeout(hideTimer); // pointer came back within the grace period
|
|
68
73
|
clearTimeout(showTimer);
|
|
@@ -72,15 +77,20 @@
|
|
|
72
77
|
reposition();
|
|
73
78
|
addEventListener('scroll', reposition, true);
|
|
74
79
|
addEventListener('resize', reposition);
|
|
80
|
+
document.addEventListener('keydown', onEscape);
|
|
75
81
|
}, delay);
|
|
76
82
|
}
|
|
77
83
|
function hideNow() {
|
|
78
84
|
clearTimeout(showTimer);
|
|
79
85
|
clearTimeout(hideTimer);
|
|
80
|
-
|
|
86
|
+
try {
|
|
87
|
+
if (tipEl?.matches(':popover-open')) tipEl.hidePopover();
|
|
88
|
+
} catch {}
|
|
81
89
|
removeEventListener('scroll', reposition, true);
|
|
82
90
|
removeEventListener('resize', reposition);
|
|
91
|
+
document.removeEventListener('keydown', onEscape);
|
|
83
92
|
}
|
|
93
|
+
$effect(() => () => hideNow());
|
|
84
94
|
// Debounced close: cancelled if the pointer/focus enters the panel (rich
|
|
85
95
|
// mode) or returns to the trigger before `closeDelay` elapses.
|
|
86
96
|
function scheduleHide() {
|
|
@@ -104,21 +114,23 @@
|
|
|
104
114
|
node.tabIndex = 0;
|
|
105
115
|
target = node;
|
|
106
116
|
}
|
|
107
|
-
|
|
108
|
-
|
|
117
|
+
// The trigger may already be described (a Field hint/error): append our
|
|
118
|
+
// id to the token list and take back only that token on destroy.
|
|
119
|
+
const describedBy = () => (target.getAttribute('aria-describedby') ?? '').split(/\s+/).filter(Boolean);
|
|
120
|
+
target.setAttribute('aria-describedby', [...describedBy().filter((t) => t !== id), id].join(' '));
|
|
109
121
|
node.addEventListener('mouseenter', show);
|
|
110
122
|
node.addEventListener('mouseleave', scheduleHide);
|
|
111
123
|
node.addEventListener('focusin', show);
|
|
112
124
|
node.addEventListener('focusout', scheduleHide);
|
|
113
|
-
node.addEventListener('keydown', onkey);
|
|
114
125
|
return {
|
|
115
126
|
destroy() {
|
|
116
|
-
|
|
127
|
+
const rest = describedBy().filter((t) => t !== id);
|
|
128
|
+
if (rest.length) target.setAttribute('aria-describedby', rest.join(' '));
|
|
129
|
+
else target.removeAttribute('aria-describedby');
|
|
117
130
|
node.removeEventListener('mouseenter', show);
|
|
118
131
|
node.removeEventListener('mouseleave', scheduleHide);
|
|
119
132
|
node.removeEventListener('focusin', show);
|
|
120
133
|
node.removeEventListener('focusout', scheduleHide);
|
|
121
|
-
node.removeEventListener('keydown', onkey);
|
|
122
134
|
}
|
|
123
135
|
};
|
|
124
136
|
}
|
|
@@ -126,19 +138,16 @@
|
|
|
126
138
|
// Action for the panel: in rich mode it accepts the pointer/focus, which
|
|
127
139
|
// cancels the pending close (hovercard persistence).
|
|
128
140
|
function panel(node: HTMLElement) {
|
|
129
|
-
const onkey = (e: KeyboardEvent) => e.key === 'Escape' && hideNow();
|
|
130
141
|
node.addEventListener('mouseenter', cancelHide);
|
|
131
142
|
node.addEventListener('mouseleave', scheduleHide);
|
|
132
143
|
node.addEventListener('focusin', cancelHide);
|
|
133
144
|
node.addEventListener('focusout', scheduleHide);
|
|
134
|
-
node.addEventListener('keydown', onkey);
|
|
135
145
|
return {
|
|
136
146
|
destroy() {
|
|
137
147
|
node.removeEventListener('mouseenter', cancelHide);
|
|
138
148
|
node.removeEventListener('mouseleave', scheduleHide);
|
|
139
149
|
node.removeEventListener('focusin', cancelHide);
|
|
140
150
|
node.removeEventListener('focusout', scheduleHide);
|
|
141
|
-
node.removeEventListener('keydown', onkey);
|
|
142
151
|
}
|
|
143
152
|
};
|
|
144
153
|
}
|