@crispy-seed/data-table 0.1.0 → 0.1.2

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 (51) hide show
  1. package/README.md +25 -13
  2. package/dist/index.cjs +4563 -2
  3. package/dist/index.d.ts +8 -1
  4. package/dist/index.js +4547 -1
  5. package/package.json +1 -2
  6. package/dist/DataTable-12s-8LMBtS2u.cjs +0 -1532
  7. package/dist/DataTable-12s-Bd20OuMs.js +0 -1482
  8. package/dist/DataTable-12s-Zml1x9Kr-12s-CTRzgvMT.cjs +0 -161
  9. package/dist/DataTable-12s-Zml1x9Kr-12s-Coi-3lUa.js +0 -153
  10. package/dist/Menu-12s-Db_zr1AP.cjs +0 -104
  11. package/dist/Menu-12s-Di5JADW4.js +0 -78
  12. package/dist/Select-12s-1Uh7XbkG.js +0 -219
  13. package/dist/Select-12s-D8DdKT-U.cjs +0 -250
  14. package/dist/body-cells-12s-BnOVUTKN.js +0 -453
  15. package/dist/body-cells-12s-BoOTcg-l.cjs +0 -462
  16. package/dist/column-calc-12s-C6IPSOaF.js +0 -129
  17. package/dist/column-calc-12s-CKuSpe-l.cjs +0 -149
  18. package/dist/column-layout-12s-CTu5yT_D.cjs +0 -120
  19. package/dist/column-layout-12s-s7wnxkMN.js +0 -99
  20. package/dist/column-menu-12s-BPE9N1gs.cjs +0 -427
  21. package/dist/column-menu-12s-mRBk-4oa.js +0 -407
  22. package/dist/editors-12s-BO9Mp034.js +0 -525
  23. package/dist/editors-12s-DO8TZkYV.cjs +0 -555
  24. package/dist/resize-12s-jQY-PX67.cjs +0 -58
  25. package/dist/resize-12s-nsMvrmLt.js +0 -58
  26. package/dist/states-12s-CLO_uVue.cjs +0 -125
  27. package/dist/states-12s-DEFSxJCO.js +0 -122
  28. package/dist/toolbar-12s-AVU-sbDW.js +0 -69
  29. package/dist/toolbar-12s-CaLgJrDo.cjs +0 -71
  30. package/dist/useDataTable-12s-BHiDUubw-12s-CBeCwTe7.js +0 -61
  31. package/dist/useDataTable-12s-BHiDUubw-12s-Dgvt7XDk.cjs +0 -61
  32. package/dist/useDataTableColumnCalc-12s-DTQQTtEJ-12s-CvD14JYo.cjs +0 -207
  33. package/dist/useDataTableColumnCalc-12s-DTQQTtEJ-12s-DsEse3uq.js +0 -183
  34. package/dist/useDataTableColumns-12s-Cn2ZAzFL-12s-CD6goR59.js +0 -289
  35. package/dist/useDataTableColumns-12s-Cn2ZAzFL-12s-CUk_CpYT.cjs +0 -292
  36. package/dist/useDataTableEdit-12s-9Wb_UCrN-12s-BoOHq3GB.js +0 -98
  37. package/dist/useDataTableEdit-12s-9Wb_UCrN-12s-De6YHBba.cjs +0 -98
  38. package/dist/useDataTableFilter-12s-ZzbmWMin-12s-CWyBeFna.js +0 -74
  39. package/dist/useDataTableFilter-12s-ZzbmWMin-12s-lpjzZY3_.cjs +0 -74
  40. package/dist/useDataTableInfiniteScroll-12s-8ZG31URd-12s-BexjP6iO.js +0 -104
  41. package/dist/useDataTableInfiniteScroll-12s-8ZG31URd-12s-Bt8gojr9.cjs +0 -104
  42. package/dist/useDataTableRow-12s-CPYaDa1p-12s-BmUSAnPe.js +0 -30
  43. package/dist/useDataTableRow-12s-CPYaDa1p-12s-D-DRjwAe.cjs +0 -30
  44. package/dist/useDataTableScroll-12s-BKDCr0en-12s-C7f2vHeG.js +0 -61
  45. package/dist/useDataTableScroll-12s-BKDCr0en-12s-CUvUYMtt.cjs +0 -61
  46. package/dist/useDataTableSearch-12s-Ck3iWcpX-12s-B-65V6gH.js +0 -37
  47. package/dist/useDataTableSearch-12s-Ck3iWcpX-12s-DhVfN2Hq.cjs +0 -37
  48. package/dist/useDataTableSelection-12s-BIDtiRQq-12s-IS__VAnU.cjs +0 -71
  49. package/dist/useDataTableSelection-12s-BIDtiRQq-12s-O6M_BhrH.js +0 -71
  50. package/dist/useDataTableSort-12s-DvJYtmeS-12s-C5VGnsq4.cjs +0 -78
  51. package/dist/useDataTableSort-12s-DvJYtmeS-12s-DABSNt6U.js +0 -78
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ 'use client';
1
2
  function __insertCSS(code) {
2
3
  if (!code || typeof document == 'undefined') return
3
4
  let head = document.head || document.getElementsByTagName('head')[0]
@@ -7,4 +8,4549 @@ function __insertCSS(code) {
7
8
  ;style.styleSheet ? (style.styleSheet.cssText = code) : style.appendChild(document.createTextNode(code))
8
9
  }
9
10
 
10
- export { E as DataTable } from './DataTable-12s-Bd20OuMs.js';
11
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
12
+ import * as React from 'react';
13
+ import React__default, { forwardRef, useCallback, useMemo, useState, useRef, useEffect, createContext, useContext } from 'react';
14
+ import { dataAttr, elementProps, ariaAttr, mergeProps as mergeProps$1 } from '@seed-design/dom-utils';
15
+ import { checkbox } from '@seed-design/css/recipes/checkbox';
16
+ import { checkmark } from '@seed-design/css/recipes/checkmark';
17
+ import { Checkbox, useCheckboxContext } from '@seed-design/react-checkbox';
18
+ import { useScaleFeedback } from '@seed-design/react-scale-feedback';
19
+ import { checkboxGroup } from '@seed-design/css/recipes/checkbox-group';
20
+ import { Image } from '@seed-design/react-image';
21
+ import { avatar } from '@seed-design/css/recipes/avatar';
22
+ import '@seed-design/css/recipes/avatar-stack';
23
+ import { badge } from '@seed-design/css/recipes/badge';
24
+ import { Select, useSelectContext, useSelectItemContext } from '@seed-design/react-select';
25
+ import { useFieldContext } from '@seed-design/react-field';
26
+ import { select } from '@seed-design/css/recipes/select';
27
+ import { selectTrigger } from '@seed-design/css/recipes/select-trigger';
28
+ import { selectItem } from '@seed-design/css/recipes/select-item';
29
+ import { Switch, useSwitchContext } from '@seed-design/react-switch';
30
+ import { switchStyle } from '@seed-design/css/recipes/switch';
31
+ import { switchmark } from '@seed-design/css/recipes/switchmark';
32
+ import { TextField, useTextFieldContext } from '@seed-design/react-text-field';
33
+ import { textInput } from '@seed-design/css/recipes/text-input';
34
+ import { Menu, useMenuContext, useMenuItemContext } from '@seed-design/react-menu';
35
+ import { menu } from '@seed-design/css/recipes/menu';
36
+ import { menuItem } from '@seed-design/css/recipes/menu-item';
37
+ import { segmentedControl } from '@seed-design/css/recipes/segmented-control';
38
+ import { SegmentedControl } from '@seed-design/react-segmented-control';
39
+
40
+ // src/IconChevronDownLine.tsx
41
+ var Icon$a = ({ size = 24, ...props }, ref)=>/* @__PURE__ */ jsx("svg", {
42
+ viewBox: "0 0 24 24",
43
+ fill: "none",
44
+ xmlns: "http://www.w3.org/2000/svg",
45
+ "data-seed-icon": "true",
46
+ width: size,
47
+ height: size,
48
+ ref,
49
+ ...props,
50
+ children: /* @__PURE__ */ jsx("g", {
51
+ children: /* @__PURE__ */ jsx("path", {
52
+ fillRule: "evenodd",
53
+ clipRule: "evenodd",
54
+ d: "M21.2612 7.27589C20.8613 6.89499 20.2283 6.9104 19.8474 7.31032L11.9996 15.55L4.15284 7.31037C3.77196 6.91043 3.13899 6.89497 2.73904 7.27584C2.3391 7.65671 2.32364 8.28969 2.70451 8.68963L11.2754 17.6896C11.4641 17.8878 11.7258 18 11.9995 18C12.2732 18 12.5349 17.8879 12.7236 17.6897L21.2956 8.68968C21.6765 8.28976 21.6611 7.65679 21.2612 7.27589Z",
55
+ fill: "currentColor"
56
+ })
57
+ })
58
+ });
59
+ var IconChevronDownLine = /*#__PURE__*/ forwardRef(Icon$a);
60
+ var IconChevronDownLine_default = IconChevronDownLine;
61
+
62
+ // src/IconChevronUpLine.tsx
63
+ var Icon$9 = ({ size = 24, ...props }, ref)=>/* @__PURE__ */ jsx("svg", {
64
+ viewBox: "0 0 24 24",
65
+ fill: "none",
66
+ xmlns: "http://www.w3.org/2000/svg",
67
+ "data-seed-icon": "true",
68
+ width: size,
69
+ height: size,
70
+ ref,
71
+ ...props,
72
+ children: /* @__PURE__ */ jsx("g", {
73
+ children: /* @__PURE__ */ jsx("path", {
74
+ fillRule: "evenodd",
75
+ clipRule: "evenodd",
76
+ d: "M21.2612 16.7241C20.8613 17.105 20.2283 17.0896 19.8474 16.6897L11.9996 8.45L4.15284 16.6896C3.77196 17.0896 3.13899 17.105 2.73904 16.7242C2.3391 16.3433 2.32364 15.7103 2.70451 15.3104L11.2754 6.31037C11.4641 6.11218 11.7258 6.00001 11.9995 6C12.2732 5.99999 12.5349 6.11215 12.7236 6.31032L21.2956 15.3103C21.6765 15.7102 21.6611 16.3432 21.2612 16.7241Z",
77
+ fill: "currentColor"
78
+ })
79
+ })
80
+ });
81
+ var IconChevronUpLine = /*#__PURE__*/ forwardRef(Icon$9);
82
+ var IconChevronUpLine_default = IconChevronUpLine;
83
+
84
+ // src/IconDot3HorizontalLine.tsx
85
+ var Icon$8 = ({ size = 24, ...props }, ref)=>/* @__PURE__ */ jsx("svg", {
86
+ viewBox: "0 0 24 24",
87
+ fill: "none",
88
+ xmlns: "http://www.w3.org/2000/svg",
89
+ "data-seed-icon": "true",
90
+ width: size,
91
+ height: size,
92
+ ref,
93
+ ...props,
94
+ children: /* @__PURE__ */ jsx("g", {
95
+ children: /* @__PURE__ */ jsxs("g", {
96
+ children: [
97
+ /* @__PURE__ */ jsx("path", {
98
+ d: "M4.625 10.375C3.72754 10.375 3 11.1025 3 12C3 12.8975 3.72754 13.625 4.625 13.625C5.52246 13.625 6.25 12.8975 6.25 12C6.25 11.1025 5.52246 10.375 4.625 10.375Z",
99
+ fill: "currentColor"
100
+ }),
101
+ /* @__PURE__ */ jsx("path", {
102
+ d: "M12.075 10.375C11.1775 10.375 10.45 11.1025 10.45 12C10.45 12.8975 11.1775 13.625 12.075 13.625C12.9724 13.625 13.7 12.8975 13.7 12C13.7 11.1025 12.9724 10.375 12.075 10.375Z",
103
+ fill: "currentColor"
104
+ }),
105
+ /* @__PURE__ */ jsx("path", {
106
+ d: "M19.375 10.375C18.4775 10.375 17.75 11.1025 17.75 12C17.75 12.8975 18.4775 13.625 19.375 13.625C20.2725 13.625 21 12.8975 21 12C21 11.1025 20.2725 10.375 19.375 10.375Z",
107
+ fill: "currentColor"
108
+ })
109
+ ]
110
+ })
111
+ })
112
+ });
113
+ var IconDot3HorizontalLine = /*#__PURE__*/ forwardRef(Icon$8);
114
+ var IconDot3HorizontalLine_default = IconDot3HorizontalLine;
115
+
116
+ // src/IconMagnifyingglassLine.tsx
117
+ var Icon$7 = ({ size = 24, ...props }, ref)=>/* @__PURE__ */ jsx("svg", {
118
+ viewBox: "0 0 24 24",
119
+ fill: "none",
120
+ xmlns: "http://www.w3.org/2000/svg",
121
+ "data-seed-icon": "true",
122
+ width: size,
123
+ height: size,
124
+ ref,
125
+ ...props,
126
+ children: /* @__PURE__ */ jsx("g", {
127
+ children: /* @__PURE__ */ jsx("path", {
128
+ fillRule: "evenodd",
129
+ clipRule: "evenodd",
130
+ d: "M11.3111 1.2002C6.00313 1.2002 1.69995 5.49902 1.69995 10.8048V10.8178C1.69995 16.1236 6.00313 20.4224 11.3111 20.4224C13.5994 20.4224 15.711 19.6233 17.3631 18.2785L21.493 22.4083C21.8835 22.7988 22.5167 22.7988 22.9072 22.4083C23.2977 22.0177 23.2977 21.3846 22.9072 20.9941L18.777 16.864C20.1196 15.2095 20.9222 13.0996 20.9222 10.8048C20.9222 5.49902 16.619 1.2002 11.3111 1.2002ZM3.69995 10.8048C3.69995 6.60502 7.10627 3.2002 11.3111 3.2002C15.5159 3.2002 18.9222 6.60502 18.9222 10.8048C18.9222 13.0794 17.9298 15.1209 16.3465 16.5182L16.3446 16.52C15.0067 17.707 13.2419 18.4224 11.3111 18.4224C7.10627 18.4224 3.69995 15.0176 3.69995 10.8178V10.8048Z",
131
+ fill: "currentColor"
132
+ })
133
+ })
134
+ });
135
+ var IconMagnifyingglassLine = /*#__PURE__*/ forwardRef(Icon$7);
136
+ var IconMagnifyingglassLine_default = IconMagnifyingglassLine;
137
+
138
+ // src/IconSlider2HorizontalLine.tsx
139
+ var Icon$6 = ({ size = 24, ...props }, ref)=>/* @__PURE__ */ jsx("svg", {
140
+ viewBox: "0 0 24 24",
141
+ fill: "none",
142
+ xmlns: "http://www.w3.org/2000/svg",
143
+ "data-seed-icon": "true",
144
+ width: size,
145
+ height: size,
146
+ ref,
147
+ ...props,
148
+ children: /* @__PURE__ */ jsx("g", {
149
+ children: /* @__PURE__ */ jsxs("g", {
150
+ children: [
151
+ /* @__PURE__ */ jsx("path", {
152
+ fillRule: "evenodd",
153
+ clipRule: "evenodd",
154
+ d: "M9.99331 7.24609H3C2.44772 7.24609 2 6.79838 2 6.24609C2 5.69381 2.44772 5.24609 3 5.24609H9.99331C10.443 3.38141 12.1222 1.99609 14.125 1.99609C16.1278 1.99609 17.807 3.38141 18.2567 5.24609H21C21.5523 5.24609 22 5.69381 22 6.24609C22 6.79838 21.5523 7.24609 21 7.24609H18.2567C17.807 9.11078 16.1278 10.4961 14.125 10.4961C12.1222 10.4961 10.443 9.11078 9.99331 7.24609ZM11.875 6.24609C11.875 5.00345 12.8824 3.99609 14.125 3.99609C15.3676 3.99609 16.375 5.00345 16.375 6.24609C16.375 7.48873 15.3676 8.49609 14.125 8.49609C12.8824 8.49609 11.875 7.48873 11.875 6.24609Z",
155
+ fill: "currentColor"
156
+ }),
157
+ /* @__PURE__ */ jsx("path", {
158
+ fillRule: "evenodd",
159
+ clipRule: "evenodd",
160
+ d: "M6.25 13.4956C3.90279 13.4956 2 15.3984 2 17.7456C2 20.0928 3.90279 21.9956 6.25 21.9956C8.25016 21.9956 9.92761 20.6139 10.3799 18.7529L20.9996 18.7456C21.5518 18.7452 21.9993 18.2972 21.9989 17.7449C21.9985 17.1926 21.5505 16.7452 20.9982 16.7456L10.3834 16.7529C9.93635 14.8845 8.25537 13.4956 6.25 13.4956ZM4 17.7456C4 16.503 5.00736 15.4956 6.25 15.4956C7.49264 15.4956 8.5 16.503 8.5 17.7456C8.5 18.9882 7.49264 19.9956 6.25 19.9956C5.00736 19.9956 4 18.9882 4 17.7456Z",
161
+ fill: "currentColor"
162
+ })
163
+ ]
164
+ })
165
+ })
166
+ });
167
+ var IconSlider2HorizontalLine = /*#__PURE__*/ forwardRef(Icon$6);
168
+ var IconSlider2HorizontalLine_default = IconSlider2HorizontalLine;
169
+
170
+ // src/IconPersonCircleLine.tsx
171
+ var Icon$5 = ({ size = 24, ...props }, ref)=>/* @__PURE__ */ jsx("svg", {
172
+ viewBox: "0 0 24 24",
173
+ fill: "none",
174
+ xmlns: "http://www.w3.org/2000/svg",
175
+ "data-seed-icon": "true",
176
+ width: size,
177
+ height: size,
178
+ ref,
179
+ ...props,
180
+ children: /* @__PURE__ */ jsx("g", {
181
+ children: /* @__PURE__ */ jsxs("g", {
182
+ children: [
183
+ /* @__PURE__ */ jsx("path", {
184
+ fillRule: "evenodd",
185
+ clipRule: "evenodd",
186
+ d: "M12 5.5C9.79172 5.5 8 7.29172 8 9.5C8 11.7083 9.79172 13.5 12 13.5C14.2083 13.5 16 11.7083 16 9.5C16 7.29172 14.2083 5.5 12 5.5ZM10 9.5C10 8.39628 10.8963 7.5 12 7.5C13.1037 7.5 14 8.39628 14 9.5C14 10.6037 13.1037 11.5 12 11.5C10.8963 11.5 10 10.6037 10 9.5Z",
187
+ fill: "currentColor"
188
+ }),
189
+ /* @__PURE__ */ jsx("path", {
190
+ fillRule: "evenodd",
191
+ clipRule: "evenodd",
192
+ d: "M1 12C1 5.92772 5.92772 1 12 1C18.0723 1 23 5.92772 23 12C23 18.0723 18.0723 23 12 23C5.92772 23 1 18.0723 1 12ZM12 3C7.03229 3 3 7.03229 3 12C3 14.3609 3.91073 16.5105 5.39977 18.1164C5.87422 17.4277 6.5536 16.8564 7.3236 16.4211C8.60413 15.6971 10.2434 15.2793 12.0002 15.2793C13.757 15.2793 15.3962 15.6971 16.6768 16.4211C17.4467 16.8564 18.126 17.4276 18.6004 18.1162C20.0894 16.5103 21 14.3608 21 12C21 7.03229 16.9677 3 12 3ZM12 21C10.1211 21 8.3761 20.4232 6.93241 19.4372C7.17777 18.9952 7.62665 18.5473 8.30791 18.1621C9.25286 17.6279 10.5454 17.2793 12.0002 17.2793C13.455 17.2793 14.7475 17.6279 15.6925 18.1621C16.3736 18.5472 16.8225 18.9951 17.0679 19.437C15.6241 20.4231 13.879 21 12 21Z",
193
+ fill: "currentColor"
194
+ })
195
+ ]
196
+ })
197
+ })
198
+ });
199
+ var IconPersonCircleLine = /*#__PURE__*/ forwardRef(Icon$5);
200
+ var IconPersonCircleLine_default = IconPersonCircleLine;
201
+
202
+ // src/IconPlusLine.tsx
203
+ var Icon$4 = ({ size = 24, ...props }, ref)=>/* @__PURE__ */ jsx("svg", {
204
+ viewBox: "0 0 24 24",
205
+ fill: "none",
206
+ xmlns: "http://www.w3.org/2000/svg",
207
+ "data-seed-icon": "true",
208
+ width: size,
209
+ height: size,
210
+ ref,
211
+ ...props,
212
+ children: /* @__PURE__ */ jsx("g", {
213
+ children: /* @__PURE__ */ jsx("path", {
214
+ d: "M4 13H11V20C11 20.55 11.45 21 12 21C12.55 21 13 20.55 13 20V13H20C20.55 13 21 12.55 21 12C21 11.45 20.55 11 20 11H13V4C13 3.45 12.55 3 12 3C11.45 3 11 3.45 11 4V11H4C3.45 11 3 11.45 3 12C3 12.55 3.45 13 4 13Z",
215
+ fill: "currentColor"
216
+ })
217
+ })
218
+ });
219
+ var IconPlusLine = /*#__PURE__*/ forwardRef(Icon$4);
220
+ var IconPlusLine_default = IconPlusLine;
221
+
222
+ // src/IconMinusLine.tsx
223
+ var Icon$3 = ({ size = 24, ...props }, ref)=>/* @__PURE__ */ jsx("svg", {
224
+ viewBox: "0 0 24 24",
225
+ fill: "none",
226
+ xmlns: "http://www.w3.org/2000/svg",
227
+ "data-seed-icon": "true",
228
+ width: size,
229
+ height: size,
230
+ ref,
231
+ ...props,
232
+ children: /* @__PURE__ */ jsx("g", {
233
+ children: /* @__PURE__ */ jsx("path", {
234
+ d: "M4 13H20C20.55 13 21 12.55 21 12C21 11.45 20.55 11 20 11H4C3.45 11 3 11.45 3 12C3 12.55 3.45 13 4 13Z",
235
+ fill: "currentColor"
236
+ })
237
+ })
238
+ });
239
+ var IconMinusLine = /*#__PURE__*/ forwardRef(Icon$3);
240
+ var IconMinusLine_default = IconMinusLine;
241
+
242
+ // src/IconCheckmarkLine.tsx
243
+ var Icon$2 = ({ size = 24, ...props }, ref)=>/* @__PURE__ */ jsx("svg", {
244
+ viewBox: "0 0 24 24",
245
+ fill: "none",
246
+ xmlns: "http://www.w3.org/2000/svg",
247
+ "data-seed-icon": "true",
248
+ width: size,
249
+ height: size,
250
+ ref,
251
+ ...props,
252
+ children: /* @__PURE__ */ jsx("g", {
253
+ children: /* @__PURE__ */ jsx("path", {
254
+ fillRule: "evenodd",
255
+ clipRule: "evenodd",
256
+ d: "M22.1186 3.71436C22.5526 4.05602 22.6273 4.68476 22.2857 5.11868L10.4749 20.1187C10.2958 20.3461 10.0269 20.4848 9.73779 20.4989C9.44865 20.5129 9.1676 20.401 8.96728 20.192L1.77809 12.692C1.39592 12.2933 1.40931 11.6603 1.80801 11.2781C2.20671 10.896 2.83973 10.9094 3.22191 11.3081L9.61511 17.9777L20.7143 3.88141C21.056 3.44749 21.6847 3.3727 22.1186 3.71436Z",
257
+ fill: "currentColor"
258
+ })
259
+ })
260
+ });
261
+ var IconCheckmarkLine = /*#__PURE__*/ forwardRef(Icon$2);
262
+ var IconCheckmarkLine_default = IconCheckmarkLine;
263
+
264
+ // src/IconChevronDownSmallLine.tsx
265
+ var Icon$1 = ({ size = 24, ...props }, ref)=>/* @__PURE__ */ jsx("svg", {
266
+ viewBox: "0 0 24 24",
267
+ fill: "none",
268
+ xmlns: "http://www.w3.org/2000/svg",
269
+ "data-seed-icon": "true",
270
+ width: size,
271
+ height: size,
272
+ ref,
273
+ ...props,
274
+ children: /* @__PURE__ */ jsx("g", {
275
+ children: /* @__PURE__ */ jsx("path", {
276
+ fillRule: "evenodd",
277
+ clipRule: "evenodd",
278
+ d: "M6.29436 9.29143C6.68569 8.90171 7.31886 8.90303 7.70857 9.29436L11.9848 13.5884L16.297 9.3256C16.6897 8.93733 17.3229 8.94097 17.7112 9.33374C18.0994 9.7265 18.0958 10.3597 17.703 10.7479L12.6823 15.7112C12.2906 16.0984 11.6594 16.096 11.2707 15.7056L6.29143 10.7056C5.90171 10.3143 5.90303 9.68114 6.29436 9.29143Z",
279
+ fill: "currentColor"
280
+ })
281
+ })
282
+ });
283
+ var IconChevronDownSmallLine = /*#__PURE__*/ forwardRef(Icon$1);
284
+ var IconChevronDownSmallLine_default = IconChevronDownSmallLine;
285
+
286
+ // src/IconCheckmarkFatFill.tsx
287
+ var Icon = ({ size = 24, ...props }, ref)=>/* @__PURE__ */ jsx("svg", {
288
+ viewBox: "0 0 24 24",
289
+ fill: "none",
290
+ xmlns: "http://www.w3.org/2000/svg",
291
+ "data-seed-icon": "true",
292
+ width: size,
293
+ height: size,
294
+ ref,
295
+ ...props,
296
+ children: /* @__PURE__ */ jsx("g", {
297
+ children: /* @__PURE__ */ jsx("path", {
298
+ d: "M22.5517 3.16422C21.8141 2.5834 20.7452 2.71054 20.1644 3.4482L9.56331 16.9118L3.72729 10.8235C3.07759 10.1457 2.00145 10.1229 1.32367 10.7726C0.645881 11.4223 0.623111 12.4985 1.27281 13.1763L8.462 20.6763C8.80254 21.0315 9.28032 21.2218 9.77186 21.1979C10.2634 21.174 10.7205 20.9382 11.0249 20.5516L22.8357 5.55155C23.4165 4.81389 23.2894 3.74505 22.5517 3.16422Z",
299
+ fill: "currentColor"
300
+ })
301
+ })
302
+ });
303
+ var IconCheckmarkFatFill = /*#__PURE__*/ forwardRef(Icon);
304
+ var IconCheckmarkFatFill_default = IconCheckmarkFatFill;
305
+
306
+ var __defProp$1 = Object.defineProperty;
307
+ var __name$1 = (target, value)=>__defProp$1(target, "name", {
308
+ value,
309
+ configurable: true
310
+ });
311
+ function setRef(ref, value) {
312
+ if (typeof ref === "function") {
313
+ return ref(value);
314
+ } else if (ref !== null && ref !== void 0) {
315
+ ref.current = value;
316
+ }
317
+ }
318
+ __name$1(setRef, "setRef");
319
+ function composeRefs(...refs) {
320
+ return (node)=>{
321
+ let hasCleanup = false;
322
+ const cleanups = refs.map((ref)=>{
323
+ const cleanup = setRef(ref, node);
324
+ if (!hasCleanup && typeof cleanup == "function") {
325
+ hasCleanup = true;
326
+ }
327
+ return cleanup;
328
+ });
329
+ if (hasCleanup) {
330
+ return ()=>{
331
+ for(let i = 0; i < cleanups.length; i++){
332
+ const cleanup = cleanups[i];
333
+ if (typeof cleanup == "function") {
334
+ cleanup();
335
+ } else {
336
+ setRef(refs[i], null);
337
+ }
338
+ }
339
+ };
340
+ }
341
+ };
342
+ }
343
+ __name$1(composeRefs, "composeRefs");
344
+ function useComposedRefs(...refs) {
345
+ return React.useCallback(composeRefs(...refs), refs);
346
+ }
347
+ __name$1(useComposedRefs, "useComposedRefs");
348
+
349
+ var __defProp = Object.defineProperty;
350
+ var __name = (target, value)=>__defProp(target, "name", {
351
+ value,
352
+ configurable: true
353
+ });
354
+ // @__NO_SIDE_EFFECTS__
355
+ function createSlot(ownerName) {
356
+ const Slot2 = /*#__PURE__*/ React.forwardRef((props, forwardedRef)=>{
357
+ let { children, ...slotProps } = props;
358
+ let slottableElement = null;
359
+ let hasSlottable = false;
360
+ const newChildren = [];
361
+ if (isLazyComponent(children) && typeof use === "function") {
362
+ children = use(children._payload);
363
+ }
364
+ React.Children.forEach(children, (maybeSlottable)=>{
365
+ if (isSlottable(maybeSlottable)) {
366
+ hasSlottable = true;
367
+ const slottable = maybeSlottable;
368
+ let child = "child" in slottable.props ? slottable.props.child : slottable.props.children;
369
+ if (isLazyComponent(child) && typeof use === "function") {
370
+ child = use(child._payload);
371
+ }
372
+ slottableElement = getSlottableElementFromSlottable(slottable, child);
373
+ newChildren.push(slottableElement?.props?.children);
374
+ } else {
375
+ newChildren.push(maybeSlottable);
376
+ }
377
+ });
378
+ if (slottableElement) {
379
+ slottableElement = /*#__PURE__*/ React.cloneElement(slottableElement, void 0, newChildren);
380
+ } else if (// A `Slottable` was found but it didn't resolve to a single element (e.g.
381
+ // it wrapped multiple elements, text, or a render-prop `child` that
382
+ // wasn't an element). Don't fall back to treating the `Slottable` wrapper
383
+ // itself as the slot target — throw a descriptive error below instead.
384
+ !hasSlottable && React.Children.count(children) === 1 && /*#__PURE__*/ React.isValidElement(children)) {
385
+ slottableElement = children;
386
+ }
387
+ const slottableElementRef = slottableElement ? getElementRef(slottableElement) : void 0;
388
+ const composedRef = useComposedRefs(forwardedRef, slottableElementRef);
389
+ if (!slottableElement) {
390
+ if (children || children === 0) {
391
+ throw new Error(hasSlottable ? createSlottableError(ownerName) : createSlotError(ownerName));
392
+ }
393
+ return children;
394
+ }
395
+ const mergedProps = mergeProps(slotProps, slottableElement.props ?? {});
396
+ if (slottableElement.type !== React.Fragment) {
397
+ mergedProps.ref = forwardedRef ? composedRef : slottableElementRef;
398
+ }
399
+ return /*#__PURE__*/ React.cloneElement(slottableElement, mergedProps);
400
+ });
401
+ Slot2.displayName = `${ownerName}.Slot`;
402
+ return Slot2;
403
+ }
404
+ __name(createSlot, "createSlot");
405
+ var Slot = /* @__PURE__ */ createSlot("Slot");
406
+ var SLOTTABLE_IDENTIFIER = Symbol.for("radix.slottable");
407
+ // @__NO_SIDE_EFFECTS__
408
+ function createSlottable(ownerName) {
409
+ const Slottable2 = /* @__PURE__ */ __name((props)=>"child" in props ? props.children(props.child) : props.children, "Slottable");
410
+ Slottable2.displayName = `${ownerName}.Slottable`;
411
+ Slottable2.__radixId = SLOTTABLE_IDENTIFIER;
412
+ return Slottable2;
413
+ }
414
+ __name(createSlottable, "createSlottable");
415
+ var getSlottableElementFromSlottable = /* @__PURE__ */ __name((slottable, child)=>{
416
+ if ("child" in slottable.props) {
417
+ const child2 = slottable.props.child;
418
+ if (!/*#__PURE__*/ React.isValidElement(child2)) return null;
419
+ return /*#__PURE__*/ React.cloneElement(child2, void 0, slottable.props.children(child2.props.children));
420
+ }
421
+ return /*#__PURE__*/ React.isValidElement(child) ? child : null;
422
+ }, "getSlottableElementFromSlottable");
423
+ function mergeProps(slotProps, childProps) {
424
+ const overrideProps = {
425
+ ...childProps
426
+ };
427
+ for(const propName in childProps){
428
+ const slotPropValue = slotProps[propName];
429
+ const childPropValue = childProps[propName];
430
+ const isHandler = /^on[A-Z]/.test(propName);
431
+ if (isHandler) {
432
+ if (slotPropValue && childPropValue) {
433
+ overrideProps[propName] = (...args)=>{
434
+ const result = childPropValue(...args);
435
+ slotPropValue(...args);
436
+ return result;
437
+ };
438
+ } else if (slotPropValue) {
439
+ overrideProps[propName] = slotPropValue;
440
+ }
441
+ } else if (propName === "style") {
442
+ overrideProps[propName] = {
443
+ ...slotPropValue,
444
+ ...childPropValue
445
+ };
446
+ } else if (propName === "className") {
447
+ overrideProps[propName] = [
448
+ slotPropValue,
449
+ childPropValue
450
+ ].filter(Boolean).join(" ");
451
+ }
452
+ }
453
+ return {
454
+ ...slotProps,
455
+ ...overrideProps
456
+ };
457
+ }
458
+ __name(mergeProps, "mergeProps");
459
+ function getElementRef(element) {
460
+ let getter = Object.getOwnPropertyDescriptor(element.props, "ref")?.get;
461
+ let mayWarn = getter && "isReactWarning" in getter && getter.isReactWarning;
462
+ if (mayWarn) {
463
+ return element.ref;
464
+ }
465
+ getter = Object.getOwnPropertyDescriptor(element, "ref")?.get;
466
+ mayWarn = getter && "isReactWarning" in getter && getter.isReactWarning;
467
+ if (mayWarn) {
468
+ return element.props.ref;
469
+ }
470
+ return element.props.ref || element.ref;
471
+ }
472
+ __name(getElementRef, "getElementRef");
473
+ function isSlottable(child) {
474
+ return /*#__PURE__*/ React.isValidElement(child) && typeof child.type === "function" && "__radixId" in child.type && child.type.__radixId === SLOTTABLE_IDENTIFIER;
475
+ }
476
+ __name(isSlottable, "isSlottable");
477
+ var REACT_LAZY_TYPE = Symbol.for("react.lazy");
478
+ function isLazyComponent(element) {
479
+ return element != null && typeof element === "object" && "$$typeof" in element && element.$$typeof === REACT_LAZY_TYPE && "_payload" in element && isPromiseLike(element._payload);
480
+ }
481
+ __name(isLazyComponent, "isLazyComponent");
482
+ function isPromiseLike(value) {
483
+ return typeof value === "object" && value !== null && "then" in value;
484
+ }
485
+ __name(isPromiseLike, "isPromiseLike");
486
+ var createSlotError = /* @__PURE__ */ __name((ownerName)=>{
487
+ return `${ownerName} failed to slot onto its children. Expected a single React element child or \`Slottable\`.`;
488
+ }, "createSlotError");
489
+ var createSlottableError = /* @__PURE__ */ __name((ownerName)=>{
490
+ return `${ownerName} failed to slot onto its \`Slottable\`. Expected \`Slottable\` to receive a single React element child.`;
491
+ }, "createSlottableError");
492
+ var use = React[" use ".trim().toString()];
493
+
494
+ function createPrimitive(node) {
495
+ const Node = /*#__PURE__*/ React.forwardRef((props, ref)=>{
496
+ const { asChild, ...primitiveProps } = props;
497
+ const Comp = asChild ? Slot : node;
498
+ return /*#__PURE__*/ jsx(Comp, {
499
+ ...primitiveProps,
500
+ ref: ref
501
+ });
502
+ });
503
+ Node.displayName = `Primitive.${node}`;
504
+ return Node;
505
+ }
506
+ const Primitive = {
507
+ div: createPrimitive("div"),
508
+ span: createPrimitive("span"),
509
+ img: createPrimitive("img"),
510
+ button: createPrimitive("button"),
511
+ label: createPrimitive("label"),
512
+ input: createPrimitive("input"),
513
+ textarea: createPrimitive("textarea"),
514
+ a: createPrimitive("a"),
515
+ p: createPrimitive("p"),
516
+ h2: createPrimitive("h2"),
517
+ ul: createPrimitive("ul"),
518
+ li: createPrimitive("li"),
519
+ nav: createPrimitive("nav"),
520
+ svg: createPrimitive("svg"),
521
+ circle: createPrimitive("circle"),
522
+ // Native table elements -- DataTable's own rendering (crispy-seed's addition, not
523
+ // upstream seed-design) needs these for a real `<table>` grid rather than a
524
+ // `display: flex` div grid faked with ARIA roles.
525
+ table: createPrimitive("table"),
526
+ thead: createPrimitive("thead"),
527
+ tbody: createPrimitive("tbody"),
528
+ tfoot: createPrimitive("tfoot"),
529
+ tr: createPrimitive("tr"),
530
+ th: createPrimitive("th"),
531
+ td: createPrimitive("td"),
532
+ colgroup: createPrimitive("colgroup"),
533
+ col: createPrimitive("col")
534
+ };
535
+
536
+ // packages/react/use-layout-effect/src/use-layout-effect.tsx
537
+ var useLayoutEffect2$1 = globalThis?.document ? React.useLayoutEffect : ()=>{};
538
+
539
+ // src/use-controllable-state.tsx
540
+ var useInsertionEffect = React[" useInsertionEffect ".trim().toString()] || useLayoutEffect2$1;
541
+ function useControllableState({ prop, defaultProp, onChange = ()=>{}, caller }) {
542
+ const [uncontrolledProp, setUncontrolledProp, onChangeRef] = useUncontrolledState({
543
+ defaultProp,
544
+ onChange
545
+ });
546
+ const isControlled = prop !== void 0;
547
+ const value = isControlled ? prop : uncontrolledProp;
548
+ {
549
+ const isControlledRef = React.useRef(prop !== void 0);
550
+ React.useEffect(()=>{
551
+ const wasControlled = isControlledRef.current;
552
+ if (wasControlled !== isControlled) {
553
+ const from = wasControlled ? "controlled" : "uncontrolled";
554
+ const to = isControlled ? "controlled" : "uncontrolled";
555
+ console.warn(`${caller} is changing from ${from} to ${to}. Components should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled value for the lifetime of the component.`);
556
+ }
557
+ isControlledRef.current = isControlled;
558
+ }, [
559
+ isControlled,
560
+ caller
561
+ ]);
562
+ }
563
+ const setValue = React.useCallback((nextValue)=>{
564
+ if (isControlled) {
565
+ const value2 = isFunction(nextValue) ? nextValue(prop) : nextValue;
566
+ if (value2 !== prop) {
567
+ onChangeRef.current?.(value2);
568
+ }
569
+ } else {
570
+ setUncontrolledProp(nextValue);
571
+ }
572
+ }, [
573
+ isControlled,
574
+ prop,
575
+ setUncontrolledProp,
576
+ onChangeRef
577
+ ]);
578
+ return [
579
+ value,
580
+ setValue
581
+ ];
582
+ }
583
+ function useUncontrolledState({ defaultProp, onChange }) {
584
+ const [value, setValue] = React.useState(defaultProp);
585
+ const prevValueRef = React.useRef(value);
586
+ const onChangeRef = React.useRef(onChange);
587
+ useInsertionEffect(()=>{
588
+ onChangeRef.current = onChange;
589
+ }, [
590
+ onChange
591
+ ]);
592
+ React.useEffect(()=>{
593
+ if (prevValueRef.current !== value) {
594
+ onChangeRef.current?.(value);
595
+ prevValueRef.current = value;
596
+ }
597
+ }, [
598
+ value,
599
+ prevValueRef
600
+ ]);
601
+ return [
602
+ value,
603
+ setValue,
604
+ onChangeRef
605
+ ];
606
+ }
607
+ function isFunction(value) {
608
+ return typeof value === "function";
609
+ }
610
+
611
+ const DATA_TABLE_UNIVERSAL_CALC_OPS = [
612
+ "count",
613
+ "countFilled"
614
+ ];
615
+ const DATA_TABLE_NUMERIC_CALC_OPS = [
616
+ "sum",
617
+ "average",
618
+ "min",
619
+ "max"
620
+ ];
621
+ const DATA_TABLE_CALC_LABEL = {
622
+ count: "Count",
623
+ countFilled: "Count filled",
624
+ sum: "Sum",
625
+ average: "Average",
626
+ min: "Min",
627
+ max: "Max"
628
+ };
629
+ /** A value counts as empty when it is null/undefined, an empty string, or an empty
630
+ * array -- crisp's own `isEmptyValue` (columncalc.tsx), reused verbatim. */ function isEmptyValueForCalc(value) {
631
+ if (value == null) return true;
632
+ if (typeof value === "string") return value.trim() === "";
633
+ if (Array.isArray(value)) return value.length === 0;
634
+ return false;
635
+ }
636
+ /**
637
+ * Pure so the arithmetic is independently (and mutation-) testable without rendering a
638
+ * table at all. `sum`/`average`/`min`/`max` skip empty values and non-numeric ones
639
+ * (coerced via `Number`, dropping `NaN`); they return `null` -- render nothing, not a
640
+ * misleading 0 -- when there is nothing numeric left to reduce over.
641
+ */ function computeColumnCalc(values, op) {
642
+ switch(op){
643
+ case "count":
644
+ return values.length;
645
+ case "countFilled":
646
+ return values.filter((value)=>!isEmptyValueForCalc(value)).length;
647
+ }
648
+ const numbers = values.filter((value)=>!isEmptyValueForCalc(value)).map((value)=>typeof value === "number" ? value : Number(value)).filter((value)=>Number.isFinite(value));
649
+ if (numbers.length === 0) return null;
650
+ switch(op){
651
+ case "sum":
652
+ return numbers.reduce((total, value)=>total + value, 0);
653
+ case "average":
654
+ return numbers.reduce((total, value)=>total + value, 0) / numbers.length;
655
+ case "min":
656
+ return Math.min(...numbers);
657
+ case "max":
658
+ return Math.max(...numbers);
659
+ default:
660
+ return null;
661
+ }
662
+ }
663
+ const EMPTY_OPS = {};
664
+ // Owns which aggregation (if any) each column's ColumnCalc footer cell currently
665
+ // shows -- the way useDataTableFilter owns one filter per column key. The actual
666
+ // REDUCE over row values needs `TRow` (a type this headless package never sees), so
667
+ // -- same split as sort/search/filter -- this hook only tracks WHICH op is active per
668
+ // column; the styled layer (DataTable.tsx's `ColumnCalcCell`) calls `computeColumnCalc`
669
+ // with the column's own resolved values.
670
+ function useDataTableColumnCalc(props) {
671
+ const { columnCalcOps: opsProp, defaultColumnCalcOps = EMPTY_OPS, onColumnCalcOpsChange } = props;
672
+ const [columnCalcOps = EMPTY_OPS, setColumnCalcOps] = useControllableState({
673
+ prop: opsProp,
674
+ defaultProp: defaultColumnCalcOps,
675
+ onChange: onColumnCalcOpsChange
676
+ });
677
+ const getColumnCalcOp = useCallback((columnKey)=>columnCalcOps[columnKey], [
678
+ columnCalcOps
679
+ ]);
680
+ const setColumnCalcOp = useCallback((columnKey, op)=>{
681
+ setColumnCalcOps((current = EMPTY_OPS)=>{
682
+ if (op === undefined) {
683
+ if (!(columnKey in current)) return current;
684
+ const next = {
685
+ ...current
686
+ };
687
+ delete next[columnKey];
688
+ return next;
689
+ }
690
+ return {
691
+ ...current,
692
+ [columnKey]: op
693
+ };
694
+ });
695
+ }, [
696
+ setColumnCalcOps
697
+ ]);
698
+ return {
699
+ columnCalcOps,
700
+ getColumnCalcOp,
701
+ setColumnCalcOp
702
+ };
703
+ }
704
+
705
+ // Select's `dom.ts` reads state off REAL rendered elements (aria-disabled, data-value)
706
+ // because its options register asynchronously through a floating-ui list and the DOM
707
+ // is the one thing always in sync with `elementsRef`. DataTable's selection has no
708
+ // such registry -- it is two plain id arrays (`rowIds`, `selectedIds`) the root already
709
+ // holds -- so there is nothing to inspect on an element. What's isolated here instead is
710
+ // the same KIND of thing Select isolates: small pure functions the state hooks would
711
+ // otherwise inline, kept out of the hooks so they can be unit-tested (and mutation-
712
+ // tested) on their own.
713
+ /**
714
+ * Tri-state selection summary of `selectedIds` against the CURRENTLY RENDERED
715
+ * `rowIds` -- never against `selectedIds` alone, so a stale id that scrolled out of a
716
+ * filtered/sorted view can't quietly count toward "all" or "some".
717
+ */ function computeSelectionState(rowIds, selectedIds) {
718
+ if (rowIds.length === 0) return "none";
719
+ const selectedIdSet = new Set(selectedIds);
720
+ const selectedRowCount = rowIds.filter((id)=>selectedIdSet.has(id)).length;
721
+ if (selectedRowCount === 0) return "none";
722
+ return selectedRowCount === rowIds.length ? "all" : "some";
723
+ }
724
+ /** Adds `id` if absent, removes it if present. Never mutates `ids`. */ function toggleMembership(ids, id) {
725
+ return ids.includes(id) ? ids.filter((existing)=>existing !== id) : [
726
+ ...ids,
727
+ id
728
+ ];
729
+ }
730
+ /**
731
+ * Reconciles a persisted/controlled column ORDER against the table's current column
732
+ * KEYS -- used by useDataTableColumns so a consumer's `columnOrder` never silently drops
733
+ * a column the table actually renders (a key added to `columns` after the order was
734
+ * first captured) nor keeps a ghost entry for one that was removed. Known keys keep
735
+ * their position from `order`; any key in `keys` missing from `order` is appended, in
736
+ * `keys`' own order; any entry in `order` no longer in `keys` is dropped.
737
+ */ function reconcileColumnOrder(order, keys) {
738
+ const keySet = new Set(keys);
739
+ const known = order.filter((key)=>keySet.has(key));
740
+ const knownSet = new Set(known);
741
+ const added = keys.filter((key)=>!knownSet.has(key));
742
+ return [
743
+ ...known,
744
+ ...added
745
+ ];
746
+ }
747
+ /**
748
+ * Moves `key` one step toward `direction` in `order` -- a no-op (same array, well,
749
+ * an equal-valued copy) at the edge it would otherwise walk off. Column-menu's
750
+ * "Move left"/"Move right" (DATATABLE-ATTIO-SPEC.md's column menu) is one call of
751
+ * this per click; drag-to-reorder is `reorderColumn` below instead, which can jump
752
+ * more than one position in a single move.
753
+ */ function moveColumnKey(order, key, direction) {
754
+ const index = order.indexOf(key);
755
+ if (index === -1) return [
756
+ ...order
757
+ ];
758
+ const targetIndex = direction === "left" ? index - 1 : index + 1;
759
+ if (targetIndex < 0 || targetIndex >= order.length) return [
760
+ ...order
761
+ ];
762
+ const next = [
763
+ ...order
764
+ ];
765
+ [next[index], next[targetIndex]] = [
766
+ next[targetIndex],
767
+ next[index]
768
+ ];
769
+ return next;
770
+ }
771
+ /**
772
+ * Drag-to-reorder: removes `dragKey` from `order` and reinserts it immediately before
773
+ * `dropKey`'s CURRENT position (i.e. its position after the removal, so dropping a
774
+ * column just after its own old slot doesn't appear to do nothing). A no-op (an
775
+ * equal-valued copy) when either key is missing or they're the same key.
776
+ */ function reorderColumnKey(order, dragKey, dropKey) {
777
+ if (dragKey === dropKey || !order.includes(dragKey) || !order.includes(dropKey)) {
778
+ return [
779
+ ...order
780
+ ];
781
+ }
782
+ const withoutDrag = order.filter((key)=>key !== dragKey);
783
+ const dropIndex = withoutDrag.indexOf(dropKey);
784
+ return [
785
+ ...withoutDrag.slice(0, dropIndex),
786
+ dragKey,
787
+ ...withoutDrag.slice(dropIndex)
788
+ ];
789
+ }
790
+ /** Clamps a drag-resized column width to a sane range -- crisp's own datatable.tsx
791
+ * resize handler clamps the same way (a column can neither collapse to unreadable
792
+ * nor balloon past a sane cap from one fast mouse move). */ function clampColumnWidth(width, min = 80, max = 640) {
793
+ return Math.min(max, Math.max(min, width));
794
+ }
795
+ /**
796
+ * Infinite scroll's own gate: fire `onLoadMore` only when the sentinel is ACTUALLY
797
+ * intersecting (not e.g. an observer's initial `false` callback, which fires once on
798
+ * `observe()` before any real intersection), there IS a next page (`hasMore`), and no
799
+ * fetch is already in flight (`loadingMore`) -- either of the latter two alone must
800
+ * suppress a trigger, which is why this is a pure `&&` rather than three separate
801
+ * call sites that could each drift. Isolated the same way `computeSelectionState`/
802
+ * `toggleMembership` are: the real IntersectionObserver callback (useDataTable
803
+ * InfiniteScroll.ts) is unit-testable only through a live browser API, but the
804
+ * DECISION it makes on every callback is plain data in, boolean out.
805
+ */ function shouldTriggerLoadMore(isIntersecting, hasMore, loadingMore) {
806
+ return hasMore && !loadingMore;
807
+ }
808
+ const EMPTY_STRINGS = [];
809
+ const EMPTY_RECORD = {};
810
+ /**
811
+ * Owns everything DATATABLE-ATTIO-SPEC.md's column menu + reorder + resize tranche
812
+ * needs -- order, visibility, a per-column label override, and a per-column width --
813
+ * the way useDataTableSelection owns the one selected-id SET rather than one hook per
814
+ * row. All four are "one value per column key" maps/lists a consumer may want to
815
+ * persist (a saved view), so they share this hook instead of splitting into four
816
+ * separate ones; drag-to-reorder and drag-to-resize (below) are the two INTERACTIONS
817
+ * built on top of the order/width state this hook already owns, not independent state
818
+ * of their own.
819
+ */ function useDataTableColumns(props) {
820
+ const { columnKeys = EMPTY_STRINGS, columnOrder: columnOrderProp, defaultColumnOrder, onColumnOrderChange, hiddenColumnKeys: hiddenProp, defaultHiddenColumnKeys = EMPTY_STRINGS, onHiddenColumnKeysChange, columnWidths: widthsProp, defaultColumnWidths = EMPTY_RECORD, onColumnWidthsChange, columnLabels: labelsProp, defaultColumnLabels = EMPTY_RECORD, onColumnLabelsChange } = props;
821
+ const [rawOrder, setRawOrder] = useControllableState({
822
+ prop: columnOrderProp,
823
+ defaultProp: defaultColumnOrder ?? columnKeys,
824
+ onChange: onColumnOrderChange
825
+ });
826
+ // Reconciled EVERY render against the table's current `columnKeys` (dom.ts's
827
+ // `reconcileColumnOrder`), not just once at mount -- a controlled `columnOrder` a
828
+ // consumer persisted before a new column existed must still show that new column
829
+ // (appended), and one holding a column that got removed from `columns` must not
830
+ // render a ghost header for it.
831
+ const columnOrder = useMemo(()=>reconcileColumnOrder(rawOrder ?? columnKeys, columnKeys), [
832
+ rawOrder,
833
+ columnKeys
834
+ ]);
835
+ const [hiddenColumnKeys = EMPTY_STRINGS, setHiddenColumnKeys] = useControllableState({
836
+ prop: hiddenProp,
837
+ defaultProp: defaultHiddenColumnKeys,
838
+ onChange: onHiddenColumnKeysChange
839
+ });
840
+ const hiddenSet = useMemo(()=>new Set(hiddenColumnKeys), [
841
+ hiddenColumnKeys
842
+ ]);
843
+ const [columnWidths = EMPTY_RECORD, setColumnWidths] = useControllableState({
844
+ prop: widthsProp,
845
+ defaultProp: defaultColumnWidths,
846
+ onChange: onColumnWidthsChange
847
+ });
848
+ const [columnLabels = EMPTY_RECORD, setColumnLabels] = useControllableState({
849
+ prop: labelsProp,
850
+ defaultProp: defaultColumnLabels,
851
+ onChange: onColumnLabelsChange
852
+ });
853
+ const visibleColumnOrder = useMemo(()=>columnOrder.filter((key)=>!hiddenSet.has(key)), [
854
+ columnOrder,
855
+ hiddenSet
856
+ ]);
857
+ const isColumnHidden = useCallback((key)=>hiddenSet.has(key), [
858
+ hiddenSet
859
+ ]);
860
+ const hideColumn = useCallback((key)=>setHiddenColumnKeys((current = EMPTY_STRINGS)=>[
861
+ ...current,
862
+ key
863
+ ]), [
864
+ setHiddenColumnKeys
865
+ ]);
866
+ const showColumn = useCallback((key)=>setHiddenColumnKeys((current = EMPTY_STRINGS)=>current.filter((existing)=>existing !== key)), [
867
+ setHiddenColumnKeys
868
+ ]);
869
+ const toggleColumnVisibility = useCallback((key)=>isColumnHidden(key) ? showColumn(key) : hideColumn(key), [
870
+ isColumnHidden,
871
+ showColumn,
872
+ hideColumn
873
+ ]);
874
+ const getColumnLabel = useCallback((key, fallback)=>columnLabels[key] ?? fallback, [
875
+ columnLabels
876
+ ]);
877
+ const renameColumn = useCallback((key, label)=>setColumnLabels((current = EMPTY_RECORD)=>({
878
+ ...current,
879
+ [key]: label
880
+ })), [
881
+ setColumnLabels
882
+ ]);
883
+ const getColumnWidth = useCallback((key)=>columnWidths[key], [
884
+ columnWidths
885
+ ]);
886
+ const setColumnWidth = useCallback((key, width)=>setColumnWidths((current = EMPTY_RECORD)=>({
887
+ ...current,
888
+ [key]: clampColumnWidth(width)
889
+ })), [
890
+ setColumnWidths
891
+ ]);
892
+ const moveColumn = useCallback((key, direction)=>setRawOrder((current)=>moveColumnKey(current ?? columnKeys, key, direction)), [
893
+ setRawOrder,
894
+ columnKeys
895
+ ]);
896
+ const reorderColumn = useCallback((dragKey, dropKey)=>setRawOrder((current)=>reorderColumnKey(current ?? columnKeys, dragKey, dropKey)), [
897
+ setRawOrder,
898
+ columnKeys
899
+ ]);
900
+ // -- drag-to-reorder ---------------------------------------------------------------
901
+ // Live state only (which column is currently being dragged) -- the styled layer
902
+ // drives it by calling `dragOverColumn` as the pointer crosses OTHER header cells
903
+ // (via `document.elementFromPoint`, since a captured pointer stops native
904
+ // `pointerenter` from firing on siblings), which reorders immediately rather than
905
+ // waiting for a drop. That is exactly what Attio's own header drag does (the column
906
+ // being passed visibly swaps live), and it means `endColumnDrag` never has to also
907
+ // commit a move -- there is nothing left to commit.
908
+ const [draggingColumnKey, setDraggingColumnKey] = useState(null);
909
+ const beginColumnDrag = useCallback((key)=>setDraggingColumnKey(key), []);
910
+ const dragOverColumn = useCallback((hoverKey)=>{
911
+ setDraggingColumnKey((current)=>{
912
+ if (!current || current === hoverKey) return current;
913
+ reorderColumn(current, hoverKey);
914
+ return current;
915
+ });
916
+ }, [
917
+ reorderColumn
918
+ ]);
919
+ const endColumnDrag = useCallback(()=>setDraggingColumnKey(null), []);
920
+ // -- drag-to-resize ------------------------------------------------------------------
921
+ // `resizeStartRef` holds the ONE thing that must survive across pointermove events
922
+ // without triggering a re-render on every pixel (the drag's origin) -- state
923
+ // (`resizingColumnKey`) is only for what the render actually needs: which column's
924
+ // handle should show its active/dragging look.
925
+ const resizeStartRef = useRef(null);
926
+ const [resizingColumnKey, setResizingColumnKey] = useState(null);
927
+ const beginColumnResize = useCallback((key, clientX, currentWidth)=>{
928
+ resizeStartRef.current = {
929
+ key,
930
+ startX: clientX,
931
+ startWidth: currentWidth
932
+ };
933
+ setResizingColumnKey(key);
934
+ }, []);
935
+ const updateColumnResize = useCallback((clientX)=>{
936
+ const start = resizeStartRef.current;
937
+ if (!start) return;
938
+ setColumnWidth(start.key, start.startWidth + (clientX - start.startX));
939
+ }, [
940
+ setColumnWidth
941
+ ]);
942
+ const endColumnResize = useCallback(()=>{
943
+ resizeStartRef.current = null;
944
+ setResizingColumnKey(null);
945
+ }, []);
946
+ return {
947
+ columnOrder,
948
+ visibleColumnOrder,
949
+ hiddenColumnKeys,
950
+ columnWidths,
951
+ columnLabels,
952
+ isColumnHidden,
953
+ hideColumn,
954
+ showColumn,
955
+ toggleColumnVisibility,
956
+ getColumnLabel,
957
+ renameColumn,
958
+ getColumnWidth,
959
+ setColumnWidth,
960
+ moveColumn,
961
+ reorderColumn,
962
+ draggingColumnKey,
963
+ beginColumnDrag,
964
+ dragOverColumn,
965
+ endColumnDrag,
966
+ resizingColumnKey,
967
+ beginColumnResize,
968
+ updateColumnResize,
969
+ endColumnResize,
970
+ // Exposed for the styled layer's header cell -- `data-dragging`/`data-resizing`
971
+ // read directly off state above already cover most of it; `dataAttr` re-exported
972
+ // via this hook's own module keeps every other sub-hook's habit of building its
973
+ // own data-attribute values rather than the styled layer re-deriving booleans.
974
+ isColumnDragging: (key)=>dataAttr(draggingColumnKey === key),
975
+ isColumnResizing: (key)=>dataAttr(resizingColumnKey === key)
976
+ };
977
+ }
978
+
979
+ function sameCell(a, id, key) {
980
+ return a !== null && a.id === id && a.key === key;
981
+ }
982
+ /**
983
+ * Owns which single cell is mid-edit, which one (if any) has a commit in flight, and
984
+ * which one last failed -- the way useDataTableColumnCalc owns which op is active per
985
+ * column. This hook never touches `TRow` or renders an editor itself; it only tracks
986
+ * WHICH cell is in which of those three states and orchestrates the async commit
987
+ * (crisp's own `commitEdit`, `datatable.tsx`) -- the styled layer (react/DataTable's
988
+ * `editors.tsx`) reads `isEditing`/`isPending`/`getEditError` to decide what to render
989
+ * for a given `(id, key)` and calls `startEdit`/`cancelEdit`/`commitEdit` in response
990
+ * to a double-click, Escape, Enter/blur, or an always-live widget's own change event.
991
+ *
992
+ * Only one cell can be mid-edit at a time (`editingCell`, not a set) -- matching
993
+ * crisp's own single `editing` state and this table's spec (no multi-cell/range
994
+ * editing is in scope here).
995
+ */ function useDataTableEdit(props) {
996
+ const { onEdit } = props;
997
+ const [editingCell, setEditingCell] = useState(null);
998
+ const [pendingCell, setPendingCell] = useState(null);
999
+ const [editError, setEditError] = useState(null);
1000
+ const isEditing = useCallback((id, key)=>sameCell(editingCell, id, key), [
1001
+ editingCell
1002
+ ]);
1003
+ const isPending = useCallback((id, key)=>sameCell(pendingCell, id, key), [
1004
+ pendingCell
1005
+ ]);
1006
+ const getEditError = useCallback((id, key)=>editError && editError.id === id && editError.key === key ? editError.message : null, [
1007
+ editError
1008
+ ]);
1009
+ const startEdit = useCallback((id, key)=>{
1010
+ // Opening a cell's editor also clears any stale error left over from a PREVIOUS
1011
+ // failed commit on it (crisp/#385 comment) -- otherwise a rejection from three
1012
+ // edits ago would still read under a freshly-reopened editor.
1013
+ setEditError(null);
1014
+ setEditingCell({
1015
+ id,
1016
+ key
1017
+ });
1018
+ }, []);
1019
+ const cancelEdit = useCallback(()=>setEditingCell(null), []);
1020
+ // Async-aware (crisp/#385): a sync `onEdit` (returns undefined) commits exactly as
1021
+ // before -- close the editor immediately. A Promise-returning `onEdit` keeps the
1022
+ // editor open with a pending state while it settles, then either closes it
1023
+ // (resolve) or closes it AND reverts to the last-known-good value while showing the
1024
+ // rejection's message beside the cell (reject). The revert needs no extra code:
1025
+ // neither this hook nor the styled layer ever mutates the caller's own row data, so
1026
+ // once `editingCell` clears, the cell simply shows that data again.
1027
+ const commitEdit = useCallback((id, key, value)=>{
1028
+ setEditError(null);
1029
+ const result = onEdit?.(id, key, value);
1030
+ if (result && typeof result.then === "function") {
1031
+ setPendingCell({
1032
+ id,
1033
+ key
1034
+ });
1035
+ result.then(()=>{
1036
+ setPendingCell(null);
1037
+ setEditingCell(null);
1038
+ }, (err)=>{
1039
+ setPendingCell(null);
1040
+ setEditingCell(null);
1041
+ setEditError({
1042
+ id,
1043
+ key,
1044
+ message: err instanceof Error ? err.message : typeof err === "string" ? err : "Could not save"
1045
+ });
1046
+ });
1047
+ return;
1048
+ }
1049
+ setEditingCell(null);
1050
+ }, [
1051
+ onEdit
1052
+ ]);
1053
+ return {
1054
+ editingCell,
1055
+ isEditing,
1056
+ isPending,
1057
+ getEditError,
1058
+ startEdit,
1059
+ cancelEdit,
1060
+ commitEdit
1061
+ };
1062
+ }
1063
+
1064
+ const EMPTY_FILTERS = {};
1065
+ // Owns one filter per column key -- the way useDataTableSelection owns one selected-id
1066
+ // SET rather than one boolean per row. Applying a column's filter to `data` needs
1067
+ // `TRow` (a type this headless package never sees), so -- same split as search/sort --
1068
+ // this hook only tracks which mode/values are checked per column; the styled layer
1069
+ // (DataTable.tsx's `getVisibleRows`) reads it to actually narrow rows.
1070
+ function useDataTableFilter(props) {
1071
+ const { filters: filtersProp, defaultFilters = EMPTY_FILTERS, onFiltersChange } = props;
1072
+ const [filters = EMPTY_FILTERS, setFilters] = useControllableState({
1073
+ prop: filtersProp,
1074
+ defaultProp: defaultFilters,
1075
+ onChange: onFiltersChange
1076
+ });
1077
+ const setFilterMode = useCallback((columnKey, mode)=>{
1078
+ setFilters((current = EMPTY_FILTERS)=>({
1079
+ ...current,
1080
+ [columnKey]: {
1081
+ mode,
1082
+ values: current[columnKey]?.values ?? []
1083
+ }
1084
+ }));
1085
+ }, [
1086
+ setFilters
1087
+ ]);
1088
+ const toggleFilterValue = useCallback((columnKey, value)=>{
1089
+ setFilters((current = EMPTY_FILTERS)=>({
1090
+ ...current,
1091
+ [columnKey]: {
1092
+ mode: current[columnKey]?.mode ?? "is",
1093
+ values: toggleMembership(current[columnKey]?.values ?? [], value)
1094
+ }
1095
+ }));
1096
+ }, [
1097
+ setFilters
1098
+ ]);
1099
+ const clearFilter = useCallback((columnKey)=>{
1100
+ setFilters((current = EMPTY_FILTERS)=>{
1101
+ if (!(columnKey in current)) return current;
1102
+ const next = {
1103
+ ...current
1104
+ };
1105
+ delete next[columnKey];
1106
+ return next;
1107
+ });
1108
+ }, [
1109
+ setFilters
1110
+ ]);
1111
+ const isFilterActive = useCallback((columnKey)=>(filters[columnKey]?.values.length ?? 0) > 0, [
1112
+ filters
1113
+ ]);
1114
+ return {
1115
+ filters,
1116
+ setFilterMode,
1117
+ toggleFilterValue,
1118
+ clearFilter,
1119
+ isFilterActive
1120
+ };
1121
+ }
1122
+
1123
+ // Triggers before the sentinel is actually on-screen -- Attio's own list prefetches
1124
+ // a page ahead of the visible edge so the next batch of rows is already there by the
1125
+ // time a fast scroll reaches them, rather than the user briefly out-running the data.
1126
+ const ROOT_MARGIN = "160px";
1127
+ /**
1128
+ * Owns the "is the user close enough to the bottom to load more" concern -- the way
1129
+ * useDataTableScroll owns the horizontal scroll-shadow edge state. An
1130
+ * IntersectionObserver watching a single sentinel row the styled layer renders as the
1131
+ * last child of `<tbody>` while `hasMore` is true (DataTable.tsx's own
1132
+ * `InfiniteScrollRow`), observed against `scrollRootRef`'s element (useDataTable.ts
1133
+ * wires this to useDataTableScroll's own scroll container) so a BOUNDED table
1134
+ * triggers off its own internal scroll, not the browsing viewport.
1135
+ */ function useDataTableInfiniteScroll(props) {
1136
+ const { onLoadMore, hasMore = true, loadingMore: loadingMoreProp, scrollRootRef } = props;
1137
+ const [internalLoadingMore, setInternalLoadingMore] = useState(false);
1138
+ const loadingMore = loadingMoreProp ?? internalLoadingMore;
1139
+ // Mirrors the latest props into a ref the observer callback reads -- the callback
1140
+ // is registered once per sentinel element (see the effect below) and must not
1141
+ // recreate the whole IntersectionObserver on every render just because a fresh
1142
+ // `onLoadMore` closure came in, the same "ref mirrors the latest value" shape a
1143
+ // debounced/throttled handler needs.
1144
+ const latest = useRef({
1145
+ onLoadMore,
1146
+ hasMore,
1147
+ loadingMore
1148
+ });
1149
+ latest.current = {
1150
+ onLoadMore,
1151
+ hasMore,
1152
+ loadingMore
1153
+ };
1154
+ const triggerLoadMore = useCallback(()=>{
1155
+ const { onLoadMore: fn, hasMore: has, loadingMore: busy } = latest.current;
1156
+ if (!shouldTriggerLoadMore(true, has, busy) || !fn) return;
1157
+ const result = fn();
1158
+ // Only a THENABLE result puts this hook in charge of `loadingMore` -- a
1159
+ // synchronous `onLoadMore` (already-fetched data, a local page) commits
1160
+ // immediately and never becomes "busy" at all, matching `loadingMoreProp`'s own
1161
+ // controlled shape: nothing to suppress a very next scroll-triggered call with.
1162
+ if (result && typeof result.then === "function") {
1163
+ if (loadingMoreProp === undefined) setInternalLoadingMore(true);
1164
+ result.finally(()=>{
1165
+ if (loadingMoreProp === undefined) setInternalLoadingMore(false);
1166
+ });
1167
+ }
1168
+ }, [
1169
+ loadingMoreProp
1170
+ ]);
1171
+ // A state slot (not a plain ref) so attaching the sentinel re-runs the effect below
1172
+ // -- a callback ref's own node can't be an effect dependency otherwise.
1173
+ const [sentinelNode, setSentinelNode] = useState(null);
1174
+ const sentinelRef = useCallback((node)=>setSentinelNode(node), []);
1175
+ useEffect(()=>{
1176
+ if (!sentinelNode || !onLoadMore || typeof IntersectionObserver === "undefined") return;
1177
+ // Effects run after every ref callback of the SAME commit (including
1178
+ // `scrollRootRef`'s own, set by useDataTableScroll's `scrollAreaRef`) regardless
1179
+ // of sibling ref attach order, so `scrollRootRef.current` is already populated
1180
+ // here even on the very first render that mounts both elements at once. `null`
1181
+ // (no scroll container yet, or none supplied) falls back to the browsing
1182
+ // viewport, IntersectionObserver's own default.
1183
+ const root = scrollRootRef?.current ?? null;
1184
+ const observer = new IntersectionObserver((entries)=>{
1185
+ if (entries.some((entry)=>entry.isIntersecting)) triggerLoadMore();
1186
+ }, {
1187
+ root,
1188
+ rootMargin: ROOT_MARGIN
1189
+ });
1190
+ observer.observe(sentinelNode);
1191
+ return ()=>observer.disconnect();
1192
+ }, [
1193
+ sentinelNode,
1194
+ onLoadMore,
1195
+ scrollRootRef,
1196
+ triggerLoadMore
1197
+ ]);
1198
+ return {
1199
+ /** Attach to the sentinel row's DOM node (a ref callback, `DataTable.tsx`'s own
1200
+ * `InfiniteScrollRow`). A no-op (no observer ever created) when `onLoadMore` is
1201
+ * omitted -- the styled layer still shouldn't render the sentinel at all in that
1202
+ * case (nothing to load more OF), this is just the inert fallback. */ sentinelRef,
1203
+ /** Whether to keep rendering the sentinel row at all. */ hasMore,
1204
+ /** A fetch is in flight -- shows the loading-more spinner row and gates further
1205
+ * triggers, whichever side (controlled or internal) reports it. */ loadingMore,
1206
+ /** Fires `onLoadMore` directly, subject to the same `hasMore`/`loadingMore` gate
1207
+ * the observer itself uses -- exposed for a consumer that wants a manual retry
1208
+ * affordance (e.g. after a failed load) rather than only the observer's own
1209
+ * scroll trigger. */ triggerLoadMore
1210
+ };
1211
+ }
1212
+
1213
+ /**
1214
+ * Tracks a horizontally-scrolling element's edge state for the sticky-column scroll
1215
+ * shadow (DATATABLE-ATTIO-SPEC.md's `stickyFirst`/`stickyLast`) -- crisp's own
1216
+ * `.crisp-table-scroll[data-scrolled-x]`/`[data-scrolled-x-end]`. Owns the DOM
1217
+ * measurement the way `useSelectFloating` owns floating-ui's own DOM measurement: a
1218
+ * real ref to a real scrollable element, re-measured on every scroll (and whenever the
1219
+ * styled layer calls `remeasure`, e.g. after a column resize/reorder changes the
1220
+ * scrollable width without the element itself ever firing a `scroll` event).
1221
+ */ function useDataTableScroll() {
1222
+ const elementRef = useRef(null);
1223
+ const [scrolledStart, setScrolledStart] = useState(false);
1224
+ const [scrolledEnd, setScrolledEnd] = useState(false);
1225
+ const remeasure = useCallback(()=>{
1226
+ const element = elementRef.current;
1227
+ if (!element) return;
1228
+ setScrolledStart(element.scrollLeft > 0);
1229
+ // A 1px slop: sub-pixel layout can leave `scrollLeft + clientWidth` a fraction
1230
+ // short of `scrollWidth` even when fully scrolled, which would otherwise leave
1231
+ // the trailing shadow stuck on forever.
1232
+ setScrolledEnd(element.scrollLeft + element.clientWidth < element.scrollWidth - 1);
1233
+ }, []);
1234
+ const scrollAreaRef = useCallback((node)=>{
1235
+ elementRef.current = node;
1236
+ if (node) remeasure();
1237
+ }, [
1238
+ remeasure
1239
+ ]);
1240
+ const onScroll = useCallback(()=>remeasure(), [
1241
+ remeasure
1242
+ ]);
1243
+ return {
1244
+ scrollAreaRef,
1245
+ scrolledStart,
1246
+ scrolledEnd,
1247
+ onScroll,
1248
+ remeasure,
1249
+ // The raw ref object itself (not just the callback ref above) -- useDataTable.ts
1250
+ // hands this to useDataTableInfiniteScroll as the IntersectionObserver `root` for
1251
+ // its sentinel row, so a scrolled/bounded table (DATATABLE-ATTIO-SPEC.md's
1252
+ // full-body-height feature) auto-loads against ITS OWN scroll boundary rather
1253
+ // than the browsing viewport. A stable object identity (a plain ref, not state):
1254
+ // useDataTableInfiniteScroll reads `.current` inside an effect, which always runs
1255
+ // after every ref callback of the same commit (including this one), so the
1256
+ // element is never read before it's attached regardless of sibling ref order.
1257
+ elementRef
1258
+ };
1259
+ }
1260
+
1261
+ // Owns the toolbar's single free-text query -- the way useDataTableSort owns the single
1262
+ // active sort column. It has no idea which columns are searchable or how a row's value
1263
+ // is read off them (that needs `TRow`, a type this headless package never sees), so
1264
+ // narrowing `data` against the query is the styled layer's job (DataTable.tsx's
1265
+ // `getVisibleRows`) -- the same split sort already draws: this hook tracks WHICH state
1266
+ // is active, the styled layer decides what that state does to the rows.
1267
+ function useDataTableSearch(props) {
1268
+ const { query: queryProp, defaultQuery = "", onQueryChange } = props;
1269
+ const [query = "", setQuery] = useControllableState({
1270
+ prop: queryProp,
1271
+ defaultProp: defaultQuery,
1272
+ onChange: onQueryChange
1273
+ });
1274
+ const clearQuery = useCallback(()=>setQuery(""), [
1275
+ setQuery
1276
+ ]);
1277
+ return {
1278
+ query,
1279
+ setQuery,
1280
+ clearQuery
1281
+ };
1282
+ }
1283
+
1284
+ // Owns the selected-id set and its two shapes of toggle (one row, select-all) --
1285
+ // the way useSelectOptions owns the selected VALUE. `selectionState` and `toggleAll`
1286
+ // are always computed against `rowIds` (the currently rendered order), never against
1287
+ // `selectedIds` alone -- see dom.ts's `computeSelectionState` doc comment.
1288
+ function useDataTableSelection(props) {
1289
+ const { rowIds = [], selectable = false, selectedIds: selectedIdsProp, defaultSelectedIds = [], onSelectionChange } = props;
1290
+ const [selectedIds = [], setSelectedIds] = useControllableState({
1291
+ prop: selectedIdsProp,
1292
+ defaultProp: defaultSelectedIds,
1293
+ onChange: onSelectionChange
1294
+ });
1295
+ const selectedIdSet = useMemo(()=>new Set(selectedIds), [
1296
+ selectedIds
1297
+ ]);
1298
+ const isSelected = useCallback((id)=>selectedIdSet.has(id), [
1299
+ selectedIdSet
1300
+ ]);
1301
+ const toggleRow = useCallback((id)=>{
1302
+ setSelectedIds((current = [])=>toggleMembership(current, id));
1303
+ }, [
1304
+ setSelectedIds
1305
+ ]);
1306
+ const selectionState = useMemo(()=>computeSelectionState(rowIds, selectedIds), [
1307
+ rowIds,
1308
+ selectedIds
1309
+ ]);
1310
+ const toggleAll = useCallback(()=>{
1311
+ setSelectedIds(selectionState === "all" ? [] : rowIds);
1312
+ }, [
1313
+ selectionState,
1314
+ rowIds,
1315
+ setSelectedIds
1316
+ ]);
1317
+ const getRowProps = useCallback((rowId)=>{
1318
+ const isRowSelected = isSelected(rowId);
1319
+ return elementProps({
1320
+ role: "row",
1321
+ "aria-selected": ariaAttr(isRowSelected),
1322
+ "data-selected": dataAttr(isRowSelected)
1323
+ });
1324
+ }, [
1325
+ isSelected
1326
+ ]);
1327
+ return {
1328
+ selectable,
1329
+ rowIds,
1330
+ selectedIds,
1331
+ selectionState,
1332
+ isSelected,
1333
+ toggleRow,
1334
+ toggleAll,
1335
+ getRowProps
1336
+ };
1337
+ }
1338
+
1339
+ // Owns the single active sort column/direction and the click/keyboard toggle that
1340
+ // cycles it -- the way useSelectHighlight owns the single active index. A header
1341
+ // cell's full prop set (aria-sort, data-sortable/data-sorted, the click and
1342
+ // Enter/Space handlers) is built here too, beside the state it reads, rather than
1343
+ // re-derived in the styled layer or the root primitive.
1344
+ function useDataTableSort(props) {
1345
+ const { sort: sortProp, defaultSort, onSortChange } = props;
1346
+ const [sort, setSort] = useControllableState({
1347
+ prop: sortProp,
1348
+ defaultProp: defaultSort,
1349
+ onChange: (nextSort)=>{
1350
+ // `toggleSort` never sets `sort` back to `undefined`, but the underlying state is
1351
+ // typed as possibly-undefined so an uncontrolled table can start with no sort at all.
1352
+ if (nextSort) onSortChange?.(nextSort);
1353
+ }
1354
+ });
1355
+ const toggleSort = useCallback((key)=>{
1356
+ setSort((current)=>{
1357
+ if (!current || current.key !== key) return {
1358
+ key,
1359
+ dir: "asc"
1360
+ };
1361
+ return {
1362
+ key,
1363
+ dir: current.dir === "asc" ? "desc" : "asc"
1364
+ };
1365
+ });
1366
+ }, [
1367
+ setSort
1368
+ ]);
1369
+ const getHeaderCellProps = useCallback((columnKey, sortable)=>{
1370
+ const isSorted = Boolean(columnKey) && sort?.key === columnKey;
1371
+ const canSort = Boolean(sortable && columnKey);
1372
+ return elementProps({
1373
+ role: "columnheader",
1374
+ "aria-sort": canSort ? isSorted ? sort?.dir === "asc" ? "ascending" : "descending" : "none" : undefined,
1375
+ tabIndex: canSort ? 0 : undefined,
1376
+ "data-sortable": dataAttr(canSort),
1377
+ "data-sorted": dataAttr(isSorted),
1378
+ "data-sort-direction": isSorted ? sort?.dir : undefined,
1379
+ onClick (event) {
1380
+ if (event.defaultPrevented || !canSort || !columnKey) return;
1381
+ toggleSort(columnKey);
1382
+ },
1383
+ onKeyDown (event) {
1384
+ if (event.defaultPrevented || !canSort || !columnKey) return;
1385
+ if (event.key === "Enter" || event.key === " ") {
1386
+ event.preventDefault();
1387
+ toggleSort(columnKey);
1388
+ }
1389
+ }
1390
+ });
1391
+ }, [
1392
+ sort,
1393
+ toggleSort
1394
+ ]);
1395
+ return {
1396
+ sort,
1397
+ toggleSort,
1398
+ getHeaderCellProps
1399
+ };
1400
+ }
1401
+
1402
+ // Composes the eight independent concerns a table root owns -- which column is
1403
+ // sorted, which rows are selected, the toolbar's free-text query, each column's own
1404
+ // filter, column order/visibility/width/label, each column's ColumnCalc footer op, the
1405
+ // horizontal scroll-shadow edge state, and which single cell is mid-edit/pending/
1406
+ // erroring -- the way useSelect composes useSelectHighlight + useSelectOptions. No
1407
+ // sub-hook knows about any other; a future concern (row grouping) is a ninth sibling
1408
+ // call here, not a branch inside an existing one.
1409
+ function useDataTable(props) {
1410
+ const sort = useDataTableSort(props);
1411
+ const selection = useDataTableSelection(props);
1412
+ const search = useDataTableSearch(props);
1413
+ const filter = useDataTableFilter(props);
1414
+ const columns = useDataTableColumns(props);
1415
+ const columnCalc = useDataTableColumnCalc(props);
1416
+ const scroll = useDataTableScroll();
1417
+ const edit = useDataTableEdit(props);
1418
+ // Wired to `scroll`'s own scroll-container ref (not a fresh one of its own) so a
1419
+ // BOUNDED table's auto-load triggers off its own internal scroll, not the
1420
+ // browsing viewport -- see useDataTableInfiniteScroll.ts's own doc comment.
1421
+ const infiniteScroll = useDataTableInfiniteScroll({
1422
+ ...props,
1423
+ scrollRootRef: scroll.elementRef
1424
+ });
1425
+ return {
1426
+ ...sort,
1427
+ ...selection,
1428
+ ...search,
1429
+ ...filter,
1430
+ ...columns,
1431
+ ...columnCalc,
1432
+ ...scroll,
1433
+ ...edit,
1434
+ ...infiniteScroll,
1435
+ rootProps: elementProps({
1436
+ role: "table"
1437
+ })
1438
+ };
1439
+ }
1440
+
1441
+ // Per-row read of the root's selection state -- the way useSelectItem reads
1442
+ // `getItemProps` off the root for one option. Kept as its own hook (rather than
1443
+ // inlined into the styled `DataTableRow`) so `DataTableCell`/`DataTableSelectionCell`
1444
+ // can read the SAME row's `isSelected`/`toggleRow` through `useDataTableRowContext`
1445
+ // without either subscribing to the whole table's context or being handed `rowId`
1446
+ // again as a prop.
1447
+ function useDataTableRow(props) {
1448
+ const { rowId } = props;
1449
+ const { isSelected, toggleRow, getRowProps } = useDataTableContext();
1450
+ return {
1451
+ rowId,
1452
+ isSelected: isSelected(rowId),
1453
+ toggleRow: ()=>toggleRow(rowId),
1454
+ rootProps: getRowProps(rowId)
1455
+ };
1456
+ }
1457
+
1458
+ const DataTableContext = /*#__PURE__*/ createContext(null);
1459
+ const DataTableProvider = DataTableContext.Provider;
1460
+ function useDataTableContext({ strict = true } = {}) {
1461
+ const context = useContext(DataTableContext);
1462
+ if (!context && strict) {
1463
+ throw new Error("useDataTableContext must be used within a DataTable");
1464
+ }
1465
+ return context;
1466
+ }
1467
+ const DataTableRowContext = /*#__PURE__*/ createContext(null);
1468
+ const DataTableRowProvider = DataTableRowContext.Provider;
1469
+ function useDataTableRowContext({ strict = true } = {}) {
1470
+ const context = useContext(DataTableRowContext);
1471
+ if (!context && strict) {
1472
+ throw new Error("useDataTableRowContext must be used within a DataTableRow");
1473
+ }
1474
+ return context;
1475
+ }
1476
+ const DataTableRoot = /*#__PURE__*/ forwardRef((props, ref)=>{
1477
+ const { sort, defaultSort, onSortChange, rowIds, selectable, selectedIds, defaultSelectedIds, onSelectionChange, query, defaultQuery, onQueryChange, filters, defaultFilters, onFiltersChange, columnKeys, columnOrder, defaultColumnOrder, onColumnOrderChange, hiddenColumnKeys, defaultHiddenColumnKeys, onHiddenColumnKeysChange, columnWidths, defaultColumnWidths, onColumnWidthsChange, columnLabels, defaultColumnLabels, onColumnLabelsChange, columnCalcOps, defaultColumnCalcOps, onColumnCalcOpsChange, onEdit, onLoadMore, hasMore, loadingMore, ...otherProps } = props;
1478
+ const api = useDataTable({
1479
+ sort,
1480
+ defaultSort,
1481
+ onSortChange,
1482
+ rowIds,
1483
+ selectable,
1484
+ selectedIds,
1485
+ defaultSelectedIds,
1486
+ onSelectionChange,
1487
+ query,
1488
+ defaultQuery,
1489
+ onQueryChange,
1490
+ filters,
1491
+ defaultFilters,
1492
+ onFiltersChange,
1493
+ columnKeys,
1494
+ columnOrder,
1495
+ defaultColumnOrder,
1496
+ onColumnOrderChange,
1497
+ hiddenColumnKeys,
1498
+ defaultHiddenColumnKeys,
1499
+ onHiddenColumnKeysChange,
1500
+ columnWidths,
1501
+ defaultColumnWidths,
1502
+ onColumnWidthsChange,
1503
+ columnLabels,
1504
+ defaultColumnLabels,
1505
+ onColumnLabelsChange,
1506
+ columnCalcOps,
1507
+ defaultColumnCalcOps,
1508
+ onColumnCalcOpsChange,
1509
+ onEdit,
1510
+ onLoadMore,
1511
+ hasMore,
1512
+ loadingMore
1513
+ });
1514
+ return /*#__PURE__*/ jsx(DataTableProvider, {
1515
+ value: api,
1516
+ children: /*#__PURE__*/ jsx(Primitive.div, {
1517
+ ref: ref,
1518
+ ...otherProps
1519
+ })
1520
+ });
1521
+ });
1522
+ DataTableRoot.displayName = "DataTableRoot";
1523
+ /**
1524
+ * The real `<table>` -- everything a native table gets for free (columns that line up
1525
+ * by construction across `<thead>`/`<tbody>`/`<tfoot>`, `<th scope="col">` semantics,
1526
+ * sticky `th`/`td` that actually work under horizontal scroll) that a `display: flex`
1527
+ * div grid faked with ARIA and broke on. `rootProps` (just `role: "table"`, which a
1528
+ * real `<table>` already implies) comes off context rather than being computed here --
1529
+ * `useDataTable()` runs once, in `DataTableRoot` above, not per render of this sibling.
1530
+ */ const DataTableTable$1 = /*#__PURE__*/ forwardRef((props, ref)=>{
1531
+ const { rootProps } = useDataTableContext();
1532
+ const mergedProps = mergeProps$1(rootProps, props);
1533
+ return /*#__PURE__*/ jsx(Primitive.table, {
1534
+ ref: ref,
1535
+ ...mergedProps
1536
+ });
1537
+ });
1538
+ DataTableTable$1.displayName = "DataTableTable";
1539
+ /**
1540
+ * `<thead>`, wrapping its own children in a single `<tr>` -- the styled layer still
1541
+ * renders every header cell as a flat child of `DataTableHeader` (one header row is
1542
+ * the only shape this table supports), so that `<tr>` is supplied HERE rather than
1543
+ * asking every call site to add it.
1544
+ */ const DataTableHeader$1 = /*#__PURE__*/ forwardRef(({ children, ...otherProps }, ref)=>{
1545
+ return /*#__PURE__*/ jsx(Primitive.thead, {
1546
+ ref: ref,
1547
+ ...otherProps,
1548
+ children: /*#__PURE__*/ jsx("tr", {
1549
+ children: children
1550
+ })
1551
+ });
1552
+ });
1553
+ DataTableHeader$1.displayName = "DataTableHeader";
1554
+ const DataTableHeaderCell$1 = /*#__PURE__*/ forwardRef(({ columnKey, sortable, ...otherProps }, ref)=>{
1555
+ const { getHeaderCellProps } = useDataTableContext();
1556
+ const mergedProps = mergeProps$1(getHeaderCellProps(columnKey, sortable), otherProps);
1557
+ return /*#__PURE__*/ jsx(Primitive.th, {
1558
+ scope: "col",
1559
+ ref: ref,
1560
+ ...mergedProps
1561
+ });
1562
+ });
1563
+ DataTableHeaderCell$1.displayName = "DataTableHeaderCell";
1564
+ const DataTableBody$1 = /*#__PURE__*/ forwardRef((props, ref)=>{
1565
+ return /*#__PURE__*/ jsx(Primitive.tbody, {
1566
+ ref: ref,
1567
+ ...props
1568
+ });
1569
+ });
1570
+ DataTableBody$1.displayName = "DataTableBody";
1571
+ const DataTableRow$1 = /*#__PURE__*/ forwardRef(({ rowId, ...otherProps }, ref)=>{
1572
+ const row = useDataTableRow({
1573
+ rowId
1574
+ });
1575
+ const mergedProps = mergeProps$1(row.rootProps, otherProps);
1576
+ return /*#__PURE__*/ jsx(DataTableRowProvider, {
1577
+ value: row,
1578
+ children: /*#__PURE__*/ jsx(Primitive.tr, {
1579
+ ref: ref,
1580
+ ...mergedProps
1581
+ })
1582
+ });
1583
+ });
1584
+ DataTableRow$1.displayName = "DataTableRow";
1585
+ const DataTableCell$1 = /*#__PURE__*/ forwardRef((props, ref)=>{
1586
+ return /*#__PURE__*/ jsx(Primitive.td, {
1587
+ ref: ref,
1588
+ ...props
1589
+ });
1590
+ });
1591
+ DataTableCell$1.displayName = "DataTableCell";
1592
+
1593
+ function r(e) {
1594
+ var t, f, n = "";
1595
+ if ("string" == typeof e || "number" == typeof e) n += e;
1596
+ else if ("object" == typeof e) if (Array.isArray(e)) {
1597
+ var o = e.length;
1598
+ for(t = 0; t < o; t++)e[t] && (f = r(e[t])) && (n && (n += " "), n += f);
1599
+ } else for(f in e)e[f] && (n && (n += " "), n += f);
1600
+ return n;
1601
+ }
1602
+ function clsx() {
1603
+ for(var e, t, f = 0, n = "", o = arguments.length; f < o; f++)(e = arguments[f]) && (t = r(e)) && (n && (n += " "), n += t);
1604
+ return n;
1605
+ }
1606
+
1607
+ function splitMultipleVariantsProps(props, recipesMap) {
1608
+ const multipleVariantsProps = {};
1609
+ const extractedKeys = new Set();
1610
+ for(const recipeKey in recipesMap){
1611
+ const recipe = recipesMap[recipeKey];
1612
+ const [variantProps] = recipe.splitVariantProps(props);
1613
+ multipleVariantsProps[recipeKey] = variantProps;
1614
+ for(const variantPropKey in variantProps){
1615
+ extractedKeys.add(variantPropKey);
1616
+ }
1617
+ }
1618
+ const remainingProps = {};
1619
+ for(const propKey in props){
1620
+ if (extractedKeys.has(propKey)) continue;
1621
+ remainingProps[propKey] = props[propKey];
1622
+ }
1623
+ return [
1624
+ multipleVariantsProps,
1625
+ remainingProps
1626
+ ];
1627
+ }
1628
+
1629
+ /** Restores defaults overwritten by explicitly passed undefined values. */ function restoreDefaultProps(props, defaultProps) {
1630
+ if (!defaultProps) return;
1631
+ for (const key of Object.keys(defaultProps)){
1632
+ if (props[key] === undefined) {
1633
+ props[key] = defaultProps[key];
1634
+ }
1635
+ }
1636
+ }
1637
+
1638
+ function createSlotRecipeContext(recipe) {
1639
+ const ClassNamesContext = /*#__PURE__*/ createContext(null);
1640
+ const PropsContext = /*#__PURE__*/ createContext(null);
1641
+ const ClassNamesProvider = ({ children, value })=>{
1642
+ return /*#__PURE__*/ jsx(ClassNamesContext.Provider, {
1643
+ value: value,
1644
+ children: children
1645
+ });
1646
+ };
1647
+ const PropsProvider = ({ children, value })=>{
1648
+ return /*#__PURE__*/ jsx(PropsContext.Provider, {
1649
+ value: value,
1650
+ children: children
1651
+ });
1652
+ };
1653
+ function useClassNames() {
1654
+ const context = useContext(ClassNamesContext);
1655
+ if (context === null) {
1656
+ throw new Error("useClassNames must be used within a ClassNamesProvider. Did you forget to wrap your component in a ClassNamesProvider?");
1657
+ }
1658
+ return context;
1659
+ }
1660
+ function useProps() {
1661
+ return useContext(PropsContext);
1662
+ }
1663
+ const withRootProvider = (Component, options)=>{
1664
+ const { defaultProps } = options ?? {};
1665
+ const StyledComponent = (innerProps)=>{
1666
+ const props = {
1667
+ ...defaultProps ?? {},
1668
+ ...useProps(),
1669
+ ...innerProps
1670
+ };
1671
+ restoreDefaultProps(props, defaultProps);
1672
+ const [variantProps, otherProps] = recipe.splitVariantProps(props);
1673
+ const classNames = recipe(variantProps); // TODO: should we memoize this?
1674
+ return /*#__PURE__*/ jsx(ClassNamesProvider, {
1675
+ value: classNames,
1676
+ children: /*#__PURE__*/ jsx(Component, {
1677
+ ...otherProps
1678
+ })
1679
+ });
1680
+ };
1681
+ // @ts-ignore
1682
+ StyledComponent.displayName = Component.displayName || Component.name;
1683
+ return StyledComponent;
1684
+ };
1685
+ const withProvider = (Component, slot, options)=>{
1686
+ const { defaultProps } = options ?? {};
1687
+ const StyledComponent = /*#__PURE__*/ forwardRef((innerProps, ref)=>{
1688
+ const props = {
1689
+ ...defaultProps ?? {},
1690
+ ...useProps(),
1691
+ ...innerProps
1692
+ };
1693
+ restoreDefaultProps(props, defaultProps);
1694
+ const [variantProps, otherProps] = recipe.splitVariantProps(props);
1695
+ const classNames = recipe(variantProps); // TODO: should we memoize this?
1696
+ const className = classNames[slot];
1697
+ return /*#__PURE__*/ jsx(ClassNamesProvider, {
1698
+ value: classNames,
1699
+ children: /*#__PURE__*/ jsx(Component, {
1700
+ ref: ref,
1701
+ ...otherProps,
1702
+ className: clsx(className, props.className)
1703
+ })
1704
+ });
1705
+ });
1706
+ // @ts-ignore
1707
+ StyledComponent.displayName = Component.displayName || Component.name;
1708
+ return StyledComponent;
1709
+ };
1710
+ const withContext = (Component, slot)=>{
1711
+ const StyledComponent = /*#__PURE__*/ forwardRef((props, ref)=>{
1712
+ const classNames = useClassNames();
1713
+ const className = classNames?.[slot];
1714
+ return /*#__PURE__*/ jsx(Component, {
1715
+ ref: ref,
1716
+ ...props,
1717
+ className: clsx(className, props.className)
1718
+ });
1719
+ });
1720
+ // @ts-ignore
1721
+ StyledComponent.displayName = Component.displayName || Component.name;
1722
+ return StyledComponent;
1723
+ };
1724
+ return {
1725
+ ClassNamesProvider,
1726
+ PropsProvider,
1727
+ useClassNames,
1728
+ useProps,
1729
+ withRootProvider,
1730
+ withProvider,
1731
+ withContext
1732
+ };
1733
+ }
1734
+
1735
+ function createWithStateProps(useContexts) {
1736
+ return function withStateProps(Component) {
1737
+ const Node = /*#__PURE__*/ forwardRef((props, ref)=>{
1738
+ const stateProps = {};
1739
+ for (const contextConfig of useContexts){
1740
+ if (typeof contextConfig === "function") {
1741
+ Object.assign(stateProps, contextConfig({
1742
+ strict: true
1743
+ })?.stateProps);
1744
+ } else {
1745
+ const { useContext, strict = false } = contextConfig;
1746
+ Object.assign(stateProps, useContext({
1747
+ strict
1748
+ })?.stateProps);
1749
+ }
1750
+ }
1751
+ // @ts-ignore
1752
+ return /*#__PURE__*/ jsx(Component, {
1753
+ ref: ref,
1754
+ ...stateProps,
1755
+ ...props
1756
+ });
1757
+ });
1758
+ Node.displayName = Component.displayName || Component.name;
1759
+ return Node;
1760
+ };
1761
+ }
1762
+
1763
+ function withScaleFeedback(Component) {
1764
+ const Pressable = /*#__PURE__*/ React.forwardRef((props, ref)=>{
1765
+ const { scaleFeedbackRef, scaleFeedbackClassName } = useScaleFeedback();
1766
+ const composedRef = useComposedRefs(scaleFeedbackRef, ref);
1767
+ return /*#__PURE__*/ jsx(Component, {
1768
+ ...props,
1769
+ ref: composedRef,
1770
+ className: clsx(scaleFeedbackClassName, props.className)
1771
+ });
1772
+ });
1773
+ Pressable.displayName = Component.displayName;
1774
+ return Pressable;
1775
+ }
1776
+
1777
+ const InternalIcon = /*#__PURE__*/ forwardRef(({ svg, ...otherProps }, ref)=>{
1778
+ return /*#__PURE__*/ jsx(Slot, {
1779
+ ref: ref,
1780
+ "aria-hidden": true,
1781
+ ...otherProps,
1782
+ children: svg
1783
+ });
1784
+ });
1785
+
1786
+ function createRecipeContext(recipe) {
1787
+ const PropsContext = /*#__PURE__*/ createContext(null);
1788
+ const PropsProvider = ({ children, value })=>{
1789
+ return /*#__PURE__*/ jsx(PropsContext.Provider, {
1790
+ value: value,
1791
+ children: children
1792
+ });
1793
+ };
1794
+ function useProps() {
1795
+ return useContext(PropsContext);
1796
+ }
1797
+ const withContext = (Component, options)=>{
1798
+ const { defaultProps } = options ?? {};
1799
+ const StyledComponent = /*#__PURE__*/ forwardRef((innerProps, ref)=>{
1800
+ const props = {
1801
+ ...defaultProps ?? {},
1802
+ ...useProps(),
1803
+ ...innerProps
1804
+ };
1805
+ restoreDefaultProps(props, defaultProps);
1806
+ const [variantProps, otherProps] = recipe.splitVariantProps(props);
1807
+ const className = recipe(variantProps); // TODO: should we memoize this?
1808
+ return /*#__PURE__*/ jsx(Component, {
1809
+ ref: ref,
1810
+ ...otherProps,
1811
+ className: clsx(className, props.className)
1812
+ });
1813
+ });
1814
+ StyledComponent.displayName = Component.displayName || Component.name;
1815
+ return StyledComponent;
1816
+ };
1817
+ function withPropsProvider() {
1818
+ return PropsProvider;
1819
+ }
1820
+ return {
1821
+ PropsProvider,
1822
+ useProps,
1823
+ withContext,
1824
+ withPropsProvider
1825
+ };
1826
+ }
1827
+
1828
+ const { withContext: withGroupContext } = createRecipeContext(checkboxGroup);
1829
+ const { ClassNamesProvider: ClassNamesProvider$3, withContext: withContext$6 } = createSlotRecipeContext(checkbox);
1830
+ const { withProvider: withCheckmarkProvider, useClassNames: useCheckmarkClassNames, PropsProvider: CheckmarkPropsProvider } = createSlotRecipeContext(checkmark);
1831
+ const withStateProps$4 = createWithStateProps([
1832
+ useCheckboxContext
1833
+ ]);
1834
+ withGroupContext(Primitive.div);
1835
+ const CheckboxRoot = Object.assign(/*#__PURE__*/ forwardRef(({ className, ...props }, ref)=>{
1836
+ const [{ checkbox: checkboxVariantProps, checkmark: checkmarkVariantProps }, otherProps] = splitMultipleVariantsProps(props, {
1837
+ checkbox,
1838
+ checkmark
1839
+ });
1840
+ const classNames = checkbox(checkboxVariantProps);
1841
+ return /*#__PURE__*/ jsx(CheckmarkPropsProvider, {
1842
+ value: checkmarkVariantProps,
1843
+ children: /*#__PURE__*/ jsx(ClassNamesProvider$3, {
1844
+ value: classNames,
1845
+ children: /*#__PURE__*/ jsx(Checkbox.Root, {
1846
+ ref: ref,
1847
+ className: clsx(classNames.root, className),
1848
+ ...otherProps
1849
+ })
1850
+ })
1851
+ });
1852
+ }), {
1853
+ Primitive: Checkbox.Root
1854
+ });
1855
+ const CheckboxControl = withScaleFeedback(withCheckmarkProvider(Checkbox.Control, "root"));
1856
+ const CheckboxIndicator = /*#__PURE__*/ forwardRef(({ unchecked: uncheckedSvg, checked: checkedSvg, indeterminate: indeterminateSvg, ...otherProps }, ref)=>{
1857
+ const { stateProps, checked, indeterminate } = useCheckboxContext();
1858
+ const classNames = useCheckmarkClassNames();
1859
+ const mergedProps = mergeProps$1(stateProps, {
1860
+ className: classNames.icon
1861
+ }, otherProps);
1862
+ if (indeterminate && !indeterminateSvg) {
1863
+ console.warn("CheckboxIndicator: the `indeterminate` prop must be provided when the checkbox is in an indeterminate state.");
1864
+ }
1865
+ if (indeterminate) return /*#__PURE__*/ jsx(InternalIcon, {
1866
+ ref: ref,
1867
+ svg: indeterminateSvg,
1868
+ ...mergedProps
1869
+ });
1870
+ if (checked) return /*#__PURE__*/ jsx(InternalIcon, {
1871
+ ref: ref,
1872
+ svg: checkedSvg,
1873
+ ...mergedProps
1874
+ });
1875
+ if (uncheckedSvg) return /*#__PURE__*/ jsx(InternalIcon, {
1876
+ ref: ref,
1877
+ svg: uncheckedSvg,
1878
+ ...mergedProps
1879
+ });
1880
+ return null;
1881
+ });
1882
+ CheckboxIndicator.displayName = "CheckboxIndicator";
1883
+ const CheckboxLabel = withContext$6(withStateProps$4(Primitive.span), "label");
1884
+ const CheckboxHiddenInput = Checkbox.HiddenInput;
1885
+
1886
+ const { withProvider: withProvider$2, withContext: withContext$5 } = createSlotRecipeContext(avatar);
1887
+ const AvatarRoot = withProvider$2(Image.Root, "root");
1888
+ const AvatarImage = withContext$5(Image.Content, "image");
1889
+ const AvatarFallback = withContext$5(Image.Fallback, "fallback");
1890
+ withContext$5(Primitive.div, "badge");
1891
+
1892
+ const Badge = /*#__PURE__*/ forwardRef(({ className, children, ...props }, ref)=>{
1893
+ const [variantProps, restProps] = badge.splitVariantProps(props);
1894
+ const { root, label } = badge(variantProps);
1895
+ return /*#__PURE__*/ jsx(Primitive.span, {
1896
+ className: clsx(root, className),
1897
+ ...restProps,
1898
+ ref: ref,
1899
+ children: /*#__PURE__*/ jsx(Primitive.span, {
1900
+ className: label,
1901
+ children: children
1902
+ })
1903
+ });
1904
+ });
1905
+ Badge.displayName = "Badge";
1906
+
1907
+ // Attio cell-type renderers, built from seed's own components -- not exported from
1908
+ // the package's public surface (kept alongside DataTable.tsx as an implementation
1909
+ // detail, the way Select keeps `InternalIcon` under `components/private`). Each
1910
+ // renderer takes the raw column value and the table's resolved recipe classNames,
1911
+ // and returns `null` for an empty value: Attio's own empty cell is genuinely blank,
1912
+ // no placeholder character, for any type (crisp's datatable.tsx, #412).
1913
+ // seed's `CheckboxIndicator` renders exactly what its `checked` prop is -- it does not
1914
+ // supply a checkmark on its own. `checked={null}` (this file's own pre-fix state)
1915
+ // therefore rendered an empty box even once the value WAS true; every read-only/
1916
+ // editable checkbox cell in this component needs a real icon here, the same one the
1917
+ // row-selection checkbox already uses (`DataTable.tsx`'s `SELECTION_ICON_SIZE`, 12px).
1918
+ const CHECKBOX_CELL_CHECKED_ICON = /*#__PURE__*/ jsx(IconCheckmarkLine_default, {
1919
+ size: 12
1920
+ });
1921
+ // The 6 tones Badge ships (`@seed-design/css/recipes/badge`) -- not Attio's own
1922
+ // wider palette (crisp's tag/status cells hash across a measured 7- or 12-colour
1923
+ // set it defines itself), because this component builds cell chrome ONLY from
1924
+ // seed's already-shipped primitives rather than adding a parallel palette. Same
1925
+ // stable-hash approach as crisp's own `toneFor` (multiply-and-add over char codes),
1926
+ // just over seed's tone list instead of crisp's.
1927
+ const BADGE_TONES = [
1928
+ "neutral",
1929
+ "brand",
1930
+ "informative",
1931
+ "positive",
1932
+ "warning",
1933
+ "critical"
1934
+ ];
1935
+ function toneFor(value) {
1936
+ let hash = 0;
1937
+ for(let index = 0; index < value.length; index++){
1938
+ hash = hash * 31 + value.charCodeAt(index) >>> 0;
1939
+ }
1940
+ return BADGE_TONES[hash % BADGE_TONES.length];
1941
+ }
1942
+ function initialsFor(name) {
1943
+ const parts = name.trim().split(/\s+/);
1944
+ return ((parts[0]?.[0] ?? "") + (parts[1]?.[0] ?? "")).toUpperCase() || "?";
1945
+ }
1946
+ function formatNumberCell(value, locale) {
1947
+ return typeof value === "number" ? value.toLocaleString(locale) : null;
1948
+ }
1949
+ function formatCurrencyCell(value, symbol, locale) {
1950
+ return typeof value === "number" ? `${symbol}${value.toLocaleString(locale)}` : null;
1951
+ }
1952
+ function toDate(value) {
1953
+ const date = value instanceof Date ? value : typeof value === "string" ? new Date(value) : null;
1954
+ return date && !Number.isNaN(date.getTime()) ? date : null;
1955
+ }
1956
+ /** `<input type="date">`'s own value shape (`yyyy-mm-dd`) -- editors.tsx's plain
1957
+ * `cellType: "date"` inline editor reads/writes exactly this string, native to the
1958
+ * browser's own date input, so no separate parse/format step is needed on commit. */ function toDateInputValue(value) {
1959
+ const date = toDate(value);
1960
+ if (!date) return "";
1961
+ const pad = (n)=>String(n).padStart(2, "0");
1962
+ return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
1963
+ }
1964
+ /** `<input type="datetime-local">`'s own value shape (`yyyy-mm-ddThh:mm`) -- same
1965
+ * reasoning as `toDateInputValue` above, one input type over. */ function toDateTimeInputValue(value) {
1966
+ const date = toDate(value);
1967
+ if (!date) return "";
1968
+ const pad = (n)=>String(n).padStart(2, "0");
1969
+ return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(date.getMinutes())}`;
1970
+ }
1971
+ function formatDateCell(value, locale) {
1972
+ const date = toDate(value);
1973
+ if (!date) return null;
1974
+ return new Intl.DateTimeFormat(locale, {
1975
+ month: "short",
1976
+ day: "numeric",
1977
+ year: "numeric"
1978
+ }).format(date);
1979
+ }
1980
+ function formatDateTimeCell(value, locale) {
1981
+ const date = toDate(value);
1982
+ if (!date) return null;
1983
+ return new Intl.DateTimeFormat(locale, {
1984
+ month: "short",
1985
+ day: "numeric",
1986
+ year: "numeric",
1987
+ hour: "numeric",
1988
+ minute: "2-digit"
1989
+ }).format(date);
1990
+ }
1991
+ function EntityCell({ value, className, nameClassName }) {
1992
+ const entity = typeof value === "object" && value !== null ? value : {
1993
+ name: String(value ?? "")
1994
+ };
1995
+ if (!entity.name) return null;
1996
+ return /*#__PURE__*/ jsxs("span", {
1997
+ className: className,
1998
+ children: [
1999
+ /*#__PURE__*/ jsxs(AvatarRoot, {
2000
+ size: "20",
2001
+ children: [
2002
+ entity.avatar && /*#__PURE__*/ jsx(AvatarImage, {
2003
+ src: entity.avatar,
2004
+ alt: ""
2005
+ }),
2006
+ /*#__PURE__*/ jsx(AvatarFallback, {
2007
+ children: initialsFor(entity.name)
2008
+ })
2009
+ ]
2010
+ }),
2011
+ /*#__PURE__*/ jsx("span", {
2012
+ className: nameClassName,
2013
+ children: entity.name
2014
+ })
2015
+ ]
2016
+ });
2017
+ }
2018
+ function TagsCell({ value, className }) {
2019
+ const tags = Array.isArray(value) ? value : [];
2020
+ if (!tags.length) return null;
2021
+ return /*#__PURE__*/ jsx("span", {
2022
+ className: className,
2023
+ children: tags.map((tag)=>/*#__PURE__*/ jsx(Badge, {
2024
+ tone: toneFor(tag),
2025
+ variant: "weak",
2026
+ size: "medium",
2027
+ children: tag
2028
+ }, tag))
2029
+ });
2030
+ }
2031
+ function StatusCell({ value }) {
2032
+ const status = value == null ? "" : String(value);
2033
+ if (!status) return null;
2034
+ return /*#__PURE__*/ jsx(Badge, {
2035
+ tone: toneFor(status),
2036
+ variant: "weak",
2037
+ size: "medium",
2038
+ children: status
2039
+ });
2040
+ }
2041
+ function LinkCell({ column, value, className }) {
2042
+ const text = value == null ? "" : String(value);
2043
+ if (!text) return null;
2044
+ const isUrl = column.type === "url";
2045
+ const href = isUrl ? /^https?:\/\//.test(text) ? text : `https://${text}` : `mailto:${text}`;
2046
+ return /*#__PURE__*/ jsx("a", {
2047
+ className: className,
2048
+ href: href,
2049
+ target: isUrl ? "_blank" : undefined,
2050
+ rel: isUrl ? "noreferrer" : undefined,
2051
+ // A link cell points somewhere other than "select this row" -- its own
2052
+ // navigation must not also fire a row click handler.
2053
+ onClick: (event)=>event.stopPropagation(),
2054
+ children: text
2055
+ });
2056
+ }
2057
+ // Attio's own measured 5-point star path (crisp's `RATING_STAR_D`, traced from the
2058
+ // live widget's SVG) -- reused verbatim rather than a generic star glyph, so a
2059
+ // rating cell's silhouette matches Attio exactly instead of approximating it. Exported
2060
+ // so editors.tsx's interactive `RatingEditor` (an `editable` rating column, clicking a
2061
+ // star commits) draws the identical silhouette rather than a second, slightly
2062
+ // different one.
2063
+ const RATING_STAR_D = "M7.32203 2.81085C7.65793 1.97438 8.84207 1.97438 9.17797 2.81085L10.0851 5.06967C10.2281 5.42587 10.5624 5.66876 10.9454 5.69473L13.3739 5.8594C14.2733 5.92038 14.6392 7.04656 13.9475 7.62451L12.0795 9.18521C11.7849 9.43132 11.6572 9.82433 11.7509 10.1966L12.3447 12.5572C12.5647 13.4313 11.6067 14.1273 10.8433 13.6481L8.78171 12.3538C8.45661 12.1497 8.04339 12.1497 7.71829 12.3538L5.65675 13.6481C4.89333 14.1273 3.93534 13.4313 4.15526 12.5572L4.74911 10.1966C4.84276 9.82433 4.71507 9.43132 4.4205 9.18521L2.55254 7.62451C1.86081 7.04656 2.22673 5.92038 3.12606 5.8594L5.55463 5.69473C5.9376 5.66876 6.27191 5.42587 6.41495 5.06967L7.32203 2.81085Z";
2064
+ // Filled = the same measured Attio brand blue the selected-row tint uses
2065
+ // (`data-table.ts`'s `ATTIO_BRAND_BLUE`); empty = crisp's own measured
2066
+ // `rgba(0,0,0,0.1)`. Literal, not a recipe token -- see that file's comment on why.
2067
+ const RATING_FILLED = "#0298d9";
2068
+ const RATING_EMPTY = "rgba(0, 0, 0, 0.1)";
2069
+ function RatingCell({ value, maxRating = 5 }) {
2070
+ const rating = typeof value === "number" ? value : Number(value);
2071
+ if (!rating || Number.isNaN(rating)) return null;
2072
+ return /*#__PURE__*/ jsx("span", {
2073
+ style: {
2074
+ display: "inline-flex",
2075
+ alignItems: "center",
2076
+ gap: 2
2077
+ },
2078
+ children: Array.from({
2079
+ length: maxRating
2080
+ }, (_, index)=>// biome-ignore lint/suspicious/noArrayIndexKey: a fixed-length star row has no other identity
2081
+ /*#__PURE__*/ jsx("svg", {
2082
+ width: 16,
2083
+ height: 16,
2084
+ viewBox: "0 0 16 16",
2085
+ fill: "none",
2086
+ role: "presentation",
2087
+ "aria-hidden": "true",
2088
+ children: /*#__PURE__*/ jsx("path", {
2089
+ d: RATING_STAR_D,
2090
+ fill: index < rating ? RATING_FILLED : RATING_EMPTY
2091
+ })
2092
+ }, index))
2093
+ });
2094
+ }
2095
+ /** `type: "checkbox"`'s READ-ONLY display -- a disabled seed `Checkbox` reflecting
2096
+ * the raw boolean, so a non-`editable` checkbox column still reads as a checkbox
2097
+ * (crisp's own measured shape, distinct from `switch`/`rating`'s toggle/star), not a
2098
+ * bare "true"/"false" string. The `editable` case (a live, clickable checkbox) is
2099
+ * `editors.tsx`'s `CheckboxEditor` -- this renderer never handles a click. */ function CheckboxCell({ value }) {
2100
+ const checked = value === true || value === "true";
2101
+ return /*#__PURE__*/ jsxs(CheckboxRoot, {
2102
+ checked: checked,
2103
+ disabled: true,
2104
+ tabIndex: -1,
2105
+ children: [
2106
+ /*#__PURE__*/ jsx(CheckboxControl, {
2107
+ children: /*#__PURE__*/ jsx(CheckboxIndicator, {
2108
+ checked: CHECKBOX_CELL_CHECKED_ICON
2109
+ })
2110
+ }),
2111
+ /*#__PURE__*/ jsx(CheckboxHiddenInput, {
2112
+ "aria-label": "Value"
2113
+ })
2114
+ ]
2115
+ });
2116
+ }
2117
+
2118
+ /** The subset of `useDataTableContext()` every "resolve which columns to actually
2119
+ * render, in what order, under what label/width" helper below needs -- narrowed so
2120
+ * each helper's own signature documents exactly what it reads, rather than the whole
2121
+ * table API. */ function useEffectiveColumns(columns) {
2122
+ const { visibleColumnOrder, getColumnLabel, getColumnWidth } = useDataTableContext();
2123
+ return React.useMemo(()=>{
2124
+ const byKey = new Map(columns.map((column)=>[
2125
+ column.key,
2126
+ column
2127
+ ]));
2128
+ return visibleColumnOrder.map((key)=>byKey.get(key)).filter((column)=>Boolean(column)).map((column)=>({
2129
+ column,
2130
+ label: getColumnLabel(column.key, column.header),
2131
+ width: getColumnWidth(column.key) ?? column.width
2132
+ }));
2133
+ }, [
2134
+ columns,
2135
+ visibleColumnOrder,
2136
+ getColumnLabel,
2137
+ getColumnWidth
2138
+ ]);
2139
+ }
2140
+ /** Sticky positioning for the leading/trailing visible cell of a row (header, body, or
2141
+ * footer) -- `stickyFirst` pins the SELECTION cell when the table has one, plus the
2142
+ * first data column; `stickyLast` always pins the last VISIBLE data column. Only the
2143
+ * OUTERMOST sticky cell on each side gets `data-sticky-edge(-end)` -- that's the one
2144
+ * whose shadow should reveal on scroll, not every sticky cell.
2145
+ *
2146
+ * When both a selection column and a sticky first data column are present, the edge
2147
+ * is the data column (e.g. Company), not the 40px selection cell: the data column is
2148
+ * ALSO `position: sticky`, sitting immediately to its right, so a shadow cast from the
2149
+ * selection cell's trailing edge would render directly underneath that opaque sticky
2150
+ * cell and never actually be visible. Putting `data-sticky-edge` on the trailing-most
2151
+ * sticky-left cell is what makes the shadow render somewhere unobstructed -- past the
2152
+ * whole pinned group, not between two members of it. */ function stickyProps(options) {
2153
+ const { selectable, stickyFirst, stickyLast, isSelectionCell, isFirst, isLast } = options;
2154
+ if (stickyFirst && isSelectionCell) {
2155
+ // Still sticky either way. The edge marker only belongs here when there is no
2156
+ // sticky first DATA column to hand it to instead (i.e. no columns at all) --
2157
+ // otherwise that data column is the true trailing edge of the pinned group.
2158
+ return selectable ? {
2159
+ "data-sticky": "",
2160
+ style: {
2161
+ insetInlineStart: 0
2162
+ }
2163
+ } : {
2164
+ "data-sticky": "",
2165
+ "data-sticky-edge": "",
2166
+ style: {
2167
+ insetInlineStart: 0
2168
+ }
2169
+ };
2170
+ }
2171
+ if (stickyFirst && isFirst && !selectable) {
2172
+ return {
2173
+ "data-sticky": "",
2174
+ "data-sticky-edge": "",
2175
+ style: {
2176
+ insetInlineStart: 0
2177
+ }
2178
+ };
2179
+ }
2180
+ if (stickyFirst && isFirst && selectable) {
2181
+ // The trailing edge of the pinned group -- past the 40px selection cell, so this
2182
+ // is where the scroll shadow actually renders unobstructed.
2183
+ return {
2184
+ "data-sticky": "",
2185
+ "data-sticky-edge": "",
2186
+ style: {
2187
+ insetInlineStart: 40
2188
+ }
2189
+ };
2190
+ }
2191
+ if (stickyLast && isLast) {
2192
+ return {
2193
+ "data-sticky": "",
2194
+ "data-sticky-edge-end": "",
2195
+ style: {
2196
+ insetInlineEnd: 0
2197
+ }
2198
+ };
2199
+ }
2200
+ return {};
2201
+ }
2202
+
2203
+ // A select-only combobox spans three specs — the trigger, the floating listbox
2204
+ // container, and the option rows — each with its own independent states and its own
2205
+ // variant map. `SelectRoot` seeds defaults for all three, but only the container
2206
+ // resolves there, since its slots are spread across the portal subtree with no
2207
+ // single element to own them. The trigger and each option row resolve their own
2208
+ // recipe, so a per-instance variant can override the root's default.
2209
+ const { ClassNamesProvider: TriggerClassNamesProvider, PropsProvider: TriggerPropsProvider, useProps: useTriggerProps, withContext: withTriggerContext, useClassNames: useTriggerClassNames } = createSlotRecipeContext(selectTrigger);
2210
+ const { ClassNamesProvider: ContentClassNamesProvider, withContext: withContentContext, useClassNames: useContentClassNames } = createSlotRecipeContext(select);
2211
+ const { ClassNamesProvider: ItemClassNamesProvider$1, PropsProvider: ItemPropsProvider$1, useProps: useItemProps$1, withContext: withItemContext$1, useClassNames: useItemClassNames } = createSlotRecipeContext(selectItem);
2212
+ const withStateProps$3 = createWithStateProps([
2213
+ useSelectContext
2214
+ ]);
2215
+ const withItemStateProps$1 = createWithStateProps([
2216
+ useSelectItemContext
2217
+ ]);
2218
+ // NOTE: `gutter` and `overflowPadding` are specified in Rootage (`select.yaml`,
2219
+ // both `$dimension.x2`) and generated into `@seed-design/css`, but nothing reads
2220
+ // them: floating-ui's `offset`/`shift` take numbers, not CSS custom properties,
2221
+ // so the headless layer falls back to its own `8`. That default stays
2222
+ // design-system-agnostic on purpose — this layer is the one that owns the
2223
+ // Rootage binding, so seed the spec values from here when they need to win,
2224
+ // the way `HelpBubbleRoot` passes `gutter` through `defaultProps`.
2225
+ const SelectRoot = (props)=>{
2226
+ const [{ select: contentVariantProps, selectTrigger: triggerVariantProps, selectItem: itemVariantProps }, otherProps] = splitMultipleVariantsProps(props, {
2227
+ select,
2228
+ selectTrigger,
2229
+ selectItem
2230
+ });
2231
+ const contentClassNames = select(contentVariantProps);
2232
+ return /*#__PURE__*/ jsx(ContentClassNamesProvider, {
2233
+ value: contentClassNames,
2234
+ children: /*#__PURE__*/ jsx(TriggerPropsProvider, {
2235
+ value: triggerVariantProps,
2236
+ children: /*#__PURE__*/ jsx(ItemPropsProvider$1, {
2237
+ value: itemVariantProps,
2238
+ children: /*#__PURE__*/ jsx(Select.Root, {
2239
+ ...otherProps
2240
+ })
2241
+ })
2242
+ })
2243
+ });
2244
+ };
2245
+ const SelectTrigger = /*#__PURE__*/ React.forwardRef(({ className, ...props }, ref)=>{
2246
+ const [variantProps, otherProps] = selectTrigger.splitVariantProps(props);
2247
+ const rootProps = useTriggerProps();
2248
+ const classNames = selectTrigger({
2249
+ ...rootProps,
2250
+ ...variantProps
2251
+ });
2252
+ const fieldContext = useFieldContext({
2253
+ strict: false
2254
+ });
2255
+ // Pull only the field's labelledby/describedby: `useSelect` owns the
2256
+ // combobox's aria-invalid/required and native disabled, so we don't merge
2257
+ // those from the field (avoids duplicate ownership and aria-disabled on top
2258
+ // of the native disabled button).
2259
+ const mergedProps = mergeProps$1(fieldContext ? {
2260
+ "aria-labelledby": fieldContext.inputAriaAttributes["aria-labelledby"],
2261
+ "aria-describedby": fieldContext.inputAriaAttributes["aria-describedby"]
2262
+ } : {}, otherProps);
2263
+ if (process.env.NODE_ENV !== "production" && !fieldContext && !otherProps["aria-label"] && !otherProps["aria-labelledby"]) {
2264
+ console.warn("SelectTrigger: Please provide `aria-label` or `aria-labelledby` for accessibility, or put `SelectTrigger` inside a `Field` where a `FieldLabel` is provided.");
2265
+ }
2266
+ return /*#__PURE__*/ jsx(TriggerClassNamesProvider, {
2267
+ value: classNames,
2268
+ children: /*#__PURE__*/ jsx(Select.Trigger, {
2269
+ ref: ref,
2270
+ className: clsx(classNames.root, className),
2271
+ ...mergedProps
2272
+ })
2273
+ });
2274
+ });
2275
+ SelectTrigger.displayName = "SelectTrigger";
2276
+ const SelectValue = withTriggerContext(withStateProps$3(Select.Value), "value");
2277
+ const SelectPlaceholder = withTriggerContext(withStateProps$3(Select.Placeholder), "placeholder");
2278
+ const SelectPrefixIcon = /*#__PURE__*/ React.forwardRef(({ fallback, className, ...otherProps }, ref)=>{
2279
+ const { selectedItem, stateProps } = useSelectContext();
2280
+ const classNames = useTriggerClassNames();
2281
+ const svg = selectedItem?.icon ?? fallback;
2282
+ if (!svg) return null;
2283
+ return /*#__PURE__*/ jsx(InternalIcon, {
2284
+ ref: ref,
2285
+ svg: svg,
2286
+ className: clsx(classNames.prefixIcon, className),
2287
+ ...stateProps,
2288
+ ...otherProps
2289
+ });
2290
+ });
2291
+ SelectPrefixIcon.displayName = "SelectPrefixIcon";
2292
+ const SelectSuffixIcon = withTriggerContext(withStateProps$3(InternalIcon), "suffixIcon");
2293
+ const SelectPositioner = /*#__PURE__*/ React.forwardRef(({ className, ...props }, ref)=>{
2294
+ const classNames = useContentClassNames();
2295
+ return /*#__PURE__*/ jsx(Select.Positioner, {
2296
+ ref: ref,
2297
+ className: clsx(classNames.positioner, className),
2298
+ ...props
2299
+ });
2300
+ });
2301
+ SelectPositioner.displayName = "SelectPositioner";
2302
+ const SelectContent = /*#__PURE__*/ React.forwardRef(({ className, ...otherProps }, ref)=>{
2303
+ const classNames = useContentClassNames();
2304
+ const fieldContext = useFieldContext({
2305
+ strict: false
2306
+ });
2307
+ // Label the listbox popup with the same field label (APG combobox pattern).
2308
+ const mergedProps = mergeProps$1(fieldContext ? {
2309
+ "aria-labelledby": fieldContext.inputAriaAttributes["aria-labelledby"]
2310
+ } : {}, otherProps);
2311
+ return /*#__PURE__*/ jsx(Select.Content, {
2312
+ ref: ref,
2313
+ className: clsx(classNames.content, className),
2314
+ ...mergedProps
2315
+ });
2316
+ });
2317
+ SelectContent.displayName = "SelectContent";
2318
+ const SelectScrollArea = withContentContext(Primitive.div, "scrollArea");
2319
+ withContentContext(Select.Group, "group");
2320
+ withContentContext(Select.GroupLabel, "groupLabel");
2321
+ const SelectItem = /*#__PURE__*/ React.forwardRef(({ prefixIcon, className, ...props }, ref)=>{
2322
+ const [variantProps, otherProps] = selectItem.splitVariantProps(props);
2323
+ const rootProps = useItemProps$1();
2324
+ const classNames = selectItem({
2325
+ ...rootProps,
2326
+ ...variantProps
2327
+ });
2328
+ return /*#__PURE__*/ jsx(ItemClassNamesProvider$1, {
2329
+ value: classNames,
2330
+ children: /*#__PURE__*/ jsx(Select.Item, {
2331
+ ref: ref,
2332
+ icon: prefixIcon,
2333
+ ...otherProps,
2334
+ className: clsx(classNames.root, className)
2335
+ })
2336
+ });
2337
+ });
2338
+ SelectItem.displayName = "SelectItem";
2339
+ const SelectItemPrefixIcon = /*#__PURE__*/ React.forwardRef(({ svg: svgOverride, className, ...otherProps }, ref)=>{
2340
+ const { icon, stateProps } = useSelectItemContext();
2341
+ const classNames = useItemClassNames();
2342
+ const svg = svgOverride ?? icon;
2343
+ if (!svg) return null;
2344
+ return /*#__PURE__*/ jsx(InternalIcon, {
2345
+ ref: ref,
2346
+ svg: svg,
2347
+ className: clsx(classNames.prefixIcon, className),
2348
+ ...stateProps,
2349
+ ...otherProps
2350
+ });
2351
+ });
2352
+ SelectItemPrefixIcon.displayName = "SelectItemPrefixIcon";
2353
+ const SelectItemBody = withItemContext$1(withItemStateProps$1(Primitive.div), "body");
2354
+ const SelectItemLabel = /*#__PURE__*/ React.forwardRef(({ children, className, ...otherProps }, ref)=>{
2355
+ const { label, stateProps } = useSelectItemContext();
2356
+ const classNames = useItemClassNames();
2357
+ // stateProps stays because the label's disabled style targets its own
2358
+ // `data-disabled`.
2359
+ return /*#__PURE__*/ jsx(Primitive.span, {
2360
+ ref: ref,
2361
+ className: clsx(classNames.label, className),
2362
+ ...stateProps,
2363
+ ...otherProps,
2364
+ children: children ?? label
2365
+ });
2366
+ });
2367
+ SelectItemLabel.displayName = "SelectItemLabel";
2368
+ withItemContext$1(withItemStateProps$1(Primitive.span), "description");
2369
+ const SelectItemIndicator = /*#__PURE__*/ React.forwardRef(({ selected: selectedSvg, unselected: unselectedSvg, className, ...otherProps }, ref)=>{
2370
+ const { isSelected, stateProps } = useSelectItemContext();
2371
+ const classNames = useItemClassNames();
2372
+ const svg = isSelected ? selectedSvg : unselectedSvg;
2373
+ if (!svg) return null;
2374
+ return /*#__PURE__*/ jsx(InternalIcon, {
2375
+ ref: ref,
2376
+ svg: svg,
2377
+ className: clsx(classNames.indicator, className),
2378
+ ...stateProps,
2379
+ ...otherProps
2380
+ });
2381
+ });
2382
+ SelectItemIndicator.displayName = "SelectItemIndicator";
2383
+ const SelectHiddenSelect = /*#__PURE__*/ React.forwardRef((props, ref)=>{
2384
+ const fieldContext = useFieldContext({
2385
+ strict: false
2386
+ });
2387
+ // The field label's `htmlFor` targets the field input id. Carrying that id on
2388
+ // the hidden native select — a labelable element whose label activation only
2389
+ // focuses, never opens anything — makes label clicks reach the trigger through
2390
+ // the hidden select's focus redirect. Associating the label with the trigger
2391
+ // button instead would forward label clicks as activation and open the listbox.
2392
+ return /*#__PURE__*/ jsx(Select.HiddenSelect, {
2393
+ ref: ref,
2394
+ id: fieldContext?.inputProps.id,
2395
+ ...props
2396
+ });
2397
+ });
2398
+ SelectHiddenSelect.displayName = "SelectHiddenSelect";
2399
+
2400
+ const { withContext: withContext$4, ClassNamesProvider: ClassNamesProvider$2 } = createSlotRecipeContext(switchStyle);
2401
+ const { withContext: withControlContext, PropsProvider: ControlPropsProvider, withProvider: withControlProvider } = createSlotRecipeContext(switchmark);
2402
+ const withStateProps$2 = createWithStateProps([
2403
+ useSwitchContext
2404
+ ]);
2405
+ const SwitchRoot = /*#__PURE__*/ React__default.forwardRef(({ className, ...props }, ref)=>{
2406
+ const [{ switch: switchVariantProps, switchmark: switchmarkVariantProps }, otherProps] = splitMultipleVariantsProps(props, {
2407
+ switchmark,
2408
+ switch: switchStyle
2409
+ });
2410
+ const classNames = switchStyle(switchVariantProps);
2411
+ return /*#__PURE__*/ jsx(ControlPropsProvider, {
2412
+ value: switchmarkVariantProps,
2413
+ children: /*#__PURE__*/ jsx(ClassNamesProvider$2, {
2414
+ value: classNames,
2415
+ children: /*#__PURE__*/ jsx(Switch.Root, {
2416
+ ref: ref,
2417
+ className: clsx(classNames.root, className),
2418
+ ...otherProps
2419
+ })
2420
+ })
2421
+ });
2422
+ });
2423
+ SwitchRoot.displayName = "SwitchRoot";
2424
+ const SwitchControl = withScaleFeedback(withControlProvider(Switch.Control, "root"));
2425
+ const SwitchThumb = withControlContext(Switch.Thumb, "thumb");
2426
+ withContext$4(withStateProps$2(Primitive.span), "label");
2427
+ const SwitchHiddenInput = Switch.HiddenInput;
2428
+
2429
+ // src/use-layout-effect.tsx
2430
+ var useLayoutEffect2 = globalThis?.document ? React.useLayoutEffect : ()=>{};
2431
+
2432
+ const { withProvider: withProvider$1, withContext: withContext$3, useClassNames: useClassNames$2 } = createSlotRecipeContext(textInput);
2433
+ const withFieldStateProps = createWithStateProps([
2434
+ {
2435
+ useContext: useFieldContext,
2436
+ strict: false
2437
+ }
2438
+ ]);
2439
+ const withStateProps$1 = createWithStateProps([
2440
+ useTextFieldContext,
2441
+ {
2442
+ useContext: useFieldContext,
2443
+ strict: false
2444
+ }
2445
+ ]);
2446
+ const TextFieldRoot = withProvider$1(withFieldStateProps(TextField.Root), "root");
2447
+ const TextFieldPrefixIcon = withContext$3(withStateProps$1(InternalIcon), "prefixIcon");
2448
+ withContext$3(withStateProps$1(Primitive.span), "prefixText");
2449
+ withContext$3(withStateProps$1(InternalIcon), "suffixIcon");
2450
+ withContext$3(withStateProps$1(Primitive.span), "suffixText");
2451
+ const TextFieldInput = /*#__PURE__*/ forwardRef(({ className, ...otherProps }, ref)=>{
2452
+ const classNames = useClassNames$2();
2453
+ const textFieldContext = useTextFieldContext();
2454
+ const fieldContext = useFieldContext({
2455
+ strict: false
2456
+ });
2457
+ const mergedProps = mergeProps$1(fieldContext ? fieldContext.stateProps : {}, fieldContext ? fieldContext.inputAriaAttributes : {}, textFieldContext.inputProps, fieldContext ? fieldContext.inputProps : {}, otherProps);
2458
+ if (// if not in field, then must have aria-label or aria-labelledby
2459
+ !fieldContext && !otherProps["aria-label"] && !otherProps["aria-labelledby"]) {
2460
+ console.warn("TextFieldInput: Please provide `aria-label` or `aria-labelledby` for accessibility, or put `TextFieldInput` inside a `Field` where a `FieldLabel` is provided.");
2461
+ }
2462
+ return /*#__PURE__*/ jsx(TextField.Input, {
2463
+ ref: ref,
2464
+ ...mergedProps,
2465
+ className: clsx(classNames.value, className)
2466
+ });
2467
+ });
2468
+ TextFieldInput.displayName = "TextFieldInput";
2469
+ const TextFieldTextarea = /*#__PURE__*/ forwardRef(({ className, autoresize = true, ...otherProps }, ref)=>{
2470
+ const classNames = useClassNames$2();
2471
+ const textFieldContext = useTextFieldContext();
2472
+ const fieldContext = useFieldContext({
2473
+ strict: false
2474
+ });
2475
+ const mergedProps = mergeProps$1(fieldContext ? fieldContext.stateProps : {}, fieldContext ? fieldContext.inputAriaAttributes : {}, textFieldContext.inputProps, fieldContext ? fieldContext.inputProps : {}, otherProps);
2476
+ if (// if not in field, then must have aria-label or aria-labelledby
2477
+ !fieldContext && !otherProps["aria-label"] && !otherProps["aria-labelledby"]) {
2478
+ console.warn("TextFieldTextarea: Please provide `aria-label` or `aria-labelledby` for accessibility, or put `TextFieldTextarea` inside a `Field` where a `FieldLabel` is provided.");
2479
+ }
2480
+ // referenced from React Spectrum
2481
+ const inputRef = useRef(null);
2482
+ // biome-ignore lint/correctness/useExhaustiveDependencies: intended
2483
+ const onHeightChange = useCallback(()=>{
2484
+ if (!inputRef.current) return;
2485
+ if (otherProps.style?.height) return;
2486
+ if (!autoresize) return;
2487
+ // Quiet textareas always grow based on their text content.
2488
+ // Standard textareas also grow by default, unless an explicit height is set.
2489
+ const input = inputRef.current;
2490
+ const prevAlignment = input.style.alignSelf;
2491
+ const prevOverflow = input.style.overflow;
2492
+ // Firefox scroll position is lost when overflow: 'hidden' is applied so we skip applying it.
2493
+ // The measure/applied height is also incorrect/reset if we turn on and off
2494
+ // overflow: hidden in Firefox https://bugzilla.mozilla.org/show_bug.cgi?id=1787062
2495
+ const isFirefox = "MozAppearance" in input.style;
2496
+ if (!isFirefox) {
2497
+ input.style.overflow = "hidden";
2498
+ }
2499
+ input.style.alignSelf = "start";
2500
+ input.style.height = "auto";
2501
+ // offsetHeight - clientHeight accounts for the border/padding.
2502
+ input.style.height = `${input.scrollHeight + (input.offsetHeight - input.clientHeight)}px`;
2503
+ input.style.overflow = prevOverflow;
2504
+ input.style.alignSelf = prevAlignment;
2505
+ }, [
2506
+ inputRef,
2507
+ otherProps.style?.height,
2508
+ autoresize
2509
+ ]);
2510
+ useLayoutEffect2(()=>{
2511
+ if (inputRef.current) {
2512
+ onHeightChange();
2513
+ }
2514
+ }, [
2515
+ onHeightChange,
2516
+ textFieldContext.value,
2517
+ inputRef
2518
+ ]);
2519
+ return /*#__PURE__*/ jsx(TextField.Textarea, {
2520
+ ref: composeRefs(inputRef, ref),
2521
+ ...mergedProps,
2522
+ className: clsx(classNames.value, className)
2523
+ });
2524
+ });
2525
+ TextFieldTextarea.displayName = "TextFieldTextarea";
2526
+
2527
+ /** The raw value a live editor reads, distinct from `DataTableColumn.value` (the
2528
+ * accessor sort/search/filter/export share): `editValue` returns whatever TYPE the
2529
+ * editor itself expects -- a real `boolean` for `editor: "switch"`/`type:
2530
+ * "checkbox"`, a `number` for `type: "rating"`, the option's own string for `select`/
2531
+ * `reference`/`actor` -- where `value`/`render` may format that same field for
2532
+ * display. Falls back to the table's own accessor when the column supplies none. */ function resolveEditValue(column, row, fallback) {
2533
+ return column.editValue ? column.editValue(row) : fallback;
2534
+ }
2535
+ function EditPendingIndicator() {
2536
+ return /*#__PURE__*/ jsxs("svg", {
2537
+ className: "seed-data-table-edit-pending",
2538
+ width: 14,
2539
+ height: 14,
2540
+ viewBox: "0 0 16 16",
2541
+ fill: "none",
2542
+ role: "presentation",
2543
+ "aria-hidden": "true",
2544
+ children: [
2545
+ /*#__PURE__*/ jsx("circle", {
2546
+ cx: 8,
2547
+ cy: 8,
2548
+ r: 6.5,
2549
+ stroke: "currentColor",
2550
+ strokeOpacity: 0.25,
2551
+ strokeWidth: 2
2552
+ }),
2553
+ /*#__PURE__*/ jsx("path", {
2554
+ d: "M14.5 8a6.5 6.5 0 0 0-6.5-6.5",
2555
+ stroke: "currentColor",
2556
+ strokeWidth: 2,
2557
+ strokeLinecap: "round"
2558
+ })
2559
+ ]
2560
+ });
2561
+ }
2562
+ function EditError({ message }) {
2563
+ return /*#__PURE__*/ jsx("span", {
2564
+ className: "seed-data-table-edit-error",
2565
+ children: message
2566
+ });
2567
+ }
2568
+ ////////////////////////////////////////////////////////////////////////////////////
2569
+ // -- plain editable: text/number (seed TextField) and date/datetime (a native input,
2570
+ // styled the same way, per `cellType`) -- both double-click gated, Enter/blur
2571
+ // commits, Escape cancels. -------------------------------------------------------
2572
+ function TextEditor({ column, value, pending, onCommit, onCancel }) {
2573
+ const [draft, setDraft] = React.useState(()=>value == null ? "" : String(value));
2574
+ return(// `size="medium"` (seed's smallest fixed `TextField` size, 40px min-height,
2575
+ // `text-input.ts`'s own token) -- the default `size="large"` overflowed a 36px
2576
+ // table row (same class of fix as `toolbar.tsx`'s `SearchBox`, which made the
2577
+ // same call for its own 32px-ish search box).
2578
+ /*#__PURE__*/ jsx(TextFieldRoot, {
2579
+ size: "medium",
2580
+ style: {
2581
+ width: "100%"
2582
+ },
2583
+ onClick: (event)=>event.stopPropagation(),
2584
+ onKeyDown: (event)=>event.stopPropagation(),
2585
+ children: /*#__PURE__*/ jsx(TextFieldInput, {
2586
+ autoFocus: true,
2587
+ type: column.cellType === "number" ? "number" : "text",
2588
+ "aria-label": `Edit ${column.key}`,
2589
+ value: draft,
2590
+ disabled: pending,
2591
+ onChange: (event)=>setDraft(event.target.value),
2592
+ onBlur: ()=>onCommit(draft),
2593
+ onKeyDown: (event)=>{
2594
+ if (event.key === "Enter") onCommit(draft);
2595
+ else if (event.key === "Escape") onCancel();
2596
+ }
2597
+ })
2598
+ }));
2599
+ }
2600
+ function DateEditor({ column, value, pending, onCommit, onCancel }) {
2601
+ const isDateTime = column.cellType === "datetime";
2602
+ const [draft, setDraft] = React.useState(()=>isDateTime ? toDateTimeInputValue(value) : toDateInputValue(value));
2603
+ return(// See `TextEditor`'s own comment above -- same `size="medium"` fix.
2604
+ /*#__PURE__*/ jsx(TextFieldRoot, {
2605
+ size: "medium",
2606
+ style: {
2607
+ width: "100%"
2608
+ },
2609
+ onClick: (event)=>event.stopPropagation(),
2610
+ onKeyDown: (event)=>event.stopPropagation(),
2611
+ children: /*#__PURE__*/ jsx(TextFieldInput, {
2612
+ autoFocus: true,
2613
+ type: isDateTime ? "datetime-local" : "date",
2614
+ "aria-label": `Edit ${column.key}`,
2615
+ value: draft,
2616
+ disabled: pending,
2617
+ onChange: (event)=>setDraft(event.target.value),
2618
+ onBlur: ()=>onCommit(draft),
2619
+ onKeyDown: (event)=>{
2620
+ if (event.key === "Enter") onCommit(draft);
2621
+ else if (event.key === "Escape") onCancel();
2622
+ }
2623
+ })
2624
+ }));
2625
+ }
2626
+ ////////////////////////////////////////////////////////////////////////////////////
2627
+ // -- editor: "select" -- double-click opens seed's own Select, auto-opened (seed's
2628
+ // `SelectRoot` takes `defaultOpen` directly -- unlike crisp's closed-API `Select`,
2629
+ // which needs a synthetic click on its own trigger to start open). -------------
2630
+ function SelectEditor({ column, value, pending, onCommit, onCancel }) {
2631
+ const options = column.options ?? [];
2632
+ const current = value == null ? "" : String(value);
2633
+ return /*#__PURE__*/ jsxs(SelectRoot, {
2634
+ // Controlled open, per docs/examples/react/select/controlled-open.tsx --
2635
+ // this editor is only ever mounted while the cell IS editing, so it always
2636
+ // wants to start (and stay) open; `onOpenChange(false)` is what tells us
2637
+ // the popover wants to close for ANY reason (Escape, an outside click, OR
2638
+ // the internal auto-close a single-select commit already triggers), so
2639
+ // `onCancel` alone now covers the Escape gap the old raw `document`
2640
+ // listener existed for, without a second, independent listener.
2641
+ open: true,
2642
+ onOpenChange: (next)=>{
2643
+ if (!next) onCancel();
2644
+ },
2645
+ disabled: pending,
2646
+ value: current ? [
2647
+ current
2648
+ ] : [],
2649
+ onValueChange: (next)=>onCommit(next[0] ?? ""),
2650
+ children: [
2651
+ /*#__PURE__*/ jsxs(SelectTrigger, {
2652
+ "aria-label": `Edit ${column.key}`,
2653
+ children: [
2654
+ /*#__PURE__*/ jsx(SelectValue, {}),
2655
+ /*#__PURE__*/ jsx(SelectPlaceholder, {
2656
+ children: "Select…"
2657
+ }),
2658
+ /*#__PURE__*/ jsx(SelectSuffixIcon, {
2659
+ svg: /*#__PURE__*/ jsx(IconChevronDownSmallLine_default, {})
2660
+ })
2661
+ ]
2662
+ }),
2663
+ /*#__PURE__*/ jsx(SelectPositioner, {
2664
+ children: /*#__PURE__*/ jsx(SelectContent, {
2665
+ children: /*#__PURE__*/ jsx(SelectScrollArea, {
2666
+ children: options.map((option)=>/*#__PURE__*/ jsxs(SelectItem, {
2667
+ value: option.value,
2668
+ label: option.label,
2669
+ children: [
2670
+ /*#__PURE__*/ jsx(SelectItemBody, {
2671
+ children: /*#__PURE__*/ jsx(SelectItemLabel, {})
2672
+ }),
2673
+ /*#__PURE__*/ jsx(SelectItemIndicator, {
2674
+ selected: /*#__PURE__*/ jsx(IconCheckmarkFatFill_default, {})
2675
+ })
2676
+ ]
2677
+ }, option.value))
2678
+ })
2679
+ })
2680
+ })
2681
+ ]
2682
+ });
2683
+ }
2684
+ ////////////////////////////////////////////////////////////////////////////////////
2685
+ // -- editor: "reference" / "actor" -- same picker, search-enabled, each option
2686
+ // carrying a small avatar (a photo for a real one, a generic person glyph for
2687
+ // none) -- `actor` is `reference` over people specifically (crisp's own #449/
2688
+ // #432 `ReferenceCell` shape), so the two share one component; only the search
2689
+ // placeholder differs. Seed's `Select` has no built-in search combobox (unlike
2690
+ // crisp's own `Select`), so this filters `refOptions` itself against a small
2691
+ // `TextField` rendered as the content's own first row. --------------------------
2692
+ function ReferenceEditor({ column, value, pending, onCommit, onCancel }) {
2693
+ const [query, setQuery] = React.useState("");
2694
+ const options = column.refOptions ?? [];
2695
+ const filtered = query.trim() ? options.filter((option)=>option.label.toLowerCase().includes(query.trim().toLowerCase())) : options;
2696
+ const current = value == null ? "" : String(value);
2697
+ const isActor = column.editor === "actor";
2698
+ return /*#__PURE__*/ jsxs(SelectRoot, {
2699
+ // See `SelectEditor`'s own comment above -- same controlled-open wiring.
2700
+ open: true,
2701
+ onOpenChange: (next)=>{
2702
+ if (!next) onCancel();
2703
+ },
2704
+ disabled: pending,
2705
+ value: current ? [
2706
+ current
2707
+ ] : [],
2708
+ onValueChange: (next)=>onCommit(next[0] ?? ""),
2709
+ children: [
2710
+ /*#__PURE__*/ jsxs(SelectTrigger, {
2711
+ "aria-label": `Edit ${column.key}`,
2712
+ children: [
2713
+ /*#__PURE__*/ jsx(SelectValue, {}),
2714
+ /*#__PURE__*/ jsx(SelectPlaceholder, {
2715
+ children: isActor ? "Assign…" : "Select…"
2716
+ }),
2717
+ /*#__PURE__*/ jsx(SelectSuffixIcon, {
2718
+ svg: /*#__PURE__*/ jsx(IconChevronDownSmallLine_default, {})
2719
+ })
2720
+ ]
2721
+ }),
2722
+ /*#__PURE__*/ jsx(SelectPositioner, {
2723
+ children: /*#__PURE__*/ jsxs(SelectContent, {
2724
+ children: [
2725
+ /*#__PURE__*/ jsx(TextFieldRoot, {
2726
+ size: "medium",
2727
+ style: {
2728
+ margin: 4
2729
+ },
2730
+ onKeyDown: (event)=>{
2731
+ // The listbox's own typeahead/arrow handling lives on THIS field's
2732
+ // sibling items, not on the field itself -- stop everything except
2733
+ // Escape from also driving the listbox's own keyboard nav. Escape
2734
+ // itself needs no special-casing to "reach" anything any more: it's
2735
+ // SelectContent's own `DismissibleLayer` (Select.tsx) that closes the
2736
+ // listbox, and that dismissal is a document-level listener outside
2737
+ // this React subtree entirely -- a synthetic `stopPropagation` here
2738
+ // was never able to block it either way, so the controlled `open`
2739
+ // above (via `onOpenChange`) is what actually ends this cell's edit.
2740
+ if (event.key !== "Escape") event.stopPropagation();
2741
+ },
2742
+ children: /*#__PURE__*/ jsx(TextFieldInput, {
2743
+ autoFocus: true,
2744
+ "aria-label": isActor ? "Search people" : `Search ${column.key}`,
2745
+ placeholder: isActor ? "Search people…" : "Search…",
2746
+ value: query,
2747
+ onChange: (event)=>setQuery(event.target.value)
2748
+ })
2749
+ }),
2750
+ /*#__PURE__*/ jsxs(SelectScrollArea, {
2751
+ children: [
2752
+ filtered.length === 0 && /*#__PURE__*/ jsx("div", {
2753
+ style: {
2754
+ padding: "6px 12px",
2755
+ opacity: 0.63,
2756
+ fontSize: 13
2757
+ },
2758
+ children: isActor ? "No users" : "No results"
2759
+ }),
2760
+ filtered.map((option)=>/*#__PURE__*/ jsxs(SelectItem, {
2761
+ value: option.value,
2762
+ label: option.label,
2763
+ children: [
2764
+ /*#__PURE__*/ jsxs(SelectItemBody, {
2765
+ children: [
2766
+ /*#__PURE__*/ jsx(ReferenceAvatar, {
2767
+ avatar: option.avatar
2768
+ }),
2769
+ /*#__PURE__*/ jsx(SelectItemLabel, {})
2770
+ ]
2771
+ }),
2772
+ /*#__PURE__*/ jsx(SelectItemIndicator, {
2773
+ selected: /*#__PURE__*/ jsx(IconCheckmarkFatFill_default, {})
2774
+ })
2775
+ ]
2776
+ }, option.value))
2777
+ ]
2778
+ })
2779
+ ]
2780
+ })
2781
+ })
2782
+ ]
2783
+ });
2784
+ }
2785
+ function ReferenceAvatar({ avatar }) {
2786
+ return /*#__PURE__*/ jsxs(AvatarRoot, {
2787
+ size: "20",
2788
+ children: [
2789
+ avatar && /*#__PURE__*/ jsx(AvatarImage, {
2790
+ src: avatar,
2791
+ alt: ""
2792
+ }),
2793
+ /*#__PURE__*/ jsx(AvatarFallback, {
2794
+ children: /*#__PURE__*/ jsx(IconPersonCircleLine_default, {})
2795
+ })
2796
+ ]
2797
+ });
2798
+ }
2799
+ ////////////////////////////////////////////////////////////////////////////////////
2800
+ // -- always-live editors: no double-click gate, no separate rest/edit state --
2801
+ // interacting with the widget IS the commit gesture (crisp's own #453/#760/#761).
2802
+ function SwitchEditor({ column, value, pending, onCommit }) {
2803
+ const checked = value === true || value === "true";
2804
+ return /*#__PURE__*/ jsxs("span", {
2805
+ className: "seed-data-table-edit-picker",
2806
+ children: [
2807
+ /*#__PURE__*/ jsxs(SwitchRoot, {
2808
+ size: "16",
2809
+ checked: checked,
2810
+ disabled: pending,
2811
+ onCheckedChange: (next)=>onCommit(String(next)),
2812
+ children: [
2813
+ /*#__PURE__*/ jsx(SwitchControl, {
2814
+ children: /*#__PURE__*/ jsx(SwitchThumb, {})
2815
+ }),
2816
+ /*#__PURE__*/ jsx(SwitchHiddenInput, {
2817
+ "aria-label": `Edit ${column.key}`
2818
+ })
2819
+ ]
2820
+ }),
2821
+ pending && /*#__PURE__*/ jsx(EditPendingIndicator, {})
2822
+ ]
2823
+ });
2824
+ }
2825
+ function CheckboxEditor({ column, value, pending, onCommit }) {
2826
+ const checked = value === true || value === "true";
2827
+ return /*#__PURE__*/ jsxs("span", {
2828
+ className: "seed-data-table-edit-picker",
2829
+ children: [
2830
+ /*#__PURE__*/ jsxs(CheckboxRoot, {
2831
+ checked: checked,
2832
+ disabled: pending,
2833
+ onCheckedChange: (next)=>onCommit(String(next)),
2834
+ children: [
2835
+ /*#__PURE__*/ jsx(CheckboxControl, {
2836
+ children: /*#__PURE__*/ jsx(CheckboxIndicator, {
2837
+ checked: CHECKBOX_CELL_CHECKED_ICON
2838
+ })
2839
+ }),
2840
+ /*#__PURE__*/ jsx(CheckboxHiddenInput, {
2841
+ "aria-label": `Edit ${column.key}`
2842
+ })
2843
+ ]
2844
+ }),
2845
+ pending && /*#__PURE__*/ jsx(EditPendingIndicator, {})
2846
+ ]
2847
+ });
2848
+ }
2849
+ function RatingEditor({ column, value, pending, onCommit }) {
2850
+ const maxRating = column.maxRating ?? 5;
2851
+ const rating = typeof value === "number" ? value : Number(value) || 0;
2852
+ return /*#__PURE__*/ jsxs("span", {
2853
+ className: "seed-data-table-edit-picker",
2854
+ children: [
2855
+ /*#__PURE__*/ jsx("span", {
2856
+ role: "group",
2857
+ "aria-label": `Edit ${column.key}`,
2858
+ style: {
2859
+ display: "inline-flex",
2860
+ alignItems: "center",
2861
+ gap: 2
2862
+ },
2863
+ children: Array.from({
2864
+ length: maxRating
2865
+ }, (_, index)=>{
2866
+ const starValue = index + 1;
2867
+ return /*#__PURE__*/ jsx("button", {
2868
+ type: "button",
2869
+ disabled: pending,
2870
+ "aria-label": `Set ${starValue} out of ${maxRating}`,
2871
+ "aria-pressed": starValue <= rating,
2872
+ onClick: ()=>onCommit(String(starValue)),
2873
+ style: {
2874
+ border: "none",
2875
+ background: "none",
2876
+ padding: 0,
2877
+ cursor: "pointer",
2878
+ lineHeight: 0
2879
+ },
2880
+ children: /*#__PURE__*/ jsx("svg", {
2881
+ width: 16,
2882
+ height: 16,
2883
+ viewBox: "0 0 16 16",
2884
+ fill: "none",
2885
+ "aria-hidden": "true",
2886
+ children: /*#__PURE__*/ jsx("path", {
2887
+ d: RATING_STAR_D,
2888
+ fill: starValue <= rating ? RATING_FILLED : RATING_EMPTY
2889
+ })
2890
+ })
2891
+ }, index);
2892
+ })
2893
+ }),
2894
+ pending && /*#__PURE__*/ jsx(EditPendingIndicator, {})
2895
+ ]
2896
+ });
2897
+ }
2898
+
2899
+ // Row-narrowing for the toolbar's search box and each column's filter picker -- split
2900
+ // out of DataTable.tsx the way cells.tsx splits out cell-type rendering: both are a
2901
+ // self-contained, independently testable concern DataTable.tsx just calls, rather than
2902
+ // inline `.filter()`/`.sort()` calls piling up in the same component that also owns
2903
+ // markup.
2904
+ /**
2905
+ * A column's `value(row)` if it supplies one, else the raw field at `column.key` --
2906
+ * the single accessor sort/search/filter/export all read through
2907
+ * (DATATABLE-ATTIO-SPEC.md's Column API), so a consumer only teaches the table how to
2908
+ * read a value ONCE rather than once per feature.
2909
+ */ function getColumnValue(column, row) {
2910
+ return column.value ? column.value(row) : row[column.key];
2911
+ }
2912
+ /**
2913
+ * Case-insensitive substring match against a row's stringified value -- crisp's own
2914
+ * `applyFilters` "contains" semantics (`tabletoolbar.tsx`), reused here for the
2915
+ * toolbar's single free-text query rather than a per-column condition. `null`/
2916
+ * `undefined` never match a non-empty query -- there is no text to search.
2917
+ */ function matchesSearch(value, query) {
2918
+ if (!query) return true;
2919
+ if (value == null) return false;
2920
+ return String(value).toLowerCase().includes(query.toLowerCase());
2921
+ }
2922
+ /**
2923
+ * A column's own filter predicate -- "is"/"is not" against a SET of checked values.
2924
+ * One checked value reads as "is X", several as crisp's "is any of X, Y": the exact
2925
+ * same predicate, just more values in the set (crisp's own `applyFilters`,
2926
+ * `tabletoolbar.tsx`: `isanyof` is membership-in-`values`, `is`/`isnot` is equality --
2927
+ * both compare the stringified value, case-insensitively, never a substring). An empty
2928
+ * `values` (the picker is open, nothing checked yet) matches every row -- crisp's own
2929
+ * `applyFilters` drops an operator with nothing to compare against from its "active"
2930
+ * set the same way, rather than treating "nothing checked" as "match nothing".
2931
+ */ function matchesFilter(value, filter) {
2932
+ if (filter.values.length === 0) return true;
2933
+ const text = value == null ? "" : String(value).toLowerCase();
2934
+ const isIncluded = filter.values.some((candidate)=>candidate.toLowerCase() === text);
2935
+ return filter.mode === "isnot" ? !isIncluded : isIncluded;
2936
+ }
2937
+ /**
2938
+ * The toolbar's search query (an OR across `searchable` columns, matching GRC's own
2939
+ * `useFilteredRows`) THEN every column's own filter (an AND across active filters,
2940
+ * matching crisp's `applyFilters`) -- in that order, since a filter chip narrows what
2941
+ * the search box already found, not the other way round. A column with no active
2942
+ * filter (absent from `filters`, or present with an empty `values`) is skipped
2943
+ * entirely rather than treated as "filter out everything".
2944
+ */ function getVisibleRows(data, columns, query, filters) {
2945
+ const searchableColumns = columns.filter((column)=>column.searchable);
2946
+ const activeFilters = Object.entries(filters).filter(([, filter])=>filter.values.length > 0);
2947
+ if (!query && activeFilters.length === 0) return data;
2948
+ return data.filter((row)=>{
2949
+ if (query) {
2950
+ const matchesQuery = searchableColumns.some((column)=>matchesSearch(getColumnValue(column, row), query));
2951
+ if (!matchesQuery) return false;
2952
+ }
2953
+ return activeFilters.every(([columnKey, filter])=>{
2954
+ const column = columns.find((candidate)=>candidate.key === columnKey);
2955
+ if (!column) return true;
2956
+ return matchesFilter(getColumnValue(column, row), filter);
2957
+ });
2958
+ });
2959
+ }
2960
+
2961
+ /** `editable` + one of these shapes needs no double-click gate and no separate rest
2962
+ * display -- interacting with the widget itself IS the commit gesture (crisp's own
2963
+ * #453/#760/#761: `switch`/`rating`/`checkbox` never had a meaningfully different
2964
+ * "rest" state to show). Every other `editable` column opens on double-click. */ function isAlwaysLiveEditable(column) {
2965
+ if (!column.editable) return false;
2966
+ return column.editor === "switch" || column.type === "rating" || column.type === "checkbox";
2967
+ }
2968
+ function ColumnBodyCells({ columns, row, classNames, locale, selectable, stickyFirst, stickyLast }) {
2969
+ const effective = useEffectiveColumns(columns);
2970
+ const { startEdit } = useDataTableContext();
2971
+ return /*#__PURE__*/ jsx(Fragment, {
2972
+ children: effective.map(({ column }, index)=>{
2973
+ const isFirst = index === 0;
2974
+ const isLast = index === effective.length - 1;
2975
+ const sticky = stickyProps({
2976
+ selectable,
2977
+ stickyFirst,
2978
+ stickyLast,
2979
+ isSelectionCell: false,
2980
+ isFirst,
2981
+ isLast
2982
+ });
2983
+ // A gated `editable` column (everything except the always-live widgets)
2984
+ // opens on double-click -- Attio's own measured gesture (crisp's #411),
2985
+ // matched here rather than a single click, which stays "select this cell"
2986
+ // in crisp's own spreadsheet model and would otherwise be indistinguishable
2987
+ // from this table's plain row click.
2988
+ const gated = column.editable && !isAlwaysLiveEditable(column);
2989
+ return /*#__PURE__*/ jsx(DataTableCell, {
2990
+ "data-numeric": dataAttr(column.type === "number" || column.type === "currency"),
2991
+ "data-editable": dataAttr(!!column.editable),
2992
+ "data-sticky": sticky["data-sticky"],
2993
+ "data-sticky-edge": sticky["data-sticky-edge"],
2994
+ "data-sticky-edge-end": sticky["data-sticky-edge-end"],
2995
+ style: sticky.style,
2996
+ onDoubleClick: gated ? ()=>startEdit(row.id, column.key) : undefined,
2997
+ children: /*#__PURE__*/ jsx(CellContent, {
2998
+ column: column,
2999
+ row: row,
3000
+ rowId: row.id,
3001
+ classNames: classNames,
3002
+ locale: locale
3003
+ })
3004
+ }, column.key);
3005
+ })
3006
+ });
3007
+ }
3008
+ /**
3009
+ * Dispatches to whichever of cells.tsx's static renderers or editors.tsx's live
3010
+ * editors applies to THIS column/row right now -- always-live first (no rest state to
3011
+ * fall back to), then a gated editor while the cell is mid-edit, else the plain
3012
+ * display (a custom `render` wins there, same as before `editable` existed; it never
3013
+ * overrides an actual editing/always-live state, matching crisp's own split between
3014
+ * `renderTyped`'s rest-state branch and the editing branches above it).
3015
+ */ function CellContent({ column, row, rowId, classNames, locale }) {
3016
+ const { isEditing, isPending, getEditError, commitEdit, cancelEdit } = useDataTableContext();
3017
+ const rawValue = getColumnValue(column, row);
3018
+ const pending = isPending(rowId, column.key);
3019
+ const error = column.editable ? getEditError(rowId, column.key) : null;
3020
+ const commit = (value)=>commitEdit(rowId, column.key, value);
3021
+ const cancel = ()=>cancelEdit();
3022
+ let node;
3023
+ if (column.editable && column.editor === "switch") {
3024
+ node = /*#__PURE__*/ jsx(SwitchEditor, {
3025
+ column: column,
3026
+ value: resolveEditValue(column, row, rawValue),
3027
+ pending: pending,
3028
+ onCommit: commit
3029
+ });
3030
+ } else if (column.editable && column.type === "rating") {
3031
+ node = /*#__PURE__*/ jsx(RatingEditor, {
3032
+ column: column,
3033
+ value: resolveEditValue(column, row, rawValue),
3034
+ pending: pending,
3035
+ onCommit: commit
3036
+ });
3037
+ } else if (column.editable && column.type === "checkbox") {
3038
+ node = /*#__PURE__*/ jsx(CheckboxEditor, {
3039
+ column: column,
3040
+ value: resolveEditValue(column, row, rawValue),
3041
+ pending: pending,
3042
+ onCommit: commit
3043
+ });
3044
+ } else if (column.editable && isEditing(rowId, column.key)) {
3045
+ if (column.editor === "select") {
3046
+ node = /*#__PURE__*/ jsx(SelectEditor, {
3047
+ column: column,
3048
+ value: resolveEditValue(column, row, rawValue),
3049
+ pending: pending,
3050
+ onCommit: commit,
3051
+ onCancel: cancel
3052
+ });
3053
+ } else if (column.editor === "reference" || column.editor === "actor") {
3054
+ node = /*#__PURE__*/ jsx(ReferenceEditor, {
3055
+ column: column,
3056
+ value: resolveEditValue(column, row, rawValue),
3057
+ pending: pending,
3058
+ onCommit: commit,
3059
+ onCancel: cancel
3060
+ });
3061
+ } else if (column.cellType === "date" || column.cellType === "datetime") {
3062
+ node = /*#__PURE__*/ jsx(DateEditor, {
3063
+ column: column,
3064
+ value: rawValue,
3065
+ pending: pending,
3066
+ onCommit: commit,
3067
+ onCancel: cancel
3068
+ });
3069
+ } else {
3070
+ node = /*#__PURE__*/ jsx(TextEditor, {
3071
+ column: column,
3072
+ value: rawValue,
3073
+ pending: pending,
3074
+ onCommit: commit,
3075
+ onCancel: cancel
3076
+ });
3077
+ }
3078
+ } else {
3079
+ node = /*#__PURE__*/ jsx(DisplayCellContent, {
3080
+ column: column,
3081
+ row: row,
3082
+ value: rawValue,
3083
+ classNames: classNames,
3084
+ locale: locale
3085
+ });
3086
+ }
3087
+ return /*#__PURE__*/ jsxs(Fragment, {
3088
+ children: [
3089
+ node,
3090
+ error && /*#__PURE__*/ jsx(EditError, {
3091
+ message: error
3092
+ })
3093
+ ]
3094
+ });
3095
+ }
3096
+ // The plain (non-editing) Attio cell-type renderer -- unchanged from before this
3097
+ // table grew inline editing, just renamed (it used to be the whole of `CellContent`)
3098
+ // since that name now belongs to the dispatcher above.
3099
+ function DisplayCellContent({ column, row, value, classNames, locale }) {
3100
+ if (column.render) return /*#__PURE__*/ jsx(Fragment, {
3101
+ children: column.render(row)
3102
+ });
3103
+ switch(column.type){
3104
+ case "entity":
3105
+ return /*#__PURE__*/ jsx(EntityCell, {
3106
+ value: value,
3107
+ className: classNames.entity,
3108
+ nameClassName: classNames.entityName
3109
+ });
3110
+ case "url":
3111
+ case "email":
3112
+ return /*#__PURE__*/ jsx(LinkCell, {
3113
+ column: column,
3114
+ value: value,
3115
+ className: classNames.link
3116
+ });
3117
+ case "tags":
3118
+ return /*#__PURE__*/ jsx(TagsCell, {
3119
+ value: value,
3120
+ className: classNames.tagList
3121
+ });
3122
+ case "status":
3123
+ return /*#__PURE__*/ jsx(StatusCell, {
3124
+ value: value
3125
+ });
3126
+ case "number":
3127
+ return /*#__PURE__*/ jsx(Fragment, {
3128
+ children: formatNumberCell(value, locale)
3129
+ });
3130
+ case "currency":
3131
+ return /*#__PURE__*/ jsx(Fragment, {
3132
+ children: formatCurrencyCell(value, column.symbol ?? "$", locale)
3133
+ });
3134
+ case "date":
3135
+ return /*#__PURE__*/ jsx(Fragment, {
3136
+ children: formatDateCell(value, locale)
3137
+ });
3138
+ case "datetime":
3139
+ return /*#__PURE__*/ jsx(Fragment, {
3140
+ children: formatDateTimeCell(value, locale)
3141
+ });
3142
+ case "rating":
3143
+ return /*#__PURE__*/ jsx(RatingCell, {
3144
+ value: value,
3145
+ maxRating: column.maxRating
3146
+ });
3147
+ case "checkbox":
3148
+ return /*#__PURE__*/ jsx(CheckboxCell, {
3149
+ value: value
3150
+ });
3151
+ default:
3152
+ return /*#__PURE__*/ jsx(Fragment, {
3153
+ children: value == null ? null : String(value)
3154
+ });
3155
+ }
3156
+ }
3157
+
3158
+ const { ClassNamesProvider: ClassNamesProvider$1, withContext: withContext$2, useClassNames: useClassNames$1 } = createSlotRecipeContext(menu);
3159
+ const { PropsProvider: ItemPropsProvider, useProps: useItemProps, withContext: withItemContext, ClassNamesProvider: ItemClassNamesProvider } = createSlotRecipeContext(menuItem);
3160
+ const withStateProps = createWithStateProps([
3161
+ useMenuContext
3162
+ ]);
3163
+ const withItemStateProps = createWithStateProps([
3164
+ useMenuItemContext
3165
+ ]);
3166
+ const MenuRoot = (props)=>{
3167
+ const [variantProps, otherProps] = menu.splitVariantProps(props);
3168
+ const classNames = menu(variantProps);
3169
+ return /*#__PURE__*/ jsx(ClassNamesProvider$1, {
3170
+ value: classNames,
3171
+ children: /*#__PURE__*/ jsx(ItemPropsProvider, {
3172
+ value: {
3173
+ size: variantProps.size
3174
+ },
3175
+ children: /*#__PURE__*/ jsx(Menu.Root, {
3176
+ ...otherProps
3177
+ })
3178
+ })
3179
+ });
3180
+ };
3181
+ Menu.Anchor;
3182
+ const MenuTrigger = Menu.Trigger;
3183
+ const MenuPositioner = /*#__PURE__*/ React.forwardRef(({ className, ...props }, ref)=>{
3184
+ const classNames = useClassNames$1();
3185
+ return /*#__PURE__*/ jsx(Menu.Positioner, {
3186
+ ref: ref,
3187
+ className: clsx(classNames.positioner, className),
3188
+ ...props
3189
+ });
3190
+ });
3191
+ MenuPositioner.displayName = "MenuPositioner";
3192
+ const MenuContent = withContext$2(Menu.Content, "content");
3193
+ const MenuScrollArea = withContext$2(withStateProps(Primitive.div), "scrollArea");
3194
+ withContext$2(Menu.Group, "group");
3195
+ withContext$2(Menu.GroupLabel, "groupLabel");
3196
+ const MenuItem = /*#__PURE__*/ React.forwardRef(({ className: propClassName, ...props }, ref)=>{
3197
+ const [variantProps, otherProps] = menuItem.splitVariantProps(props);
3198
+ const parentProps = useItemProps();
3199
+ const classNames = menuItem({
3200
+ ...parentProps,
3201
+ ...variantProps
3202
+ });
3203
+ return /*#__PURE__*/ jsx(ItemClassNamesProvider, {
3204
+ value: classNames,
3205
+ children: /*#__PURE__*/ jsx(Menu.Item, {
3206
+ ref: ref,
3207
+ className: clsx(classNames.root, propClassName),
3208
+ ...otherProps
3209
+ })
3210
+ });
3211
+ });
3212
+ MenuItem.displayName = "MenuItem";
3213
+ withItemContext(withItemStateProps(Primitive.div), "body");
3214
+ const MenuItemLabel = withItemContext(withItemStateProps(Primitive.span), "label");
3215
+ withItemContext(withItemStateProps(Primitive.span), "description");
3216
+
3217
+ function ColumnCalcCells({ columns, rows, selectable, stickyFirst, stickyLast }) {
3218
+ const effective = useEffectiveColumns(columns);
3219
+ return /*#__PURE__*/ jsx(Fragment, {
3220
+ children: effective.map(({ column }, index)=>{
3221
+ const isFirst = index === 0;
3222
+ const isLast = index === effective.length - 1;
3223
+ const sticky = stickyProps({
3224
+ selectable,
3225
+ stickyFirst,
3226
+ stickyLast,
3227
+ isSelectionCell: false,
3228
+ isFirst,
3229
+ isLast
3230
+ });
3231
+ return /*#__PURE__*/ jsx(DataTableFooterCell, {
3232
+ "data-sticky": sticky["data-sticky"],
3233
+ "data-sticky-edge": sticky["data-sticky-edge"],
3234
+ "data-sticky-edge-end": sticky["data-sticky-edge-end"],
3235
+ style: sticky.style,
3236
+ children: column.summary && /*#__PURE__*/ jsx(ColumnCalcCell, {
3237
+ column: column,
3238
+ rows: rows,
3239
+ numeric: column.type === "number" || column.type === "currency",
3240
+ align: column.type === "number" || column.type === "currency" ? "right" : "left"
3241
+ })
3242
+ }, column.key);
3243
+ })
3244
+ });
3245
+ }
3246
+ function formatCalcValue(value) {
3247
+ return value.toLocaleString(undefined, {
3248
+ maximumFractionDigits: 2
3249
+ });
3250
+ }
3251
+ /**
3252
+ * One ColumnCalc footer cell -- a click-anywhere-in-the-cell trigger (crisp's own
3253
+ * measured 246x36 hit target, `columncalc.recipe.ts`) that reads "Add calculation"
3254
+ * until an op is picked, then shows the computed value with its operation name beside
3255
+ * it. The math itself (`computeColumnCalc`) is headless and independently tested;
3256
+ * this component only resolves this column's own values and renders.
3257
+ */ function ColumnCalcCell({ column, rows, numeric, align }) {
3258
+ const { getColumnCalcOp, setColumnCalcOp } = useDataTableContext();
3259
+ const op = getColumnCalcOp(column.key) ?? column.summaryDefaultOp;
3260
+ const values = React.useMemo(()=>rows.map((row)=>getColumnValue(column, row)), [
3261
+ rows,
3262
+ column
3263
+ ]);
3264
+ const result = React.useMemo(()=>op ? computeColumnCalc(values, op) : null, [
3265
+ values,
3266
+ op
3267
+ ]);
3268
+ const ops = [
3269
+ ...DATA_TABLE_UNIVERSAL_CALC_OPS,
3270
+ ...numeric ? DATA_TABLE_NUMERIC_CALC_OPS : []
3271
+ ];
3272
+ return /*#__PURE__*/ jsxs(MenuRoot, {
3273
+ children: [
3274
+ /*#__PURE__*/ jsx(MenuTrigger, {
3275
+ asChild: true,
3276
+ children: /*#__PURE__*/ jsx("button", {
3277
+ type: "button",
3278
+ className: "seed-data-table-column-calc-trigger",
3279
+ "data-align": align === "right" ? "right" : undefined,
3280
+ children: op && result !== null ? /*#__PURE__*/ jsxs(Fragment, {
3281
+ children: [
3282
+ /*#__PURE__*/ jsx("span", {
3283
+ className: "seed-data-table-column-calc-value",
3284
+ children: formatCalcValue(result)
3285
+ }),
3286
+ /*#__PURE__*/ jsx("span", {
3287
+ className: "seed-data-table-column-calc-op",
3288
+ children: DATA_TABLE_CALC_LABEL[op]
3289
+ }),
3290
+ /*#__PURE__*/ jsx(IconChevronDownLine_default, {
3291
+ size: 14
3292
+ })
3293
+ ]
3294
+ }) : /*#__PURE__*/ jsx("span", {
3295
+ className: "seed-data-table-column-calc-placeholder",
3296
+ children: "Add calculation"
3297
+ })
3298
+ })
3299
+ }),
3300
+ /*#__PURE__*/ jsx(MenuPositioner, {
3301
+ children: /*#__PURE__*/ jsx(MenuContent, {
3302
+ style: {
3303
+ minWidth: 160
3304
+ },
3305
+ children: /*#__PURE__*/ jsxs(MenuScrollArea, {
3306
+ children: [
3307
+ ops.map((candidate)=>/*#__PURE__*/ jsx(MenuItem, {
3308
+ onClick: ()=>setColumnCalcOp(column.key, candidate),
3309
+ children: /*#__PURE__*/ jsx(MenuItemLabel, {
3310
+ children: DATA_TABLE_CALC_LABEL[candidate]
3311
+ })
3312
+ }, candidate)),
3313
+ op && /*#__PURE__*/ jsx(MenuItem, {
3314
+ onClick: ()=>setColumnCalcOp(column.key, undefined),
3315
+ children: /*#__PURE__*/ jsx(MenuItemLabel, {
3316
+ children: "Clear"
3317
+ })
3318
+ })
3319
+ ]
3320
+ })
3321
+ })
3322
+ })
3323
+ ]
3324
+ });
3325
+ }
3326
+
3327
+ const { withProvider, withContext: withContext$1 } = createSlotRecipeContext(segmentedControl);
3328
+ const SegmentedControlRoot = withProvider(SegmentedControl.Root, "root");
3329
+ const SegmentedControlIndicator = withContext$1(Primitive.div, "indicator");
3330
+ const SegmentedControlItem = withContext$1(SegmentedControl.Item, "item");
3331
+ const SegmentedControlItemHiddenInput = SegmentedControl.ItemHiddenInput;
3332
+
3333
+ /**
3334
+ * The column-edge resize grip -- geometry ported from crisp's `resizablepanel`
3335
+ * recipe's "column" variant (`resizablepanel.recipe.ts`): a 7px hit target straddling
3336
+ * the 1px grid line (not a 1px target of its own -- far too thin to reliably grab),
3337
+ * its 3px inner bar invisible at rest and brand-solid on hover/drag. Uses native
3338
+ * Pointer Capture (`setPointerCapture`) rather than a `window`-level listener pair:
3339
+ * once captured, this SAME element keeps receiving `pointermove`/`pointerup` even
3340
+ * after the cursor leaves it, so the drag math can live entirely in this one
3341
+ * handler set with no effect/cleanup to get wrong.
3342
+ */ function ColumnResizeHandle({ columnKey, width }) {
3343
+ const { beginColumnResize, updateColumnResize, endColumnResize, isColumnResizing } = useDataTableContext();
3344
+ return /*#__PURE__*/ jsx("span", {
3345
+ // Mouse/pointer-drag only for now (no keyboard equivalent yet) -- `aria-hidden`
3346
+ // rather than `role="separator"` + `aria-label`, which would otherwise fold
3347
+ // "Resize column" into the OWNING header cell's own accessible name (a
3348
+ // columnheader's name is the concatenation of its own text plus every
3349
+ // descendant's), turning "Name" into "Name Resize column" for anyone using an
3350
+ // accessible-name query -- including this file's own tests.
3351
+ "aria-hidden": "true",
3352
+ className: "seed-data-table-resize-handle",
3353
+ "data-resizing": isColumnResizing(columnKey),
3354
+ draggable: false,
3355
+ onPointerDown: (event)=>{
3356
+ event.stopPropagation();
3357
+ event.preventDefault();
3358
+ event.currentTarget.setPointerCapture(event.pointerId);
3359
+ const startWidth = width ?? event.currentTarget.closest('[role="columnheader"]')?.getBoundingClientRect().width ?? 150;
3360
+ beginColumnResize(columnKey, event.clientX, startWidth);
3361
+ },
3362
+ onPointerMove: (event)=>{
3363
+ if (event.currentTarget.hasPointerCapture(event.pointerId)) {
3364
+ updateColumnResize(event.clientX);
3365
+ }
3366
+ },
3367
+ onPointerUp: (event)=>{
3368
+ event.currentTarget.releasePointerCapture(event.pointerId);
3369
+ endColumnResize();
3370
+ },
3371
+ children: /*#__PURE__*/ jsx("span", {
3372
+ className: "seed-data-table-resize-handle-inner"
3373
+ })
3374
+ });
3375
+ }
3376
+
3377
+ // Same fix as cells.tsx's `CheckboxCell`/editors.tsx's `CheckboxEditor` -- seed's
3378
+ // `CheckboxIndicator` needs a real icon for its `checked` prop (it renders exactly
3379
+ // what's passed there, nothing automatically); `checked={null}` here used to render an
3380
+ // empty box even once a value was actually checked. 12px, matching the row-selection
3381
+ // checkbox's own already-correct icon size (`DataTable.tsx`'s `SELECTION_ICON_SIZE`).
3382
+ const FILTER_CHECKED_ICON = /*#__PURE__*/ jsx(IconCheckmarkLine_default, {
3383
+ size: 12
3384
+ });
3385
+ // Fix (bug report): karrot icons default to a 24px intrinsic `width`/`height`
3386
+ // SVG ATTRIBUTE (not CSS) -- inside a fixed 16px `headerCellIcon` box (a flex
3387
+ // item), flexbox only auto-shrinks the MAIN axis (width) to fit, leaving the
3388
+ // CROSS axis (height) at its full 24px intrinsic size, so the glyph rendered
3389
+ // visibly oversized/non-square and spilled out of its slot (measured live:
3390
+ // 16×24, not 16×16). Passing `size` sets both attributes directly, sidestepping
3391
+ // the mismatch entirely rather than fighting it with more CSS. Matches crisp's
3392
+ // own measured `.crisp-datatable-chevron` size (14px).
3393
+ const SORT_ICON_SIZE = 14;
3394
+ const DEFAULT_SORT_ICON = {
3395
+ idle: /*#__PURE__*/ jsx(IconChevronDownLine_default, {
3396
+ size: SORT_ICON_SIZE
3397
+ }),
3398
+ asc: /*#__PURE__*/ jsx(IconChevronUpLine_default, {
3399
+ size: SORT_ICON_SIZE
3400
+ }),
3401
+ desc: /*#__PURE__*/ jsx(IconChevronDownLine_default, {
3402
+ size: SORT_ICON_SIZE
3403
+ })
3404
+ };
3405
+ /**
3406
+ * Rendered for EVERY sortable column, sorted or not -- unlike a naive "only show the
3407
+ * active column's arrow", Attio reveals a neutral chevron on ANY sortable header on
3408
+ * hover (`data-chevron`'s own CSS in `data-table.ts` owns the opacity: 0 at rest, 0.6
3409
+ * on header hover, 1 + full foreground once sorted). Conditionally rendering only the
3410
+ * sorted state, the way this component used to, has nothing for that hover state to
3411
+ * reveal.
3412
+ */ function SortIndicator({ columnKey, icon }) {
3413
+ const { sort } = useDataTableContext();
3414
+ const isSorted = sort?.key === columnKey;
3415
+ const resolved = isSorted ? sort?.dir === "asc" ? icon?.asc ?? DEFAULT_SORT_ICON.asc : icon?.desc ?? DEFAULT_SORT_ICON.desc : icon?.idle ?? DEFAULT_SORT_ICON.idle;
3416
+ return /*#__PURE__*/ jsx(DataTableHeaderCellIcon, {
3417
+ "aria-hidden": true,
3418
+ "data-chevron": "",
3419
+ draggable: false,
3420
+ children: resolved
3421
+ });
3422
+ }
3423
+ /** Attio's own condition word: "Is"/"Is not" alone with zero or one value checked,
3424
+ * "Is any of"/"Is not any of" once a second value joins the set -- the underlying
3425
+ * predicate never changes (`useDataTableFilter.ts`'s own comment: membership in a
3426
+ * set of one IS "is X"), only the word describing it does. */ function filterConditionLabel(mode, hasMultipleValues) {
3427
+ if (mode === "isnot") return hasMultipleValues ? "Is not any of" : "Is not";
3428
+ return hasMultipleValues ? "Is any of" : "Is";
3429
+ }
3430
+ /**
3431
+ * A column's "is / is not / is any of" checkbox filter picker (DATATABLE-ATTIO-
3432
+ * SPEC.md's Feature contract), Attio's own chrome: a two-way condition control above a
3433
+ * checkbox value list, both built from seed's own documented components -- `Menu` for
3434
+ * the popover shell (`MenuRoot`/`Trigger`/`Positioner`/`Content`, unchanged), seed's
3435
+ * `SegmentedControl` (docs/content/react/components/segmented-control.mdx) for the
3436
+ * is/is-not toggle in place of the two hand-styled buttons (hardcoded hex, no seed
3437
+ * token) this used to be, and plain `<label>` + `CheckboxRoot` rows for the value
3438
+ * list -- NOT `MenuItem` there either: `MenuItem`'s own `getItemProps` (`useMenu.ts`)
3439
+ * closes the menu unconditionally after every click, which is correct for a normal
3440
+ * command menu but would close this ENTIRE popover after a single condition pick or a
3441
+ * single checked value -- exactly wrong for a control meant to stay open while the
3442
+ * condition changes and several boxes get checked. `SegmentedControl` has no such
3443
+ * auto-close (it's a plain controlled radio group, not a menu), which is exactly why
3444
+ * it -- not `MenuItem` -- is the documented component that fits here.
3445
+ */ function FilterMenu({ column }) {
3446
+ const { filters, toggleFilterValue, setFilterMode } = useDataTableContext();
3447
+ const values = column.filterValues ?? [];
3448
+ const current = filters[column.key];
3449
+ const mode = current?.mode ?? "is";
3450
+ const checkedValues = current?.values ?? [];
3451
+ const hasMultipleValues = checkedValues.length > 1;
3452
+ const columnLabel = typeof column.header === "string" ? column.header : column.key;
3453
+ // A filter trigger lives INSIDE a (possibly sortable) header cell, whose own
3454
+ // onClick/onKeyDown toggle sort -- stop the event here so opening the filter
3455
+ // picker never also reorders the rows out from under it.
3456
+ const stopPropagation = (event)=>event.stopPropagation();
3457
+ return /*#__PURE__*/ jsxs(MenuRoot, {
3458
+ children: [
3459
+ /*#__PURE__*/ jsx(DataTableHeaderCellIcon, {
3460
+ asChild: true,
3461
+ onClick: stopPropagation,
3462
+ onKeyDown: stopPropagation,
3463
+ draggable: false,
3464
+ children: /*#__PURE__*/ jsx(MenuTrigger, {
3465
+ "aria-label": `Filter ${columnLabel}`,
3466
+ "data-active": dataAttr(checkedValues.length > 0),
3467
+ children: /*#__PURE__*/ jsx(IconSlider2HorizontalLine_default, {
3468
+ size: 16
3469
+ })
3470
+ })
3471
+ }),
3472
+ /*#__PURE__*/ jsx(MenuPositioner, {
3473
+ children: /*#__PURE__*/ jsx(MenuContent, {
3474
+ style: {
3475
+ minWidth: 180
3476
+ },
3477
+ children: /*#__PURE__*/ jsxs(MenuScrollArea, {
3478
+ children: [
3479
+ /*#__PURE__*/ jsx("div", {
3480
+ style: {
3481
+ padding: "0 8px"
3482
+ },
3483
+ children: /*#__PURE__*/ jsxs(SegmentedControlRoot, {
3484
+ "aria-label": `${columnLabel} filter condition`,
3485
+ value: mode,
3486
+ onValueChange: (next)=>setFilterMode(column.key, next),
3487
+ style: {
3488
+ width: "100%"
3489
+ },
3490
+ children: [
3491
+ /*#__PURE__*/ jsxs(SegmentedControlItem, {
3492
+ value: "is",
3493
+ children: [
3494
+ /*#__PURE__*/ jsx(SegmentedControlItemHiddenInput, {}),
3495
+ filterConditionLabel("is", hasMultipleValues)
3496
+ ]
3497
+ }),
3498
+ /*#__PURE__*/ jsxs(SegmentedControlItem, {
3499
+ value: "isnot",
3500
+ children: [
3501
+ /*#__PURE__*/ jsx(SegmentedControlItemHiddenInput, {}),
3502
+ filterConditionLabel("isnot", hasMultipleValues)
3503
+ ]
3504
+ }),
3505
+ /*#__PURE__*/ jsx(SegmentedControlIndicator, {})
3506
+ ]
3507
+ })
3508
+ }),
3509
+ values.map((value)=>// `CheckboxRoot` is ALREADY a `<label>` (`Checkbox.tsx`'s own
3510
+ // `Primitive.label`) wrapping the hidden input + `CheckboxLabel` text --
3511
+ // wrapping it in a SECOND `<label>` here used to nest one label inside
3512
+ // another, which is invalid HTML and made a click on the row's own text
3513
+ // reach the checkbox's native activation twice (once via each label),
3514
+ // toggling it on then immediately back off. `CheckboxRoot` takes the
3515
+ // row's own layout via `style` directly instead.
3516
+ /*#__PURE__*/ jsxs(CheckboxRoot, {
3517
+ checked: checkedValues.includes(value),
3518
+ onCheckedChange: ()=>toggleFilterValue(column.key, value),
3519
+ style: {
3520
+ display: "flex",
3521
+ alignItems: "center",
3522
+ gap: 8,
3523
+ padding: "4px 8px",
3524
+ cursor: "pointer"
3525
+ },
3526
+ children: [
3527
+ /*#__PURE__*/ jsx(CheckboxControl, {
3528
+ children: /*#__PURE__*/ jsx(CheckboxIndicator, {
3529
+ checked: FILTER_CHECKED_ICON
3530
+ })
3531
+ }),
3532
+ /*#__PURE__*/ jsx(CheckboxLabel, {
3533
+ children: value
3534
+ }),
3535
+ /*#__PURE__*/ jsx(CheckboxHiddenInput, {
3536
+ "aria-label": value
3537
+ })
3538
+ ]
3539
+ }, value))
3540
+ ]
3541
+ })
3542
+ })
3543
+ })
3544
+ ]
3545
+ });
3546
+ }
3547
+ /** Every header cell, in the effective (ordered, visible) order -- a standalone
3548
+ * component (not inlined in `DataTableInner`'s render) so it alone re-renders when
3549
+ * `visibleColumnOrder`/labels/widths change, without also re-deriving `sortedData`'s
3550
+ * render above it. */ function ColumnHeaderCells({ columns, columnMenu, reorderable, resizable, sortIcon, selectable, stickyFirst, stickyLast }) {
3551
+ const { draggingColumnKey, beginColumnDrag, dragOverColumn, endColumnDrag } = useDataTableContext();
3552
+ const effective = useEffectiveColumns(columns);
3553
+ const [renamingKey, setRenamingKey] = React.useState(null);
3554
+ return /*#__PURE__*/ jsx(Fragment, {
3555
+ children: effective.map(({ column, label, width }, index)=>{
3556
+ const isFirst = index === 0;
3557
+ const isLast = index === effective.length - 1;
3558
+ const sticky = stickyProps({
3559
+ selectable,
3560
+ stickyFirst,
3561
+ stickyLast,
3562
+ isSelectionCell: false,
3563
+ isFirst,
3564
+ isLast
3565
+ });
3566
+ return /*#__PURE__*/ jsxs(DataTableHeaderCell, {
3567
+ columnKey: column.key,
3568
+ sortable: column.sortable,
3569
+ "data-column-key": column.key,
3570
+ draggable: reorderable && renamingKey !== column.key,
3571
+ onDragStart: (event)=>{
3572
+ event.dataTransfer.effectAllowed = "move";
3573
+ beginColumnDrag(column.key);
3574
+ },
3575
+ onDragOver: (event)=>{
3576
+ event.preventDefault();
3577
+ if (draggingColumnKey && draggingColumnKey !== column.key) dragOverColumn(column.key);
3578
+ },
3579
+ onDrop: (event)=>event.preventDefault(),
3580
+ onDragEnd: endColumnDrag,
3581
+ "data-dragging": dataAttr(draggingColumnKey === column.key),
3582
+ style: sticky.style,
3583
+ "data-sticky": sticky["data-sticky"],
3584
+ "data-sticky-edge": sticky["data-sticky-edge"],
3585
+ "data-sticky-edge-end": sticky["data-sticky-edge-end"],
3586
+ children: [
3587
+ /*#__PURE__*/ jsx(DataTableHeaderCellInner, {
3588
+ children: renamingKey === column.key ? /*#__PURE__*/ jsx(ColumnRenameInput, {
3589
+ column: column,
3590
+ initialLabel: typeof label === "string" ? label : column.key,
3591
+ onDone: ()=>setRenamingKey(null)
3592
+ }) : /*#__PURE__*/ jsxs(Fragment, {
3593
+ children: [
3594
+ /*#__PURE__*/ jsx(DataTableHeaderCellLabel, {
3595
+ children: label
3596
+ }),
3597
+ column.filterValues && column.filterValues.length > 0 && /*#__PURE__*/ jsx(FilterMenu, {
3598
+ column: column
3599
+ }),
3600
+ column.sortable && /*#__PURE__*/ jsx(SortIndicator, {
3601
+ columnKey: column.key,
3602
+ icon: sortIcon
3603
+ }),
3604
+ columnMenu && /*#__PURE__*/ jsx(ColumnMenu, {
3605
+ column: column,
3606
+ onRename: ()=>setRenamingKey(column.key)
3607
+ })
3608
+ ]
3609
+ })
3610
+ }),
3611
+ resizable && column.resizable !== false && /*#__PURE__*/ jsx(ColumnResizeHandle, {
3612
+ columnKey: column.key,
3613
+ width: width
3614
+ })
3615
+ ]
3616
+ }, column.key);
3617
+ })
3618
+ });
3619
+ }
3620
+ function ColumnRenameInput({ column, initialLabel, onDone }) {
3621
+ const { renameColumn } = useDataTableContext();
3622
+ const [value, setValue] = React.useState(initialLabel);
3623
+ // Opening this input is ITSELF the result of clicking a `MenuItem` -- the menu's own
3624
+ // close (which happens on that same click) restores focus to its trigger a beat
3625
+ // later, and that focus move fires as a `blur` on this input the instant it mounts
3626
+ // with `autoFocus`. Committing on THAT blur would silently re-close the editor
3627
+ // before anyone saw it (found writing this file's own rename test, which failed
3628
+ // with the header reverting to its plain label with no visible input in between).
3629
+ // `ready` flips true one frame after mount, so only a blur after that -- an actual
3630
+ // click/tab away -- commits.
3631
+ const readyRef = React.useRef(false);
3632
+ const inputRef = React.useRef(null);
3633
+ React.useEffect(()=>{
3634
+ // The menu's close restores focus to its trigger one frame after this input
3635
+ // mounts, stealing focus from `autoFocus`. Re-grab it in the SAME frame (after
3636
+ // the restore) and select the text, so the user can type/replace immediately.
3637
+ const frame = requestAnimationFrame(()=>{
3638
+ readyRef.current = true;
3639
+ inputRef.current?.focus();
3640
+ inputRef.current?.select();
3641
+ });
3642
+ return ()=>cancelAnimationFrame(frame);
3643
+ }, []);
3644
+ const commit = ()=>{
3645
+ if (value.trim()) renameColumn(column.key, value.trim());
3646
+ onDone();
3647
+ };
3648
+ return(// A bare, minimal input -- NOT seed's `TextField` (its labeled root measures
3649
+ // ~52px tall, per DATATABLE-ATTIO-SPEC.md's own note; this has to fit a 40px
3650
+ // header cell with no label/description chrome of its own). Seed has no lighter
3651
+ // input primitive at this level, so this follows crisp's own measured
3652
+ // `.crisp-datatable-th-rename` (`datatable.recipe.ts`) instead: full-width,
3653
+ // header-row height, a small inset, no border (an inset brand-solid box-shadow
3654
+ // stands in for a focus ring), the header's own font weight/size inherited
3655
+ // rather than a new size introduced for one input.
3656
+ /*#__PURE__*/ jsx("input", {
3657
+ ref: inputRef,
3658
+ // biome-ignore lint/a11y/noAutofocus: opening this input IS the user's own click a moment earlier (crisp's `.crisp-datatable-th-rename` does the same), not an unannounced page-load autofocus.
3659
+ autoFocus: true,
3660
+ "aria-label": `Rename ${initialLabel}`,
3661
+ value: value,
3662
+ onClick: (event)=>event.stopPropagation(),
3663
+ onChange: (event)=>setValue(event.target.value),
3664
+ onBlur: ()=>{
3665
+ if (readyRef.current) commit();
3666
+ },
3667
+ onKeyDown: (event)=>{
3668
+ event.stopPropagation();
3669
+ if (event.key === "Enter") commit();
3670
+ if (event.key === "Escape") onDone();
3671
+ },
3672
+ // Bare inline input: no border/ring/background/radius -- it reads as the
3673
+ // header label made editable in place, not a boxed field.
3674
+ style: {
3675
+ width: "100%",
3676
+ minWidth: 0,
3677
+ height: "100%",
3678
+ margin: 0,
3679
+ padding: 0,
3680
+ border: "none",
3681
+ borderRadius: 0,
3682
+ background: "transparent",
3683
+ boxShadow: "none",
3684
+ font: "inherit",
3685
+ fontWeight: "var(--seed-font-weight-medium)",
3686
+ color: "var(--seed-color-fg-neutral)",
3687
+ outline: "none"
3688
+ }
3689
+ }));
3690
+ }
3691
+ /** The per-column header menu (rename / hide / move left / move right) -- seed
3692
+ * `Menu`, opened from the same `headerCellIcon` slot the filter trigger and sort
3693
+ * chevron already share. Hover/focus-reveal (`data-column-menu`'s own CSS in
3694
+ * `data-table.ts`) -- crisp's own header shows the trigger only on hover, not
3695
+ * permanently, so this one carries its own marker attribute distinct from that
3696
+ * slot's OTHER occupants (the sort chevron, the filter trigger), which stay visible
3697
+ * under their own separate rules regardless of hover. */ function ColumnMenu({ column, onRename }) {
3698
+ const { moveColumn, hideColumn } = useDataTableContext();
3699
+ const columnLabel = typeof column.header === "string" ? column.header : column.key;
3700
+ const stopPropagation = (event)=>event.stopPropagation();
3701
+ return /*#__PURE__*/ jsxs(MenuRoot, {
3702
+ children: [
3703
+ /*#__PURE__*/ jsx(DataTableHeaderCellIcon, {
3704
+ asChild: true,
3705
+ "data-column-menu": "",
3706
+ onClick: stopPropagation,
3707
+ onKeyDown: stopPropagation,
3708
+ draggable: false,
3709
+ children: /*#__PURE__*/ jsx(MenuTrigger, {
3710
+ "aria-label": `${columnLabel} column menu`,
3711
+ children: /*#__PURE__*/ jsx(IconDot3HorizontalLine_default, {
3712
+ size: 16
3713
+ })
3714
+ })
3715
+ }),
3716
+ /*#__PURE__*/ jsx(MenuPositioner, {
3717
+ children: /*#__PURE__*/ jsx(MenuContent, {
3718
+ style: {
3719
+ minWidth: 160
3720
+ },
3721
+ children: /*#__PURE__*/ jsxs(MenuScrollArea, {
3722
+ children: [
3723
+ /*#__PURE__*/ jsx(MenuItem, {
3724
+ onClick: onRename,
3725
+ children: /*#__PURE__*/ jsx(MenuItemLabel, {
3726
+ children: "Rename"
3727
+ })
3728
+ }),
3729
+ /*#__PURE__*/ jsx(MenuItem, {
3730
+ onClick: ()=>moveColumn(column.key, "left"),
3731
+ children: /*#__PURE__*/ jsx(MenuItemLabel, {
3732
+ children: "Move left"
3733
+ })
3734
+ }),
3735
+ /*#__PURE__*/ jsx(MenuItem, {
3736
+ onClick: ()=>moveColumn(column.key, "right"),
3737
+ children: /*#__PURE__*/ jsx(MenuItemLabel, {
3738
+ children: "Move right"
3739
+ })
3740
+ }),
3741
+ /*#__PURE__*/ jsx(MenuItem, {
3742
+ onClick: ()=>hideColumn(column.key),
3743
+ children: /*#__PURE__*/ jsx(MenuItemLabel, {
3744
+ children: "Hide column"
3745
+ })
3746
+ })
3747
+ ]
3748
+ })
3749
+ })
3750
+ })
3751
+ ]
3752
+ });
3753
+ }
3754
+
3755
+ __insertCSS(".seed-data-table__root{box-sizing:border-box;display:flex;flex-direction:column;width:100%;border-width:1px;border-style:solid;font-family:Inter,-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif}.seed-data-table__table{width:100%;border-collapse:separate;border-spacing:0;table-layout:fixed}.seed-data-table__headerCell{box-sizing:border-box;position:sticky;top:0;z-index:2;vertical-align:middle;text-align:start;padding:0;height:var(--seed-dimension-x10);background-color:var(--seed-color-bg-layer-default);border-top:1px solid var(--seed-color-stroke-neutral-muted);border-bottom:1px solid var(--seed-color-stroke-neutral-muted);border-inline-end:1px solid var(--seed-color-stroke-neutral-muted);border-inline-start:none;color:var(--seed-color-fg-neutral);font-size:var(--seed-font-size-t4);line-height:var(--seed-line-height-t4);font-weight:var(--seed-font-weight-medium);letter-spacing:-.14px}.seed-data-table__headerCell:last-child{border-inline-end:none}.seed-data-table__headerCell[data-sortable]{cursor:pointer;user-select:none}.seed-data-table__headerCell [data-chevron]{opacity:0;transition:opacity .12s}.seed-data-table__headerCell[data-sortable]:hover [data-chevron]{opacity:.6}.seed-data-table__headerCell[data-sorted] [data-chevron]{opacity:1;color:var(--seed-color-fg-neutral)}.seed-data-table__headerCell:hover [data-column-menu]{opacity:1}.seed-data-table__headerCell [data-column-menu]:focus-visible,.seed-data-table__headerCell [data-column-menu][data-open]{opacity:1}.seed-data-table__headerCell[data-dragging]{opacity:.5}.seed-data-table__headerCell[data-sticky]{position:sticky;z-index:3;background-color:var(--seed-color-bg-layer-default)}.seed-data-table__headerCell{outline:var(--seed-dimension-x0_5) solid transparent;outline-offset:calc(var(--seed-dimension-x0_5) * -1)}.seed-data-table__headerCell:is(:focus-visible,[data-focus-visible]){outline:var(--seed-dimension-x0_5) solid var(--seed-color-stroke-focus-ring);outline-offset:calc(var(--seed-dimension-x0_5) * -1)}.seed-data-table__headerCell .seed-data-table-resize-handle{position:absolute;top:0;inset-inline-end:calc(7px / -2);width:7px;height:100%;z-index:4;display:flex;align-items:center;justify-content:center;cursor:col-resize;touch-action:none}.seed-data-table__headerCell .seed-data-table-resize-handle-inner{width:3px;height:100%;border-radius:3px;background-color:#0298d9;opacity:0;transition:opacity .1s}.seed-data-table__headerCell .seed-data-table-resize-handle:hover .seed-data-table-resize-handle-inner,.seed-data-table__headerCell .seed-data-table-resize-handle[data-resizing] .seed-data-table-resize-handle-inner{opacity:1}.seed-data-table__headerCellInner{box-sizing:border-box;display:flex;align-items:center;width:100%;height:100%;min-width:0;gap:var(--seed-dimension-x1_5);padding-inline:var(--seed-dimension-x3)}.seed-data-table__headerCellLabel{min-width:0;flex:1 1 0%;overflow:clip;text-overflow:ellipsis;white-space:nowrap}.seed-data-table__headerCellIcon{flex-shrink:0;margin-inline-start:auto;display:inline-flex;align-items:center;justify-content:center;width:var(--seed-dimension-x4);height:var(--seed-dimension-x4);color:var(--seed-color-fg-neutral-subtle);border:none;background:0 0;padding:0;font:inherit;cursor:pointer}.seed-data-table__headerCellIcon[data-active]{color:var(--seed-color-fg-neutral)}.seed-data-table__headerCellIcon[data-column-menu]{opacity:0;transition:opacity .12s}.seed-data-table__row{position:relative;background-color:var(--seed-color-bg-layer-default);--seed-data-table-row-bg:var(--seed-color-bg-layer-default)}.seed-data-table__row[data-clickable]{cursor:pointer}.seed-data-table__row:is([aria-selected=true],[data-selected]){background-color:color-mix(in srgb,#0298d9 10%,var(--seed-color-bg-layer-default));--seed-data-table-row-bg:color-mix(in srgb, #0298d9 10%, var(--seed-color-bg-layer-default))}.seed-data-table__row:is([aria-selected=true],[data-selected]):is(:hover,[data-hover]){background-color:color-mix(in srgb,var(--seed-color-fg-neutral) 3%,color-mix(in srgb,#0298d9 10%,var(--seed-color-bg-layer-default)));--seed-data-table-row-bg:color-mix(in srgb, var(--seed-color-fg-neutral) 3%, color-mix(in srgb, #0298d9 10%, var(--seed-color-bg-layer-default)))}.seed-data-table__cell{box-sizing:border-box;position:relative;vertical-align:middle;text-align:start;height:var(--seed-dimension-x9);border-bottom:1px solid var(--seed-color-stroke-neutral-muted);border-inline-end:1px solid var(--seed-color-stroke-neutral-muted);border-top:none;border-inline-start:none;padding-block:0;padding-inline:var(--seed-dimension-x3);color:var(--seed-color-fg-neutral);font-size:var(--seed-font-size-t4);line-height:var(--seed-line-height-t4);font-weight:var(--seed-font-weight-regular);letter-spacing:-.14px;overflow:clip;text-overflow:ellipsis;white-space:nowrap}.seed-data-table__cell:last-child{border-inline-end:none}tbody tr:last-child .seed-data-table__cell{border-bottom:none}.seed-data-table__cell[data-numeric]{text-align:end;font-variant-numeric:tabular-nums}.seed-data-table__cell[data-sticky]{position:sticky;z-index:1;background-color:var(--seed-data-table-row-bg)}.seed-data-table__cell[data-editable]{cursor:default}.seed-data-table__cell .seed-data-table-edit-picker{display:flex;align-items:center;gap:var(--seed-dimension-x1_5);min-width:0}.seed-data-table__cell .seed-data-table-edit-pending{flex-shrink:0;color:var(--seed-color-fg-neutral-subtle);animation:rotate .6s linear infinite}.seed-data-table__cell .seed-data-table-edit-error{position:absolute;top:100%;inset-inline-start:var(--seed-dimension-x3);inset-inline-end:var(--seed-dimension-x3);z-index:2;padding-top:2px;font-size:12px;line-height:1.3;color:var(--seed-color-fg-critical);overflow:clip;text-overflow:ellipsis;white-space:nowrap}.seed-data-table__entity{display:flex;align-items:center;min-width:0;gap:var(--seed-dimension-x1_5)}.seed-data-table__entityName{min-width:0;font-weight:var(--seed-font-weight-medium);overflow:clip;text-overflow:ellipsis;white-space:nowrap}.seed-data-table__tagList{display:flex;align-items:center;gap:var(--seed-dimension-x1_5);overflow:hidden;min-width:0}.seed-data-table__link{color:var(--seed-color-fg-neutral);text-decoration:none;min-width:0;overflow:clip;text-overflow:ellipsis;white-space:nowrap}.seed-data-table__link:is(:hover,[data-hover]){text-decoration:underline}.seed-data-table__toolbar{box-sizing:border-box;display:flex;align-items:center;width:100%;flex-shrink:0;min-height:49px;gap:var(--seed-dimension-x2);padding-inline:var(--seed-dimension-x3);border-top:1px solid var(--seed-color-stroke-neutral-muted)}.seed-data-table__search{flex:1 1 0%;max-width:280px}.seed-data-table__count{flex-shrink:0;margin-inline-start:auto;color:var(--seed-color-fg-neutral-subtle);font-size:var(--seed-font-size-t3);font-variant-numeric:tabular-nums}.seed-data-table__scrollArea{position:relative;display:flex;flex-direction:column;width:100%;min-width:0;flex:1 1 auto;min-height:0;overflow-x:auto;overflow-y:auto}.seed-data-table__scrollArea[data-scrolled-start] [data-sticky-edge]{clip-path:inset(0 -38px 0 0);box-shadow:6px 0 16px 4px rgba(0,0,0,.12)}.seed-data-table__scrollArea[data-scrolled-end] [data-sticky-edge-end]{clip-path:inset(0 0 0 -38px);box-shadow:-6px 0 16px 4px rgba(0,0,0,.12)}.seed-data-table__footerCell{box-sizing:border-box;position:sticky;bottom:0;z-index:2;vertical-align:middle;padding:0;height:var(--seed-dimension-x9);background-color:var(--seed-color-bg-layer-default);border-top:1px solid var(--seed-color-stroke-neutral-muted);border-inline-end:1px solid var(--seed-color-stroke-neutral-muted);border-inline-start:none}.seed-data-table__footerCell:last-child{border-inline-end:none}.seed-data-table__footerCell .seed-data-table-column-calc-trigger{display:flex;align-items:center;gap:var(--seed-dimension-x1_5);width:100%;height:100%;min-width:0;margin:0;padding:0 var(--seed-dimension-x3);border:none;background:0 0;color:var(--seed-color-fg-neutral-subtle);font-family:inherit;font-size:var(--seed-font-size-t4);font-weight:var(--seed-font-weight-medium);white-space:nowrap;overflow:clip;cursor:pointer;transition:background-color .12s,color .12s}.seed-data-table__footerCell .seed-data-table-column-calc-trigger:hover{background-color:color-mix(in srgb,var(--seed-color-fg-neutral) 4%,transparent);color:var(--seed-color-fg-neutral)}.seed-data-table__footerCell .seed-data-table-column-calc-trigger[data-align=right]{justify-content:flex-end}.seed-data-table__footerCell .seed-data-table-column-calc-trigger svg{flex-shrink:0;opacity:.7}.seed-data-table__footerCell .seed-data-table-column-calc-placeholder{overflow:clip;text-overflow:ellipsis;white-space:nowrap}.seed-data-table__footerCell .seed-data-table-column-calc-value{min-width:0;flex-shrink:1;color:var(--seed-color-fg-neutral);font-variant-numeric:tabular-nums;overflow:clip;text-overflow:ellipsis;white-space:nowrap}.seed-data-table__footerCell .seed-data-table-column-calc-op{min-width:0;flex-shrink:1;color:color-mix(in srgb,var(--seed-color-fg-neutral) 63%,transparent);overflow:clip;text-overflow:ellipsis;white-space:nowrap}.seed-data-table__emptyState{box-sizing:border-box;text-align:center;padding-block:var(--seed-dimension-x8);padding-inline:var(--seed-dimension-x3);color:var(--seed-color-fg-neutral-subtle);font-size:var(--seed-font-size-t4)}.seed-data-table__loadingState{box-sizing:border-box;display:flex;align-items:center;justify-content:center;gap:10px;padding-block:var(--seed-dimension-x8);padding-inline:var(--seed-dimension-x3);color:color-mix(in srgb,var(--seed-color-fg-neutral) 63%,transparent);font-size:var(--seed-font-size-t4);line-height:var(--seed-line-height-t4);font-weight:var(--seed-font-weight-medium)}.seed-data-table__loadingState[data-inline]{padding-block:0;padding-inline:0;height:var(--seed-dimension-x9)}.seed-data-table__loadingState .seed-data-table-loading-spinner{flex-shrink:0;color:color-mix(in srgb,var(--seed-color-fg-neutral) 63%,transparent);animation:rotate .6s linear infinite}.seed-data-table__addRowCell{box-sizing:border-box;padding:0;border-top:none}.seed-data-table__addRow{box-sizing:border-box;display:flex;align-items:center;width:100%;margin:0;border:none;border-top:1px solid var(--seed-color-stroke-neutral-muted);background-color:var(--seed-color-bg-layer-default);height:var(--seed-dimension-x9);gap:var(--seed-dimension-x2);padding-inline:var(--seed-dimension-x2);color:var(--seed-color-fg-neutral-subtle);font-family:inherit;font-size:var(--seed-font-size-t4);cursor:pointer;transition:background-color .12s,color .12s}.seed-data-table__addRow:hover{background-color:color-mix(in srgb,var(--seed-color-fg-neutral) 4%,var(--seed-color-bg-layer-default));color:var(--seed-color-fg-neutral)}.seed-data-table__addRow svg{flex-shrink:0}.seed-data-table__selectionCell{text-align:center;padding:0}.seed-data-table__root--surface_card{border-color:var(--seed-color-stroke-neutral-muted);background-color:var(--seed-color-bg-layer-default);border-radius:var(--seed-radius-r3)}.seed-data-table__root--surface_bleed{border-color:var(--seed-color-bg-transparent);background-color:var(--seed-color-bg-transparent)}.seed-data-table__row--interactive_true{transition:background-color .12s}.seed-data-table__row--interactive_true:is(:hover,[data-hover]){background-color:color-mix(in srgb,var(--seed-color-fg-neutral) 3%,transparent);--seed-data-table-row-bg:color-mix(in srgb, var(--seed-color-fg-neutral) 3%, var(--seed-color-bg-layer-default))}");
3756
+
3757
+ const createClassName = (className, variants, compoundVariants = [])=>{
3758
+ const variantKeys = Object.keys(variants);
3759
+ const variantValues = variantKeys.map((key)=>variants[key]);
3760
+ const variantClassName = variantKeys.map((key, index)=>`${className}--${key}_${variantValues[index]}`).join(" ");
3761
+ const compountVariantClassName = compoundVariants.filter((compoundVariant)=>Object.keys(compoundVariant).every((key)=>compoundVariant[key] === variants[key])).map((compoundVariant)=>`${className}--${Object.keys(compoundVariant).map((key)=>{
3762
+ return `${key}_${compoundVariant[key]}`;
3763
+ }).join("-")}`).join(" ");
3764
+ return [
3765
+ className,
3766
+ variantClassName,
3767
+ compountVariantClassName
3768
+ ].filter(Boolean).join(" ");
3769
+ };
3770
+ function mergeVariants(a, b) {
3771
+ const result = {
3772
+ ...a
3773
+ };
3774
+ for(const k in b){
3775
+ if (b[k] != null) {
3776
+ result[k] = b[k];
3777
+ }
3778
+ }
3779
+ return result;
3780
+ }
3781
+ function splitVariantProps(props, variantMap) {
3782
+ const variantProps = {};
3783
+ const otherProps = {};
3784
+ for(const key in props){
3785
+ if (variantMap[key] != null) {
3786
+ variantProps[key] = props[key];
3787
+ } else {
3788
+ otherProps[key] = props[key];
3789
+ }
3790
+ }
3791
+ return [
3792
+ variantProps,
3793
+ otherProps
3794
+ ];
3795
+ }
3796
+
3797
+ const dataTableSlotNames = [
3798
+ [
3799
+ "root",
3800
+ "seed-data-table__root"
3801
+ ],
3802
+ [
3803
+ "table",
3804
+ "seed-data-table__table"
3805
+ ],
3806
+ [
3807
+ "header",
3808
+ "seed-data-table__header"
3809
+ ],
3810
+ [
3811
+ "headerCell",
3812
+ "seed-data-table__headerCell"
3813
+ ],
3814
+ [
3815
+ "headerCellInner",
3816
+ "seed-data-table__headerCellInner"
3817
+ ],
3818
+ [
3819
+ "headerCellLabel",
3820
+ "seed-data-table__headerCellLabel"
3821
+ ],
3822
+ [
3823
+ "headerCellIcon",
3824
+ "seed-data-table__headerCellIcon"
3825
+ ],
3826
+ [
3827
+ "row",
3828
+ "seed-data-table__row"
3829
+ ],
3830
+ [
3831
+ "cell",
3832
+ "seed-data-table__cell"
3833
+ ],
3834
+ [
3835
+ "entity",
3836
+ "seed-data-table__entity"
3837
+ ],
3838
+ [
3839
+ "entityName",
3840
+ "seed-data-table__entityName"
3841
+ ],
3842
+ [
3843
+ "tagList",
3844
+ "seed-data-table__tagList"
3845
+ ],
3846
+ [
3847
+ "link",
3848
+ "seed-data-table__link"
3849
+ ],
3850
+ [
3851
+ "toolbar",
3852
+ "seed-data-table__toolbar"
3853
+ ],
3854
+ [
3855
+ "search",
3856
+ "seed-data-table__search"
3857
+ ],
3858
+ [
3859
+ "count",
3860
+ "seed-data-table__count"
3861
+ ],
3862
+ [
3863
+ "scrollArea",
3864
+ "seed-data-table__scrollArea"
3865
+ ],
3866
+ [
3867
+ "footer",
3868
+ "seed-data-table__footer"
3869
+ ],
3870
+ [
3871
+ "footerCell",
3872
+ "seed-data-table__footerCell"
3873
+ ],
3874
+ [
3875
+ "emptyState",
3876
+ "seed-data-table__emptyState"
3877
+ ],
3878
+ [
3879
+ "loadingState",
3880
+ "seed-data-table__loadingState"
3881
+ ],
3882
+ [
3883
+ "addRow",
3884
+ "seed-data-table__addRow"
3885
+ ],
3886
+ [
3887
+ "addRowCell",
3888
+ "seed-data-table__addRowCell"
3889
+ ],
3890
+ [
3891
+ "selectionCell",
3892
+ "seed-data-table__selectionCell"
3893
+ ]
3894
+ ];
3895
+ const defaultVariant = {
3896
+ "surface": "card",
3897
+ "interactive": false
3898
+ };
3899
+ const compoundVariants = [];
3900
+ const dataTableVariantMap = {
3901
+ "surface": [
3902
+ "card",
3903
+ "bleed"
3904
+ ],
3905
+ "interactive": [
3906
+ true,
3907
+ false
3908
+ ]
3909
+ };
3910
+ function dataTable(props) {
3911
+ return Object.fromEntries(dataTableSlotNames.map(([slot, className])=>{
3912
+ return [
3913
+ slot,
3914
+ createClassName(className, mergeVariants(defaultVariant, props), compoundVariants)
3915
+ ];
3916
+ }));
3917
+ }
3918
+ Object.assign(dataTable, {
3919
+ splitVariantProps: (props)=>splitVariantProps(props, dataTableVariantMap)
3920
+ }); // @recipe(seed): data-table
3921
+
3922
+ // The one `createSlotRecipeContext(dataTable)` call every sibling file in this
3923
+ // directory shares -- `ClassNamesProvider` and `withContext` must be the SAME
3924
+ // binding everywhere (a slot component built from a second, independent call would
3925
+ // read a DIFFERENT React context and never see the classNames `DataTableInner`
3926
+ // provides), so this is the one place that calls `createSlotRecipeContext`, and every
3927
+ // other file (`DataTable.tsx`, `column-menu.tsx`, `resize.tsx`, `column-calc.tsx`,
3928
+ // `toolbar.tsx`, `body-cells.tsx`, `editors.tsx`) imports its exports from here rather
3929
+ // than re-deriving its own.
3930
+ const { ClassNamesProvider, withContext, useClassNames } = createSlotRecipeContext(dataTable);
3931
+
3932
+ /**
3933
+ * A small inline spinner, not seed's own `ProgressCircle` -- that component's
3934
+ * smallest size (24px) is already bigger than crisp's own measured 16px loading
3935
+ * icon, the same gap `editors.tsx`'s `EditPendingIndicator` already works around for
3936
+ * its own (smaller still, 14px) in-cell pending spinner. Same shape, one size up,
3937
+ * reusing the SAME global `rotate` keyframe (this file's own qvism recipe slot
3938
+ * nests `.seed-data-table-loading-spinner` under it) rather than a third near-
3939
+ * identical spinner.
3940
+ */ function LoadingSpinner() {
3941
+ return /*#__PURE__*/ jsxs("svg", {
3942
+ className: "seed-data-table-loading-spinner",
3943
+ width: 16,
3944
+ height: 16,
3945
+ viewBox: "0 0 16 16",
3946
+ fill: "none",
3947
+ role: "presentation",
3948
+ "aria-hidden": "true",
3949
+ children: [
3950
+ /*#__PURE__*/ jsx("circle", {
3951
+ cx: 8,
3952
+ cy: 8,
3953
+ r: 6.5,
3954
+ stroke: "currentColor",
3955
+ strokeOpacity: 0.25,
3956
+ strokeWidth: 2
3957
+ }),
3958
+ /*#__PURE__*/ jsx("path", {
3959
+ d: "M14.5 8a6.5 6.5 0 0 0-6.5-6.5",
3960
+ stroke: "currentColor",
3961
+ strokeWidth: 2,
3962
+ strokeLinecap: "round"
3963
+ })
3964
+ ]
3965
+ });
3966
+ }
3967
+ /** The full-body empty/error row's own content -- one `<tr>`/`<td colSpan>` wrapping
3968
+ * this, rendered by `DataTable.tsx` itself (it already owns `totalCols`). Shared by
3969
+ * `empty` and `error` alike, matching crisp's own single `.crisp-datatable-empty`
3970
+ * class doing double duty for both. */ function EmptyStateContent({ children, classNames }) {
3971
+ return /*#__PURE__*/ jsx("div", {
3972
+ className: classNames.emptyState,
3973
+ children: children
3974
+ });
3975
+ }
3976
+ /** The full-body loading row's own content (spinner + `loadingLabel`). */ function LoadingStateContent({ label, classNames }) {
3977
+ return /*#__PURE__*/ jsxs("div", {
3978
+ className: classNames.loadingState,
3979
+ children: [
3980
+ /*#__PURE__*/ jsx(LoadingSpinner, {}),
3981
+ label
3982
+ ]
3983
+ });
3984
+ }
3985
+ /**
3986
+ * Infinite scroll's own sentinel + "loading more" row -- the LAST child of
3987
+ * `<tbody>` while `hasMore` (the headless `useDataTableInfiniteScroll`'s own
3988
+ * return, read off context the same way `SelectAllCheckbox` reads `selectionState`).
3989
+ * Renders nothing once `hasMore` is false -- there is no manual pagination
3990
+ * fallback, matching DATATABLE-ATTIO-SPEC.md's auto-load-only contract. While a
3991
+ * fetch is in flight (`loadingMore`) the SAME row shows the compact spinner
3992
+ * (`loadingState`'s own `[data-inline]` modifier, a normal 36px row); at rest it's
3993
+ * an empty sentinel row the IntersectionObserver watches.
3994
+ */ function InfiniteScrollRow({ colSpan, classNames, loadingLabel }) {
3995
+ const { sentinelRef, hasMore, loadingMore } = useDataTableContext();
3996
+ if (!hasMore) return null;
3997
+ return /*#__PURE__*/ jsx("tr", {
3998
+ ref: sentinelRef,
3999
+ "aria-hidden": loadingMore ? undefined : "true",
4000
+ children: /*#__PURE__*/ jsx(DataTableCell, {
4001
+ colSpan: colSpan,
4002
+ children: loadingMore && /*#__PURE__*/ jsxs("div", {
4003
+ className: classNames.loadingState,
4004
+ "data-inline": "",
4005
+ children: [
4006
+ /*#__PURE__*/ jsx(LoadingSpinner, {}),
4007
+ loadingLabel
4008
+ ]
4009
+ })
4010
+ })
4011
+ });
4012
+ }
4013
+ /** The trailing "+ New" add-row footer -- crisp's own `.crisp-datatable-addrow`
4014
+ * inside a zero-padding `.crisp-datatable-addcell`, its own `<tfoot>` (a table can
4015
+ * have more than one; the ColumnCalc summary footer, when both are present, is a
4016
+ * separate one, matching crisp's own two independent `<TableFooter>` rows). */ function AddRowFooter({ onAddRow, addRowLabel, colSpan, classNames }) {
4017
+ return /*#__PURE__*/ jsx("tfoot", {
4018
+ children: /*#__PURE__*/ jsx("tr", {
4019
+ children: /*#__PURE__*/ jsx("td", {
4020
+ className: classNames.addRowCell,
4021
+ colSpan: colSpan,
4022
+ children: /*#__PURE__*/ jsxs("button", {
4023
+ type: "button",
4024
+ className: classNames.addRow,
4025
+ onClick: onAddRow,
4026
+ children: [
4027
+ /*#__PURE__*/ jsx(IconPlusLine_default, {
4028
+ size: 15
4029
+ }),
4030
+ addRowLabel
4031
+ ]
4032
+ })
4033
+ })
4034
+ })
4035
+ });
4036
+ }
4037
+
4038
+ const DataTableToolbar = withContext(Primitive.div, "toolbar");
4039
+ const DataTableCount = withContext(Primitive.span, "count");
4040
+ /**
4041
+ * Fix (recurring bug): the search box is seed's own DOCUMENTED `TextField`
4042
+ * composition -- `TextFieldRoot` (`variant="outline" size="medium"`) +
4043
+ * `TextFieldPrefixIcon` + `TextFieldInput`, exactly `docs/examples/react/
4044
+ * text-field-input/prefix.tsx` -- not that same trio fought with a custom
4045
+ * `classNames.search` overriding its height/padding/icon position with `!important`.
4046
+ * That override (this file's own previous state) is what kept mis-placing the icon:
4047
+ * every fix landed on ANOTHER property the override was still fighting (the icon's own
4048
+ * intrinsic SVG size, its inherited margin, the root's own min-height beating a plain
4049
+ * `height` override) because the override was never the right layer to fix it at.
4050
+ * `size="medium"`'s built-in prefix-icon spacing (a flex `gap` + edge margins, not
4051
+ * absolute positioning) is what `text-input.ts`'s own variant CSS already gets right,
4052
+ * so it needs no help from this component at all -- `search` (recipe.ts) is now just
4053
+ * this box's own layout constraint (`flex`, `max-width`) within the toolbar row, not
4054
+ * its internal chrome.
4055
+ */ function SearchBox({ placeholder, label }) {
4056
+ const { query, setQuery } = useDataTableContext();
4057
+ const classNames = dataTable();
4058
+ return /*#__PURE__*/ jsxs(TextFieldRoot, {
4059
+ variant: "outline",
4060
+ size: "medium",
4061
+ className: classNames.search,
4062
+ children: [
4063
+ /*#__PURE__*/ jsx(TextFieldPrefixIcon, {
4064
+ svg: /*#__PURE__*/ jsx(IconMagnifyingglassLine_default, {})
4065
+ }),
4066
+ /*#__PURE__*/ jsx(TextFieldInput, {
4067
+ type: "text",
4068
+ value: query,
4069
+ onChange: (event)=>setQuery(event.target.value),
4070
+ placeholder: placeholder,
4071
+ "aria-label": label
4072
+ })
4073
+ ]
4074
+ });
4075
+ }
4076
+ /** crisp's own count format (`datatable.tsx`'s `resolvedCountLabel`): unfiltered reads
4077
+ * "N rows" ("N row" for exactly one), filtered/searched reads "N of M" with no
4078
+ * trailing word at all -- reproduced literally rather than reinvented, since this is
4079
+ * the one piece of copy DATATABLE-ATTIO-SPEC.md calls out by example. */ function defaultCountLabel(shown, total) {
4080
+ if (shown === total) return `${total} ${total === 1 ? "row" : "rows"}`;
4081
+ return `${shown} of ${total}`;
4082
+ }
4083
+ function RowCount({ shown, total, countLabel }) {
4084
+ return /*#__PURE__*/ jsx(DataTableCount, {
4085
+ children: (countLabel ?? defaultCountLabel)(shown, total)
4086
+ });
4087
+ }
4088
+
4089
+ function DataTableInner(props, ref) {
4090
+ const { columns, data, surface, className, locale = "en-US", sort, defaultSort, onSortChange, sortIcon, selectable, selectedIds, defaultSelectedIds, onSelectionChange, selectAllLabel = "Select all rows", getRowSelectionLabel = ()=>"Select row", selectionCheckedIcon, selectionIndeterminateIcon, query, defaultQuery, onQueryChange, searchPlaceholder = "Search…", searchLabel = "Search table", filters, defaultFilters, onFiltersChange, showCount = true, countLabel, columnMenu = false, reorderable = true, resizable = true, columnOrder, defaultColumnOrder, onColumnOrderChange, hiddenColumnKeys, defaultHiddenColumnKeys, onHiddenColumnKeysChange, columnWidths, defaultColumnWidths, onColumnWidthsChange, columnLabels, defaultColumnLabels, onColumnLabelsChange, onColumnsChange, columnCalcOps, defaultColumnCalcOps, onColumnCalcOpsChange, stickyFirst = false, stickyLast = false, onEdit, interactive = false, onRowClick, onAddRow, addRowLabel = "New record", loading = false, loadingLabel = "Loading...", error, empty = "No results.", onLoadMore, hasMore, loadingMore, scrollProps, ...restProps } = props;
4091
+ const classNames = dataTable({
4092
+ surface,
4093
+ interactive
4094
+ });
4095
+ // The batteries-included DataTable owns the sort state (controllable) so it
4096
+ // can actually reorder `data` -- the headless primitive only tracks which
4097
+ // column/direction is active. A consumer that pre-sorts server-side can pass
4098
+ // a controlled `sort`; this still sorts the rows it's given, which for
4099
+ // already-sorted data is a no-op.
4100
+ const [internalSort, setInternalSort] = React.useState(defaultSort);
4101
+ const resolvedSort = sort !== undefined ? sort : internalSort;
4102
+ const handleSortChange = React.useCallback((next)=>{
4103
+ if (sort === undefined) setInternalSort(next);
4104
+ onSortChange?.(next);
4105
+ }, [
4106
+ sort,
4107
+ onSortChange
4108
+ ]);
4109
+ // Same shape as sort just above -- the headless primitive only tracks the ACTIVE
4110
+ // query/filter state (it never sees `TRow`), this component owns the controllable
4111
+ // state that actually narrows `data` (`getVisibleRows`, ./filtering.ts).
4112
+ const [internalQuery, setInternalQuery] = React.useState(defaultQuery ?? "");
4113
+ const resolvedQuery = query !== undefined ? query : internalQuery;
4114
+ const handleQueryChange = React.useCallback((next)=>{
4115
+ if (query === undefined) setInternalQuery(next);
4116
+ onQueryChange?.(next);
4117
+ }, [
4118
+ query,
4119
+ onQueryChange
4120
+ ]);
4121
+ const [internalFilters, setInternalFilters] = React.useState(defaultFilters ?? {});
4122
+ const resolvedFilters = filters !== undefined ? filters : internalFilters;
4123
+ const handleFiltersChange = React.useCallback((next)=>{
4124
+ if (filters === undefined) setInternalFilters(next);
4125
+ onFiltersChange?.(next);
4126
+ }, [
4127
+ filters,
4128
+ onFiltersChange
4129
+ ]);
4130
+ const visibleRows = React.useMemo(()=>getVisibleRows(data, columns, resolvedQuery, resolvedFilters), [
4131
+ data,
4132
+ columns,
4133
+ resolvedQuery,
4134
+ resolvedFilters
4135
+ ]);
4136
+ const sortedData = React.useMemo(()=>{
4137
+ if (!resolvedSort) return visibleRows;
4138
+ const column = columns.find((candidate)=>candidate.key === resolvedSort.key);
4139
+ const factor = resolvedSort.dir === "asc" ? 1 : -1;
4140
+ return [
4141
+ ...visibleRows
4142
+ ].sort((a, b)=>{
4143
+ const av = column ? getColumnValue(column, a) : a[resolvedSort.key];
4144
+ const bv = column ? getColumnValue(column, b) : b[resolvedSort.key];
4145
+ if (av == null && bv == null) return 0;
4146
+ if (av == null) return -factor;
4147
+ if (bv == null) return factor;
4148
+ if (typeof av === "number" && typeof bv === "number") return (av - bv) * factor;
4149
+ return String(av).localeCompare(String(bv)) * factor;
4150
+ });
4151
+ }, [
4152
+ visibleRows,
4153
+ resolvedSort,
4154
+ columns
4155
+ ]);
4156
+ const rowIds = React.useMemo(()=>sortedData.map((row)=>row.id), [
4157
+ sortedData
4158
+ ]);
4159
+ const columnKeys = React.useMemo(()=>columns.map((column)=>column.key), [
4160
+ columns
4161
+ ]);
4162
+ const searchable = React.useMemo(()=>columns.some((column)=>column.searchable), [
4163
+ columns
4164
+ ]);
4165
+ const showToolbar = searchable || showCount;
4166
+ const showFooter = columns.some((column)=>column.summary);
4167
+ // crisp's own `totalCols` (`datatable.tsx`) -- every full-width spanning row (the
4168
+ // loading/error/empty body, infinite scroll's sentinel, the add-row footer) reads
4169
+ // this ONE value rather than each re-deriving it, so they can never drift apart.
4170
+ const totalCols = columns.length + (selectable ? 1 : 0);
4171
+ return /*#__PURE__*/ jsx(ClassNamesProvider, {
4172
+ value: classNames,
4173
+ children: /*#__PURE__*/ jsxs(DataTableRoot, {
4174
+ ref: ref,
4175
+ className: clsx(classNames.root, className),
4176
+ rowIds: rowIds,
4177
+ selectable: selectable,
4178
+ selectedIds: selectedIds,
4179
+ defaultSelectedIds: defaultSelectedIds,
4180
+ onSelectionChange: onSelectionChange,
4181
+ sort: resolvedSort,
4182
+ onSortChange: handleSortChange,
4183
+ query: resolvedQuery,
4184
+ onQueryChange: handleQueryChange,
4185
+ filters: resolvedFilters,
4186
+ onFiltersChange: handleFiltersChange,
4187
+ columnKeys: columnKeys,
4188
+ columnOrder: columnOrder,
4189
+ defaultColumnOrder: defaultColumnOrder,
4190
+ onColumnOrderChange: onColumnOrderChange,
4191
+ hiddenColumnKeys: hiddenColumnKeys,
4192
+ defaultHiddenColumnKeys: defaultHiddenColumnKeys,
4193
+ onHiddenColumnKeysChange: onHiddenColumnKeysChange,
4194
+ columnWidths: columnWidths,
4195
+ defaultColumnWidths: defaultColumnWidths,
4196
+ onColumnWidthsChange: onColumnWidthsChange,
4197
+ columnLabels: columnLabels,
4198
+ defaultColumnLabels: defaultColumnLabels,
4199
+ onColumnLabelsChange: onColumnLabelsChange,
4200
+ columnCalcOps: columnCalcOps,
4201
+ defaultColumnCalcOps: defaultColumnCalcOps,
4202
+ onColumnCalcOpsChange: onColumnCalcOpsChange,
4203
+ onEdit: onEdit,
4204
+ onLoadMore: onLoadMore,
4205
+ hasMore: hasMore,
4206
+ loadingMore: loadingMore,
4207
+ ...restProps,
4208
+ children: [
4209
+ onColumnsChange && /*#__PURE__*/ jsx(ColumnsChangeEffect, {
4210
+ columns: columns,
4211
+ onColumnsChange: onColumnsChange
4212
+ }),
4213
+ showToolbar && /*#__PURE__*/ jsxs(DataTableToolbar, {
4214
+ children: [
4215
+ searchable && /*#__PURE__*/ jsx(SearchBox, {
4216
+ placeholder: searchPlaceholder,
4217
+ label: searchLabel
4218
+ }),
4219
+ showCount && /*#__PURE__*/ jsx(RowCount, {
4220
+ shown: sortedData.length,
4221
+ total: data.length,
4222
+ countLabel: countLabel
4223
+ })
4224
+ ]
4225
+ }),
4226
+ /*#__PURE__*/ jsx(DataTableScrollBody, {
4227
+ scrollProps: scrollProps,
4228
+ children: /*#__PURE__*/ jsxs(DataTableTable, {
4229
+ children: [
4230
+ /*#__PURE__*/ jsx(DataTableColGroup, {
4231
+ columns: columns,
4232
+ selectable: !!selectable
4233
+ }),
4234
+ /*#__PURE__*/ jsxs(DataTableHeader, {
4235
+ children: [
4236
+ selectable && /*#__PURE__*/ jsx(DataTableSelectionHeaderCell, {
4237
+ ...stickyProps({
4238
+ selectable,
4239
+ stickyFirst,
4240
+ stickyLast,
4241
+ isSelectionCell: true,
4242
+ isFirst: false,
4243
+ isLast: false
4244
+ }),
4245
+ children: /*#__PURE__*/ jsx(SelectAllCheckbox, {
4246
+ label: selectAllLabel,
4247
+ checkedIcon: selectionCheckedIcon ?? DEFAULT_CHECKED_ICON,
4248
+ indeterminateIcon: selectionIndeterminateIcon ?? DEFAULT_INDETERMINATE_ICON
4249
+ })
4250
+ }),
4251
+ /*#__PURE__*/ jsx(ColumnHeaderCells, {
4252
+ columns: columns,
4253
+ columnMenu: columnMenu,
4254
+ reorderable: reorderable,
4255
+ resizable: resizable,
4256
+ sortIcon: sortIcon,
4257
+ selectable: !!selectable,
4258
+ stickyFirst: stickyFirst,
4259
+ stickyLast: stickyLast
4260
+ })
4261
+ ]
4262
+ }),
4263
+ /*#__PURE__*/ jsx(DataTableBody, {
4264
+ children: loading ? /*#__PURE__*/ jsx("tr", {
4265
+ children: /*#__PURE__*/ jsx(DataTableCell, {
4266
+ colSpan: totalCols,
4267
+ children: /*#__PURE__*/ jsx(LoadingStateContent, {
4268
+ label: loadingLabel,
4269
+ classNames: classNames
4270
+ })
4271
+ })
4272
+ }) : error ? /*#__PURE__*/ jsx("tr", {
4273
+ children: /*#__PURE__*/ jsx(DataTableCell, {
4274
+ colSpan: totalCols,
4275
+ children: /*#__PURE__*/ jsx(EmptyStateContent, {
4276
+ classNames: classNames,
4277
+ children: error
4278
+ })
4279
+ })
4280
+ }) : sortedData.length === 0 ? /*#__PURE__*/ jsx("tr", {
4281
+ children: /*#__PURE__*/ jsx(DataTableCell, {
4282
+ colSpan: totalCols,
4283
+ children: /*#__PURE__*/ jsx(EmptyStateContent, {
4284
+ classNames: classNames,
4285
+ children: empty
4286
+ })
4287
+ })
4288
+ }) : /*#__PURE__*/ jsxs(Fragment, {
4289
+ children: [
4290
+ sortedData.map((row)=>/*#__PURE__*/ jsxs(DataTableRow, {
4291
+ rowId: row.id,
4292
+ "data-clickable": onRowClick ? "" : undefined,
4293
+ onClick: onRowClick ? ()=>onRowClick(row) : undefined,
4294
+ children: [
4295
+ selectable && /*#__PURE__*/ jsx(DataTableSelectionCell, {
4296
+ ...stickyProps({
4297
+ selectable,
4298
+ stickyFirst,
4299
+ stickyLast,
4300
+ isSelectionCell: true,
4301
+ isFirst: false,
4302
+ isLast: false
4303
+ }),
4304
+ children: /*#__PURE__*/ jsx(RowCheckbox, {
4305
+ label: getRowSelectionLabel(row),
4306
+ checkedIcon: selectionCheckedIcon ?? DEFAULT_CHECKED_ICON
4307
+ })
4308
+ }),
4309
+ /*#__PURE__*/ jsx(ColumnBodyCells, {
4310
+ columns: columns,
4311
+ row: row,
4312
+ classNames: classNames,
4313
+ locale: locale,
4314
+ selectable: !!selectable,
4315
+ stickyFirst: stickyFirst,
4316
+ stickyLast: stickyLast
4317
+ })
4318
+ ]
4319
+ }, row.id)),
4320
+ onLoadMore && /*#__PURE__*/ jsx(InfiniteScrollRow, {
4321
+ colSpan: totalCols,
4322
+ classNames: classNames,
4323
+ loadingLabel: loadingLabel
4324
+ })
4325
+ ]
4326
+ })
4327
+ }),
4328
+ showFooter && /*#__PURE__*/ jsxs(DataTableFooter, {
4329
+ children: [
4330
+ selectable && // The 40px-wide selection SPACER cell is now a plain `DataTableFooterCell`
4331
+ // (not the `selectionCell` slot) -- the `<colgroup>`'s own 40px `<col>`
4332
+ // is what makes every row's selection column line up now, not a matching
4333
+ // per-slot width formula, so there is no more reason for this cell to
4334
+ // borrow a different slot than its footer siblings.
4335
+ /*#__PURE__*/ jsx(DataTableFooterCell, {
4336
+ ...stickyProps({
4337
+ selectable,
4338
+ stickyFirst,
4339
+ stickyLast,
4340
+ isSelectionCell: true,
4341
+ isFirst: false,
4342
+ isLast: false
4343
+ })
4344
+ }),
4345
+ /*#__PURE__*/ jsx(ColumnCalcCells, {
4346
+ columns: columns,
4347
+ rows: sortedData,
4348
+ selectable: !!selectable,
4349
+ stickyFirst: stickyFirst,
4350
+ stickyLast: stickyLast
4351
+ })
4352
+ ]
4353
+ }),
4354
+ onAddRow && /*#__PURE__*/ jsx(AddRowFooter, {
4355
+ onAddRow: onAddRow,
4356
+ addRowLabel: addRowLabel,
4357
+ colSpan: totalCols,
4358
+ classNames: classNames
4359
+ })
4360
+ ]
4361
+ })
4362
+ })
4363
+ ]
4364
+ })
4365
+ });
4366
+ }
4367
+ DataTableInner.displayName = "DataTable";
4368
+ /**
4369
+ * @see https://seed-design.io/react/components/data-table
4370
+ */ const DataTable = /*#__PURE__*/ forwardRef(DataTableInner);
4371
+ ////////////////////////////////////////////////////////////////////////////////////
4372
+ /** Fires `onColumnsChange` with the effective (ordered, visible, relabelled) column
4373
+ * list whenever it actually changes -- a thin convenience over the four controllable
4374
+ * pairs (order/hidden/width/label), for a consumer that would rather persist "the
4375
+ * current column configuration" as one blob. A standalone component (not inlined in
4376
+ * `DataTableInner`) purely so it can call `useDataTableContext()` -- that context is
4377
+ * provided by the `DataTablePrimitiveRoot` this renders as a SIBLING of, one level
4378
+ * up from where `DataTableInner` itself sits. */ function ColumnsChangeEffect({ columns, onColumnsChange }) {
4379
+ const effective = useEffectiveColumns(columns);
4380
+ // A NEW array every time `effective` itself is a new array (its own `useMemo`
4381
+ // recomputes only when order/hidden/label/width actually change) -- `header`
4382
+ // resolves to the menu's rename override (falling back to the column's own static
4383
+ // `header`) and `width` resolves to the live resize/consumer-set width (falling
4384
+ // back to the column's own initial one), so this is genuinely "what's showing now",
4385
+ // not the original static `columns` prop echoed back unchanged.
4386
+ const resolvedColumns = React.useMemo(()=>effective.map(({ column, label, width })=>({
4387
+ ...column,
4388
+ header: label,
4389
+ width
4390
+ })), [
4391
+ effective
4392
+ ]);
4393
+ React.useEffect(()=>{
4394
+ onColumnsChange(resolvedColumns);
4395
+ }, [
4396
+ resolvedColumns,
4397
+ onColumnsChange
4398
+ ]);
4399
+ return null;
4400
+ }
4401
+ ////////////////////////////////////////////////////////////////////////////////////
4402
+ // The real `<table>` -- see react-headless's own `DataTableTable` comment for why this
4403
+ // is a SIBLING of the toolbar (both inside `DataTablePrimitiveRoot`'s card `<div>`)
4404
+ // rather than `DataTablePrimitiveRoot` itself: a `<table>` cannot host the toolbar as a
4405
+ // child without the HTML parser foster-parenting it back out.
4406
+ const DataTableTable = withContext(DataTableTable$1, "table");
4407
+ /** One `<col>` per rendered column (plus the 40px selection column, if any) -- the
4408
+ * SINGLE place a column's width is ever set now. Every row's header/body/footer cell
4409
+ * reads its own width off this shared `<colgroup>` by construction (native table
4410
+ * column-sizing), which is what makes them align -- no per-cell width style to drift
4411
+ * out of sync between the three. A column with no explicit width gets a bare `<col>`
4412
+ * (no `style`): `table-layout: fixed`'s own algorithm gives every such column an equal
4413
+ * share of whatever space is left, matching the old flex grid's `flex: 1 1 0%`
4414
+ * default with no extra code needed here. */ function DataTableColGroup({ columns, selectable }) {
4415
+ const effective = useEffectiveColumns(columns);
4416
+ return /*#__PURE__*/ jsxs("colgroup", {
4417
+ children: [
4418
+ selectable && /*#__PURE__*/ jsx("col", {
4419
+ style: {
4420
+ width: 40
4421
+ }
4422
+ }),
4423
+ effective.map(({ column, width })=>/*#__PURE__*/ jsx("col", {
4424
+ style: width === undefined ? undefined : {
4425
+ width
4426
+ }
4427
+ }, column.key))
4428
+ ]
4429
+ });
4430
+ }
4431
+ const DataTableHeader = withContext(DataTableHeader$1, "header");
4432
+ const DataTableHeaderCell = withContext(DataTableHeaderCell$1, "headerCell");
4433
+ // `.crisp-datatable-th-inner` -- the flex row INSIDE a `<th>` (label + filter trigger +
4434
+ // sort chevron + column menu). See qvism-preset's own `headerCellInner` slot comment
4435
+ // for why this lives one level below the cell itself rather than on it.
4436
+ const DataTableHeaderCellInner = withContext(Primitive.div, "headerCellInner");
4437
+ /** The label text specifically -- the one thing in `DataTableHeaderCellInner` that
4438
+ * truncates; the trailing filter/sort/menu icons stay fully visible regardless of how
4439
+ * long a column's own header text is. */ const DataTableHeaderCellLabel = withContext(Primitive.span, "headerCellLabel");
4440
+ /**
4441
+ * A selection `<th>`/`<td>`/footer `<td>` composes ITS base slot (`headerCell`/`cell`/
4442
+ * `footerCell`) together with the `selectionCell` MODIFIER, rather than replacing it --
4443
+ * so the checkbox column still inherits every shared table-wide rule (sticky header,
4444
+ * sticky footer, height, borders) for free instead of a second, independent copy of
4445
+ * them under a slot of its own. `useClassNames()` reads the SAME classNames object
4446
+ * `ClassNamesProvider` (below, in `DataTableInner`) already put in context; this isn't
4447
+ * a second recipe call.
4448
+ */ function withSelectionCell(Component, baseSlot) {
4449
+ const StyledComponent = /*#__PURE__*/ forwardRef((props, ref)=>{
4450
+ const classNames = useClassNames();
4451
+ return /*#__PURE__*/ jsx(Component, {
4452
+ ref: ref,
4453
+ ...props,
4454
+ className: clsx(classNames[baseSlot], classNames.selectionCell, props["className"])
4455
+ });
4456
+ });
4457
+ StyledComponent.displayName = `SelectionCell(${baseSlot})`;
4458
+ return StyledComponent;
4459
+ }
4460
+ const DataTableSelectionHeaderCell = withSelectionCell(DataTableHeaderCell$1, "headerCell");
4461
+ const DataTableHeaderCellIcon = withContext(Primitive.span, "headerCellIcon");
4462
+ // body has no CSS slot of its own in the rootage spec -- it is a plain structural
4463
+ // `<tbody>`, so it is not wrapped in `withContext`.
4464
+ const DataTableBody = DataTableBody$1;
4465
+ const DataTableRow = withContext(DataTableRow$1, "row");
4466
+ const DataTableCell = withContext(DataTableCell$1, "cell");
4467
+ const DataTableSelectionCell = withSelectionCell(DataTableCell$1, "cell");
4468
+ // Tranche 4 -- column menu + reorder + resize + sticky columns. A scroll boundary
4469
+ // wrapping the whole `<table>`: `overflowX: auto` is what makes a wide table (more
4470
+ // column width than the card is wide) scroll horizontally at all, which sticky columns
4471
+ // and their scroll shadow (this slot's own `data-scrolled-start`/`-end`, set from
4472
+ // `useDataTableScroll`) need something concrete to measure and listen to. `overflowY`
4473
+ // stays out of this slot's own base css (nothing here has a bounded height of its own
4474
+ // to clip), so it never introduces a second vertical scrollbar next to whatever
4475
+ // ambient container the whole DataTable already sits in.
4476
+ const DataTableScrollBodyBase = withContext(Primitive.div, "scrollArea");
4477
+ function DataTableScrollBody({ children, scrollProps }) {
4478
+ const { scrollAreaRef, scrolledStart, scrolledEnd, onScroll } = useDataTableContext();
4479
+ const { className: scrollClassName, style: scrollStyle, ...otherScrollProps } = scrollProps ?? {};
4480
+ return /*#__PURE__*/ jsx(DataTableScrollBodyBase, {
4481
+ ...otherScrollProps,
4482
+ ref: scrollAreaRef,
4483
+ onScroll: onScroll,
4484
+ className: scrollClassName,
4485
+ style: scrollStyle,
4486
+ "data-scrolled-start": dataAttr(scrolledStart),
4487
+ "data-scrolled-end": dataAttr(scrolledEnd),
4488
+ children: children
4489
+ });
4490
+ }
4491
+ const DataTableFooterBase = withContext(Primitive.tfoot, "footer");
4492
+ function DataTableFooter({ children, ...props }) {
4493
+ return /*#__PURE__*/ jsx(DataTableFooterBase, {
4494
+ ...props,
4495
+ children: /*#__PURE__*/ jsx("tr", {
4496
+ children: children
4497
+ })
4498
+ });
4499
+ }
4500
+ const DataTableFooterCell = withContext(Primitive.td, "footerCell");
4501
+ ////////////////////////////////////////////////////////////////////////////////////
4502
+ // Fix (bug report): karrot icons default to a 24px intrinsic SVG attribute size
4503
+ // -- see column-menu.tsx's own `SORT_ICON_SIZE` comment for the full mechanism.
4504
+ // The selection checkbox's own indicator is the smallest glyph in the grid
4505
+ // (crisp's own measured checkmark), so it gets the smallest explicit size.
4506
+ const SELECTION_ICON_SIZE = 12;
4507
+ const DEFAULT_CHECKED_ICON = /*#__PURE__*/ jsx(IconCheckmarkLine_default, {
4508
+ size: SELECTION_ICON_SIZE
4509
+ });
4510
+ const DEFAULT_INDETERMINATE_ICON = /*#__PURE__*/ jsx(IconMinusLine_default, {
4511
+ size: SELECTION_ICON_SIZE
4512
+ });
4513
+ function SelectAllCheckbox({ label, checkedIcon, indeterminateIcon }) {
4514
+ const { selectionState, toggleAll } = useDataTableContext();
4515
+ return /*#__PURE__*/ jsxs(CheckboxRoot, {
4516
+ checked: selectionState === "all",
4517
+ indeterminate: selectionState === "some",
4518
+ onCheckedChange: toggleAll,
4519
+ children: [
4520
+ /*#__PURE__*/ jsx(CheckboxControl, {
4521
+ children: /*#__PURE__*/ jsx(CheckboxIndicator, {
4522
+ checked: checkedIcon ?? null,
4523
+ indeterminate: indeterminateIcon
4524
+ })
4525
+ }),
4526
+ /*#__PURE__*/ jsx(CheckboxHiddenInput, {
4527
+ "aria-label": label
4528
+ })
4529
+ ]
4530
+ });
4531
+ }
4532
+ /**
4533
+ * Reads its OWN row's selected state off `useDataTableRowContext` (set by the
4534
+ * `DataTableRow` it renders inside) rather than being handed `rowId` again as a prop
4535
+ * and looking it up in the table-wide context -- the same shape Select's
4536
+ * `SelectItemLabel` reads `label` off `useSelectItemContext` instead of a prop
4537
+ * re-threaded down from `SelectItem`.
4538
+ */ function RowCheckbox({ label, checkedIcon }) {
4539
+ const { isSelected, toggleRow } = useDataTableRowContext();
4540
+ return /*#__PURE__*/ jsxs(CheckboxRoot, {
4541
+ checked: isSelected,
4542
+ onCheckedChange: toggleRow,
4543
+ children: [
4544
+ /*#__PURE__*/ jsx(CheckboxControl, {
4545
+ children: /*#__PURE__*/ jsx(CheckboxIndicator, {
4546
+ checked: checkedIcon ?? null
4547
+ })
4548
+ }),
4549
+ /*#__PURE__*/ jsx(CheckboxHiddenInput, {
4550
+ "aria-label": label
4551
+ })
4552
+ ]
4553
+ });
4554
+ }
4555
+
4556
+ export { DataTable };