@loidolt/theme-svelte 0.5.0 → 0.7.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 (125) hide show
  1. package/README.md +11 -0
  2. package/dist/announcer.svelte.d.ts +36 -0
  3. package/dist/announcer.svelte.js +73 -0
  4. package/dist/attachments.d.ts +83 -0
  5. package/dist/attachments.js +211 -0
  6. package/dist/components/ActiveFilterChips.svelte +80 -0
  7. package/dist/components/ActiveFilterChips.svelte.d.ts +41 -0
  8. package/dist/components/AspectRatio.svelte +32 -0
  9. package/dist/components/AspectRatio.svelte.d.ts +13 -0
  10. package/dist/components/AudioPlayer.svelte +94 -0
  11. package/dist/components/AudioPlayer.svelte.d.ts +24 -0
  12. package/dist/components/Avatar.svelte +85 -0
  13. package/dist/components/Avatar.svelte.d.ts +31 -0
  14. package/dist/components/Badge.svelte +28 -2
  15. package/dist/components/Badge.svelte.d.ts +13 -1
  16. package/dist/components/Combobox.svelte +180 -0
  17. package/dist/components/Combobox.svelte.d.ts +77 -0
  18. package/dist/components/DataTable.svelte +228 -0
  19. package/dist/components/DataTable.svelte.d.ts +62 -0
  20. package/dist/components/DataTableColumnVisibility.svelte +46 -0
  21. package/dist/components/DataTableColumnVisibility.svelte.d.ts +31 -0
  22. package/dist/components/DataTableFacetedFilter.svelte +87 -0
  23. package/dist/components/DataTableFacetedFilter.svelte.d.ts +40 -0
  24. package/dist/components/DataTablePagination.svelte +68 -0
  25. package/dist/components/DataTablePagination.svelte.d.ts +42 -0
  26. package/dist/components/DataTableSearch.svelte +67 -0
  27. package/dist/components/DataTableSearch.svelte.d.ts +37 -0
  28. package/dist/components/Dialog.svelte +32 -11
  29. package/dist/components/Dialog.svelte.d.ts +7 -0
  30. package/dist/components/DropdownMenu.svelte +95 -28
  31. package/dist/components/DropdownMenu.svelte.d.ts +12 -3
  32. package/dist/components/Field.svelte +2 -3
  33. package/dist/components/Fieldset.svelte +2 -3
  34. package/dist/components/Filmstrip.svelte +10 -3
  35. package/dist/components/FilterPanel.svelte +62 -0
  36. package/dist/components/FilterPanel.svelte.d.ts +22 -0
  37. package/dist/components/Lightbox.svelte +239 -0
  38. package/dist/components/Lightbox.svelte.d.ts +40 -0
  39. package/dist/components/LiveRegion.svelte +48 -0
  40. package/dist/components/LiveRegion.svelte.d.ts +20 -0
  41. package/dist/components/MediaCarousel.svelte +177 -0
  42. package/dist/components/MediaCarousel.svelte.d.ts +32 -0
  43. package/dist/components/MediaEmbed.svelte +116 -0
  44. package/dist/components/MediaEmbed.svelte.d.ts +34 -0
  45. package/dist/components/MediaGrid.svelte +91 -0
  46. package/dist/components/MediaGrid.svelte.d.ts +24 -0
  47. package/dist/components/MultiSelect.svelte +232 -0
  48. package/dist/components/MultiSelect.svelte.d.ts +67 -0
  49. package/dist/components/NumberField.svelte +53 -25
  50. package/dist/components/NumberField.svelte.d.ts +47 -18
  51. package/dist/components/OTPInput.svelte +94 -0
  52. package/dist/components/OTPInput.svelte.d.ts +26 -0
  53. package/dist/components/PasswordInput.svelte +93 -0
  54. package/dist/components/PasswordInput.svelte.d.ts +24 -0
  55. package/dist/components/Progress.svelte +57 -14
  56. package/dist/components/Progress.svelte.d.ts +13 -0
  57. package/dist/components/RadioGroup.svelte +19 -5
  58. package/dist/components/RadioGroup.svelte.d.ts +3 -2
  59. package/dist/components/Select.svelte +15 -5
  60. package/dist/components/Select.svelte.d.ts +3 -2
  61. package/dist/components/SignaturePad.svelte +317 -0
  62. package/dist/components/SignaturePad.svelte.d.ts +48 -0
  63. package/dist/components/Skeleton.svelte +40 -9
  64. package/dist/components/Skeleton.svelte.d.ts +8 -0
  65. package/dist/components/SkipLink.svelte +48 -0
  66. package/dist/components/SkipLink.svelte.d.ts +14 -0
  67. package/dist/components/Slider.svelte +148 -0
  68. package/dist/components/Slider.svelte.d.ts +55 -0
  69. package/dist/components/Spinner.svelte +11 -2
  70. package/dist/components/Spinner.svelte.d.ts +3 -0
  71. package/dist/components/Tabs.svelte.d.ts +1 -1
  72. package/dist/components/Thumbnail.svelte +52 -6
  73. package/dist/components/Thumbnail.svelte.d.ts +14 -2
  74. package/dist/components/Toast.svelte +24 -0
  75. package/dist/components/Toast.svelte.d.ts +10 -0
  76. package/dist/components/ToastViewport.svelte +9 -1
  77. package/dist/components/ToastViewport.svelte.d.ts +2 -0
  78. package/dist/components/Toolbar.svelte +49 -0
  79. package/dist/components/Toolbar.svelte.d.ts +23 -0
  80. package/dist/components/Tooltip.svelte +9 -1
  81. package/dist/components/Tooltip.svelte.d.ts +2 -0
  82. package/dist/components/VideoPlayer.svelte +136 -0
  83. package/dist/components/VideoPlayer.svelte.d.ts +38 -0
  84. package/dist/data-table-utils.d.ts +62 -0
  85. package/dist/data-table-utils.js +91 -0
  86. package/dist/data-table.svelte.d.ts +132 -0
  87. package/dist/data-table.svelte.js +292 -0
  88. package/dist/hls.d.ts +35 -0
  89. package/dist/hls.js +37 -0
  90. package/dist/hls.svelte.d.ts +22 -0
  91. package/dist/hls.svelte.js +66 -0
  92. package/dist/index.d.ts +49 -4
  93. package/dist/index.js +36 -2
  94. package/dist/internal/EditableCell.svelte +58 -0
  95. package/dist/internal/EditableCell.svelte.d.ts +8 -0
  96. package/dist/internal/ListboxEntries.svelte +49 -0
  97. package/dist/internal/ListboxEntries.svelte.d.ts +35 -0
  98. package/dist/internal/media/MediaControls.svelte +163 -0
  99. package/dist/internal/media/MediaControls.svelte.d.ts +12 -0
  100. package/dist/internal/media/MediaStage.svelte +49 -0
  101. package/dist/internal/media/MediaStage.svelte.d.ts +11 -0
  102. package/dist/internal/media/MediaTile.svelte +36 -0
  103. package/dist/internal/media/MediaTile.svelte.d.ts +11 -0
  104. package/dist/internal/media/keys.d.ts +7 -0
  105. package/dist/internal/media/keys.js +51 -0
  106. package/dist/internal/media/labels.d.ts +19 -0
  107. package/dist/internal/media/labels.js +15 -0
  108. package/dist/internal/options.d.ts +9 -0
  109. package/dist/internal/options.js +20 -0
  110. package/dist/media-player.svelte.d.ts +61 -0
  111. package/dist/media-player.svelte.js +246 -0
  112. package/dist/media-utils.d.ts +112 -0
  113. package/dist/media-utils.js +205 -0
  114. package/dist/media-zoom.svelte.d.ts +33 -0
  115. package/dist/media-zoom.svelte.js +126 -0
  116. package/dist/signature.d.ts +55 -0
  117. package/dist/signature.js +52 -0
  118. package/dist/toaster.svelte.d.ts +24 -0
  119. package/dist/toaster.svelte.js +23 -0
  120. package/dist/token-colors.svelte.d.ts +49 -0
  121. package/dist/token-colors.svelte.js +165 -0
  122. package/dist/types.d.ts +62 -3
  123. package/dist/utils.d.ts +9 -0
  124. package/dist/utils.js +11 -0
  125. package/package.json +10 -4
