@thomasfosterau/components 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (240) hide show
  1. package/README.md +241 -56
  2. package/dist/canvas/VectorCanvas.svelte +89 -8
  3. package/dist/canvas/VectorCanvas.svelte.d.ts +19 -1
  4. package/dist/canvas/types.d.ts +1 -1
  5. package/dist/charts/types.d.ts +1 -1
  6. package/dist/citations/list-format.js +3 -1
  7. package/dist/data/ChatBubble.svelte +48 -0
  8. package/dist/data/ChatBubble.svelte.d.ts +33 -0
  9. package/dist/data/DescriptionList.svelte +9 -3
  10. package/dist/data/DescriptionList.svelte.d.ts +1 -1
  11. package/dist/data/DescriptionRow.svelte +6 -5
  12. package/dist/data/MessageThread.svelte +29 -0
  13. package/dist/data/MessageThread.svelte.d.ts +17 -0
  14. package/dist/data/Pagination.svelte +5 -5
  15. package/dist/data/Pagination.svelte.d.ts +8 -0
  16. package/dist/data/PaginationFooter.svelte +2 -1
  17. package/dist/data/PaginationFooter.svelte.d.ts +2 -0
  18. package/dist/data/Table.svelte +9 -3
  19. package/dist/data/description-context.d.ts +12 -6
  20. package/dist/data/description-context.js +6 -5
  21. package/dist/data/index.d.ts +2 -0
  22. package/dist/data/index.js +2 -0
  23. package/dist/data/table-types.d.ts +7 -0
  24. package/dist/feedback/Dialog.svelte +19 -49
  25. package/dist/feedback/Dialog.svelte.d.ts +0 -1
  26. package/dist/feedback/DialogFrame.svelte +64 -0
  27. package/dist/feedback/DialogFrame.svelte.d.ts +23 -0
  28. package/dist/feedback/Drawer.svelte +43 -0
  29. package/dist/feedback/Drawer.svelte.d.ts +43 -0
  30. package/dist/feedback/SegmentBar.svelte.d.ts +1 -0
  31. package/dist/feedback/StatTile.svelte.d.ts +1 -0
  32. package/dist/feedback/Steps.svelte +72 -0
  33. package/dist/feedback/Steps.svelte.d.ts +61 -0
  34. package/dist/feedback/Timeline.svelte.d.ts +3 -0
  35. package/dist/feedback/index.d.ts +3 -0
  36. package/dist/feedback/index.js +2 -0
  37. package/dist/feedback/sizes.d.ts +14 -0
  38. package/dist/feedback/sizes.js +28 -0
  39. package/dist/fields/Calendar.svelte +60 -0
  40. package/dist/fields/Calendar.svelte.d.ts +11 -0
  41. package/dist/fields/CalendarMonths.svelte +86 -0
  42. package/dist/fields/CalendarMonths.svelte.d.ts +30 -0
  43. package/dist/fields/Checkbox.svelte +1 -1
  44. package/dist/fields/Checkbox.svelte.d.ts +6 -0
  45. package/dist/fields/Combobox.svelte +61 -54
  46. package/dist/fields/Combobox.svelte.d.ts +38 -3
  47. package/dist/fields/DateField.svelte +37 -0
  48. package/dist/fields/DateField.svelte.d.ts +20 -0
  49. package/dist/fields/DateInput.svelte +1 -1
  50. package/dist/fields/DateInput.svelte.d.ts +6 -0
  51. package/dist/fields/DatePicker.svelte +47 -0
  52. package/dist/fields/DatePicker.svelte.d.ts +25 -0
  53. package/dist/fields/DateRangePicker.svelte +70 -0
  54. package/dist/fields/DateRangePicker.svelte.d.ts +43 -0
  55. package/dist/fields/Dropzone.svelte +232 -0
  56. package/dist/fields/Dropzone.svelte.d.ts +103 -0
  57. package/dist/fields/Fieldset.svelte +7 -2
  58. package/dist/fields/Fieldset.svelte.d.ts +3 -2
  59. package/dist/fields/FileInput.svelte +3 -2
  60. package/dist/fields/FileInput.svelte.d.ts +6 -0
  61. package/dist/fields/FormField.svelte +27 -3
  62. package/dist/fields/ImageInput.svelte.d.ts +6 -0
  63. package/dist/fields/MultiSelect.svelte +112 -0
  64. package/dist/fields/MultiSelect.svelte.d.ts +75 -0
  65. package/dist/fields/NumberField.svelte +190 -0
  66. package/dist/fields/NumberField.svelte.d.ts +76 -0
  67. package/dist/fields/PasswordInput.svelte +95 -0
  68. package/dist/fields/PasswordInput.svelte.d.ts +58 -0
  69. package/dist/fields/PickerPopover.svelte +47 -0
  70. package/dist/fields/PickerPopover.svelte.d.ts +16 -0
  71. package/dist/fields/PinInput.svelte +83 -0
  72. package/dist/fields/PinInput.svelte.d.ts +66 -0
  73. package/dist/fields/RadioGroup.svelte +3 -0
  74. package/dist/fields/RangeCalendar.svelte +51 -0
  75. package/dist/fields/RangeCalendar.svelte.d.ts +23 -0
  76. package/dist/fields/Rating.svelte +127 -0
  77. package/dist/fields/Rating.svelte.d.ts +86 -0
  78. package/dist/fields/SearchInput.svelte +2 -1
  79. package/dist/fields/SegmentGroup.svelte +91 -0
  80. package/dist/fields/SegmentGroup.svelte.d.ts +20 -0
  81. package/dist/fields/Select.svelte +1 -1
  82. package/dist/fields/Select.svelte.d.ts +6 -0
  83. package/dist/fields/Slider.svelte +194 -0
  84. package/dist/fields/Slider.svelte.d.ts +100 -0
  85. package/dist/fields/TagsInput.svelte +256 -0
  86. package/dist/fields/TagsInput.svelte.d.ts +102 -0
  87. package/dist/fields/TextInput.svelte +1 -1
  88. package/dist/fields/TextInput.svelte.d.ts +6 -0
  89. package/dist/fields/Textarea.svelte +1 -1
  90. package/dist/fields/Textarea.svelte.d.ts +6 -0
  91. package/dist/fields/TimeField.svelte +30 -0
  92. package/dist/fields/TimeField.svelte.d.ts +19 -0
  93. package/dist/fields/Toggle.svelte +1 -1
  94. package/dist/fields/Toggle.svelte.d.ts +6 -0
  95. package/dist/fields/VideoInput.svelte.d.ts +6 -0
  96. package/dist/fields/calendar-props.d.ts +119 -0
  97. package/dist/fields/calendar-props.js +51 -0
  98. package/dist/fields/dates.d.ts +28 -0
  99. package/dist/fields/dates.js +26 -0
  100. package/dist/fields/field-types.d.ts +29 -0
  101. package/dist/fields/field-types.js +32 -0
  102. package/dist/fields/file-validation.d.ts +30 -0
  103. package/dist/fields/file-validation.js +51 -0
  104. package/dist/fields/index.d.ts +9 -0
  105. package/dist/fields/index.js +8 -0
  106. package/dist/fields/latest-search.svelte.d.ts +38 -0
  107. package/dist/fields/latest-search.svelte.js +85 -0
  108. package/dist/fields/number-format.d.ts +52 -0
  109. package/dist/fields/number-format.js +89 -0
  110. package/dist/fields/segmented-field.d.ts +128 -0
  111. package/dist/fields/segmented-field.js +52 -0
  112. package/dist/fields/slider-keys.d.ts +21 -0
  113. package/dist/fields/slider-keys.js +37 -0
  114. package/dist/formatting/Countdown.svelte +110 -0
  115. package/dist/formatting/Countdown.svelte.d.ts +35 -0
  116. package/dist/formatting/Duration.svelte +18 -0
  117. package/dist/formatting/Duration.svelte.d.ts +20 -0
  118. package/dist/formatting/Highlight.svelte +4 -0
  119. package/dist/formatting/Highlight.svelte.d.ts +4 -0
  120. package/dist/formatting/duration.d.ts +44 -0
  121. package/dist/formatting/duration.js +125 -0
  122. package/dist/formatting/index.d.ts +3 -0
  123. package/dist/formatting/index.js +3 -0
  124. package/dist/forms/EntityForm.svelte +53 -45
  125. package/dist/forms/EntityForm.svelte.d.ts +12 -3
  126. package/dist/hotkeys/index.d.ts +5 -0
  127. package/dist/hotkeys/index.js +5 -0
  128. package/dist/hotkeys/match.js +12 -10
  129. package/dist/hotkeys/types.d.ts +4 -2
  130. package/dist/index.d.ts +2 -1
  131. package/dist/index.js +2 -1
  132. package/dist/infobox/Infobox.svelte +2 -2
  133. package/dist/infobox/Infobox.svelte.d.ts +7 -0
  134. package/dist/infobox/InfoboxHeader.svelte +15 -7
  135. package/dist/infobox/InfoboxHeader.svelte.d.ts +7 -0
  136. package/dist/infobox/types.d.ts +5 -5
  137. package/dist/layout/AppShell.svelte +50 -0
  138. package/dist/layout/AppShell.svelte.d.ts +28 -0
  139. package/dist/layout/Cluster.svelte +28 -0
  140. package/dist/layout/Cluster.svelte.d.ts +27 -0
  141. package/dist/layout/Fits.svelte +27 -0
  142. package/dist/layout/Fits.svelte.d.ts +13 -0
  143. package/dist/layout/Flow.svelte +46 -0
  144. package/dist/layout/Flow.svelte.d.ts +16 -0
  145. package/dist/layout/Grid.svelte +23 -0
  146. package/dist/layout/Grid.svelte.d.ts +26 -0
  147. package/dist/layout/HStack.svelte +12 -0
  148. package/dist/layout/HStack.svelte.d.ts +34 -0
  149. package/dist/layout/Hero.svelte +76 -0
  150. package/dist/layout/Hero.svelte.d.ts +36 -0
  151. package/dist/layout/Navbar.svelte +2 -2
  152. package/dist/layout/Sidebar.svelte +1 -1
  153. package/dist/layout/Spacer.svelte +12 -0
  154. package/dist/layout/Spacer.svelte.d.ts +6 -0
  155. package/dist/layout/VStack.svelte +12 -0
  156. package/dist/layout/VStack.svelte.d.ts +29 -0
  157. package/dist/layout/index.d.ts +9 -0
  158. package/dist/layout/index.js +9 -0
  159. package/dist/layout/space.d.ts +44 -0
  160. package/dist/layout/space.js +76 -0
  161. package/dist/search/types.d.ts +3 -0
  162. package/dist/seo/og-image.d.ts +4 -3
  163. package/dist/seo/og-image.js +4 -3
  164. package/dist/tips/tip-stack.d.ts +1 -0
  165. package/dist/tips/tip-store.svelte.d.ts +2 -1
  166. package/dist/ui/Accordion.svelte +75 -0
  167. package/dist/ui/Accordion.svelte.d.ts +50 -0
  168. package/dist/ui/AccordionItem.svelte +73 -0
  169. package/dist/ui/AccordionItem.svelte.d.ts +18 -0
  170. package/dist/ui/Button.svelte +3 -2
  171. package/dist/ui/ButtonGroup.svelte +24 -0
  172. package/dist/ui/ButtonGroup.svelte.d.ts +17 -0
  173. package/dist/ui/ContextMenu.svelte +75 -0
  174. package/dist/ui/ContextMenu.svelte.d.ts +25 -0
  175. package/dist/ui/Disclosure.svelte +7 -0
  176. package/dist/ui/Disclosure.svelte.d.ts +2 -2
  177. package/dist/ui/Divider.svelte +47 -0
  178. package/dist/ui/Divider.svelte.d.ts +28 -0
  179. package/dist/ui/DropdownMenu.svelte +44 -0
  180. package/dist/ui/DropdownMenu.svelte.d.ts +30 -0
  181. package/dist/ui/Menu.svelte +17 -47
  182. package/dist/ui/Menu.svelte.d.ts +3 -24
  183. package/dist/ui/MenuCheckboxItem.svelte +33 -0
  184. package/dist/ui/MenuCheckboxItem.svelte.d.ts +20 -0
  185. package/dist/ui/MenuItem.svelte +29 -31
  186. package/dist/ui/MenuItem.svelte.d.ts +2 -2
  187. package/dist/ui/MenuRadioGroup.svelte +25 -0
  188. package/dist/ui/MenuRadioGroup.svelte.d.ts +20 -0
  189. package/dist/ui/MenuRadioItem.svelte +20 -0
  190. package/dist/ui/MenuRadioItem.svelte.d.ts +15 -0
  191. package/dist/ui/MenuSeparator.svelte +12 -11
  192. package/dist/ui/MenuSub.svelte +24 -0
  193. package/dist/ui/MenuSub.svelte.d.ts +15 -0
  194. package/dist/ui/Spinner.svelte +41 -0
  195. package/dist/ui/Spinner.svelte.d.ts +35 -0
  196. package/dist/ui/Stack.svelte +36 -0
  197. package/dist/ui/Stack.svelte.d.ts +48 -0
  198. package/dist/ui/ToggleButton.svelte +58 -0
  199. package/dist/ui/ToggleButton.svelte.d.ts +45 -0
  200. package/dist/ui/ToggleGroup.svelte +142 -0
  201. package/dist/ui/ToggleGroup.svelte.d.ts +86 -0
  202. package/dist/ui/Toolbar.svelte +40 -0
  203. package/dist/ui/Toolbar.svelte.d.ts +20 -0
  204. package/dist/ui/ToolbarButton.svelte +29 -0
  205. package/dist/ui/ToolbarButton.svelte.d.ts +21 -0
  206. package/dist/ui/ToolbarLink.svelte +21 -0
  207. package/dist/ui/ToolbarLink.svelte.d.ts +12 -0
  208. package/dist/ui/ToolbarSeparator.svelte +22 -0
  209. package/dist/ui/ToolbarSeparator.svelte.d.ts +12 -0
  210. package/dist/ui/ToolbarToggleGroup.svelte +66 -0
  211. package/dist/ui/ToolbarToggleGroup.svelte.d.ts +52 -0
  212. package/dist/ui/accordion-context.d.ts +26 -0
  213. package/dist/ui/accordion-context.js +23 -0
  214. package/dist/ui/context-menu.d.ts +13 -0
  215. package/dist/ui/context-menu.js +13 -0
  216. package/dist/ui/index.d.ts +19 -0
  217. package/dist/ui/index.js +18 -0
  218. package/dist/ui/menu-context.d.ts +13 -15
  219. package/dist/ui/menu-context.js +14 -18
  220. package/dist/ui/menu-parts/CheckboxItemPart.svelte +26 -0
  221. package/dist/ui/menu-parts/CheckboxItemPart.svelte.d.ts +5 -0
  222. package/dist/ui/menu-parts/ItemPart.svelte +29 -0
  223. package/dist/ui/menu-parts/ItemPart.svelte.d.ts +5 -0
  224. package/dist/ui/menu-parts/RadioGroupPart.svelte +24 -0
  225. package/dist/ui/menu-parts/RadioGroupPart.svelte.d.ts +5 -0
  226. package/dist/ui/menu-parts/RadioItemPart.svelte +22 -0
  227. package/dist/ui/menu-parts/RadioItemPart.svelte.d.ts +5 -0
  228. package/dist/ui/menu-parts/SeparatorPart.svelte +16 -0
  229. package/dist/ui/menu-parts/SeparatorPart.svelte.d.ts +5 -0
  230. package/dist/ui/menu-parts/SubPart.svelte +28 -0
  231. package/dist/ui/menu-parts/SubPart.svelte.d.ts +5 -0
  232. package/dist/ui/menu-parts/menu-parts.d.ts +104 -0
  233. package/dist/ui/menu-parts/menu-parts.js +31 -0
  234. package/dist/ui/menu-parts/parts.d.ts +12 -0
  235. package/dist/ui/menu-parts/parts.js +27 -0
  236. package/dist/ui/toggle-classes.d.ts +39 -0
  237. package/dist/ui/toggle-classes.js +36 -0
  238. package/dist/ui/tones.d.ts +35 -0
  239. package/dist/ui/tones.js +90 -0
  240. package/package.json +34 -18
