@torpor/ui 0.4.0 → 1.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 (342) hide show
  1. package/README.md +83 -7
  2. package/dist/Accordion/Accordion.torp +42 -165
  3. package/dist/Accordion/AccordionContent.torp +6 -6
  4. package/dist/Accordion/AccordionHeader.torp +7 -11
  5. package/dist/Accordion/AccordionItem.torp +24 -24
  6. package/dist/Accordion/AccordionTrigger.torp +17 -8
  7. package/dist/Accordion/AccordionTypes.ts +1 -1
  8. package/dist/Accordion/index.d.ts +23 -25
  9. package/dist/Accordion/index.js +6 -22
  10. package/dist/Breadcrumb/Breadcrumb.torp +12 -10
  11. package/dist/Breadcrumb/BreadcrumbItem.torp +6 -6
  12. package/dist/Breadcrumb/BreadcrumbList.torp +21 -0
  13. package/dist/Breadcrumb/BreadcrumbSeparator.torp +4 -4
  14. package/dist/Breadcrumb/index.d.ts +39 -15
  15. package/dist/Breadcrumb/index.js +5 -14
  16. package/dist/Calendar/Calendar.torp +163 -156
  17. package/dist/Calendar/CalendarDay.torp +91 -45
  18. package/dist/Calendar/CalendarDayHeader.torp +36 -0
  19. package/dist/Calendar/CalendarGrid.torp +104 -0
  20. package/dist/Calendar/CalendarGridHeader.torp +43 -0
  21. package/dist/Calendar/CalendarHeader.torp +48 -0
  22. package/dist/Calendar/CalendarHeaderTrigger.torp +54 -0
  23. package/dist/Calendar/CalendarTypes.ts +24 -3
  24. package/dist/Calendar/buildDays.ts +54 -0
  25. package/dist/Calendar/index.d.ts +191 -5
  26. package/dist/Calendar/index.js +8 -10
  27. package/dist/Carousel/Carousel.torp +180 -0
  28. package/dist/Carousel/CarouselIndicators.torp +65 -0
  29. package/dist/Carousel/CarouselNext.torp +43 -0
  30. package/dist/Carousel/CarouselPrevious.torp +44 -0
  31. package/dist/Carousel/CarouselSlide.torp +57 -0
  32. package/dist/Carousel/CarouselTypes.ts +28 -0
  33. package/dist/Carousel/index.d.ts +147 -0
  34. package/dist/Carousel/index.js +6 -0
  35. package/dist/Charts/BarChart.torp +55 -144
  36. package/dist/Charts/Chart.torp +120 -0
  37. package/dist/Charts/ChartBars.torp +78 -0
  38. package/dist/Charts/ChartColumns.torp +66 -0
  39. package/dist/Charts/ChartGrid.torp +54 -0
  40. package/dist/Charts/ChartLines.torp +60 -0
  41. package/dist/Charts/ChartTypes.ts +25 -0
  42. package/dist/Charts/ChartXAxis.torp +83 -0
  43. package/dist/Charts/ChartYAxis.torp +105 -0
  44. package/dist/Charts/ColumnChart.torp +70 -0
  45. package/dist/Charts/GridLines.torp +4 -4
  46. package/dist/Charts/LineChart.torp +55 -178
  47. package/dist/Charts/PieChart.torp +14 -20
  48. package/dist/Charts/ScatterChart.torp +45 -38
  49. package/dist/Charts/SparkLine.torp +16 -19
  50. package/dist/Charts/XAxis.torp +11 -11
  51. package/dist/Charts/YAxis.torp +8 -8
  52. package/dist/Charts/index.d.ts +328 -79
  53. package/dist/Charts/index.js +17 -34
  54. package/dist/ColorPicker/ColorPalette.torp +154 -0
  55. package/dist/ColorPicker/ColorPicker.torp +157 -0
  56. package/dist/ColorPicker/index.d.ts +85 -0
  57. package/dist/ColorPicker/index.js +3 -0
  58. package/dist/ComboBox/ComboBox.torp +205 -0
  59. package/dist/ComboBox/ComboBoxContent.torp +112 -0
  60. package/dist/ComboBox/ComboBoxInput.torp +145 -0
  61. package/dist/ComboBox/ComboBoxTrigger.torp +49 -0
  62. package/dist/ComboBox/ComboBoxTypes.ts +39 -0
  63. package/dist/ComboBox/index.d.ts +152 -0
  64. package/dist/ComboBox/index.js +6 -0
  65. package/dist/CommandPalette/CommandPalette.torp +220 -0
  66. package/dist/CommandPalette/CommandTypes.ts +14 -0
  67. package/dist/CommandPalette/index.d.ts +45 -0
  68. package/dist/CommandPalette/index.js +2 -0
  69. package/dist/Contextual/Contextual.torp +32 -50
  70. package/dist/Contextual/ContextualContent.torp +29 -73
  71. package/dist/Contextual/ContextualHover.torp +112 -0
  72. package/dist/Contextual/ContextualTrigger.torp +31 -42
  73. package/dist/Contextual/ContextualTypes.ts +4 -17
  74. package/dist/Contextual/index.d.ts +44 -27
  75. package/dist/Contextual/index.js +5 -14
  76. package/dist/DataGrid/DataGrid.torp +496 -0
  77. package/dist/DataGrid/DataGridTypes.ts +25 -0
  78. package/dist/DataGrid/index.d.ts +90 -0
  79. package/dist/DataGrid/index.js +3 -0
  80. package/dist/DataGrid/sortRows.ts +32 -0
  81. package/dist/DatePicker/DatePicker.torp +222 -0
  82. package/dist/DatePicker/DatePickerHeader.torp +20 -0
  83. package/dist/DatePicker/index.d.ts +55 -0
  84. package/dist/DatePicker/index.js +2 -0
  85. package/dist/DateRangePicker/DateRangePicker.torp +311 -0
  86. package/dist/DateRangePicker/DateRangePickerDay.torp +137 -0
  87. package/dist/DateRangePicker/DateRangePickerTypes.ts +21 -0
  88. package/dist/DateRangePicker/index.d.ts +76 -0
  89. package/dist/DateRangePicker/index.js +3 -0
  90. package/dist/Dialog/Dialog.torp +24 -17
  91. package/dist/Dialog/DialogBody.torp +5 -9
  92. package/dist/Dialog/DialogButton.torp +12 -12
  93. package/dist/Dialog/DialogFooter.torp +3 -7
  94. package/dist/Dialog/DialogHeader.torp +6 -10
  95. package/dist/Dialog/index.d.ts +27 -27
  96. package/dist/Dialog/index.js +6 -22
  97. package/dist/Disclosure/Disclosure.torp +18 -18
  98. package/dist/Disclosure/DisclosureContent.torp +5 -6
  99. package/dist/Disclosure/DisclosureHeader.torp +7 -11
  100. package/dist/Disclosure/DisclosureTrigger.torp +4 -5
  101. package/dist/Disclosure/index.d.ts +14 -16
  102. package/dist/Disclosure/index.js +5 -18
  103. package/dist/Drawer/Drawer.torp +19 -49
  104. package/dist/Drawer/DrawerContent.torp +67 -13
  105. package/dist/Drawer/DrawerOverlay.torp +4 -4
  106. package/dist/Drawer/DrawerTrigger.torp +16 -4
  107. package/dist/Drawer/DrawerTypes.ts +3 -0
  108. package/dist/Drawer/index.d.ts +16 -20
  109. package/dist/Drawer/index.js +5 -18
  110. package/dist/FocusGroup/FocusGroup.torp +70 -0
  111. package/dist/FocusGroup/FocusGroupButton.torp +59 -0
  112. package/dist/FocusGroup/FocusGroupTypes.ts +14 -0
  113. package/dist/FocusGroup/index.d.ts +44 -0
  114. package/dist/FocusGroup/index.js +3 -0
  115. package/dist/Form/CheckBox.torp +27 -18
  116. package/dist/Form/Field.torp +17 -17
  117. package/dist/Form/File.torp +31 -22
  118. package/dist/Form/FileDrop.torp +187 -0
  119. package/dist/Form/Form.torp +24 -22
  120. package/dist/Form/FormTypes.ts +2 -0
  121. package/dist/Form/FormValues.torp +37 -0
  122. package/dist/Form/Hidden.torp +4 -12
  123. package/dist/Form/Input.torp +38 -32
  124. package/dist/Form/Label.torp +3 -3
  125. package/dist/Form/Message.torp +13 -5
  126. package/dist/Form/MessageSummary.torp +5 -4
  127. package/dist/Form/Option.torp +11 -7
  128. package/dist/Form/Select.torp +32 -28
  129. package/dist/Form/TextArea.torp +35 -31
  130. package/dist/Form/Toggle.torp +109 -0
  131. package/dist/Form/index.d.ts +178 -79
  132. package/dist/Form/index.js +16 -50
  133. package/dist/ListBox/ListBox.torp +253 -150
  134. package/dist/ListBox/ListBoxItem.torp +54 -30
  135. package/dist/ListBox/ListBoxSeparator.torp +5 -2
  136. package/dist/ListBox/ListBoxTypes.ts +23 -5
  137. package/dist/ListBox/LoadedListBoxItems.torp +41 -0
  138. package/dist/ListBox/index.d.ts +56 -17
  139. package/dist/ListBox/index.js +4 -14
  140. package/dist/MaskedInput/MaskedInput.torp +143 -0
  141. package/dist/MaskedInput/index.d.ts +52 -0
  142. package/dist/MaskedInput/index.js +2 -0
  143. package/dist/Menu/Menu.torp +86 -71
  144. package/dist/Menu/MenuButton.torp +17 -10
  145. package/dist/Menu/MenuCheck.torp +22 -18
  146. package/dist/Menu/MenuGroup.torp +4 -4
  147. package/dist/Menu/MenuIndicator.torp +5 -13
  148. package/dist/Menu/MenuPopout.torp +20 -50
  149. package/dist/Menu/MenuPopoutContent.torp +15 -66
  150. package/dist/Menu/MenuPopoutTrigger.torp +19 -100
  151. package/dist/Menu/MenuRadio.torp +25 -26
  152. package/dist/Menu/MenuRadioGroup.torp +25 -68
  153. package/dist/Menu/MenuSeparator.torp +3 -2
  154. package/dist/Menu/MenuTypes.ts +9 -19
  155. package/dist/Menu/index.d.ts +45 -51
  156. package/dist/Menu/index.js +12 -46
  157. package/dist/MenuBar/MenuBar.torp +31 -83
  158. package/dist/MenuBar/MenuBarItem.torp +33 -51
  159. package/dist/MenuBar/MenuBarItemContent.torp +15 -101
  160. package/dist/MenuBar/MenuBarItemTrigger.torp +20 -55
  161. package/dist/MenuBar/MenuBarTypes.ts +11 -10
  162. package/dist/MenuBar/index.d.ts +17 -19
  163. package/dist/MenuBar/index.js +5 -18
  164. package/dist/Modal/Modal.torp +22 -47
  165. package/dist/Modal/ModalContent.torp +21 -44
  166. package/dist/Modal/ModalOverlay.torp +4 -4
  167. package/dist/Modal/ModalTrigger.torp +16 -32
  168. package/dist/Modal/ModalTypes.ts +5 -16
  169. package/dist/Modal/index.d.ts +17 -29
  170. package/dist/Modal/index.js +29 -28
  171. package/dist/Modal/index.js.map +1 -1
  172. package/dist/Modal/showPrompt.ts +2 -2
  173. package/dist/NavMenu/NavMenu.torp +22 -111
  174. package/dist/NavMenu/NavMenuButton.torp +7 -8
  175. package/dist/NavMenu/NavMenuElement.torp +6 -7
  176. package/dist/NavMenu/NavMenuGroup.torp +5 -5
  177. package/dist/NavMenu/NavMenuGroupHeader.torp +5 -8
  178. package/dist/NavMenu/NavMenuLink.torp +7 -10
  179. package/dist/NavMenu/NavMenuPopout.torp +15 -60
  180. package/dist/NavMenu/NavMenuPopoutContent.torp +14 -73
  181. package/dist/NavMenu/NavMenuPopoutTrigger.torp +17 -99
  182. package/dist/NavMenu/NavMenuSeparator.torp +3 -7
  183. package/dist/NavMenu/NavMenuTypes.ts +4 -14
  184. package/dist/NavMenu/index.d.ts +40 -44
  185. package/dist/NavMenu/index.js +11 -42
  186. package/dist/Notification/Notification.torp +12 -24
  187. package/dist/Notification/NotificationClose.torp +6 -3
  188. package/dist/Notification/NotificationContainer.torp +3 -7
  189. package/dist/Notification/NotificationContent.torp +3 -7
  190. package/dist/Notification/NotificationTypes.ts +1 -1
  191. package/dist/Notification/index.d.ts +15 -12
  192. package/dist/Notification/index.js +22 -32
  193. package/dist/Notification/index.js.map +1 -1
  194. package/dist/NumberInput/NumberInput.torp +255 -0
  195. package/dist/NumberInput/index.d.ts +44 -0
  196. package/dist/NumberInput/index.js +2 -0
  197. package/dist/Pagination/Pagination.torp +41 -34
  198. package/dist/Pagination/PaginationItem.torp +50 -13
  199. package/dist/Pagination/PaginationTypes.ts +3 -6
  200. package/dist/Pagination/buildPages.ts +9 -5
  201. package/dist/Pagination/index.d.ts +19 -9
  202. package/dist/Pagination/index.js +3 -40
  203. package/dist/Popover/Popover.torp +32 -50
  204. package/dist/Popover/PopoverContent.torp +27 -76
  205. package/dist/Popover/PopoverHover.torp +90 -0
  206. package/dist/Popover/PopoverTrigger.torp +17 -40
  207. package/dist/Popover/PopoverTypes.ts +4 -17
  208. package/dist/Popover/index.d.ts +40 -25
  209. package/dist/Popover/index.js +18 -18
  210. package/dist/Popover/index.js.map +1 -1
  211. package/dist/Progress/Progress.torp +88 -0
  212. package/dist/Progress/index.d.ts +37 -0
  213. package/dist/Progress/index.js +2 -0
  214. package/dist/Rating/Rating.torp +225 -0
  215. package/dist/Rating/index.d.ts +44 -0
  216. package/dist/Rating/index.js +2 -0
  217. package/dist/SegmentedControl/SegmentedControl.torp +233 -0
  218. package/dist/SegmentedControl/SegmentedControlTypes.ts +26 -0
  219. package/dist/SegmentedControl/SegmentedItem.torp +78 -0
  220. package/dist/SegmentedControl/index.d.ts +74 -0
  221. package/dist/SegmentedControl/index.js +3 -0
  222. package/dist/SelectBox/SelectBox.torp +107 -53
  223. package/dist/SelectBox/SelectBoxContent.torp +45 -19
  224. package/dist/SelectBox/SelectBoxTrigger.torp +108 -8
  225. package/dist/SelectBox/SelectBoxTypes.ts +26 -11
  226. package/dist/SelectBox/index.d.ts +39 -54
  227. package/dist/SelectBox/index.js +5 -18
  228. package/dist/Slider/Slider.torp +248 -0
  229. package/dist/Slider/index.d.ts +45 -0
  230. package/dist/Slider/index.js +2 -0
  231. package/dist/Splitter/Splitter.torp +156 -0
  232. package/dist/Splitter/SplitterHandle.torp +161 -0
  233. package/dist/Splitter/SplitterPane.torp +63 -0
  234. package/dist/Splitter/SplitterTypes.ts +31 -0
  235. package/dist/Splitter/index.d.ts +103 -0
  236. package/dist/Splitter/index.js +4 -0
  237. package/dist/Stepper/Stepper.torp +146 -0
  238. package/dist/Stepper/StepperMarker.torp +24 -0
  239. package/dist/Stepper/StepperNext.torp +44 -0
  240. package/dist/Stepper/StepperPrevious.torp +44 -0
  241. package/dist/Stepper/StepperStep.torp +90 -0
  242. package/dist/Stepper/StepperTypes.ts +30 -0
  243. package/dist/Stepper/index.d.ts +149 -0
  244. package/dist/Stepper/index.js +6 -0
  245. package/dist/TabGroup/TabContent.torp +6 -6
  246. package/dist/TabGroup/TabGroup.torp +97 -113
  247. package/dist/TabGroup/TabGroupTypes.ts +1 -1
  248. package/dist/TabGroup/TabHeader.torp +23 -19
  249. package/dist/TabGroup/TabList.torp +8 -21
  250. package/dist/TabGroup/TabTrigger.torp +6 -7
  251. package/dist/TabGroup/index.d.ts +26 -26
  252. package/dist/TabGroup/index.js +6 -22
  253. package/dist/TagInput/TagInput.torp +378 -0
  254. package/dist/TagInput/index.d.ts +48 -0
  255. package/dist/TagInput/index.js +2 -0
  256. package/dist/TimePicker/TimePicker.torp +462 -0
  257. package/dist/TimePicker/index.d.ts +53 -0
  258. package/dist/TimePicker/index.js +2 -0
  259. package/dist/ToolBar/ToolBar.torp +29 -110
  260. package/dist/ToolBar/ToolBarButton.torp +7 -8
  261. package/dist/ToolBar/ToolBarElement.torp +7 -11
  262. package/dist/ToolBar/ToolBarPopout.torp +16 -68
  263. package/dist/ToolBar/ToolBarPopoutContent.torp +15 -73
  264. package/dist/ToolBar/ToolBarPopoutTrigger.torp +19 -110
  265. package/dist/ToolBar/ToolBarSeparator.torp +3 -7
  266. package/dist/ToolBar/ToolBarTypes.ts +7 -14
  267. package/dist/ToolBar/index.d.ts +30 -32
  268. package/dist/ToolBar/index.js +8 -30
  269. package/dist/Tree/Tree.torp +395 -0
  270. package/dist/Tree/TreeItem.torp +176 -0
  271. package/dist/Tree/TreeItemChildren.torp +60 -0
  272. package/dist/Tree/TreeItemContent.torp +46 -0
  273. package/dist/Tree/TreeItemLabel.torp +94 -0
  274. package/dist/Tree/TreeItemTrigger.torp +78 -0
  275. package/dist/Tree/TreeLoadedChildren.torp +122 -0
  276. package/dist/Tree/TreeTypes.ts +63 -0
  277. package/dist/Tree/index.d.ts +262 -0
  278. package/dist/Tree/index.js +7 -0
  279. package/dist/icons/ChevronDoubleLeft.torp +19 -0
  280. package/dist/icons/ChevronDoubleRight.torp +19 -0
  281. package/dist/icons/index.d.ts +61 -1
  282. package/dist/icons/index.js +17 -58
  283. package/dist/loader-DEeG5vFY.js +44 -0
  284. package/dist/loader-DEeG5vFY.js.map +1 -0
  285. package/dist/motion/index.d.ts +12 -8
  286. package/dist/motion/index.d.ts.map +1 -0
  287. package/dist/motion/index.js +72 -88
  288. package/dist/motion/index.js.map +1 -1
  289. package/dist/mount/draggable.ts +63 -0
  290. package/dist/mount/dropTarget.ts +108 -0
  291. package/dist/mount/hoverTrigger.ts +1 -1
  292. package/dist/utils/$handle.ts +23 -0
  293. package/dist/utils/LoadedItems.torp +47 -0
  294. package/dist/utils/PopoutClickTrigger.torp +99 -0
  295. package/dist/utils/PopoutTypes.ts +59 -12
  296. package/dist/utils/SubmenuPopout.torp +89 -0
  297. package/dist/utils/SubmenuPopoutContent.torp +87 -0
  298. package/dist/utils/SubmenuPopoutTrigger.torp +160 -0
  299. package/dist/utils/chartColors.ts +13 -13
  300. package/dist/utils/chartLayout.ts +143 -0
  301. package/dist/utils/colorUtils.ts +103 -0
  302. package/dist/utils/createItemGroup.ts +152 -0
  303. package/dist/utils/dateUtils.ts +8 -7
  304. package/dist/utils/documentEvents.ts +4 -4
  305. package/dist/utils/dragState.ts +86 -0
  306. package/dist/utils/focusGroup.ts +204 -0
  307. package/dist/utils/focusItem.ts +44 -0
  308. package/dist/utils/formField.ts +70 -0
  309. package/dist/utils/hoverOpen.ts +50 -0
  310. package/dist/utils/hoverReveal.ts +194 -0
  311. package/dist/utils/loader.ts +113 -0
  312. package/dist/utils/maskUtils.ts +98 -0
  313. package/dist/utils/popoutContent.ts +137 -0
  314. package/dist/utils/reorderList.ts +52 -0
  315. package/package.json +97 -22
  316. package/dist/Accordion/index.js.map +0 -1
  317. package/dist/Breadcrumb/index.js.map +0 -1
  318. package/dist/Calendar/index.js.map +0 -1
  319. package/dist/Charts/index.js.map +0 -1
  320. package/dist/Contextual/index.js.map +0 -1
  321. package/dist/Dialog/index.js.map +0 -1
  322. package/dist/Disclosure/index.js.map +0 -1
  323. package/dist/Drawer/index.js.map +0 -1
  324. package/dist/Form/index.js.map +0 -1
  325. package/dist/ListBox/index.js.map +0 -1
  326. package/dist/Menu/index.js.map +0 -1
  327. package/dist/MenuBar/index.js.map +0 -1
  328. package/dist/NavMenu/index.js.map +0 -1
  329. package/dist/Pagination/index.js.map +0 -1
  330. package/dist/SelectBox/SelectBoxInput.torp +0 -95
  331. package/dist/SelectBox/index.js.map +0 -1
  332. package/dist/TabGroup/index.js.map +0 -1
  333. package/dist/ToolBar/index.js.map +0 -1
  334. package/dist/icons/index.js.map +0 -1
  335. package/dist/index.d.ts +0 -19
  336. package/dist/metafile-esm.json +0 -1
  337. package/dist/motion/fade.ts +0 -16
  338. package/dist/motion/grow.ts +0 -23
  339. package/dist/motion/measure.ts +0 -28
  340. package/dist/motion/slide.ts +0 -45
  341. package/dist/mount/index.d.ts +0 -2
  342. package/dist/utils/index.d.ts +0 -2
