@placeholderco/placeholder-ui 1.0.12 → 2.0.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 (276) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +134 -130
  3. package/dist/app.css +178 -0
  4. package/dist/bootstrap.min.css +3600 -0
  5. package/dist/cards/GitCard.svelte +115 -0
  6. package/dist/cards/GitCard.svelte.d.ts +22 -0
  7. package/dist/display/Avatar.svelte +29 -17
  8. package/dist/display/Avatar.svelte.d.ts +12 -2
  9. package/dist/display/LinkCollection.svelte +40 -18
  10. package/dist/display/LinkCollection.svelte.d.ts +9 -2
  11. package/dist/display/Paper.svelte +31 -19
  12. package/dist/display/Paper.svelte.d.ts +12 -3
  13. package/dist/display/alert/Alert.svelte +129 -0
  14. package/dist/display/alert/Alert.svelte.d.ts +15 -0
  15. package/dist/display/alert/AlertVariant.d.ts +1 -0
  16. package/dist/display/alert/AlertVariant.js +1 -0
  17. package/dist/display/filetree/FileTree.svelte +259 -0
  18. package/dist/display/filetree/FileTree.svelte.d.ts +76 -0
  19. package/dist/display/filetree/FileTreeItem.svelte +396 -0
  20. package/dist/display/filetree/FileTreeItem.svelte.d.ts +40 -0
  21. package/dist/display/filetree/FileTreeNode.d.ts +46 -0
  22. package/dist/display/filetree/FileTreeNode.js +115 -0
  23. package/dist/display/filetree/index.d.ts +5 -0
  24. package/dist/display/filetree/index.js +3 -0
  25. package/dist/editors/CodeMirror.svelte +306 -0
  26. package/dist/editors/CodeMirror.svelte.d.ts +31 -0
  27. package/dist/editors/tiptap/LinkActionMenu.svelte +307 -0
  28. package/dist/editors/tiptap/LinkActionMenu.svelte.d.ts +8 -0
  29. package/dist/editors/tiptap/TipTap.svelte +856 -0
  30. package/dist/editors/tiptap/TipTap.svelte.d.ts +54 -0
  31. package/dist/editors/tiptap/TipTapBubble.svelte +790 -0
  32. package/dist/editors/tiptap/TipTapBubble.svelte.d.ts +44 -0
  33. package/dist/editors/tiptap/collaboration.d.ts +55 -0
  34. package/dist/editors/tiptap/collaboration.js +1 -0
  35. package/dist/editors/tiptap/extensions/Alert.d.ts +18 -0
  36. package/dist/editors/tiptap/extensions/Alert.js +72 -0
  37. package/dist/editors/tiptap/extensions/CodeBlockEditor.d.ts +21 -0
  38. package/dist/editors/tiptap/extensions/CodeBlockEditor.js +326 -0
  39. package/dist/editors/tiptap/extensions/Div.d.ts +19 -0
  40. package/dist/editors/tiptap/extensions/Div.js +89 -0
  41. package/dist/editors/tiptap/extensions/Span.d.ts +5 -0
  42. package/dist/editors/tiptap/extensions/Span.js +56 -0
  43. package/dist/editors/tiptap/extensions/codeBlockEditor.css +236 -0
  44. package/dist/editors/tiptap/extensions/codeBlockLanguages.d.ts +17 -0
  45. package/dist/editors/tiptap/extensions/codeBlockLanguages.js +124 -0
  46. package/dist/editors/tiptap/extensions/index.d.ts +7 -0
  47. package/dist/editors/tiptap/extensions/index.js +5 -0
  48. package/dist/editors/tiptap/toolbar/AlertButton.svelte +261 -0
  49. package/dist/editors/tiptap/toolbar/AlertButton.svelte.d.ts +7 -0
  50. package/dist/editors/tiptap/toolbar/AlignCenterButton.svelte +18 -0
  51. package/dist/editors/tiptap/toolbar/AlignCenterButton.svelte.d.ts +7 -0
  52. package/dist/editors/tiptap/toolbar/AlignJustifyButton.svelte +18 -0
  53. package/dist/editors/tiptap/toolbar/AlignJustifyButton.svelte.d.ts +7 -0
  54. package/dist/editors/tiptap/toolbar/AlignLeftButton.svelte +18 -0
  55. package/dist/editors/tiptap/toolbar/AlignLeftButton.svelte.d.ts +7 -0
  56. package/dist/editors/tiptap/toolbar/AlignRightButton.svelte +18 -0
  57. package/dist/editors/tiptap/toolbar/AlignRightButton.svelte.d.ts +7 -0
  58. package/dist/editors/tiptap/toolbar/BoldButton.svelte +18 -0
  59. package/dist/editors/tiptap/toolbar/BoldButton.svelte.d.ts +7 -0
  60. package/dist/editors/tiptap/toolbar/BulletListButton.svelte +18 -0
  61. package/dist/editors/tiptap/toolbar/BulletListButton.svelte.d.ts +7 -0
  62. package/dist/editors/tiptap/toolbar/CodeBlockButton.svelte +18 -0
  63. package/dist/editors/tiptap/toolbar/CodeBlockButton.svelte.d.ts +7 -0
  64. package/dist/editors/tiptap/toolbar/CodeButton.svelte +18 -0
  65. package/dist/editors/tiptap/toolbar/CodeButton.svelte.d.ts +7 -0
  66. package/dist/editors/tiptap/toolbar/ColorButton.svelte +299 -0
  67. package/dist/editors/tiptap/toolbar/ColorButton.svelte.d.ts +20 -0
  68. package/dist/editors/tiptap/toolbar/DocumentButton.svelte +58 -0
  69. package/dist/editors/tiptap/toolbar/DocumentButton.svelte.d.ts +9 -0
  70. package/dist/editors/tiptap/toolbar/HeadingButton.svelte +30 -0
  71. package/dist/editors/tiptap/toolbar/HeadingButton.svelte.d.ts +8 -0
  72. package/dist/editors/tiptap/toolbar/HtmlButton.svelte +68 -0
  73. package/dist/editors/tiptap/toolbar/HtmlButton.svelte.d.ts +7 -0
  74. package/dist/editors/tiptap/toolbar/ImageButton.svelte +54 -0
  75. package/dist/editors/tiptap/toolbar/ImageButton.svelte.d.ts +8 -0
  76. package/dist/editors/tiptap/toolbar/ItalicButton.svelte +18 -0
  77. package/dist/editors/tiptap/toolbar/ItalicButton.svelte.d.ts +7 -0
  78. package/dist/editors/tiptap/toolbar/LinkButton.svelte +257 -0
  79. package/dist/editors/tiptap/toolbar/LinkButton.svelte.d.ts +7 -0
  80. package/dist/editors/tiptap/toolbar/OrderedListButton.svelte +18 -0
  81. package/dist/editors/tiptap/toolbar/OrderedListButton.svelte.d.ts +7 -0
  82. package/dist/editors/tiptap/toolbar/RedoButton.svelte +18 -0
  83. package/dist/editors/tiptap/toolbar/RedoButton.svelte.d.ts +7 -0
  84. package/dist/editors/tiptap/toolbar/StrikethroughButton.svelte +18 -0
  85. package/dist/editors/tiptap/toolbar/StrikethroughButton.svelte.d.ts +7 -0
  86. package/dist/editors/tiptap/toolbar/SubscriptButton.svelte +18 -0
  87. package/dist/editors/tiptap/toolbar/SubscriptButton.svelte.d.ts +7 -0
  88. package/dist/editors/tiptap/toolbar/SuperscriptButton.svelte +18 -0
  89. package/dist/editors/tiptap/toolbar/SuperscriptButton.svelte.d.ts +7 -0
  90. package/dist/editors/tiptap/toolbar/TableButton.svelte +258 -0
  91. package/dist/editors/tiptap/toolbar/TableButton.svelte.d.ts +7 -0
  92. package/dist/editors/tiptap/toolbar/ToolbarButton.svelte +84 -0
  93. package/dist/editors/tiptap/toolbar/ToolbarButton.svelte.d.ts +10 -0
  94. package/dist/editors/tiptap/toolbar/UnderlineButton.svelte +18 -0
  95. package/dist/editors/tiptap/toolbar/UnderlineButton.svelte.d.ts +7 -0
  96. package/dist/editors/tiptap/toolbar/UndoButton.svelte +18 -0
  97. package/dist/editors/tiptap/toolbar/UndoButton.svelte.d.ts +7 -0
  98. package/dist/form/Autocomplete.svelte +86 -135
  99. package/dist/form/Autocomplete.svelte.d.ts +6 -8
  100. package/dist/form/AutocompleteMulti.svelte +385 -227
  101. package/dist/form/AutocompleteMulti.svelte.d.ts +59 -10
  102. package/dist/form/Checkbox.svelte +59 -54
  103. package/dist/form/Checkbox.svelte.d.ts +14 -5
  104. package/dist/form/Chips.svelte +29 -13
  105. package/dist/form/Chips.svelte.d.ts +16 -2
  106. package/dist/form/ComboBox.svelte +30 -20
  107. package/dist/form/ComboBox.svelte.d.ts +1 -1
  108. package/dist/form/ComboBoxItemBuilder.svelte +161 -188
  109. package/dist/form/ComboBoxItemBuilder.svelte.d.ts +19 -2
  110. package/dist/form/ComboBoxMulti.svelte +38 -24
  111. package/dist/form/ComboBoxMulti.svelte.d.ts +1 -1
  112. package/dist/form/CronBuilder.svelte +84 -67
  113. package/dist/form/CronBuilder.svelte.d.ts +16 -3
  114. package/dist/form/DatePicker.svelte +148 -195
  115. package/dist/form/DateRangePicker.svelte +101 -98
  116. package/dist/form/DateTimePicker.svelte +167 -208
  117. package/dist/form/DateTimePicker.svelte.d.ts +10 -2
  118. package/dist/form/FormGroup.svelte +33 -10
  119. package/dist/form/FormGroup.svelte.d.ts +12 -2
  120. package/dist/form/Number.svelte +70 -47
  121. package/dist/form/Number.svelte.d.ts +36 -6
  122. package/dist/form/Radio.svelte +18 -18
  123. package/dist/form/RadioGroup.svelte +23 -12
  124. package/dist/form/RadioGroup.svelte.d.ts +13 -2
  125. package/dist/form/SegmentedControl.svelte +76 -56
  126. package/dist/form/SegmentedControl.svelte.d.ts +9 -4
  127. package/dist/form/Select.svelte +150 -233
  128. package/dist/form/Select.svelte.d.ts +29 -5
  129. package/dist/form/SelectMulti.svelte +163 -277
  130. package/dist/form/SelectMulti.svelte.d.ts +36 -7
  131. package/dist/form/Slider.svelte +183 -269
  132. package/dist/form/Slider.svelte.d.ts +32 -17
  133. package/dist/form/StringArrayBuilder.svelte +249 -0
  134. package/dist/form/StringArrayBuilder.svelte.d.ts +15 -0
  135. package/dist/form/Switch.svelte +164 -106
  136. package/dist/form/Switch.svelte.d.ts +22 -3
  137. package/dist/form/TextArea.svelte +49 -29
  138. package/dist/form/TextArea.svelte.d.ts +23 -3
  139. package/dist/form/Textbox.svelte +69 -68
  140. package/dist/form/Textbox.svelte.d.ts +32 -4
  141. package/dist/form/TimePicker.svelte +42 -47
  142. package/dist/form/TimePicker.svelte.d.ts +12 -2
  143. package/dist/form/comboBoxCore.svelte.d.ts +99 -0
  144. package/dist/form/comboBoxCore.svelte.js +243 -0
  145. package/dist/icon/Icon.svelte +8 -3
  146. package/dist/icon/Icon.svelte.d.ts +8 -3
  147. package/dist/icon/file-type-bmp.svg +1 -0
  148. package/dist/icon/file-type-csv.svg +1 -0
  149. package/dist/icon/file-type-doc.svg +1 -0
  150. package/dist/icon/file-type-docx.svg +1 -0
  151. package/dist/icon/file-type-jpg.svg +1 -0
  152. package/dist/icon/file-type-pdf.svg +1 -0
  153. package/dist/icon/file-type-png.svg +1 -0
  154. package/dist/icon/file-type-ppt.svg +1 -0
  155. package/dist/icon/file-type-svg.svg +1 -0
  156. package/dist/icon/file-type-txt.svg +1 -0
  157. package/dist/icon/file-type-xls.svg +1 -0
  158. package/dist/icon/file-type-zip.svg +1 -0
  159. package/dist/icon/index.d.ts +166 -151
  160. package/dist/icon/index.js +166 -152
  161. package/dist/icon/logo-placeholder.svg +6 -0
  162. package/dist/icon/source-code.svg +1 -0
  163. package/dist/icon/sun-moon.svg +1 -0
  164. package/dist/icon/table.svg +6 -0
  165. package/dist/index.d.ts +87 -26
  166. package/dist/index.js +25 -28
  167. package/dist/layout/AppShell.svelte +12 -162
  168. package/dist/layout/AppShell.svelte.d.ts +6 -27
  169. package/dist/layout/CustomNavbar.svelte +9 -7
  170. package/dist/layout/CustomNavbar.svelte.d.ts +4 -2
  171. package/dist/layout/Navbar.svelte +161 -129
  172. package/dist/layout/Navbar.svelte.d.ts +29 -15
  173. package/dist/layout/NavbarItemDisplay.svelte +2 -2
  174. package/dist/layout/NavbarItemView.svelte +182 -0
  175. package/dist/layout/NavbarItemView.svelte.d.ts +10 -0
  176. package/dist/layout/Sidenav.svelte +280 -341
  177. package/dist/layout/Sidenav.svelte.d.ts +36 -4
  178. package/dist/models/ComboBoxItem.d.ts +2 -0
  179. package/dist/models/Employee.d.ts +53 -0
  180. package/dist/models/Employee.js +23 -0
  181. package/dist/models/Hyperlink.d.ts +3 -1
  182. package/dist/models/LinkCollectionItem.d.ts +3 -1
  183. package/dist/models/NavbarItem.d.ts +6 -2
  184. package/dist/theme.svelte.d.ts +0 -1
  185. package/dist/theme.svelte.js +46 -58
  186. package/dist/ui/Accordion.svelte +81 -242
  187. package/dist/ui/Accordion.svelte.d.ts +37 -14
  188. package/dist/ui/AccordionItem.svelte +303 -0
  189. package/dist/ui/AccordionItem.svelte.d.ts +21 -0
  190. package/dist/ui/ActionIcon.svelte +25 -21
  191. package/dist/ui/ActionIcon.svelte.d.ts +15 -2
  192. package/dist/ui/Badge.svelte +163 -171
  193. package/dist/ui/Badge.svelte.d.ts +13 -5
  194. package/dist/ui/Button.svelte +178 -205
  195. package/dist/ui/Button.svelte.d.ts +19 -2
  196. package/dist/ui/ButtonVariant.d.ts +15 -1
  197. package/dist/ui/ContextMenu.svelte +146 -0
  198. package/dist/ui/ContextMenu.svelte.d.ts +37 -0
  199. package/dist/ui/ContextMenuList.svelte +188 -0
  200. package/dist/ui/ContextMenuList.svelte.d.ts +12 -0
  201. package/dist/ui/Dialog.svelte +183 -111
  202. package/dist/ui/Dialog.svelte.d.ts +21 -4
  203. package/dist/ui/Drawer.svelte +237 -184
  204. package/dist/ui/Drawer.svelte.d.ts +20 -2
  205. package/dist/ui/Dropdown.svelte +24 -14
  206. package/dist/ui/Dropdown.svelte.d.ts +11 -2
  207. package/dist/ui/Dropzone.svelte +159 -57
  208. package/dist/ui/Dropzone.svelte.d.ts +29 -2
  209. package/dist/ui/Link.svelte +12 -4
  210. package/dist/ui/Link.svelte.d.ts +8 -2
  211. package/dist/ui/Loader.svelte +23 -18
  212. package/dist/ui/Loader.svelte.d.ts +5 -2
  213. package/dist/ui/Logo.svelte +37 -0
  214. package/dist/ui/Logo.svelte.d.ts +16 -0
  215. package/dist/ui/MultiSortable.svelte +287 -0
  216. package/dist/ui/MultiSortable.svelte.d.ts +50 -0
  217. package/dist/ui/Pagination.svelte +15 -9
  218. package/dist/ui/Pagination.svelte.d.ts +6 -2
  219. package/dist/ui/Popover.svelte +43 -65
  220. package/dist/ui/Popover.svelte.d.ts +14 -5
  221. package/dist/ui/ProgressBar.svelte +28 -30
  222. package/dist/ui/ProgressBarVariant.d.ts +7 -1
  223. package/dist/ui/SegmentedControl.svelte +0 -0
  224. package/dist/ui/SegmentedControl.svelte.d.ts +26 -0
  225. package/dist/ui/Sortable.svelte +163 -0
  226. package/dist/ui/Sortable.svelte.d.ts +47 -0
  227. package/dist/ui/Table.svelte +383 -80
  228. package/dist/ui/Table.svelte.d.ts +43 -2
  229. package/dist/ui/Tabs.svelte +22 -18
  230. package/dist/ui/Tabs.svelte.d.ts +6 -2
  231. package/dist/ui/ThemeSwitcher.svelte +26 -7
  232. package/dist/ui/ThemeSwitcher.svelte.d.ts +4 -2
  233. package/dist/ui/Tooltip.svelte +312 -180
  234. package/dist/ui/Tooltip.svelte.d.ts +4 -3
  235. package/dist/uiTheme.svelte.d.ts +86 -0
  236. package/dist/uiTheme.svelte.js +214 -0
  237. package/dist/util/DateFunctions.js +1 -1
  238. package/dist/util/DragDrop.d.ts +50 -0
  239. package/dist/util/DragDrop.js +154 -0
  240. package/dist/util/NavigateTo.js +2 -2
  241. package/dist/util/dayjs.d.ts +4 -0
  242. package/dist/util/dayjs.js +15 -0
  243. package/package.json +66 -26
  244. package/dist/display/Alert.svelte +0 -179
  245. package/dist/display/Alert.svelte.d.ts +0 -13
  246. package/dist/form/FileInput.svelte +0 -235
  247. package/dist/form/FileInput.svelte.d.ts +0 -23
  248. package/dist/form/PasswordInput.svelte +0 -253
  249. package/dist/form/PasswordInput.svelte.d.ts +0 -34
  250. package/dist/form/Rating.svelte +0 -235
  251. package/dist/form/Rating.svelte.d.ts +0 -21
  252. package/dist/styles/components.css +0 -199
  253. package/dist/styles/dark.css +0 -146
  254. package/dist/styles/index.css +0 -116
  255. package/dist/styles/reset.css +0 -110
  256. package/dist/styles/semantic.css +0 -86
  257. package/dist/styles/tokens.css +0 -203
  258. package/dist/styles/utilities.css +0 -523
  259. package/dist/ui/Breadcrumbs.svelte +0 -131
  260. package/dist/ui/Breadcrumbs.svelte.d.ts +0 -16
  261. package/dist/ui/LoadingOverlay.svelte +0 -53
  262. package/dist/ui/LoadingOverlay.svelte.d.ts +0 -11
  263. package/dist/ui/Progress.svelte +0 -191
  264. package/dist/ui/Progress.svelte.d.ts +0 -15
  265. package/dist/ui/RingProgress.svelte +0 -141
  266. package/dist/ui/RingProgress.svelte.d.ts +0 -18
  267. package/dist/ui/Skeleton.svelte +0 -85
  268. package/dist/ui/Skeleton.svelte.d.ts +0 -12
  269. package/dist/ui/Stepper.svelte +0 -355
  270. package/dist/ui/Stepper.svelte.d.ts +0 -24
  271. package/dist/ui/Timeline.svelte +0 -225
  272. package/dist/ui/Timeline.svelte.d.ts +0 -23
  273. /package/dist/{actions → util}/ClickOutside.d.ts +0 -0
  274. /package/dist/{actions → util}/ClickOutside.js +0 -0
  275. /package/dist/{transitions → util}/Transitions.d.ts +0 -0
  276. /package/dist/{transitions → util}/Transitions.js +0 -0
