@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,106 +1,110 @@
1
- <script lang="ts">
1
+ <script module lang="ts">
2
2
  import type { ComboBoxItem } from '../models/ComboBoxItem.js';
3
- import ActionIcon from '../ui/ActionIcon.svelte';
4
- import Button from '../ui/Button.svelte';
5
- import Checkbox from './Checkbox.svelte';
6
- import { iconPlus, iconTrash } from '../icon/index.js';
7
- import FormGroup from './FormGroup.svelte';
8
3
 
9
- interface Props {
4
+ export interface ComboBoxItemBuilderProps {
5
+ /** Label text displayed above the builder */
10
6
  label?: string;
7
+ /** Array of combo box items (bindable) */
11
8
  items?: ComboBoxItem[];
9
+ /** Array of default selected values (bindable) */
12
10
  defaultValues?: string[];
11
+ /** Header text for the label column */
12
+ labelHeader?: string;
13
+ /** Header text for the value column */
14
+ valueHeader?: string;
15
+ /** Placeholder for label input field (defaults to labelHeader) */
13
16
  labelPlaceholder?: string;
17
+ /** Placeholder for value input field (defaults to valueHeader) */
14
18
  valuePlaceholder?: string;
19
+ /** Hide the "Selected" column and skip default-value tracking */
20
+ hideSelected?: boolean;
21
+ /** Disable auto-copying the label into the value field */
22
+ disableValueSync?: boolean;
23
+ /** Allow multiple items to share the same value. Note: default-value tracking is keyed by value, so items sharing a value also share their selected state */
24
+ allowDuplicateValues?: boolean;
25
+ /** Additional CSS classes */
15
26
  class?: string;
27
+ /** Callback when items or default values change */
16
28
  onchange?: (items: ComboBoxItem[], defaultValues: string[]) => void;
17
29
  }
30
+ </script>
31
+
32
+ <script lang="ts">
33
+ import { tick } from 'svelte';
34
+ import ActionIcon from '../ui/ActionIcon.svelte';
35
+ import Checkbox from './Checkbox.svelte';
36
+ import { iconTrash } from '../icon/index.js';
37
+ import FormGroup from './FormGroup.svelte';
18
38
 
19
39
  let {
20
40
  label = '',
21
41
  items = $bindable([]),
22
42
  defaultValues = $bindable([]),
23
- labelPlaceholder = 'Label',
24
- valuePlaceholder = 'Value',
43
+ labelHeader = 'Label',
44
+ valueHeader = 'Value',
45
+ labelPlaceholder = undefined,
46
+ valuePlaceholder = undefined,
47
+ hideSelected = false,
48
+ disableValueSync = false,
49
+ allowDuplicateValues = false,
25
50
  class: classes = '',
26
51
  onchange
27
- }: Props = $props();
52
+ }: ComboBoxItemBuilderProps = $props();
28
53
 
29
- let newLabel = $state('');
30
- let newValue = $state('');
31
- let valueManuallyEdited = $state(false);
32
- let labelInputEl: HTMLInputElement | undefined = $state(undefined);
54
+ let effectiveLabelPlaceholder = $derived(labelPlaceholder ?? labelHeader);
55
+ let effectiveValuePlaceholder = $derived(valuePlaceholder ?? valueHeader);
33
56
 
