@loidolt/theme-svelte 0.4.0 → 0.6.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 (127) 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/AlertDialog.svelte +4 -4
  9. package/dist/components/AppShell.svelte +9 -2
  10. package/dist/components/AppShell.svelte.d.ts +3 -1
  11. package/dist/components/AspectRatio.svelte +32 -0
  12. package/dist/components/AspectRatio.svelte.d.ts +13 -0
  13. package/dist/components/AudioPlayer.svelte +94 -0
  14. package/dist/components/AudioPlayer.svelte.d.ts +24 -0
  15. package/dist/components/Avatar.svelte +85 -0
  16. package/dist/components/Avatar.svelte.d.ts +31 -0
  17. package/dist/components/Badge.svelte +28 -2
  18. package/dist/components/Badge.svelte.d.ts +13 -1
  19. package/dist/components/Combobox.svelte +180 -0
  20. package/dist/components/Combobox.svelte.d.ts +77 -0
  21. package/dist/components/DataTable.svelte +228 -0
  22. package/dist/components/DataTable.svelte.d.ts +62 -0
  23. package/dist/components/DataTableColumnVisibility.svelte +46 -0
  24. package/dist/components/DataTableColumnVisibility.svelte.d.ts +31 -0
  25. package/dist/components/DataTableFacetedFilter.svelte +87 -0
  26. package/dist/components/DataTableFacetedFilter.svelte.d.ts +40 -0
  27. package/dist/components/DataTablePagination.svelte +68 -0
  28. package/dist/components/DataTablePagination.svelte.d.ts +42 -0
  29. package/dist/components/DataTableSearch.svelte +67 -0
  30. package/dist/components/DataTableSearch.svelte.d.ts +37 -0
  31. package/dist/components/Dialog.svelte +35 -14
  32. package/dist/components/Dialog.svelte.d.ts +7 -0
  33. package/dist/components/Drawer.svelte +3 -3
  34. package/dist/components/DropdownMenu.svelte +95 -28
  35. package/dist/components/DropdownMenu.svelte.d.ts +12 -3
  36. package/dist/components/Field.svelte +2 -3
  37. package/dist/components/Fieldset.svelte +2 -3
  38. package/dist/components/Filmstrip.svelte +10 -3
  39. package/dist/components/FilterPanel.svelte +62 -0
  40. package/dist/components/FilterPanel.svelte.d.ts +22 -0
  41. package/dist/components/Lightbox.svelte +239 -0
  42. package/dist/components/Lightbox.svelte.d.ts +40 -0
  43. package/dist/components/LiveRegion.svelte +48 -0
  44. package/dist/components/LiveRegion.svelte.d.ts +20 -0
  45. package/dist/components/MediaCarousel.svelte +177 -0
  46. package/dist/components/MediaCarousel.svelte.d.ts +32 -0
  47. package/dist/components/MediaEmbed.svelte +116 -0
  48. package/dist/components/MediaEmbed.svelte.d.ts +34 -0
  49. package/dist/components/MediaGrid.svelte +91 -0
  50. package/dist/components/MediaGrid.svelte.d.ts +24 -0
  51. package/dist/components/MultiSelect.svelte +232 -0
  52. package/dist/components/MultiSelect.svelte.d.ts +67 -0
  53. package/dist/components/NumberField.svelte +53 -25
  54. package/dist/components/NumberField.svelte.d.ts +47 -18
  55. package/dist/components/OTPInput.svelte +94 -0
  56. package/dist/components/OTPInput.svelte.d.ts +26 -0
  57. package/dist/components/PasswordInput.svelte +93 -0
  58. package/dist/components/PasswordInput.svelte.d.ts +24 -0
  59. package/dist/components/Progress.svelte +57 -14
  60. package/dist/components/Progress.svelte.d.ts +13 -0
  61. package/dist/components/RadioGroup.svelte +19 -5
  62. package/dist/components/RadioGroup.svelte.d.ts +3 -2
  63. package/dist/components/Select.svelte +15 -5
  64. package/dist/components/Select.svelte.d.ts +3 -2
  65. package/dist/components/SignaturePad.svelte +317 -0
  66. package/dist/components/SignaturePad.svelte.d.ts +48 -0
  67. package/dist/components/Skeleton.svelte +40 -9
  68. package/dist/components/Skeleton.svelte.d.ts +8 -0
  69. package/dist/components/SkipLink.svelte +48 -0
  70. package/dist/components/SkipLink.svelte.d.ts +14 -0
  71. package/dist/components/Slider.svelte +148 -0
  72. package/dist/components/Slider.svelte.d.ts +55 -0
  73. package/dist/components/Spinner.svelte +11 -2
  74. package/dist/components/Spinner.svelte.d.ts +3 -0
  75. package/dist/components/Tabs.svelte.d.ts +1 -1
  76. package/dist/components/Thumbnail.svelte +52 -6
  77. package/dist/components/Thumbnail.svelte.d.ts +14 -2
  78. package/dist/components/Toast.svelte +24 -0
  79. package/dist/components/Toast.svelte.d.ts +10 -0
  80. package/dist/components/ToastViewport.svelte +10 -1
  81. package/dist/components/ToastViewport.svelte.d.ts +2 -0
  82. package/dist/components/Toolbar.svelte +49 -0
  83. package/dist/components/Toolbar.svelte.d.ts +23 -0
  84. package/dist/components/Tooltip.svelte +9 -1
  85. package/dist/components/Tooltip.svelte.d.ts +2 -0
  86. package/dist/components/VideoPlayer.svelte +136 -0
  87. package/dist/components/VideoPlayer.svelte.d.ts +38 -0
  88. package/dist/data-table-utils.d.ts +62 -0
  89. package/dist/data-table-utils.js +91 -0
  90. package/dist/data-table.svelte.d.ts +132 -0
  91. package/dist/data-table.svelte.js +292 -0
  92. package/dist/hls.d.ts +35 -0
  93. package/dist/hls.js +37 -0
  94. package/dist/hls.svelte.d.ts +22 -0
  95. package/dist/hls.svelte.js +66 -0
  96. package/dist/index.d.ts +47 -4
  97. package/dist/index.js +35 -2
  98. package/dist/internal/EditableCell.svelte +58 -0
  99. package/dist/internal/EditableCell.svelte.d.ts +8 -0
  100. package/dist/internal/ListboxEntries.svelte +49 -0
  101. package/dist/internal/ListboxEntries.svelte.d.ts +35 -0
  102. package/dist/internal/media/MediaControls.svelte +163 -0
  103. package/dist/internal/media/MediaControls.svelte.d.ts +12 -0
  104. package/dist/internal/media/MediaStage.svelte +49 -0
  105. package/dist/internal/media/MediaStage.svelte.d.ts +11 -0
  106. package/dist/internal/media/MediaTile.svelte +36 -0
  107. package/dist/internal/media/MediaTile.svelte.d.ts +11 -0
  108. package/dist/internal/media/keys.d.ts +7 -0
  109. package/dist/internal/media/keys.js +51 -0
  110. package/dist/internal/media/labels.d.ts +19 -0
  111. package/dist/internal/media/labels.js +15 -0
  112. package/dist/internal/options.d.ts +9 -0
  113. package/dist/internal/options.js +20 -0
  114. package/dist/media-player.svelte.d.ts +61 -0
  115. package/dist/media-player.svelte.js +246 -0
  116. package/dist/media-utils.d.ts +112 -0
  117. package/dist/media-utils.js +205 -0
  118. package/dist/media-zoom.svelte.d.ts +33 -0
  119. package/dist/media-zoom.svelte.js +126 -0
  120. package/dist/signature.d.ts +55 -0
  121. package/dist/signature.js +52 -0
  122. package/dist/toaster.svelte.d.ts +31 -2
  123. package/dist/toaster.svelte.js +30 -2
  124. package/dist/types.d.ts +62 -3
  125. package/dist/utils.d.ts +9 -0
  126. package/dist/utils.js +11 -0
  127. package/package.json +10 -4