@@ -0,0 +1,378 @@
1
+ import type { Bindable, ClassValue, StyleValue } from "@torpor/view";
2
+ import $handle from "../utils/$handle";
3
+ import {
4
+ type FieldContext,
5
+ FieldContextName,
6
+ type FormContext,
7
+ FormContextName,
8
+ } from "../Form/FormTypes";
9
+ import FormValues from "../Form/FormValues.torp";
10
+ import { type Loader, createItemLoader } from "../utils/loader";
11
+ import getId from "../utils/getId";
12
+ import createFormField from "../utils/formField";
13
+
14
+ interface TagInputProps {
15
+ /** An ID for the root element */
16
+ id?: string;
17
+ /** Classes for the root element */
18
+ class?: ClassValue;
19
+ /** Styles for the root element */
20
+ style?: StyleValue;
21
+ /** The current tags */
22
+ value?: Bindable<string[]>;
23
+ /** Placeholder text for the empty input */
24
+ placeholder?: string;
25
+ /** If set to true, the user cannot interact with this input */
26
+ disabled?: boolean;
27
+ /** An accessible name for the input */
28
+ ariaLabel?: string;
29
+ /** The name of the component's form data field */
30
+ name?: string;
31
+ /** Raised when the tags have changed */
32
+ onchange?: (value: string[]) => void;
33
+ /**
34
+ * Reads suggestions from the network (or anywhere async) as the user
35
+ * types, receiving `{ searchText }`. Suggestions are offered in a
36
+ * dropdown; selecting one adds it as a tag
37
+ */
38
+ load?: Loader;
39
+ /** Extracts the display text for a suggestion (defaults to `label ?? text ?? item`) */
40
+ getItemLabel?: (item: any) => any;
41
+ /** Raised when the suggestion list opens or closes */
42
+ onopen?: (open: boolean) => void;
43
+ }
44
+
45
+ /**
46
+ * An input where the user enters multiple free-text values, each shown as a
47
+ * tag that can be removed. Typing a value and pressing Enter or comma adds
48
+ * it as a tag, and pressing Backspace in an empty field removes the last one.
49
+ */
50
+ export default function TagInput($props: TagInputProps | undefined) {
51
+ let input: HTMLInputElement;
52
+ let root: HTMLElement;
53
+
54
+ // Wire up the enclosing Form and Field, if any
55
+ const form = createFormField(
56
+ $props?.name,
57
+ $context[FormContextName] as FormContext | undefined,
58
+ $context[FieldContextName] as FieldContext | undefined,
59
+ );
60
+
61
+ function getDisabled(): boolean {
62
+ return $props?.disabled === true;
63
+ }
64
+
65
+ let defaultId = getId();
66
+
67
+ let $state = $watch({
68
+ // Seed from the props so the initial tags render; $bind handles later
69
+ // pushes both ways
70
+ value: ($props?.value ?? []) as string[],
71
+ text: "",
72
+ suggestionsOpen: false,
73
+ activeSuggestion: -1,
74
+ });
75
+
76
+ $bind($state, $props, "value");
77
+
78
+ if ($props?.load !== undefined && typeof $props.load !== "function") {
79
+ throw new Error("TagInput: `load` must be a function");
80
+ }
81
+ const hasLoader = typeof $props?.load === "function";
82
+
83
+ function getLabel(item: any): string {
84
+ const getItemLabel = $props?.getItemLabel;
85
+ if (getItemLabel) return String(getItemLabel(item));
86
+ const label = (item as any)?.label ?? (item as any)?.text ?? item;
87
+ return typeof label === "string" ? label : String(label);
88
+ }
89
+
90
+ // Fetches suggestions as the text changes; nothing is fetched while the
91
+ // field is empty (the loader only mounts with the suggestion list)
92
+ const $load = hasLoader
93
+ ? createItemLoader({
94
+ load: $props!.load!,
95
+ getRequest: () => ({ searchText: $state.text }),
96
+ })
97
+ : undefined;
98
+
99
+ function openSuggestions() {
100
+ if (!hasLoader || $state.text.trim() === "") return;
101
+ if (!$state.suggestionsOpen) {
102
+ $state.suggestionsOpen = true;
103
+ $props?.onopen?.(true);
104
+ }
105
+ }
106
+
107
+ function closeSuggestions() {
108
+ if ($state.suggestionsOpen) {
109
+ $state.suggestionsOpen = false;
110
+ $state.activeSuggestion = -1;
111
+ $props?.onopen?.(false);
112
+ }
113
+ }
114
+
115
+ function suggestionId(index: number): string {
116
+ return `${defaultId}-suggestion-${index}`;
117
+ }
118
+
119
+ function suggestions(): any[] {
120
+ return $load ? $load.items : [];
121
+ }
122
+
123
+ // Dispatch the change event when the tags change
124
+ $handle((first) => {
125
+ const values = $state.value;
126
+ if (!first) $props?.onchange?.([...values]);
127
+ });
128
+
129
+ // Keep the input's value in sync with the text; typing sets the property
130
+ // directly, so an attribute alone would not update it
131
+ function syncInput() {
132
+ if (input && input.value !== $state.text) {
133
+ input.value = $state.text;
134
+ }
135
+ }
136
+ $run(() => {
137
+ const text = $state.text;
138
+ if (input && input.value !== text) {
139
+ input.value = text;
140
+ }
141
+ });
142
+ $onmount(() => syncInput());
143
+
144
+ function addTagFromText(text: string) {
145
+ const trimmed = text.trim();
146
+ if (trimmed === "") return;
147
+ $state.value = [...$state.value, trimmed];
148
+ $state.text = "";
149
+ syncInput();
150
+ closeSuggestions();
151
+ input?.focus();
152
+ form.handleInput();
153
+ }
154
+
155
+ function addTag() {
156
+ if (getDisabled()) return;
157
+
158
+ // A highlighted suggestion is selected over the raw text
159
+ if ($state.suggestionsOpen && $state.activeSuggestion >= 0) {
160
+ const item = suggestions()[$state.activeSuggestion];
161
+ if (item !== undefined) {
162
+ addTagFromText(getLabel(item));
163
+ return;
164
+ }
165
+ }
166
+ addTagFromText($state.text);
167
+ }
168
+
169
+ function removeTag(index: number) {
170
+ if (getDisabled()) return;
171
+ $state.value = $state.value.filter((_, i) => i !== index);
172
+ form.handleInput();
173
+ }
174
+
175
+ function removeLastTag() {
176
+ if (getDisabled()) return;
177
+ if ($state.text === "" && $state.value.length > 0) {
178
+ $state.value = $state.value.slice(0, -1);
179
+ form.handleInput();
180
+ }
181
+ }
182
+
183
+ function handleInput(e: Event) {
184
+ const el = e.currentTarget as HTMLInputElement;
185
+ $state.text = el.value;
186
+ $state.activeSuggestion = -1;
187
+ if ($state.text.trim() === "") {
188
+ closeSuggestions();
189
+ } else {
190
+ openSuggestions();
191
+ }
192
+ }
193
+
194
+ function handleKey(e: KeyboardEvent) {
195
+ if (getDisabled()) return;
196
+
197
+ switch (e.key) {
198
+ case "ArrowDown": {
199
+ if ($state.suggestionsOpen) {
200
+ e.preventDefault();
201
+ const count = suggestions().length;
202
+ if (count > 0) {
203
+ $state.activeSuggestion = Math.min(count - 1, $state.activeSuggestion + 1);
204
+ }
205
+ }
206
+ break;
207
+ }
208
+ case "ArrowUp": {
209
+ if ($state.suggestionsOpen) {
210
+ e.preventDefault();
211
+ $state.activeSuggestion = Math.max(-1, $state.activeSuggestion - 1);
212
+ }
213
+ break;
214
+ }
215
+ case "Escape": {
216
+ if ($state.suggestionsOpen) {
217
+ e.preventDefault();
218
+ closeSuggestions();
219
+ }
220
+ break;
221
+ }
222
+ case "Enter": {
223
+ e.preventDefault();
224
+ addTag();
225
+ break;
226
+ }
227
+ case ",": {
228
+ e.preventDefault();
229
+ addTag();
230
+ break;
231
+ }
232
+ case "Backspace": {
233
+ if ($state.text === "") {
234
+ e.preventDefault();
235
+ removeLastTag();
236
+ }
237
+ break;
238
+ }
239
+ }
240
+ }
241
+
242
+ function pickSuggestion(item: any) {
243
+ addTagFromText(getLabel(item));
244
+ }
245
+
246
+ @render {
247
+ <div
248
+ {$props?.id}
249
+ class={[
250
+ "torp-tag-input",
251
+ getDisabled() ? "torp-tag-input-disabled" : undefined,
252
+ $props?.class,
253
+ ]}
254
+ {$props?.style}
255
+ data-valid={form.valid ? "valid" : "invalid"}
256
+ aria-describedby={form.messageId}
257
+ aria-invalid={form.inField && !form.valid ? "true" : undefined}
258
+ &ref={root}
259
+ onfocusout={(e: FocusEvent) => {
260
+ // Only blur when focus leaves the whole input
261
+ if (!root.contains(e.relatedTarget as Node | null)) {
262
+ form.handleBlur();
263
+ }
264
+ }}
265
+ onmousedown={(e: MouseEvent) => {
266
+ // Focus the field wherever the user clicks, without stealing
267
+ // focus from the remove button they may have clicked
268
+ if (!getDisabled() && e.target === e.currentTarget) {
269
+ e.preventDefault();
270
+ input?.focus();
271
+ }
272
+ }}
273
+ >
274
+ <ul class="torp-tag-input-tags">
275
+ @for (let [index, tag] of $state.value.entries()) {
276
+ @key = index
277
+ <li class="torp-tag-input-tag">
278
+ <span class="torp-tag-input-tag-text">{tag}</span>
279
+ <button
280
+ type="button"
281
+ class="torp-tag-input-remove"
282
+ aria-label={`Remove ${tag}`}
283
+ disabled={getDisabled()}
284
+ onclick={() => removeTag(index)}
285
+ >
286
+ <slot>×</slot>
287
+ </button>
288
+ </li>
289
+ }
290
+ </ul>
291
+ <input
292
+ class="torp-tag-input-field"
293
+ type="text"
294
+ &ref={input}
295
+ value={$state.text}
296
+ placeholder={$props?.placeholder}
297
+ disabled={getDisabled()}
298
+ aria-label={$props?.ariaLabel}
299
+ role={hasLoader ? "combobox" : undefined}
300
+ aria-expanded={hasLoader ? ($state.suggestionsOpen ? "true" : "false") : undefined}
301
+ aria-controls={$state.suggestionsOpen ? `${defaultId}-suggestions` : undefined}
302
+ aria-autocomplete="list"
303
+ aria-activedescendant={$state.activeSuggestion >= 0 ? suggestionId($state.activeSuggestion) : undefined}
304
+ oninput={handleInput}
305
+ onkeydown={handleKey}
306
+ />
307
+ @if ($state.suggestionsOpen) {
308
+ @try {
309
+ @await {
310
+ <ul
311
+ id={`${defaultId}-suggestions`}
312
+ class="torp-tag-input-suggestions"
313
+ role="listbox"
314
+ aria-label={$props?.ariaLabel}
315
+ >
316
+ @for (let [index, item] of suggestions().entries()) {
317
+ @key = getLabel(item)
318
+ <li
319
+ class="torp-tag-input-suggestion"
320
+ role="option"
321
+ id={suggestionId(index)}
322
+ aria-selected={$state.activeSuggestion === index ? "true" : "false"}
323
+ data-active={$state.activeSuggestion === index ? "active" : undefined}
324
+ onmouseenter={() => ($state.activeSuggestion = index)}
325
+ onmousedown={(e: MouseEvent) => {
326
+ // mousedown so the field doesn't blur first
327
+ e.preventDefault();
328
+ pickSuggestion(item);
329
+ }}
330
+ >
331
+ {getLabel(item)}
332
+ </li>
333
+ }
334
+ @if (suggestions().length === 0) {
335
+ <li class="torp-tag-input-empty" role="presentation">No matches.</li>
336
+ }
337
+ </ul>
338
+ } with {
339
+ <div class="torp-tag-input-loading" role="status">Loading…</div>
340
+ }
341
+ } catch (err) {
342
+ <div class="torp-tag-input-error" role="alert">
343
+ {err instanceof Error ? err.message : String(err)}
344
+ </div>
345
+ }
346
+ }
347
+ </div>
348
+
349
+ <FormValues name={form.name} value={$state.value} />
350
+ }
351
+
352
+ @style {
353
+ /* Headless: layout only, so the tags sit in a row with the field. All
354
+ appearance is up to the consuming theme */
355
+ .torp-tag-input {
356
+ display: flex;
357
+ flex-wrap: wrap;
358
+ }
359
+ .torp-tag-input-tags {
360
+ display: flex;
361
+ flex-wrap: wrap;
362
+ list-style: none;
363
+ margin: 0;
364
+ padding: 0;
365
+ }
366
+ .torp-tag-input-field {
367
+ flex: 1 1 auto;
368
+ min-width: 4em;
369
+ }
370
+ .torp-tag-input-suggestions {
371
+ position: absolute;
372
+ list-style: none;
373
+ margin: 0;
374
+ padding: 0;
375
+ z-index: 10;
376
+ }
377
+ }
378
+ }
@@ -0,0 +1,48 @@
1
+ import { type SlotRender } from "@torpor/view";
2
+
3
+
4
+ interface TagInputProps {
5
+ /** An ID for the root element */
6
+ id?: string;
7
+ /** Classes for the root element */
8
+ class?: ClassValue;
9
+ /** Styles for the root element */
10
+ style?: StyleValue;
11
+ /** The current tags */
12
+ value?: Bindable<string[]>;
13
+ /** Placeholder text for the empty input */
14
+ placeholder?: string;
15
+ /** If set to true, the user cannot interact with this input */
16
+ disabled?: boolean;
17
+ /** An accessible name for the input */
18
+ ariaLabel?: string;
19
+ /** The name of the component's form data field */
20
+ name?: string;
21
+ /** Raised when the tags have changed */
22
+ onchange?: (value: string[]) => void;
23
+ /**
24
+ * Reads suggestions from the network (or anywhere async) as the user
25
+ * types, receiving `{ searchText }`. Suggestions are offered in a
26
+ * dropdown; selecting one adds it as a tag
27
+ */
28
+ load?: Loader;
29
+ /** Extracts the display text for a suggestion (defaults to `label ?? text ?? item`) */
30
+ getItemLabel?: (item: any) => any;
31
+ /** Raised when the suggestion list opens or closes */
32
+ onopen?: (open: boolean) => void;
33
+ }
34
+
35
+ /**
36
+ * An input where the user enters multiple free-text values, each shown as a
37
+ * tag that can be removed. Typing a value and pressing Enter or comma adds
38
+ * it as a tag, and pressing Backspace in an empty field removes the last one.
39
+ */
40
+ declare function TagInput(
41
+ $parent: ParentNode,
42
+ $anchor: Node | null,
43
+ $props: TagInputProps | undefined,
44
+ $context?: Record<PropertyKey, any>,
45
+ $slots?: Record<string, SlotRender>,
46
+ ): void;
47
+
48
+ export { TagInput };
@@ -0,0 +1,2 @@
1
+ import TagInput from "./TagInput.torp";
2
+ export { TagInput };