@loidolt/theme-svelte 0.2.0 → 0.4.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 (92) hide show
  1. package/README.md +21 -0
  2. package/dist/components/Accordion.svelte +100 -0
  3. package/dist/components/Accordion.svelte.d.ts +50 -0
  4. package/dist/components/Alert.svelte +6 -3
  5. package/dist/components/Alert.svelte.d.ts +3 -2
  6. package/dist/components/AlertDialog.svelte +130 -0
  7. package/dist/components/AlertDialog.svelte.d.ts +35 -0
  8. package/dist/components/AppShell.svelte +2 -1
  9. package/dist/components/AppShell.svelte.d.ts +1 -0
  10. package/dist/components/Badge.svelte +9 -2
  11. package/dist/components/Badge.svelte.d.ts +2 -1
  12. package/dist/components/Brand.svelte +29 -15
  13. package/dist/components/Brand.svelte.d.ts +8 -4
  14. package/dist/components/Breadcrumbs.svelte +53 -0
  15. package/dist/components/Breadcrumbs.svelte.d.ts +18 -0
  16. package/dist/components/Button.svelte +43 -21
  17. package/dist/components/Button.svelte.d.ts +9 -4
  18. package/dist/components/Card.svelte +23 -5
  19. package/dist/components/Card.svelte.d.ts +11 -4
  20. package/dist/components/CodeBlock.svelte +55 -0
  21. package/dist/components/CodeBlock.svelte.d.ts +14 -0
  22. package/dist/components/CommentList.svelte +67 -0
  23. package/dist/components/CommentList.svelte.d.ts +53 -0
  24. package/dist/components/Dialog.svelte +25 -3
  25. package/dist/components/Dialog.svelte.d.ts +4 -1
  26. package/dist/components/Drawer.svelte +105 -0
  27. package/dist/components/Drawer.svelte.d.ts +31 -0
  28. package/dist/components/DropdownMenu.svelte +2 -2
  29. package/dist/components/DropdownMenu.svelte.d.ts +1 -1
  30. package/dist/components/EmptyState.svelte +40 -0
  31. package/dist/components/EmptyState.svelte.d.ts +19 -0
  32. package/dist/components/Fieldset.svelte +60 -0
  33. package/dist/components/Fieldset.svelte.d.ts +19 -0
  34. package/dist/components/FileInput.svelte +47 -0
  35. package/dist/components/FileInput.svelte.d.ts +15 -0
  36. package/dist/components/Filmstrip.svelte +72 -0
  37. package/dist/components/Filmstrip.svelte.d.ts +50 -0
  38. package/dist/components/FloatingBar.svelte +43 -0
  39. package/dist/components/FloatingBar.svelte.d.ts +16 -0
  40. package/dist/components/IconButton.svelte +1 -1
  41. package/dist/components/Input.svelte.d.ts +1 -1
  42. package/dist/components/ListRow.svelte +50 -0
  43. package/dist/components/ListRow.svelte.d.ts +18 -0
  44. package/dist/components/Marker.svelte +38 -0
  45. package/dist/components/Marker.svelte.d.ts +14 -0
  46. package/dist/components/NavMenu.svelte +6 -1
  47. package/dist/components/NavMenu.svelte.d.ts +2 -1
  48. package/dist/components/NumberField.svelte +12 -1
  49. package/dist/components/NumberField.svelte.d.ts +1 -1
  50. package/dist/components/Pagination.svelte +86 -0
  51. package/dist/components/Pagination.svelte.d.ts +22 -0
  52. package/dist/components/Popover.svelte +24 -3
  53. package/dist/components/Popover.svelte.d.ts +4 -1
  54. package/dist/components/RadioGroup.svelte.d.ts +2 -2
  55. package/dist/components/RecordStepper.svelte +58 -0
  56. package/dist/components/RecordStepper.svelte.d.ts +18 -0
  57. package/dist/components/SegmentedNav.svelte +53 -0
  58. package/dist/components/SegmentedNav.svelte.d.ts +22 -0
  59. package/dist/components/Select.svelte.d.ts +2 -2
  60. package/dist/components/Stat.svelte +41 -0
  61. package/dist/components/Stat.svelte.d.ts +17 -0
  62. package/dist/components/StatusDot.svelte +27 -0
  63. package/dist/components/StatusDot.svelte.d.ts +12 -0
  64. package/dist/components/SwatchGroup.svelte +69 -0
  65. package/dist/components/SwatchGroup.svelte.d.ts +44 -0
  66. package/dist/components/Switch.svelte +19 -0
  67. package/dist/components/Table.svelte +30 -3
  68. package/dist/components/Table.svelte.d.ts +13 -0
  69. package/dist/components/TableHeader.svelte +72 -0
  70. package/dist/components/TableHeader.svelte.d.ts +24 -0
  71. package/dist/components/Tabs.svelte +23 -5
  72. package/dist/components/Tabs.svelte.d.ts +4 -3
  73. package/dist/components/Textarea.svelte.d.ts +1 -1
  74. package/dist/components/ThemeToggle.svelte +133 -0
  75. package/dist/components/ThemeToggle.svelte.d.ts +20 -0
  76. package/dist/components/Thumbnail.svelte +51 -0
  77. package/dist/components/Thumbnail.svelte.d.ts +19 -0
  78. package/dist/components/ToggleGroup.svelte +107 -0
  79. package/dist/components/ToggleGroup.svelte.d.ts +46 -0
  80. package/dist/components/TooltipProvider.svelte +1 -0
  81. package/dist/components/Workspace.svelte +27 -6
  82. package/dist/components/Workspace.svelte.d.ts +12 -0
  83. package/dist/index.d.ts +29 -2
  84. package/dist/index.js +26 -1
  85. package/dist/media.svelte.d.ts +48 -0
  86. package/dist/media.svelte.js +69 -0
  87. package/dist/theme.svelte.d.ts +86 -0
  88. package/dist/theme.svelte.js +166 -0
  89. package/dist/toaster.svelte.d.ts +2 -0
  90. package/dist/toaster.svelte.js +51 -9
  91. package/dist/types.d.ts +21 -0
  92. package/package.json +10 -8