@@ -0,0 +1,37 @@
1
+ import type { HTMLInputAttributes } from 'svelte/elements';
2
+ import type { DataTableState } from '../data-table.svelte.js';
3
+ declare function $$render<T>(): {
4
+ props: Omit<HTMLInputAttributes, "value" | "type"> & {
5
+ table: DataTableState<T>;
6
+ /** Filter one column instead of searching them all. */
7
+ column?: string;
8
+ /** Accessible name, when not wrapped in a `Field`. */
9
+ label?: string;
10
+ /** Milliseconds to wait after the last keystroke — worth raising for a server search. */
11
+ debounce?: number;
12
+ boxed?: boolean;
13
+ class?: string;
14
+ ref?: HTMLInputElement | null;
15
+ };
16
+ exports: {};
17
+ bindings: "ref";
18
+ slots: {};
19
+ events: {};
20
+ };
21
+ declare class __sveltets_Render<T> {
22
+ props(): ReturnType<typeof $$render<T>>['props'];
23
+ events(): ReturnType<typeof $$render<T>>['events'];
24
+ slots(): ReturnType<typeof $$render<T>>['slots'];
25
+ bindings(): "ref";
26
+ exports(): {};
27
+ }
28
+ interface $$IsomorphicComponent {
29
+ new <T>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
30
+ $$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
31
+ } & ReturnType<__sveltets_Render<T>['exports']>;
32
+ <T>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
33
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
34
+ }
35
+ declare const DataTableSearch: $$IsomorphicComponent;
36
+ type DataTableSearch<T> = InstanceType<typeof DataTableSearch<T>>;
37
+ export default DataTableSearch;
@@ -25,6 +25,13 @@
25
25
  triggerClass?: string;
