@salmexio/ui 0.1.0 → 0.2.0

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.
Files changed (67) hide show
  1. package/dist/dialogs/Modal/Modal.svelte +427 -0
  2. package/dist/dialogs/Modal/Modal.svelte.d.ts +43 -0
  3. package/dist/dialogs/Modal/Modal.svelte.d.ts.map +1 -0
  4. package/dist/dialogs/Modal/index.d.ts +2 -0
  5. package/dist/dialogs/Modal/index.d.ts.map +1 -0
  6. package/dist/dialogs/Modal/index.js +1 -0
  7. package/dist/dialogs/index.d.ts +3 -0
  8. package/dist/dialogs/index.d.ts.map +1 -0
  9. package/dist/dialogs/index.js +2 -0
  10. package/dist/dialogs/modalStore.d.ts +13 -0
  11. package/dist/dialogs/modalStore.d.ts.map +1 -0
  12. package/dist/dialogs/modalStore.js +13 -0
  13. package/dist/feedback/Alert/Alert.svelte +63 -63
  14. package/dist/feedback/Alert/Alert.svelte.d.ts.map +1 -1
  15. package/dist/feedback/Spinner/Spinner.svelte +55 -55
  16. package/dist/feedback/Spinner/Spinner.svelte.d.ts.map +1 -1
  17. package/dist/forms/Checkbox/Checkbox.svelte +328 -0
  18. package/dist/forms/Checkbox/Checkbox.svelte.d.ts +30 -0
  19. package/dist/forms/Checkbox/Checkbox.svelte.d.ts.map +1 -0
  20. package/dist/forms/Checkbox/index.d.ts +2 -0
  21. package/dist/forms/Checkbox/index.d.ts.map +1 -0
  22. package/dist/forms/Checkbox/index.js +1 -0
  23. package/dist/forms/TextInput/TextInput.svelte +141 -141
  24. package/dist/forms/TextInput/TextInput.svelte.d.ts.map +1 -1
  25. package/dist/forms/index.d.ts +1 -0
  26. package/dist/forms/index.d.ts.map +1 -1
  27. package/dist/forms/index.js +1 -0
  28. package/dist/index.d.ts +2 -0
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +2 -0
  31. package/dist/layout/Card/Card.svelte +59 -59
  32. package/dist/layout/Card/Card.svelte.d.ts.map +1 -1
  33. package/dist/layout/Container/Container.svelte +34 -34
  34. package/dist/layout/Container/Container.svelte.d.ts.map +1 -1
  35. package/dist/navigation/Tabs/Tabs.svelte +100 -101
  36. package/dist/navigation/Tabs/Tabs.svelte.d.ts.map +1 -1
  37. package/dist/primitives/Badge/Badge.svelte +35 -42
  38. package/dist/primitives/Badge/Badge.svelte.d.ts.map +1 -1
  39. package/dist/primitives/Button/Button.svelte +47 -47
  40. package/dist/primitives/Button/Button.svelte.d.ts.map +1 -1
  41. package/dist/routes/+layout.svelte +1 -1
  42. package/dist/styles/tokens.css +94 -91
  43. package/dist/utils/index.d.ts +2 -0
  44. package/dist/utils/index.d.ts.map +1 -1
  45. package/dist/utils/index.js +1 -0
  46. package/dist/utils/keyboard.d.ts +28 -0
  47. package/dist/utils/keyboard.d.ts.map +1 -0
  48. package/dist/utils/keyboard.js +67 -0
  49. package/dist/windowing/Window/Window.svelte +602 -0
  50. package/dist/windowing/Window/Window.svelte.d.ts +65 -0
  51. package/dist/windowing/Window/Window.svelte.d.ts.map +1 -0
  52. package/dist/windowing/Window/index.d.ts +2 -0
  53. package/dist/windowing/Window/index.d.ts.map +1 -0
  54. package/dist/windowing/Window/index.js +1 -0
  55. package/dist/windowing/WindowManager/WindowManager.svelte +410 -0
  56. package/dist/windowing/WindowManager/WindowManager.svelte.d.ts +38 -0
  57. package/dist/windowing/WindowManager/WindowManager.svelte.d.ts.map +1 -0
  58. package/dist/windowing/WindowManager/index.d.ts +2 -0
  59. package/dist/windowing/WindowManager/index.d.ts.map +1 -0
  60. package/dist/windowing/WindowManager/index.js +1 -0
  61. package/dist/windowing/index.d.ts +5 -0
  62. package/dist/windowing/index.d.ts.map +1 -0
  63. package/dist/windowing/index.js +3 -0
  64. package/dist/windowing/windowStore.svelte.d.ts +49 -0
  65. package/dist/windowing/windowStore.svelte.d.ts.map +1 -0
  66. package/dist/windowing/windowStore.svelte.js +170 -0
  67. package/package.json +1 -1
