@ibobbyts/svelte-ui-utils 0.4.3 → 0.4.5
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 +119 -14
- package/dist/data-table/FilterControl.svelte +30 -4
- package/dist/data-table/FilterControl.svelte.d.ts.map +1 -1
- package/dist/data-table/filter.d.ts +3 -1
- package/dist/data-table/filter.d.ts.map +1 -1
- package/dist/data-table/filter.js +4 -0
- package/dist/data-table/index.d.ts +1 -1
- package/dist/data-table/index.d.ts.map +1 -1
- package/dist/data-table/types.d.ts +41 -11
- package/dist/data-table/types.d.ts.map +1 -1
- package/dist/dialog/ConfirmDialog.svelte +2 -0
- package/dist/dialog/ConfirmDialog.svelte.d.ts +1 -0
- package/dist/dialog/ConfirmDialog.svelte.d.ts.map +1 -1
- package/dist/dialog/Dialog.svelte +164 -71
- package/dist/dialog/Dialog.svelte.d.ts +1 -0
- package/dist/dialog/Dialog.svelte.d.ts.map +1 -1
- package/dist/dropdown/Dropdown.svelte +470 -43
- package/dist/dropdown/Dropdown.svelte.d.ts +18 -1
- package/dist/dropdown/Dropdown.svelte.d.ts.map +1 -1
- package/dist/dropdown/DropdownMultiSelect.svelte +2 -0
- package/dist/dropdown/DropdownMultiSelect.svelte.d.ts +1 -0
- package/dist/dropdown/DropdownMultiSelect.svelte.d.ts.map +1 -1
- package/dist/dropdown/index.d.ts +1 -1
- package/dist/dropdown/index.d.ts.map +1 -1
- package/dist/dropdown/types.d.ts +24 -2
- package/dist/dropdown/types.d.ts.map +1 -1
- package/dist/dropdown-search/DropdownSearch.svelte +12 -10
- package/dist/dropdown-search/DropdownSearch.svelte.d.ts +2 -2
- package/dist/dropdown-search/DropdownSearch.svelte.d.ts.map +1 -1
- package/dist/dropdown-search/DropdownSearchMultiSelect.svelte +3 -3
- package/dist/dropdown-search/DropdownSearchMultiSelect.svelte.d.ts +2 -2
- package/dist/dropdown-search/index.d.ts +1 -1
- package/dist/dropdown-search/types.d.ts +10 -3
- package/dist/dropdown-search/types.d.ts.map +1 -1
- package/dist/pagination/Pagination.svelte +5 -4
- package/dist/pagination/Pagination.svelte.d.ts.map +1 -1
- package/dist/style.css +119 -0
- package/package.json +1 -1
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
<svelte:options runes={false} />
|
|
2
2
|
|
|
3
|
+
<script context="module" lang="ts">
|
|
4
|
+
// Mount order mirrors the native top layer: the last registered element is
|
|
5
|
+
// the topmost dialog, and only it reacts to Escape, Tab, and backdrop clicks.
|
|
6
|
+
const openDialogElements: HTMLDialogElement[] = [];
|
|
7
|
+
|
|
8
|
+
const focusableSelector =
|
|
9
|
+
'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
|
|
10
|
+
</script>
|
|
11
|
+
|
|
3
12
|
<script lang="ts">
|
|
4
13
|
import { tick } from 'svelte';
|
|
5
14
|
import type { DialogSize } from './types.js';
|
|
@@ -23,44 +32,91 @@
|
|
|
23
32
|
export let countdownLabel = 'Dialog countdown';
|
|
24
33
|
export let labelledBy: string | undefined = undefined;
|
|
25
34
|
export let describedBy: string | undefined = undefined;
|
|
35
|
+
export let restoreFocusSelector: string | undefined = undefined;
|
|
26
36
|
export let onClose: (() => void) | undefined = undefined;
|
|
27
37
|
|
|
28
|
-
let dialogElement:
|
|
38
|
+
let dialogElement: HTMLDialogElement | undefined;
|
|
29
39
|
let titleId = `suu-dialog-title-${Math.random().toString(36).slice(2)}`;
|
|
30
40
|
let descriptionId = `suu-dialog-description-${Math.random().toString(36).slice(2)}`;
|
|
31
41
|
let wasOpen = false;
|
|
32
42
|
let previouslyFocusedElement: HTMLElement | null = null;
|
|
43
|
+
let restoreFocusSelectorAtOpen: string | undefined;
|
|
44
|
+
|
|
45
|
+
// The dialog element is the full-viewport top layer surface; the visual
|
|
46
|
+
// backdrop and panel stay on their existing inner elements.
|
|
47
|
+
const dialogResetStyle =
|
|
48
|
+
'width: 100%; height: 100%; max-width: none; max-height: none; margin: 0; padding: 0; border: none; background: transparent;';
|
|
33
49
|
|
|
34
50
|
$: resolvedLabelledBy = labelledBy ?? (title ? titleId : undefined);
|
|
35
51
|
$: resolvedDescribedBy =
|
|
36
52
|
describedBy ?? (description ? descriptionId : undefined);
|
|
37
53
|
$: backdropStyle =
|
|
38
54
|
`--suu-dialog-backdrop-opacity: ${backdropOpacity}; --suu-dialog-backdrop-blur: ${backdropBlur};`;
|
|
39
|
-
$: dialogStyle = padding ? `--suu-dialog-padding: ${padding};` : '';
|
|
40
55
|
$: countdownStyle = `--suu-dialog-countdown-duration: ${Math.max(0, countdownDurationMs)}ms;`;
|
|
41
56
|
$: if (open && !wasOpen) {
|
|
42
57
|
wasOpen = true;
|
|
43
|
-
void focusDialog();
|
|
44
|
-
} else if (!open && wasOpen) {
|
|
45
|
-
wasOpen = false;
|
|
46
|
-
restoreFocus();
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
async function focusDialog() {
|
|
50
58
|
if (typeof document !== 'undefined') {
|
|
51
59
|
previouslyFocusedElement =
|
|
52
60
|
document.activeElement instanceof HTMLElement
|
|
53
61
|
? document.activeElement
|
|
54
62
|
: null;
|
|
63
|
+
restoreFocusSelectorAtOpen = restoreFocusSelector;
|
|
64
|
+
}
|
|
65
|
+
void focusDialog();
|
|
66
|
+
} else if (!open && wasOpen) {
|
|
67
|
+
wasOpen = false;
|
|
68
|
+
void restoreFocus();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function presentDialog(element: HTMLDialogElement) {
|
|
72
|
+
if (!openDialogElements.includes(element)) {
|
|
73
|
+
openDialogElements.push(element);
|
|
74
|
+
}
|
|
75
|
+
if (typeof element.showModal === 'function') {
|
|
76
|
+
if (!element.open) {
|
|
77
|
+
element.showModal();
|
|
78
|
+
}
|
|
79
|
+
} else if (!element.hasAttribute('open')) {
|
|
80
|
+
// jsdom does not implement showModal; the attribute keeps the element
|
|
81
|
+
// rendered as an open dialog there.
|
|
82
|
+
element.setAttribute('open', '');
|
|
55
83
|
}
|
|
56
84
|
|
|
85
|
+
return {
|
|
86
|
+
destroy() {
|
|
87
|
+
const index = openDialogElements.indexOf(element);
|
|
88
|
+
if (index !== -1) {
|
|
89
|
+
openDialogElements.splice(index, 1);
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function focusDialog() {
|
|
57
96
|
await tick();
|
|
58
97
|
dialogElement?.focus();
|
|
59
98
|
}
|
|
60
99
|
|
|
61
|
-
function restoreFocus() {
|
|
62
|
-
previouslyFocusedElement
|
|
100
|
+
async function restoreFocus() {
|
|
101
|
+
const previousTarget = previouslyFocusedElement;
|
|
102
|
+
const selectorAtOpen = restoreFocusSelectorAtOpen;
|
|
63
103
|
previouslyFocusedElement = null;
|
|
104
|
+
restoreFocusSelectorAtOpen = undefined;
|
|
105
|
+
if (!previousTarget && !selectorAtOpen) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
await tick();
|
|
110
|
+
const replacementTarget = selectorAtOpen
|
|
111
|
+
? document.querySelector(selectorAtOpen)
|
|
112
|
+
: null;
|
|
113
|
+
const target =
|
|
114
|
+
replacementTarget instanceof HTMLElement
|
|
115
|
+
? replacementTarget
|
|
116
|
+
: previousTarget?.isConnected
|
|
117
|
+
? previousTarget
|
|
118
|
+
: null;
|
|
119
|
+
target?.focus();
|
|
64
120
|
}
|
|
65
121
|
|
|
66
122
|
function requestClose() {
|
|
@@ -79,78 +135,115 @@
|
|
|
79
135
|
requestClose();
|
|
80
136
|
}
|
|
81
137
|
|
|
82
|
-
function
|
|
83
|
-
|
|
84
|
-
|
|
138
|
+
function handleCancel(event: Event) {
|
|
139
|
+
// Prevent the browser's own Escape close so the open prop stays the only
|
|
140
|
+
// source of truth; only the topmost dialog receives this event natively.
|
|
141
|
+
event.preventDefault();
|
|
142
|
+
if (closeOnEscape) {
|
|
85
143
|
requestClose();
|
|
86
144
|
}
|
|
87
145
|
}
|
|
88
|
-
</script>
|
|
89
146
|
|
|
90
|
-
|
|
147
|
+
function handleKeydown(event: KeyboardEvent) {
|
|
148
|
+
if (event.key !== 'Tab' || !dialogElement) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (openDialogElements[openDialogElements.length - 1] !== dialogElement) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
event.preventDefault();
|
|
156
|
+
const focusables = Array.from(
|
|
157
|
+
dialogElement.querySelectorAll<HTMLElement>(focusableSelector),
|
|
158
|
+
);
|
|
159
|
+
if (focusables.length === 0) {
|
|
160
|
+
dialogElement.focus();
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const activeElement =
|
|
165
|
+
document.activeElement instanceof HTMLElement
|
|
166
|
+
? document.activeElement
|
|
167
|
+
: undefined;
|
|
168
|
+
const currentIndex = activeElement
|
|
169
|
+
? focusables.indexOf(activeElement)
|
|
170
|
+
: -1;
|
|
171
|
+
const nextIndex = event.shiftKey
|
|
172
|
+
? currentIndex <= 0
|
|
173
|
+
? focusables.length - 1
|
|
174
|
+
: currentIndex - 1
|
|
175
|
+
: currentIndex === -1 || currentIndex === focusables.length - 1
|
|
176
|
+
? 0
|
|
177
|
+
: currentIndex + 1;
|
|
178
|
+
focusables[nextIndex].focus();
|
|
179
|
+
}
|
|
180
|
+
</script>
|
|
91
181
|
|
|
92
182
|
{#if open}
|
|
93
|
-
<
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
183
|
+
<dialog
|
|
184
|
+
bind:this={dialogElement}
|
|
185
|
+
class="suu-dialog-root"
|
|
186
|
+
style={dialogResetStyle}
|
|
187
|
+
aria-labelledby={resolvedLabelledBy}
|
|
188
|
+
aria-describedby={resolvedDescribedBy}
|
|
189
|
+
tabindex="-1"
|
|
190
|
+
use:presentDialog
|
|
191
|
+
on:cancel={handleCancel}
|
|
192
|
+
on:keydown={handleKeydown}
|
|
100
193
|
>
|
|
101
194
|
<div
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
aria-describedby={resolvedDescribedBy}
|
|
109
|
-
tabindex="-1"
|
|
195
|
+
class="suu-dialog-backdrop"
|
|
196
|
+
data-dimmed={dimBackdrop ? 'true' : 'false'}
|
|
197
|
+
data-blurred={blurBackdrop ? 'true' : 'false'}
|
|
198
|
+
style={backdropStyle}
|
|
199
|
+
role="presentation"
|
|
200
|
+
on:mousedown={handleBackdropMouseDown}
|
|
110
201
|
>
|
|
111
|
-
{
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
<
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
{
|
|
127
|
-
|
|
202
|
+
<div class={`suu-dialog suu-dialog--${size}`} style:--suu-dialog-padding={padding}>
|
|
203
|
+
{#if showCountdown && countdownDurationMs > 0}
|
|
204
|
+
<div
|
|
205
|
+
class="suu-dialog__countdown"
|
|
206
|
+
style={countdownStyle}
|
|
207
|
+
role="timer"
|
|
208
|
+
aria-label={countdownLabel}
|
|
209
|
+
></div>
|
|
210
|
+
{/if}
|
|
211
|
+
<header class="suu-dialog__header">
|
|
212
|
+
<div class="suu-dialog__heading">
|
|
213
|
+
{#if title}
|
|
214
|
+
<h2 class="suu-dialog__title" id={titleId}>{title}</h2>
|
|
215
|
+
{/if}
|
|
216
|
+
{#if description}
|
|
217
|
+
<p class="suu-dialog__description" id={descriptionId}>
|
|
218
|
+
{description}
|
|
219
|
+
</p>
|
|
220
|
+
{/if}
|
|
221
|
+
</div>
|
|
222
|
+
{#if showCloseButton && dismissible}
|
|
223
|
+
<button
|
|
224
|
+
type="button"
|
|
225
|
+
class="suu-dialog__close"
|
|
226
|
+
aria-label={closeLabel}
|
|
227
|
+
title={closeLabel}
|
|
228
|
+
on:click={requestClose}
|
|
229
|
+
>
|
|
230
|
+
<svg viewBox="0 0 20 20" aria-hidden="true">
|
|
231
|
+
<path d="m6 6 8 8M14 6l-8 8"></path>
|
|
232
|
+
</svg>
|
|
233
|
+
</button>
|
|
128
234
|
{/if}
|
|
235
|
+
</header>
|
|
236
|
+
|
|
237
|
+
<div class="suu-dialog__body">
|
|
238
|
+
<slot />
|
|
129
239
|
</div>
|
|
130
|
-
{#if showCloseButton && dismissible}
|
|
131
|
-
<button
|
|
132
|
-
type="button"
|
|
133
|
-
class="suu-dialog__close"
|
|
134
|
-
aria-label={closeLabel}
|
|
135
|
-
title={closeLabel}
|
|
136
|
-
on:click={requestClose}
|
|
137
|
-
>
|
|
138
|
-
<svg viewBox="0 0 20 20" aria-hidden="true">
|
|
139
|
-
<path d="m6 6 8 8M14 6l-8 8"></path>
|
|
140
|
-
</svg>
|
|
141
|
-
</button>
|
|
142
|
-
{/if}
|
|
143
|
-
</header>
|
|
144
240
|
|
|
145
|
-
|
|
146
|
-
|
|
241
|
+
{#if $$slots.footer}
|
|
242
|
+
<footer class="suu-dialog__footer">
|
|
243
|
+
<slot name="footer" />
|
|
244
|
+
</footer>
|
|
245
|
+
{/if}
|
|
147
246
|
</div>
|
|
148
|
-
|
|
149
|
-
{#if $$slots.footer}
|
|
150
|
-
<footer class="suu-dialog__footer">
|
|
151
|
-
<slot name="footer" />
|
|
152
|
-
</footer>
|
|
153
|
-
{/if}
|
|
154
247
|
</div>
|
|
155
|
-
</
|
|
248
|
+
</dialog>
|
|
156
249
|
{/if}
|
|
@@ -37,6 +37,7 @@ declare const Dialog: $$__sveltets_2_IsomorphicComponent<$$__sveltets_2_PropsWit
|
|
|
37
37
|
countdownLabel?: string;
|
|
38
38
|
labelledBy?: string | undefined;
|
|
39
39
|
describedBy?: string | undefined;
|
|
40
|
+
restoreFocusSelector?: string | undefined;
|
|
40
41
|
onClose?: (() => void) | undefined;
|
|
41
42
|
}, {
|
|
42
43
|
default: {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Dialog.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/dialog/Dialog.svelte.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Dialog.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/dialog/Dialog.svelte.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AA2N7C,UAAU,kCAAkC,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,OAAO,GAAG,EAAE,EAAE,QAAQ,GAAG,MAAM;IACpM,KAAK,OAAO,EAAE,OAAO,QAAQ,EAAE,2BAA2B,CAAC,KAAK,CAAC,GAAG,OAAO,QAAQ,EAAE,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,GAAG;QAAE,UAAU,CAAC,EAAE,QAAQ,CAAA;KAAE,GAAG,OAAO,CAAC;IACjK,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,GAAG;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,KAAK,CAAA;KAAC,GAAG,OAAO,GAAG;QAAE,IAAI,CAAC,EAAE,GAAG,CAAC;QAAC,GAAG,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC;IAC9G,YAAY,CAAC,EAAE,QAAQ,CAAC;CAC3B;AACD,KAAK,gCAAgC,CAAC,KAAK,EAAE,KAAK,IAAI,KAAK,GACvD,CAAC,KAAK,SAAS;IAAE,OAAO,EAAE,GAAG,CAAA;CAAE,GACzB,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,GACnC,GAAG,GACH;IAAE,QAAQ,CAAC,EAAE,GAAG,CAAA;CAAE,GAClB,EAAE,CAAC,CAAC;AAId,QAAA,MAAM,MAAM;;;;;WAfksB,UAAU;cAAY,MAAM,GAAG,SAAS;;;;;;;sBAAsP,MAAM,GAAG,MAAM;;;;;iBAAmL,MAAM,GAAG,SAAS;kBAAgB,MAAM,GAAG,SAAS;2BAAyB,MAAM,GAAG,SAAS;cAAY,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS;;;;;;;;;cAehtC,CAAC;AAChF,KAAK,MAAM,GAAG,YAAY,CAAC,OAAO,MAAM,CAAC,CAAC;AAC5C,eAAe,MAAM,CAAC"}
|