@torpor/ui 0.3.7 → 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 (346) hide show
  1. package/README.md +83 -7
  2. package/dist/Accordion/Accordion.torp +43 -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 +6 -6
  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 +25 -4
  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 +70 -0
  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/{Chart → Charts}/GridLines.torp +7 -4
  46. package/dist/Charts/LineChart.torp +73 -0
  47. package/dist/{Chart → Charts}/PieChart.torp +14 -20
  48. package/dist/{Chart → Charts}/ScatterChart.torp +45 -38
  49. package/dist/{Chart → Charts}/SparkLine.torp +16 -19
  50. package/dist/{Chart → Charts}/XAxis.torp +18 -12
  51. package/dist/{Chart → Charts}/YAxis.torp +15 -9
  52. package/dist/Charts/index.d.ts +469 -0
  53. package/dist/Charts/index.js +17 -0
  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 +39 -52
  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 +5 -18
  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 +33 -30
  91. package/dist/Dialog/DialogBody.torp +5 -9
  92. package/dist/Dialog/DialogButton.torp +14 -11
  93. package/dist/Dialog/DialogFooter.torp +3 -7
  94. package/dist/Dialog/DialogHeader.torp +6 -10
  95. package/dist/Dialog/DialogTypes.ts +2 -2
  96. package/dist/Dialog/index.d.ts +28 -26
  97. package/dist/Dialog/index.js +6 -22
  98. package/dist/Disclosure/Disclosure.torp +18 -18
  99. package/dist/Disclosure/DisclosureContent.torp +5 -6
  100. package/dist/Disclosure/DisclosureHeader.torp +7 -11
  101. package/dist/Disclosure/DisclosureTrigger.torp +4 -5
  102. package/dist/Disclosure/DisclosureTypes.ts +1 -1
  103. package/dist/Disclosure/index.d.ts +14 -16
  104. package/dist/Disclosure/index.js +5 -18
  105. package/dist/Drawer/Drawer.torp +21 -51
  106. package/dist/Drawer/DrawerContent.torp +67 -13
  107. package/dist/Drawer/DrawerOverlay.torp +4 -4
  108. package/dist/Drawer/DrawerTrigger.torp +16 -4
  109. package/dist/Drawer/DrawerTypes.ts +4 -1
  110. package/dist/Drawer/index.d.ts +16 -20
  111. package/dist/Drawer/index.js +5 -18
  112. package/dist/FocusGroup/FocusGroup.torp +70 -0
  113. package/dist/FocusGroup/FocusGroupButton.torp +59 -0
  114. package/dist/FocusGroup/FocusGroupTypes.ts +14 -0
  115. package/dist/FocusGroup/index.d.ts +44 -0
  116. package/dist/FocusGroup/index.js +3 -0
  117. package/dist/Form/CheckBox.torp +28 -20
  118. package/dist/Form/Field.torp +17 -17
  119. package/dist/Form/File.torp +31 -22
  120. package/dist/Form/FileDrop.torp +187 -0
  121. package/dist/Form/Form.torp +24 -22
  122. package/dist/Form/FormTypes.ts +2 -0
  123. package/dist/Form/FormValues.torp +37 -0
  124. package/dist/Form/Hidden.torp +4 -12
  125. package/dist/Form/Input.torp +38 -32
  126. package/dist/Form/Label.torp +3 -3
  127. package/dist/Form/Message.torp +13 -5
  128. package/dist/Form/MessageSummary.torp +39 -0
  129. package/dist/Form/Option.torp +11 -7
  130. package/dist/Form/Select.torp +32 -28
  131. package/dist/Form/TextArea.torp +35 -31
  132. package/dist/Form/Toggle.torp +109 -0
  133. package/dist/Form/index.d.ts +198 -77
  134. package/dist/Form/index.js +16 -46
  135. package/dist/ListBox/ListBox.torp +374 -0
  136. package/dist/ListBox/ListBoxItem.torp +131 -0
  137. package/dist/ListBox/ListBoxSeparator.torp +29 -0
  138. package/dist/ListBox/ListBoxTypes.ts +41 -0
  139. package/dist/ListBox/LoadedListBoxItems.torp +41 -0
  140. package/dist/ListBox/index.d.ts +136 -0
  141. package/dist/ListBox/index.js +4 -0
  142. package/dist/MaskedInput/MaskedInput.torp +143 -0
  143. package/dist/MaskedInput/index.d.ts +52 -0
  144. package/dist/MaskedInput/index.js +2 -0
  145. package/dist/Menu/Menu.torp +90 -70
  146. package/dist/Menu/MenuButton.torp +17 -10
  147. package/dist/Menu/MenuCheck.torp +22 -18
  148. package/dist/Menu/MenuGroup.torp +4 -4
  149. package/dist/Menu/MenuIndicator.torp +5 -13
  150. package/dist/Menu/MenuPopout.torp +20 -50
  151. package/dist/Menu/MenuPopoutContent.torp +15 -66
  152. package/dist/Menu/MenuPopoutTrigger.torp +19 -100
  153. package/dist/Menu/MenuRadio.torp +25 -26
  154. package/dist/Menu/MenuRadioGroup.torp +25 -68
  155. package/dist/Menu/MenuSeparator.torp +3 -2
  156. package/dist/Menu/MenuTypes.ts +12 -21
  157. package/dist/Menu/index.d.ts +45 -51
  158. package/dist/Menu/index.js +12 -46
  159. package/dist/MenuBar/MenuBar.torp +31 -83
  160. package/dist/MenuBar/MenuBarItem.torp +34 -55
  161. package/dist/MenuBar/MenuBarItemContent.torp +15 -101
  162. package/dist/MenuBar/MenuBarItemTrigger.torp +20 -55
  163. package/dist/MenuBar/MenuBarTypes.ts +13 -12
  164. package/dist/MenuBar/index.d.ts +17 -19
  165. package/dist/MenuBar/index.js +5 -18
  166. package/dist/Modal/Modal.torp +28 -48
  167. package/dist/Modal/ModalContent.torp +21 -44
  168. package/dist/Modal/ModalOverlay.torp +4 -4
  169. package/dist/Modal/ModalTrigger.torp +16 -32
  170. package/dist/Modal/ModalTypes.ts +6 -17
  171. package/dist/Modal/index.d.ts +17 -29
  172. package/dist/Modal/index.js +29 -28
  173. package/dist/Modal/index.js.map +1 -1
  174. package/dist/Modal/showPrompt.ts +2 -2
  175. package/dist/NavMenu/NavMenu.torp +22 -104
  176. package/dist/NavMenu/NavMenuButton.torp +10 -8
  177. package/dist/NavMenu/NavMenuElement.torp +6 -7
  178. package/dist/NavMenu/NavMenuGroup.torp +5 -5
  179. package/dist/NavMenu/NavMenuGroupHeader.torp +5 -8
  180. package/dist/NavMenu/NavMenuLink.torp +7 -10
  181. package/dist/NavMenu/NavMenuPopout.torp +16 -61
  182. package/dist/NavMenu/NavMenuPopoutContent.torp +14 -73
  183. package/dist/NavMenu/NavMenuPopoutTrigger.torp +47 -0
  184. package/dist/NavMenu/NavMenuSeparator.torp +3 -6
  185. package/dist/NavMenu/NavMenuTypes.ts +6 -15
  186. package/dist/NavMenu/index.d.ts +55 -55
  187. package/dist/NavMenu/index.js +11 -42
  188. package/dist/Notification/Notification.torp +12 -24
  189. package/dist/Notification/NotificationClose.torp +6 -3
  190. package/dist/Notification/NotificationContainer.torp +3 -7
  191. package/dist/Notification/NotificationContent.torp +3 -7
  192. package/dist/Notification/NotificationTypes.ts +1 -1
  193. package/dist/Notification/index.d.ts +15 -12
  194. package/dist/Notification/index.js +22 -32
  195. package/dist/Notification/index.js.map +1 -1
  196. package/dist/NumberInput/NumberInput.torp +255 -0
  197. package/dist/NumberInput/index.d.ts +44 -0
  198. package/dist/NumberInput/index.js +2 -0
  199. package/dist/Pagination/Pagination.torp +152 -0
  200. package/dist/Pagination/PaginationItem.torp +137 -0
  201. package/dist/Pagination/PaginationTypes.ts +22 -0
  202. package/dist/Pagination/buildPages.ts +61 -0
  203. package/dist/Pagination/index.d.ts +63 -0
  204. package/dist/Pagination/index.js +3 -0
  205. package/dist/Popover/Popover.torp +39 -52
  206. package/dist/Popover/PopoverContent.torp +27 -76
  207. package/dist/Popover/PopoverHover.torp +90 -0
  208. package/dist/Popover/PopoverTrigger.torp +17 -40
  209. package/dist/Popover/PopoverTypes.ts +5 -18
  210. package/dist/Popover/index.d.ts +40 -25
  211. package/dist/Popover/index.js +18 -18
  212. package/dist/Popover/index.js.map +1 -1
  213. package/dist/Progress/Progress.torp +88 -0
  214. package/dist/Progress/index.d.ts +37 -0
  215. package/dist/Progress/index.js +2 -0
  216. package/dist/Rating/Rating.torp +225 -0
  217. package/dist/Rating/index.d.ts +44 -0
  218. package/dist/Rating/index.js +2 -0
  219. package/dist/SegmentedControl/SegmentedControl.torp +233 -0
  220. package/dist/SegmentedControl/SegmentedControlTypes.ts +26 -0
  221. package/dist/SegmentedControl/SegmentedItem.torp +78 -0
  222. package/dist/SegmentedControl/index.d.ts +74 -0
  223. package/dist/SegmentedControl/index.js +3 -0
  224. package/dist/SelectBox/SelectBox.torp +190 -0
  225. package/dist/SelectBox/SelectBoxContent.torp +107 -0
  226. package/dist/SelectBox/SelectBoxTrigger.torp +141 -0
  227. package/dist/SelectBox/SelectBoxTypes.ts +33 -0
  228. package/dist/SelectBox/index.d.ts +112 -0
  229. package/dist/SelectBox/index.js +5 -0
  230. package/dist/Slider/Slider.torp +248 -0
  231. package/dist/Slider/index.d.ts +45 -0
  232. package/dist/Slider/index.js +2 -0
  233. package/dist/Splitter/Splitter.torp +156 -0
  234. package/dist/Splitter/SplitterHandle.torp +161 -0
  235. package/dist/Splitter/SplitterPane.torp +63 -0
  236. package/dist/Splitter/SplitterTypes.ts +31 -0
  237. package/dist/Splitter/index.d.ts +103 -0
  238. package/dist/Splitter/index.js +4 -0
  239. package/dist/Stepper/Stepper.torp +146 -0
  240. package/dist/Stepper/StepperMarker.torp +24 -0
  241. package/dist/Stepper/StepperNext.torp +44 -0
  242. package/dist/Stepper/StepperPrevious.torp +44 -0
  243. package/dist/Stepper/StepperStep.torp +90 -0
  244. package/dist/Stepper/StepperTypes.ts +30 -0
  245. package/dist/Stepper/index.d.ts +149 -0
  246. package/dist/Stepper/index.js +6 -0
  247. package/dist/TabGroup/TabContent.torp +6 -6
  248. package/dist/TabGroup/TabGroup.torp +97 -113
  249. package/dist/TabGroup/TabGroupTypes.ts +3 -3
  250. package/dist/TabGroup/TabHeader.torp +23 -19
  251. package/dist/TabGroup/TabList.torp +8 -21
  252. package/dist/TabGroup/TabTrigger.torp +6 -7
  253. package/dist/TabGroup/index.d.ts +26 -26
  254. package/dist/TabGroup/index.js +6 -22
  255. package/dist/TagInput/TagInput.torp +378 -0
  256. package/dist/TagInput/index.d.ts +48 -0
  257. package/dist/TagInput/index.js +2 -0
  258. package/dist/TimePicker/TimePicker.torp +462 -0
  259. package/dist/TimePicker/index.d.ts +53 -0
  260. package/dist/TimePicker/index.js +2 -0
  261. package/dist/ToolBar/ToolBar.torp +117 -0
  262. package/dist/ToolBar/ToolBarButton.torp +56 -0
  263. package/dist/ToolBar/ToolBarElement.torp +51 -0
  264. package/dist/ToolBar/ToolBarPopout.torp +45 -0
  265. package/dist/ToolBar/ToolBarPopoutContent.torp +34 -0
  266. package/dist/ToolBar/ToolBarPopoutTrigger.torp +47 -0
  267. package/dist/ToolBar/ToolBarSeparator.torp +21 -0
  268. package/dist/ToolBar/ToolBarTypes.ts +28 -0
  269. package/dist/ToolBar/index.d.ts +210 -0
  270. package/dist/ToolBar/index.js +8 -0
  271. package/dist/Tree/Tree.torp +395 -0
  272. package/dist/Tree/TreeItem.torp +176 -0
  273. package/dist/Tree/TreeItemChildren.torp +60 -0
  274. package/dist/Tree/TreeItemContent.torp +46 -0
  275. package/dist/Tree/TreeItemLabel.torp +94 -0
  276. package/dist/Tree/TreeItemTrigger.torp +78 -0
  277. package/dist/Tree/TreeLoadedChildren.torp +122 -0
  278. package/dist/Tree/TreeTypes.ts +63 -0
  279. package/dist/Tree/index.d.ts +262 -0
  280. package/dist/Tree/index.js +7 -0
  281. package/dist/icons/ChevronDoubleLeft.torp +19 -0
  282. package/dist/icons/ChevronDoubleRight.torp +19 -0
  283. package/dist/icons/index.d.ts +61 -1
  284. package/dist/icons/index.js +17 -58
  285. package/dist/loader-DEeG5vFY.js +44 -0
  286. package/dist/loader-DEeG5vFY.js.map +1 -0
  287. package/dist/motion/index.d.ts +12 -8
  288. package/dist/motion/index.d.ts.map +1 -0
  289. package/dist/motion/index.js +72 -88
  290. package/dist/motion/index.js.map +1 -1
  291. package/dist/mount/draggable.ts +63 -0
  292. package/dist/mount/dropTarget.ts +108 -0
  293. package/dist/mount/hoverTrigger.ts +1 -1
  294. package/dist/utils/$handle.ts +23 -0
  295. package/dist/utils/LoadedItems.torp +47 -0
  296. package/dist/utils/PopoutClickTrigger.torp +99 -0
  297. package/dist/utils/PopoutTypes.ts +82 -0
  298. package/dist/utils/SubmenuPopout.torp +89 -0
  299. package/dist/utils/SubmenuPopoutContent.torp +87 -0
  300. package/dist/utils/SubmenuPopoutTrigger.torp +160 -0
  301. package/dist/utils/chartColors.ts +13 -13
  302. package/dist/utils/chartLayout.ts +143 -0
  303. package/dist/utils/chartUtils.ts +1 -1
  304. package/dist/utils/colorUtils.ts +103 -0
  305. package/dist/utils/createItemGroup.ts +152 -0
  306. package/dist/utils/dateUtils.ts +8 -7
  307. package/dist/utils/documentEvents.ts +4 -4
  308. package/dist/utils/dragState.ts +86 -0
  309. package/dist/utils/focusGroup.ts +204 -0
  310. package/dist/utils/focusItem.ts +44 -0
  311. package/dist/utils/formField.ts +70 -0
  312. package/dist/utils/hoverOpen.ts +50 -0
  313. package/dist/utils/hoverReveal.ts +194 -0
  314. package/dist/utils/loader.ts +113 -0
  315. package/dist/utils/maskUtils.ts +98 -0
  316. package/dist/utils/popoutContent.ts +137 -0
  317. package/dist/utils/reorderList.ts +52 -0
  318. package/package.json +116 -25
  319. package/dist/Accordion/index.js.map +0 -1
  320. package/dist/Breadcrumb/index.js.map +0 -1
  321. package/dist/Calendar/index.js.map +0 -1
  322. package/dist/Chart/BarChart.torp +0 -159
  323. package/dist/Chart/LineChart.torp +0 -196
  324. package/dist/Chart/index.d.ts +0 -220
  325. package/dist/Chart/index.js +0 -34
  326. package/dist/Chart/index.js.map +0 -1
  327. package/dist/Contextual/index.js.map +0 -1
  328. package/dist/Dialog/index.js.map +0 -1
  329. package/dist/Disclosure/index.js.map +0 -1
  330. package/dist/Drawer/index.js.map +0 -1
  331. package/dist/Form/index.js.map +0 -1
  332. package/dist/Menu/index.js.map +0 -1
  333. package/dist/MenuBar/index.js.map +0 -1
  334. package/dist/NavMenu/NavMenuPopoutButton.torp +0 -129
  335. package/dist/NavMenu/index.js.map +0 -1
  336. package/dist/TabGroup/index.js.map +0 -1
  337. package/dist/icons/index.js.map +0 -1
  338. package/dist/index.d.ts +0 -19
  339. package/dist/metafile-esm.json +0 -1
  340. package/dist/motion/fade.ts +0 -16
  341. package/dist/motion/grow.ts +0 -23
  342. package/dist/motion/measure.ts +0 -28
  343. package/dist/motion/slide.ts +0 -45
  344. package/dist/mount/index.d.ts +0 -2
  345. package/dist/utils/index.d.ts +0 -2
  346. /package/dist/{Chart → Charts}/DataSeries.ts +0 -0