@@ -1,51 +1,77 @@
1
1
  <script lang="ts">
2
- import { clickOutside } from "../actions/ClickOutside.js";
3
- import type { NotifyModel } from "../models/NotifyModel.js";
2
+ import { clickOutside } from '../util/ClickOutside.js';
4
3
  import type {
5
4
  ComboBoxGroup,
6
5
  ComboBoxItem,
7
6
  FetchFunctionType,
8
7
  RetrieveLabelFunctionType,
9
- SearchFunctionType,
10
- } from "../models/ComboBoxItem.js";
11
- import ComboBoxMulti from "./ComboBoxMulti.svelte";
12
- import FormGroup from "./FormGroup.svelte";
13
- import Icon from "../icon/Icon.svelte";
14
- import Loader from "../ui/Loader.svelte";
15
- import { useDialogEvents } from "../ui/DialogEvents.svelte.js";
16
- import selectorSvg from "../icon/selector.svg?raw";
17
- import { iconX, iconCopy } from "../icon/index.js";
18
- import ActionIcon from "../ui/ActionIcon.svelte";
19
- import { onMount, tick, type Snippet } from "svelte";
20
-
21
- interface Props {
8
+ SearchFunctionType
9
+ } from '../models/ComboBoxItem.js';
10
+ import { ComboBoxCore } from './comboBoxCore.svelte.js';
11
+ import ComboBoxMulti from './ComboBoxMulti.svelte';
12
+ import FormGroup from './FormGroup.svelte';
13
+ import Icon from '../icon/Icon.svelte';
14
+ import Loader from '../ui/Loader.svelte';
15
+ import { useDialogEvents } from '../ui/DialogEvents.svelte.js';
16
+ import selectorSvg from '../icon/selector.svg?raw';
17
+ import { iconX, iconCopy } from '../icon/index.js';
18
+ import ActionIcon from '../ui/ActionIcon.svelte';
19
+ import { onMount, tick, type Snippet } from 'svelte';
20
+
21
+ const autoName = `select-${crypto.randomUUID()}`;
22
+
23
+ export interface SelectProps {
22
24
  /** HTML name attribute for the select (falls back to label, then auto-generated) */
23
25
  name?: string;
26
+ /** Label text displayed above the select */
24
27
  label?: string;
28
+ /** Selected value (bindable) */
25
29
  value?: string;
30
+ /** Full selected item object */
26
31
  rawValue?: ComboBoxItem;
32
+ /** Placeholder text when no value is selected */
27
33
  placeholder?: string;
34
+ /** CSS classes for the select element */
28
35
  class?: string;
36
+ /** CSS classes for the container element */
29
37
  containerClass?: string;
38
+ /** Disable the select */
30
39
  disabled?: boolean;
40
+ /** Mark field as required (shows asterisk) */
31
41
  required?: boolean;
42
+ /** Show error state styling */
32
43
  showError?: boolean;
44
+ /** Error message to display */
33
45
  errorText?: string;
34
- autofocus?: boolean; // will also cause dialog autofocus on first open only
35
- autofocusDialog?: boolean; // will cause autofocus on every open, not just the first time
46
+ /** Auto-focus on mount (first dialog open only) */
47
+ autofocus?: boolean;
48
+ /** Auto-focus on every dialog open */
49
+ autofocusDialog?: boolean;
50
+ /** Enable search/filter within options */
36
51
  allowSearch?: boolean;
37
52
  /** Hide "no results" message when search finds nothing */
38
53
  hideNoResults?: boolean;
54
+ /** Text shown while loading options */
39
55
  loadingText?: string;
56
+ /** Flat list of selectable options */
40
57
  options?: ComboBoxItem[];
58
+ /** Options organized into groups */
41
59
  groupedOptions?: ComboBoxGroup[];
42
- tooltipLocation?: "top" | "bottom" | "left" | "right";
60
+ /** Position of the tooltip */
61
+ tooltipLocation?: 'top' | 'bottom' | 'left' | 'right';
62
+ /** Async function to fetch options */
43
63
  fetchFunction?: FetchFunctionType | null;
64
+ /** Async function to search/filter options */
44
65
  searchFunction?: SearchFunctionType | null;
66
+ /** Async function to retrieve label for a value */
45
67
  retrieveLabelFunction?: RetrieveLabelFunctionType | null;
68
+ /** Rich tooltip content using a Svelte snippet */
46
69
  tooltipContent?: Snippet;
70
+ /** Simple tooltip text */
47
71
  tooltipText?: string;
72
+ /** Callback when selection changes (returns value string) */
48
73
  onchange?: (e: string | undefined) => void;
74
+ /** Callback when selection changes (returns full item object) */
49
75
  onchangeRaw?: (e: ComboBoxItem | undefined) => void;
50
76
  /** HTML autocomplete attribute for the input */
51
77
  autocomplete?: string;
@@ -53,24 +79,24 @@
53
79
 
54
80
  let {
55
81
  name,
56
- label = "",
82
+ label = '',
57
83
  value = $bindable(undefined),
58
84
  rawValue = undefined,
59
- placeholder = "\u00a0", // non-breaking space to prevent the height collapsing
60
- class: classes = "",
61
- containerClass = "",
85
+ placeholder = '\u00a0', // non-breaking space to prevent the height collapsing
86
+ class: classes = '',
87
+ containerClass = '',
62
88
  disabled = false,
63
89
  required = false,
64
90
  showError = false,
65
- errorText = "",
91
+ errorText = '',
66
92
  autofocus = false,
67
93
  autofocusDialog = false,
68
94
  allowSearch = false,
69
95
  hideNoResults = false,
70
- loadingText = "",
96
+ loadingText = '',
71
97
  options = [],
72
98
  groupedOptions = [],
73
- tooltipLocation = "top",
99
+ tooltipLocation = 'top',
74
100
  fetchFunction = null,
75
101
  searchFunction = null,
76
102
  retrieveLabelFunction = null,
@@ -78,25 +104,37 @@
78
104
  tooltipText,
79
105
  onchange,
80
106
  onchangeRaw,
81
- autocomplete = undefined,
82
- }: Props = $props();
83
-
84
- const autoName = `select-${Math.random().toString(36).substring(2, 15)}`;
85
- let resolvedName = $derived(
86
- (name || label || autoName).replace(/[^a-zA-Z0-9_\-:.]/g, '_')
87
- );
107
+ autocomplete
108
+ }: SelectProps = $props();
88
109
 