@@ -0,0 +1,94 @@
1
+ <script lang="ts">
2
+ import {
3
+ PinInput as PinInputPrimitive,
4
+ REGEXP_ONLY_DIGITS,
5
+ REGEXP_ONLY_DIGITS_AND_CHARS,
6
+ type PinInputRootProps,
7
+ } from 'bits-ui';
8
+ import { cx } from '../utils.js';
9
+
10
+ type Props = Omit<
11
+ PinInputRootProps,
12
+ | 'children'
13
+ | 'maxlength'
14
+ | 'value'
15
+ | 'onValueChange'
16
+ | 'onComplete'
17
+ | 'inputId'
18
+ | 'inputRef'
19
+ | 'pattern'
20
+ | 'inputmode'
21
+ | 'textalign'
22
+ | 'ref'
23
+ | 'class'
24
+ > & {
25
+ value?: string;
26
+ /** Number of characters in the code. */
27
+ length?: number;
28
+ /** `numeric` accepts digits and opens the number pad; `alphanumeric` accepts letters too. */
29
+ type?: 'numeric' | 'alphanumeric';
30
+ /** Accessible name of the input, e.g. "Verification code". */
31
+ label: string;
32
+ /** Draw a gap after every `groupSize` characters — `3` renders a six-digit code as 123 456. */
33
+ groupSize?: number;
34
+ /** Show dots instead of the characters. */
35
+ mask?: boolean;
36
+ /** The id of the underlying input, so a `Field` label can point at it. */
37
+ id?: string;
38
+ onValueChange?: (value: string) => void;
39
+ /** Called with the code once every cell is filled. */
40
+ onComplete?: (value: string) => void;
41
+ class?: string;
42
+ cellClass?: string;
43
+ /** The hidden input that holds the code. */
44
+ ref?: HTMLInputElement | null;
45
+ };
46
+
47
+ let {
48
+ value = $bindable(''),
49
+ length = 6,
50
+ type = 'numeric',
51
+ label,
52
+ groupSize,
53
+ mask = false,
54
+ id,
55
+ onValueChange,
56
+ onComplete,
57
+ class: className,
58
+ cellClass,
59
+ ref = $bindable(null),
60
+ ...rest
61
+ }: Props = $props();
62
+
63
+ // Codes are often copied as "123-456" or "123 456"; keep only what the field accepts.
64
+ const pasteTransformer = (text: string) => text.replace(/[\s-]/g, '');
65
+ </script>
66
+
67
+ <PinInputPrimitive.Root
68
+ bind:value
69
+ bind:inputRef={ref}
70
+ maxlength={length}
71
+ inputId={id}
72
+ pattern={type === 'numeric' ? REGEXP_ONLY_DIGITS : REGEXP_ONLY_DIGITS_AND_CHARS}
73
+ inputmode={type === 'numeric' ? 'numeric' : 'text'}
74
+ aria-label={label}
75
+ {pasteTransformer}
76
+ {onValueChange}
77
+ onComplete={(code: string) => onComplete?.(code)}
78
+ class={cx('ldt-otp-input', className)}
79
+ {...rest}
80
+ >
81
+ {#snippet children({ cells })}
82
+ {#each cells as cell, index (index)}
83
+ {#if groupSize && index > 0 && index % groupSize === 0}<span
84
+ class="ldt-otp-input__gap"
85
+ aria-hidden="true"
86
+ ></span>{/if}
87
+ <PinInputPrimitive.Cell {cell} class={cx('ldt-otp-input__cell', cellClass)}>
88
+ {#if cell.char}{mask ? '•' : cell.char}{/if}{#if cell.hasFakeCaret}<span
89
+ class="ldt-otp-input__caret"
90
+ ></span>{/if}
91
+ </PinInputPrimitive.Cell>
92
+ {/each}
93
+ {/snippet}
94
+ </PinInputPrimitive.Root>
@@ -0,0 +1,26 @@
1
+ import { type PinInputRootProps } from 'bits-ui';
2
+ type Props = Omit<PinInputRootProps, 'children' | 'maxlength' | 'value' | 'onValueChange' | 'onComplete' | 'inputId' | 'inputRef' | 'pattern' | 'inputmode' | 'textalign' | 'ref' | 'class'> & {
3
+ value?: string;
4
+ /** Number of characters in the code. */
5
+ length?: number;
6
+ /** `numeric` accepts digits and opens the number pad; `alphanumeric` accepts letters too. */
7
+ type?: 'numeric' | 'alphanumeric';
8
+ /** Accessible name of the input, e.g. "Verification code". */
9
+ label: string;
10
+ /** Draw a gap after every `groupSize` characters — `3` renders a six-digit code as 123 456. */
11
+ groupSize?: number;
12
+ /** Show dots instead of the characters. */
13
+ mask?: boolean;
14
+ /** The id of the underlying input, so a `Field` label can point at it. */
15
+ id?: string;
16
+ onValueChange?: (value: string) => void;
17
+ /** Called with the code once every cell is filled. */
18
+ onComplete?: (value: string) => void;
19
+ class?: string;
20
+ cellClass?: string;
21
+ /** The hidden input that holds the code. */
22
+ ref?: HTMLInputElement | null;
23
+ };
24
+ declare const OTPInput: import("svelte").Component<Props, {}, "value" | "ref">;
25
+ type OTPInput = ReturnType<typeof OTPInput>;
26
+ export default OTPInput;
@@ -0,0 +1,93 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import type { HTMLInputAttributes } from 'svelte/elements';
4
+ import { cx } from '../utils.js';
5
+
6
+ interface Props extends Omit<HTMLInputAttributes, 'value' | 'type'> {
7
+ value?: string;
8
+ /** Whether the password is shown as plain text. */
9
+ visible?: boolean;
10
+ onVisibleChange?: (visible: boolean) => void;
11
+ /**
12
+ * Accessible name of the reveal button. It stays the same in both states and `aria-pressed`
13
+ * reports which one is active — changing the name as well would announce the state twice.
14
+ */
15
+ toggleLabel?: string;
16
+ /** Custom button content; receives whether the password is visible. */
17
+ toggle?: Snippet<[boolean]>;
18
+ /** Draws the full box instead of the default underline. */
19
+ boxed?: boolean;
20
+ /** Lands on the wrapper; the rest props land on the input. */
21
+ class?: string;
22
+ inputClass?: string;
23
+ ref?: HTMLInputElement | null;
24
+ }
25
+
26
+ const generatedId = $props.id();
27
+
28
+ let {
29
+ value = $bindable(''),
30
+ visible = $bindable(false),
31
+ onVisibleChange,
32
+ toggleLabel = 'Show password',
33
+ toggle,
34
+ boxed = false,
35
+ autocomplete = 'current-password',
36
+ id = generatedId,
37
+ disabled,
38
+ class: className,
39
+ inputClass,
40
+ ref = $bindable(null),
41
+ ...rest
42
+ }: Props = $props();
43
+
44
+ function flip() {
45
+ visible = !visible;
46
+ onVisibleChange?.(visible);
47
+ }
48
+ </script>
49
+
50
+ <div
51
+ class={cx('ldt-password-input', boxed && 'ldt-password-input--boxed', className)}
52
+ data-disabled={disabled ? '' : undefined}
53
+ >
54
+ <input
55
+ bind:this={ref}
56
+ bind:value
57
+ {id}
58
+ {disabled}
59
+ {autocomplete}
60
+ type={visible ? 'text' : 'password'}
61
+ class={cx('ldt-password-input__input', inputClass)}
62
+ autocapitalize="off"
63
+ spellcheck="false"
64
+ {...rest}
65
+ />
66
+ <button
67
+ type="button"
68
+ class="ldt-password-input__toggle"
69
+ aria-label={toggleLabel}
70
+ aria-pressed={visible}
71
+ aria-controls={id}
72
+ {disabled}
73
+ onclick={flip}
74
+ >
75
+ {#if toggle}{@render toggle(visible)}{:else}
76
+ <svg
77
+ viewBox="0 0 20 20"
78
+ width="18"
79
+ height="18"
80
+ aria-hidden="true"
81
+ focusable="false"
82
+ fill="none"
83
+ stroke="currentColor"
84
+ stroke-width="1.5"
85
+ ><path d="M1.5 10S4.6 4 10 4s8.5 6 8.5 6-3.1 6-8.5 6-8.5-6-8.5-6Z" /><circle
86
+ cx="10"
87
+ cy="10"
88
+ r="2.6"
89
+ />{#if visible}<path d="m3 17 14-14" />{/if}</svg
90
+ >
91
+ {/if}
92
+ </button>
93
+ </div>
@@ -0,0 +1,24 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLInputAttributes } from 'svelte/elements';
3
+ interface Props extends Omit<HTMLInputAttributes, 'value' | 'type'> {
4
+ value?: string;
5
+ /** Whether the password is shown as plain text. */
6
+ visible?: boolean;
7
+ onVisibleChange?: (visible: boolean) => void;
8
+ /**
9
+ * Accessible name of the reveal button. It stays the same in both states and `aria-pressed`
10
+ * reports which one is active — changing the name as well would announce the state twice.
11
+ */
12
+ toggleLabel?: string;
13
+ /** Custom button content; receives whether the password is visible. */
14
+ toggle?: Snippet<[boolean]>;
15
+ /** Draws the full box instead of the default underline. */
16
+ boxed?: boolean;
17
+ /** Lands on the wrapper; the rest props land on the input. */
18
+ class?: string;
19
+ inputClass?: string;
20
+ ref?: HTMLInputElement | null;
21
+ }
22
+ declare const PasswordInput: import("svelte").Component<Props, {}, "value" | "ref" | "visible">;
23
+ type PasswordInput = ReturnType<typeof PasswordInput>;
24
+ export default PasswordInput;
@@ -1,5 +1,6 @@
1
1
  <script lang="ts">
2
2
  import type { HTMLAttributes } from 'svelte/elements';
3
+ import type { ControlSize, StatusVariant } from '../types.js';
3
4
  import { cx } from '../utils.js';
4
5
 
5
6
  interface Props extends HTMLAttributes<HTMLDivElement> {
@@ -7,6 +8,18 @@
7
8
  value?: number;
8
9
  max?: number;
9
10
  label?: string;
11
+ /** Colours the bar by outcome — `success` when done, `error` when a run failed part-way. */
12
+ variant?: 'default' | StatusVariant;
13
+ /** Bar thickness. */
14
+ size?: ControlSize;
15
+ /** Show the formatted value after the bar. */
16
+ showValue?: boolean;
17
+ /**
18
+ * Text for the value, shown by `showValue` and spoken as `aria-valuetext` — "3 of 12 sheets"
19
+ * says more than 25%. Defaults to a whole percentage.
20
+ */
21
+ formatValue?: (value: number, max: number) => string;
22
+ /** Lands on the progressbar element. */
10
23
  class?: string;
11
24
  ref?: HTMLDivElement | null;
12
25
  }
@@ -15,6 +28,10 @@
15
28
  value,
16
29
  max = 100,
17
30
  label = 'Progress',
31
+ variant = 'default',
32
+ size = 'md',
33
+ showValue = false,
34
+ formatValue,
18
35
  class: className,
19
36
  ref = $bindable(null),
20
37
  ...rest
@@ -24,20 +41,46 @@
24
41
  // A non-positive `max` would make every ratio Infinity or NaN.
25
42
  const safeMax = $derived(Number.isFinite(max) && max > 0 ? max : 100);
26
43
  const clamped = $derived(Math.max(0, Math.min(safeMax, value ?? 0)));
44
+ const text = $derived(
45
+ indeterminate
46
+ ? undefined
47
+ : formatValue
48
+ ? formatValue(clamped, safeMax)
49
+ : `${Math.round((clamped / safeMax) * 100)}%`
50
+ );
27
51
  </script>
28
52
 
29
- <div
30
- bind:this={ref}
31
- class={cx('ldt-progress', indeterminate && 'ldt-progress--indeterminate', className)}
32
- role="progressbar"
33
- aria-label={label}
34
- aria-valuenow={indeterminate ? undefined : clamped}
35
- aria-valuemin="0"
36
- aria-valuemax={safeMax}
37
- {...rest}
38
- >
53
+ {#snippet bar()}
39
54
  <div
40
- class="ldt-progress__bar"
41
- style:--ldt-progress-value={indeterminate ? undefined : clamped / safeMax}
42
- ></div>
43
- </div>
55
+ bind:this={ref}
56
+ class={cx(
57
+ 'ldt-progress',
58
+ indeterminate && 'ldt-progress--indeterminate',
59
+ variant !== 'default' && `ldt-progress--${variant}`,
60
+ size !== 'md' && `ldt-progress--${size}`,
61
+ className
62
+ )}
63
+ role="progressbar"
64
+ aria-label={label}
65
+ aria-valuenow={indeterminate ? undefined : clamped}
66
+ aria-valuemin="0"
67
+ aria-valuemax={safeMax}
68
+ aria-valuetext={formatValue ? text : undefined}
69
+ {...rest}
70
+ >
71
+ <div
72
+ class="ldt-progress__bar"
73
+ style:--ldt-progress-value={indeterminate ? undefined : clamped / safeMax}
74
+ ></div>
75
+ </div>
76
+ {/snippet}
77
+
78
+ {#if showValue}
79
+ <div class="ldt-progress-field">
80
+ {@render bar()}
81
+ <!-- The progressbar already reports its value; this is the sighted copy. -->
82
+ <span class="ldt-progress-field__value" aria-hidden="true">{text ?? ''}</span>
83
+ </div>
84
+ {:else}
85
+ {@render bar()}
86
+ {/if}
@@ -1,9 +1,22 @@
1
1
  import type { HTMLAttributes } from 'svelte/elements';
2
+ import type { ControlSize, StatusVariant } from '../types.js';
2
3
  interface Props extends HTMLAttributes<HTMLDivElement> {
3
4
  /** Omit (or pass `undefined`) for an indeterminate bar. */
4
5
  value?: number;
5
6
  max?: number;
6
7
  label?: string;
8
+ /** Colours the bar by outcome — `success` when done, `error` when a run failed part-way. */
9
+ variant?: 'default' | StatusVariant;
10
+ /** Bar thickness. */
11
+ size?: ControlSize;
12
+ /** Show the formatted value after the bar. */
13
+ showValue?: boolean;
14
+ /**
15
+ * Text for the value, shown by `showValue` and spoken as `aria-valuetext` — "3 of 12 sheets"
16
+ * says more than 25%. Defaults to a whole percentage.
17
+ */
18
+ formatValue?: (value: number, max: number) => string;
19
+ /** Lands on the progressbar element. */
7
20
  class?: string;
8
21
  ref?: HTMLDivElement | null;
9
22
  }
@@ -1,11 +1,12 @@
1
1
  <script lang="ts" generics="T extends string = string">
2
2
  import type { HTMLFieldsetAttributes } from 'svelte/elements';
3
- import type { Option, Orientation } from '../types.js';
3
+ import type { ChoiceOption, Orientation } from '../types.js';
4
4
  import { cx } from '../utils.js';
5
5
 
6
6
  interface Props extends HTMLFieldsetAttributes {
7
7
  value?: T;
8
- options: Option<T>[];
8
+ /** Choices. A `description` renders under its label and is read after it. */
9
+ options: ChoiceOption<T>[];
9
10
  /** Shared `name` for the inputs. Defaults to an SSR-stable generated id. */
10
11
  name?: string;
11
12
  /** Visible group label, rendered as a `<legend>`. */
@@ -35,15 +36,28 @@
35
36
  <fieldset bind:this={ref} class={cx('ldt-fieldset', className)} {...rest}>
36
37
  {#if label}<legend class="ldt-legend">{label}</legend>{/if}
37
38
  <div class={cx('ldt-radio-group', orientation === 'vertical' && 'ldt-radio-group--vertical')}>
38
- {#each options as option (option.value)}<label class="ldt-choice"
39
+ {#each options as option, index (option.value)}
40
+ {@const labelId = `${generatedName}-${index}-label`}
41
+ {@const descriptionId = option.description
42
+ ? `${generatedName}-${index}-description`
43
+ : undefined}
44
+ <label class={cx('ldt-choice', option.description && 'ldt-choice--described')}
39
45
  ><input
40
46
  type="radio"
41
47
  {name}
42
48
  value={option.value}
43
49
  bind:group={value}
44
50
  disabled={option.disabled}
51
+ aria-labelledby={option.description ? labelId : undefined}
52
+ aria-describedby={descriptionId}
45
53
  onchange={() => onValueChange?.(option.value)}
46
- />{option.label}</label
47
- >{/each}
54
+ /><span class="ldt-choice__text"
55
+ ><span id={labelId}>{option.label}</span>{#if option.description}<span
56
+ class="ldt-choice__description"
57
+ id={descriptionId}>{option.description}</span
58
+ >{/if}</span
59
+ ></label
60
+ >
61
+ {/each}
48
62
  </div>
49
63
  </fieldset>
@@ -1,9 +1,10 @@
1
1
  import type { HTMLFieldsetAttributes } from 'svelte/elements';
2
- import type { Option, Orientation } from '../types.js';
2
+ import type { ChoiceOption, Orientation } from '../types.js';
3
3
  declare function $$render<T extends string = string>(): {
4
4
  props: HTMLFieldsetAttributes & {
5
5
  value?: T;
6
- options: Option<T>[];
6
+ /** Choices. A `description` renders under its label and is read after it. */
7
+ options: ChoiceOption<T>[];
7
8
  /** Shared `name` for the inputs. Defaults to an SSR-stable generated id. */
8
9
  name?: string;
9
10
  /** Visible group label, rendered as a `<legend>`. */
@@ -1,11 +1,12 @@
1
1
  <script lang="ts" generics="T extends string = string">
2
2
  import type { HTMLSelectAttributes } from 'svelte/elements';
3
- import type { Option } from '../types.js';
3
+ import { isOptionGroup, type OptionEntry } from '../internal/options.js';
4
4
  import { cx } from '../utils.js';
5
5
 
6
6
  interface Props extends Omit<HTMLSelectAttributes, 'value'> {
7
7
  value?: T | '';
8
- options: Option<T>[];
8
+ /** Options, optionally in labelled groups — rendered as native `<optgroup>`s. */
9
+ options: OptionEntry<T>[];
9
10
  /**
10
11
  * Empty-choice text. Selectable again after a real choice unless the field is `required`,
11
12
  * so a user can undo their selection.
@@ -51,7 +52,16 @@
51
52
  >
52
53
  {#if placeholder}<option value="" disabled={required} hidden={required}>{placeholder}</option
53
54
  >{/if}
54
- {#each options as option (option.value)}<option value={option.value} disabled={option.disabled}
55
- >{option.label}</option
56
- >{/each}
55
+ {#each options as entry (isOptionGroup(entry) ? `group:${entry.label}` : entry.value)}
56
+ {#if isOptionGroup(entry)}
57
+ <optgroup label={entry.label} disabled={entry.disabled}>
58
+ {#each entry.options as option (option.value)}<option
59
+ value={option.value}
60
+ disabled={option.disabled}>{option.label}</option
61
+ >{/each}
62
+ </optgroup>
63
+ {:else}
64
+ <option value={entry.value} disabled={entry.disabled}>{entry.label}</option>
65
+ {/if}
66
+ {/each}
57
67
  </select>
@@ -1,9 +1,10 @@
1
1
  import type { HTMLSelectAttributes } from 'svelte/elements';
2
- import type { Option } from '../types.js';
2
+ import { type OptionEntry } from '../internal/options.js';
3
3
  declare function $$render<T extends string = string>(): {
4
4
  props: Omit<HTMLSelectAttributes, "value"> & {
5
5
  value?: T | "";
6
- options: Option<T>[];
6
+ /** Options, optionally in labelled groups — rendered as native `<optgroup>`s. */
7
+ options: OptionEntry<T>[];
7
8
  /**
8
9
  * Empty-choice text. Selectable again after a real choice unless the field is `required`,
9
10
  * so a user can undo their selection.