@@ -0,0 +1,496 @@
1
+ import type { Bindable, ClassValue, StyleValue } from "@torpor/view";
2
+ import { type DataColumn } from "./DataGridTypes";
3
+ import type {
4
+ LoadRequest,
5
+ LoadResult,
6
+ Loader,
7
+ } from "../utils/loader";
8
+ import { normalizeLoadResult } from "../utils/loader";
9
+ import sortRows from "./sortRows";
10
+ import getId from "../utils/getId";
11
+
12
+ interface DataGridProps {
13
+ /** An ID for the root element */
14
+ id?: string;
15
+ /** Classes for the root element */
16
+ class?: ClassValue;
17
+ /** Styles for the root element */
18
+ style?: StyleValue;
19
+ /** A caption for the table, rendered in a `<caption>` element */
20
+ caption?: string;
21
+ /** The columns to display */
22
+ columns: DataColumn[];
23
+ /** The rows to display. Pass either `data` (static rows) or `load`. */
24
+ data?: any[];
25
+ /**
26
+ * Reads rows from the network (or anywhere async). Receives a request with
27
+ * `page`/`pageSize`, `searchText` and `sortBy`/`sortDirection`, plus an
28
+ * AbortSignal. Pass either `load` or `data`, not both.
29
+ */
30
+ load?: Loader;
31
+ /** The active page (1-based). Two-way bindable. */
32
+ page?: Bindable<number>;
33
+ /** The number of rows per page. Static data is sliced locally; loaders receive it in the request. */
34
+ pageSize?: number;
35
+ /**
36
+ * Renders only the rows visible in the viewport, for large datasets.
37
+ * Requires `rowHeight`; supersedes local page slicing while enabled
38
+ */
39
+ virtual?: boolean;
40
+ /** The fixed pixel height of a row, required by virtualization */
41
+ rowHeight?: number;
42
+ /** The scroll viewport height in pixels when virtual (default 400) */
43
+ height?: number;
44
+ /** Extra rows rendered above and below the visible window (default 5) */
45
+ overscan?: number;
46
+ /** The field the data is sorted by. Two-way bindable. */
47
+ sortBy?: Bindable<string | undefined>;
48
+ /** The direction the data is sorted in. Two-way bindable. */
49
+ sortDirection?: Bindable<"asc" | "desc">;
50
+ /** An ARIA label describing the grid to screen readers */
51
+ ariaLabel?: string;
52
+ /** An ID of an element that labels the grid */
53
+ ariaLabelledBy?: string;
54
+ /** Called after each successful load with the normalized result (including `total`) */
55
+ onload?: (result: LoadResult) => void;
56
+ /** Called when the user sorts by a column */
57
+ onsortchange?: (sortBy: string | undefined, sortDirection: "asc" | "desc") => void;
58
+ }
59
+
60
+ /**
61
+ * A table of rows that supports sorting, paging and loading data from the
62
+ * network.
63
+ *
64
+ * See [the WAI-ARIA guidelines for grids](https://www.w3.org/WAI/ARIA/apg/patterns/grid/).
65
+ *
66
+ * The structure of a DataGrid is:
67
+ *
68
+ * ```
69
+ * <DataGrid columns={columns} data={rows}>
70
+ * (cell content, using $slot.row / $slot.column / $slot.value)
71
+ * </DataGrid>
72
+ * ```
73
+ *
74
+ * Cells render their value as text by default. Pass children to customize:
75
+ *
76
+ * ```
77
+ * <DataGrid columns={columns} data={rows}>
78
+ * {@if ($slot.column.key === "avatar") {
79
+ * <img src={$slot.value} alt="" />
80
+ * } else {
81
+ * {$slot.value}
82
+ * }}
83
+ * </DataGrid>
84
+ * ```
85
+ *
86
+ * Named slots: `loading` (while the first load is pending), `error`
87
+ * (receives nothing -- use it around the message), `empty` (when there are
88
+ * no rows) and `header` isn't needed since headers come from the columns.
89
+ */
90
+ export default function DataGrid($props: DataGridProps) {
91
+ let defaultId = getId();
92
+
93
+ if (!$props.columns || $props.columns.length === 0) {
94
+ throw new Error("DataGrid: pass a non-empty `columns` array");
95
+ }
96
+ const hasLoader = typeof $props.load === "function";
97
+ if (hasLoader && $props.data !== undefined) {
98
+ throw new Error("DataGrid: pass either `load` or `data`, not both");
99
+ }
100
+ if (!hasLoader && $props.data === undefined) {
101
+ throw new Error("DataGrid: pass either `load` or `data`");
102
+ }
103
+
104
+ let tableElement: HTMLElement;
105
+ let wrap: HTMLElement;
106
+
107
+ // Columns are assumed static once passed; precompute entries with indices
108
+ // so cells can be keyed and addressed by position
109
+ const columnEntries = $props.columns.map((column, index) => ({ column, index }));
110
+
111
+ let $state = $watch({
112
+ page: $props.page ?? 1,
113
+ sortBy: $props.sortBy,
114
+ sortDirection: $props.sortDirection ?? "asc",
115
+ activeCell: { row: 0, col: 0 },
116
+
117
+ // The result of the current load. Suspends while pending when read,
118
+ // which drives the @await boundary in the template.
119
+ get loadResult(): LoadResult | undefined {
120
+ if (!hasLoader) {
121
+ return undefined;
122
+ }
123
+ return $async(() => runLoad());
124
+ },
125
+ get sortedRows(): any[] {
126
+ if (hasLoader) {
127
+ return this.loadResult?.items ?? [];
128
+ }
129
+ return sortRows($props.data ?? [], this.sortBy, this.sortDirection, columnEntries.map((e) => e.column));
130
+ },
131
+ get totalCount(): number {
132
+ if (hasLoader && this.loadResult?.total !== undefined) {
133
+ return this.loadResult.total;
134
+ }
135
+ return this.sortedRows.length;
136
+ },
137
+ get rows(): any[] {
138
+ const pageSize = $props.pageSize;
139
+ if (!pageSize || hasLoader) {
140
+ return this.sortedRows;
141
+ }
142
+ const start = (this.page - 1) * pageSize;
143
+ return this.sortedRows.slice(start, start + pageSize);
144
+ },
145
+ get rowEntries() {
146
+ return this.rows.map((row: any, index: number) => ({ row, index }));
147
+ },
148
+ // The rows currently rendered: everything when not virtual, the
149
+ // visible window otherwise
150
+ get bodyEntries() {
151
+ if (!isVirtual()) return this.rowEntries;
152
+ return this.rows.slice(win.start, win.endExclusive).map(
153
+ (row: any, i: number) => ({ row, index: win.start + i }),
154
+ );
155
+ },
156
+ });
157
+
158
+ $bind($state, $props, "page", "sortBy", "sortDirection");
159
+
160
+ // --- Virtualization ---
161
+
162
+ function isVirtual(): boolean {
163
+ return $props?.virtual === true && ($props?.rowHeight ?? 0) > 0;
164
+ }
165
+
166
+ function getRowHeight(): number {
167
+ return Math.max(1, Math.floor($props?.rowHeight ?? 32));
168
+ }
169
+
170
+ function getOverscan(): number {
171
+ return Math.max(0, Math.floor($props?.overscan ?? 5));
172
+ }
173
+
174
+ function getViewportHeight(): number {
175
+ return wrap?.clientHeight || $props?.height || 400;
176
+ }
177
+
178
+ // The rendered row window; endExclusive may pass loaded rows when a
179
+ // loader reports a total larger than what it sent so far -- slicing is
180
+ // naturally safe against that
181
+ const win = $watch({ start: 0, endExclusive: 0 });
182
+
183
+ function updateWindow() {
184
+ if (!isVirtual()) return;
185
+
186
+ const total = $state.totalCount;
187
+ if (!total || !wrap) {
188
+ win.start = 0;
189
+ win.endExclusive = 0;
190
+ return;
191
+ }
192
+
193
+ const rh = getRowHeight();
194
+ const overscan = getOverscan();
195
+ const start = Math.min(Math.max(0, Math.floor(wrap.scrollTop / rh) - overscan), total);
196
+ const visible = Math.ceil(getViewportHeight() / rh);
197
+ win.start = start;
198
+ win.endExclusive = Math.min(total, start + visible + overscan * 2);
199
+ }
200
+
201
+ function handleScroll() {
202
+ updateWindow();
203
+ }
204
+
205
+ // The active cell, clamped to the current grid size -- rows can shrink
206
+ // (e.g. after a load returns fewer rows) and the stored position must
207
+ // stay addressable so exactly one cell remains the tab stop.
208
+ // Clamping is done at the use sites rather than in an effect: effects
209
+ // can't safely read suspended ($async) getters.
210
+ function activeCell(): { row: number; col: number } {
211
+ const maxRow = Math.max(0, $state.rows.length - 1);
212
+ const maxCol = Math.max(0, $props.columns.length - 1);
213
+ return {
214
+ row: Math.min(Math.max(0, $state.activeCell.row), maxRow),
215
+ col: Math.min(Math.max(0, $state.activeCell.col), maxCol),
216
+ };
217
+ }
218
+
219
+ function runLoad(): Promise<LoadResult> {
220
+ const request: LoadRequest = {};
221
+ if ($props.pageSize) {
222
+ request.page = $state.page;
223
+ request.pageSize = $props.pageSize;
224
+ }
225
+ if ($state.sortBy != null) {
226
+ request.sortBy = $state.sortBy;
227
+ request.sortDirection = $state.sortDirection;
228
+ }
229
+ const result = Promise.resolve(($props.load as Loader)(request)).then(normalizeLoadResult);
230
+ // Report successful loads to the consumer; failures surface through
231
+ // the component's error boundary, not this notification chain
232
+ result.then((r) => $props.onload?.(r), () => {});
233
+ return result;
234
+ }
235
+
236
+ function cellValue(row: any, column: DataColumn): any {
237
+ if (column.getValue) {
238
+ return column.getValue(row);
239
+ }
240
+ return row?.[column.key];
241
+ }
242
+
243
+ function cellId(row: number, col: number): string {
244
+ return `${row}:${col}`;
245
+ }
246
+
247
+ function isActiveCell(row: number, col: number): boolean {
248
+ const active = activeCell();
249
+ return active.row === row && active.col === col;
250
+ }
251
+
252
+ function ariaSort(key: string): "ascending" | "descending" | undefined {
253
+ if ($state.sortBy !== key) {
254
+ return undefined;
255
+ }
256
+ return $state.sortDirection === "asc" ? "ascending" : "descending";
257
+ }
258
+
259
+ function toggleSort(column: DataColumn) {
260
+ if (!column.sortable) {
261
+ return;
262
+ }
263
+ if ($state.sortBy === column.key) {
264
+ $state.sortDirection = $state.sortDirection === "asc" ? "desc" : "asc";
265
+ } else {
266
+ $state.sortBy = column.key;
267
+ $state.sortDirection = "asc";
268
+ }
269
+ $props.onsortchange?.($state.sortBy, $state.sortDirection);
270
+ }
271
+
272
+ // Scrolls the virtual window so a logical row is rendered, before it is
273
+ // focused
274
+ function makeRowVisible(row: number) {
275
+ if (!isVirtual() || !wrap) return;
276
+
277
+ const rh = getRowHeight();
278
+ const overscan = getOverscan();
279
+ if (row < win.start) {
280
+ wrap.scrollTop = Math.max(0, (row - overscan) * rh);
281
+ updateWindow();
282
+ } else if (row >= win.endExclusive) {
283
+ const below = Math.max(
284
+ 0,
285
+ (row + 1 + overscan) * rh - getViewportHeight(),
286
+ );
287
+ wrap.scrollTop = below;
288
+ updateWindow();
289
+ }
290
+ }
291
+
292
+ function focusCell(row: number, col: number) {
293
+ makeRowVisible(row);
294
+ $state.activeCell = { row, col };
295
+ tableElement.querySelector<HTMLElement>(`[data-cell="${cellId(row, col)}"]`)?.focus();
296
+ }
297
+
298
+ // Any other way of focusing a cell (tabbing in, script) also moves the
299
+ // keyboard navigation origin
300
+ function handleFocusin(e: FocusEvent) {
301
+ const cell = (e.target as HTMLElement).closest?.("td[data-cell]");
302
+ if (!cell) {
303
+ return;
304
+ }
305
+ const [row, col] = cell.getAttribute("data-cell")!.split(":").map(Number);
306
+ $state.activeCell = { row, col };
307
+ }
308
+
309
+ // Standard grid keyboard navigation (roving tabindex over body cells)
310
+ function handleKeydown(e: KeyboardEvent) {
311
+ const target = e.target as HTMLElement;
312
+ if (!target.matches?.("td[data-cell]")) {
313
+ return;
314
+ }
315
+ if (target.closest("button,input,select,textarea,a[href]")) {
316
+ return;
317
+ }
318
+ const maxRow = $state.rows.length - 1;
319
+ const maxCol = $props.columns.length - 1;
320
+ const active = activeCell();
321
+ const row = active.row;
322
+ const col = active.col;
323
+ switch (e.key) {
324
+ case "ArrowLeft": {
325
+ if (col > 0) {
326
+ e.preventDefault();
327
+ focusCell(row, col - 1);
328
+ }
329
+ break;
330
+ }
331
+ case "ArrowRight": {
332
+ if (col < maxCol) {
333
+ e.preventDefault();
334
+ focusCell(row, col + 1);
335
+ }
336
+ break;
337
+ }
338
+ case "ArrowUp": {
339
+ if (row > 0) {
340
+ e.preventDefault();
341
+ focusCell(row - 1, col);
342
+ }
343
+ break;
344
+ }
345
+ case "ArrowDown": {
346
+ if (row < maxRow) {
347
+ e.preventDefault();
348
+ focusCell(row + 1, col);
349
+ }
350
+ break;
351
+ }
352
+ case "Home": {
353
+ e.preventDefault();
354
+ focusCell(e.ctrlKey || e.metaKey ? 0 : row, e.ctrlKey || e.metaKey ? 0 : 0);
355
+ break;
356
+ }
357
+ case "End": {
358
+ e.preventDefault();
359
+ focusCell(e.ctrlKey || e.metaKey ? maxRow : row, maxCol);
360
+ break;
361
+ }
362
+ case "PageUp": {
363
+ if (isVirtual()) {
364
+ e.preventDefault();
365
+ const step = Math.max(1, Math.floor(getViewportHeight() / getRowHeight()));
366
+ focusCell(Math.max(0, active.row - step), col);
367
+ }
368
+ break;
369
+ }
370
+ case "PageDown": {
371
+ if (isVirtual()) {
372
+ e.preventDefault();
373
+ const step = Math.max(1, Math.floor(getViewportHeight() / getRowHeight()));
374
+ focusCell(Math.min(maxRow, active.row + step), col);
375
+ }
376
+ break;
377
+ }
378
+ }
379
+ }
380
+
381
+ const alignClass = (align: "start" | "center" | "end" | undefined) =>
382
+ align ? `torp-data-grid-align-${align}` : undefined;
383
+
384
+ function wrapStyle(): string | undefined {
385
+ if (!isVirtual()) return undefined;
386
+ return `max-height:${getViewportHeight()}px;overflow:auto;`;
387
+ }
388
+
389
+ @render {
390
+ @try {
391
+ @await {
392
+ <div
393
+ class="torp-data-grid-wrap"
394
+ style={wrapStyle()}
395
+ onscroll={handleScroll}
396
+ onmount={(el: HTMLElement) => {
397
+ wrap = el;
398
+ updateWindow();
399
+ }}
400
+ &ref={wrap}
401
+ >
402
+ <table
403
+ id={$props.id ?? defaultId}
404
+ class={["torp-data-grid", $props.class]}
405
+ {$props?.style}
406
+ &ref={tableElement}
407
+ role="grid"
408
+ aria-label={$props.ariaLabel}
409
+ aria-labelledby={$props.ariaLabelledBy}
410
+ aria-rowcount={hasLoader || isVirtual() ? $state.totalCount : undefined}
411
+ onkeydown={handleKeydown}
412
+ onfocusin={handleFocusin}
413
+ >
414
+ @if ($props.caption) {
415
+ <caption>{$props.caption}</caption>
416
+ }
417
+ <thead>
418
+ <tr role="row">
419
+ @for (let entry of columnEntries) {
420
+ @key = entry.column.key
421
+ <th
422
+ scope="col"
423
+ role="columnheader"
424
+ class={[alignClass(entry.column.align), entry.column.sortable ? "torp-data-grid-sortable" : undefined]}
425
+ aria-sort={ariaSort(entry.column.key)}
426
+ >
427
+ @if (entry.column.sortable) {
428
+ <button type="button" onclick={() => toggleSort(entry.column)}>
429
+ {entry.column.label ?? entry.column.key}
430
+ </button>
431
+ } else {
432
+ <span>{entry.column.label ?? entry.column.key}</span>
433
+ }
434
+ </th>
435
+ }
436
+ </tr>
437
+ </thead>
438
+ <tbody>
439
+ @if ($state.rowEntries.length === 0) {
440
+ <tr role="row">
441
+ <td colspan={$props.columns.length}>
442
+ <slot name="empty">No results.</slot>
443
+ </td>
444
+ </tr>
445
+ } else {
446
+ @if (isVirtual()) {
447
+ <tr aria-hidden="true" class="torp-data-grid-spacer">
448
+ <td colspan={$props.columns.length} style={`height:${win.start * getRowHeight()}px`} />
449
+ </tr>
450
+ }
451
+ @for (let rowEntry of $state.bodyEntries) {
452
+ @key = rowEntry.row
453
+ <tr role="row">
454
+ @for (let entry of columnEntries) {
455
+ @key = entry.column.key
456
+ <td
457
+ role="gridcell"
458
+ class={alignClass(entry.column.align)}
459
+ data-cell={cellId(rowEntry.index, entry.index)}
460
+ tabindex={isActiveCell(rowEntry.index, entry.index) ? 0 : -1}
461
+ >
462
+ <slot
463
+ row={rowEntry.row}
464
+ column={entry.column}
465
+ value={cellValue(rowEntry.row, entry.column)}
466
+ >
467
+ {cellValue(rowEntry.row, entry.column)}
468
+ </slot>
469
+ </td>
470
+ }
471
+ </tr>
472
+ }
473
+ @if (isVirtual()) {
474
+ <tr aria-hidden="true" class="torp-data-grid-spacer">
475
+ <td
476
+ colspan={$props.columns.length}
477
+ style={`height:${Math.max(0, ($state.rows.length - win.endExclusive)) * getRowHeight()}px`}
478
+ />
479
+ </tr>
480
+ }
481
+ }
482
+ </tbody>
483
+ </table>
484
+ </div>
485
+ } with {
486
+ <div class="torp-data-grid-loading" role="status">
487
+ <slot name="loading">Loading…</slot>
488
+ </div>
489
+ }
490
+ } catch (err) {
491
+ <div class="torp-data-grid-error" role="alert">
492
+ <slot name="error">{err instanceof Error ? err.message : String(err)}</slot>
493
+ </div>
494
+ }
495
+ }
496
+ }
@@ -0,0 +1,25 @@
1
+ import type { LoadResult } from "../utils/loader";
2
+
3
+ /** A column in a DataGrid */
4
+ export interface DataColumn<T = any> {
5
+ /** The field name to read from each row (or pass a `getValue` function) */
6
+ key: string;
7
+ /** Header text; defaults to the key */
8
+ label?: string;
9
+ /** Cell content alignment */
10
+ align?: "start" | "center" | "end";
11
+ /** Whether clicking this column's header sorts by it */
12
+ sortable?: boolean;
13
+ /** Extracts the cell value from a row; defaults to `row[key]` */
14
+ getValue?: (row: T) => any;
15
+ }
16
+
17
+ /** What a DataGrid passes to its cell slot for custom rendering */
18
+ export interface DataGridCellSlot<T = any> {
19
+ row: T;
20
+ column: DataColumn<T>;
21
+ value: any;
22
+ }
23
+
24
+ /** Data passed to the `onload` callback after each successful load */
25
+ export type DataGridLoadEvent<T = any> = LoadResult<T>;
@@ -0,0 +1,90 @@
1
+ import { type SlotRender } from "@torpor/view";
2
+
3
+
4
+ interface DataGridProps {
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
+ /** A caption for the table, rendered in a `<caption>` element */
12
+ caption?: string;
13
+ /** The columns to display */
14
+ columns: DataColumn[];
15
+ /** The rows to display. Pass either `data` (static rows) or `load`. */
16
+ data?: any[];
17
+ /**
18
+ * Reads rows from the network (or anywhere async). Receives a request with
19
+ * `page`/`pageSize`, `searchText` and `sortBy`/`sortDirection`, plus an
20
+ * AbortSignal. Pass either `load` or `data`, not both.
21
+ */
22
+ load?: Loader;
23
+ /** The active page (1-based). Two-way bindable. */
24
+ page?: Bindable<number>;
25
+ /** The number of rows per page. Static data is sliced locally; loaders receive it in the request. */
26
+ pageSize?: number;
27
+ /**
28
+ * Renders only the rows visible in the viewport, for large datasets.
29
+ * Requires `rowHeight`; supersedes local page slicing while enabled
30
+ */
31
+ virtual?: boolean;
32
+ /** The fixed pixel height of a row, required by virtualization */
33
+ rowHeight?: number;
34
+ /** The scroll viewport height in pixels when virtual (default 400) */
35
+ height?: number;
36
+ /** Extra rows rendered above and below the visible window (default 5) */
37
+ overscan?: number;
38
+ /** The field the data is sorted by. Two-way bindable. */
39
+ sortBy?: Bindable<string | undefined>;
40
+ /** The direction the data is sorted in. Two-way bindable. */
41
+ sortDirection?: Bindable<"asc" | "desc">;
42
+ /** An ARIA label describing the grid to screen readers */
43
+ ariaLabel?: string;
44
+ /** An ID of an element that labels the grid */
45
+ ariaLabelledBy?: string;
46
+ /** Called after each successful load with the normalized result (including `total`) */
47
+ onload?: (result: LoadResult) => void;
48
+ /** Called when the user sorts by a column */
49
+ onsortchange?: (sortBy: string | undefined, sortDirection: "asc" | "desc") => void;
50
+ }
51
+
52
+ /**
53
+ * A table of rows that supports sorting, paging and loading data from the
54
+ * network.
55
+ *
56
+ * See [the WAI-ARIA guidelines for grids](https://www.w3.org/WAI/ARIA/apg/patterns/grid/).
57
+ *
58
+ * The structure of a DataGrid is:
59
+ *
60
+ * ```
61
+ * <DataGrid columns={columns} data={rows}>
62
+ * (cell content, using $slot.row / $slot.column / $slot.value)
63
+ * </DataGrid>
64
+ * ```
65
+ *
66
+ * Cells render their value as text by default. Pass children to customize:
67
+ *
68
+ * ```
69
+ * <DataGrid columns={columns} data={rows}>
70
+ * {@if ($slot.column.key === "avatar") {
71
+ * <img src={$slot.value} alt="" />
72
+ * } else {
73
+ * {$slot.value}
74
+ * }}
75
+ * </DataGrid>
76
+ * ```
77
+ *
78
+ * Named slots: `loading` (while the first load is pending), `error`
79
+ * (receives nothing -- use it around the message), `empty` (when there are
80
+ * no rows) and `header` isn't needed since headers come from the columns.
81
+ */
82
+ declare function DataGrid(
83
+ $parent: ParentNode,
84
+ $anchor: Node | null,
85
+ $props: DataGridProps,
86
+ $context?: Record<PropertyKey, any>,
87
+ $slots?: Record<string, SlotRender>,
88
+ ): void;
89
+
90
+ export { DataGrid };
@@ -0,0 +1,3 @@
1
+ import { n as normalizeLoadResult } from "../loader-DEeG5vFY.js";
2
+ import DataGrid from "./DataGrid.torp";
3
+ export { DataGrid, normalizeLoadResult };
@@ -0,0 +1,32 @@
1
+ /** Compares two cell values, handling mixed types and nullish values */
2
+ function compareValues(a: any, b: any): number {
3
+ if (a == null && b == null) return 0;
4
+ if (a == null) return -1;
5
+ if (b == null) return 1;
6
+ if (typeof a === "string" || typeof b === "string") {
7
+ return String(a).localeCompare(String(b));
8
+ }
9
+ return a < b ? -1 : a > b ? 1 : 0;
10
+ }
11
+
12
+ /**
13
+ * Sorts rows by the given column key (a shallow copy -- the source array is
14
+ * not modified). Returns the rows unchanged when no sort is active.
15
+ */
16
+ export default function sortRows<T>(
17
+ rows: T[],
18
+ sortBy: string | undefined,
19
+ sortDirection: "asc" | "desc",
20
+ columns: { key: string; getValue?: (row: T) => any }[],
21
+ ): T[] {
22
+ if (!sortBy) {
23
+ return rows;
24
+ }
25
+ const column = columns.find((c) => c.key === sortBy);
26
+ const getValue = column?.getValue ?? ((row: any) => row?.[sortBy]);
27
+ const sorted = [...rows].sort((a, b) => compareValues(getValue(a), getValue(b)));
28
+ if (sortDirection === "desc") {
29
+ sorted.reverse();
30
+ }
31
+ return sorted;
32
+ }