@classic-homes/theme-svelte 0.1.54 → 0.1.56
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/dist/__tests__/FileUpload.test.js +45 -0
- package/dist/lib/components/Button.svelte +17 -1
- package/dist/lib/components/Checkbox.svelte +3 -2
- package/dist/lib/components/Combobox.svelte +4 -4
- package/dist/lib/components/DataPanel.svelte +11 -5
- package/dist/lib/components/DateTimePicker.svelte +3 -2
- package/dist/lib/components/FileUpload.svelte +39 -4
- package/dist/lib/components/FileUpload.svelte.d.ts +6 -0
- package/dist/lib/components/HeaderSearch.svelte +5 -0
- package/dist/lib/components/RadioGroup.svelte +3 -2
- package/dist/lib/components/Select.svelte +3 -2
- package/dist/lib/components/Signature.svelte +3 -2
- package/dist/lib/components/Tabs.svelte +3 -2
- package/dist/lib/components/data-table/DataTableColumnFilter.svelte +2 -2
- package/dist/lib/components/data-table/DataTableColumnFilter.svelte.d.ts +33 -13
- package/dist/lib/components/data-table/DataTableColumnVisibility.svelte +2 -2
- package/dist/lib/components/data-table/DataTableColumnVisibility.svelte.d.ts +27 -7
- package/dist/lib/components/data-table/DataTableFacetedFilter.svelte +2 -2
- package/dist/lib/components/data-table/DataTableFacetedFilter.svelte.d.ts +36 -17
- package/dist/lib/components/data-table/DataTablePagination.svelte +2 -2
- package/dist/lib/components/data-table/DataTablePagination.svelte.d.ts +31 -11
- package/dist/lib/components/data-table/DataTableSearch.svelte +2 -2
- package/dist/lib/components/data-table/DataTableSearch.svelte.d.ts +31 -11
- package/dist/lib/components/data-table/DataTableToolbar.svelte +2 -2
- package/dist/lib/components/data-table/DataTableToolbar.svelte.d.ts +37 -17
- package/dist/lib/composables/useAnnounce.svelte.js +9 -9
- package/dist/lib/composables/useArrowNavigation.svelte.js +5 -4
- package/dist/lib/composables/useEscapeKey.svelte.d.ts +4 -2
- package/dist/lib/composables/useEscapeKey.svelte.js +3 -2
- package/dist/lib/composables/useFocusRef.svelte.js +4 -3
- package/dist/lib/composables/useFocusTrap.svelte.d.ts +9 -3
- package/dist/lib/composables/useFocusTrap.svelte.js +34 -28
- package/dist/lib/composables/useRovingTabindex.svelte.js +4 -3
- package/dist/lib/utils.d.ts +0 -10
- package/dist/lib/utils.js +0 -13
- package/package.json +1 -1
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { render, screen } from '@testing-library/svelte';
|
|
3
|
+
import FileUpload from '../lib/components/FileUpload.svelte';
|
|
4
|
+
describe('FileUpload accepted-types label', () => {
|
|
5
|
+
it('derives a friendly label from MIME types instead of showing raw values', () => {
|
|
6
|
+
render(FileUpload, {
|
|
7
|
+
props: {
|
|
8
|
+
label: 'Upload files',
|
|
9
|
+
acceptedTypes: [
|
|
10
|
+
'image/jpeg',
|
|
11
|
+
'image/png',
|
|
12
|
+
'application/pdf',
|
|
13
|
+
'video/mp4',
|
|
14
|
+
'video/quicktime',
|
|
15
|
+
],
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
expect(screen.getByText(/Images, PDF, Videos \(max/)).toBeInTheDocument();
|
|
19
|
+
// Raw MIME types should not be shown
|
|
20
|
+
expect(screen.queryByText(/image\/jpeg/)).not.toBeInTheDocument();
|
|
21
|
+
});
|
|
22
|
+
it('uses the default accepted types (Images, PDF) when none are provided', () => {
|
|
23
|
+
render(FileUpload, { props: { label: 'Upload files' } });
|
|
24
|
+
expect(screen.getByText(/^Images, PDF \(max/)).toBeInTheDocument();
|
|
25
|
+
});
|
|
26
|
+
it('respects an explicit typesLabel override', () => {
|
|
27
|
+
render(FileUpload, {
|
|
28
|
+
props: {
|
|
29
|
+
label: 'Upload files',
|
|
30
|
+
acceptedTypes: ['image/png', 'video/mp4'],
|
|
31
|
+
typesLabel: 'Photos and clips',
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
expect(screen.getByText(/^Photos and clips \(max/)).toBeInTheDocument();
|
|
35
|
+
});
|
|
36
|
+
it('falls back to the uppercased subtype for unmapped types', () => {
|
|
37
|
+
render(FileUpload, {
|
|
38
|
+
props: {
|
|
39
|
+
label: 'Upload files',
|
|
40
|
+
acceptedTypes: ['application/zip'],
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
expect(screen.getByText(/^ZIP \(max/)).toBeInTheDocument();
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -110,7 +110,23 @@
|
|
|
110
110
|
{/snippet}
|
|
111
111
|
|
|
112
112
|
{#if href}
|
|
113
|
-
|
|
113
|
+
<!-- Anchors can't be natively disabled: remove from tab order, expose
|
|
114
|
+
aria-disabled, drop the href, and block clicks when disabled/loading -->
|
|
115
|
+
<a
|
|
116
|
+
href={disabled || loading ? undefined : href}
|
|
117
|
+
class={classes}
|
|
118
|
+
aria-disabled={disabled || loading || undefined}
|
|
119
|
+
aria-busy={loading || undefined}
|
|
120
|
+
tabindex={disabled || loading ? -1 : undefined}
|
|
121
|
+
onclick={(e) => {
|
|
122
|
+
if (disabled || loading) {
|
|
123
|
+
e.preventDefault();
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
onclick?.(e);
|
|
127
|
+
}}
|
|
128
|
+
{...restProps}
|
|
129
|
+
>
|
|
114
130
|
{@render buttonContent()}
|
|
115
131
|
</a>
|
|
116
132
|
{:else}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
3
|
import { Checkbox as CheckboxPrimitive } from 'bits-ui';
|
|
4
|
-
import { cn
|
|
4
|
+
import { cn } from '../utils.js';
|
|
5
5
|
|
|
6
6
|
interface Props {
|
|
7
7
|
checked?: boolean;
|
|
@@ -32,7 +32,8 @@
|
|
|
32
32
|
}: Props = $props();
|
|
33
33
|
|
|
34
34
|
// Generate a stable unique ID if none provided (for accessibility)
|
|
35
|
-
const
|
|
35
|
+
const uid = $props.id();
|
|
36
|
+
const generatedId = `checkbox-${uid}`;
|
|
36
37
|
const effectiveId = $derived(id ?? generatedId);
|
|
37
38
|
|
|
38
39
|
function handleCheckedChange(newChecked: boolean | 'indeterminate') {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { Combobox as ComboboxPrimitive } from 'bits-ui';
|
|
3
3
|
import { cn } from '../utils.js';
|
|
4
|
-
import {
|
|
4
|
+
import { computeDescribedBy } from '../utils/form.js';
|
|
5
5
|
import Spinner from './Spinner.svelte';
|
|
6
6
|
import Label from './Label.svelte';
|
|
7
7
|
import LiveRegion from './LiveRegion.svelte';
|
|
@@ -66,9 +66,9 @@
|
|
|
66
66
|
class: className,
|
|
67
67
|
}: Props = $props();
|
|
68
68
|
|
|
69
|
-
// Generate unique IDs for accessibility
|
|
70
|
-
const
|
|
71
|
-
const componentId = $derived(id || `combobox-${
|
|
69
|
+
// Generate unique IDs for accessibility ($props.id() is SSR-stable)
|
|
70
|
+
const uid = $props.id();
|
|
71
|
+
const componentId = $derived(id || `combobox-${uid}`);
|
|
72
72
|
const hintId = $derived(`${componentId}-hint`);
|
|
73
73
|
const errorId = $derived(`${componentId}-error`);
|
|
74
74
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { cn } from '../utils.js';
|
|
3
|
-
import { onMount } from 'svelte';
|
|
3
|
+
import { onMount, onDestroy } from 'svelte';
|
|
4
4
|
import type { Snippet } from 'svelte';
|
|
5
5
|
import type {
|
|
6
6
|
PanelEdge,
|
|
@@ -222,11 +222,13 @@
|
|
|
222
222
|
}
|
|
223
223
|
|
|
224
224
|
saveTimeout = setTimeout(() => {
|
|
225
|
+
// Persist the resolved values so controlled mode saves what's shown,
|
|
226
|
+
// not the untouched internal defaults
|
|
225
227
|
savePanelState(persistKey, {
|
|
226
|
-
mode
|
|
227
|
-
variant
|
|
228
|
-
edge
|
|
229
|
-
isExpanded
|
|
228
|
+
mode,
|
|
229
|
+
variant,
|
|
230
|
+
edge,
|
|
231
|
+
isExpanded,
|
|
230
232
|
detachedPosition,
|
|
231
233
|
detachedSize,
|
|
232
234
|
pinnedSize,
|
|
@@ -236,6 +238,10 @@
|
|
|
236
238
|
}, 300);
|
|
237
239
|
}
|
|
238
240
|
|
|
241
|
+
onDestroy(() => {
|
|
242
|
+
if (saveTimeout) clearTimeout(saveTimeout);
|
|
243
|
+
});
|
|
244
|
+
|
|
239
245
|
// Initialize from persistence on mount
|
|
240
246
|
onMount(() => {
|
|
241
247
|
// Update viewport dimensions
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { Popover, Calendar } from 'bits-ui';
|
|
3
3
|
import { tick } from 'svelte';
|
|
4
|
-
import { cn
|
|
4
|
+
import { cn } from '../utils.js';
|
|
5
5
|
import { parseDate, toISOString, parseDateInput } from '../utils/date.js';
|
|
6
6
|
import Label from './Label.svelte';
|
|
7
7
|
|
|
@@ -69,7 +69,8 @@
|
|
|
69
69
|
}: Props = $props();
|
|
70
70
|
|
|
71
71
|
// Generate unique IDs for accessibility using sequential counter
|
|
72
|
-
const
|
|
72
|
+
const uid = $props.id();
|
|
73
|
+
const generatedId = `datetimepicker-${uid}`;
|
|
73
74
|
const componentId = $derived(id || generatedId);
|
|
74
75
|
const hintId = $derived(`${componentId}-hint`);
|
|
75
76
|
const errorId = $derived(`${componentId}-error`);
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* - Accessible with keyboard navigation
|
|
13
13
|
*/
|
|
14
14
|
import type { FileMetadata } from '../types/components.js';
|
|
15
|
-
import { cn
|
|
15
|
+
import { cn } from '../utils.js';
|
|
16
16
|
import Button from './Button.svelte';
|
|
17
17
|
|
|
18
18
|
interface Props {
|
|
@@ -38,11 +38,18 @@
|
|
|
38
38
|
label?: string;
|
|
39
39
|
/** Hint text shown below the drop zone */
|
|
40
40
|
hint?: string;
|
|
41
|
+
/**
|
|
42
|
+
* Human-friendly label for the accepted file types shown in the drop zone.
|
|
43
|
+
* When omitted, a readable label is derived from `acceptedTypes`
|
|
44
|
+
* (e.g. "Images, PDF, Videos") instead of showing raw MIME types.
|
|
45
|
+
*/
|
|
46
|
+
typesLabel?: string;
|
|
41
47
|
/** Additional classes */
|
|
42
48
|
class?: string;
|
|
43
49
|
}
|
|
44
50
|
|
|
45
|
-
const
|
|
51
|
+
const uid = $props.id();
|
|
52
|
+
const generatedId = `file-upload-${uid}`;
|
|
46
53
|
|
|
47
54
|
let {
|
|
48
55
|
id = generatedId,
|
|
@@ -56,6 +63,7 @@
|
|
|
56
63
|
onUpload,
|
|
57
64
|
label,
|
|
58
65
|
hint,
|
|
66
|
+
typesLabel,
|
|
59
67
|
class: className,
|
|
60
68
|
}: Props = $props();
|
|
61
69
|
|
|
@@ -68,6 +76,33 @@
|
|
|
68
76
|
|
|
69
77
|
const displayError = $derived(externalError || internalError);
|
|
70
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Readable label for the accepted types shown in the drop zone.
|
|
81
|
+
* Uses the explicit `typesLabel` when provided, otherwise groups the
|
|
82
|
+
* accepted MIME types into friendly names (Images, PDF, Videos, ...).
|
|
83
|
+
*/
|
|
84
|
+
const acceptedTypesLabel = $derived.by(() => {
|
|
85
|
+
if (typesLabel) return typesLabel;
|
|
86
|
+
|
|
87
|
+
const groups: string[] = [];
|
|
88
|
+
const add = (name: string) => {
|
|
89
|
+
if (!groups.includes(name)) groups.push(name);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
for (const type of acceptedTypes) {
|
|
93
|
+
if (type.startsWith('image/')) add('Images');
|
|
94
|
+
else if (type === 'application/pdf') add('PDF');
|
|
95
|
+
else if (type.startsWith('video/')) add('Videos');
|
|
96
|
+
else if (type.startsWith('audio/')) add('Audio');
|
|
97
|
+
else {
|
|
98
|
+
const subtype = type.split('/')[1];
|
|
99
|
+
add(subtype ? subtype.toUpperCase() : type);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return groups.join(', ');
|
|
104
|
+
});
|
|
105
|
+
|
|
71
106
|
// Helper to generate unique ID
|
|
72
107
|
function generateId(): string {
|
|
73
108
|
return `file-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
@@ -302,7 +337,7 @@
|
|
|
302
337
|
for={id}
|
|
303
338
|
aria-describedby={displayError ? `${id}-error` : undefined}
|
|
304
339
|
class={cn(
|
|
305
|
-
'relative flex flex-col items-center justify-center rounded-lg border-2 border-dashed p-6
|
|
340
|
+
'relative flex flex-col items-center justify-center rounded-lg border-2 border-dashed p-6',
|
|
306
341
|
'cursor-pointer focus-within:outline-none focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2',
|
|
307
342
|
isDragging && !disabled && 'border-primary bg-primary/5',
|
|
308
343
|
disabled && 'cursor-not-allowed opacity-50',
|
|
@@ -337,7 +372,7 @@
|
|
|
337
372
|
<span class="font-medium text-foreground">Click to upload</span> or drag and drop
|
|
338
373
|
</p>
|
|
339
374
|
<p class="mt-1 text-xs text-muted-foreground">
|
|
340
|
-
{
|
|
375
|
+
{acceptedTypesLabel} (max {formatSize(maxSizeBytes)})
|
|
341
376
|
</p>
|
|
342
377
|
|
|
343
378
|
<!-- Hidden file input -->
|
|
@@ -34,6 +34,12 @@ interface Props {
|
|
|
34
34
|
label?: string;
|
|
35
35
|
/** Hint text shown below the drop zone */
|
|
36
36
|
hint?: string;
|
|
37
|
+
/**
|
|
38
|
+
* Human-friendly label for the accepted file types shown in the drop zone.
|
|
39
|
+
* When omitted, a readable label is derived from `acceptedTypes`
|
|
40
|
+
* (e.g. "Images, PDF, Videos") instead of showing raw MIME types.
|
|
41
|
+
*/
|
|
42
|
+
typesLabel?: string;
|
|
37
43
|
/** Additional classes */
|
|
38
44
|
class?: string;
|
|
39
45
|
}
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
* ```
|
|
26
26
|
*/
|
|
27
27
|
import { Dialog as DialogPrimitive } from 'bits-ui';
|
|
28
|
+
import { onDestroy } from 'svelte';
|
|
28
29
|
import { cn } from '../utils.js';
|
|
29
30
|
import type { Snippet } from 'svelte';
|
|
30
31
|
import type { SearchResultItem, SearchResultGroup } from '../types/layout.js';
|
|
@@ -131,6 +132,10 @@
|
|
|
131
132
|
}, 200);
|
|
132
133
|
}
|
|
133
134
|
|
|
135
|
+
onDestroy(() => {
|
|
136
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
137
|
+
});
|
|
138
|
+
|
|
134
139
|
// Keyboard navigation within results
|
|
135
140
|
function handleKeydown(e: KeyboardEvent) {
|
|
136
141
|
switch (e.key) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { RadioGroup as RadioGroupPrimitive } from 'bits-ui';
|
|
3
|
-
import { cn
|
|
3
|
+
import { cn } from '../utils.js';
|
|
4
4
|
import { computeDescribedBy } from '../utils/form.js';
|
|
5
5
|
import { validateNonEmptyArray } from '../validation.js';
|
|
6
6
|
import Label from './Label.svelte';
|
|
@@ -52,7 +52,8 @@
|
|
|
52
52
|
}: Props = $props();
|
|
53
53
|
|
|
54
54
|
// Generate unique IDs for accessibility (sequential counter, SSR-safe)
|
|
55
|
-
const
|
|
55
|
+
const uid = $props.id();
|
|
56
|
+
const generatedId = `radio-group-${uid}`;
|
|
56
57
|
const componentName = $derived(name || generatedId);
|
|
57
58
|
const labelId = $derived(`${componentName}-label`);
|
|
58
59
|
const hintId = $derived(`${componentName}-hint`);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { Select as SelectPrimitive } from 'bits-ui';
|
|
3
|
-
import { cn
|
|
3
|
+
import { cn } from '../utils.js';
|
|
4
4
|
import { validateNonEmptyArray } from '../validation.js';
|
|
5
5
|
import { DEV } from 'esm-env';
|
|
6
6
|
import Label from './Label.svelte';
|
|
@@ -59,7 +59,8 @@
|
|
|
59
59
|
}: Props = $props();
|
|
60
60
|
|
|
61
61
|
// Generate unique IDs for accessibility using sequential counter
|
|
62
|
-
const
|
|
62
|
+
const uid = $props.id();
|
|
63
|
+
const generatedId = `select-${uid}`;
|
|
63
64
|
const componentId = $derived(id || generatedId);
|
|
64
65
|
const hintId = $derived(`${componentId}-hint`);
|
|
65
66
|
const errorId = $derived(`${componentId}-error`);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { untrack } from 'svelte';
|
|
3
|
-
import { cn
|
|
3
|
+
import { cn } from '../utils.js';
|
|
4
4
|
import { tv, type VariantProps } from 'tailwind-variants';
|
|
5
5
|
import type {
|
|
6
6
|
SignatureData,
|
|
@@ -842,7 +842,8 @@
|
|
|
842
842
|
});
|
|
843
843
|
|
|
844
844
|
// Computed IDs (using stable sequential ID generation)
|
|
845
|
-
const
|
|
845
|
+
const uid = $props.id();
|
|
846
|
+
const generatedInputId = `signature-${uid}`;
|
|
846
847
|
const inputId = $derived(id || generatedInputId);
|
|
847
848
|
const errorId = $derived(`${inputId}-error`);
|
|
848
849
|
const hintId = $derived(`${inputId}-hint`);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
3
|
import { setContext } from 'svelte';
|
|
4
|
-
import { cn
|
|
4
|
+
import { cn } from '../utils.js';
|
|
5
5
|
import type { Tab } from '../types/components.js';
|
|
6
6
|
|
|
7
7
|
interface Props {
|
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
onValueChange?: (value: string) => void;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
const
|
|
28
|
+
const uid = $props.id();
|
|
29
|
+
const generatedId = `tabs-${uid}`;
|
|
29
30
|
|
|
30
31
|
let {
|
|
31
32
|
tabs,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<script lang="ts">
|
|
1
|
+
<script lang="ts" generics="TData extends object">
|
|
2
2
|
/**
|
|
3
3
|
* DataTableColumnFilter - Per-column text filter input
|
|
4
4
|
*
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
interface Props {
|
|
14
14
|
/** The native DataTable instance */
|
|
15
|
-
table: DataTableInstance<
|
|
15
|
+
table: DataTableInstance<TData>;
|
|
16
16
|
/** Column ID to filter */
|
|
17
17
|
columnId: string;
|
|
18
18
|
/** Placeholder text */
|
|
@@ -5,18 +5,38 @@
|
|
|
5
5
|
* Works with the native Svelte 5 DataTable implementation.
|
|
6
6
|
*/
|
|
7
7
|
import type { DataTableInstance } from './createDataTable.svelte.js';
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
8
|
+
declare function $$render<TData extends object>(): {
|
|
9
|
+
props: {
|
|
10
|
+
/** The native DataTable instance */
|
|
11
|
+
table: DataTableInstance<TData>;
|
|
12
|
+
/** Column ID to filter */
|
|
13
|
+
columnId: string;
|
|
14
|
+
/** Placeholder text */
|
|
15
|
+
placeholder?: string;
|
|
16
|
+
/** Debounce delay in ms */
|
|
17
|
+
debounceMs?: number;
|
|
18
|
+
/** Additional classes */
|
|
19
|
+
class?: string;
|
|
20
|
+
};
|
|
21
|
+
exports: {};
|
|
22
|
+
bindings: "";
|
|
23
|
+
slots: {};
|
|
24
|
+
events: {};
|
|
25
|
+
};
|
|
26
|
+
declare class __sveltets_Render<TData extends object> {
|
|
27
|
+
props(): ReturnType<typeof $$render<TData>>['props'];
|
|
28
|
+
events(): ReturnType<typeof $$render<TData>>['events'];
|
|
29
|
+
slots(): ReturnType<typeof $$render<TData>>['slots'];
|
|
30
|
+
bindings(): "";
|
|
31
|
+
exports(): {};
|
|
19
32
|
}
|
|
20
|
-
|
|
21
|
-
|
|
33
|
+
interface $$IsomorphicComponent {
|
|
34
|
+
new <TData extends object>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<TData>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<TData>['props']>, ReturnType<__sveltets_Render<TData>['events']>, ReturnType<__sveltets_Render<TData>['slots']>> & {
|
|
35
|
+
$$bindings?: ReturnType<__sveltets_Render<TData>['bindings']>;
|
|
36
|
+
} & ReturnType<__sveltets_Render<TData>['exports']>;
|
|
37
|
+
<TData extends object>(internal: unknown, props: ReturnType<__sveltets_Render<TData>['props']> & {}): ReturnType<__sveltets_Render<TData>['exports']>;
|
|
38
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
39
|
+
}
|
|
40
|
+
declare const DataTableColumnFilter: $$IsomorphicComponent;
|
|
41
|
+
type DataTableColumnFilter<TData extends object> = InstanceType<typeof DataTableColumnFilter<TData>>;
|
|
22
42
|
export default DataTableColumnFilter;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<script lang="ts">
|
|
1
|
+
<script lang="ts" generics="TData extends object">
|
|
2
2
|
/**
|
|
3
3
|
* DataTableColumnVisibility - Column visibility toggle dropdown
|
|
4
4
|
*
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
interface Props {
|
|
14
14
|
/** The native DataTable instance */
|
|
15
|
-
table: DataTableInstance<
|
|
15
|
+
table: DataTableInstance<TData>;
|
|
16
16
|
/** Additional classes */
|
|
17
17
|
class?: string;
|
|
18
18
|
}
|
|
@@ -5,12 +5,32 @@
|
|
|
5
5
|
* Works with the native Svelte 5 DataTable implementation.
|
|
6
6
|
*/
|
|
7
7
|
import type { DataTableInstance } from './createDataTable.svelte.js';
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
declare function $$render<TData extends object>(): {
|
|
9
|
+
props: {
|
|
10
|
+
/** The native DataTable instance */
|
|
11
|
+
table: DataTableInstance<TData>;
|
|
12
|
+
/** Additional classes */
|
|
13
|
+
class?: string;
|
|
14
|
+
};
|
|
15
|
+
exports: {};
|
|
16
|
+
bindings: "";
|
|
17
|
+
slots: {};
|
|
18
|
+
events: {};
|
|
19
|
+
};
|
|
20
|
+
declare class __sveltets_Render<TData extends object> {
|
|
21
|
+
props(): ReturnType<typeof $$render<TData>>['props'];
|
|
22
|
+
events(): ReturnType<typeof $$render<TData>>['events'];
|
|
23
|
+
slots(): ReturnType<typeof $$render<TData>>['slots'];
|
|
24
|
+
bindings(): "";
|
|
25
|
+
exports(): {};
|
|
13
26
|
}
|
|
14
|
-
|
|
15
|
-
|
|
27
|
+
interface $$IsomorphicComponent {
|
|
28
|
+
new <TData extends object>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<TData>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<TData>['props']>, ReturnType<__sveltets_Render<TData>['events']>, ReturnType<__sveltets_Render<TData>['slots']>> & {
|
|
29
|
+
$$bindings?: ReturnType<__sveltets_Render<TData>['bindings']>;
|
|
30
|
+
} & ReturnType<__sveltets_Render<TData>['exports']>;
|
|
31
|
+
<TData extends object>(internal: unknown, props: ReturnType<__sveltets_Render<TData>['props']> & {}): ReturnType<__sveltets_Render<TData>['exports']>;
|
|
32
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
33
|
+
}
|
|
34
|
+
declare const DataTableColumnVisibility: $$IsomorphicComponent;
|
|
35
|
+
type DataTableColumnVisibility<TData extends object> = InstanceType<typeof DataTableColumnVisibility<TData>>;
|
|
16
36
|
export default DataTableColumnVisibility;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<script lang="ts">
|
|
1
|
+
<script lang="ts" generics="TData extends object">
|
|
2
2
|
/**
|
|
3
3
|
* DataTableFacetedFilter - Multi-select filter for categorical columns
|
|
4
4
|
*
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
|
|
24
24
|
interface Props {
|
|
25
25
|
/** The native DataTable instance */
|
|
26
|
-
table: DataTableInstance<
|
|
26
|
+
table: DataTableInstance<TData>;
|
|
27
27
|
/** Column ID to filter */
|
|
28
28
|
columnId: string;
|
|
29
29
|
/** Title for the filter */
|
|
@@ -9,23 +9,42 @@
|
|
|
9
9
|
* from the main table's column filter state.
|
|
10
10
|
*/
|
|
11
11
|
import type { DataTableInstance } from './createDataTable.svelte.js';
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
declare function $$render<TData extends object>(): {
|
|
13
|
+
props: {
|
|
14
|
+
/** The native DataTable instance */
|
|
15
|
+
table: DataTableInstance<TData>;
|
|
16
|
+
/** Column ID to filter */
|
|
17
|
+
columnId: string;
|
|
18
|
+
/** Title for the filter */
|
|
19
|
+
title: string;
|
|
20
|
+
/** Available filter options */
|
|
21
|
+
options: {
|
|
22
|
+
value: string;
|
|
23
|
+
label: string;
|
|
24
|
+
count?: number;
|
|
25
|
+
}[];
|
|
26
|
+
/** Additional classes */
|
|
27
|
+
class?: string;
|
|
28
|
+
};
|
|
29
|
+
exports: {};
|
|
30
|
+
bindings: "";
|
|
31
|
+
slots: {};
|
|
32
|
+
events: {};
|
|
33
|
+
};
|
|
34
|
+
declare class __sveltets_Render<TData extends object> {
|
|
35
|
+
props(): ReturnType<typeof $$render<TData>>['props'];
|
|
36
|
+
events(): ReturnType<typeof $$render<TData>>['events'];
|
|
37
|
+
slots(): ReturnType<typeof $$render<TData>>['slots'];
|
|
38
|
+
bindings(): "";
|
|
39
|
+
exports(): {};
|
|
16
40
|
}
|
|
17
|
-
interface
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
title: string;
|
|
24
|
-
/** Available filter options */
|
|
25
|
-
options: FilterOption[];
|
|
26
|
-
/** Additional classes */
|
|
27
|
-
class?: string;
|
|
41
|
+
interface $$IsomorphicComponent {
|
|
42
|
+
new <TData extends object>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<TData>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<TData>['props']>, ReturnType<__sveltets_Render<TData>['events']>, ReturnType<__sveltets_Render<TData>['slots']>> & {
|
|
43
|
+
$$bindings?: ReturnType<__sveltets_Render<TData>['bindings']>;
|
|
44
|
+
} & ReturnType<__sveltets_Render<TData>['exports']>;
|
|
45
|
+
<TData extends object>(internal: unknown, props: ReturnType<__sveltets_Render<TData>['props']> & {}): ReturnType<__sveltets_Render<TData>['exports']>;
|
|
46
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
28
47
|
}
|
|
29
|
-
declare const DataTableFacetedFilter:
|
|
30
|
-
type DataTableFacetedFilter =
|
|
48
|
+
declare const DataTableFacetedFilter: $$IsomorphicComponent;
|
|
49
|
+
type DataTableFacetedFilter<TData extends object> = InstanceType<typeof DataTableFacetedFilter<TData>>;
|
|
31
50
|
export default DataTableFacetedFilter;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<script lang="ts">
|
|
1
|
+
<script lang="ts" generics="TData extends object">
|
|
2
2
|
/**
|
|
3
3
|
* DataTablePagination - Pagination controls for DataTable
|
|
4
4
|
*
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
interface Props {
|
|
14
14
|
/** The native DataTable instance */
|
|
15
|
-
table: DataTableInstance<
|
|
15
|
+
table: DataTableInstance<TData>;
|
|
16
16
|
/** Available page sizes */
|
|
17
17
|
pageSizeOptions?: number[];
|
|
18
18
|
/** Show selected row count */
|
|
@@ -5,16 +5,36 @@
|
|
|
5
5
|
* Works with the native Svelte 5 DataTable implementation.
|
|
6
6
|
*/
|
|
7
7
|
import type { DataTableInstance } from './createDataTable.svelte.js';
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
8
|
+
declare function $$render<TData extends object>(): {
|
|
9
|
+
props: {
|
|
10
|
+
/** The native DataTable instance */
|
|
11
|
+
table: DataTableInstance<TData>;
|
|
12
|
+
/** Available page sizes */
|
|
13
|
+
pageSizeOptions?: number[];
|
|
14
|
+
/** Show selected row count */
|
|
15
|
+
showSelectedCount?: boolean;
|
|
16
|
+
/** Additional classes */
|
|
17
|
+
class?: string;
|
|
18
|
+
};
|
|
19
|
+
exports: {};
|
|
20
|
+
bindings: "";
|
|
21
|
+
slots: {};
|
|
22
|
+
events: {};
|
|
23
|
+
};
|
|
24
|
+
declare class __sveltets_Render<TData extends object> {
|
|
25
|
+
props(): ReturnType<typeof $$render<TData>>['props'];
|
|
26
|
+
events(): ReturnType<typeof $$render<TData>>['events'];
|
|
27
|
+
slots(): ReturnType<typeof $$render<TData>>['slots'];
|
|
28
|
+
bindings(): "";
|
|
29
|
+
exports(): {};
|
|
17
30
|
}
|
|
18
|
-
|
|
19
|
-
|
|
31
|
+
interface $$IsomorphicComponent {
|
|
32
|
+
new <TData extends object>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<TData>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<TData>['props']>, ReturnType<__sveltets_Render<TData>['events']>, ReturnType<__sveltets_Render<TData>['slots']>> & {
|
|
33
|
+
$$bindings?: ReturnType<__sveltets_Render<TData>['bindings']>;
|
|
34
|
+
} & ReturnType<__sveltets_Render<TData>['exports']>;
|
|
35
|
+
<TData extends object>(internal: unknown, props: ReturnType<__sveltets_Render<TData>['props']> & {}): ReturnType<__sveltets_Render<TData>['exports']>;
|
|
36
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
37
|
+
}
|
|
38
|
+
declare const DataTablePagination: $$IsomorphicComponent;
|
|
39
|
+
type DataTablePagination<TData extends object> = InstanceType<typeof DataTablePagination<TData>>;
|
|
20
40
|
export default DataTablePagination;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<script lang="ts">
|
|
1
|
+
<script lang="ts" generics="TData extends object">
|
|
2
2
|
/**
|
|
3
3
|
* DataTableSearch - Global search input for DataTable
|
|
4
4
|
*
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
interface Props {
|
|
13
13
|
/** The native DataTable instance */
|
|
14
|
-
table: DataTableInstance<
|
|
14
|
+
table: DataTableInstance<TData>;
|
|
15
15
|
/** Placeholder text */
|
|
16
16
|
placeholder?: string;
|
|
17
17
|
/** Debounce delay in ms */
|
|
@@ -5,16 +5,36 @@
|
|
|
5
5
|
* Works with the native Svelte 5 DataTable implementation.
|
|
6
6
|
*/
|
|
7
7
|
import type { DataTableInstance } from './createDataTable.svelte.js';
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
8
|
+
declare function $$render<TData extends object>(): {
|
|
9
|
+
props: {
|
|
10
|
+
/** The native DataTable instance */
|
|
11
|
+
table: DataTableInstance<TData>;
|
|
12
|
+
/** Placeholder text */
|
|
13
|
+
placeholder?: string;
|
|
14
|
+
/** Debounce delay in ms */
|
|
15
|
+
debounceMs?: number;
|
|
16
|
+
/** Additional classes */
|
|
17
|
+
class?: string;
|
|
18
|
+
};
|
|
19
|
+
exports: {};
|
|
20
|
+
bindings: "";
|
|
21
|
+
slots: {};
|
|
22
|
+
events: {};
|
|
23
|
+
};
|
|
24
|
+
declare class __sveltets_Render<TData extends object> {
|
|
25
|
+
props(): ReturnType<typeof $$render<TData>>['props'];
|
|
26
|
+
events(): ReturnType<typeof $$render<TData>>['events'];
|
|
27
|
+
slots(): ReturnType<typeof $$render<TData>>['slots'];
|
|
28
|
+
bindings(): "";
|
|
29
|
+
exports(): {};
|
|
17
30
|
}
|
|
18
|
-
|
|
19
|
-
|
|
31
|
+
interface $$IsomorphicComponent {
|
|
32
|
+
new <TData extends object>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<TData>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<TData>['props']>, ReturnType<__sveltets_Render<TData>['events']>, ReturnType<__sveltets_Render<TData>['slots']>> & {
|
|
33
|
+
$$bindings?: ReturnType<__sveltets_Render<TData>['bindings']>;
|
|
34
|
+
} & ReturnType<__sveltets_Render<TData>['exports']>;
|
|
35
|
+
<TData extends object>(internal: unknown, props: ReturnType<__sveltets_Render<TData>['props']> & {}): ReturnType<__sveltets_Render<TData>['exports']>;
|
|
36
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
37
|
+
}
|
|
38
|
+
declare const DataTableSearch: $$IsomorphicComponent;
|
|
39
|
+
type DataTableSearch<TData extends object> = InstanceType<typeof DataTableSearch<TData>>;
|
|
20
40
|
export default DataTableSearch;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<script lang="ts">
|
|
1
|
+
<script lang="ts" generics="TData extends object">
|
|
2
2
|
/**
|
|
3
3
|
* DataTableToolbar - Combined toolbar with search and controls
|
|
4
4
|
*
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
interface Props {
|
|
16
16
|
/** The native DataTable instance */
|
|
17
|
-
table: DataTableInstance<
|
|
17
|
+
table: DataTableInstance<TData>;
|
|
18
18
|
/** Show global search */
|
|
19
19
|
showSearch?: boolean;
|
|
20
20
|
/** Show column visibility toggle */
|
|
@@ -6,22 +6,42 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import type { Snippet } from 'svelte';
|
|
8
8
|
import type { DataTableInstance } from './createDataTable.svelte.js';
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
9
|
+
declare function $$render<TData extends object>(): {
|
|
10
|
+
props: {
|
|
11
|
+
/** The native DataTable instance */
|
|
12
|
+
table: DataTableInstance<TData>;
|
|
13
|
+
/** Show global search */
|
|
14
|
+
showSearch?: boolean;
|
|
15
|
+
/** Show column visibility toggle */
|
|
16
|
+
showColumnVisibility?: boolean;
|
|
17
|
+
/** Search placeholder */
|
|
18
|
+
searchPlaceholder?: string;
|
|
19
|
+
/** Custom filters slot */
|
|
20
|
+
filters?: Snippet;
|
|
21
|
+
/** Custom actions slot */
|
|
22
|
+
actions?: Snippet;
|
|
23
|
+
/** Additional classes */
|
|
24
|
+
class?: string;
|
|
25
|
+
};
|
|
26
|
+
exports: {};
|
|
27
|
+
bindings: "";
|
|
28
|
+
slots: {};
|
|
29
|
+
events: {};
|
|
30
|
+
};
|
|
31
|
+
declare class __sveltets_Render<TData extends object> {
|
|
32
|
+
props(): ReturnType<typeof $$render<TData>>['props'];
|
|
33
|
+
events(): ReturnType<typeof $$render<TData>>['events'];
|
|
34
|
+
slots(): ReturnType<typeof $$render<TData>>['slots'];
|
|
35
|
+
bindings(): "";
|
|
36
|
+
exports(): {};
|
|
24
37
|
}
|
|
25
|
-
|
|
26
|
-
|
|
38
|
+
interface $$IsomorphicComponent {
|
|
39
|
+
new <TData extends object>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<TData>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<TData>['props']>, ReturnType<__sveltets_Render<TData>['events']>, ReturnType<__sveltets_Render<TData>['slots']>> & {
|
|
40
|
+
$$bindings?: ReturnType<__sveltets_Render<TData>['bindings']>;
|
|
41
|
+
} & ReturnType<__sveltets_Render<TData>['exports']>;
|
|
42
|
+
<TData extends object>(internal: unknown, props: ReturnType<__sveltets_Render<TData>['props']> & {}): ReturnType<__sveltets_Render<TData>['exports']>;
|
|
43
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
44
|
+
}
|
|
45
|
+
declare const DataTableToolbar: $$IsomorphicComponent;
|
|
46
|
+
type DataTableToolbar<TData extends object> = InstanceType<typeof DataTableToolbar<TData>>;
|
|
27
47
|
export default DataTableToolbar;
|
|
@@ -28,18 +28,18 @@
|
|
|
28
28
|
export function useAnnounce(defaultOptions) {
|
|
29
29
|
let message = $state('');
|
|
30
30
|
let politeness = $state(defaultOptions?.politeness ?? 'polite');
|
|
31
|
-
let
|
|
31
|
+
let clearTimer = null;
|
|
32
32
|
function clear() {
|
|
33
33
|
message = '';
|
|
34
|
-
if (
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
if (clearTimer) {
|
|
35
|
+
clearTimeout(clearTimer);
|
|
36
|
+
clearTimer = null;
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
function announce(newMessage, options) {
|
|
40
40
|
// Clear any pending timeout
|
|
41
|
-
if (
|
|
42
|
-
|
|
41
|
+
if (clearTimer) {
|
|
42
|
+
clearTimeout(clearTimer);
|
|
43
43
|
}
|
|
44
44
|
// Update politeness if provided
|
|
45
45
|
if (options?.politeness) {
|
|
@@ -50,7 +50,7 @@ export function useAnnounce(defaultOptions) {
|
|
|
50
50
|
// Set up auto-clear if delay > 0
|
|
51
51
|
const delay = options?.clearDelay ?? defaultOptions?.clearDelay ?? 1000;
|
|
52
52
|
if (delay > 0) {
|
|
53
|
-
|
|
53
|
+
clearTimer = setTimeout(() => {
|
|
54
54
|
message = '';
|
|
55
55
|
}, delay);
|
|
56
56
|
}
|
|
@@ -58,8 +58,8 @@ export function useAnnounce(defaultOptions) {
|
|
|
58
58
|
// Cleanup on destroy
|
|
59
59
|
$effect(() => {
|
|
60
60
|
return () => {
|
|
61
|
-
if (
|
|
62
|
-
|
|
61
|
+
if (clearTimer) {
|
|
62
|
+
clearTimeout(clearTimer);
|
|
63
63
|
}
|
|
64
64
|
};
|
|
65
65
|
});
|
|
@@ -23,10 +23,11 @@ const DEFAULT_SELECTOR = '[role="menuitem"], [role="option"], [role="tab"], butt
|
|
|
23
23
|
* ```
|
|
24
24
|
*/
|
|
25
25
|
export function useArrowNavigation(options = {}) {
|
|
26
|
-
|
|
27
|
-
let
|
|
28
|
-
let
|
|
29
|
-
let
|
|
26
|
+
// Plain locals: only read imperatively (handlers/action update), never tracked
|
|
27
|
+
let orientation = options.orientation ?? 'vertical';
|
|
28
|
+
let loop = options.loop ?? true;
|
|
29
|
+
let itemSelector = options.itemSelector ?? DEFAULT_SELECTOR;
|
|
30
|
+
let onFocusChange = options.onFocusChange;
|
|
30
31
|
let container = null;
|
|
31
32
|
let focusedIndex = $state(-1);
|
|
32
33
|
function getFocusableItems() {
|
|
@@ -5,9 +5,11 @@ export interface UseEscapeKeyOptions {
|
|
|
5
5
|
/**
|
|
6
6
|
* Whether the escape key handler is enabled.
|
|
7
7
|
* Useful for conditionally enabling based on component state (e.g., dialog open).
|
|
8
|
+
* Pass a getter (`() => isOpen`) for a value that should stay reactive —
|
|
9
|
+
* a plain boolean is captured once when the composable is called.
|
|
8
10
|
* Default: true
|
|
9
11
|
*/
|
|
10
|
-
enabled?: boolean;
|
|
12
|
+
enabled?: boolean | (() => boolean);
|
|
11
13
|
/**
|
|
12
14
|
* Whether to prevent default behavior when Escape is pressed.
|
|
13
15
|
* Default: true
|
|
@@ -37,7 +39,7 @@ export interface UseEscapeKeyOptions {
|
|
|
37
39
|
* useEscapeKey(() => {
|
|
38
40
|
* isOpen = false;
|
|
39
41
|
* onClose?.();
|
|
40
|
-
* }, { enabled: isOpen });
|
|
42
|
+
* }, { enabled: () => isOpen });
|
|
41
43
|
* </script>
|
|
42
44
|
*
|
|
43
45
|
* {#if isOpen}
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* useEscapeKey(() => {
|
|
17
17
|
* isOpen = false;
|
|
18
18
|
* onClose?.();
|
|
19
|
-
* }, { enabled: isOpen });
|
|
19
|
+
* }, { enabled: () => isOpen });
|
|
20
20
|
* </script>
|
|
21
21
|
*
|
|
22
22
|
* {#if isOpen}
|
|
@@ -25,10 +25,11 @@
|
|
|
25
25
|
* ```
|
|
26
26
|
*/
|
|
27
27
|
export function useEscapeKey(callback, options) {
|
|
28
|
-
const enabled = $derived(options?.enabled ?? true);
|
|
29
28
|
const preventDefault = options?.preventDefault ?? true;
|
|
30
29
|
const stopPropagation = options?.stopPropagation ?? false;
|
|
31
30
|
$effect(() => {
|
|
31
|
+
// Resolve inside the effect so a getter's reactive reads are tracked
|
|
32
|
+
const enabled = typeof options?.enabled === 'function' ? options.enabled() : (options?.enabled ?? true);
|
|
32
33
|
if (!enabled)
|
|
33
34
|
return;
|
|
34
35
|
function handleKeyDown(event) {
|
|
@@ -27,9 +27,10 @@
|
|
|
27
27
|
* ```
|
|
28
28
|
*/
|
|
29
29
|
export function useFocusRef(options = {}) {
|
|
30
|
-
|
|
31
|
-
let
|
|
32
|
-
let
|
|
30
|
+
// Plain locals: only read imperatively (action lifecycle), never tracked
|
|
31
|
+
let focusOnMount = options.focusOnMount ?? false;
|
|
32
|
+
let preventScroll = options.preventScroll ?? false;
|
|
33
|
+
let delay = options.delay ?? 0;
|
|
33
34
|
let element = null;
|
|
34
35
|
let timeoutId = null;
|
|
35
36
|
let frameId = null;
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
export interface UseFocusTrapOptions {
|
|
2
|
-
/**
|
|
3
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Whether the focus trap is active.
|
|
4
|
+
* Pass a getter (`() => isOpen`) for a value that should stay reactive —
|
|
5
|
+
* a plain boolean is captured once when the composable is called.
|
|
6
|
+
*/
|
|
7
|
+
active?: boolean | (() => boolean);
|
|
4
8
|
/** Element to return focus to when trap is deactivated */
|
|
5
9
|
returnFocusTo?: HTMLElement | null;
|
|
6
10
|
/** Selector for the element to focus initially (defaults to first focusable) */
|
|
@@ -15,12 +19,14 @@ export interface UseFocusTrapOptions {
|
|
|
15
19
|
* focusable elements without escaping. Useful for modals, dialogs,
|
|
16
20
|
* and dropdown menus.
|
|
17
21
|
*
|
|
22
|
+
* Must be called during component initialization (it registers a `$effect`).
|
|
23
|
+
*
|
|
18
24
|
* @example
|
|
19
25
|
* ```svelte
|
|
20
26
|
* <script>
|
|
21
27
|
* import { useFocusTrap } from '@classic-homes/theme-svelte';
|
|
22
28
|
*
|
|
23
|
-
* const { action } = useFocusTrap({ active: isOpen });
|
|
29
|
+
* const { action } = useFocusTrap({ active: () => isOpen });
|
|
24
30
|
* </script>
|
|
25
31
|
*
|
|
26
32
|
* {#if isOpen}
|
|
@@ -22,12 +22,14 @@ function getFocusableElements(container) {
|
|
|
22
22
|
* focusable elements without escaping. Useful for modals, dialogs,
|
|
23
23
|
* and dropdown menus.
|
|
24
24
|
*
|
|
25
|
+
* Must be called during component initialization (it registers a `$effect`).
|
|
26
|
+
*
|
|
25
27
|
* @example
|
|
26
28
|
* ```svelte
|
|
27
29
|
* <script>
|
|
28
30
|
* import { useFocusTrap } from '@classic-homes/theme-svelte';
|
|
29
31
|
*
|
|
30
|
-
* const { action } = useFocusTrap({ active: isOpen });
|
|
32
|
+
* const { action } = useFocusTrap({ active: () => isOpen });
|
|
31
33
|
* </script>
|
|
32
34
|
*
|
|
33
35
|
* {#if isOpen}
|
|
@@ -40,15 +42,18 @@ function getFocusableElements(container) {
|
|
|
40
42
|
* ```
|
|
41
43
|
*/
|
|
42
44
|
export function useFocusTrap(options = {}) {
|
|
43
|
-
let
|
|
44
|
-
let
|
|
45
|
-
let initialFocus = $state(options.initialFocus);
|
|
46
|
-
let preventScroll = $state(options.preventScroll ?? false);
|
|
47
|
-
let container = null;
|
|
45
|
+
let currentOptions = $state(options);
|
|
46
|
+
let container = $state(null);
|
|
48
47
|
let previousActiveElement = null;
|
|
48
|
+
let trapped = false;
|
|
49
|
+
function isActive() {
|
|
50
|
+
const active = currentOptions.active;
|
|
51
|
+
return typeof active === 'function' ? active() : (active ?? true);
|
|
52
|
+
}
|
|
49
53
|
function handleKeyDown(event) {
|
|
50
|
-
if (!
|
|
54
|
+
if (!trapped || event.key !== 'Tab' || !container)
|
|
51
55
|
return;
|
|
56
|
+
const preventScroll = currentOptions.preventScroll ?? false;
|
|
52
57
|
const focusableElements = getFocusableElements(container);
|
|
53
58
|
if (focusableElements.length === 0)
|
|
54
59
|
return;
|
|
@@ -70,16 +75,18 @@ export function useFocusTrap(options = {}) {
|
|
|
70
75
|
}
|
|
71
76
|
}
|
|
72
77
|
function activate() {
|
|
73
|
-
if (!container)
|
|
78
|
+
if (!container || trapped)
|
|
74
79
|
return;
|
|
80
|
+
trapped = true;
|
|
75
81
|
// Save current active element to return focus later
|
|
76
82
|
previousActiveElement = document.activeElement;
|
|
77
83
|
// Focus initial element
|
|
84
|
+
const preventScroll = currentOptions.preventScroll ?? false;
|
|
78
85
|
const focusableElements = getFocusableElements(container);
|
|
79
86
|
if (focusableElements.length > 0) {
|
|
80
87
|
let elementToFocus = null;
|
|
81
|
-
if (initialFocus) {
|
|
82
|
-
elementToFocus = container.querySelector(initialFocus);
|
|
88
|
+
if (currentOptions.initialFocus) {
|
|
89
|
+
elementToFocus = container.querySelector(currentOptions.initialFocus);
|
|
83
90
|
}
|
|
84
91
|
if (!elementToFocus) {
|
|
85
92
|
elementToFocus = focusableElements[0];
|
|
@@ -91,39 +98,38 @@ export function useFocusTrap(options = {}) {
|
|
|
91
98
|
document.addEventListener('keydown', handleKeyDown);
|
|
92
99
|
}
|
|
93
100
|
function deactivate() {
|
|
101
|
+
if (!trapped)
|
|
102
|
+
return;
|
|
103
|
+
trapped = false;
|
|
94
104
|
document.removeEventListener('keydown', handleKeyDown);
|
|
95
105
|
// Return focus when trap is deactivated
|
|
96
|
-
const
|
|
106
|
+
const preventScroll = currentOptions.preventScroll ?? false;
|
|
107
|
+
const elementToReturn = currentOptions.returnFocusTo ?? previousActiveElement;
|
|
97
108
|
if (elementToReturn && typeof elementToReturn.focus === 'function') {
|
|
98
109
|
elementToReturn.focus({ preventScroll });
|
|
99
110
|
}
|
|
100
111
|
}
|
|
112
|
+
// Activate/deactivate as the container attaches/detaches, `active` changes
|
|
113
|
+
// (via getter or action update), or the component is destroyed.
|
|
114
|
+
$effect(() => {
|
|
115
|
+
if (container !== null && isActive()) {
|
|
116
|
+
activate();
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
deactivate();
|
|
120
|
+
}
|
|
121
|
+
});
|
|
101
122
|
/**
|
|
102
123
|
* Svelte action to apply focus trap to an element
|
|
103
124
|
*/
|
|
104
125
|
function action(node) {
|
|
105
126
|
container = node;
|
|
106
|
-
if (active) {
|
|
107
|
-
activate();
|
|
108
|
-
}
|
|
109
127
|
return {
|
|
110
128
|
update(newOptions) {
|
|
111
|
-
|
|
112
|
-
active = newOptions.active ?? true;
|
|
113
|
-
returnFocusTo = newOptions.returnFocusTo ?? null;
|
|
114
|
-
initialFocus = newOptions.initialFocus;
|
|
115
|
-
preventScroll = newOptions.preventScroll ?? false;
|
|
116
|
-
if (!wasActive && active) {
|
|
117
|
-
activate();
|
|
118
|
-
}
|
|
119
|
-
else if (wasActive && !active) {
|
|
120
|
-
deactivate();
|
|
121
|
-
}
|
|
129
|
+
currentOptions = newOptions;
|
|
122
130
|
},
|
|
123
131
|
destroy() {
|
|
124
|
-
|
|
125
|
-
deactivate();
|
|
126
|
-
}
|
|
132
|
+
deactivate();
|
|
127
133
|
container = null;
|
|
128
134
|
},
|
|
129
135
|
};
|
|
@@ -32,10 +32,11 @@ const DEFAULT_SELECTOR = '[data-roving-tabindex-item]';
|
|
|
32
32
|
* ```
|
|
33
33
|
*/
|
|
34
34
|
export function useRovingTabindex(options = {}) {
|
|
35
|
-
|
|
36
|
-
let
|
|
35
|
+
// Plain locals: only read imperatively (handlers/action update), never tracked
|
|
36
|
+
let loop = options.loop ?? true;
|
|
37
|
+
let itemSelector = options.itemSelector ?? DEFAULT_SELECTOR;
|
|
37
38
|
let activeIndex = $state(options.initialIndex ?? 0);
|
|
38
|
-
let onIndexChange =
|
|
39
|
+
let onIndexChange = options.onIndexChange;
|
|
39
40
|
let container = null;
|
|
40
41
|
function getItems() {
|
|
41
42
|
if (!container)
|
package/dist/lib/utils.d.ts
CHANGED
|
@@ -4,13 +4,3 @@ import { type ClassValue } from 'clsx';
|
|
|
4
4
|
* Uses clsx for conditional classes and tailwind-merge to handle conflicts
|
|
5
5
|
*/
|
|
6
6
|
export declare function cn(...inputs: ClassValue[]): string;
|
|
7
|
-
/**
|
|
8
|
-
* Generate a unique ID with an optional prefix.
|
|
9
|
-
* Uses crypto.randomUUID() to avoid SSR hydration mismatches caused by
|
|
10
|
-
* sequential counters (where server and client counts can diverge).
|
|
11
|
-
* The short suffix is derived from the UUID for compact, readable IDs.
|
|
12
|
-
*
|
|
13
|
-
* @param prefix - Optional prefix for the ID (e.g., 'checkbox', 'tabs')
|
|
14
|
-
* @returns A unique ID string like 'checkbox-a1b2c3d4' or 'ct-a1b2c3d4'
|
|
15
|
-
*/
|
|
16
|
-
export declare function useId(prefix?: string): string;
|
package/dist/lib/utils.js
CHANGED
|
@@ -7,16 +7,3 @@ import { twMerge } from 'tailwind-merge';
|
|
|
7
7
|
export function cn(...inputs) {
|
|
8
8
|
return twMerge(clsx(inputs));
|
|
9
9
|
}
|
|
10
|
-
/**
|
|
11
|
-
* Generate a unique ID with an optional prefix.
|
|
12
|
-
* Uses crypto.randomUUID() to avoid SSR hydration mismatches caused by
|
|
13
|
-
* sequential counters (where server and client counts can diverge).
|
|
14
|
-
* The short suffix is derived from the UUID for compact, readable IDs.
|
|
15
|
-
*
|
|
16
|
-
* @param prefix - Optional prefix for the ID (e.g., 'checkbox', 'tabs')
|
|
17
|
-
* @returns A unique ID string like 'checkbox-a1b2c3d4' or 'ct-a1b2c3d4'
|
|
18
|
-
*/
|
|
19
|
-
export function useId(prefix) {
|
|
20
|
-
const id = crypto.randomUUID().slice(0, 8);
|
|
21
|
-
return prefix ? `${prefix}-${id}` : `ct-${id}`;
|
|
22
|
-
}
|