@tulipfarm/sprout 0.0.0-stage → 0.2.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 (314) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +183 -2
  3. package/dist/components/accordion.d.ts +28 -0
  4. package/dist/components/accordion.js +26 -0
  5. package/dist/components/alert-dialog.d.ts +60 -0
  6. package/dist/components/alert-dialog.js +69 -0
  7. package/dist/components/alert.d.ts +30 -0
  8. package/dist/components/alert.js +35 -0
  9. package/dist/components/aspect-ratio.d.ts +7 -0
  10. package/dist/components/aspect-ratio.js +6 -0
  11. package/dist/components/attachment.d.ts +118 -0
  12. package/dist/components/attachment.js +285 -0
  13. package/dist/components/avatar.d.ts +32 -0
  14. package/dist/components/avatar.js +73 -0
  15. package/dist/components/badge.d.ts +21 -0
  16. package/dist/components/badge.js +27 -0
  17. package/dist/components/brand-icon.d.ts +28 -0
  18. package/dist/components/brand-icon.js +82 -0
  19. package/dist/components/breadcrumb.d.ts +29 -0
  20. package/dist/components/breadcrumb.js +44 -0
  21. package/dist/components/button-group.d.ts +28 -0
  22. package/dist/components/button-group.js +36 -0
  23. package/dist/components/button.d.ts +79 -0
  24. package/dist/components/button.js +93 -0
  25. package/dist/components/calendar.d.ts +27 -0
  26. package/dist/components/calendar.js +107 -0
  27. package/dist/components/canvas.d.ts +78 -0
  28. package/dist/components/canvas.js +180 -0
  29. package/dist/components/card.d.ts +43 -0
  30. package/dist/components/card.js +42 -0
  31. package/dist/components/carousel.d.ts +57 -0
  32. package/dist/components/carousel.js +422 -0
  33. package/dist/components/chart.d.ts +112 -0
  34. package/dist/components/chart.js +172 -0
  35. package/dist/components/chat-confirmation.d.ts +42 -0
  36. package/dist/components/chat-confirmation.js +62 -0
  37. package/dist/components/chat-input.d.ts +94 -0
  38. package/dist/components/chat-input.js +279 -0
  39. package/dist/components/chat-plan.d.ts +21 -0
  40. package/dist/components/chat-plan.js +35 -0
  41. package/dist/components/chat-sources.d.ts +64 -0
  42. package/dist/components/chat-sources.js +66 -0
  43. package/dist/components/checkbox.d.ts +16 -0
  44. package/dist/components/checkbox.js +17 -0
  45. package/dist/components/choice-list.d.ts +53 -0
  46. package/dist/components/choice-list.js +36 -0
  47. package/dist/components/code-block.d.ts +35 -0
  48. package/dist/components/code-block.js +64 -0
  49. package/dist/components/collapsible.d.ts +24 -0
  50. package/dist/components/collapsible.js +27 -0
  51. package/dist/components/combobox.d.ts +95 -0
  52. package/dist/components/combobox.js +106 -0
  53. package/dist/components/command.d.ts +94 -0
  54. package/dist/components/command.js +414 -0
  55. package/dist/components/context-menu.d.ts +57 -0
  56. package/dist/components/context-menu.js +60 -0
  57. package/dist/components/dialog.d.ts +60 -0
  58. package/dist/components/dialog.js +79 -0
  59. package/dist/components/direction.d.ts +6 -0
  60. package/dist/components/direction.js +6 -0
  61. package/dist/components/dropdown-menu.d.ts +62 -0
  62. package/dist/components/dropdown-menu.js +59 -0
  63. package/dist/components/field.d.ts +117 -0
  64. package/dist/components/field.js +150 -0
  65. package/dist/components/file-explorer.d.ts +27 -0
  66. package/dist/components/file-explorer.js +300 -0
  67. package/dist/components/file-viewer.d.ts +137 -0
  68. package/dist/components/file-viewer.js +400 -0
  69. package/dist/components/form.d.ts +87 -0
  70. package/dist/components/form.js +55 -0
  71. package/dist/components/glass-banner.d.ts +27 -0
  72. package/dist/components/glass-banner.js +116 -0
  73. package/dist/components/gradient-banner.d.ts +37 -0
  74. package/dist/components/gradient-banner.js +43 -0
  75. package/dist/components/hover-card.d.ts +27 -0
  76. package/dist/components/hover-card.js +26 -0
  77. package/dist/components/input-group.d.ts +68 -0
  78. package/dist/components/input-group.js +88 -0
  79. package/dist/components/input-otp.d.ts +38 -0
  80. package/dist/components/input-otp.js +43 -0
  81. package/dist/components/input.d.ts +16 -0
  82. package/dist/components/input.js +18 -0
  83. package/dist/components/kbd.d.ts +12 -0
  84. package/dist/components/kbd.js +11 -0
  85. package/dist/components/label.d.ts +8 -0
  86. package/dist/components/label.js +10 -0
  87. package/dist/components/mention-list.d.ts +54 -0
  88. package/dist/components/mention-list.js +31 -0
  89. package/dist/components/menubar.d.ts +64 -0
  90. package/dist/components/menubar.js +68 -0
  91. package/dist/components/message-actions.d.ts +34 -0
  92. package/dist/components/message-actions.js +68 -0
  93. package/dist/components/message-response.d.ts +20 -0
  94. package/dist/components/message-response.js +46 -0
  95. package/dist/components/message-scroller.d.ts +71 -0
  96. package/dist/components/message-scroller.js +217 -0
  97. package/dist/components/message.d.ts +49 -0
  98. package/dist/components/message.js +59 -0
  99. package/dist/components/navigation-menu.d.ts +45 -0
  100. package/dist/components/navigation-menu.js +54 -0
  101. package/dist/components/notice-banner.d.ts +14 -0
  102. package/dist/components/notice-banner.js +11 -0
  103. package/dist/components/page-content.d.ts +7 -0
  104. package/dist/components/page-content.js +9 -0
  105. package/dist/components/page-header.d.ts +37 -0
  106. package/dist/components/page-header.js +53 -0
  107. package/dist/components/pagination.d.ts +29 -0
  108. package/dist/components/pagination.js +29 -0
  109. package/dist/components/plan-outline.d.ts +60 -0
  110. package/dist/components/plan-outline.js +108 -0
  111. package/dist/components/popover.d.ts +34 -0
  112. package/dist/components/popover.js +35 -0
  113. package/dist/components/progress.d.ts +27 -0
  114. package/dist/components/progress.js +35 -0
  115. package/dist/components/prompt-list.d.ts +23 -0
  116. package/dist/components/prompt-list.js +11 -0
  117. package/dist/components/questionnaire.d.ts +148 -0
  118. package/dist/components/questionnaire.js +1002 -0
  119. package/dist/components/radio-group.d.ts +16 -0
  120. package/dist/components/radio-group.js +21 -0
  121. package/dist/components/reasoning-effort.d.ts +24 -0
  122. package/dist/components/reasoning-effort.js +269 -0
  123. package/dist/components/resizable.d.ts +67 -0
  124. package/dist/components/resizable.js +536 -0
  125. package/dist/components/result-card.d.ts +58 -0
  126. package/dist/components/result-card.js +88 -0
  127. package/dist/components/review-card.d.ts +62 -0
  128. package/dist/components/review-card.js +49 -0
  129. package/dist/components/rich-text-editor.d.ts +51 -0
  130. package/dist/components/rich-text-editor.js +248 -0
  131. package/dist/components/schema-diagram.d.ts +52 -0
  132. package/dist/components/schema-diagram.js +226 -0
  133. package/dist/components/scroll-area.d.ts +22 -0
  134. package/dist/components/scroll-area.js +19 -0
  135. package/dist/components/search-dialog.d.ts +103 -0
  136. package/dist/components/search-dialog.js +65 -0
  137. package/dist/components/search-field.d.ts +17 -0
  138. package/dist/components/search-field.js +27 -0
  139. package/dist/components/select.d.ts +57 -0
  140. package/dist/components/select.js +57 -0
  141. package/dist/components/selection-slot.d.ts +18 -0
  142. package/dist/components/selection-slot.js +53 -0
  143. package/dist/components/separator.d.ts +48 -0
  144. package/dist/components/separator.js +54 -0
  145. package/dist/components/settings-list.d.ts +26 -0
  146. package/dist/components/settings-list.js +17 -0
  147. package/dist/components/sheet.d.ts +70 -0
  148. package/dist/components/sheet.js +77 -0
  149. package/dist/components/sidebar.d.ts +70 -0
  150. package/dist/components/sidebar.js +187 -0
  151. package/dist/components/skeleton.d.ts +31 -0
  152. package/dist/components/skeleton.js +32 -0
  153. package/dist/components/slider.d.ts +17 -0
  154. package/dist/components/slider.js +24 -0
  155. package/dist/components/spinner.d.ts +37 -0
  156. package/dist/components/spinner.js +43 -0
  157. package/dist/components/state-panel-samples.d.ts +25 -0
  158. package/dist/components/state-panel-samples.js +423 -0
  159. package/dist/components/state-panel.d.ts +35 -0
  160. package/dist/components/state-panel.js +29 -0
  161. package/dist/components/state-sample.d.ts +63 -0
  162. package/dist/components/state-sample.js +42 -0
  163. package/dist/components/switch.d.ts +12 -0
  164. package/dist/components/switch.js +14 -0
  165. package/dist/components/table-nuqs.d.ts +16 -0
  166. package/dist/components/table-nuqs.js +59 -0
  167. package/dist/components/table.d.ts +120 -0
  168. package/dist/components/table.js +663 -0
  169. package/dist/components/tabs.d.ts +37 -0
  170. package/dist/components/tabs.js +36 -0
  171. package/dist/components/textarea.d.ts +18 -0
  172. package/dist/components/textarea.js +19 -0
  173. package/dist/components/toast.d.ts +45 -0
  174. package/dist/components/toast.js +83 -0
  175. package/dist/components/toggle-group.d.ts +30 -0
  176. package/dist/components/toggle-group.js +39 -0
  177. package/dist/components/toggle.d.ts +27 -0
  178. package/dist/components/toggle.js +21 -0
  179. package/dist/components/tool-activity.d.ts +79 -0
  180. package/dist/components/tool-activity.js +226 -0
  181. package/dist/components/toolbar-status.d.ts +21 -0
  182. package/dist/components/toolbar-status.js +14 -0
  183. package/dist/components/tooltip.d.ts +31 -0
  184. package/dist/components/tooltip.js +99 -0
  185. package/dist/components/typed-text.d.ts +17 -0
  186. package/dist/components/typed-text.js +56 -0
  187. package/dist/index.d.ts +90 -0
  188. package/dist/index.js +90 -0
  189. package/dist/internal/brand-glyphs.d.ts +370 -0
  190. package/dist/internal/brand-glyphs.js +262 -0
  191. package/dist/internal/classes.d.ts +3 -0
  192. package/dist/internal/classes.js +20 -0
  193. package/dist/internal/code-tokens.d.ts +4 -0
  194. package/dist/internal/code-tokens.js +34 -0
  195. package/dist/internal/fieldset.d.ts +6 -0
  196. package/dist/internal/fieldset.js +38 -0
  197. package/dist/internal/file-kind.d.ts +4 -0
  198. package/dist/internal/file-kind.js +62 -0
  199. package/dist/internal/languages.d.ts +7 -0
  200. package/dist/internal/languages.js +1010 -0
  201. package/dist/internal/message-scroller.d.ts +102 -0
  202. package/dist/internal/message-scroller.js +637 -0
  203. package/dist/internal/pressable.d.ts +2 -0
  204. package/dist/internal/pressable.js +29 -0
  205. package/dist/internal/syntax.d.ts +14 -0
  206. package/dist/internal/syntax.js +137 -0
  207. package/dist/styles/accordion.css +102 -0
  208. package/dist/styles/alert-dialog.css +198 -0
  209. package/dist/styles/alert.css +149 -0
  210. package/dist/styles/aspect-ratio.css +8 -0
  211. package/dist/styles/attachment.css +700 -0
  212. package/dist/styles/avatar.css +186 -0
  213. package/dist/styles/badge.css +178 -0
  214. package/dist/styles/brand-icon.css +104 -0
  215. package/dist/styles/breadcrumb.css +121 -0
  216. package/dist/styles/button-group.css +113 -0
  217. package/dist/styles/button.css +380 -0
  218. package/dist/styles/calendar.css +295 -0
  219. package/dist/styles/canvas.css +313 -0
  220. package/dist/styles/card.css +146 -0
  221. package/dist/styles/carousel.css +107 -0
  222. package/dist/styles/chart.css +227 -0
  223. package/dist/styles/chat-confirmation.css +29 -0
  224. package/dist/styles/chat-input.css +240 -0
  225. package/dist/styles/chat-plan.css +109 -0
  226. package/dist/styles/chat-sources.css +191 -0
  227. package/dist/styles/checkbox.css +122 -0
  228. package/dist/styles/choice-list.css +343 -0
  229. package/dist/styles/code-block.css +211 -0
  230. package/dist/styles/collapsible.css +72 -0
  231. package/dist/styles/combobox.css +347 -0
  232. package/dist/styles/command.css +233 -0
  233. package/dist/styles/context-menu.css +151 -0
  234. package/dist/styles/dialog.css +237 -0
  235. package/dist/styles/dropdown-menu.css +150 -0
  236. package/dist/styles/field.css +340 -0
  237. package/dist/styles/file-explorer.css +517 -0
  238. package/dist/styles/file-viewer.css +375 -0
  239. package/dist/styles/form.css +155 -0
  240. package/dist/styles/glass-banner.css +308 -0
  241. package/dist/styles/gradient-banner.css +256 -0
  242. package/dist/styles/hover-card.css +95 -0
  243. package/dist/styles/index.css +268 -0
  244. package/dist/styles/input-group.css +285 -0
  245. package/dist/styles/input-otp.css +138 -0
  246. package/dist/styles/input.css +98 -0
  247. package/dist/styles/kbd.css +59 -0
  248. package/dist/styles/label.css +89 -0
  249. package/dist/styles/mention-list.css +166 -0
  250. package/dist/styles/menubar.css +226 -0
  251. package/dist/styles/message-actions.css +83 -0
  252. package/dist/styles/message-response.css +176 -0
  253. package/dist/styles/message-scroller.css +136 -0
  254. package/dist/styles/message.css +310 -0
  255. package/dist/styles/navigation-menu.css +291 -0
  256. package/dist/styles/notice-banner.css +68 -0
  257. package/dist/styles/page-content.css +21 -0
  258. package/dist/styles/page-header.css +156 -0
  259. package/dist/styles/pagination.css +72 -0
  260. package/dist/styles/plan-outline.css +393 -0
  261. package/dist/styles/popover.css +140 -0
  262. package/dist/styles/progress.css +93 -0
  263. package/dist/styles/prompt-list.css +165 -0
  264. package/dist/styles/questionnaire.css +308 -0
  265. package/dist/styles/radio-group.css +119 -0
  266. package/dist/styles/reasoning-effort.css +157 -0
  267. package/dist/styles/resizable.css +108 -0
  268. package/dist/styles/result-card.css +215 -0
  269. package/dist/styles/review-card.css +235 -0
  270. package/dist/styles/rich-text-editor.css +169 -0
  271. package/dist/styles/schema-diagram.css +419 -0
  272. package/dist/styles/scroll-area.css +77 -0
  273. package/dist/styles/search-dialog.css +280 -0
  274. package/dist/styles/search-field.css +17 -0
  275. package/dist/styles/select.css +285 -0
  276. package/dist/styles/selection-slot.css +31 -0
  277. package/dist/styles/separator.css +206 -0
  278. package/dist/styles/settings-list.css +81 -0
  279. package/dist/styles/sheet.css +361 -0
  280. package/dist/styles/sidebar.css +363 -0
  281. package/dist/styles/skeleton.css +80 -0
  282. package/dist/styles/slider.css +142 -0
  283. package/dist/styles/spinner.css +39 -0
  284. package/dist/styles/state-panel-samples.css +41 -0
  285. package/dist/styles/state-panel.css +173 -0
  286. package/dist/styles/state-sample.css +285 -0
  287. package/dist/styles/switch.css +128 -0
  288. package/dist/styles/table.css +604 -0
  289. package/dist/styles/tabs.css +223 -0
  290. package/dist/styles/textarea.css +77 -0
  291. package/dist/styles/themes/chatgpt.css +125 -0
  292. package/dist/styles/themes/claude.css +125 -0
  293. package/dist/styles/themes/cloudflare.css +125 -0
  294. package/dist/styles/themes/discord.css +125 -0
  295. package/dist/styles/themes/github.css +125 -0
  296. package/dist/styles/themes/grok.css +125 -0
  297. package/dist/styles/themes/linear.css +126 -0
  298. package/dist/styles/themes/neutral.css +125 -0
  299. package/dist/styles/themes/notion.css +125 -0
  300. package/dist/styles/themes/perplexity.css +125 -0
  301. package/dist/styles/themes/raycast.css +125 -0
  302. package/dist/styles/themes/spotify.css +125 -0
  303. package/dist/styles/themes/stripe.css +125 -0
  304. package/dist/styles/themes/supabase.css +125 -0
  305. package/dist/styles/themes/tulipfarm.css +115 -0
  306. package/dist/styles/themes/vercel.css +126 -0
  307. package/dist/styles/toast.css +197 -0
  308. package/dist/styles/toggle-group.css +161 -0
  309. package/dist/styles/toggle.css +119 -0
  310. package/dist/styles/tool-activity.css +396 -0
  311. package/dist/styles/toolbar-status.css +52 -0
  312. package/dist/styles/tooltip.css +122 -0
  313. package/dist/styles/typed-text.css +17 -0
  314. package/package.json +452 -4