@@ -0,0 +1,66 @@
1
+ import { PinInput } from 'bits-ui';
2
+ import type { HTMLInputAttributes } from 'svelte/elements';
3
+ import { type FieldSize } from './field-types.ts';
4
+ interface Props extends Omit<HTMLInputAttributes, 'value' | 'size' | 'type' | 'maxlength' | 'pattern' | 'children' | 'inputmode'> {
5
+ /**
6
+ * The code typed so far. Bindable.
7
+ * @defaultValue `''`
8
+ */
9
+ value?: string | undefined;
10
+ /**
11
+ * Number of cells, and the input's `maxlength`.
12
+ * @defaultValue `6`
13
+ */
14
+ length?: number | undefined;
15
+ /**
16
+ * Which characters are accepted; sets `inputmode` and the input's pattern.
17
+ * @defaultValue `'numeric'`
18
+ */
19
+ charset?: 'numeric' | 'alphanumeric' | undefined;
20
+ /**
21
+ * Show `•` in the cells instead of the characters (a PIN rather than a code).
22
+ * @defaultValue `false`
23
+ */
24
+ masked?: boolean | undefined;
25
+ /** Called once every cell is filled, with the full code — typically to submit. */
26
+ onComplete?: ((code: string) => void) | undefined;
27
+ /** Normalise pasted text before it is applied. Defaults to stripping whitespace and hyphens. */
28
+ pasteTransformer?: ((text: string) => string) | undefined;
29
+ /**
30
+ * The input's `autocomplete`.
31
+ * @defaultValue `'one-time-code'`
32
+ */
33
+ autocomplete?: HTMLInputAttributes['autocomplete'];
34
+ /**
35
+ * Size step of the cells.
36
+ * @defaultValue `'md'`
37
+ */
38
+ size?: FieldSize | undefined;
39
+ /**
40
+ * Disables the input.
41
+ * @defaultValue `false`
42
+ */
43
+ disabled?: boolean | undefined;
44
+ /** `FieldControl`: the real input's `id`, which `FormField`'s label points at. */
45
+ id?: string | undefined;
46
+ /**
47
+ * `FieldControl`: space-separated ids for `aria-describedby`.
48
+ * @defaultValue `''`
49
+ */
50
+ describedBy?: string | undefined;
51
+ /**
52
+ * `FieldControl`: the visible label's id. Not used — the real input is
53
+ * labelable, so the `FormField`'s `<label for>` already names it; taken
54
+ * here so a spread `FieldControl` does not leak it as an attribute.
55
+ */
56
+ labelId?: string | undefined;
57
+ /**
58
+ * `FieldControl`: the field is in error — sets `aria-invalid` and the error border.
59
+ * @defaultValue `false`
60
+ */
61
+ invalid?: boolean | undefined;
62
+ }
63
+ /** A fixed-length code — a sign-in code, a TOTP, an invite code — typed one character per visible cell into one real input, on bits-ui's `PinInput`. */
64
+ declare const PinInput: import("svelte").Component<Props, {}, "value">;
65
+ type PinInput = ReturnType<typeof PinInput>;
66
+ export default PinInput;
@@ -17,6 +17,9 @@ One choice from a short, known list, on bits-ui’s `RadioGroup`.
17
17
  question as a heading-ish `<span>` and points `aria-labelledby` at it. That
