@sepale/components 0.0.0-stage → 0.1.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 (261) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +65 -3
  3. package/dist/components/Accordion.svelte +48 -0
  4. package/dist/components/Accordion.svelte.d.ts +24 -0
  5. package/dist/components/Alert.svelte +107 -0
  6. package/dist/components/Alert.svelte.d.ts +40 -0
  7. package/dist/components/Avatar.svelte +75 -0
  8. package/dist/components/Avatar.svelte.d.ts +37 -0
  9. package/dist/components/Badge.svelte +46 -0
  10. package/dist/components/Badge.svelte.d.ts +22 -0
  11. package/dist/components/Breadcrumbs.svelte +36 -0
  12. package/dist/components/Breadcrumbs.svelte.d.ts +18 -0
  13. package/dist/components/Bubble.svelte +43 -0
  14. package/dist/components/Bubble.svelte.d.ts +23 -0
  15. package/dist/components/Button.svelte +132 -0
  16. package/dist/components/Button.svelte.d.ts +40 -0
  17. package/dist/components/Calendar.svelte +193 -0
  18. package/dist/components/Calendar.svelte.d.ts +60 -0
  19. package/dist/components/Card.svelte +69 -0
  20. package/dist/components/Card.svelte.d.ts +37 -0
  21. package/dist/components/Carousel.svelte +550 -0
  22. package/dist/components/Carousel.svelte.d.ts +124 -0
  23. package/dist/components/Checkbox.svelte +48 -0
  24. package/dist/components/Checkbox.svelte.d.ts +26 -0
  25. package/dist/components/Chip.svelte +60 -0
  26. package/dist/components/Chip.svelte.d.ts +26 -0
  27. package/dist/components/ChipsInput.svelte +158 -0
  28. package/dist/components/ChipsInput.svelte.d.ts +64 -0
  29. package/dist/components/Collapse.svelte +101 -0
  30. package/dist/components/Collapse.svelte.d.ts +46 -0
  31. package/dist/components/ColorPicker.svelte +135 -0
  32. package/dist/components/ColorPicker.svelte.d.ts +44 -0
  33. package/dist/components/Combobox.svelte +496 -0
  34. package/dist/components/Combobox.svelte.d.ts +96 -0
  35. package/dist/components/DataControl.svelte +754 -0
  36. package/dist/components/DataControl.svelte.d.ts +143 -0
  37. package/dist/components/DataHandler.svelte +67 -0
  38. package/dist/components/DataHandler.svelte.d.ts +44 -0
  39. package/dist/components/DatePicker.svelte +185 -0
  40. package/dist/components/DatePicker.svelte.d.ts +56 -0
  41. package/dist/components/Dialog.svelte +194 -0
  42. package/dist/components/Dialog.svelte.d.ts +51 -0
  43. package/dist/components/Divider.svelte +54 -0
  44. package/dist/components/Divider.svelte.d.ts +27 -0
  45. package/dist/components/Dock.svelte +34 -0
  46. package/dist/components/Dock.svelte.d.ts +18 -0
  47. package/dist/components/Drawer.svelte +174 -0
  48. package/dist/components/Drawer.svelte.d.ts +51 -0
  49. package/dist/components/Dropdown.svelte +183 -0
  50. package/dist/components/Dropdown.svelte.d.ts +52 -0
  51. package/dist/components/Editable.svelte +292 -0
  52. package/dist/components/Editable.svelte.d.ts +113 -0
  53. package/dist/components/EmptyState.svelte +64 -0
  54. package/dist/components/EmptyState.svelte.d.ts +29 -0
  55. package/dist/components/Fieldset.svelte +42 -0
  56. package/dist/components/Fieldset.svelte.d.ts +20 -0
  57. package/dist/components/FileDropzone.svelte +154 -0
  58. package/dist/components/FileDropzone.svelte.d.ts +45 -0
  59. package/dist/components/FileExplorer.svelte +643 -0
  60. package/dist/components/FileExplorer.svelte.d.ts +114 -0
  61. package/dist/components/FileInput.svelte +125 -0
  62. package/dist/components/FileInput.svelte.d.ts +51 -0
  63. package/dist/components/FloatingLabel.svelte +38 -0
  64. package/dist/components/FloatingLabel.svelte.d.ts +24 -0
  65. package/dist/components/Footer.svelte +68 -0
  66. package/dist/components/Footer.svelte.d.ts +37 -0
  67. package/dist/components/Grid.svelte +122 -0
  68. package/dist/components/Grid.svelte.d.ts +72 -0
  69. package/dist/components/Hero.svelte +62 -0
  70. package/dist/components/Hero.svelte.d.ts +33 -0
  71. package/dist/components/Input.svelte +184 -0
  72. package/dist/components/Input.svelte.d.ts +65 -0
  73. package/dist/components/Join.svelte +38 -0
  74. package/dist/components/Join.svelte.d.ts +21 -0
  75. package/dist/components/Kanban.svelte +284 -0
  76. package/dist/components/Kanban.svelte.d.ts +93 -0
  77. package/dist/components/Label.svelte +32 -0
  78. package/dist/components/Label.svelte.d.ts +21 -0
  79. package/dist/components/LaptopFrame.svelte +44 -0
  80. package/dist/components/LaptopFrame.svelte.d.ts +21 -0
  81. package/dist/components/List.svelte +95 -0
  82. package/dist/components/List.svelte.d.ts +69 -0
  83. package/dist/components/Loading.svelte +37 -0
  84. package/dist/components/Loading.svelte.d.ts +17 -0
  85. package/dist/components/Menu.svelte +144 -0
  86. package/dist/components/Menu.svelte.d.ts +53 -0
  87. package/dist/components/MobileFrame.svelte +41 -0
  88. package/dist/components/MobileFrame.svelte.d.ts +21 -0
  89. package/dist/components/Navbar.svelte +49 -0
  90. package/dist/components/Navbar.svelte.d.ts +25 -0
  91. package/dist/components/NumberInput.svelte +255 -0
  92. package/dist/components/NumberInput.svelte.d.ts +68 -0
  93. package/dist/components/Pagination.svelte +169 -0
  94. package/dist/components/Pagination.svelte.d.ts +50 -0
  95. package/dist/components/Progress.svelte +65 -0
  96. package/dist/components/Progress.svelte.d.ts +28 -0
  97. package/dist/components/RadialProgress.svelte +48 -0
  98. package/dist/components/RadialProgress.svelte.d.ts +20 -0
  99. package/dist/components/Radio.svelte +50 -0
  100. package/dist/components/Radio.svelte.d.ts +28 -0
  101. package/dist/components/Range.svelte +53 -0
  102. package/dist/components/Range.svelte.d.ts +26 -0
  103. package/dist/components/Rating.svelte +90 -0
  104. package/dist/components/Rating.svelte.d.ts +41 -0
  105. package/dist/components/Select.svelte +338 -0
  106. package/dist/components/Select.svelte.d.ts +74 -0
  107. package/dist/components/Sidebar.svelte +100 -0
  108. package/dist/components/Sidebar.svelte.d.ts +54 -0
  109. package/dist/components/Skeleton.svelte +40 -0
  110. package/dist/components/Skeleton.svelte.d.ts +18 -0
  111. package/dist/components/Stat.svelte +71 -0
  112. package/dist/components/Stat.svelte.d.ts +35 -0
  113. package/dist/components/Stats.svelte +38 -0
  114. package/dist/components/Stats.svelte.d.ts +22 -0
  115. package/dist/components/Steps.svelte +204 -0
  116. package/dist/components/Steps.svelte.d.ts +66 -0
  117. package/dist/components/Subtitle.svelte +44 -0
  118. package/dist/components/Subtitle.svelte.d.ts +26 -0
  119. package/dist/components/Swatch.svelte +61 -0
  120. package/dist/components/Swatch.svelte.d.ts +27 -0
  121. package/dist/components/Switch.svelte +105 -0
  122. package/dist/components/Switch.svelte.d.ts +44 -0
  123. package/dist/components/Table.svelte +243 -0
  124. package/dist/components/Table.svelte.d.ts +121 -0
  125. package/dist/components/Tabs.svelte +142 -0
  126. package/dist/components/Tabs.svelte.d.ts +80 -0
  127. package/dist/components/Text.svelte +43 -0
  128. package/dist/components/Text.svelte.d.ts +25 -0
  129. package/dist/components/Textarea.svelte +76 -0
  130. package/dist/components/Textarea.svelte.d.ts +39 -0
  131. package/dist/components/Title.svelte +46 -0
  132. package/dist/components/Title.svelte.d.ts +29 -0
  133. package/dist/components/Toast.svelte +154 -0
  134. package/dist/components/Toast.svelte.d.ts +49 -0
  135. package/dist/components/Toggle.svelte +48 -0
  136. package/dist/components/Toggle.svelte.d.ts +25 -0
  137. package/dist/components/Tooltip.svelte +70 -0
  138. package/dist/components/Tooltip.svelte.d.ts +34 -0
  139. package/dist/components/WindowFrame.svelte +53 -0
  140. package/dist/components/WindowFrame.svelte.d.ts +24 -0
  141. package/dist/components/accordion-context.d.ts +8 -0
  142. package/dist/components/accordion-context.js +25 -0
  143. package/dist/components/color.d.ts +39 -0
  144. package/dist/components/color.js +92 -0
  145. package/dist/components/data-handler.svelte.d.ts +362 -0
  146. package/dist/components/data-handler.svelte.js +690 -0
  147. package/dist/components/file-icons.d.ts +25 -0
  148. package/dist/components/file-icons.js +46 -0
  149. package/dist/family-types.d.ts +45 -0
  150. package/dist/family-types.js +73 -0
  151. package/dist/generated/classes.d.ts +17 -0
  152. package/dist/generated/classes.js +2511 -0
  153. package/dist/generated/components.d.ts +39 -0
  154. package/dist/generated/components.js +28666 -0
  155. package/dist/generated/registry.d.ts +35 -0
  156. package/dist/generated/registry.js +2658 -0
  157. package/dist/index.d.ts +73 -0
  158. package/dist/index.js +99 -0
  159. package/dist/sepale-types.d.ts +8 -0
  160. package/dist/sepale-types.js +200 -0
  161. package/dist/theme.d.ts +123 -0
  162. package/dist/theme.js +226 -0
  163. package/dist/variants.d.ts +124 -0
  164. package/dist/variants.js +70 -0
  165. package/dist/vendor/air-datepicker/LICENSE.md +21 -0
  166. package/dist/vendor/air-datepicker/README.md +19 -0
  167. package/dist/vendor/air-datepicker/buttonPresets.d.ts +13 -0
  168. package/dist/vendor/air-datepicker/buttonPresets.js +12 -0
  169. package/dist/vendor/air-datepicker/consts.d.ts +18 -0
  170. package/dist/vendor/air-datepicker/consts.js +35 -0
  171. package/dist/vendor/air-datepicker/datepicker.d.ts +289 -0
  172. package/dist/vendor/air-datepicker/datepicker.js +1435 -0
  173. package/dist/vendor/air-datepicker/datepickerBody.d.ts +46 -0
  174. package/dist/vendor/air-datepicker/datepickerBody.js +386 -0
  175. package/dist/vendor/air-datepicker/datepickerButtons.d.ts +25 -0
  176. package/dist/vendor/air-datepicker/datepickerButtons.js +85 -0
  177. package/dist/vendor/air-datepicker/datepickerCell.d.ts +35 -0
  178. package/dist/vendor/air-datepicker/datepickerCell.js +297 -0
  179. package/dist/vendor/air-datepicker/datepickerKeyboard.d.ts +26 -0
  180. package/dist/vendor/air-datepicker/datepickerKeyboard.js +207 -0
  181. package/dist/vendor/air-datepicker/datepickerNav.d.ts +31 -0
  182. package/dist/vendor/air-datepicker/datepickerNav.js +200 -0
  183. package/dist/vendor/air-datepicker/datepickerTime.d.ts +60 -0
  184. package/dist/vendor/air-datepicker/datepickerTime.js +276 -0
  185. package/dist/vendor/air-datepicker/defaults.d.ts +65 -0
  186. package/dist/vendor/air-datepicker/defaults.js +79 -0
  187. package/dist/vendor/air-datepicker/index.d.ts +168 -0
  188. package/dist/vendor/air-datepicker/index.js +3 -0
  189. package/dist/vendor/air-datepicker/locale/ar.d.ts +4 -0
  190. package/dist/vendor/air-datepicker/locale/ar.js +14 -0
  191. package/dist/vendor/air-datepicker/locale/bg.d.ts +4 -0
  192. package/dist/vendor/air-datepicker/locale/bg.js +14 -0
  193. package/dist/vendor/air-datepicker/locale/ca.d.ts +4 -0
  194. package/dist/vendor/air-datepicker/locale/ca.js +14 -0
  195. package/dist/vendor/air-datepicker/locale/cs.d.ts +4 -0
  196. package/dist/vendor/air-datepicker/locale/cs.js +14 -0
  197. package/dist/vendor/air-datepicker/locale/da.d.ts +4 -0
  198. package/dist/vendor/air-datepicker/locale/da.js +14 -0
  199. package/dist/vendor/air-datepicker/locale/de.d.ts +4 -0
  200. package/dist/vendor/air-datepicker/locale/de.js +14 -0
  201. package/dist/vendor/air-datepicker/locale/el.d.ts +4 -0
  202. package/dist/vendor/air-datepicker/locale/el.js +14 -0
  203. package/dist/vendor/air-datepicker/locale/en.d.ts +4 -0
  204. package/dist/vendor/air-datepicker/locale/en.js +14 -0
  205. package/dist/vendor/air-datepicker/locale/es.d.ts +4 -0
  206. package/dist/vendor/air-datepicker/locale/es.js +14 -0
  207. package/dist/vendor/air-datepicker/locale/eu.d.ts +4 -0
  208. package/dist/vendor/air-datepicker/locale/eu.js +14 -0
  209. package/dist/vendor/air-datepicker/locale/fi.d.ts +4 -0
  210. package/dist/vendor/air-datepicker/locale/fi.js +14 -0
  211. package/dist/vendor/air-datepicker/locale/fr.d.ts +4 -0
  212. package/dist/vendor/air-datepicker/locale/fr.js +14 -0
  213. package/dist/vendor/air-datepicker/locale/hr.d.ts +4 -0
  214. package/dist/vendor/air-datepicker/locale/hr.js +14 -0
  215. package/dist/vendor/air-datepicker/locale/hu.d.ts +4 -0
  216. package/dist/vendor/air-datepicker/locale/hu.js +14 -0
  217. package/dist/vendor/air-datepicker/locale/id.d.ts +4 -0
  218. package/dist/vendor/air-datepicker/locale/id.js +14 -0
  219. package/dist/vendor/air-datepicker/locale/it.d.ts +4 -0
  220. package/dist/vendor/air-datepicker/locale/it.js +14 -0
  221. package/dist/vendor/air-datepicker/locale/ja.d.ts +4 -0
  222. package/dist/vendor/air-datepicker/locale/ja.js +14 -0
  223. package/dist/vendor/air-datepicker/locale/ko.d.ts +4 -0
  224. package/dist/vendor/air-datepicker/locale/ko.js +14 -0
  225. package/dist/vendor/air-datepicker/locale/nb.d.ts +4 -0
  226. package/dist/vendor/air-datepicker/locale/nb.js +14 -0
  227. package/dist/vendor/air-datepicker/locale/nl.d.ts +4 -0
  228. package/dist/vendor/air-datepicker/locale/nl.js +14 -0
  229. package/dist/vendor/air-datepicker/locale/pl.d.ts +4 -0
  230. package/dist/vendor/air-datepicker/locale/pl.js +14 -0
  231. package/dist/vendor/air-datepicker/locale/pt-BR.d.ts +4 -0
  232. package/dist/vendor/air-datepicker/locale/pt-BR.js +14 -0
  233. package/dist/vendor/air-datepicker/locale/pt.d.ts +4 -0
  234. package/dist/vendor/air-datepicker/locale/pt.js +14 -0
  235. package/dist/vendor/air-datepicker/locale/ro.d.ts +4 -0
  236. package/dist/vendor/air-datepicker/locale/ro.js +14 -0
  237. package/dist/vendor/air-datepicker/locale/ru.d.ts +4 -0
  238. package/dist/vendor/air-datepicker/locale/ru.js +14 -0
  239. package/dist/vendor/air-datepicker/locale/si.d.ts +4 -0
  240. package/dist/vendor/air-datepicker/locale/si.js +14 -0
  241. package/dist/vendor/air-datepicker/locale/sk.d.ts +4 -0
  242. package/dist/vendor/air-datepicker/locale/sk.js +14 -0
  243. package/dist/vendor/air-datepicker/locale/sl.d.ts +4 -0
  244. package/dist/vendor/air-datepicker/locale/sl.js +14 -0
  245. package/dist/vendor/air-datepicker/locale/sv.d.ts +4 -0
  246. package/dist/vendor/air-datepicker/locale/sv.js +14 -0
  247. package/dist/vendor/air-datepicker/locale/th.d.ts +4 -0
  248. package/dist/vendor/air-datepicker/locale/th.js +14 -0
  249. package/dist/vendor/air-datepicker/locale/tr.d.ts +4 -0
  250. package/dist/vendor/air-datepicker/locale/tr.js +16 -0
  251. package/dist/vendor/air-datepicker/locale/uk.d.ts +4 -0
  252. package/dist/vendor/air-datepicker/locale/uk.js +14 -0
  253. package/dist/vendor/air-datepicker/locale/zh.d.ts +4 -0
  254. package/dist/vendor/air-datepicker/locale/zh.js +14 -0
  255. package/dist/vendor/air-datepicker/typedefs.d.ts +3 -0
  256. package/dist/vendor/air-datepicker/typedefs.js +15 -0
  257. package/dist/vendor/air-datepicker/utils.d.ts +155 -0
  258. package/dist/vendor/air-datepicker/utils.js +395 -0
  259. package/dist/vendor/air-datepicker/withEvents.d.ts +1 -0
  260. package/dist/vendor/air-datepicker/withEvents.js +39 -0
  261. package/package.json +44 -3