89
110
  // Use derived for computed allowSearch
90
111
  let computedAllowSearch = $derived(searchFunction ? true : allowSearch);
112
+ let resolvedName = $derived((name || label || autoName).replace(/[^a-zA-Z0-9_\-:.]/g, '_'));
91
113
 
92
114
  let internalValue = $state(value);
93
- let filterString = $state("");
94
- let preloading = $state(false);
95
- let searching = $state(false);
96
- let timeout: number;
97
-
98
- let allGroups: ComboBoxGroup[] = $state([]);
99
- let filteredGroups: ComboBoxGroup[] = $state([]);
115
+ let filterString = $state('');
116
+
117
+ const core = new ComboBoxCore({
118
+ getOptions: () => options,
119
+ getGroupedOptions: () => groupedOptions,
120
+ getValue: () => value,
121
+ setValue: (v) => (value = v),
122
+ setRawValue: (r) => (rawValue = r),
123
+ getFetchFunction: () => fetchFunction,
124
+ getSearchFunction: () => searchFunction,
125
+ getRetrieveLabelFunction: () => retrieveLabelFunction,
126
+ clearUnknownStatic: true,
127
+ onValueCleared: () => {
128
+ internalValue = undefined;
129
+ onchange?.(undefined);
130
+ onchangeRaw?.(undefined);
131
+ },
132
+ onRawValueResolved: (raw) => onchangeRaw?.(raw),
133
+ // Re-apply the active filter so a mid-search options swap doesn't drop results
134
+ onOptionsChanged: () => {
135
+ if (filterString) onFilterChange(filterString);
136
+ }
137
+ });
100
138
 