@@ -0,0 +1,427 @@
1
+ <!--
2
+ @component Modal
3
+
4
+ Win2K × Basquiat — Dialog with canvas surface, bold 3px border, title bar header.
5
+ Follows WAI-ARIA Dialog (Modal) pattern: focus trap, restore focus on close,
6
+ aria-labelledby, aria-describedby, aria-modal. Escape and backdrop close.
7
+ For full inert support (background not focusable), render modal in a portal
8
+ and set inert on main content when modalOpenCount > 0 (see modalStore).
9
+ -->
10
+ <script lang="ts">
11
+ import type { Snippet } from 'svelte';
12
+ import { createFocusTrap, Keys, generateId } from '../../utils/keyboard.js';
13
+ import { modalOpenCount } from '../modalStore.js';
14
+
15
+ type ModalSize = 'sm' | 'md' | 'lg' | 'xl' | 'full';
16
+ type ModalPosition = 'center' | 'top';
17
+ type ScrollBehavior = 'inside' | 'outside';
18
+ /** APG: 'first' = first focusable in body; 'title' = dialog title (for long content so AT reads title first). */
19
+ type InitialFocusStrategy = 'first' | 'title';
20
+
21
+ interface Props {
22
+ open?: boolean;
23
+ id?: string;
24
+ title?: string;
25
+ size?: ModalSize;
26
+ position?: ModalPosition;
27
+ scrollBehavior?: ScrollBehavior;
28
+ showCloseButton?: boolean;
29
+ closeOnBackdrop?: boolean;
30
+ closeOnEscape?: boolean;
31
+ preventClose?: boolean;
32
+ /** Element to focus when opening (overrides initialFocusStrategy). */
33
+ initialFocus?: HTMLElement | null;
34
+ /** APG: 'first' = first focusable; 'title' = focus title (tabindex="-1") so screen readers announce it first. */
35
+ initialFocusStrategy?: InitialFocusStrategy;
36
+ enableFocusTrap?: boolean;
37
+ header?: Snippet;
38
+ footer?: Snippet;
39
+ children?: Snippet;
40
+ class?: string;
41
+ onclose?: () => void;
42
+ closeBtnAriaLabel?: string;
43
+ testId?: string;
44
+ }
45
+
46
+ let {
47
+ open = false,
48
+ id,
49
+ title = 'Dialog',
50
+ size = 'md',
51
+ position = 'center',
52
+ scrollBehavior = 'inside',
53
+ showCloseButton = true,
54
+ closeOnBackdrop = true,
55
+ closeOnEscape = true,
56
+ preventClose = false,
57
+ initialFocus,
58
+ initialFocusStrategy = 'first',
59
+ enableFocusTrap = true,
60
+ header,
61
+ footer,
62
+ children,
63
+ class: className = '',
64
+ onclose,
65
+ closeBtnAriaLabel,
66
+ testId
67
+ }: Props = $props();
68
+
69
+ const modalId = $derived(id ?? generateId('modal'));
70
+ const titleId = $derived(`${modalId}-title`);
71
+ const descriptionId = $derived(`${modalId}-description`);
72
+
73
+ let dialogElement = $state<HTMLDivElement | null>(null);
74
+ let focusTrap: ReturnType<typeof createFocusTrap> | null = null;
75
+ let previouslyFocusedElement: HTMLElement | null = null;
76
+ let hasActivated = $state(false);
77
+
78
+ function getScrollbarWidth(): number {
79
+ return window.innerWidth - document.documentElement.clientWidth;
80
+ }
81
+
82
+ const FOCUSABLE =
83
+ 'button:not([disabled]):not([tabindex="-1"]), [href]:not([tabindex="-1"]), input:not([disabled]):not([tabindex="-1"]), select:not([disabled]):not([tabindex="-1"]), textarea:not([disabled]):not([tabindex="-1"]), [tabindex]:not([tabindex="-1"])';
84
+
85
+ function setInitialFocus() {
86
+ if (initialFocus) {
87
+ initialFocus.focus();
88
+ return;
89
+ }
90
+ if (!dialogElement) return;
91
+ const titleEl = dialogElement.querySelector<HTMLElement>('.salmex-modal-title-wrap');
92
+ const body = dialogElement.querySelector('.salmex-modal-body');
93
+ const first = body?.querySelector<HTMLElement>(FOCUSABLE);
94
+ const closeBtn = dialogElement.querySelector<HTMLElement>('.salmex-modal-close');
95
+
96
+ if (initialFocusStrategy === 'title' && titleEl) {
97
+ titleEl.focus();
98
+ return;
99
+ }
100
+ if (first) {
101
+ first.focus();
102
+ } else if (closeBtn) {
103
+ closeBtn.focus();
104
+ } else if (titleEl) {
105
+ titleEl.focus();
106
+ }
107
+ }
108
+
109
+ $effect(() => {
110
+ if (open) {
111
+ if (!hasActivated && dialogElement) {
112
+ previouslyFocusedElement = document.activeElement as HTMLElement | null;
113
+ modalOpenCount.update((n) => n + 1);
114
+ if (enableFocusTrap) {
115
+ focusTrap = createFocusTrap(dialogElement);
116
+ focusTrap.activate();
117
+ }
118
+ const scrollbarWidth = getScrollbarWidth();
119
+ document.body.style.overflow = 'hidden';
120
+ if (scrollbarWidth > 0) {
121
+ document.body.style.paddingRight = `${scrollbarWidth}px`;
122
+ }
123
+ hasActivated = true;
124
+ setTimeout(setInitialFocus, 0);
125
+ }
126
+ } else {
127
+ if (hasActivated) {
128
+ modalOpenCount.update((n) => Math.max(0, n - 1));
129
+ focusTrap?.deactivate();
130
+ focusTrap = null;
131
+ hasActivated = false;
132
+ document.body.style.overflow = '';
133
+ document.body.style.paddingRight = '';
134
+ if (previouslyFocusedElement?.focus) {
135
+ const el = previouslyFocusedElement;
136
+ setTimeout(() => el.focus(), 0);
137
+ }
138
+ previouslyFocusedElement = null;
139
+ }
140
+ }
141
+ });
142
+
143
+ function handleClose() {
144
+ if (!preventClose) onclose?.();
145
+ }
146
+
147
+ function handleBackdropClick(e: MouseEvent) {
148
+ if (closeOnBackdrop && e.target === e.currentTarget) handleClose();
149
+ }
150
+
151
+ function handleKeyDown(e: KeyboardEvent) {
152
+ if (closeOnEscape && e.key === Keys.Escape) {
153
+ e.preventDefault();
154
+ handleClose();
155
+ }
156
+ }
157
+
158
+ const closeLabel = $derived(closeBtnAriaLabel ?? 'Close dialog');
159
+ </script>
160
+
161
+ <svelte:window onkeydown={open ? handleKeyDown : undefined} />
162
+
163
+ {#if open}
164
+ <!-- Backdrop -->
165
+ <div
166
+ class="salmex-modal-backdrop salmex-modal-position-{position}"
167
+ role="presentation"
168
+ onclick={handleBackdropClick}
169
+ onkeydown={() => {}}
170
+ >
171
+ <div
172
+ bind:this={dialogElement}
173
+ id={modalId}
174
+ class="salmex-modal-container salmex-modal-{size} salmex-modal-scroll-{scrollBehavior} {className}"
175
+ role="dialog"
176
+ aria-modal="true"
177
+ aria-labelledby={titleId}
178
+ aria-describedby={children ? descriptionId : undefined}
179
+ data-testid={testId}
180
+ >
181
+ {#if header || title || showCloseButton}
182
+ <div class="salmex-modal-header">
183
+ <div id={titleId} class="salmex-modal-title-wrap" tabindex="-1">
184
+ {#if header}
185
+ {@render header()}
186
+ {:else if title}
187
+ <h2 class="salmex-modal-title">{title}</h2>
188
+ {/if}
189
+ </div>
190
+ {#if showCloseButton && !preventClose}
191
+ <button
192
+ type="button"
193
+ class="salmex-modal-close"
194
+ onclick={handleClose}
195
+ aria-label={closeLabel}
196
+ >
197
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
198
+ <line x1="18" y1="6" x2="6" y2="18" />
199
+ <line x1="6" y1="6" x2="18" y2="18" />
200
+ </svg>
201
+ </button>
202
+ {/if}
203
+ </div>
204
+ {/if}
205
+ {#if children}
206
+ <div id={descriptionId} class="salmex-modal-body">
207
+ {@render children()}
208
+ </div>
209
+ {/if}
210
+ {#if footer}
211
+ <div class="salmex-modal-footer">
212
+ {@render footer()}
213
+ </div>
214
+ {/if}
215
+ </div>
216
+ </div>
217
+ {/if}
218
+
219
+ <style>
220
+ .salmex-modal-backdrop {
221
+ position: fixed;
222
+ inset: 0;
223
+ z-index: var(--salmex-z-modal-backdrop, 1040);
224
+ display: flex;
225
+ justify-content: center;
226
+ padding: var(--salmex-space-6);
227
+ background: rgb(0 0 0 / 0.6);
228
+ overflow-y: auto;
229
+ animation: salmex-modal-fade-in var(--salmex-transition-base);
230
+ }
231
+
232
+ .salmex-modal-position-center {
233
+ align-items: center;
234
+ }
235
+
236
+ .salmex-modal-position-top {
237
+ align-items: flex-start;
238
+ padding-top: 3rem;
239
+ }
240
+
241
+ @keyframes salmex-modal-fade-in {
242
+ from {
243
+ opacity: 0;
244
+ }
245
+ to {
246
+ opacity: 1;
247
+ }
248
+ }
249
+
250
+ .salmex-modal-container {
251
+ position: relative;
252
+ display: flex;
253
+ flex-direction: column;
254
+ width: 100%;
255
+ max-height: calc(100vh - 2rem);
256
+ background: rgb(var(--salmex-window-surface));
257
+ border: 3px solid rgb(var(--salmex-button-dark-edge));
258
+ box-shadow: var(--salmex-shadow-lg);
259
+ animation: salmex-modal-scale-in var(--salmex-transition-slow) ease-out;
260
+ overflow: hidden;
261
+ }
262
+
263
+ @keyframes salmex-modal-scale-in {
264
+ from {
265
+ opacity: 0;
266
+ transform: scale(0.97) translateY(8px);
267
+ }
268
+ to {
269
+ opacity: 1;
270
+ transform: scale(1) translateY(0);
271
+ }
272
+ }
273
+
274
+ .salmex-modal-sm {
275
+ max-width: 24rem;
276
+ }
277
+ .salmex-modal-md {
278
+ max-width: 28rem;
279
+ }
280
+ .salmex-modal-lg {
281
+ max-width: 32rem;
282
+ }
283
+ .salmex-modal-xl {
284
+ max-width: 40rem;
285
+ }
286
+ .salmex-modal-full {
287
+ max-width: 56rem;
288
+ }
289
+
290
+ .salmex-modal-scroll-inside {
291
+ max-height: calc(100vh - 4rem);
292
+ }
293
+
294
+ .salmex-modal-scroll-inside .salmex-modal-body {
295
+ overflow-y: auto;
296
+ }
297
+
298
+ /* Title bar — Win2K gradient */
299
+ .salmex-modal-header {
300
+ display: flex;
301
+ align-items: center;
302
+ justify-content: space-between;
303
+ gap: var(--salmex-space-4);
304
+ padding: var(--salmex-space-4);
305
+ background: linear-gradient(
306
+ 90deg,
307
+ rgb(var(--salmex-electric-blue)),
308
+ rgb(var(--salmex-titlebar-bold))
309
+ );
310
+ color: rgb(var(--salmex-chalk-white));
311
+ border-bottom: 3px solid rgb(var(--salmex-button-dark-edge));
312
+ flex-shrink: 0;
313
+ font-family: var(--salmex-font-display);
314
+ }
315
+
316
+ :global([data-theme='dark']) .salmex-modal-header {
317
+ background: linear-gradient(
318
+ 90deg,
319
+ rgb(0 100 220),
320
+ rgb(0 140 255)
321
+ );
322
+ }
323
+
324
+ .salmex-modal-title-wrap {
325
+ flex: 1;
326
+ min-width: 0;
327
+ outline: none;
328
+ }
329
+
330
+ .salmex-modal-title-wrap:focus-visible {
331
+ outline: 2px solid rgb(var(--salmex-crown-yellow));
332
+ outline-offset: 2px;
333
+ }
334
+
335
+ .salmex-modal-title {
336
+ margin: 0;
337
+ font-size: var(--salmex-font-size-md);
338
+ font-weight: 900;
339
+ text-transform: uppercase;
340
+ letter-spacing: 0.5px;
341
+ line-height: 1.3;
342
+ }
343
+
344
+ .salmex-modal-close {
345
+ display: flex;
346
+ align-items: center;
347
+ justify-content: center;
348
+ width: 32px;
349
+ height: 32px;
350
+ padding: 0;
351
+ background: rgb(var(--salmex-button-face));
352
+ border: 2px solid rgb(var(--salmex-button-dark-edge));
353
+ color: rgb(var(--salmex-text-primary));
354
+ cursor: pointer;
355
+ flex-shrink: 0;
356
+ box-shadow:
357
+ inset 1px 1px 0 rgb(var(--salmex-button-highlight)),
358
+ inset -1px -1px 0 rgb(var(--salmex-button-shadow)),
359
+ 2px 2px 0 rgb(0 0 0 / 0.3);
360
+ transition: all var(--salmex-transition-fast);
361
+ }
362
+
363
+ .salmex-modal-close:hover {
364
+ background: rgb(var(--salmex-button-light));
365
+ }
366
+
367
+ .salmex-modal-close:active {
368
+ box-shadow:
369
+ inset -1px -1px 0 rgb(var(--salmex-button-highlight)),
370
+ inset 1px 1px 0 rgb(var(--salmex-button-shadow)),
371
+ 1px 1px 0 rgb(0 0 0 / 0.3);
372
+ transform: translate(1px, 1px);
373
+ }
374
+
375
+ .salmex-modal-close:focus-visible {
376
+ outline: none;
377
+ box-shadow:
378
+ inset 1px 1px 0 rgb(var(--salmex-button-highlight)),
379
+ inset -1px -1px 0 rgb(var(--salmex-button-shadow)),
380
+ 2px 2px 0 rgb(0 0 0 / 0.3),
381
+ 0 0 0 2px rgb(var(--salmex-midnight-black)),
382
+ 0 0 0 5px rgb(var(--salmex-crown-yellow));
383
+ }
384
+
385
+ .salmex-modal-body {
386
+ padding: var(--salmex-space-6);
387
+ flex: 1;
388
+ min-height: 0;
389
+ font-family: var(--salmex-font-system);
390
+ color: rgb(var(--salmex-text-primary));
391
+ }
392
+
393
+ .salmex-modal-footer {
394
+ display: flex;
395
+ align-items: center;
396
+ justify-content: flex-end;
397
+ gap: var(--salmex-space-3);
398
+ padding: var(--salmex-space-4);
399
+ border-top: 3px solid rgb(var(--salmex-button-dark-edge));
400
+ background: rgb(var(--salmex-bg-secondary));
401
+ flex-shrink: 0;
402
+ }
403
+
404
+ @media (prefers-reduced-motion: reduce) {
405
+ .salmex-modal-backdrop,
406
+ .salmex-modal-container {
407
+ animation: none;
408
+ }
409
+ }
410
+
411
+ @media (max-width: 640px) {
412
+ .salmex-modal-backdrop {
413
+ padding: 0;
414
+ align-items: flex-end;
415
+ }
416
+
417
+ .salmex-modal-position-top {
418
+ padding-top: 0;
419
+ }
420
+
421
+ .salmex-modal-container {
422
+ max-width: 100%;
423
+ max-height: 90vh;
424
+ margin: 0;
425
+ }
426
+ }
427
+ </style>
@@ -0,0 +1,43 @@
1
+ import type { Snippet } from 'svelte';
2
+ type ModalSize = 'sm' | 'md' | 'lg' | 'xl' | 'full';
3
+ type ModalPosition = 'center' | 'top';
4
+ type ScrollBehavior = 'inside' | 'outside';
5
+ /** APG: 'first' = first focusable in body; 'title' = dialog title (for long content so AT reads title first). */
6
+ type InitialFocusStrategy = 'first' | 'title';
7
+ interface Props {
8
+ open?: boolean;
9
+ id?: string;
10
+ title?: string;
11
+ size?: ModalSize;
12
+ position?: ModalPosition;
13
+ scrollBehavior?: ScrollBehavior;
14
+ showCloseButton?: boolean;
15
+ closeOnBackdrop?: boolean;
16
+ closeOnEscape?: boolean;
17
+ preventClose?: boolean;
18
+ /** Element to focus when opening (overrides initialFocusStrategy). */
19
+ initialFocus?: HTMLElement | null;
20
+ /** APG: 'first' = first focusable; 'title' = focus title (tabindex="-1") so screen readers announce it first. */
21
+ initialFocusStrategy?: InitialFocusStrategy;
22
+ enableFocusTrap?: boolean;
23
+ header?: Snippet;
24
+ footer?: Snippet;
25
+ children?: Snippet;
26
+ class?: string;
27
+ onclose?: () => void;
28
+ closeBtnAriaLabel?: string;
29
+ testId?: string;
30
+ }
31
+ /**
32
+ * Modal
33
+ *
34
+ * Win2K × Basquiat — Dialog with canvas surface, bold 3px border, title bar header.
35
+ * Follows WAI-ARIA Dialog (Modal) pattern: focus trap, restore focus on close,
36
+ * aria-labelledby, aria-describedby, aria-modal. Escape and backdrop close.
37
+ * For full inert support (background not focusable), render modal in a portal
38
+ * and set inert on main content when modalOpenCount > 0 (see modalStore).
39
+ */
40
+ declare const Modal: import("svelte").Component<Props, {}, "">;
41
+ type Modal = ReturnType<typeof Modal>;
42
+ export default Modal;
43
+ //# sourceMappingURL=Modal.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Modal.svelte.d.ts","sourceRoot":"","sources":["../../../src/dialogs/Modal/Modal.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAKtC,KAAK,SAAS,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,MAAM,CAAC;AACpD,KAAK,aAAa,GAAG,QAAQ,GAAG,KAAK,CAAC;AACtC,KAAK,cAAc,GAAG,QAAQ,GAAG,SAAS,CAAC;AAC3C,iHAAiH;AACjH,KAAK,oBAAoB,GAAG,OAAO,GAAG,OAAO,CAAC;AAE9C,UAAU,KAAK;IACd,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,sEAAsE;IACtE,YAAY,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IAClC,iHAAiH;IACjH,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;IAC5C,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AA0KD;;;;;;;;GAQG;AACH,QAAA,MAAM,KAAK,2CAAwC,CAAC;AACpD,KAAK,KAAK,GAAG,UAAU,CAAC,OAAO,KAAK,CAAC,CAAC;AACtC,eAAe,KAAK,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { default as Modal } from './Modal.svelte';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/dialogs/Modal/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1 @@
1
+ export { default as Modal } from './Modal.svelte';
@@ -0,0 +1,3 @@
1
+ export { Modal } from './Modal/index.js';
2
+ export { modalOpenCount } from './modalStore.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/dialogs/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { Modal } from './Modal/index.js';
2
+ export { modalOpenCount } from './modalStore.js';
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Count of currently open Modal instances.
3
+ * Use this to set the `inert` attribute on main content when any modal is open,
4
+ * so keyboard and screen reader users cannot interact with background content (APG).
5
+ *
6
+ * Example (when using a portal so the modal renders outside the inert wrapper):
7
+ * <div inert={$modalOpenCount > 0}>
8
+ * <Sidebar />
9
+ * <main>...</main>
10
+ * </div>
11
+ */
12
+ export declare const modalOpenCount: import("svelte/store").Writable<number>;
13
+ //# sourceMappingURL=modalStore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"modalStore.d.ts","sourceRoot":"","sources":["../../src/dialogs/modalStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,eAAO,MAAM,cAAc,yCAAc,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Count of currently open Modal instances.
3
+ * Use this to set the `inert` attribute on main content when any modal is open,
4
+ * so keyboard and screen reader users cannot interact with background content (APG).
5
+ *
6
+ * Example (when using a portal so the modal renders outside the inert wrapper):
7
+ * <div inert={$modalOpenCount > 0}>
8
+ * <Sidebar />
9
+ * <main>...</main>
10
+ * </div>
11
+ */
12
+ import { writable } from 'svelte/store';
13
+ export const modalOpenCount = writable(0);
@@ -5,69 +5,69 @@
5
5
  Dismissible, expandable, optional title; role="alert" for errors.
6
6
  -->
7
7
  <script lang="ts">
8
- import type { Snippet } from 'svelte';
9
- import { cn } from '../../utils/cn.js';
10
-
11
- type AlertType = 'success' | 'error' | 'warning' | 'info' | 'neutral';
12
- type AlertVariant = 'standard' | 'slim' | 'bordered';
13
- type HeadingLevel = 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'p';
14
-
15
- interface Props {
16
- type?: AlertType;
17
- variant?: AlertVariant;
18
- title?: string;
19
- headingLevel?: HeadingLevel;
20
- dismissible?: boolean;
21
- showIcon?: boolean;
22
- expandable?: boolean;
23
- defaultExpanded?: boolean;
24
- class?: string;
25
- children?: Snippet;
26
- actions?: Snippet;
27
- onclose?: () => void;
28
- closeBtnAriaLabel?: string;
29
- id?: string;
30
- }
31
-
32
- let {
33
- type = 'info',
34
- variant = 'standard',
35
- title = '',
36
- headingLevel = 'h3',
37
- dismissible = false,
38
- showIcon = true,
39
- expandable = false,
40
- defaultExpanded = true,
41
- class: className = '',
42
- children,
43
- actions,
44
- onclose,
45
- closeBtnAriaLabel,
46
- id
47
- }: Props = $props();
48
-
49
- let isExpanded = $state(true);
50
-
51
- $effect(() => {
52
- isExpanded = defaultExpanded;
53
- });
54
-
55
- const generatedId = `alert-${Math.random().toString(36).slice(2, 9)}`;
56
- const alertId = $derived(id ?? generatedId);
57
- const contentId = $derived(`${alertId}-content`);
58
- const titleId = $derived(`${alertId}-title`);
59
-
60
- const ariaRole = $derived(type === 'error' || type === 'warning' ? 'alert' : 'status');
61
- const ariaLive = $derived(type === 'error' ? 'assertive' : 'polite');
62
- const closeLabel = $derived(closeBtnAriaLabel ?? `Close ${title || type} notification`);
63
-
64
- function handleDismiss() {
65
- onclose?.();
66
- }
67
-
68
- function toggleExpanded() {
69
- isExpanded = !isExpanded;
70
- }
8
+ import type { Snippet } from 'svelte';
9
+ import { cn } from '../../utils/cn.js';
10
+
11
+ type AlertType = 'success' | 'error' | 'warning' | 'info' | 'neutral';
12
+ type AlertVariant = 'standard' | 'slim' | 'bordered';
13
+ type HeadingLevel = 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'p';
14
+
15
+ interface Props {
16
+ type?: AlertType;
17
+ variant?: AlertVariant;
18
+ title?: string;
19
+ headingLevel?: HeadingLevel;
20
+ dismissible?: boolean;
21
+ showIcon?: boolean;
22
+ expandable?: boolean;
23
+ defaultExpanded?: boolean;
24
+ class?: string;
25
+ children?: Snippet;
26
+ actions?: Snippet;
27
+ onclose?: () => void;
28
+ closeBtnAriaLabel?: string;
29
+ id?: string;
30
+ }
31
+
32
+ let {
33
+ type = 'info',
34
+ variant = 'standard',
35
+ title = '',
36
+ headingLevel = 'h3',
37
+ dismissible = false,
38
+ showIcon = true,
39
+ expandable = false,
40
+ defaultExpanded = true,
41
+ class: className = '',
42
+ children,
43
+ actions,
44
+ onclose,
45
+ closeBtnAriaLabel,
46
+ id
47
+ }: Props = $props();
48
+
49
+ let isExpanded = $state(true);
50
+
51
+ $effect(() => {
52
+ isExpanded = defaultExpanded;
53
+ });
54
+
55
+ const generatedId = `alert-${Math.random().toString(36).slice(2, 9)}`;
56
+ const alertId = $derived(id ?? generatedId);
57
+ const contentId = $derived(`${alertId}-content`);
58
+ const titleId = $derived(`${alertId}-title`);
59
+
60
+ const ariaRole = $derived(type === 'error' || type === 'warning' ? 'alert' : 'status');
61
+ const ariaLive = $derived(type === 'error' ? 'assertive' : 'polite');
62
+ const closeLabel = $derived(closeBtnAriaLabel ?? `Close ${title || type} notification`);
63
+
64
+ function handleDismiss() {
65
+ onclose?.();
66
+ }
67
+
68
+ function toggleExpanded() {
69
+ isExpanded = !isExpanded;
70
+ }
71
71
  </script>
72
72
 
73
73
  <div
@@ -1 +1 @@
1
- {"version":3,"file":"Alert.svelte.d.ts","sourceRoot":"","sources":["../../../src/feedback/Alert/Alert.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAIrC,KAAK,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAAC;AACtE,KAAK,YAAY,GAAG,UAAU,GAAG,MAAM,GAAG,UAAU,CAAC;AACrD,KAAK,YAAY,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,GAAG,CAAC;AAE3D,UAAU,KAAK;IACd,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,EAAE,CAAC,EAAE,MAAM,CAAC;CACZ;AAmHF;;;;;GAKG;AACH,QAAA,MAAM,KAAK,2CAAwC,CAAC;AACpD,KAAK,KAAK,GAAG,UAAU,CAAC,OAAO,KAAK,CAAC,CAAC;AACtC,eAAe,KAAK,CAAC"}
1
+ {"version":3,"file":"Alert.svelte.d.ts","sourceRoot":"","sources":["../../../src/feedback/Alert/Alert.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAItC,KAAK,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAAC;AACtE,KAAK,YAAY,GAAG,UAAU,GAAG,MAAM,GAAG,UAAU,CAAC;AACrD,KAAK,YAAY,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,GAAG,CAAC;AAE3D,UAAU,KAAK;IACd,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,EAAE,CAAC,EAAE,MAAM,CAAC;CACZ;AAmHD;;;;;GAKG;AACH,QAAA,MAAM,KAAK,2CAAwC,CAAC;AACpD,KAAK,KAAK,GAAG,UAAU,CAAC,OAAO,KAAK,CAAC,CAAC;AACtC,eAAe,KAAK,CAAC"}