26
26
  children: Snippet;
27
27
  footer?: Snippet;
28
+ /**
29
+ * Replaces the visible title block — for a header with media, a status badge or a custom
30
+ * layout. `title` is still required: it stays the dialog's accessible name, visually hidden.
31
+ */
32
+ header?: Snippet;
33
+ /** Controls beside the close button, e.g. an overflow menu or a "pop out" link. */
34
+ headerActions?: Snippet;
28
35
  /** Sets `--ldt-dialog-width` via a modifier class. Omit to inherit the CSS default. */
29
36
  size?: DialogSize;
30
37
  headingLevel?: HeadingLevel;
@@ -48,6 +55,8 @@
48
55
  triggerClass,
49
56
  children,
50
57
  footer,
58
+ header,
59
+ headerActions,
51
60
  size,
52
61
  headingLevel = 2,
53
62
  showClose = true,
@@ -87,17 +96,29 @@
87
96
  {...rest}
88
97
  >
89
98
  <div class="ldt-dialog__header">
90
- <div>
91
- <DialogPrimitive.Title level={headingLevel} class="ldt-dialog__title"
92
- >{title}</DialogPrimitive.Title
93
- >{#if description}<DialogPrimitive.Description class="ldt-dialog__description"
94
- >{description}</DialogPrimitive.Description
95
- >{/if}
96
- </div>
97
- {#if showClose}<DialogPrimitive.Close
98
- class="ldt-button ldt-icon-button ldt-button--ghost"
99
- aria-label={closeLabel}>×</DialogPrimitive.Close
100
- >{/if}
99
+ {#if header}
100
+ <div>
101
+ <DialogPrimitive.Title level={headingLevel} class="ldt-sr-only"
102
+ >{title}</DialogPrimitive.Title
103
+ >{@render header()}{#if description}<DialogPrimitive.Description class="ldt-sr-only"
104
+ >{description}</DialogPrimitive.Description
105
+ >{/if}
106
+ </div>
107
+ {:else}
108
+ <div>
109
+ <DialogPrimitive.Title level={headingLevel} class="ldt-dialog__title"
110
+ >{title}</DialogPrimitive.Title
111
+ >{#if description}<DialogPrimitive.Description class="ldt-dialog__description"
112
+ >{description}</DialogPrimitive.Description
113
+ >{/if}
114
+ </div>
115
+ {/if}
116
+ {#if headerActions || showClose}<div class="ldt-dialog__actions">
117
+ {#if headerActions}{@render headerActions()}{/if}{#if showClose}<DialogPrimitive.Close
118
+ class="ldt-button ldt-icon-button ldt-button--ghost"
119
+ aria-label={closeLabel}>×</DialogPrimitive.Close
120
+ >{/if}
121
+ </div>{/if}
101
122
  </div>
102
123
  <div class="ldt-dialog__body">{@render children()}</div>
103
124
  {#if footer}<div class="ldt-dialog__footer">{@render footer()}</div>{/if}
@@ -15,6 +15,13 @@ type Props = Omit<DialogContentProps, 'children' | 'class' | 'child'> & {
15
15
  triggerClass?: string;
16
16
  children: Snippet;
17
17
  footer?: Snippet;
18
+ /**
19
+ * Replaces the visible title block — for a header with media, a status badge or a custom
20
+ * layout. `title` is still required: it stays the dialog's accessible name, visually hidden.
21
+ */
22
+ header?: Snippet;
23
+ /** Controls beside the close button, e.g. an overflow menu or a "pop out" link. */
24
+ headerActions?: Snippet;
18
25
  /** Sets `--ldt-dialog-width` via a modifier class. Omit to inherit the CSS default. */
19
26
  size?: DialogSize;
20
27
  headingLevel?: HeadingLevel;
@@ -2,7 +2,7 @@
2
2
  import { DropdownMenu as MenuPrimitive } from 'bits-ui';
3
3
  import type { Snippet } from 'svelte';
4
4
  import type { DropdownMenuContentProps } from 'bits-ui';
5
- import type { Alignment, MenuItem, Placement, TriggerChildProps } from '../types.js';
5
+ import type { Alignment, MenuEntry, Placement, TriggerChildProps } from '../types.js';
6
6
  import { cx } from '../utils.js';
7
7
 
8
8
  type Props = Omit<DropdownMenuContentProps, 'children' | 'class' | 'child'> & {
@@ -13,8 +13,12 @@
13
13
  /** Full control of the trigger element — receives the props Bits needs on it. */
14
14
  triggerChild?: Snippet<[TriggerChildProps]>;
15
15
  triggerClass?: string;
16
- /** Declarative rows. Ignored when `children` is provided. */
17
- items?: MenuItem<T>[];
16
+ /**
17
+ * Declarative rows: plain items, plus `type: 'group' | 'checkbox' | 'radio' | 'sub'` entries
18
+ * for headings, toggles, one-of-many choices and nested menus. Ignored when `children` is
19
+ * provided.
20
+ */
21
+ items?: MenuEntry<T>[];
18
22
  /** Escape hatch: compose the menu body yourself from the re-exported primitives. */
19
23
  children?: Snippet;
20
24
  side?: Placement;
@@ -22,7 +26,12 @@
22
26
  sideOffset?: number;
23
27
  /** `class` lands on the portaled Content element. */
24
28
  class?: string;
29
+ /** An action row was chosen. */
25
30
  onSelect?: (value: T) => void;
31
+ /** A `checkbox` entry was toggled. Update its `checked` in your `items`. */
32
+ onCheckedChange?: (value: T, checked: boolean) => void;
33
+ /** A `radio` group's choice changed. Update its `value` in your `items`. */
34
+ onValueChange?: (name: string, value: T) => void;
26
35
  onOpenChange?: (open: boolean) => void;
27
36
  ref?: HTMLElement | null;
28
37
  };
@@ -40,36 +49,94 @@
40
49
  sideOffset = 6,
41
50
  class: className,
42
51
  onSelect,
52
+ onCheckedChange,
53
+ onValueChange,
43
54
  onOpenChange,
44
55
  ref = $bindable(null),
45
56
  ...rest
46
57
  }: Props = $props();
47
58
  </script>
48
59
 
49
- {#snippet rows()}
50
- {#each items as item (item.value)}
51
- {#if item.separatorBefore}<MenuPrimitive.Separator class="ldt-menu__separator" />{/if}
52
- <MenuPrimitive.Item
53
- class="ldt-menu__item"
54
- disabled={item.disabled}
55
- onSelect={() => onSelect?.(item.value)}
56
- >
57
- {#snippet child({ props })}
58
- {#if item.href}
59
- <a {...props} href={item.href}
60
- >{#if item.icon}{@render item.icon()}{/if}<span>{item.label}</span>{#if item.hint}<span
61
- class="ldt-menu__hint">{item.hint}</span
62
- >{/if}</a
63
- >
64
- {:else}
65
- <button {...props} type="button"
66
- >{#if item.icon}{@render item.icon()}{/if}<span>{item.label}</span>{#if item.hint}<span
67
- class="ldt-menu__hint">{item.hint}</span
68
- >{/if}</button
60
+ {#snippet label(text: string, hint?: string, icon?: Snippet)}
61
+ {#if icon}{@render icon()}{/if}<span>{text}</span>{#if hint}<span class="ldt-menu__hint"
62
+ >{hint}</span
63
+ >{/if}
64
+ {/snippet}
65
+
66
+ {#snippet rows(entries: MenuEntry<T>[])}
67
+ {#each entries as entry, index (entry.type === 'radio' ? `radio:${entry.name}` : entry.type === 'group' || entry.type === 'sub' ? `${entry.type}:${entry.label ?? index}` : entry.value)}
68
+ {#if entry.separatorBefore}<MenuPrimitive.Separator class="ldt-menu__separator" />{/if}
69
+ {#if entry.type === 'group'}
70
+ <MenuPrimitive.Group class="ldt-menu__group">
71
+ {#if entry.label}<MenuPrimitive.GroupHeading class="ldt-menu__label"
72
+ >{entry.label}</MenuPrimitive.GroupHeading
73
+ >{/if}
74
+ {@render rows(entry.items)}
75
+ </MenuPrimitive.Group>
76
+ {:else if entry.type === 'checkbox'}
77
+ <MenuPrimitive.CheckboxItem
78
+ class="ldt-menu__item ldt-menu__item--check"
79
+ checked={entry.checked}
80
+ disabled={entry.disabled}
81
+ closeOnSelect={false}
82
+ onCheckedChange={(checked) => onCheckedChange?.(entry.value, checked)}
83
+ >
84
+ <span class="ldt-menu__indicator" aria-hidden="true"></span>{@render label(
85
+ entry.label,
86
+ entry.hint
87
+ )}
88
+ </MenuPrimitive.CheckboxItem>
89
+ {:else if entry.type === 'radio'}
90
+ <MenuPrimitive.RadioGroup
91
+ class="ldt-menu__group"
92
+ value={entry.value}
93
+ onValueChange={(value) => onValueChange?.(entry.name, value as T)}
94
+ >
95
+ {#if entry.label}<MenuPrimitive.GroupHeading class="ldt-menu__label"
96
+ >{entry.label}</MenuPrimitive.GroupHeading
97
+ >{/if}
98
+ {#each entry.options as option (option.value)}
99
+ <MenuPrimitive.RadioItem
100
+ class="ldt-menu__item ldt-menu__item--radio"
101
+ value={option.value}
102
+ disabled={option.disabled}
103
+ closeOnSelect={false}
69
104
  >
70
- {/if}
71
- {/snippet}
72
- </MenuPrimitive.Item>
105
+ <span class="ldt-menu__indicator" aria-hidden="true"></span><span>{option.label}</span>
106
+ </MenuPrimitive.RadioItem>
107
+ {/each}
108
+ </MenuPrimitive.RadioGroup>
109
+ {:else if entry.type === 'sub'}
110
+ <MenuPrimitive.Sub>
111
+ <MenuPrimitive.SubTrigger
112
+ class="ldt-menu__item ldt-menu__item--sub"
113
+ disabled={entry.disabled}
114
+ >{@render label(entry.label, undefined, entry.icon)}<span
115
+ class="ldt-menu__chevron"
116
+ aria-hidden="true"
117
+ ></span></MenuPrimitive.SubTrigger
118
+ >
119
+ <MenuPrimitive.SubContent class="ldt-menu" sideOffset={2}>
120
+ {@render rows(entry.items)}
121
+ </MenuPrimitive.SubContent>
122
+ </MenuPrimitive.Sub>
123
+ {:else}
124
+ {@const hint = entry.hint ?? entry.shortcut}
125
+ <MenuPrimitive.Item
126
+ class={cx('ldt-menu__item', entry.destructive && 'ldt-menu__item--danger')}
127
+ disabled={entry.disabled}
128
+ aria-keyshortcuts={entry.shortcut}
129
+ onSelect={() => onSelect?.(entry.value)}
130
+ >
131
+ {#snippet child({ props })}
132
+ {#if entry.href}
133
+ <a {...props} href={entry.href}>{@render label(entry.label, hint, entry.icon)}</a>
134
+ {:else}
135
+ <button {...props} type="button">{@render label(entry.label, hint, entry.icon)}</button>
136
+ {/if}
137
+ {/snippet}
138
+ </MenuPrimitive.Item>
139
+ {/if}
73
140
  {/each}
74
141
  {/snippet}
75
142
 
@@ -92,8 +159,8 @@
92
159
  >{#if children}{@render children()}{:else if groupLabel}<MenuPrimitive.Group
93
160
  ><MenuPrimitive.GroupHeading class="ldt-menu__label"
94
161
  >{groupLabel}</MenuPrimitive.GroupHeading
95
- >{@render rows()}</MenuPrimitive.Group
96
- >{:else}{@render rows()}{/if}</MenuPrimitive.Content
162
+ >{@render rows(items)}</MenuPrimitive.Group
163
+ >{:else}{@render rows(items)}{/if}</MenuPrimitive.Content
97
164
  ></MenuPrimitive.Portal
98
165
  >
99
166
  </MenuPrimitive.Root>
@@ -1,6 +1,6 @@
1
1
  import { DropdownMenu as MenuPrimitive } from 'bits-ui';
2
2
  import type { Snippet } from 'svelte';
3
- import type { Alignment, MenuItem, Placement, TriggerChildProps } from '../types.js';
3
+ import type { Alignment, MenuEntry, Placement, TriggerChildProps } from '../types.js';
4
4
  declare function $$render<T extends string = string>(): {
5
5
  props: Omit<MenuPrimitive.ContentProps, "children" | "class" | "child"> & {
6
6
  open?: boolean;
@@ -10,8 +10,12 @@ declare function $$render<T extends string = string>(): {
10
10
  /** Full control of the trigger element — receives the props Bits needs on it. */
11
11
  triggerChild?: Snippet<[TriggerChildProps]>;
12
12
  triggerClass?: string;
13
- /** Declarative rows. Ignored when `children` is provided. */
14
- items?: MenuItem<T>[];
13
+ /**
14
+ * Declarative rows: plain items, plus `type: 'group' | 'checkbox' | 'radio' | 'sub'` entries
15
+ * for headings, toggles, one-of-many choices and nested menus. Ignored when `children` is
16
+ * provided.
17
+ */
18
+ items?: MenuEntry<T>[];
15
19
  /** Escape hatch: compose the menu body yourself from the re-exported primitives. */
16
20
  children?: Snippet;
17
21
  side?: Placement;
@@ -19,7 +23,12 @@ declare function $$render<T extends string = string>(): {
19
23
  sideOffset?: number;
20
24
  /** `class` lands on the portaled Content element. */
21
25
  class?: string;
26
+ /** An action row was chosen. */
22
27
  onSelect?: (value: T) => void;
28
+ /** A `checkbox` entry was toggled. Update its `checked` in your `items`. */
29
+ onCheckedChange?: (value: T, checked: boolean) => void;
30
+ /** A `radio` group's choice changed. Update its `value` in your `items`. */
31
+ onValueChange?: (name: string, value: T) => void;
23
32
  onOpenChange?: (open: boolean) => void;
24
33
  ref?: HTMLElement | null;
25
34
  };
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
3
  import type { HTMLAttributes } from 'svelte/elements';
4
- import { cx } from '../utils.js';
4
+ import { cx, describedBy as describedByIds } from '../utils.js';
5
5
 
6
6
  // `children` is omitted from the base: this snippet takes the field's wiring as an argument.
7
7
  interface Props extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
@@ -37,8 +37,7 @@
37
37
  }: Props = $props();
38
38
 
39
39
  const describedBy = $derived(
40
- [description && `${id}-description`, error && `${id}-error`].filter(Boolean).join(' ') ||
41
- undefined
40
+ describedByIds(description && `${id}-description`, error && `${id}-error`)
42
41
  );
43
42
  </script>
44
43
 
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
3
  import type { HTMLFieldsetAttributes } from 'svelte/elements';
4
- import { cx } from '../utils.js';
4
+ import { cx, describedBy as describedByIds } from '../utils.js';
5
5
 
6
6
  interface Props extends HTMLFieldsetAttributes {
7
7
  /** Defaults to an SSR-stable generated id, used to wire the description and error. */
@@ -34,8 +34,7 @@
34
34
  }: Props = $props();
35
35
 
36
36
  const describedBy = $derived(
37
- [description && `${id}-description`, error && `${id}-error`].filter(Boolean).join(' ') ||
38
- undefined
37
+ describedByIds(description && `${id}-description`, error && `${id}-error`)
39
38
  );
40
39
  </script>
41
40
 
@@ -34,10 +34,17 @@
34
34
 
35
35
  let buttons = $state<Record<string, HTMLButtonElement | null>>({});
36
36
 
37
+ // Keep the selected item in view by scrolling the strip itself. `scrollIntoView` would also
38
+ // scroll every ancestor — the page included — so a strip that changes selection on its own
39
+ // (a rotating carousel) would drag the whole document along with it.
37
40
  $effect(() => {
38
- if (selectedId) {
39
- buttons[selectedId]?.scrollIntoView({ block: 'nearest', inline: 'nearest' });
40
- }
41
+ const button = selectedId ? buttons[selectedId] : null;
42
+ if (!button || !ref) return;
43
+ const [item, strip] = [button.getBoundingClientRect(), ref.getBoundingClientRect()];
44
+ if (item.left < strip.left) ref.scrollLeft -= strip.left - item.left;
45
+ else if (item.right > strip.right) ref.scrollLeft += item.right - strip.right;
46
+ if (item.top < strip.top) ref.scrollTop -= strip.top - item.top;
47
+ else if (item.bottom > strip.bottom) ref.scrollTop += item.bottom - strip.bottom;
41
48
  });
42
49
 
43
50
  function select(id: string) {
@@ -0,0 +1,62 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import type { HTMLAttributes } from 'svelte/elements';
4
+ import { cx } from '../utils.js';
5
+ import Button from './Button.svelte';
6
+
7
+ interface Props extends HTMLAttributes<HTMLDivElement> {
8
+ /**
9
+ * Whether the panel is expanded. The toggle is yours — a `Button` with
10
+ * `aria-expanded={open}` and `aria-controls` set to this panel's `id`.
11
+ */
12
+ open?: boolean;
13
+ /** Accessible name of the region. */
14
+ label?: string;
15
+ /** How many filters are set; shows "Clear all" when above zero and `onClear` is given. */
16
+ activeCount?: number;
17
+ onClear?: () => void;
18
+ clearLabel?: string;
19
+ /** The filter controls, laid out on an auto-fit grid (`--ldt-min` sets the column width). */
20
+ children: Snippet;
21
+ class?: string;
22
+ ref?: HTMLDivElement | null;
23
+ }
24
+
25
+ const generatedId = $props.id();
26
+
27
+ let {
28
+ open = $bindable(false),
29
+ id = generatedId,
30
+ label = 'Filters',
31
+ activeCount = 0,
32
+ onClear,
33
+ clearLabel = 'Clear all',
34
+ children,
35
+ class: className,
36
+ ref = $bindable(null),
37
+ ...rest
38
+ }: Props = $props();
39
+ </script>
40
+
41
+ <!--
42
+ Collapsed with `inert`, not `display: none`, so the height can animate; `inert` still takes the
43
+ hidden controls out of the tab order and the accessibility tree.
44
+ -->
45
+ <div
46
+ bind:this={ref}
47
+ {id}
48
+ class={cx('ldt-filter-panel', className)}
49
+ data-state={open ? 'open' : 'closed'}
50
+ role="region"
51
+ aria-label={label}
52
+ {...rest}
53
+ >
54
+ <div class="ldt-filter-panel__clip" inert={!open}>
55
+ <div class="ldt-filter-panel__body">
56
+ <div class="ldt-filter-panel__grid">{@render children()}</div>
57
+ {#if onClear && activeCount > 0}<div class="ldt-filter-panel__footer">
58
+ <Button variant="text" size="sm" onclick={onClear}>{clearLabel}</Button>
59
+ </div>{/if}
60
+ </div>
61
+ </div>
62
+ </div>
@@ -0,0 +1,22 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ interface Props extends HTMLAttributes<HTMLDivElement> {
4
+ /**
5
+ * Whether the panel is expanded. The toggle is yours — a `Button` with
6
+ * `aria-expanded={open}` and `aria-controls` set to this panel's `id`.
7
+ */
8
+ open?: boolean;
9
+ /** Accessible name of the region. */
10
+ label?: string;
11
+ /** How many filters are set; shows "Clear all" when above zero and `onClear` is given. */
12
+ activeCount?: number;
13
+ onClear?: () => void;
14
+ clearLabel?: string;
15
+ /** The filter controls, laid out on an auto-fit grid (`--ldt-min` sets the column width). */
16
+ children: Snippet;
17
+ class?: string;
18
+ ref?: HTMLDivElement | null;
19
+ }
20
+ declare const FilterPanel: import("svelte").Component<Props, {}, "ref" | "open">;
21
+ type FilterPanel = ReturnType<typeof FilterPanel>;
22
+ export default FilterPanel;