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