18
18
  is why this is the one field group NOT built on `Fieldset`, and the hint and
19
19
  errors hang off the radiogroup element itself through `aria-describedby`.
20
+ It is a whole frame (question, hint, errors), not a control a `FormField`
21
+ wraps, so it keeps its own `legend` rather than taking a `FieldControl`'s
22
+ `labelId`: inside a `FormField` it would render two labels.
20
23
 
21
24
  The marker is drawn rather than delegated to daisyUI's `.radio`, because
22
25
  that class styles an `<input type="radio">` through `:checked` and bits-ui's
@@ -0,0 +1,51 @@
1
+ <!-- @component
2
+ An inline, always-visible month grid for picking a start–end date range, on bits-ui's `RangeCalendar`.
3
+ -->
4
+ <!--
5
+ RangeCalendar — `Calendar`'s range sibling: the first chosen day starts
6
+ the range, the second ends it, and the days between are drawn as one band.
7
+ `minDays` / `maxDays` bound the length; `excludeDisabled` decides whether a
8
+ range may run across disabled days. It shares everything else — props,
9
+ header, grids, day styling — with `Calendar` (`calendar-props.ts`,
10
+ `CalendarMonths.svelte`), so the ARIA contract is the same: see
11
+ `Calendar`'s header. bits-ui adds `data-range-start` / `-middle` / `-end`
12
+ to the days, which is all the band styling reads.
13
+
14
+ The value is `{ start, end }` of `DateValue`s, either side `undefined`
15
+ while the reader is still choosing.
16
+ -->
17
+ <script lang="ts">import { RangeCalendar } from "bits-ui";
18
+ import { getComponentsContext } from "../context.svelte.js";
19
+ import CalendarMonths from "./CalendarMonths.svelte";
20
+ import { calendarRoot, definedProps } from "./calendar-props.js";
21
+ let { value = $bindable(), month = $bindable(), minDays, maxDays, excludeDisabled = false, ...props } = $props();
22
+ const context = getComponentsContext();
23
+ const shared = $derived(calendarRoot(props, context.locale()));
24
+ const lengths = $derived(definedProps({
25
+ minDays,
26
+ maxDays
27
+ }));
28
+ </script>
29
+
30
+ <RangeCalendar.Root
31
+ {...shared.root}
32
+ {...lengths}
33
+ {excludeDisabled}
34
+ bind:placeholder={
35
+ // Unset, bits-ui picks the value's month or today's.
36
+ () => month as DateValue,
37
+ (next: DateValue) => {
38
+ month = next;
39
+ }
40
+ }
41
+ bind:value={
42
+ () => value ?? { start: undefined, end: undefined },
43
+ (next) => {
44
+ value = next;
45
+ }
46
+ }
47
+ >
48
+ {#snippet children({ months, weekdays })}
49
+ <CalendarMonths parts={RangeCalendar} {...shared.months} {months} {weekdays} />
50
+ {/snippet}
51
+ </RangeCalendar.Root>
@@ -0,0 +1,23 @@
1
+ import type { DateValue } from '@internationalized/date';
2
+ import { RangeCalendar } from 'bits-ui';
3
+ import { type CalendarBaseProps } from './calendar-props.ts';
4
+ interface Props extends CalendarBaseProps {
5
+ /** `{ start, end }`; either side `undefined` while choosing. Bindable. */
6
+ value?: {
7
+ start: DateValue | undefined;
8
+ end: DateValue | undefined;
9
+ } | undefined;
10
+ /** Shortest allowed range, in days. */
11
+ minDays?: number | undefined;
12
+ /** Longest allowed range, in days. */
13
+ maxDays?: number | undefined;
14
+ /**
15
+ * Refuse a range that would span a disabled day.
16
+ * @defaultValue `false`
17
+ */
18
+ excludeDisabled?: boolean | undefined;
19
+ }
20
+ /** An inline, always-visible month grid for picking a start–end date range, on bits-ui's `RangeCalendar`. */
21
+ declare const RangeCalendar: import("svelte").Component<Props, {}, "value" | "month">;
22
+ type RangeCalendar = ReturnType<typeof RangeCalendar>;
23
+ export default RangeCalendar;
@@ -0,0 +1,127 @@
1
+ <!-- @component
2
+ A score out of N as stars: an input on bits-ui's `RatingGroup` that binds a number (optionally in half steps) and posts under `name`, or, with `readonly`, a static display named "4.5 out of 5".
3
+ -->
4
+ <!--
5
+ Rating — one component, two branches.
6
+
7
+ Input (the default): bits-ui's `RatingGroup`. Its root is `role="slider"`
8
+ (bits-ui 2.20, `rating-group.svelte.js`) — one Tab stop, with the stars
9
+ `role="presentation"` — so there is no "radio button 4 of 5" with no
10
+ context. There is no APG rating pattern; bits-ui follows the slider one.
11
+ bits-ui supplies the role, `aria-valuenow` / `min` / `max`,
12
+ `aria-valuetext` (from `formatValue` here), `aria-readonly`,
13
+ `aria-disabled`, `aria-required`, the hidden input under `name`, the
14
+ arrow / Home / End / Page keys, digit keys (with `allowHalf`, "3" then
15
+ ".5" gives 3.5), and half-step pointer handling. This file adds the
16
+ accessible name, `aria-describedby` and `aria-invalid`.
17
+
18
+ Naming: a `<label for>` cannot name a `div`, so inside a `FormField` the
19
+ root takes `aria-labelledby={labelId}` (`FieldControl.labelId`, #179) and
20
+ `FormField` forwards a label click to it. Outside one, `label` becomes the
21
+ root's `aria-label`.
22
+
23
+ Clearing: bits-ui clears the value to 0 when the first star is clicked
24
+ while the value already equals it (pointer only; no key clears below
25
+ `min`, which is 0). That is left as bits-ui ships it, not hand-patched.
26
+
27
+ Read-only display (`readonly`): plain markup with no bits-ui root mounted —
28
+ `role="img"` with `aria-label={formatValue(value, max)}`, every star
29
+ `aria-hidden`, nothing focusable, nothing posted. It rounds to the nearest
30
+ half star whatever `allowHalf` says, because a display usually shows an
31
+ average ("4.5 out of 5"). The label still says the exact value.
32
+
33
+ Styling: daisyUI's `rating-*` size classes set `--size`; each star is two
34
+ `mask mask-star-2` halves (`mask-half-1` / `mask-half-2`, which flip in
35
+ RTL), filled in the tone's background or left at `bg-base-content/20`.
36
+ daisyUI's `rating` class itself is not used: its `.rating *` rules style
37
+ every descendant, which fights bits-ui's item wrappers.
38
+ -->
39
+ <script lang="ts">import { RatingGroup } from "bits-ui";
40
+ import { backgroundTone } from "../ui/tones.js";
41
+ import { ratingSize } from "./field-types.js";
42
+ let { value = $bindable(0), max = 5, allowHalf = false, readonly = false, hoverPreview = true, formatValue = (score, outOf) => `${score} out of ${outOf}`, name, disabled = false, tone = "warning", size = "md", item, label = "Rating", id: idProp, labelId, describedBy = "", invalid = false, required = false, class: className, ...rest } = $props();
43
+ const uid = $props.id();
44
+ const id = $derived(idProp ?? uid);
45
+ const fill = $derived(backgroundTone[tone]);
46
+ // bits-ui types `name` as a bare optional, so it is omitted rather than passed as `undefined`.
47
+ const nameProp = $derived(name === undefined ? {} : { name });
48
+ /** The display's stars: the value rounded to the nearest half. */
49
+ const displayStates = $derived.by(() => {
50
+ const shown = Math.round(value * 2) / 2;
51
+ return Array.from({ length: max }, (_, index) => {
52
+ if (shown >= index + 1) return "active";
53
+ return shown >= index + .5 ? "partial" : "inactive";
54
+ });
55
+ });
56
+ </script>
57
+
58
+ {#snippet star(state: StarState)}
59
+ <span aria-hidden="true" class="flex">
60
+ {#if item}
61
+ {@render item({ state })}
62
+ {:else}
63
+ <span
64
+ class={[
65
+ 'mask mask-star-2 mask-half-1 h-[calc(var(--size))] w-[calc(var(--size)*0.5)]',
66
+ state === 'inactive' ? 'bg-base-content/20' : fill,
67
+ ]}
68
+ ></span>
69
+ <span
70
+ class={[
71
+ 'mask mask-star-2 mask-half-2 h-[calc(var(--size))] w-[calc(var(--size)*0.5)]',
72
+ state === 'active' ? fill : 'bg-base-content/20',
73
+ ]}
74
+ ></span>
75
+ {/if}
76
+ </span>
77
+ {/snippet}
78
+
79
+ {#if readonly}
80
+ <div
81
+ {...rest}
82
+ {id}
83
+ role="img"
84
+ aria-label={formatValue(value, max)}
85
+ class={['inline-flex align-middle', ratingSize[size], className]}
86
+ >
87
+ {#each displayStates as state, index (index)}
88
+ {@render star(state)}
89
+ {/each}
90
+ </div>
91
+ {:else}
92
+ <RatingGroup.Root
93
+ {...rest}
94
+ bind:value
95
+ {id}
96
+ {max}
97
+ {allowHalf}
98
+ {hoverPreview}
99
+ {...nameProp}
100
+ {disabled}
101
+ {required}
102
+ aria-valuetext={formatValue}
103
+ aria-label={labelId === undefined ? label : undefined}
104
+ aria-labelledby={labelId}
105
+ aria-describedby={describedBy === '' ? undefined : describedBy}
106
+ aria-invalid={invalid ? 'true' : undefined}
107
+ class={[
108
+ 'inline-flex rounded-field align-middle focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary data-[disabled]:opacity-50',
109
+ invalid && 'outline-2 outline-offset-2 outline-error',
110
+ ratingSize[size],
111
+ className,
112
+ ]}
113
+ >
114
+ {#snippet children({ items })}
115
+ {#each items as entry (entry.index)}
116
+ <RatingGroup.Item
117
+ index={entry.index}
118
+ class="cursor-pointer data-[disabled]:cursor-not-allowed"
119
+ >
120
+ {#snippet children({ state })}
121
+ {@render star(state)}
122
+ {/snippet}
123
+ </RatingGroup.Item>
124
+ {/each}
125
+ {/snippet}
126
+ </RatingGroup.Root>
127
+ {/if}
@@ -0,0 +1,86 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ import { type Size, type Tone } from '../ui/tones.ts';
4
+ /** How much of one star is filled. */
5
+ type StarState = 'active' | 'partial' | 'inactive';
6
+ interface Props extends Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'dir'> {
7
+ /**
8
+ * The score. Bindable.
9
+ * @defaultValue `0`
10
+ */
11
+ value?: number | undefined;
12
+ /**
13
+ * Number of stars.
14
+ * @defaultValue `5`
15
+ */
16
+ max?: number | undefined;
17
+ /**
18
+ * Allow half-star values (bits-ui `allowHalf`).
19
+ * @defaultValue `false`
20
+ */
21
+ allowHalf?: boolean | undefined;
22
+ /**
23
+ * Render a static, unfocusable display instead of an input.
24
+ * @defaultValue `false`
25
+ */
26
+ readonly?: boolean | undefined;
27
+ /**
28
+ * Preview the hovered value before it is chosen (bits-ui `hoverPreview`).
29
+ * @defaultValue `true`
30
+ */
31
+ hoverPreview?: boolean | undefined;
32
+ /**
33
+ * The spoken value (the input's `aria-valuetext`); also the display's accessible name.
34
+ * @defaultValue `(value, max) => \`${value} out of ${max}\``
35
+ */
36
+ formatValue?: ((value: number, max: number) => string) | undefined;
37
+ /** Posts the value under this name (input only). */
38
+ name?: string | undefined;
39
+ /**
40
+ * Blocks keys and clicks, and posts nothing.
41
+ * @defaultValue `false`
42
+ */
43
+ disabled?: boolean | undefined;
44
+ /**
45
+ * Colour of the filled stars.
46
+ * @defaultValue `'warning'`
47
+ */
48
+ tone?: Tone | undefined;
49
+ /**
50
+ * daisyUI `rating` size step.
51
+ * @defaultValue `'md'`
52
+ */
53
+ size?: Size | undefined;
54
+ /** Replace the star glyph (a heart, a leaf); receives how much of it is filled. It is rendered inside an `aria-hidden` wrapper. */
55
+ item?: Snippet<[{
56
+ state: StarState;
57
+ }]> | undefined;
58
+ /**
59
+ * Accessible name, for a rating outside a `FormField`; ignored when `labelId` is set.
60
+ * @defaultValue `'Rating'`
61
+ */
62
+ label?: string | undefined;
63
+ /** `FieldControl`: the root's `id`. Minted when omitted. */
64
+ id?: string | undefined;
65
+ /** `FieldControl`: the visible label's id; the root is `aria-labelledby` it. */
66
+ labelId?: string | undefined;
67
+ /**
68
+ * `FieldControl`: space-separated ids for `aria-describedby`.
69
+ * @defaultValue `''`
70
+ */
71
+ describedBy?: string | undefined;
72
+ /**
73
+ * `FieldControl`: the field is in error — sets `aria-invalid` and an error outline.
74
+ * @defaultValue `false`
75
+ */
76
+ invalid?: boolean | undefined;
77
+ /**
78
+ * `FieldControl`: sets `aria-required` and the hidden input's `required`.
79
+ * @defaultValue `false`
80
+ */
81
+ required?: boolean | undefined;
82
+ }
83
+ /** A score out of N as stars: an input on bits-ui's `RatingGroup` that binds a number (optionally in half steps) and posts under `name`, or, with `readonly`, a static display named "4.5 out of 5". */
84
+ declare const Rating: import("svelte").Component<Props, {}, "value">;
85
+ type Rating = ReturnType<typeof Rating>;
86
+ export default Rating;
@@ -20,6 +20,7 @@ A `type="search"` field with a leading icon, a clear button while there is text,
20
20
  uses) and `onSubmit` on Enter.
21
21
  -->
22
22
  <script lang="ts">import { Search, X } from "@lucide/svelte";
23
+ import Spinner from "../ui/Spinner.svelte";
23
24
  import { inputSize } from "./field-types.js";
24
25
  let { label, value = $bindable(""), onInput, onSubmit, onClear, pending = false, clearLabel = "Clear search", size = "md", name = "q", placeholder = "Search", class: className, ...rest } = $props();
25
26
  const clear = () => {
@@ -47,7 +48,7 @@ const clear = () => {
47
48
  }}
48
49
  />
49
50
  {#if pending}
50
- <span class="loading loading-spinner loading-xs" aria-hidden="true"></span>
51
+ <Spinner decorative size="xs" />
51
52
  {:else if value !== ''}
52
53
  <button type="button" class="btn btn-ghost btn-xs btn-circle" onclick={clear}>
53
54
  <X class="size-3.5" aria-hidden="true" />
@@ -0,0 +1,91 @@
1
+ <!-- @component
2
+ The segment group the bits-ui date and time controls share; internal, not exported.
3
+ -->
4
+ <!--
5
+ SegmentGroup — the `role="group"` of spinbutton segments inside a bits-ui
6
+ `DateField`, `TimeField`, `DatePicker` or `DateRangePicker` root, written
7
+ once for all four. `parts` is the bits-ui namespace in use; each exposes
8
+ `Input` and `Segment` (a range's `Input` also takes `type`).
9
+
10
+ `<label for>` cannot name a `role="group"`, so the group is
11
+ `aria-labelledby` the `FormField`'s label (`labelId`), and each segment is
12
+ labelled by its own part name ("day, ") followed by that label, so a
13
+ reader landing on one hears "day, Sown on". bits-ui would name them from
14
+ its own `Label`, which a `FormField` has no use for, so the segments are
15
+ rendered through their `child` snippet with our `aria-labelledby` last.
16
+ The literal separators ("/", ":") are `aria-hidden`: the spinbutton names
17
+ already say which part is which.
18
+
19
+ Two shapes. By default the group IS the field: the `input` shell, named
20
+ by the label, carrying `describedBy` / `invalid`, with an optional
21
+ `trailing` snippet (the picker's calendar button) inside it. With
22
+ `range`, it is one half of a `DateRangePicker`, whose root draws the
23
+ shell: a bare inline group named by `range.label` ("Start date"), whose
24
+ segments read "day, <label> Start date".
25
+ -->
26
+ <script lang="ts">import { inputSize } from "./field-types.js";
27
+ let { parts, group, range, trailing } = $props();
28
+ const { class: className, ...attributes } = $derived(group.attributes);
29
+ // SAFETY: the namespaces' `Segment`s differ only in the `part` union they
30
+ // accept, and every `part` rendered here comes from the same root's own
31
+ // `segments`, so a TimeField segment is only ever handed a time part.
32
+ const Segment = $derived(parts.Segment);
33
+ // SAFETY: every `Input` takes `id`, `name` and `child`; only the range's
34
+ // also takes `type`, which is passed only when `range` is set, and a range
35
+ // is only ever rendered with `DateRangePicker`'s parts.
36
+ const Input = $derived(parts.Input);
37
+ const inputProps = $derived({
38
+ ...group.id === undefined || range !== undefined ? {} : { id: group.id },
39
+ name: (range === undefined ? group.name : range.name) ?? "",
40
+ ...range === undefined ? {} : { type: range.type }
41
+ });
42
+ </script>
43
+
44
+ {#snippet segmentList(segments: readonly { part: string; value: string }[], names: string | undefined)}
45
+ {#each segments as { part, value }, index (index)}
46
+ <Segment part={part as 'literal'}>
47
+ {#snippet child({ props: segmentProps })}
48
+ {#if part === 'literal'}
49
+ <span {...segmentProps} aria-hidden="true" class="text-base-content/60 px-px">{value}</span>
50
+ {:else}
51
+ <span
52
+ {...segmentProps}
53
+ aria-labelledby={names === undefined ? undefined : `${String(segmentProps['id'])} ${names}`}
54
+ class="focus:bg-primary focus:text-primary-content rounded-sm px-0.5 outline-none aria-[valuetext=Empty]:text-base-content/50"
55
+ >{value}</span
56
+ >
57
+ {/if}
58
+ {/snippet}
59
+ </Segment>
60
+ {/each}
61
+ {/snippet}
62
+
63
+ <Input {...inputProps}>
64
+ {#snippet child({ props, segments })}
65
+ {#if range !== undefined}
66
+ {@const ownId = String(props['id'])}
67
+ <div {...props} role="group" aria-label={range.label} class="inline-flex items-center">
68
+ {@render segmentList(segments, [group.labelId, ownId].filter(Boolean).join(' '))}
69
+ </div>
70
+ {:else}
71
+ <div
72
+ {...attributes}
73
+ {...props}
74
+ aria-labelledby={group.labelId}
75
+ aria-describedby={group.describedBy === '' ? undefined : group.describedBy}
76
+ aria-invalid={group.invalid ? 'true' : undefined}
77
+ class={[
78
+ 'input inline-flex w-auto items-center gap-0 tabular-nums',
79
+ inputSize[group.size],
80
+ group.invalid && 'input-error',
81
+ 'data-invalid:input-error',
82
+ group.disabled && 'opacity-50',
83
+ className,
84
+ ]}
85
+ >
86
+ {@render segmentList(segments, group.labelId)}
87
+ {@render trailing?.()}
88
+ </div>
89
+ {/if}
90
+ {/snippet}
91
+ </Input>
@@ -0,0 +1,20 @@
1
+ import type { DateField, DatePicker, DateRangePicker, TimeField } from 'bits-ui';
2
+ import type { Snippet } from 'svelte';
3
+ import type { SegmentGroupOptions } from './segmented-field.ts';
4
+ interface Props {
5
+ /** The bits-ui namespace whose parts to render. */
6
+ parts: typeof DateField | typeof TimeField | typeof DatePicker | typeof DateRangePicker;
7
+ group: SegmentGroupOptions;
8
+ /** One half of a range: which half, its name, and its posted field name. */
9
+ range?: {
10
+ readonly type: 'start' | 'end';
11
+ readonly label: string;
12
+ readonly name?: string | undefined;
13
+ } | undefined;
14
+ /** Rendered inside the shell after the segments (a picker's trigger). */
15
+ trailing?: Snippet<[]> | undefined;
16
+ }
17
+ /** The segment group the bits-ui date and time controls share; internal, not exported. */
18
+ declare const SegmentGroup: import("svelte").Component<Props, {}, "">;
19
+ type SegmentGroup = ReturnType<typeof SegmentGroup>;
20
+ export default SegmentGroup;
@@ -16,7 +16,7 @@ A native `<select>` in daisyUI styling over a list of options, with an optional
16
16
  attributes added.
17
17
  -->
18
18
  <script lang="ts">import { selectSize, toFieldOptions } from "./field-types.js";
19
- let { options, value = $bindable(""), placeholder, size = "md", invalid = false, describedBy = "", class: className, ...rest } = $props();
19
+ let { options, value = $bindable(""), placeholder, size = "md", invalid = false, describedBy = "", labelId: _labelId, class: className, ...rest } = $props();
20
20
  const entries = $derived(toFieldOptions(options));
21
21
  </script>
22
22
 
@@ -22,6 +22,12 @@ interface Props extends Omit<HTMLSelectAttributes, 'value' | 'size'> {
22
22
  * @defaultValue `''`
23
23
  */
24
24
  describedBy?: string | undefined;
25
+ /**
26
+ * `FieldControl`: the visible label's id. Not used — this control is
27
+ * labelable, so the `FormField`'s `<label for>` already names it; taken
28
+ * here so a spread `FieldControl` does not leak it as an attribute.
29
+ */
30
+ labelId?: string | undefined;
25
31
  }
26
32
  /** A native `<select>` in daisyUI styling over a list of options, with an optional placeholder and `FieldControl` wiring. */
27
33
  declare const Select: import("svelte").Component<Props, {}, "value">;