@@ -8,6 +8,19 @@
8
8
  caption?: string;
9
9
  /** Accessible name for the scroll region when there is no `caption`. */
10
10
  regionLabel?: string;
11
+ /**
12
+ * Pins the header row while the body scrolls. Needs a bounded scroll container — set
13
+ * `--ldt-table-height` on the wrapper, or the page scrolls instead and nothing sticks.
14
+ */
15
+ sticky?: boolean;
16
+ /**
17
+ * Rendered in place of the rows, as a single full-width cell. Pass it only when there is
18
+ * nothing to show — `empty={rows.length === 0 ? nothingYet : undefined}` — since the
19
+ * component cannot see inside `children` to count rows.
20
+ */
21
+ empty?: Snippet;
22
+ /** Columns the empty cell spans. Defaults to the whole row. */
23
+ columns?: number;
11
24
  /** `class` lands on the `<table>`; use `wrapperClass` for the scroll container. */
12
25
  class?: string;
13
26
  wrapperClass?: string;
@@ -19,6 +32,9 @@
19
32
  let {
20
33
  caption,
21
34
  regionLabel = 'Data table',
35
+ sticky = false,
36
+ empty,
37
+ columns,
22
38
  class: className,
23
39
  wrapperClass,
24
40
  children,
@@ -32,12 +48,23 @@
32
48
  <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
33
49
  <div
34
50
  bind:this={wrapperRef}
35
- class={cx('ldt-table-wrap', wrapperClass)}
51
+ class={cx('ldt-table-wrap', sticky && 'ldt-table-wrap--sticky', wrapperClass)}
36
52
  role="region"
37
53
  tabindex="0"
38
54
  aria-label={caption ?? regionLabel}
39
55
  >
40
- <table bind:this={ref} class={cx('ldt-table', className)} {...rest}>
41
- {#if caption}<caption class="ldt-sr-only">{caption}</caption>{/if}{@render children()}
56
+ <table
57
+ bind:this={ref}
58
+ class={cx('ldt-table', sticky && 'ldt-table--sticky', className)}
59
+ {...rest}
60
+ >
61
+ {#if caption}<caption class="ldt-sr-only">{caption}</caption
62
+ >{/if}{@render children()}{#if empty}<tbody class="ldt-table__empty">
63
+ <tr>
64
+ <!-- `colspan` defaults to the row-spanning maximum so the cell never leaves a gap
65
+ beside it, whatever the column count turns out to be. -->
66
+ <td colspan={columns ?? 1000}>{@render empty()}</td>
67
+ </tr>
68
+ </tbody>{/if}
42
69
  </table>
43
70
  </div>
@@ -5,6 +5,19 @@ interface Props extends HTMLTableAttributes {
5
5
  caption?: string;
6
6
  /** Accessible name for the scroll region when there is no `caption`. */
7
7
  regionLabel?: string;
8
+ /**
9
+ * Pins the header row while the body scrolls. Needs a bounded scroll container — set
10
+ * `--ldt-table-height` on the wrapper, or the page scrolls instead and nothing sticks.
11
+ */
12
+ sticky?: boolean;
13
+ /**
14
+ * Rendered in place of the rows, as a single full-width cell. Pass it only when there is
15
+ * nothing to show — `empty={rows.length === 0 ? nothingYet : undefined}` — since the
16
+ * component cannot see inside `children` to count rows.
17
+ */
18
+ empty?: Snippet;
19
+ /** Columns the empty cell spans. Defaults to the whole row. */
20
+ columns?: number;
8
21
  /** `class` lands on the `<table>`; use `wrapperClass` for the scroll container. */
9
22
  class?: string;
10
23
  wrapperClass?: string;
@@ -0,0 +1,72 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import type { HTMLThAttributes } from 'svelte/elements';
4
+ import type { ColumnAlign, SortDirection } from '../types.js';
5
+ import { cx } from '../utils.js';
6
+
7
+ // `align` is omitted from the base: the deprecated HTML attribute of that name takes
8
+ // physical values (`left`/`right`), and this one is logical.
9
+ interface Props extends Omit<HTMLThAttributes, 'children' | 'align'> {
10
+ /**
11
+ * Current sort state of this column. Drives `aria-sort` and the indicator; leave at `none`
12
+ * for every column that is not the sorted one, so screen readers report a single sort.
13
+ */
14
+ sort?: SortDirection;
15
+ /**
16
+ * Makes the header a sort control. Called with the direction a press asks for: `ascending`
17
+ * for an unsorted or descending column, `descending` for an ascending one.
18
+ */
19
+ onSort?: (direction: SortDirection) => void;
20
+ align?: ColumnAlign;
21
+ /** Accessible name of the sort button when the header's own text is not enough. */
22
+ sortLabel?: string;
23
+ class?: string;
24
+ children: Snippet;
25
+ ref?: HTMLTableCellElement | null;
26
+ }
27
+
28
+ let {
29
+ sort = 'none',
30
+ onSort,
31
+ align,
32
+ sortLabel,
33
+ class: className,
34
+ children,
35
+ ref = $bindable(null),
36
+ ...rest
37
+ }: Props = $props();
38
+
39
+ // Two-state cycle. A third "unsorted" step reads as a broken control far more often than it
40
+ // reads as a feature: the table has to be sorted *somehow*, so there is nothing to return to.
41
+ const next = $derived<SortDirection>(sort === 'ascending' ? 'descending' : 'ascending');
42
+ </script>
43
+
44
+ <!--
45
+ A `<th>` that can sort. `aria-sort` lives on the cell (where the spec puts it) while the
46
+ press target is a real button inside it, so the column is reachable and announced by keyboard
47
+ without turning the whole cell into a click surface.
48
+ -->
49
+ <th
50
+ bind:this={ref}
51
+ scope="col"
52
+ class={cx('ldt-table__header', className)}
53
+ data-align={align}
54
+ aria-sort={onSort ? sort : undefined}
55
+ {...rest}
56
+ >
57
+ {#if onSort}
58
+ <button
59
+ type="button"
60
+ class="ldt-table__sort"
61
+ data-sort={sort}
62
+ aria-label={sortLabel}
63
+ onclick={() => onSort(next)}
64
+ >
65
+ {@render children()}
66
+ <!-- Decorative: `aria-sort` already carries the state to assistive tech. -->
67
+ <span class="ldt-table__sort-indicator" aria-hidden="true"></span>
68
+ </button>
69
+ {:else}
70
+ {@render children()}
71
+ {/if}
72
+ </th>
@@ -0,0 +1,24 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLThAttributes } from 'svelte/elements';
3
+ import type { ColumnAlign, SortDirection } from '../types.js';
4
+ interface Props extends Omit<HTMLThAttributes, 'children' | 'align'> {
5
+ /**
6
+ * Current sort state of this column. Drives `aria-sort` and the indicator; leave at `none`
7
+ * for every column that is not the sorted one, so screen readers report a single sort.
8
+ */
9
+ sort?: SortDirection;
10
+ /**
11
+ * Makes the header a sort control. Called with the direction a press asks for: `ascending`
12
+ * for an unsorted or descending column, `descending` for an ascending one.
13
+ */
14
+ onSort?: (direction: SortDirection) => void;
15
+ align?: ColumnAlign;
16
+ /** Accessible name of the sort button when the header's own text is not enough. */
17
+ sortLabel?: string;
18
+ class?: string;
19
+ children: Snippet;
20
+ ref?: HTMLTableCellElement | null;
21
+ }
22
+ declare const TableHeader: import("svelte").Component<Props, {}, "ref">;
23
+ type TableHeader = ReturnType<typeof TableHeader>;
24
+ export default TableHeader;
@@ -1,10 +1,14 @@
1
1
  <script lang="ts" generics="T extends string = string">
2
2
  import { Tabs as TabsPrimitive } from 'bits-ui';
3
+ import type { TabsRootProps } from 'bits-ui';
3
4
  import type { Snippet } from 'svelte';
4
5
  import type { NavItem, Orientation } from '../types.js';
5
6
  import { cx } from '../utils.js';
6
7
 
7
- interface Props {
8
+ interface Props extends Omit<
9
+ TabsRootProps,
10
+ 'value' | 'onValueChange' | 'orientation' | 'children' | 'child' | 'class' | 'ref'
11
+ > {
8
12
  /** Defaults to the first tab, so a panel is always rendered. */
9
13
  value?: T;
10
14
  tabs: NavItem<T>[];
@@ -20,8 +24,10 @@
20
24
  }
21
25
 
22
26
  let {
23
- value = $bindable(),
24
27
  tabs,
28
+ // Defaulted at destructure time (not in an effect) so SSR output and the first client
29
+ // frame already agree with what a parent binding observes.
30
+ value = $bindable(tabs[0]?.value),
25
31
  label,
26
32
  orientation = 'horizontal',
27
33
  children,
@@ -30,13 +36,24 @@
30
36
  contentClass,
31
37
  onValueChange,
32
38
  ref = $bindable(null),
39
+ ...rest
33
40
  }: Props = $props();
34
41
 
42
+ // Falls back to the first tab when `value` points at a tab that no longer exists,
43
+ // so removing the selected tab cannot blank every panel.
44
+ const active = $derived(
45
+ value !== undefined && tabs.some((tab) => tab.value === value) ? value : tabs[0]?.value
46
+ );
47
+
48
+ // Keep the public binding honest when a dynamic collection removes the selected tab. Effects
49
+ // do not run during SSR, so the server render remains side-effect free while the client and its
50
+ // parent converge on the fallback that is already being displayed.
35
51
  $effect(() => {
36
- if (value === undefined && tabs.length > 0) value = tabs[0].value;
52
+ if (active !== undefined && value !== active) {
53
+ value = active;
54
+ onValueChange?.(active);
55
+ }
37
56
  });
38
-
39
- const active = $derived(value ?? tabs[0]?.value);
40
57
  </script>
41
58
 
42
59
  <TabsPrimitive.Root
@@ -48,6 +65,7 @@
48
65
  }}
49
66
  {orientation}
50
67
  class={cx('ldt-tabs', className)}
68
+ {...rest}
51
69
  >
52
70
  <TabsPrimitive.List class={cx('ldt-tabs__list', listClass)} aria-label={label}
53
71
  >{#each tabs as tab (tab.value)}<TabsPrimitive.Trigger
@@ -1,7 +1,8 @@
1
+ import { Tabs as TabsPrimitive } from 'bits-ui';
1
2
  import type { Snippet } from 'svelte';
2
3
  import type { NavItem, Orientation } from '../types.js';
3
4
  declare function $$render<T extends string = string>(): {
4
- props: {
5
+ props: Omit<TabsPrimitive.RootProps, "children" | "class" | "value" | "onValueChange" | "ref" | "child" | "orientation"> & {
5
6
  /** Defaults to the first tab, so a panel is always rendered. */
6
7
  value?: T;
7
8
  tabs: NavItem<T>[];
@@ -18,7 +19,7 @@ declare function $$render<T extends string = string>(): {
18
19
  ref?: HTMLElement | null;
19
20
  };
20
21
  exports: {};
21
- bindings: "ref" | "value";
22
+ bindings: "value" | "ref";
22
23
  slots: {};
23
24
  events: {};
24
25
  };
@@ -26,7 +27,7 @@ declare class __sveltets_Render<T extends string = string> {
26
27
  props(): ReturnType<typeof $$render<T>>['props'];
27
28
  events(): ReturnType<typeof $$render<T>>['events'];
28
29
  slots(): ReturnType<typeof $$render<T>>['slots'];
29
- bindings(): "ref" | "value";
30
+ bindings(): "value" | "ref";
30
31
  exports(): {};
31
32
  }
32
33
  interface $$IsomorphicComponent {
@@ -6,6 +6,6 @@ interface Props extends Omit<HTMLTextareaAttributes, 'value'> {
6
6
  class?: string;
7
7
  ref?: HTMLTextAreaElement | null;
8
8
  }
9
- declare const Textarea: import("svelte").Component<Props, {}, "ref" | "value">;
9
+ declare const Textarea: import("svelte").Component<Props, {}, "value" | "ref">;
10
10
  type Textarea = ReturnType<typeof Textarea>;
11
11
  export default Textarea;
@@ -0,0 +1,133 @@
1
+ <script lang="ts">
2
+ import type { HTMLButtonAttributes } from 'svelte/elements';
3
+ import type { ControlSize } from '../types.js';
4
+ import type { Theme, ThemePreference } from '../theme.svelte.js';
5
+ import { cx } from '../utils.js';
6
+
7
+ interface Props extends Omit<HTMLButtonAttributes, 'children' | 'aria-label'> {
8
+ /** The store from `createTheme()`. */
9
+ theme: Theme;
10
+ /** Accessible name for the preference represented by the icon. */
11
+ label?: string;
12
+ lightLabel?: string;
13
+ darkLabel?: string;
14
+ systemLabel?: string;
15
+ /** Include "system" in the cycle. */
16
+ showSystem?: boolean;
17
+ size?: ControlSize;
18
+ class?: string;
19
+ ref?: HTMLButtonElement | null;
20
+ }
21
+
22
+ let {
23
+ theme,
24
+ label = 'Colour scheme',
25
+ lightLabel = 'Light',
26
+ darkLabel = 'Dark',
27
+ systemLabel = 'System',
28
+ showSystem = true,
29
+ size = 'sm',
30
+ class: className,
31
+ disabled = false,
32
+ type = 'button',
33
+ title,
34
+ onclick,
35
+ ref = $bindable(null),
36
+ ...rest
37
+ }: Props = $props();
38
+
39
+ const preferences = $derived<ThemePreference[]>(
40
+ showSystem ? ['light', 'dark', 'system'] : ['light', 'dark']
41
+ );
42
+
43
+ // If the system option is hidden, represent the scheme that is actually showing and toggle
44
+ // away from it on the first press.
45
+ const activePreference = $derived<ThemePreference>(
46
+ !showSystem && theme.preference === 'system' ? theme.resolved : theme.preference
47
+ );
48
+ const currentIndex = $derived(preferences.indexOf(activePreference));
49
+ const nextPreference = $derived(preferences[(currentIndex + 1) % preferences.length]);
50
+
51
+ const preferenceLabel = (preference: ThemePreference) => {
52
+ if (preference === 'light') return lightLabel;
53
+ if (preference === 'dark') return darkLabel;
54
+ return systemLabel;
55
+ };
56
+
57
+ const currentLabel = $derived(preferenceLabel(activePreference));
58
+ const nextLabel = $derived(preferenceLabel(nextPreference));
59
+ const buttonLabel = $derived(`${label}: ${currentLabel}`);
60
+ const buttonTitle = $derived(title ?? `${buttonLabel} → ${nextLabel}`);
61
+ </script>
62
+
63
+ <!-- A native button gives the three-state cycle standard keyboard behavior in one compact target. -->
64
+ <button
65
+ bind:this={ref}
66
+ {type}
67
+ {disabled}
68
+ aria-label={buttonLabel}
69
+ title={buttonTitle}
70
+ class={cx(
71
+ 'ldt-button ldt-icon-button ldt-button--ghost',
72
+ size !== 'md' && `ldt-button--${size}`,
73
+ className
74
+ )}
75
+ data-theme-preference={activePreference}
76
+ onclick={(event) => {
77
+ theme.preference = nextPreference;
78
+ onclick?.(event);
79
+ }}
80
+ {...rest}
81
+ >
82
+ {#if activePreference === 'light'}
83
+ <svg
84
+ data-theme-icon="light"
85
+ aria-hidden="true"
86
+ focusable="false"
87
+ width="18"
88
+ height="18"
89
+ viewBox="0 0 24 24"
90
+ fill="none"
91
+ stroke="currentColor"
92
+ stroke-width="1.8"
93
+ stroke-linecap="square"
94
+ >
95
+ <circle cx="12" cy="12" r="3.5" />
96
+ <path
97
+ d="M12 2v2M12 20v2M4.93 4.93l1.42 1.42M17.65 17.65l1.42 1.42M2 12h2M20 12h2M4.93 19.07l1.42-1.42M17.65 6.35l1.42-1.42"
98
+ />
99
+ </svg>
100
+ {:else if activePreference === 'dark'}
101
+ <svg
102
+ data-theme-icon="dark"
103
+ aria-hidden="true"
104
+ focusable="false"
105
+ width="18"
106
+ height="18"
107
+ viewBox="0 0 24 24"
108
+ fill="none"
109
+ stroke="currentColor"
110
+ stroke-width="1.8"
111
+ stroke-linejoin="miter"
112
+ >
113
+ <path d="M20.4 15.4A8.5 8.5 0 0 1 8.6 3.6 8.5 8.5 0 1 0 20.4 15.4Z" />
114
+ </svg>
115
+ {:else}
116
+ <svg
117
+ data-theme-icon="system"
118
+ aria-hidden="true"
119
+ focusable="false"
120
+ width="18"
121
+ height="18"
122
+ viewBox="0 0 24 24"
123
+ fill="none"
124
+ stroke="currentColor"
125
+ stroke-width="1.8"
126
+ stroke-linecap="square"
127
+ stroke-linejoin="miter"
128
+ >
129
+ <rect x="3" y="4" width="18" height="13" />
130
+ <path d="M9 21h6M12 17v4" />
131
+ </svg>
132
+ {/if}
133
+ </button>
@@ -0,0 +1,20 @@
1
+ import type { HTMLButtonAttributes } from 'svelte/elements';
2
+ import type { ControlSize } from '../types.js';
3
+ import type { Theme } from '../theme.svelte.js';
4
+ interface Props extends Omit<HTMLButtonAttributes, 'children' | 'aria-label'> {
5
+ /** The store from `createTheme()`. */
6
+ theme: Theme;
7
+ /** Accessible name for the preference represented by the icon. */
8
+ label?: string;
9
+ lightLabel?: string;
10
+ darkLabel?: string;
11
+ systemLabel?: string;
12
+ /** Include "system" in the cycle. */
13
+ showSystem?: boolean;
14
+ size?: ControlSize;
15
+ class?: string;
16
+ ref?: HTMLButtonElement | null;
17
+ }
18
+ declare const ThemeToggle: import("svelte").Component<Props, {}, "ref">;
19
+ type ThemeToggle = ReturnType<typeof ThemeToggle>;
20
+ export default ThemeToggle;
@@ -0,0 +1,51 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import type { HTMLAttributes } from 'svelte/elements';
4
+ import { cx } from '../utils.js';
5
+
6
+ interface Props extends Omit<HTMLAttributes<HTMLElement>, 'children'> {
7
+ src?: string;
8
+ /** Required whenever `src` is set — decorative images should say so with `alt=""`. */
9
+ alt?: string;
10
+ /** CSS aspect-ratio value: `'4/3'`, `'1/1'`, or a number. */
11
+ ratio?: string | number;
12
+ fit?: 'contain' | 'cover';
13
+ /** `sunken` recesses into the page; `paper` stays white in every theme for artwork. */
14
+ surface?: 'sunken' | 'paper';
15
+ /** Alternative to `src` — inline SVG, canvas, anything that fills the frame. */
16
+ children?: Snippet;
17
+ class?: string;
18
+ ref?: HTMLElement | null;
19
+ }
20
+
21
+ let {
22
+ src,
23
+ alt = '',
24
+ ratio = '4/3',
25
+ fit = 'contain',
26
+ surface = 'sunken',
27
+ children,
28
+ class: className,
29
+ ref = $bindable(null),
30
+ ...rest
31
+ }: Props = $props();
32
+ </script>
33
+
34
+ <figure
35
+ bind:this={ref}
36
+ class={cx(
37
+ 'ldt-thumbnail',
38
+ surface === 'paper' && 'ldt-thumbnail--paper',
39
+ fit === 'cover' && 'ldt-thumbnail--cover',
40
+ className
41
+ )}
42
+ style={`--ldt-thumbnail-ratio: ${ratio};`}
43
+ {...rest}
44
+ >
45
+ {#if children}{@render children()}{:else if src}<img
46
+ class="ldt-thumbnail__image"
47
+ {src}
48
+ {alt}
49
+ loading="lazy"
50
+ />{/if}
51
+ </figure>
@@ -0,0 +1,19 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ interface Props extends Omit<HTMLAttributes<HTMLElement>, 'children'> {
4
+ src?: string;
5
+ /** Required whenever `src` is set — decorative images should say so with `alt=""`. */
6
+ alt?: string;
7
+ /** CSS aspect-ratio value: `'4/3'`, `'1/1'`, or a number. */
8
+ ratio?: string | number;
9
+ fit?: 'contain' | 'cover';
10
+ /** `sunken` recesses into the page; `paper` stays white in every theme for artwork. */
11
+ surface?: 'sunken' | 'paper';
12
+ /** Alternative to `src` — inline SVG, canvas, anything that fills the frame. */
13
+ children?: Snippet;
14
+ class?: string;
15
+ ref?: HTMLElement | null;
16
+ }
17
+ declare const Thumbnail: import("svelte").Component<Props, {}, "ref">;
18
+ type Thumbnail = ReturnType<typeof Thumbnail>;
19
+ export default Thumbnail;
@@ -0,0 +1,107 @@
1
+ <script lang="ts" generics="T extends string = string">
2
+ import { ToggleGroup as ToggleGroupPrimitive } from 'bits-ui';
3
+ import type { Snippet } from 'svelte';
4
+ import type { HTMLAttributes } from 'svelte/elements';
5
+ import type { ControlSize, Option, Orientation } from '../types.js';
6
+ import { cx } from '../utils.js';
7
+
8
+ interface Props extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
9
+ options: Option<T>[];
10
+ /** Narrowed from the DOM attribute: the Bits root this spreads onto rejects `null`. */
11
+ id?: string;
12
+ /** Lets more than one option be pressed at once. `value` is then an array. */
13
+ multiple?: boolean;
14
+ /** Pressed option, or options when `multiple`. Bindable. */
15
+ value?: T | T[];
16
+ /** Accessible name of the group. */
17
+ label?: string;
18
+ size?: ControlSize;
19
+ orientation?: Orientation;
20
+ disabled?: boolean;
21
+ /** Renders an option's contents — an icon, say — in place of its plain label. */
22
+ children?: Snippet<[Option<T>]>;
23
+ class?: string;
24
+ itemClass?: string;
25
+ onValueChange?: (value: T | T[]) => void;
26
+ ref?: HTMLDivElement | null;
27
+ }
28
+
29
+ let {
30
+ options,
31
+ multiple = false,
32
+ value = $bindable(),
33
+ label,
34
+ size = 'md',
35
+ orientation = 'horizontal',
36
+ disabled = false,
37
+ children,
38
+ class: className,
39
+ itemClass,
40
+ onValueChange,
41
+ ref = $bindable(null),
42
+ ...rest
43
+ }: Props = $props();
44
+
45
+ const classes = $derived(
46
+ cx('ldt-toggle-group', size !== 'md' && `ldt-toggle-group--${size}`, className)
47
+ );
48
+
49
+ // As with `Accordion`, only the Root's typing differs between the two modes.
50
+ const change = (next: string | string[]) => {
51
+ value = next as T | T[];
52
+ onValueChange?.(next as T | T[]);
53
+ };
54
+ </script>
55
+
56
+ <!--
57
+ A segmented control: a small, fixed set of choices shown side by side rather than folded into a
58
+ Select. Single mode is a radio group in behaviour — one press replaces the last — so reach for
59
+ it when the options are worth showing at all times.
60
+ -->
61
+ {#snippet items()}
62
+ {#each options as option (option.value)}
63
+ <ToggleGroupPrimitive.Item
64
+ value={option.value}
65
+ disabled={option.disabled}
66
+ class={cx('ldt-toggle-group__item', itemClass)}
67
+ >{#if children}{@render children(option)}{:else}{option.label}{/if}</ToggleGroupPrimitive.Item
68
+ >
69
+ {/each}
70
+ {/snippet}
71
+
72
+ {#if multiple}
73
+ <ToggleGroupPrimitive.Root
74
+ bind:ref
75
+ type="multiple"
76
+ value={(value ?? []) as string[]}
77
+ onValueChange={change}
78
+ {orientation}
79
+ {disabled}
80
+ class={classes}
81
+ aria-label={label}
82
+ {...rest}
83
+ >
84
+ {@render items()}
85
+ </ToggleGroupPrimitive.Root>
86
+ {:else}
87
+ <!--
88
+ Rendered through `child` to correct the root's role. In single mode Bits marks the items
89
+ `role="radio"` but leaves the root at `role="group"`, and a radio must be owned by a
90
+ `radiogroup` — otherwise assistive tech reports loose radios with no set and no position.
91
+ -->
92
+ <ToggleGroupPrimitive.Root
93
+ bind:ref
94
+ type="single"
95
+ value={(value ?? '') as string}
96
+ onValueChange={change}
97
+ {orientation}
98
+ {disabled}
99
+ class={classes}
100
+ aria-label={label}
101
+ {...rest}
102
+ >
103
+ {#snippet child({ props })}
104
+ <div {...props} role="radiogroup">{@render items()}</div>
105
+ {/snippet}
106
+ </ToggleGroupPrimitive.Root>
107
+ {/if}
@@ -0,0 +1,46 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ import type { ControlSize, Option, Orientation } from '../types.js';
4
+ declare function $$render<T extends string = string>(): {
5
+ props: Omit<HTMLAttributes<HTMLDivElement>, "children"> & {
6
+ options: Option<T>[];
7
+ /** Narrowed from the DOM attribute: the Bits root this spreads onto rejects `null`. */
8
+ id?: string;
9
+ /** Lets more than one option be pressed at once. `value` is then an array. */
10
+ multiple?: boolean;
11
+ /** Pressed option, or options when `multiple`. Bindable. */
12
+ value?: T | T[];
13
+ /** Accessible name of the group. */
14
+ label?: string;
15
+ size?: ControlSize;
16
+ orientation?: Orientation;
17
+ disabled?: boolean;
18
+ /** Renders an option's contents — an icon, say — in place of its plain label. */
19
+ children?: Snippet<[Option<T>]>;
20
+ class?: string;
21
+ itemClass?: string;
22
+ onValueChange?: (value: T | T[]) => void;
23
+ ref?: HTMLDivElement | null;
24
+ };
25
+ exports: {};
26
+ bindings: "value" | "ref";
27
+ slots: {};
28
+ events: {};
29
+ };
30
+ declare class __sveltets_Render<T extends string = string> {
31
+ props(): ReturnType<typeof $$render<T>>['props'];
32
+ events(): ReturnType<typeof $$render<T>>['events'];
33
+ slots(): ReturnType<typeof $$render<T>>['slots'];
34
+ bindings(): "value" | "ref";
35
+ exports(): {};
36
+ }
37
+ interface $$IsomorphicComponent {
38
+ new <T extends string = string>(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']>> & {
39
+ $$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
40
+ } & ReturnType<__sveltets_Render<T>['exports']>;
41
+ <T extends string = string>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
42
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
43
+ }
44
+ declare const ToggleGroup: $$IsomorphicComponent;
45
+ type ToggleGroup<T extends string = string> = InstanceType<typeof ToggleGroup<T>>;
46
+ export default ToggleGroup;
@@ -6,6 +6,7 @@
6
6
  import { Tooltip as TooltipPrimitive } from 'bits-ui';
7
7
  import { setContext, type Snippet } from 'svelte';
8
8
 
9
+ // Renderless: no element of its own, so there are no rest attributes to forward.
9
10
  interface Props {
10
11
  /** Delay before the first tooltip in the group opens. */
11
12
  delayDuration?: number;