101
139
  let open = $state(false);
102
140
 
@@ -126,7 +164,7 @@
126
164
  focus();
127
165
  }, 150);
128
166
  }
129
- },
167
+ }
130
168
  });
131
169
 
132
170
  function focus() {
@@ -137,155 +175,48 @@
137
175
  $effect(() => {
138
176
  if (value !== internalValue) {
139
177
  internalValue = value;
140
- rawValue =
141
- allGroups
142
- .flatMap((x) => x.items)
143
- .find((x) => x.value === value) ?? undefined;
144
- isEmpty = rawValue === undefined;
145
- onchangeRaw?.(rawValue);
146
- preloadValues();
147
- }
148
- });
149
-
150
- function preloadValues() {
151
- if (!value) {
152
- rawValue = undefined;
153
- return;
154
- }
155
-
156
- const foundOption = allGroups
157
- .flatMap((x) => x.items)
158
- .find((x) => x.value === value);
159
-
160
- if (foundOption !== undefined) {
161
- rawValue = foundOption;
162
- } else if (searchFunction && retrieveLabelFunction) {
163
- preloading = true;
164
- retrieveLabelFunction(value).then((response) => {
165
- rawValue = {
166
- label: response.object ?? value!.toString(),
167
- value: value!,
168
- };
169
-
170
- preloading = false;
171
- });
172
- } else {
173
- rawValue = { label: value.toString(), value };
174
- }
175
- }
176
-
177
- function initOptions(opts: ComboBoxItem[]) {
178
- if (fetchFunction) {
179
- preloading = true;
180
- fetchFunction().then((response: NotifyModel<ComboBoxItem[]>) => {
181
- convertOptions(response.object!);
182
- preloadValues();
183
- preloading = false;
184
- });
185
- } else {
186
- if (groupedOptions.length) {
187
- convertGroupOptions(groupedOptions);
188
- } else if (opts.length) {
189
- convertOptions(opts);
190
- } else {
191
- filteredGroups = [];
178
+ const selfCleared = core.preloadValues();
179
+ // preloadValues fires its own callbacks on the clear path; don't double-notify
180
+ if (!selfCleared) {
181
+ onchangeRaw?.(rawValue);
192
182
  }
193
- preloadValues();
194
183
  }
195
- }
196
-
197
- onMount(() => {
198
- initOptions(options);
199
184
  });
200
185
 
201
- function convertOptions(options: ComboBoxItem[]) {
202
- const allOptions = options.map((option) => {
203
- return {
204
- ...option,
205
- selected: value === option.value,
206
- };
207
- });
208
- allGroups = [{ label: "", items: [...allOptions] }];
209
- filteredGroups = [{ label: "", items: [...allOptions] }];
210
- }
211
-
212
- function convertGroupOptions(groups: ComboBoxGroup[]) {
213
- allGroups = groups.map((group) => {
214
- return {
215
- ...group,
216
- items: group.items.map((option) => {
217
- return {
218
- ...option,
219
- selected: value === option.value,
220
- groupName: group.label,
221
- };
222
- }),
223
- };
224
- });
225
- filteredGroups = [...allGroups];
226
- }
227
-
228
186
  function onSelection(selectedValue: ComboBoxItem) {
229
- options.forEach((option) => {
230
- if (option.value === selectedValue.value)
231
- option.selected = selectedValue.selected;
187
+ core.allGroups.forEach((group) => {
188
+ group.items.forEach((item) => {
189
+ item.selected = item.value === selectedValue.value;
190
+ });
232
191
  });
233
192
 
234
193
  value = selectedValue.value;
194
+ internalValue = value;
235
195
  rawValue = selectedValue;
236
196
 
237
- onchange?.(value ? value : "");
197
+ onchange?.(value);
238
198
  onchangeRaw?.(rawValue);
239
199
 
240
200
  open = false;
241
201
  }
242
202
 
243
- function onFilterChange(value: string) {
244
- if (value.length === 0) {
245
- filteredGroups = allGroups;
246
- return;
247
- }
248
- if (searchFunction && value) {
249
- clearTimeout(timeout);
250
- searching = true;
251
- timeout = window.setTimeout(() => {
252
- searchFunction(value).then(
253
- (response: NotifyModel<ComboBoxItem[]>) => {
254
- convertOptions(response.object!);
255
- searching = false;
256
- open = true;
257
- },
258
- );
259
- }, 300);
260
- } else if (allGroups) {
261
- filteredGroups = allGroups.filter((group) =>
262
- group.items.some((x) =>
263
- x.label.toLowerCase().includes(value.toLowerCase()),
264
- ),
265
- );
266
-
267
- if (filteredGroups.length > 0) {
268
- filteredGroups = filteredGroups.map((group) => ({
269
- ...group,
270
- items: group.items.filter((x) =>
271
- x.label.toLowerCase().includes(value.toLowerCase()),
272
- ),
273
- }));
274
- }
203
+ function onFilterChange(filterValue: string) {
204
+ if (searchFunction && filterValue) {
205
+ core.search(filterValue, () => (open = true));
206
+ } else {
207
+ core.filterLocal(filterValue);
275
208
  }
276
209
  }
277
210
 
278
- let comboBoxEl: ReturnType<typeof ComboBoxMulti> | undefined =
279
- $state(undefined);
211
+ let comboBoxEl: ReturnType<typeof ComboBoxMulti> | undefined = $state(undefined);
280
212
  let textboxElement: HTMLElement | undefined = $state(undefined);
281
213
  let buttonElement: HTMLElement | undefined = $state(undefined);
282
- let comboBoxKeyDown: ((e: KeyboardEvent) => void) | undefined =
283
- $state(undefined);
214
+ let comboBoxKeyDown: ((e: KeyboardEvent) => void) | undefined = $state(undefined);
284
215
 
285
216
  function onKeyDown(e: KeyboardEvent) {
286
217
  if (!open) return;
287
218
 
288
- if (e.key === "Escape") {
219
+ if (e.key === 'Escape') {
289
220
  e.preventDefault();
290
221
  open = false;
291
222
  } else if (comboBoxKeyDown) {
@@ -296,10 +227,8 @@
296
227
  }
297
228
 
298
229
  function closePopover(e: MouseEvent) {
299
- if (textboxElement && textboxElement.contains(e.target as HTMLElement))
300
- return;
301
- if (buttonElement && buttonElement.contains(e.target as HTMLElement))
302
- return;
230
+ if (textboxElement && textboxElement.contains(e.target as HTMLElement)) return;
231
+ if (buttonElement && buttonElement.contains(e.target as HTMLElement)) return;
303
232
 
304
233
  open = false;
305
234
  }
@@ -307,31 +236,25 @@
307
236
  function copyCurrentValue() {
308
237
  if (rawValue?.label) {
309
238
  navigator.clipboard.writeText(rawValue.label).catch((err) => {
310
- console.error("Failed to copy text: ", err);
239
+ console.error('Failed to copy text: ', err);
311
240
  });
312
241
  }
313
242
  }
314
243
 
315
244
  const extraClasses: string[] = $derived.by(() => {
316
245
  const classes = [];
317
- if (open) classes.push("!border-accent");
318
- if (disabled) classes.push("disabled");
319
- if (showError) classes.push("show-error");
320
- if (computedAllowSearch) classes.push("searchable");
321
- if (open && !isEmpty) classes.push("has-actions");
246
+ if (open) classes.push('!border-accent');
247
+ if (disabled) classes.push('disabled');
248
+ if (showError) classes.push('show-error');
249
+ if (computedAllowSearch) classes.push('searchable');
250
+ if (open && !isEmpty) classes.push('has-actions');
322
251
 
323
252
  return classes;
324
253
  });
325
254
  </script>
326
255
 
327
256
  <div class="select-container {containerClass}">
328
- <FormGroup
329
- {label}
330
- {required}
331
- {tooltipContent}
332
- {tooltipText}
333
- {tooltipLocation}
334
- >
257
+ <FormGroup {label} {required} {tooltipContent} {tooltipText} {tooltipLocation}>
335
258
  {#if computedAllowSearch && open}
336
259
  <div class="select-input-wrapper">
337
260
  <input
@@ -340,13 +263,13 @@
340
263
  oninput={() => onFilterChange(filterString)}
341
264
  onkeydown={onKeyDown}
342
265
  name={resolvedName}
343
- autocomplete={(autocomplete ?? 'off') as AutoFill}
344
- disabled={preloading || disabled}
345
- placeholder={isEmpty ? placeholder : rawValue?.label || ""}
266
+ autocomplete={(autocomplete as any) ?? 'off'}
267
+ disabled={core.preloading || disabled}
268
+ placeholder={isEmpty ? placeholder : rawValue?.label || ''}
346
269
  class="select select-input {extraClasses} {classes}"
347
270
  type="search"
348
271
  />
349
- {#if preloading || searching}
272
+ {#if core.preloading || core.searching}
350
273
  <div class="loader">
351
274
  <Loader sizeOverride="1.125rem" />
352
275
  </div>
@@ -366,18 +289,16 @@
366
289
  svg={iconX}
367
290
  size="0.75rem"
368
291
  onclick={(e: MouseEvent) => {
292
+ internalValue = undefined;
369
293
  value = undefined;
370
294
  rawValue = undefined;
371
- filterString = "";
372
- filteredGroups = allGroups;
295
+ filterString = '';
296
+ core.filteredGroups = core.allGroups;
373
297
 
374
298
  onchange?.(undefined);
375
299
  onchangeRaw?.(undefined);
376
300
 
377
- options.forEach((x) => (x.selected = false));
378
- allGroups
379
- .flatMap((x) => x.items)
380
- .forEach((x) => (x.selected = false));
301
+ core.allGroups.flatMap((x) => x.items).forEach((x) => (x.selected = false));
381
302
 
382
303
  e.stopPropagation();
383
304
  }}
@@ -399,25 +320,21 @@
399
320
  }
400
321
  }}
401
322
  onkeydown={(e) => onKeyDown(e)}
402
- disabled={preloading || disabled}
323
+ name={resolvedName}
324
+ disabled={core.preloading || disabled}
403
325
  class="select {extraClasses} {classes}"
404
326
  >
405
327
  {#if isEmpty}
406
- <span class="placeholder"
407
- >{preloading ? "Loading..." : placeholder}</span
408
- >
328
+ <span class="placeholder">{core.preloading ? 'Loading...' : placeholder}</span>
409
329
  {:else if rawValue?.selectedComponent}
410
330
  {@const DisplayComponent = rawValue.selectedComponent}
411
- <DisplayComponent
412
- option={rawValue}
413
- {...rawValue.selectedComponentProps}
414
- />
331
+ <DisplayComponent option={rawValue} {...rawValue.selectedComponentProps} />
415
332
  {:else}
416
333
  <span>
417
334
  {rawValue?.label}
418
335
  </span>
419
336
  {/if}
420
- {#if preloading || searching}
337
+ {#if core.preloading || core.searching}
421
338
  <div class="loader">
422
339
  <Loader sizeOverride="1.125rem" />
423
340
  </div>
@@ -437,16 +354,14 @@
437
354
  svg={iconX}
438
355
  size="0.75rem"
439
356
  onclick={(e: MouseEvent) => {
357
+ internalValue = undefined;
440
358
  value = undefined;
441
359
  rawValue = undefined;
442
360
 
443
361
  onchange?.(undefined);
444
362
  onchangeRaw?.(undefined);
445
363
 
446
- options.forEach((x) => (x.selected = false));
447
- allGroups
448
- .flatMap((x) => x.items)
449
- .forEach((x) => (x.selected = false));
364
+ core.allGroups.flatMap((x) => x.items).forEach((x) => (x.selected = false));
450
365
 
451
366
  e.stopPropagation();
452
367
  }}
@@ -472,10 +387,10 @@
472
387
  values={rawValue ? [rawValue] : []}
473
388
  {onSelection}
474
389
  onkeydown={comboBoxKeyDown}
475
- groupedOptions={filteredGroups}
390
+ groupedOptions={core.filteredGroups}
476
391
  {open}
477
- loading={searching}
478
- showNoResultsMessage={!hideNoResults}
392
+ loading={core.searching}
393
+ {hideNoResults}
479
394
  {loadingText}
480
395
  />
481
396
  </div>
@@ -491,26 +406,26 @@
491
406
  }
492
407
 
493
408
  .select {
494
- font-size: var(--pui-font-size-md);
495
- line-height: var(--pui-line-height-normal);
409
+ font-size: 1rem;
410
+ line-height: 1.5rem;
496
411
  position: relative;
497
412
  display: flex;
498
413
  flex-wrap: wrap;
499
- padding: var(--pui-spacing-1) var(--pui-spacing-2);
500
- border-radius: var(--pui-radius-base);
414
+ padding: 0.25rem 0.5rem;
415
+ border-radius: 0.25rem;
501
416
  text-align: left;
502
- background-color: var(--pui-input-bg);
503
- color: var(--pui-text-primary);
504
- border-color: var(--pui-border-default);
417
+ background-color: var(--input-bg-color);
418
+ color: var(--text-color);
419
+ border-color: var(--border-color);
505
420
  cursor: pointer;
506
421
  width: 100%;
507
422
  align-items: center;
508
- border: 1px solid var(--pui-border-default);
509
- padding-right: var(--pui-spacing-8);
423
+ border: 1px solid var(--border-color);
424
+ padding-right: 2rem;
510
425
  }
511
426
 
512
427
  .select.has-actions {
513
- padding-right: var(--pui-spacing-14);
428
+ padding-right: 3.5rem;
514
429
  }
515
430
 
516
431
  .select.searchable {
@@ -518,9 +433,9 @@
518
433
  }
519
434
 
520
435
  .select:focus {
521
- border-color: var(--pui-input-border-focus);
436
+ border-color: var(--accent-color);
522
437
  outline: 2px solid transparent;
523
- outline-offset: var(--pui-focus-ring-offset);
438
+ outline-offset: 2px;
524
439
  }
525
440
 
526
441
  .select-input-wrapper {
@@ -534,10 +449,10 @@
534
449
 
535
450
  .select-icon {
536
451
  position: absolute;
537
- right: var(--pui-spacing-2);
452
+ right: 0.5rem;
538
453
  top: 50%;
539
454
  transform: translateY(-50%);
540
- width: var(--pui-spacing-4);
455
+ width: 1rem;
541
456
  display: flex;
542
457
  pointer-events: none;
543
458
  }
@@ -545,16 +460,16 @@
545
460
  .btn-clear-container {
546
461
  position: absolute;
547
462
  line-height: 0;
548
- right: var(--pui-spacing-2);
463
+ right: 0.5rem;
549
464
  top: 50%;
550
465
  transform: translateY(-50%);
551
466
  display: flex;
552
- gap: var(--pui-spacing-1);
467
+ gap: 0.25rem;
553
468
  }
554
469
 
555
470
  .select-btn-clear {
556
- border-radius: var(--pui-radius-base);
557
- padding: var(--pui-spacing-1);
471
+ border-radius: 0.25rem;
472
+ padding: 0.25rem;
558
473
  right: 0.4rem;
559
474
  }
560
475
 
@@ -562,9 +477,10 @@
562
477
  position: absolute;
563
478
  width: 100%;
564
479
  left: 0;
565
- z-index: var(--pui-z-dropdown);
480
+ z-index: 70;
566
481
  border-top-left-radius: 0;
567
482
  border-top-right-radius: 0;
483
+ /* absolute w-full left-0 z-20 */
568
484
  }
569
485
 
570
486
  .select-panel-inner {
@@ -573,7 +489,8 @@
573
489
  }
574
490
 
575
491
  .placeholder {
576
- color: var(--pui-text-placeholder);
492
+ color: var(--placeholder-color);
493
+ /* text-zinc-400 dark:text-neutral-500 */
577
494
  }
578
495
 
579
496
  .hidden {
@@ -583,22 +500,22 @@
583
500
  .disabled {
584
501
  opacity: 0.5;
585
502
  cursor: not-allowed;
586
- background-color: var(--pui-bg-disabled);
503
+ background-color: var(--border-color);
587
504
  }
588
505
 
589
506
  .loader {
590
507
  position: absolute;
591
- right: var(--pui-spacing-2_5);
508
+ right: 0.65rem;
592
509
  top: 50%;
593
510
  transform: translateY(-50%);
594
- width: var(--pui-spacing-4);
511
+ width: 1rem;
595
512
  }
596
513
 
597
514
  .show-error {
598
- border-color: var(--pui-text-danger);
515
+ border-color: var(--danger-text);
599
516
  }
600
517
 
601
518
  .text-error {
602
- color: var(--pui-text-danger);
519
+ color: var(--danger-text);
603
520
  }
604
521
  </style>