@@ -0,0 +1,44 @@
1
+ /** What .color-picker is made of, classified. The props follow the
2
+ families the project registers (variants.ts, ADR 0015). */
3
+ export declare const PREFIX = "color-picker";
4
+ export declare const AXES: {
5
+ readonly size: readonly ["xs", "sm", "md", "lg", "xl"];
6
+ };
7
+ export declare const FLAGS: {
8
+ readonly flat: "No box of its own: the host — a dropdown, a card — draws it.";
9
+ };
10
+ export declare const PARTS: readonly ["head", "preview", "value", "channel", "track", "presets"];
11
+ /**
12
+ * One colour, edited in OKLCH: lightness, chroma, hue, each a slider
13
+ * whose track is the gradient it moves along, plus the raw value for
14
+ * anything the sliders cannot say (`color-mix(…)`, a `var()`).
15
+ *
16
+ * A panel, not a popover: put it in a Dropdown to float it.
17
+ *
18
+ * The three numbers are state, seeded from `value` and then only written:
19
+ * reading them back from the colour on every drag goes through a lossy
20
+ * conversion, and moving the hue would nudge the lightness.
21
+ */
22
+ import type { HTMLAttributes } from 'svelte/elements';
23
+ import { type AxisProps } from '../variants.js';
24
+ interface Props extends AxisProps<typeof AXES, typeof FLAGS, typeof PREFIX>, Omit<HTMLAttributes<HTMLDivElement>, 'onchange'> {
25
+ /** Any CSS colour. Bindable; written back as `oklch(…)`. */
26
+ value?: string;
27
+ /** Colours one click away, drawn as swatches. */
28
+ presets?: string[];
29
+ /** Hide the raw value field. */
30
+ noInput?: boolean;
31
+ /** The channels' accessible names. */
32
+ labels?: {
33
+ l?: string;
34
+ c?: string;
35
+ h?: string;
36
+ value?: string;
37
+ };
38
+ /** After every change. */
39
+ onChange?: (value: string) => void;
40
+ class?: string;
41
+ }
42
+ declare const ColorPicker: import("svelte").Component<Props, {}, "value">;
43
+ type ColorPicker = ReturnType<typeof ColorPicker>;
44
+ export default ColorPicker;
@@ -0,0 +1,496 @@
1
+ <script module lang="ts">
2
+ /** What .combobox is made of, classified. The props follow the
3
+ families the project registers (variants.ts, ADR 0015).
4
+
5
+ `.combobox-multiple` is a PART: the wrapper writes it from `multiple`. */
6
+ export const PREFIX = 'combobox';
7
+
8
+ export const AXES = {
9
+ size: ['xs', 'sm', 'md', 'lg', 'xl']
10
+ } as const;
11
+
12
+ export const FLAGS = {} as const;
13
+ export const PARTS = ['option', 'multiple'] as const;
14
+
15
+ /** Where the options come from: a list, a function, or an address answering JSON. */
16
+ export type ComboboxSource =
17
+ | any[]
18
+ | ((query: string) => any[] | Promise<any[]>)
19
+ | string
20
+ | { uri: string | ((query: string) => string); options?: RequestInit | ((query: string) => RequestInit) };
21
+ </script>
22
+
23
+ <script lang="ts">
24
+ /**
25
+ * A field whose value is looked up in a list of options while typing.
26
+ *
27
+ * **Strict** (default): the value is a picked option, never the typed
28
+ * text. A hidden `<select name>` carries it for a form, so the DOM says what
29
+ * was chosen. The field shows two things over time: the search being typed,
30
+ * and the label of the pick. Typing drops the pick; leaving a search
31
+ * unfinished brings the last pick back.
32
+ *
33
+ * **Free** (`strict={false}`): the value is the text — the options only
34
+ * complete it (autocompletion). The `<input>` itself carries `name`.
35
+ *
36
+ * **Multiple** (implies strict): the field shows a summary of the picks,
37
+ * the list stays open while ticking, and a hidden `<select multiple>`
38
+ * carries them. To collect free values as chips, see ChipsInput.
39
+ *
40
+ * Options come from a list (filtered here), a function of the query, or an
41
+ * address answering JSON (`then` reshapes the answer). The frame is the
42
+ * family's `.input`, the list its `.list-suggestions`.
43
+ */
44
+ import { untrack, type Snippet } from 'svelte';
45
+ import type { HTMLInputAttributes } from 'svelte/elements';
46
+ import { applyAxes, cx, type AxisProps } from '../variants.js';
47
+
48
+ interface Props extends AxisProps<typeof AXES, typeof FLAGS, typeof PREFIX>, Omit<HTMLInputAttributes, 'size' | 'value'> {
49
+ /** The options, or where to fetch them. */
50
+ options: ComboboxSource;
51
+ /** Reshape what a function or an address answered into the list. */
52
+ then?: (answer: any) => any[];
53
+ /** The text an option stands for: in the list, and in the field once picked. */
54
+ show?: (option: any) => string;
55
+ /** What identifies an option, and what the hidden select submits. Defaults to `id`, `value`, then the label. */
56
+ key?: (option: any) => string | number;
57
+ /** The value is a picked option (default), or the text (`false`). */
58
+ strict?: boolean;
59
+ /** Pick several. Implies strict. */
60
+ multiple?: boolean;
61
+ /** The pick(s) — the text, when not strict. Followed when changed from outside. */
62
+ value?: any;
63
+ /** A pick changed: the option, `undefined` when dropped, or the list with `multiple`. */
64
+ onSelect?: (value: any) => void;
65
+ /** The text changed, typed or completed. */
66
+ onInput?: (text: string) => void;
67
+ /** Milliseconds without typing before the source is asked. Default 100. */
68
+ wait?: number;
69
+ /** Open the list as soon as the field takes the focus. */
70
+ eager?: boolean;
71
+ /** Draw the list over scrolling ancestors, at fixed coordinates. */
72
+ float?: boolean;
73
+ /** A last row, "create what is being typed", once something is typed. */
74
+ add?: { show?: (text: string) => string; max?: number; onAdd: (text: string) => void };
75
+ /** The field's text for several picks. Defaults to the labels joined, or "n selected". */
76
+ summary?: (picks: any[]) => string;
77
+ /** How one option is drawn in the list. Defaults to its label. */
78
+ option?: Snippet<[any]>;
79
+ /** Inside the frame, before and after the text. */
80
+ start?: Snippet;
81
+ end?: Snippet;
82
+ /** What the list says when nothing matches. */
83
+ noresult?: string;
84
+ /** Extra classes on the `.input` frame: `input-ghost`, `input-primary`… */
85
+ field?: string;
86
+ /** Colour the field by its validity and reveal `hint` once the browser
87
+ marks it invalid. A strict field holding typed text but no pick is
88
+ invalid, with `unpickedLabel` as its message. */
89
+ validator?: boolean;
90
+ /** Shown under the field when it is invalid. */
91
+ hint?: Snippet;
92
+ /** The validity message of a strict field left with text but no pick. */
93
+ unpickedLabel?: string;
94
+ class?: string;
95
+ }
96
+
97
+ // The axes are deliberately NOT destructured: they travel in `axes` and are
98
+ // resolved by name, so an axis a family drops changes nothing down here.
99
+ let {
100
+ options,
101
+ then,
102
+ show,
103
+ key,
104
+ strict: strictProp = true,
105
+ multiple = false,
106
+ value: initial,
107
+ onSelect,
108
+ onInput,
109
+ wait = 100,
110
+ eager = false,
111
+ float = false,
112
+ add,
113
+ summary,
114
+ option,
115
+ start,
116
+ end,
117
+ noresult = 'No results',
118
+ field = '',
119
+ validator = false,
120
+ hint,
121
+ unpickedLabel = 'Pick an option from the list',
122
+ name,
123
+ disabled = false,
124
+ class: klass = '',
125
+ onfocus,
126
+ onblur,
127
+ onkeydown,
128
+ ...axes
129
+ }: Props = $props();
130
+
131
+ let applied = $derived(applyAxes(PREFIX, AXES, FLAGS, axes));
132
+ let classes = $derived(cx(PREFIX, applied.classes, multiple && `${PREFIX}-multiple`, klass));
133
+
134
+ const uid = $props.id();
135
+ let strict = $derived(strictProp || multiple);
136
+
137
+ /* ── What an option is ────────────────────────────────────────────── */
138
+
139
+ function label(o: any): string {
140
+ if (o == null) return '';
141
+ if (show) return show(o);
142
+ if (typeof o !== 'object') return `${o}`;
143
+ return `${o.label ?? o.name ?? o.value ?? o.id ?? ''}`;
144
+ }
145
+ function keyOf(o: any): string | number {
146
+ if (key) return key(o);
147
+ if (o !== null && typeof o === 'object') return o.id ?? o.value ?? label(o);
148
+ return o;
149
+ }
150
+
151
+ /* ── State ────────────────────────────────────────────────────────── */
152
+
153
+ /* Where it starts, read once: afterwards the visitor owns it. */
154
+ const start0 = untrack(() => initial);
155
+ const many = untrack(() => multiple);
156
+ const picks = untrack(() => strictProp || multiple);
157
+ /** The text in the field. */
158
+ let text = $state(picks ? (many ? '' : label(start0)) : `${start0 ?? ''}`);
159
+ /** The pick (single), or the picks (multiple). */
160
+ let picked: any = $state(many ? (Array.isArray(start0) ? [...start0] : []) : picks ? start0 : undefined);
161
+ /** The search being typed, in strict mode. `undefined` when not searching. */
162
+ let query: string | undefined = $state();
163
+ /** The last pick, for a search left unfinished to fall back to. */
164
+ let lastPicked: any = $state(start0);
165
+
166
+ let open = $state(false);
167
+ let loading = $state(false);
168
+ let items: any[] = $state([]);
169
+ /** Keyboard position in the list; -1 is the field itself. */
170
+ let active = $state(-1);
171
+ let timer: ReturnType<typeof setTimeout> | undefined;
172
+ /** Answers can arrive out of order: only the latest request may write. */
173
+ let ticket = 0;
174
+
175
+ let input: HTMLInputElement | undefined = $state();
176
+
177
+ /* A `value` changed from outside (a form reset, another field) replaces
178
+ the pick(s). A value echoing the current pick changes nothing, so a
179
+ parent writing back what `onSelect` gave it does not disturb a search. */
180
+ const same = (a: any, b: any) => (a == null || b == null ? a == b : keyOf(a) === keyOf(b));
181
+ $effect(() => {
182
+ const next = initial;
183
+ untrack(() => {
184
+ if (many) {
185
+ const list = Array.isArray(next) ? next : [];
186
+ const now = picked as any[];
187
+ if (list.length === now.length && list.every((o, i) => same(o, now[i]))) return;
188
+ picked = [...list];
189
+ } else if (picks) {
190
+ if (same(next, picked)) return;
191
+ picked = next;
192
+ lastPicked = next;
193
+ query = undefined;
194
+ } else if (`${next ?? ''}` !== text) text = `${next ?? ''}`;
195
+ });
196
+ });
197
+
198
+ /* A strict field's text is only a search: left with text but no pick, it
199
+ holds nothing, and says so to the form (an empty one is `required`'s). */
200
+ $effect(() => {
201
+ input?.setCustomValidity(strict && !multiple && picked == null && shown.trim() !== '' ? unpickedLabel : '');
202
+ });
203
+
204
+ const isPicked = (o: any) =>
205
+ multiple ? (picked as any[]).some((p) => keyOf(p) === keyOf(o)) : picked != null && keyOf(picked) === keyOf(o);
206
+
207
+ function summarize(list: any[]): string {
208
+ if (summary) return summary(list);
209
+ if (!list.length) return '';
210
+ const joined = list.map(label).join(', ');
211
+ return joined.length <= 32 ? joined : `${list.length} selected`;
212
+ }
213
+
214
+ /* Not searching: a strict field shows its pick, a multiple one its summary. */
215
+ let shown = $derived(
216
+ !strict ? text : query !== undefined ? query : multiple ? summarize(picked) : label(picked)
217
+ );
218
+
219
+ /* ── The source ───────────────────────────────────────────────────── */
220
+
221
+ async function search() {
222
+ const mine = ++ticket;
223
+ open = true;
224
+ loading = true;
225
+ const q = strict ? (query ?? '') : text;
226
+
227
+ let answer: any = [];
228
+ try {
229
+ if (Array.isArray(options)) {
230
+ const needle = q.trim().toLowerCase();
231
+ answer = needle ? options.filter((o) => label(o).toLowerCase().includes(needle)) : options;
232
+ } else if (typeof options === 'function') answer = await options(q);
233
+ else {
234
+ const src = typeof options === 'string' ? { uri: options } : options;
235
+ const uri = typeof src.uri === 'function' ? src.uri(q) : src.uri;
236
+ const init = typeof src.options === 'function' ? src.options(q) : src.options;
237
+ const res = await fetch(uri, init ?? { credentials: 'include', headers: { Accept: 'application/json' } });
238
+ answer = res.ok ? await res.json() : [];
239
+ }
240
+ } catch {
241
+ answer = [];
242
+ }
243
+
244
+ if (mine !== ticket) return;
245
+ const list = then && !Array.isArray(options) ? then(answer) : answer;
246
+ // Never undefined: a source that answers nothing leaves an empty list.
247
+ items = Array.isArray(list) ? list : [];
248
+ active = -1;
249
+ loading = false;
250
+ }
251
+
252
+ function close() {
253
+ clearTimeout(timer);
254
+ ticket++;
255
+ open = false;
256
+ loading = false;
257
+ active = -1;
258
+ }
259
+
260
+ /* ── Picking ──────────────────────────────────────────────────────── */
261
+
262
+ function choose(o: any) {
263
+ if (multiple) {
264
+ picked = isPicked(o) ? (picked as any[]).filter((p) => keyOf(p) !== keyOf(o)) : [...picked, o];
265
+ onSelect?.(picked);
266
+ return; // the list stays open while ticking
267
+ }
268
+ query = undefined;
269
+ if (strict) {
270
+ picked = o;
271
+ lastPicked = o;
272
+ } else {
273
+ text = label(o);
274
+ onInput?.(text);
275
+ }
276
+ onSelect?.(o);
277
+ close();
278
+ }
279
+
280
+ /** What "+ Create …" would create: the text as typed. */
281
+ let typed = $derived((strict ? (query ?? '') : text).trim());
282
+ let addLabel = $derived(
283
+ add && typed !== '' && (add.max === undefined || items.length < add.max)
284
+ ? (add.show?.(typed) ?? `+ Create ${typed}`)
285
+ : undefined
286
+ );
287
+ /** Rows the keyboard walks through: the options, then the add row. */
288
+ let rows = $derived(items.length + (addLabel ? 1 : 0));
289
+
290
+ function create() {
291
+ const t = typed;
292
+ close();
293
+ query = undefined;
294
+ add?.onAdd(t);
295
+ }
296
+
297
+ /** Empty the field and drop the pick(s). */
298
+ export function clear() {
299
+ query = undefined;
300
+ text = '';
301
+ lastPicked = undefined;
302
+ if (multiple) {
303
+ if ((picked as any[]).length) onSelect?.((picked = []));
304
+ } else if (strict && picked != null) onSelect?.((picked = undefined));
305
+ }
306
+
307
+ export function focus() {
308
+ input?.focus();
309
+ }
310
+
311
+ /* ── A list at fixed coordinates follows its field ────────────────── */
312
+
313
+ let frame: HTMLLabelElement | undefined = $state();
314
+ let floatStyle = $state('');
315
+
316
+ function place() {
317
+ if (!frame) return;
318
+ const box = frame.getBoundingClientRect();
319
+ floatStyle = `position: fixed; top: ${box.bottom + 4}px; left: ${box.left}px; min-width: ${box.width}px;`;
320
+ }
321
+
322
+ $effect(() => {
323
+ if (!float || !open || !frame) return;
324
+ place();
325
+ // Capture: the scroll of an inner container does not bubble.
326
+ window.addEventListener('scroll', place, true);
327
+ window.addEventListener('resize', place);
328
+ return () => {
329
+ window.removeEventListener('scroll', place, true);
330
+ window.removeEventListener('resize', place);
331
+ };
332
+ });
333
+
334
+ /* ── Events ───────────────────────────────────────────────────────── */
335
+
336
+ function handleInput(e: Event & { currentTarget: HTMLInputElement }) {
337
+ const t = e.currentTarget.value;
338
+ if (strict) {
339
+ // Typing, pasting or an autofill: the text is a search again, and no
340
+ // longer stands for the pick.
341
+ query = t;
342
+ if (!multiple && picked != null) onSelect?.((picked = undefined));
343
+ } else {
344
+ text = t;
345
+ onInput?.(t);
346
+ }
347
+ clearTimeout(timer);
348
+ timer = setTimeout(search, wait);
349
+ }
350
+
351
+ /* The caller's own focus, blur and keydown run after ours: a key the list
352
+ used (an arrow moving in it, Enter picking) reaches them prevented. */
353
+ function handleFocus(e: FocusEvent & { currentTarget: EventTarget & HTMLInputElement }) {
354
+ // A multiple field shows its summary at rest, and an empty search when in use.
355
+ if (multiple) query = '';
356
+ if (eager) search();
357
+ onfocus?.(e);
358
+ }
359
+
360
+ function handleBlur(e: FocusEvent & { currentTarget: EventTarget & HTMLInputElement }) {
361
+ blurred();
362
+ onblur?.(e);
363
+ }
364
+
365
+ function blurred() {
366
+ close();
367
+ if (multiple) {
368
+ query = undefined;
369
+ return;
370
+ }
371
+ // Leaving a strict field on an unfinished search: the leftover text
372
+ // stands for nothing, so the last pick comes back. An emptied field is
373
+ // a deliberate reset and stays empty.
374
+ if (strict && picked == null) {
375
+ const searching = query !== undefined && query.trim() !== '';
376
+ if (searching && lastPicked != null) onSelect?.((picked = lastPicked));
377
+ else if (!searching) lastPicked = undefined;
378
+ }
379
+ query = undefined;
380
+ }
381
+
382
+ function handleKeydown(e: KeyboardEvent & { currentTarget: EventTarget & HTMLInputElement }) {
383
+ listKeydown(e);
384
+ onkeydown?.(e);
385
+ }
386
+
387
+ function listKeydown(e: KeyboardEvent) {
388
+ if (e.key === 'ArrowDown') {
389
+ e.preventDefault();
390
+ if (!open) search();
391
+ else if (rows) active = (active + 1) % rows;
392
+ } else if (e.key === 'ArrowUp' && open && rows) {
393
+ e.preventDefault();
394
+ active = active <= 0 ? rows - 1 : active - 1;
395
+ } else if (e.key === 'Enter' && open && active >= 0) {
396
+ e.preventDefault();
397
+ if (active < items.length) choose(items[active]);
398
+ else create();
399
+ } else if (e.key === 'Escape' && open) {
400
+ e.preventDefault();
401
+ close();
402
+ }
403
+ }
404
+ </script>
405
+
406
+ <div class={classes}>
407
+ <label bind:this={frame} class={cx('input', field, validator && 'validator')}>
408
+ {@render start?.()}
409
+ <!-- Password managers fill a field without going through the list,
410
+ which would leave a value with no pick: keep them away. -->
411
+ <input
412
+ {...applied.rest}
413
+ bind:this={input}
414
+ type="text"
415
+ role="combobox"
416
+ name={strict ? undefined : name}
417
+ value={shown}
418
+ {disabled}
419
+ autocomplete="off"
420
+ data-1p-ignore
421
+ data-lpignore="true"
422
+ data-bwignore="true"
423
+ data-form-type="other"
424
+ aria-autocomplete="list"
425
+ aria-expanded={open}
426
+ aria-controls="{uid}-list"
427
+ aria-activedescendant={open && active >= 0 ? `${uid}-${active}` : undefined}
428
+ oninput={handleInput}
429
+ onfocus={handleFocus}
430
+ onblur={handleBlur}
431
+ onkeydown={handleKeydown}
432
+ />
433
+ {@render end?.()}
434
+
435
+ {#if open}
436
+ <!-- A row is picked on mousedown, before the field can lose the focus:
437
+ a blur first would close the list and, in strict mode, undo the
438
+ pick. The keyboard goes through the field (aria-activedescendant). -->
439
+ <ul
440
+ id="{uid}-list"
441
+ class="list list-suggestions"
442
+ role="listbox"
443
+ aria-multiselectable={multiple || undefined}
444
+ style={float ? floatStyle : undefined}
445
+ >
446
+ {#if loading}
447
+ <li class="list-row"><span class="loading loading-dots loading-sm"></span></li>
448
+ {:else}
449
+ {#each items as o, i (i)}
450
+ <li
451
+ id="{uid}-{i}"
452
+ class="list-row combobox-option"
453
+ role="option"
454
+ aria-selected={multiple ? isPicked(o) : active === i}
455
+ data-active={multiple && active === i ? '' : undefined}
456
+ onmousedown={(e) => {
457
+ e.preventDefault();
458
+ choose(o);
459
+ }}
460
+ >
461
+ {#if option}{@render option(o)}{:else}{label(o)}{/if}
462
+ </li>
463
+ {/each}
464
+
465
+ {#if addLabel}
466
+ <!-- Always last: the way out when nothing in the list fits. -->
467
+ <li
468
+ id="{uid}-{items.length}"
469
+ class="list-row"
470
+ role="option"
471
+ aria-selected={active === items.length}
472
+ onmousedown={(e) => {
473
+ e.preventDefault();
474
+ create();
475
+ }}
476
+ >
477
+ {addLabel}
478
+ </li>
479
+ {:else if items.length === 0}
480
+ <li class="list-row" role="option" aria-selected="false" aria-disabled="true">{noresult}</li>
481
+ {/if}
482
+ {/if}
483
+ </ul>
484
+ {/if}
485
+ </label>
486
+ {#if hint}<p class="validator-hint">{@render hint()}</p>{/if}
487
+
488
+ {#if strict}
489
+ <!-- What was chosen, for a form and for whoever reads the DOM. -->
490
+ <select {name} {disabled} multiple={multiple || undefined} hidden aria-hidden="true" tabindex="-1">
491
+ {#each multiple ? picked : picked != null ? [picked] : [] as p (keyOf(p))}
492
+ <option value={keyOf(p)} selected>{label(p)}</option>
493
+ {/each}
494
+ </select>
495
+ {/if}
496
+ </div>
@@ -0,0 +1,96 @@
1
+ /** What .combobox is made of, classified. The props follow the
2
+ families the project registers (variants.ts, ADR 0015).
3
+
4
+ `.combobox-multiple` is a PART: the wrapper writes it from `multiple`. */
5
+ export declare const PREFIX = "combobox";
6
+ export declare const AXES: {
7
+ readonly size: readonly ["xs", "sm", "md", "lg", "xl"];
8
+ };
9
+ export declare const FLAGS: {};
10
+ export declare const PARTS: readonly ["option", "multiple"];
11
+ /** Where the options come from: a list, a function, or an address answering JSON. */
12
+ export type ComboboxSource = any[] | ((query: string) => any[] | Promise<any[]>) | string | {
13
+ uri: string | ((query: string) => string);
14
+ options?: RequestInit | ((query: string) => RequestInit);
15
+ };
16
+ /**
17
+ * A field whose value is looked up in a list of options while typing.
18
+ *
19
+ * **Strict** (default): the value is a picked option, never the typed
20
+ * text. A hidden `<select name>` carries it for a form, so the DOM says what
21
+ * was chosen. The field shows two things over time: the search being typed,
22
+ * and the label of the pick. Typing drops the pick; leaving a search
23
+ * unfinished brings the last pick back.
24
+ *
25
+ * **Free** (`strict={false}`): the value is the text — the options only
26
+ * complete it (autocompletion). The `<input>` itself carries `name`.
27
+ *
28
+ * **Multiple** (implies strict): the field shows a summary of the picks,
29
+ * the list stays open while ticking, and a hidden `<select multiple>`
30
+ * carries them. To collect free values as chips, see ChipsInput.
31
+ *
32
+ * Options come from a list (filtered here), a function of the query, or an
33
+ * address answering JSON (`then` reshapes the answer). The frame is the
34
+ * family's `.input`, the list its `.list-suggestions`.
35
+ */
36
+ import { type Snippet } from 'svelte';
37
+ import type { HTMLInputAttributes } from 'svelte/elements';
38
+ import { type AxisProps } from '../variants.js';
39
+ interface Props extends AxisProps<typeof AXES, typeof FLAGS, typeof PREFIX>, Omit<HTMLInputAttributes, 'size' | 'value'> {
40
+ /** The options, or where to fetch them. */
41
+ options: ComboboxSource;
42
+ /** Reshape what a function or an address answered into the list. */
43
+ then?: (answer: any) => any[];
44
+ /** The text an option stands for: in the list, and in the field once picked. */
45
+ show?: (option: any) => string;
46
+ /** What identifies an option, and what the hidden select submits. Defaults to `id`, `value`, then the label. */
47
+ key?: (option: any) => string | number;
48
+ /** The value is a picked option (default), or the text (`false`). */
49
+ strict?: boolean;
50
+ /** Pick several. Implies strict. */
51
+ multiple?: boolean;
52
+ /** The pick(s) — the text, when not strict. Followed when changed from outside. */
53
+ value?: any;
54
+ /** A pick changed: the option, `undefined` when dropped, or the list with `multiple`. */
55
+ onSelect?: (value: any) => void;
56
+ /** The text changed, typed or completed. */
57
+ onInput?: (text: string) => void;
58
+ /** Milliseconds without typing before the source is asked. Default 100. */
59
+ wait?: number;
60
+ /** Open the list as soon as the field takes the focus. */
61
+ eager?: boolean;
62
+ /** Draw the list over scrolling ancestors, at fixed coordinates. */
63
+ float?: boolean;
64
+ /** A last row, "create what is being typed", once something is typed. */
65
+ add?: {
66
+ show?: (text: string) => string;
67
+ max?: number;
68
+ onAdd: (text: string) => void;
69
+ };
70
+ /** The field's text for several picks. Defaults to the labels joined, or "n selected". */
71
+ summary?: (picks: any[]) => string;
72
+ /** How one option is drawn in the list. Defaults to its label. */
73
+ option?: Snippet<[any]>;
74
+ /** Inside the frame, before and after the text. */
75
+ start?: Snippet;
76
+ end?: Snippet;
77
+ /** What the list says when nothing matches. */
78
+ noresult?: string;
79
+ /** Extra classes on the `.input` frame: `input-ghost`, `input-primary`… */
80
+ field?: string;
81
+ /** Colour the field by its validity and reveal `hint` once the browser
82
+ marks it invalid. A strict field holding typed text but no pick is
83
+ invalid, with `unpickedLabel` as its message. */
84
+ validator?: boolean;
85
+ /** Shown under the field when it is invalid. */
86
+ hint?: Snippet;
87
+ /** The validity message of a strict field left with text but no pick. */
88
+ unpickedLabel?: string;
89
+ class?: string;
90
+ }
91
+ declare const Combobox: import("svelte").Component<Props, {
92
+ clear: () => void;
93
+ focus: () => void;
94
+ }, "">;
95
+ type Combobox = ReturnType<typeof Combobox>;
96
+ export default Combobox;