@@ -0,0 +1,663 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { createContext, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState, } from "react";
4
+ import { DndContext, KeyboardSensor, PointerSensor, closestCenter, useSensor, useSensors, } from "@dnd-kit/core";
5
+ import { SortableContext, arrayMove, horizontalListSortingStrategy, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy, } from "@dnd-kit/sortable";
6
+ import { CSS } from "@dnd-kit/utilities";
7
+ import { aggregationFn_count, aggregationFn_max, aggregationFn_mean, aggregationFn_min, aggregationFn_sum, columnFilteringFeature, columnGroupingFeature, columnOrderingFeature, columnPinningFeature, columnResizingFeature, columnSizingFeature, columnVisibilityFeature, constructFilterFn, createExpandedRowModel, createFilteredRowModel, createGroupedRowModel, createPaginatedRowModel, createSortedRowModel, filterFn_equals, filterFn_inDateRange, filterFn_inNumberRange, filterFn_includesString, functionalUpdate, globalFilteringFeature, rowAggregationFeature, rowExpandingFeature, rowPaginationFeature, rowSelectionFeature, rowSortingFeature, sortFn_alphanumeric, sortFn_basic, sortFn_datetime, sortFn_text, tableFeatures, useTable, } from "@tanstack/react-table";
8
+ import { useVirtualizer } from "@tanstack/react-virtual";
9
+ import { ArrowDown } from "reicon-react/icons/ArrowDown";
10
+ import { ArrowUp } from "reicon-react/icons/ArrowUp";
11
+ import { ChevronDown } from "reicon-react/icons/ChevronDown";
12
+ import { ChevronRight } from "reicon-react/icons/ChevronRight";
13
+ import { InfoCircle } from "reicon-react/icons/InfoCircle";
14
+ import { Plus } from "reicon-react/icons/Plus";
15
+ import { Reorder } from "reicon-react/icons/Reorder";
16
+ import { SortV } from "reicon-react/icons/SortV";
17
+ import { X } from "reicon-react/icons/X";
18
+ import { cn } from "../internal/classes.js";
19
+ import { Button } from "./button.js";
20
+ import { Checkbox } from "./checkbox.js";
21
+ import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuItem, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, } from "./dropdown-menu.js";
22
+ import { Input } from "./input.js";
23
+ import { Popover, PopoverContent, PopoverTrigger } from "./popover.js";
24
+ import { ScrollArea } from "./scroll-area.js";
25
+ import { SearchField } from "./search-field.js";
26
+ import { Skeleton } from "./skeleton.js";
27
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "./tooltip.js";
28
+ export const tableVariantNames = ["plain", "grid"];
29
+ export const tableNarrowNames = ["scroll", "stack"];
30
+ export const tableAlignNames = ["left", "right"];
31
+ export const tableFilterNames = [
32
+ "text",
33
+ "number",
34
+ "select",
35
+ "date",
36
+ "boolean",
37
+ ];
38
+ export const tableEditNames = [
39
+ "text",
40
+ "number",
41
+ "select",
42
+ "date",
43
+ "checkbox",
44
+ ];
45
+ export const tableAggregateNames = [
46
+ "sum",
47
+ "count",
48
+ "min",
49
+ "max",
50
+ "mean",
51
+ ];
52
+ export const tableLayoutKeys = [
53
+ "columnSizing",
54
+ "columnOrder",
55
+ "columnPinning",
56
+ "columnVisibility",
57
+ ];
58
+ // dnd-kit's live region is a div; inside <table> it breaks the table roles.
59
+ const dndAccessibility = {
60
+ container: typeof document === "undefined" ? undefined : document.body,
61
+ };
62
+ const isAnyOf = constructFilterFn({
63
+ filter: (dataValue, filterValue) => filterValue.includes(String(dataValue)),
64
+ autoRemove: (value) => !value?.length,
65
+ });
66
+ const features = tableFeatures({
67
+ columnFilteringFeature,
68
+ globalFilteringFeature,
69
+ rowSortingFeature,
70
+ columnGroupingFeature,
71
+ rowAggregationFeature,
72
+ rowExpandingFeature,
73
+ rowPaginationFeature,
74
+ rowSelectionFeature,
75
+ columnVisibilityFeature,
76
+ columnOrderingFeature,
77
+ columnPinningFeature,
78
+ columnSizingFeature,
79
+ columnResizingFeature,
80
+ filteredRowModel: createFilteredRowModel(),
81
+ sortedRowModel: createSortedRowModel(),
82
+ groupedRowModel: createGroupedRowModel(),
83
+ expandedRowModel: createExpandedRowModel(),
84
+ paginatedRowModel: createPaginatedRowModel(),
85
+ filterFns: {
86
+ includesString: filterFn_includesString,
87
+ inNumberRange: filterFn_inNumberRange,
88
+ inDateRange: filterFn_inDateRange,
89
+ equals: filterFn_equals,
90
+ isAnyOf,
91
+ },
92
+ sortFns: {
93
+ alphanumeric: sortFn_alphanumeric,
94
+ text: sortFn_text,
95
+ datetime: sortFn_datetime,
96
+ basic: sortFn_basic,
97
+ },
98
+ aggregationFns: {
99
+ sum: aggregationFn_sum,
100
+ count: aggregationFn_count,
101
+ min: aggregationFn_min,
102
+ max: aggregationFn_max,
103
+ mean: aggregationFn_mean,
104
+ },
105
+ });
106
+ const filterFnFor = {
107
+ text: "includesString",
108
+ number: "inNumberRange",
109
+ select: "isAnyOf",
110
+ date: "inDateRange",
111
+ boolean: "equals",
112
+ };
113
+ const DEFAULT_PAGE_SIZE = 10;
114
+ const pageResetKeys = new Set([
115
+ "sorting",
116
+ "columnFilters",
117
+ "globalFilter",
118
+ "grouping",
119
+ ]);
120
+ function initialState(columns, pageSize) {
121
+ return {
122
+ sorting: [],
123
+ columnFilters: [],
124
+ globalFilter: "",
125
+ grouping: [],
126
+ expanded: true,
127
+ pagination: { pageIndex: 0, pageSize: pageSize ?? DEFAULT_PAGE_SIZE },
128
+ columnSizing: {},
129
+ columnOrder: [],
130
+ columnPinning: {
131
+ start: columns.filter((c) => c.pin === "left").map((c) => c.key),
132
+ end: columns.filter((c) => c.pin === "right").map((c) => c.key),
133
+ },
134
+ columnVisibility: Object.fromEntries(columns.filter((c) => c.hidden).map((c) => [c.key, false])),
135
+ };
136
+ }
137
+ const storageKey = (key) => `sprout-table:${key}`;
138
+ function readLayout(key) {
139
+ try {
140
+ const raw = localStorage.getItem(storageKey(key));
141
+ return raw ? JSON.parse(raw) : {};
142
+ }
143
+ catch {
144
+ return {};
145
+ }
146
+ }
147
+ function writeLayout(key, state) {
148
+ try {
149
+ localStorage.setItem(storageKey(key), JSON.stringify(Object.fromEntries(tableLayoutKeys.map((k) => [k, state[k]]))));
150
+ }
151
+ catch {
152
+ // Storage can be full or blocked; the Layout then lasts for this visit only.
153
+ }
154
+ }
155
+ /**
156
+ * A data table. Pass rows and column settings; it renders the header, body,
157
+ * Toolbar, and footer, and runs sorting, filtering, grouping, selection,
158
+ * paging, Row edit, and Layout changes for you.
159
+ */
160
+ export function Table(props) {
161
+ const { data, columns, getRowId, rowLabel = getRowId, variant = "plain", narrow = "scroll", caption, manual = false, rowCount, onStateChange, persistKey, searchable = false, columnMenu = false, resizable = false, reorderable = false, selectable = false, onSelectionChange, pageSize, virtual = false, onRowReorder, getSubRows, onRowSave, getRowHref, onRowClick, onRowDrop, actions, loading = false, empty, className, } = props;
162
+ const label = props["aria-label"];
163
+ const [inner, setInner] = useState(() => initialState(columns, pageSize));
164
+ const state = { ...inner, ...props.state };
165
+ const stateRef = useRef(state);
166
+ stateRef.current = state;
167
+ const [rowSelection, setRowSelection] = useState({});
168
+ const [editing, setEditing] = useState(null);
169
+ const [dropTarget, setDropTarget] = useState(null);
170
+ const [pinOffsets, setPinOffsets] = useState({});
171
+ const headRowRef = useRef(null);
172
+ const anchorRef = useRef(null);
173
+ useEffect(() => {
174
+ if (!persistKey)
175
+ return;
176
+ const saved = readLayout(persistKey);
177
+ if (Object.keys(saved).length)
178
+ setInner((prev) => ({ ...prev, ...saved }));
179
+ }, [persistKey]);
180
+ const setSlice = (key) => (updater) => {
181
+ const next = {
182
+ ...stateRef.current,
183
+ [key]: functionalUpdate(updater, stateRef.current[key]),
184
+ };
185
+ if (pageResetKeys.has(key) && next.pagination.pageIndex !== 0) {
186
+ next.pagination = { ...next.pagination, pageIndex: 0 };
187
+ }
188
+ stateRef.current = next;
189
+ setInner(next);
190
+ if (persistKey && tableLayoutKeys.includes(key)) {
191
+ writeLayout(persistKey, next);
192
+ }
193
+ onStateChange?.(next);
194
+ };
195
+ const byKey = useMemo(() => new Map(columns.map((c) => [c.key, c])), [columns]);
196
+ const columnDefs = useMemo(() => columns.map((c) => ({
197
+ id: c.key,
198
+ accessorFn: (row) => row[c.key],
199
+ header: c.header,
200
+ enableSorting: c.sort ?? false,
201
+ sortDescFirst: false,
202
+ enableColumnFilter: !!c.filter,
203
+ enableGrouping: c.group ?? false,
204
+ ...(c.filter ? { filterFn: filterFnFor[c.filter] } : {}),
205
+ ...(c.aggregate ? { aggregationFn: c.aggregate } : {}),
206
+ ...(c.width ? { size: c.width } : {}),
207
+ })), [columns]);
208
+ const table = useTable({
209
+ features,
210
+ columns: columnDefs,
211
+ data,
212
+ getRowId,
213
+ ...(getSubRows ? { getSubRows } : {}),
214
+ state: { ...state, rowSelection },
215
+ onSortingChange: setSlice("sorting"),
216
+ onColumnFiltersChange: setSlice("columnFilters"),
217
+ onGlobalFilterChange: setSlice("globalFilter"),
218
+ onGroupingChange: setSlice("grouping"),
219
+ onExpandedChange: setSlice("expanded"),
220
+ onPaginationChange: setSlice("pagination"),
221
+ onColumnSizingChange: setSlice("columnSizing"),
222
+ onColumnOrderChange: setSlice("columnOrder"),
223
+ onColumnPinningChange: setSlice("columnPinning"),
224
+ onColumnVisibilityChange: setSlice("columnVisibility"),
225
+ onRowSelectionChange: (updater) => {
226
+ const next = functionalUpdate(updater, rowSelection);
227
+ setRowSelection(next);
228
+ onSelectionChange?.(Object.keys(next).filter((id) => next[id]));
229
+ },
230
+ globalFilterFn: "includesString",
231
+ groupedColumnMode: "remove",
232
+ // The reset would use TanStack's empty default, closing every group on each search.
233
+ autoResetExpanded: false,
234
+ // TanStack resets on every new state array (inline or URL state) and never in manual mode; setSlice resets instead.
235
+ autoResetPageIndex: false,
236
+ columnResizeMode: "onChange",
237
+ enableMultiSort: true,
238
+ manualSorting: manual,
239
+ manualFiltering: manual,
240
+ manualPagination: manual || !pageSize,
241
+ ...(rowCount !== undefined ? { rowCount } : {}),
242
+ });
243
+ const rows = table.getRowModel().rows;
244
+ // getVisibleLeafColumns keeps the column order but ignores pinning; the header follows pinning.
245
+ const leafColumns = [
246
+ ...table.getStartVisibleLeafColumns(),
247
+ ...table.getCenterVisibleLeafColumns(),
248
+ ...table.getEndVisibleLeafColumns(),
249
+ ];
250
+ const editable = !!onRowSave && columns.some((c) => c.edit);
251
+ const hasActions = !!actions || editable;
252
+ const draggableRows = !!onRowReorder &&
253
+ state.sorting.length === 0 &&
254
+ state.grouping.length === 0 &&
255
+ state.columnFilters.length === 0 &&
256
+ !state.globalFilter;
257
+ const totals = columns.some((c) => c.aggregate) && !loading && rows.length > 0;
258
+ const colSpan = leafColumns.length +
259
+ (selectable ? 1 : 0) +
260
+ (onRowReorder ? 1 : 0) +
261
+ (hasActions ? 1 : 0);
262
+ const sized = resizable || columns.some((c) => c.width);
263
+ const pinsStart = !!state.columnPinning.start?.length;
264
+ const pinned = pinsStart || !!state.columnPinning.end?.length;
265
+ const pinLayout = `${leafColumns.map((c) => `${c.id}:${c.getIsPinned()}`).join()}|${selectable}|${!!onRowReorder}|${hasActions}`;
266
+ useLayoutEffect(() => {
267
+ const row = headRowRef.current;
268
+ if (!pinned || !row)
269
+ return;
270
+ const observer = new ResizeObserver(() => {
271
+ const next = measurePinOffsets(Array.from(row.children));
272
+ setPinOffsets((prev) => JSON.stringify(prev) === JSON.stringify(next) ? prev : next);
273
+ });
274
+ for (const cell of row.children)
275
+ observer.observe(cell);
276
+ return () => observer.disconnect();
277
+ }, [pinned, pinLayout]);
278
+ const selectRow = (row, checked, range) => {
279
+ const ids = rows
280
+ .filter((r) => !r.getIsGrouped() && r.getCanSelect())
281
+ .map((r) => r.id);
282
+ const from = anchorRef.current ? ids.indexOf(anchorRef.current) : -1;
283
+ const to = ids.indexOf(row.id);
284
+ if (range && from !== -1 && to !== -1) {
285
+ const span = ids.slice(Math.min(from, to), Math.max(from, to) + 1);
286
+ table.setRowSelection((prev) => {
287
+ const next = { ...prev };
288
+ for (const id of span) {
289
+ if (checked)
290
+ next[id] = true;
291
+ else
292
+ delete next[id];
293
+ }
294
+ return next;
295
+ });
296
+ }
297
+ else {
298
+ row.toggleSelected(checked);
299
+ }
300
+ anchorRef.current = row.id;
301
+ };
302
+ const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 4 } }), useSensor(KeyboardSensor, {
303
+ coordinateGetter: sortableKeyboardCoordinates,
304
+ }));
305
+ const scrollRef = useRef(null);
306
+ const virtualizer = useVirtualizer({
307
+ count: virtual ? rows.length : 0,
308
+ getScrollElement: () => scrollRef.current,
309
+ estimateSize: () => 41,
310
+ overscan: 8,
311
+ initialRect: { width: 0, height: 600 },
312
+ });
313
+ const virtualItems = virtualizer.getVirtualItems();
314
+ const visibleRows = virtual
315
+ ? virtualItems.flatMap((item) => rows[item.index] ?? [])
316
+ : rows;
317
+ const paddingTop = virtual ? (virtualItems[0]?.start ?? 0) : 0;
318
+ const paddingBottom = virtual
319
+ ? virtualizer.getTotalSize() - (virtualItems.at(-1)?.end ?? 0)
320
+ : 0;
321
+ const startEdit = (row) => setEditing({
322
+ id: row.id,
323
+ draft: Object.fromEntries(columns.filter((c) => c.edit).map((c) => [c.key, row.original[c.key]])),
324
+ });
325
+ const saveEdit = async (row) => {
326
+ if (!editing || !onRowSave)
327
+ return;
328
+ const changes = Object.fromEntries(Object.entries(editing.draft).filter(([key, value]) => row.original[key] !== value));
329
+ setEditing({ ...editing, saving: true });
330
+ try {
331
+ await onRowSave(row.original, changes);
332
+ setEditing(null);
333
+ }
334
+ catch (error) {
335
+ setEditing({
336
+ ...editing,
337
+ saving: false,
338
+ error: error instanceof Error ? error.message : String(error),
339
+ });
340
+ }
341
+ };
342
+ const onColumnDragEnd = ({ active, over }) => {
343
+ if (!over || active.id === over.id)
344
+ return;
345
+ const order = state.columnOrder.length
346
+ ? state.columnOrder
347
+ : table.getAllLeafColumns().map((c) => c.id);
348
+ table.setColumnOrder(arrayMove(order, order.indexOf(String(active.id)), order.indexOf(String(over.id))));
349
+ };
350
+ const onRowDragEnd = ({ active, over }) => {
351
+ if (!over || active.id === over.id || !onRowReorder)
352
+ return;
353
+ const ids = data.map(getRowId);
354
+ onRowReorder(ids.indexOf(String(active.id)), ids.indexOf(String(over.id)));
355
+ };
356
+ const toolbar = searchable || columns.some((c) => c.filter || c.group) || columnMenu;
357
+ const allPageSelected = table.getIsAllPageRowsSelected();
358
+ const somePageSelected = table.getIsSomePageRowsSelected();
359
+ const stack = narrow === "stack";
360
+ const role = (value) => (stack ? value : undefined);
361
+ const header = (_jsx("thead", { "data-slot": "table-header", className: "sprout-table-header", role: role("rowgroup"), children: _jsx(DndContext, { sensors: sensors, accessibility: dndAccessibility, collisionDetection: closestCenter, onDragEnd: onColumnDragEnd, children: _jsx(SortableContext, { items: leafColumns.map((c) => c.id), strategy: horizontalListSortingStrategy, children: _jsxs("tr", { ref: headRowRef, "data-slot": "table-row", className: "sprout-table-row", role: role("row"), children: [onRowReorder && (_jsx("th", { className: "sprout-table-head", "data-selection": "", "data-column-id": "__reorder", role: role("columnheader"), children: _jsx("span", { className: "sprout-table-sr-only", children: "Reorder" }) })), selectable && (_jsx("th", { className: "sprout-table-head", "data-selection": "", "data-column-id": "__select", role: role("columnheader"), children: _jsx(Checkbox, { "aria-label": "Select all rows", checked: allPageSelected, indeterminate: !allPageSelected && somePageSelected, onCheckedChange: (checked) => table.toggleAllPageRowsSelected(checked) }) })), table
362
+ .getFlatHeaders()
363
+ .map((h) => h.column.getIsVisible() ? (_jsx(HeadCell, { header: h, config: byKey.get(h.column.id), resizable: resizable, reorderable: reorderable, sized: sized, role: role("columnheader") }, h.id)) : null), hasActions && (_jsx("th", { className: "sprout-table-head", "data-sticky-end": "", "data-column-id": "__actions", role: role("columnheader"), children: _jsx("span", { className: "sprout-table-sr-only", children: "Actions" }) }))] }) }) }) }));
364
+ const body = (_jsxs("tbody", { "data-slot": "table-body", className: "sprout-table-body", role: role("rowgroup"), children: [paddingTop > 0 && (_jsx("tr", { "aria-hidden": "true", children: _jsx("td", { colSpan: colSpan, style: { height: paddingTop, padding: 0 } }) })), loading ? (Array.from({ length: 3 }, (_, i) => (_jsxs("tr", { className: "sprout-table-row", "data-loading-row": "", role: role("row"), children: [onRowReorder && (_jsx("td", { className: "sprout-table-cell", "data-selection": "" })), selectable && (_jsx("td", { className: "sprout-table-cell", "data-selection": "", children: _jsx(Skeleton, { shape: "checkbox" }) })), leafColumns.map((c) => (_jsx("td", { className: "sprout-table-cell", role: role("cell"), children: _jsx(Skeleton, {}) }, c.id))), hasActions && (_jsx("td", { className: "sprout-table-cell", "data-sticky-end": "", children: _jsx(Skeleton, { shape: "action" }) }))] }, i)))) : rows.length === 0 ? (_jsx("tr", { className: "sprout-table-row", role: role("row"), children: _jsx("td", { className: "sprout-table-cell sprout-table-empty", colSpan: colSpan, role: role("cell"), children: empty ?? "No results." }) })) : (_jsx(DndContext, { sensors: sensors, accessibility: dndAccessibility, collisionDetection: closestCenter, onDragEnd: onRowDragEnd, children: _jsx(SortableContext, { items: rows.map((r) => r.id), strategy: verticalListSortingStrategy, children: visibleRows.map((row) => row.getIsGrouped() ? (_jsx(GroupRow, { row: row, columns: leafColumns, byKey: byKey, selectable: selectable, reorder: !!onRowReorder, hasActions: hasActions, rowIndex: virtual ? row.index + 2 : undefined, role: role }, row.id)) : (_jsx(BodyRow, { row: row, columns: leafColumns, byKey: byKey, label: rowLabel(row.original), selectable: selectable, onSelect: (checked, range) => selectRow(row, checked, range), reorder: onRowReorder ? draggableRows : undefined, editing: editing?.id === row.id ? editing : null, editable: editable, onEditChange: (key, value) => setEditing((prev) => prev && {
365
+ ...prev,
366
+ draft: { ...prev.draft, [key]: value },
367
+ }), onEdit: () => startEdit(row), onSave: () => void saveEdit(row), onCancel: () => setEditing(null), actions: actions, hasActions: hasActions, href: getRowHref?.(row.original), onOpen: onRowClick ? () => onRowClick(row.original) : undefined, drop: onRowDrop
368
+ ? {
369
+ active: dropTarget === row.id,
370
+ set: (on) => setDropTarget(on ? row.id : null),
371
+ onDrop: (files) => onRowDrop(row.original, files),
372
+ }
373
+ : undefined, colSpan: colSpan, rowIndex: virtual ? row.index + 2 : undefined, role: role }, row.id))) }) })), paddingBottom > 0 && (_jsx("tr", { "aria-hidden": "true", children: _jsx("td", { colSpan: colSpan, style: { height: paddingBottom, padding: 0 } }) }))] }));
374
+ const footer = totals && (_jsx("tfoot", { "data-slot": "table-footer", className: "sprout-table-footer", role: role("rowgroup"), children: _jsxs("tr", { className: "sprout-table-row", role: role("row"), children: [onRowReorder && _jsx("td", { className: "sprout-table-cell", "data-selection": "" }), selectable && _jsx("td", { className: "sprout-table-cell", "data-selection": "" }), leafColumns.map((c, index) => {
375
+ const config = byKey.get(c.id);
376
+ return (_jsx("td", { className: "sprout-table-cell", "data-align": config?.align ?? "left", "data-label": config?.header, role: role("cell"), style: pinStyle(c, sized, pinOffsets), "data-pinned": c.getIsPinned() || undefined, children: config?.aggregate
377
+ ? formatAggregate(config, c.getAggregationValue())
378
+ : index === 0
379
+ ? "Total"
380
+ : null }, c.id));
381
+ }), hasActions && _jsx("td", { className: "sprout-table-cell", "data-sticky-end": "" })] }) }));
382
+ const tableElement = (_jsxs("table", { "data-slot": "table", "data-variant": variant, "data-narrow": narrow, "data-sized": sized || undefined, "data-pin-start": pinsStart || undefined, className: "sprout-table", "aria-label": label, "aria-rowcount": virtual ? rows.length + 1 : undefined, "aria-busy": loading || undefined, role: role("table"), style: {
383
+ ...(sized ? { minWidth: table.getTotalSize() } : {}),
384
+ ...(pinOffsets.__select !== undefined
385
+ ? { "--sprout-table-select-start": `${pinOffsets.__select}px` }
386
+ : {}),
387
+ }, children: [caption && (_jsx("caption", { "data-slot": "table-caption", className: "sprout-table-caption", children: caption })), _jsxs(PinOffsetsContext, { value: pinOffsets, children: [header, body] }), footer] }));
388
+ const pageCount = table.getPageCount();
389
+ return (_jsx(TooltipProvider, { children: _jsxs("div", { "data-slot": "table-root", className: cn("sprout-table-root", className), children: [toolbar && (_jsx(Toolbar, { table: table, columns: columns, label: label, searchable: searchable, columnMenu: columnMenu, onReset: () => {
390
+ const reset = initialState(columns, pageSize);
391
+ for (const key of tableLayoutKeys)
392
+ setSlice(key)(reset[key]);
393
+ } })), virtual ? (_jsx("div", { ref: scrollRef, "data-slot": "table-container", "data-virtual": "", className: "sprout-table-container", tabIndex: 0, role: "region", "aria-label": label, children: tableElement })) : variant === "grid" ? (_jsx(ScrollArea, { variant: "table", orientation: "both", viewportLabel: label, "data-slot": "table-container", children: tableElement })) : (_jsx(PlainContainer, { label: label, children: tableElement })), pageSize && pageCount > 1 && (_jsxs("nav", { className: "sprout-table-pages", "aria-label": `${label} pages`, children: [_jsxs("span", { "aria-live": "polite", children: ["Page ", state.pagination.pageIndex + 1, " of ", pageCount] }), _jsx(Button, { variant: "outline", size: "sm", disabled: !table.getCanPreviousPage(), onClick: () => table.previousPage(), children: "Previous" }), _jsx(Button, { variant: "outline", size: "sm", disabled: !table.getCanNextPage(), onClick: () => table.nextPage(), children: "Next" })] }))] }) }));
394
+ }
395
+ const PinOffsetsContext = createContext({});
396
+ /** Sticky offsets from the real header cell widths, so columns without a set width pin in the right place. */
397
+ function measurePinOffsets(cells) {
398
+ const offsets = {};
399
+ let start = 0;
400
+ for (const cell of cells) {
401
+ const id = cell.dataset.columnId;
402
+ if (!id ||
403
+ (cell.dataset.selection === undefined && cell.dataset.pinned !== "start"))
404
+ continue;
405
+ offsets[id] = start;
406
+ start += cell.getBoundingClientRect().width;
407
+ }
408
+ let end = 0;
409
+ for (const cell of [...cells].reverse()) {
410
+ const id = cell.dataset.columnId;
411
+ if (!id ||
412
+ (cell.dataset.stickyEnd === undefined && cell.dataset.pinned !== "end"))
413
+ continue;
414
+ offsets[id] = end;
415
+ end += cell.getBoundingClientRect().width;
416
+ }
417
+ return offsets;
418
+ }
419
+ function pinStyle(column, sized, offsets) {
420
+ const pinned = column.getIsPinned();
421
+ const width = sized ? column.getSize() : undefined;
422
+ if (!pinned)
423
+ return width ? { width } : undefined;
424
+ return pinned === "start"
425
+ ? {
426
+ width,
427
+ insetInlineStart: offsets[column.id] ?? column.getStart("start"),
428
+ }
429
+ : { width, insetInlineEnd: offsets[column.id] ?? column.getAfter("end") };
430
+ }
431
+ function formatAggregate(config, value) {
432
+ if (typeof value !== "number")
433
+ return null;
434
+ return config.formatAggregate
435
+ ? config.formatAggregate(value)
436
+ : value.toLocaleString();
437
+ }
438
+ /** Joins the Tab order only while the table is wider than its container, so keyboard users can scroll it. */
439
+ function PlainContainer({ label, children, }) {
440
+ const ref = useRef(null);
441
+ const [overflowing, setOverflowing] = useState(false);
442
+ useEffect(() => {
443
+ const element = ref.current;
444
+ if (!element || typeof ResizeObserver === "undefined")
445
+ return;
446
+ const measure = () => setOverflowing(element.scrollWidth > element.clientWidth);
447
+ measure();
448
+ const observer = new ResizeObserver(measure);
449
+ observer.observe(element);
450
+ if (element.firstElementChild)
451
+ observer.observe(element.firstElementChild);
452
+ return () => observer.disconnect();
453
+ }, []);
454
+ return (_jsx("div", { ref: ref, "data-slot": "table-container", "data-overflowing": overflowing || undefined, className: "sprout-table-container", tabIndex: overflowing ? 0 : undefined, role: overflowing ? "region" : undefined, "aria-label": overflowing ? label : undefined, children: children }));
455
+ }
456
+ function HeadCell({ header, config, resizable, reorderable, sized, role, }) {
457
+ const column = header.column;
458
+ const title = config?.header ?? column.id;
459
+ const align = config?.align ?? "left";
460
+ const sorted = column.getIsSorted();
461
+ const sortable = column.getCanSort();
462
+ const sortable_ = useSortable({ id: column.id, disabled: !reorderable });
463
+ const pinned = column.getIsPinned();
464
+ const offsets = useContext(PinOffsetsContext);
465
+ const style = {
466
+ ...pinStyle(column, sized, offsets),
467
+ ...(reorderable && sortable_.transform
468
+ ? {
469
+ transform: CSS.Translate.toString(sortable_.transform),
470
+ transition: sortable_.transition,
471
+ }
472
+ : {}),
473
+ };
474
+ const next = column.getNextSortingOrder();
475
+ const hint = sorted === "asc"
476
+ ? `, ascending. ${next === "desc" ? "Select for descending" : "Select to clear"}`
477
+ : sorted === "desc"
478
+ ? `, descending. ${next === "asc" ? "Select for ascending" : "Select to clear"}`
479
+ : "";
480
+ const Icon = sorted === "asc" ? ArrowUp : sorted === "desc" ? ArrowDown : SortV;
481
+ const onResizeKey = (event) => {
482
+ const step = event.shiftKey ? 40 : 10;
483
+ const delta = event.key === "ArrowRight" ? step : event.key === "ArrowLeft" ? -step : 0;
484
+ if (!delta)
485
+ return;
486
+ event.preventDefault();
487
+ column.table.setColumnSizing((prev) => ({
488
+ ...prev,
489
+ [column.id]: Math.max(48, column.getSize() + delta),
490
+ }));
491
+ };
492
+ return (_jsxs("th", { ref: reorderable ? sortable_.setNodeRef : undefined, "data-slot": "table-head", "data-align": align, "data-pinned": pinned || undefined, "data-column-id": column.id, "data-dragging": sortable_.isDragging || undefined, className: "sprout-table-head", "aria-sort": sorted === "asc"
493
+ ? "ascending"
494
+ : sorted === "desc"
495
+ ? "descending"
496
+ : undefined, role: role, style: style, children: [_jsxs("span", { className: "sprout-table-head-inner", children: [reorderable && (_jsx("button", { type: "button", className: "sprout-table-handle", "aria-label": `Move ${title} column`, ...sortable_.attributes, ...sortable_.listeners, children: _jsx(Reorder, { size: 14, "aria-hidden": "true" }) })), sortable ? (_jsxs(Button, { variant: sorted ? "secondary" : "ghost", size: align === "right" ? "column-header-end" : "column-header", "aria-label": `Sort by ${title}${hint}`, title: sorted ? undefined : "Sort ascending", onClick: column.getToggleSortingHandler(), children: [title, _jsx(Icon, { size: 14, "aria-hidden": "true" })] })) : (title), config?.hint && (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { render: _jsx("button", { type: "button", "aria-label": `About ${title}`, className: "sprout-table-hint" }), children: _jsx(InfoCircle, { size: 14, "aria-hidden": "true" }) }), _jsx(TooltipContent, { children: config.hint })] }))] }), resizable && (_jsx("div", { role: "separator", "aria-orientation": "vertical", "aria-label": `Resize ${title} column`, "aria-valuenow": column.getSize(), "aria-valuemin": 48, tabIndex: 0, className: "sprout-table-resizer", "data-resizing": column.getIsResizing() || undefined, onMouseDown: header.getResizeHandler(), onTouchStart: header.getResizeHandler(), onKeyDown: onResizeKey, onDoubleClick: () => column.resetSize() }))] }));
497
+ }
498
+ function GroupRow({ row, columns, byKey, selectable, reorder, hasActions, rowIndex, role, }) {
499
+ const groupColumn = byKey.get(row.groupingColumnId ?? "");
500
+ const value = row.groupingValue;
501
+ const option = groupColumn?.options?.find((o) => o.value === String(value));
502
+ const name = option?.label ?? (value == null || value === "" ? "None" : String(value));
503
+ const expanded = row.getIsExpanded();
504
+ const leafRows = row.getLeafRows().filter((r) => !r.getIsGrouped());
505
+ const offsets = useContext(PinOffsetsContext);
506
+ return (_jsxs("tr", { "data-slot": "table-row", "data-group": "", className: "sprout-table-row", "aria-rowindex": rowIndex, role: role("row"), children: [reorder && _jsx("td", { className: "sprout-table-cell", "data-selection": "" }), selectable && _jsx("td", { className: "sprout-table-cell", "data-selection": "" }), columns.map((column, index) => {
507
+ const config = byKey.get(column.id);
508
+ return (_jsx("td", { className: "sprout-table-cell", "data-align": index === 0 ? "left" : (config?.align ?? "left"), "data-pinned": column.getIsPinned() || undefined, role: role("cell"), style: pinStyle(column, false, offsets), children: index === 0 ? (_jsxs("button", { type: "button", className: "sprout-table-toggle", "aria-expanded": expanded, onClick: row.getToggleExpandedHandler(), style: { paddingInlineStart: row.depth * 16 }, children: [expanded ? (_jsx(ChevronDown, { size: 14, "aria-hidden": "true" })) : (_jsx(ChevronRight, { size: 14, "aria-hidden": "true" })), _jsxs("span", { className: "sprout-table-group-name", children: [groupColumn?.header, ": ", name] }), _jsx("span", { className: "sprout-table-group-count", children: leafRows.length })] })) : config?.aggregate ? (formatAggregate(config, column.getAggregationValue({ rows: leafRows }))) : null }, column.id));
509
+ }), hasActions && _jsx("td", { className: "sprout-table-cell", "data-sticky-end": "" })] }));
510
+ }
511
+ function BodyRow({ row, columns, byKey, label, selectable, onSelect, reorder, editing, editable, onEditChange, onEdit, onSave, onCancel, actions, hasActions, href, onOpen, drop, colSpan, rowIndex, role, }) {
512
+ const sortable = useSortable({ id: row.id, disabled: !reorder });
513
+ const selected = row.getIsSelected();
514
+ const offsets = useContext(PinOffsetsContext);
515
+ const hasFiles = (event) => event.dataTransfer.types.includes("Files");
516
+ const onKeyDown = (event) => {
517
+ if (!editing)
518
+ return;
519
+ if (event.key === "Escape") {
520
+ event.preventDefault();
521
+ onCancel();
522
+ }
523
+ else if (event.key === "Enter" &&
524
+ event.target instanceof HTMLInputElement) {
525
+ event.preventDefault();
526
+ onSave();
527
+ }
528
+ };
529
+ return (_jsxs(_Fragment, { children: [_jsxs("tr", { ref: reorder !== undefined ? sortable.setNodeRef : undefined, "data-slot": "table-row", "data-state": selected ? "selected" : undefined, "data-editing": editing ? "" : undefined, "data-drop-target": drop?.active || undefined, "data-linked": href || onOpen ? "" : undefined, "data-dragging": sortable.isDragging || undefined, className: "sprout-table-row", "aria-rowindex": rowIndex, "aria-selected": selectable ? selected : undefined, role: role("row"), style: sortable.transform
530
+ ? {
531
+ transform: CSS.Translate.toString(sortable.transform),
532
+ transition: sortable.transition,
533
+ }
534
+ : undefined, onKeyDown: onKeyDown, onDragOver: drop
535
+ ? (event) => {
536
+ if (!hasFiles(event))
537
+ return;
538
+ event.preventDefault();
539
+ if (!drop.active)
540
+ drop.set(true);
541
+ }
542
+ : undefined, onDragLeave: drop
543
+ ? (event) => {
544
+ if (!event.currentTarget.contains(event.relatedTarget))
545
+ drop.set(false);
546
+ }
547
+ : undefined, onDrop: drop
548
+ ? (event) => {
549
+ if (!hasFiles(event))
550
+ return;
551
+ event.preventDefault();
552
+ drop.set(false);
553
+ drop.onDrop(Array.from(event.dataTransfer.files));
554
+ }
555
+ : undefined, children: [reorder !== undefined && (_jsx("td", { className: "sprout-table-cell", "data-selection": "", role: role("cell"), children: _jsx("button", { type: "button", className: "sprout-table-handle", "aria-label": `Move ${label}`, disabled: !reorder, title: reorder
556
+ ? undefined
557
+ : "Clear sorting, filters, and grouping to reorder", ...sortable.attributes, ...sortable.listeners, children: _jsx(Reorder, { size: 14, "aria-hidden": "true" }) }) })), selectable && (_jsx("td", { className: "sprout-table-cell", "data-selection": "", role: role("cell"), children: _jsx(Checkbox, { "aria-label": `Select ${label}`, checked: selected, disabled: !row.getCanSelect(), onCheckedChange: (checked, details) => onSelect(checked, details.event.shiftKey ===
558
+ true) }) })), columns.map((column, index) => {
559
+ const config = byKey.get(column.id);
560
+ const content = config?.cell
561
+ ? config.cell(row.original)
562
+ : row.getValue(column.id);
563
+ const first = index === 0;
564
+ const canExpand = first && row.getCanExpand();
565
+ return (_jsx("td", { "data-slot": "table-cell", "data-align": config?.align ?? "left", "data-pinned": column.getIsPinned() || undefined, "data-label": config?.header, className: "sprout-table-cell", role: role("cell"), style: pinStyle(column, false, offsets), children: editing && config?.edit ? (_jsx(Editor, { config: config, label: `${config.header} for ${label}`, value: editing.draft[column.id], onChange: (value) => onEditChange(column.id, value), autoFocus: first ||
566
+ columns.findIndex((c) => byKey.get(c.id)?.edit) === index })) : first ? (_jsxs("span", { className: "sprout-table-first", style: row.depth
567
+ ? { paddingInlineStart: row.depth * 16 }
568
+ : undefined, children: [canExpand && (_jsx("button", { type: "button", className: "sprout-table-toggle", "aria-expanded": row.getIsExpanded(), "aria-label": `${row.getIsExpanded() ? "Collapse" : "Expand"} ${label}`, onClick: row.getToggleExpandedHandler(), children: row.getIsExpanded() ? (_jsx(ChevronDown, { size: 14, "aria-hidden": "true" })) : (_jsx(ChevronRight, { size: 14, "aria-hidden": "true" })) })), href ? (_jsx("a", { className: "sprout-table-link", href: href, children: content })) : onOpen ? (_jsx("button", { type: "button", className: "sprout-table-link", onClick: onOpen, children: content })) : (content)] })) : (content) }, column.id));
569
+ }), hasActions && (_jsx("td", { className: "sprout-table-cell", "data-sticky-end": "", role: role("cell"), children: _jsx("span", { className: "sprout-table-actions", children: editing ? (_jsxs(_Fragment, { children: [_jsx(Button, { size: "sm", disabled: editing.saving, onClick: onSave, children: editing.saving ? "Saving" : "Save" }), _jsx(Button, { size: "sm", variant: "ghost", disabled: editing.saving, onClick: onCancel, children: "Cancel" })] })) : (_jsxs(_Fragment, { children: [editable && (_jsx(Button, { size: "sm", variant: "ghost", "aria-label": `Edit ${label}`, onClick: onEdit, children: "Edit" })), actions?.(row.original)] })) }) }))] }), editing?.error && (_jsx("tr", { className: "sprout-table-row", "data-error-row": "", role: role("row"), children: _jsx("td", { colSpan: colSpan, className: "sprout-table-cell sprout-table-error", role: "alert", children: editing.error }) }))] }));
570
+ }
571
+ function Editor({ config, label, value, onChange, autoFocus, }) {
572
+ if (config.edit === "checkbox") {
573
+ return (_jsx(Checkbox, { "aria-label": label, checked: Boolean(value), onCheckedChange: (checked) => onChange(checked), autoFocus: autoFocus }));
574
+ }
575
+ if (config.edit === "select") {
576
+ return (_jsx("select", { "aria-label": label, className: "sprout-table-select", value: String(value ?? ""), onChange: (event) => onChange(event.target.value), autoFocus: autoFocus, children: config.options?.map((o) => (_jsx("option", { value: o.value, children: o.label }, o.value))) }));
577
+ }
578
+ return (_jsx(Input, { "aria-label": label, type: config.edit === "number"
579
+ ? "number"
580
+ : config.edit === "date"
581
+ ? "date"
582
+ : "text", value: value == null ? "" : String(value), onValueChange: (next) => onChange(config.edit === "number" ? (next === "" ? null : Number(next)) : next), autoFocus: autoFocus, className: "sprout-table-input" }));
583
+ }
584
+ function Toolbar({ table, columns, label, searchable, columnMenu, onReset, }) {
585
+ const [pending, setPending] = useState([]);
586
+ const [open, setOpen] = useState(null);
587
+ const filters = table.state.columnFilters;
588
+ const filterable = columns.filter((c) => c.filter);
589
+ const groupable = columns.filter((c) => c.group);
590
+ const grouping = table.state.grouping;
591
+ const chips = filterable.filter((c) => pending.includes(c.key) || filters.some((f) => f.id === c.key));
592
+ return (_jsxs("div", { className: "sprout-table-toolbar", role: "toolbar", "aria-label": `${label} tools`, children: [searchable && (_jsx(SearchField, { label: `Search ${label}`, value: String(table.state.globalFilter ?? ""), onChange: (value) => table.setGlobalFilter(value) })), chips.map((c) => {
593
+ const column = table.getColumn(c.key);
594
+ if (!column)
595
+ return null;
596
+ return (_jsxs("span", { className: "sprout-table-chip", children: [_jsxs(Popover, { open: open === c.key, onOpenChange: (next) => setOpen(next ? c.key : null), children: [_jsxs(PopoverTrigger, { className: "sprout-table-chip-label", children: [c.header, describeFilter(c, column.getFilterValue())] }), _jsx(PopoverContent, { align: "start", children: _jsx(FilterEditor, { config: c, column: column }) })] }), _jsx("button", { type: "button", className: "sprout-table-chip-remove", "aria-label": `Remove ${c.header} filter`, onClick: () => {
597
+ column.setFilterValue(undefined);
598
+ setPending((p) => p.filter((k) => k !== c.key));
599
+ }, children: _jsx(X, { size: 12, "aria-hidden": "true" }) })] }, c.key));
600
+ }), filterable.length > chips.length && (_jsxs(DropdownMenu, { children: [_jsxs(DropdownMenuTrigger, { render: _jsx(Button, { variant: "ghost", size: "sm" }), children: [_jsx(Plus, { size: 14, "aria-hidden": "true" }), "Filter"] }), _jsx(DropdownMenuContent, { align: "start", size: "menu", children: filterable
601
+ .filter((c) => !chips.includes(c))
602
+ .map((c) => (_jsx(DropdownMenuItem, { onClick: () => {
603
+ setPending((p) => [...p, c.key]);
604
+ setOpen(c.key);
605
+ }, children: c.header }, c.key))) })] })), _jsxs("span", { className: "sprout-table-toolbar-end", children: [groupable.length > 0 && (_jsxs(DropdownMenu, { children: [_jsxs(DropdownMenuTrigger, { render: _jsx(Button, { variant: "ghost", size: "sm" }), children: [grouping.length ? `Group: ${grouping.length}` : "Group", _jsx(ChevronDown, { size: 14, "aria-hidden": "true" })] }), _jsx(DropdownMenuContent, { align: "end", size: "menu", children: groupable.map((c) => (_jsx(DropdownMenuCheckboxItem, { checked: grouping.includes(c.key), onCheckedChange: () => table.getColumn(c.key)?.toggleGrouping(), children: c.header }, c.key))) })] })), columnMenu && (_jsxs(DropdownMenu, { children: [_jsxs(DropdownMenuTrigger, { render: _jsx(Button, { variant: "ghost", size: "sm" }), children: ["Columns", _jsx(ChevronDown, { size: 14, "aria-hidden": "true" })] }), _jsxs(DropdownMenuContent, { align: "end", size: "menu", children: [columns.map((c) => {
606
+ const column = table.getColumn(c.key);
607
+ if (!column)
608
+ return null;
609
+ return (_jsxs(DropdownMenuSub, { children: [_jsx(DropdownMenuSubTrigger, { children: c.header }), _jsxs(DropdownMenuSubContent, { children: [_jsx(DropdownMenuCheckboxItem, { checked: column.getIsVisible(), onCheckedChange: (checked) => column.toggleVisibility(checked), children: "Show" }), _jsx(DropdownMenuSeparator, {}), _jsxs(DropdownMenuRadioGroup, { value: column.getIsPinned() || "none", onValueChange: (value) => column.pin(value === "none"
610
+ ? false
611
+ : value), children: [_jsx(DropdownMenuRadioItem, { value: "none", children: "Not pinned" }), _jsx(DropdownMenuRadioItem, { value: "start", children: "Pin to start" }), _jsx(DropdownMenuRadioItem, { value: "end", children: "Pin to end" })] })] })] }, c.key));
612
+ }), _jsx(DropdownMenuSeparator, {}), _jsx(DropdownMenuItem, { onClick: onReset, children: "Reset columns" })] })] }))] })] }));
613
+ }
614
+ function describeFilter(config, value) {
615
+ if (value === undefined)
616
+ return "";
617
+ if (config.filter === "select" && Array.isArray(value)) {
618
+ const labels = value.map((v) => config.options?.find((o) => o.value === v)?.label ?? String(v));
619
+ return `: ${labels.join(", ")}`;
620
+ }
621
+ if ((config.filter === "number" || config.filter === "date") &&
622
+ Array.isArray(value)) {
623
+ const [min, max] = value;
624
+ const has = (v) => v !== undefined && v !== null && v !== "";
625
+ if (has(min) && has(max))
626
+ return `: ${min} – ${max}`;
627
+ if (has(min))
628
+ return `: ≥ ${min}`;
629
+ if (has(max))
630
+ return `: ≤ ${max}`;
631
+ return "";
632
+ }
633
+ if (config.filter === "boolean")
634
+ return `: ${value ? "Yes" : "No"}`;
635
+ return `: ${String(value)}`;
636
+ }
637
+ function FilterEditor({ config, column, }) {
638
+ const value = column.getFilterValue();
639
+ const name = config.header;
640
+ if (config.filter === "select") {
641
+ const selected = value ?? [];
642
+ return (_jsxs("fieldset", { className: "sprout-table-filter", children: [_jsx("legend", { children: name }), config.options?.map((o) => (_jsxs("label", { className: "sprout-table-filter-option", children: [_jsx(Checkbox, { variant: "neutral", checked: selected.includes(o.value), onCheckedChange: (checked) => column.setFilterValue(checked
643
+ ? [...selected, o.value]
644
+ : selected.filter((v) => v !== o.value)) }), o.label] }, o.value)))] }));
645
+ }
646
+ if (config.filter === "boolean") {
647
+ return (_jsxs("fieldset", { className: "sprout-table-filter", children: [_jsx("legend", { children: name }), [true, false].map((option) => (_jsxs("label", { className: "sprout-table-filter-option", children: [_jsx("input", { type: "radio", name: `${column.id}-filter`, checked: value === option, onChange: () => column.setFilterValue(option) }), option ? "Yes" : "No"] }, String(option))))] }));
648
+ }
649
+ if (config.filter === "number" || config.filter === "date") {
650
+ const [min, max] = value ?? [
651
+ undefined,
652
+ undefined,
653
+ ];
654
+ const type = config.filter === "number" ? "number" : "date";
655
+ const set = (index, next) => {
656
+ const parsed = next === "" ? undefined : type === "number" ? Number(next) : next;
657
+ const range = index === 0 ? [parsed, max] : [min, parsed];
658
+ column.setFilterValue(range[0] === undefined && range[1] === undefined ? undefined : range);
659
+ };
660
+ return (_jsxs("fieldset", { className: "sprout-table-filter", "data-range": "", children: [_jsx("legend", { children: name }), _jsx(Input, { "aria-label": `${name} from`, type: type, value: min == null ? "" : String(min), onValueChange: (next) => set(0, next) }), _jsx(Input, { "aria-label": `${name} to`, type: type, value: max == null ? "" : String(max), onValueChange: (next) => set(1, next) })] }));
661
+ }
662
+ return (_jsx(Input, { "aria-label": `${name} contains`, autoFocus: true, value: String(value ?? ""), onValueChange: (next) => column.setFilterValue(next || undefined) }));
663
+ }