34
- // Copy label exactly for default value
35
- function labelToValue(label: string): string {
36
- return label.trim();
37
- }
57
+ let labelInputEl: HTMLInputElement | undefined = $state(undefined);
58
+ let itemLabelInputs: HTMLInputElement[] = $state([]);
59
+ let itemValueInputs: HTMLInputElement[] = $state([]);
60
+
61
+ // Typing in the empty bottom row immediately creates a real item and moves focus into it
62
+ async function handleNewInput(
63
+ field: 'label' | 'value',
64
+ e: Event & { currentTarget: EventTarget & HTMLInputElement }
65
+ ) {
66
+ const text = e.currentTarget.value;
67
+ e.currentTarget.value = '';
68
+ if (!text) return;
69
+
70
+ const newItem: ComboBoxItem =
71
+ field === 'label'
72
+ ? { label: text, value: disableValueSync ? '' : text }
73
+ : { label: '', value: text };
38
74
 
39
- // Handle label input - auto-sync to value if not manually edited
40
- function handleLabelInput(value: string) {
41
- newLabel = value;
42
- if (!valueManuallyEdited) {
43
- newValue = labelToValue(value);
44
- }
45
- }
75
+ items = [...items, newItem];
76
+ notifyChange();
46
77
 
47
- // Handle value input - mark as manually edited
48
- function handleValueInput(value: string) {
49
- newValue = value;
50
- if (value !== labelToValue(newLabel)) {
51
- valueManuallyEdited = true;
52
- }
78
+ await tick();
79
+ const input =
80
+ field === 'label' ? itemLabelInputs[items.length - 1] : itemValueInputs[items.length - 1];
81
+ input?.focus();
82
+ input?.setSelectionRange(text.length, text.length);
53
83
  }
54
84
 
55
85
  // Check if a value already exists (excluding a specific index for editing)
56
- function valueExists(value: string, excludeIndex?: number): boolean {
86
+ function valueExists(value: string, excludeIndex: number | undefined = undefined): boolean {
57
87
  return items.some((item, i) =>
58
88
  item.value === value && i !== excludeIndex
59
89
  );
60
90
  }
61
91
 
62
92
  // Check if a label already exists (excluding a specific index for editing)
63
- function labelExists(label: string, excludeIndex?: number): boolean {
93
+ function labelExists(label: string, excludeIndex: number | undefined = undefined): boolean {
64
94
  return items.some((item, i) =>
65
95
  item.label === label && i !== excludeIndex
66
96
  );
67
97
  }
68
98
 
69
- // Check if new value/label would be a duplicate
70
- let isDuplicateNewValue = $derived(
71
- newValue.trim() !== '' && valueExists(newValue.trim())
72
- );
73
-
74
- let isDuplicateNewLabel = $derived(
75
- newLabel.trim() !== '' && labelExists(newLabel.trim())
76
- );
77
-
78
- function addItem() {
79
- if (!newLabel.trim() || !newValue.trim()) return;
80
- if (isDuplicateNewValue || isDuplicateNewLabel) return;
81
-
82
- const newItem: ComboBoxItem = {
83
- label: newLabel.trim(),
84
- value: newValue.trim()
85
- };
86
-
87
- items = [...items, newItem];
88
- newLabel = '';
89
- newValue = '';
90
- valueManuallyEdited = false;
91
-
92
- notifyChange();
93
-
94
- // Focus back on label input for quick entry of next item
95
- labelInputEl?.focus();
96
- }
97
-
98
99
  function removeItem(index: number) {
99
100
  const removedItem = items[index];
100
101
  items = items.filter((_, i) => i !== index);
101
102
 
102
- // Remove from defaults if it was selected
103
- if (defaultValues.includes(removedItem.value)) {
103
+ // Remove from defaults if it was selected and no remaining item shares the value
104
+ if (
105
+ defaultValues.includes(removedItem.value) &&
106
+ !items.some(item => item.value === removedItem.value)
107
+ ) {
104
108
  defaultValues = defaultValues.filter(v => v !== removedItem.value);
105
109
  }
106
110
 
@@ -123,17 +127,20 @@
123
127
  const currentItem = items[index];
124
128
 
125
129
  // Check if label and value were in sync (exact case-sensitive match)
126
- const wasInSync = currentItem.label === currentItem.value;
130
+ const wasInSync = !disableValueSync && currentItem.label === currentItem.value;
127
131
 
128
132
  // If they were in sync, also update the value to maintain exact match (if it wouldn't create a duplicate)
129
- if (wasInSync && !valueExists(label, index)) {
133
+ if (wasInSync && (allowDuplicateValues || !valueExists(label, index))) {
130
134
  const oldValue = currentItem.value;
131
135
  items = items.map((item, i) =>
132
136
  i === index ? { ...item, label, value: label } : item
133
137
  );
134
138
 
135
- // Update defaults if the value was changed
136
- if (defaultValues.includes(oldValue)) {
139
+ // Update defaults if the value was changed and no remaining item shares the old value
140
+ if (
141
+ defaultValues.includes(oldValue) &&
142
+ !items.some(item => item.value === oldValue)
143
+ ) {
137
144
  defaultValues = defaultValues.map(v => v === oldValue ? label : v);
138
145
  }
139
146
  } else {
@@ -147,15 +154,18 @@
147
154
 
148
155
  function updateItemValue(index: number, newVal: string) {
149
156
  // Don't allow duplicate values
150
- if (valueExists(newVal, index)) return;
157
+ if (!allowDuplicateValues && valueExists(newVal, index)) return;
151
158
 
152
159
  const oldValue = items[index].value;
153
160
  items = items.map((item, i) =>
154
161
  i === index ? { ...item, value: newVal } : item
155
162
  );
156
163
 
157
- // Update defaults if the value was changed
158
- if (defaultValues.includes(oldValue)) {
164
+ // Update defaults if the value was changed and no remaining item shares the old value
165
+ if (
166
+ defaultValues.includes(oldValue) &&
167
+ !items.some(item => item.value === oldValue)
168
+ ) {
159
169
  defaultValues = defaultValues.map(v => v === oldValue ? newVal : v);
160
170
  }
161
171
 
@@ -166,10 +176,11 @@
166
176
  onchange?.(items, defaultValues);
167
177
  }
168
178
 
169
- function handleKeyDown(e: KeyboardEvent) {
179
+ // Enter in an item row jumps back to the empty row for quick entry of the next item
180
+ function handleItemKeyDown(e: KeyboardEvent) {
170
181
  if (e.key === 'Enter') {
171
182
  e.preventDefault();
172
- addItem();
183
+ labelInputEl?.focus();
173
184
  }
174
185
  }
175
186
 
@@ -196,40 +207,48 @@
196
207
  </FormGroup>
197
208
  {/if}
198
209
 
199
- <div class="builder-content">
210
+ <div class="builder-content" class:no-selected={hideSelected}>
200
211
  <!-- Header row -->
201
212
  <div class="header-row">
202
- <div class="col-default">Selected</div>
203
- <div class="col-label">Label</div>
204
- <div class="col-value">Value</div>
213
+ {#if !hideSelected}
214
+ <div class="col-default">Selected</div>
215
+ {/if}
216
+ <div class="col-label">{labelHeader}</div>
217
+ <div class="col-value">{valueHeader}</div>
205
218
  <div class="col-actions"></div>
206
219
  </div>
207
220
 
208
221
  <!-- Existing items -->
209
222
  {#each items as item, index (index)}
210
223
  <div class="item-row">
211
- <div class="col-default">
212
- <Checkbox
213
- checked={defaultValues.includes(item.value)}
214
- onchange={(checked) => toggleDefault(item.value, checked)}
215
- />
216
- </div>
224
+ {#if !hideSelected}
225
+ <div class="col-default">
226
+ <Checkbox
227
+ checked={defaultValues.includes(item.value)}
228
+ onchange={(checked) => toggleDefault(item.value, checked)}
229
+ />
230
+ </div>
231
+ {/if}
217
232
  <div class="col-label">
218
233
  <input
234
+ bind:this={itemLabelInputs[index]}
219
235
  type="text"
220
236
  class="item-input"
221
237
  value={item.label}
222
238
  oninput={(e) => updateItemLabel(index, e.currentTarget.value)}
223
- placeholder={labelPlaceholder}
239
+ onkeydown={handleItemKeyDown}
240
+ placeholder={effectiveLabelPlaceholder}
224
241
  />
225
242
  </div>
226
243
  <div class="col-value">
227
244
  <input
245
+ bind:this={itemValueInputs[index]}
228
246
  type="text"
229
247
  class="item-input"
230
248
  value={item.value}
231
249
  oninput={(e) => updateItemValue(index, e.currentTarget.value)}
232
- placeholder={valuePlaceholder}
250
+ onkeydown={handleItemKeyDown}
251
+ placeholder={effectiveValuePlaceholder}
233
252
  />
234
253
  </div>
235
254
  <div class="col-actions">
@@ -246,69 +265,35 @@
246
265
 
247
266
  <!-- Add new item row -->
248
267
  <div class="add-row">
249
- <div class="col-default"></div>
268
+ {#if !hideSelected}
269
+ <div class="col-default"></div>
270
+ {/if}
250
271
  <div class="col-label">
251
272
  <input
252
273
  bind:this={labelInputEl}
253
274
  type="text"
254
275
  class="item-input new-input"
255
- class:error={isDuplicateNewLabel}
256
- value={newLabel}
257
- oninput={(e) => handleLabelInput(e.currentTarget.value)}
258
- placeholder={labelPlaceholder}
259
- onkeydown={handleKeyDown}
276
+ oninput={(e) => handleNewInput('label', e)}
277
+ placeholder={effectiveLabelPlaceholder}
260
278
  />
261
279
  </div>
262
280
  <div class="col-value">
263
281
  <input
264
282
  type="text"
265
283
  class="item-input new-input"
266
- class:error={isDuplicateNewValue}
267
- value={newValue}
268
- oninput={(e) => handleValueInput(e.currentTarget.value)}
269
- placeholder={valuePlaceholder}
270
- onkeydown={handleKeyDown}
271
- />
272
- </div>
273
- <div class="col-actions">
274
- <ActionIcon
275
- variant="auto-subtle"
276
- svg={iconPlus}
277
- size="1rem"
278
- onclick={addItem}
279
- disabled={!newLabel.trim() || !newValue.trim() || isDuplicateNewValue || isDuplicateNewLabel}
280
- tooltip="Add item"
284
+ oninput={(e) => handleNewInput('value', e)}
285
+ placeholder={effectiveValuePlaceholder}
281
286
  />
282
287
  </div>
288
+ <div class="col-actions"></div>
283
289
  </div>
284
-
285
- {#if isDuplicateNewLabel || isDuplicateNewValue}
286
- <div class="error-message">
287
- {#if isDuplicateNewLabel && isDuplicateNewValue}
288
- Label "{newLabel}" and value "{newValue}" already exist
289
- {:else if isDuplicateNewLabel}
290
- Label "{newLabel}" already exists
291
- {:else}
292
- Value "{newValue}" already exists
293
- {/if}
294
- </div>
295
- {/if}
296
-
297
- <!-- Quick add button for mobile/convenience -->
298
- {#if newLabel.trim() && newValue.trim() && !isDuplicateNewValue && !isDuplicateNewLabel}
299
- <div class="quick-add">
300
- <Button variant="auto-subtle" onclick={addItem}>
301
- Add "{newLabel}"
302
- </Button>
303
- </div>
304
- {/if}
305
290
  </div>
306
291
 
307
292
  <!-- Summary -->
308
293
  {#if items.length > 0}
309
294
  <div class="summary">
310
295
  <span class="summary-count">{items.length} item{items.length !== 1 ? 's' : ''}</span>
311
- {#if defaultValues.length > 0}
296
+ {#if !hideSelected && defaultValues.length > 0}
312
297
  <span class="summary-default">
313
298
  Selected: {defaultLabels}
314
299
  </span>
@@ -323,21 +308,27 @@
323
308
  }
324
309
 
325
310
  .builder-content {
326
- border: 1px solid var(--pui-border-default);
327
- border-radius: var(--pui-radius-md);
311
+ border: 1px solid var(--border-color);
312
+ border-radius: 0.375rem;
328
313
  overflow: hidden;
329
314
  }
330
315
 
316
+ .no-selected .header-row,
317
+ .no-selected .item-row,
318
+ .no-selected .add-row {
319
+ grid-template-columns: 1fr 1fr 2.5rem;
320
+ }
321
+
331
322
  .header-row {
332
323
  display: grid;
333
324
  grid-template-columns: 3.5rem 1fr 1fr 2.5rem;
334
- gap: var(--pui-spacing-2);
335
- padding: var(--pui-spacing-2) var(--pui-spacing-3);
336
- background-color: var(--pui-input-bg);
337
- border-bottom: 1px solid var(--pui-border-default);
338
- font-size: var(--pui-font-size-xs);
339
- font-weight: var(--pui-font-weight-semibold);
340
- color: var(--pui-text-muted);
325
+ gap: 0.5rem;
326
+ padding: 0.5rem 0.75rem;
327
+ background-color: var(--input-bg-color);
328
+ border-bottom: 1px solid var(--border-color);
329
+ font-size: 0.75rem;
330
+ font-weight: 600;
331
+ color: var(--text-muted);
341
332
  text-transform: uppercase;
342
333
  letter-spacing: 0.025em;
343
334
  }
@@ -346,18 +337,18 @@
346
337
  .add-row {
347
338
  display: grid;
348
339
  grid-template-columns: 3.5rem 1fr 1fr 2.5rem;
349
- gap: var(--pui-spacing-2);
350
- padding: var(--pui-spacing-2) var(--pui-spacing-3);
340
+ gap: 0.5rem;
341
+ padding: 0.5rem 0.75rem;
351
342
  align-items: center;
352
- border-bottom: 1px solid var(--pui-border-default);
343
+ border-bottom: 1px solid var(--border-color);
353
344
  }
354
345
 
355
346
  .item-row:last-of-type {
356
- border-bottom: 1px solid var(--pui-border-default);
347
+ border-bottom: 1px solid var(--border-color);
357
348
  }
358
349
 
359
350
  .add-row {
360
- background-color: var(--pui-bg-subtle);
351
+ background-color: rgba(var(--ui-primary-rgbc), 0.02);
361
352
  border-bottom: none;
362
353
  }
363
354
 
@@ -375,71 +366,47 @@
375
366
 
376
367
  .item-input {
377
368
  width: 100%;
378
- padding: var(--pui-spacing-1_5) var(--pui-spacing-2);
379
- border: 1px solid var(--pui-border-default);
380
- border-radius: var(--pui-radius-sm);
381
- background-color: var(--pui-input-bg);
382
- color: var(--pui-text-primary);
383
- font-size: var(--pui-font-size-sm);
384
- transition: border-color var(--pui-transition-fast) var(--pui-ease-in-out);
369
+ padding: 0.375rem 0.5rem;
370
+ border: 1px solid var(--border-color);
371
+ border-radius: 0.25rem;
372
+ background-color: var(--input-bg-color);
373
+ color: var(--text-color);
374
+ font-size: 0.875rem;
375
+ transition: border-color 0.15s;
385
376
  }
386
377
 
387
378
  .item-input:focus {
388
379
  outline: none;
389
- border-color: var(--pui-input-border-focus);
380
+ border-color: var(--accent-color);
390
381
  }
391
382
 
392
383
  .item-input::placeholder {
393
- color: var(--pui-text-placeholder);
384
+ color: var(--placeholder-color);
394
385
  }
395
386
 
396
387
  .new-input {
397
388
  background-color: transparent;
398
389
  }
399
390
 
400
- .item-input.error {
401
- border-color: var(--pui-text-danger);
402
- }
403
-
404
- .item-input.error:focus {
405
- border-color: var(--pui-text-danger);
406
- }
407
-
408
- .error-message {
409
- padding: var(--pui-spacing-1_5) var(--pui-spacing-3);
410
- font-size: var(--pui-font-size-xs);
411
- color: var(--pui-text-danger);
412
- background-color: var(--pui-bg-subtle);
413
- }
414
-
415
- .quick-add {
416
- padding: var(--pui-spacing-2) var(--pui-spacing-3);
417
- background-color: var(--pui-bg-subtle);
418
- display: flex;
419
- justify-content: flex-end;
420
- }
421
-
422
391
  .summary {
423
392
  display: flex;
424
- gap: var(--pui-spacing-4);
425
- padding: var(--pui-spacing-2) 0;
426
- font-size: var(--pui-font-size-sm);
427
- color: var(--pui-text-muted);
428
- margin-top: var(--pui-spacing-2);
393
+ gap: 1rem;
394
+ padding: 0.5rem 0;
395
+ font-size: 0.875rem;
396
+ color: var(--text-muted);
397
+ margin-top: 0.5rem;
429
398
  }
430
399
 
431
400
  .summary-count {
432
- font-weight: var(--pui-font-weight-medium);
401
+ font-weight: 500;
433
402
  }
434
403
 
435
404
  .summary-default {
436
- color: var(--pui-accent-color);
405
+ color: var(--accent-color);
437
406
  }
438
407
 
439
- :global(.dark) .add-row,
440
- :global(.dark) .quick-add,
441
- :global(.dark) .error-message {
442
- background-color: var(--pui-bg-subtle);
408
+ :global(.dark) .add-row {
409
+ background-color: rgba(var(--ui-accent-rgbc), 0.02);
443
410
  }
444
411
 
445
412
  /* Responsive adjustments */
@@ -448,13 +415,19 @@
448
415
  .item-row,
449
416
  .add-row {
450
417
  grid-template-columns: 2.5rem 1fr 1fr 2rem;
451
- gap: var(--pui-spacing-1);
452
- padding: var(--pui-spacing-1_5) var(--pui-spacing-2);
418
+ gap: 0.25rem;
419
+ padding: 0.375rem 0.5rem;
420
+ }
421
+
422
+ .no-selected .header-row,
423
+ .no-selected .item-row,
424
+ .no-selected .add-row {
425
+ grid-template-columns: 1fr 1fr 2rem;
453
426
  }
454
427
 
455
428
  .item-input {
456
- padding: var(--pui-spacing-1) var(--pui-spacing-1_5);
457
- font-size: var(--pui-font-size-sm);
429
+ padding: 0.25rem 0.375rem;
430
+ font-size: 0.8125rem;
458
431
  }
459
432
  }
460
433
  </style>
@@ -1,13 +1,30 @@
1
1
  import type { ComboBoxItem } from '../models/ComboBoxItem.js';
2
- interface Props {
2
+ export interface ComboBoxItemBuilderProps {
3
+ /** Label text displayed above the builder */
3
4
  label?: string;
5
+ /** Array of combo box items (bindable) */
4
6
  items?: ComboBoxItem[];
7
+ /** Array of default selected values (bindable) */
5
8
  defaultValues?: string[];
9
+ /** Header text for the label column */
10
+ labelHeader?: string;
11
+ /** Header text for the value column */
12
+ valueHeader?: string;
13
+ /** Placeholder for label input field (defaults to labelHeader) */
6
14
  labelPlaceholder?: string;
15
+ /** Placeholder for value input field (defaults to valueHeader) */
7
16
  valuePlaceholder?: string;
17
+ /** Hide the "Selected" column and skip default-value tracking */
18
+ hideSelected?: boolean;
19
+ /** Disable auto-copying the label into the value field */
20
+ disableValueSync?: boolean;
21
+ /** Allow multiple items to share the same value. Note: default-value tracking is keyed by value, so items sharing a value also share their selected state */
22
+ allowDuplicateValues?: boolean;
23
+ /** Additional CSS classes */
8
24
  class?: string;
25
+ /** Callback when items or default values change */
9
26
  onchange?: (items: ComboBoxItem[], defaultValues: string[]) => void;
10
27
  }
11
- declare const ComboBoxItemBuilder: import("svelte").Component<Props, {}, "items" | "defaultValues">;
28
+ declare const ComboBoxItemBuilder: import("svelte").Component<ComboBoxItemBuilderProps, {}, "items" | "defaultValues">;
12
29
  type ComboBoxItemBuilder = ReturnType<typeof ComboBoxItemBuilder>;
13
30
  export default ComboBoxItemBuilder;