@tensor_1/react 2.11.0 → 2.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -1
- package/dist/ChartCore-Bt1C84ty.d.cts +22 -0
- package/dist/chart-preset.cjs +168 -0
- package/dist/chart-preset.d.cts +9 -0
- package/dist/chart-preset.d.ts +9 -0
- package/dist/chart-preset.js +35 -0
- package/dist/chart.cjs +58 -32
- package/dist/chart.d.cts +6 -21
- package/dist/chart.d.ts +2 -1
- package/dist/components/Chart.d.ts +6 -21
- package/dist/components/Chart.js +5 -76
- package/dist/components/ChartCore.d.ts +33 -0
- package/dist/components/ChartCore.js +100 -0
- package/dist/components/Inbox.js +0 -1
- package/dist/components/Meter.js +0 -1
- package/dist/components/NavFavourites.js +35 -108
- package/dist/components/Segmented.js +3 -2
- package/dist/components/Table.d.ts +1 -1
- package/dist/components/Table.js +4 -3
- package/dist/components/Tabs.js +3 -2
- package/dist/hooks/useTable.js +11 -7
- package/dist/hooks/useVirtualRows.d.ts +27 -0
- package/dist/hooks/useVirtualRows.js +50 -0
- package/dist/index.cjs +177 -192
- package/dist/index.d.cts +26 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -1
- package/dist/table-engine.js +8 -6
- package/package.json +16 -2
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useId, useRef } from "react";
|
|
4
|
+
import { cx } from "../cx.js";
|
|
5
|
+
function readToken(name) {
|
|
6
|
+
if (typeof getComputedStyle === "undefined") return "";
|
|
7
|
+
return getComputedStyle(document.documentElement).getPropertyValue(name).trim();
|
|
8
|
+
}
|
|
9
|
+
function uixChartPalette() {
|
|
10
|
+
return [1, 2, 3, 4, 5, 6, 7, 8].map((i) => readToken(`--uix-chart-${i}`));
|
|
11
|
+
}
|
|
12
|
+
function ChartCore({
|
|
13
|
+
engine,
|
|
14
|
+
option,
|
|
15
|
+
title,
|
|
16
|
+
subtitle,
|
|
17
|
+
height = 280,
|
|
18
|
+
className,
|
|
19
|
+
style,
|
|
20
|
+
ariaLabel,
|
|
21
|
+
tableData,
|
|
22
|
+
tableHeaders,
|
|
23
|
+
onReady,
|
|
24
|
+
header
|
|
25
|
+
}) {
|
|
26
|
+
const containerRef = useRef(null);
|
|
27
|
+
const chartRef = useRef(null);
|
|
28
|
+
const appliedOptionRef = useRef(null);
|
|
29
|
+
const latestOptionRef = useRef(option);
|
|
30
|
+
const onReadyRef = useRef(onReady);
|
|
31
|
+
const tableId = useId();
|
|
32
|
+
latestOptionRef.current = option;
|
|
33
|
+
onReadyRef.current = onReady;
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
const element = containerRef.current;
|
|
36
|
+
if (!element) return;
|
|
37
|
+
const chart = engine.init(element, null, { renderer: "svg" });
|
|
38
|
+
chartRef.current = chart;
|
|
39
|
+
chart.setOption(latestOptionRef.current);
|
|
40
|
+
appliedOptionRef.current = latestOptionRef.current;
|
|
41
|
+
onReadyRef.current?.(chart);
|
|
42
|
+
let frame = 0;
|
|
43
|
+
let width = element.clientWidth;
|
|
44
|
+
let height2 = element.clientHeight;
|
|
45
|
+
const observer = new ResizeObserver(() => {
|
|
46
|
+
const nextWidth = element.clientWidth;
|
|
47
|
+
const nextHeight = element.clientHeight;
|
|
48
|
+
if (nextWidth === width && nextHeight === height2) return;
|
|
49
|
+
width = nextWidth;
|
|
50
|
+
height2 = nextHeight;
|
|
51
|
+
if (frame) return;
|
|
52
|
+
frame = requestAnimationFrame(() => {
|
|
53
|
+
frame = 0;
|
|
54
|
+
chart.resize();
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
observer.observe(element);
|
|
58
|
+
return () => {
|
|
59
|
+
observer.disconnect();
|
|
60
|
+
if (frame) cancelAnimationFrame(frame);
|
|
61
|
+
chart.dispose();
|
|
62
|
+
chartRef.current = null;
|
|
63
|
+
appliedOptionRef.current = null;
|
|
64
|
+
};
|
|
65
|
+
}, [engine]);
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
const chart = chartRef.current;
|
|
68
|
+
if (!chart || appliedOptionRef.current === option) return;
|
|
69
|
+
chart.setOption(option, { notMerge: false });
|
|
70
|
+
appliedOptionRef.current = option;
|
|
71
|
+
}, [option]);
|
|
72
|
+
const heightValue = typeof height === "number" ? `${height}px` : height;
|
|
73
|
+
const effectiveAriaLabel = ariaLabel ?? title ?? "Chart";
|
|
74
|
+
const hasTable = Boolean(tableData?.length && tableHeaders?.length);
|
|
75
|
+
return /* @__PURE__ */ jsxs("div", { className: cx("uix-chart", className), style, children: [
|
|
76
|
+
title != null || subtitle != null || header != null ? /* @__PURE__ */ jsxs("div", { children: [
|
|
77
|
+
header,
|
|
78
|
+
title ? /* @__PURE__ */ jsx("div", { className: "uix-chart__title", children: title }) : null,
|
|
79
|
+
subtitle ? /* @__PURE__ */ jsx("div", { className: "uix-chart__subtitle", children: subtitle }) : null
|
|
80
|
+
] }) : null,
|
|
81
|
+
/* @__PURE__ */ jsx(
|
|
82
|
+
"div",
|
|
83
|
+
{
|
|
84
|
+
ref: containerRef,
|
|
85
|
+
style: { height: heightValue },
|
|
86
|
+
role: "img",
|
|
87
|
+
"aria-label": effectiveAriaLabel,
|
|
88
|
+
"aria-describedby": hasTable ? tableId : void 0
|
|
89
|
+
}
|
|
90
|
+
),
|
|
91
|
+
hasTable ? /* @__PURE__ */ jsxs("table", { id: tableId, className: "sr-only", "aria-label": `${effectiveAriaLabel} \u2014 data table`, children: [
|
|
92
|
+
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { children: tableHeaders.map((label) => /* @__PURE__ */ jsx("th", { scope: "col", children: label }, label)) }) }),
|
|
93
|
+
/* @__PURE__ */ jsx("tbody", { children: tableData.map((row, index) => /* @__PURE__ */ jsx("tr", { children: tableHeaders.map((label) => /* @__PURE__ */ jsx("td", { children: row[label] }, label)) }, index)) })
|
|
94
|
+
] }) : null
|
|
95
|
+
] });
|
|
96
|
+
}
|
|
97
|
+
export {
|
|
98
|
+
ChartCore,
|
|
99
|
+
uixChartPalette
|
|
100
|
+
};
|
package/dist/components/Inbox.js
CHANGED
package/dist/components/Meter.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useEffect, useId, useRef, useState } from "react";
|
|
3
|
+
import { memo, useCallback, useEffect, useId, useMemo, useRef, useState } from "react";
|
|
4
4
|
import { cx } from "../cx.js";
|
|
5
5
|
const ChevronDownIcon = () => /* @__PURE__ */ jsx(
|
|
6
6
|
"svg",
|
|
@@ -14,7 +14,7 @@ const ChevronDownIcon = () => /* @__PURE__ */ jsx(
|
|
|
14
14
|
strokeLinecap: "round",
|
|
15
15
|
strokeLinejoin: "round",
|
|
16
16
|
"aria-hidden": "true",
|
|
17
|
-
|
|
17
|
+
className: "uix-favourites__chevron",
|
|
18
18
|
children: /* @__PURE__ */ jsx("path", { d: "m6 9 6 6 6-6" })
|
|
19
19
|
}
|
|
20
20
|
);
|
|
@@ -30,7 +30,7 @@ const ChevronRightIcon = () => /* @__PURE__ */ jsx(
|
|
|
30
30
|
strokeLinecap: "round",
|
|
31
31
|
strokeLinejoin: "round",
|
|
32
32
|
"aria-hidden": "true",
|
|
33
|
-
|
|
33
|
+
className: "uix-favourites__chevron",
|
|
34
34
|
children: /* @__PURE__ */ jsx("path", { d: "m9 18 6-6-6-6" })
|
|
35
35
|
}
|
|
36
36
|
);
|
|
@@ -53,7 +53,6 @@ const MoreIcon = () => /* @__PURE__ */ jsxs(
|
|
|
53
53
|
]
|
|
54
54
|
}
|
|
55
55
|
);
|
|
56
|
-
const MUTED = "color-mix(in srgb, var(--uix-text) 50%, transparent)";
|
|
57
56
|
function NavFavourites({
|
|
58
57
|
items,
|
|
59
58
|
collapsed = false,
|
|
@@ -69,7 +68,7 @@ function NavFavourites({
|
|
|
69
68
|
const regionRef = useRef(null);
|
|
70
69
|
const focusAfterRef = useRef(null);
|
|
71
70
|
const triggerRefs = useRef(/* @__PURE__ */ new Map());
|
|
72
|
-
const order = items.map((
|
|
71
|
+
const order = useMemo(() => items.map((item) => item.id), [items]);
|
|
73
72
|
useEffect(() => {
|
|
74
73
|
const id = focusAfterRef.current;
|
|
75
74
|
if (!id) return;
|
|
@@ -77,7 +76,7 @@ function NavFavourites({
|
|
|
77
76
|
const el = triggerRefs.current.get(id);
|
|
78
77
|
if (el) el.focus();
|
|
79
78
|
else headerRef.current?.focus();
|
|
80
|
-
}, [order
|
|
79
|
+
}, [order]);
|
|
81
80
|
const handleToggle = () => {
|
|
82
81
|
if (!collapsed) {
|
|
83
82
|
const active = document.activeElement;
|
|
@@ -87,7 +86,7 @@ function NavFavourites({
|
|
|
87
86
|
}
|
|
88
87
|
onToggleCollapsed?.();
|
|
89
88
|
};
|
|
90
|
-
const move = (id, dir) => {
|
|
89
|
+
const move = useCallback((id, dir) => {
|
|
91
90
|
const idx = order.indexOf(id);
|
|
92
91
|
const next = idx + dir;
|
|
93
92
|
if (idx < 0 || next < 0 || next >= order.length) return;
|
|
@@ -97,14 +96,18 @@ function NavFavourites({
|
|
|
97
96
|
reordered.splice(next, 0, it);
|
|
98
97
|
focusAfterRef.current = id;
|
|
99
98
|
onReorder(reordered);
|
|
100
|
-
};
|
|
101
|
-
const remove = (id) => {
|
|
99
|
+
}, [onReorder, order]);
|
|
100
|
+
const remove = useCallback((id) => {
|
|
102
101
|
const idx = order.indexOf(id);
|
|
103
102
|
const neighbour = order[idx + 1] ?? order[idx - 1] ?? null;
|
|
104
103
|
focusAfterRef.current = neighbour;
|
|
105
104
|
onRemove(id);
|
|
106
|
-
};
|
|
107
|
-
|
|
105
|
+
}, [onRemove, order]);
|
|
106
|
+
const registerTrigger = useCallback((id, element) => {
|
|
107
|
+
if (element) triggerRefs.current.set(id, element);
|
|
108
|
+
else triggerRefs.current.delete(id);
|
|
109
|
+
}, []);
|
|
110
|
+
return /* @__PURE__ */ jsxs("div", { className: cx("uix-favourites", className), children: [
|
|
108
111
|
/* @__PURE__ */ jsxs(
|
|
109
112
|
"button",
|
|
110
113
|
{
|
|
@@ -113,18 +116,7 @@ function NavFavourites({
|
|
|
113
116
|
"aria-expanded": !collapsed,
|
|
114
117
|
"aria-controls": regionId,
|
|
115
118
|
onClick: handleToggle,
|
|
116
|
-
className: "uix-text-eyebrow",
|
|
117
|
-
style: {
|
|
118
|
-
display: "flex",
|
|
119
|
-
width: "100%",
|
|
120
|
-
alignItems: "center",
|
|
121
|
-
gap: "6px",
|
|
122
|
-
padding: "8px 4px 4px",
|
|
123
|
-
border: 0,
|
|
124
|
-
background: "transparent",
|
|
125
|
-
cursor: "pointer",
|
|
126
|
-
color: "inherit"
|
|
127
|
-
},
|
|
119
|
+
className: "uix-favourites__heading uix-text-eyebrow",
|
|
128
120
|
children: [
|
|
129
121
|
collapsed ? /* @__PURE__ */ jsx(ChevronRightIcon, {}) : /* @__PURE__ */ jsx(ChevronDownIcon, {}),
|
|
130
122
|
labels.heading
|
|
@@ -137,20 +129,12 @@ function NavFavourites({
|
|
|
137
129
|
ref: regionRef,
|
|
138
130
|
id: regionId,
|
|
139
131
|
hidden: collapsed,
|
|
140
|
-
|
|
141
|
-
display: "flex",
|
|
142
|
-
flexDirection: "column",
|
|
143
|
-
gap: "1px",
|
|
144
|
-
listStyle: "none",
|
|
145
|
-
margin: 0,
|
|
146
|
-
padding: 0
|
|
147
|
-
},
|
|
132
|
+
className: "uix-favourites__list",
|
|
148
133
|
children: items.length === 0 ? /* @__PURE__ */ jsx(
|
|
149
134
|
"li",
|
|
150
135
|
{
|
|
151
|
-
className: "uix-text-meta",
|
|
136
|
+
className: "uix-favourites__empty uix-text-meta",
|
|
152
137
|
"data-empty-reason": emptyReason,
|
|
153
|
-
style: { padding: "6px 8px", color: MUTED },
|
|
154
138
|
children: labels.empty
|
|
155
139
|
}
|
|
156
140
|
) : items.map((item, idx) => /* @__PURE__ */ jsx(
|
|
@@ -160,10 +144,9 @@ function NavFavourites({
|
|
|
160
144
|
isFirst: idx === 0,
|
|
161
145
|
isLast: idx === items.length - 1,
|
|
162
146
|
labels,
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
registerTrigger: (el) => triggerRefs.current.set(item.id, el)
|
|
147
|
+
onMove: move,
|
|
148
|
+
onRemove: remove,
|
|
149
|
+
registerTrigger
|
|
167
150
|
},
|
|
168
151
|
item.id
|
|
169
152
|
))
|
|
@@ -171,26 +154,24 @@ function NavFavourites({
|
|
|
171
154
|
)
|
|
172
155
|
] });
|
|
173
156
|
}
|
|
174
|
-
|
|
157
|
+
const FavouriteRow = memo(function FavouriteRow2({
|
|
175
158
|
item,
|
|
176
159
|
isFirst,
|
|
177
160
|
isLast,
|
|
178
161
|
labels,
|
|
179
|
-
|
|
180
|
-
onMoveDown,
|
|
162
|
+
onMove,
|
|
181
163
|
onRemove,
|
|
182
164
|
registerTrigger
|
|
183
165
|
}) {
|
|
184
166
|
const [open, setOpen] = useState(false);
|
|
185
|
-
const [revealed, setRevealed] = useState(false);
|
|
186
167
|
const menuId = useId();
|
|
187
168
|
const triggerRef = useRef(null);
|
|
188
169
|
const itemRefs = useRef([]);
|
|
189
170
|
const optionsTemplate = labels.options ?? "{label} favourite options";
|
|
190
171
|
const actions = [
|
|
191
|
-
...isFirst ? [] : [{ label: labels.moveUp, onSelect:
|
|
192
|
-
...isLast ? [] : [{ label: labels.moveDown, onSelect:
|
|
193
|
-
{ label: labels.remove.replace("{label}", item.label), onSelect: onRemove }
|
|
172
|
+
...isFirst ? [] : [{ label: labels.moveUp, onSelect: () => onMove(item.id, -1) }],
|
|
173
|
+
...isLast ? [] : [{ label: labels.moveDown, onSelect: () => onMove(item.id, 1) }],
|
|
174
|
+
{ label: labels.remove.replace("{label}", item.label), onSelect: () => onRemove(item.id) }
|
|
194
175
|
];
|
|
195
176
|
useEffect(() => {
|
|
196
177
|
if (open) itemRefs.current[0]?.focus();
|
|
@@ -219,34 +200,27 @@ function FavouriteRow({
|
|
|
219
200
|
if (!e.altKey) return;
|
|
220
201
|
if (e.key === "ArrowUp" && !isFirst) {
|
|
221
202
|
e.preventDefault();
|
|
222
|
-
|
|
203
|
+
onMove(item.id, -1);
|
|
223
204
|
} else if (e.key === "ArrowDown" && !isLast) {
|
|
224
205
|
e.preventDefault();
|
|
225
|
-
|
|
206
|
+
onMove(item.id, 1);
|
|
226
207
|
}
|
|
227
208
|
};
|
|
228
|
-
const triggerVisible = revealed || open;
|
|
229
209
|
return (
|
|
230
210
|
// biome-ignore lint/a11y/noStaticElementInteractions: keyboard accelerators on the row; the focusable controls inside carry their own semantics.
|
|
231
211
|
/* @__PURE__ */ jsxs(
|
|
232
212
|
"li",
|
|
233
213
|
{
|
|
234
|
-
|
|
214
|
+
className: "uix-favourites__row",
|
|
235
215
|
onKeyDown: onRowKeyDown,
|
|
236
|
-
onMouseEnter: () => setRevealed(true),
|
|
237
|
-
onMouseLeave: () => setRevealed(false),
|
|
238
|
-
onFocus: () => setRevealed(true),
|
|
239
|
-
onBlur: (e) => {
|
|
240
|
-
if (!e.currentTarget.contains(e.relatedTarget)) setRevealed(false);
|
|
241
|
-
},
|
|
242
216
|
children: [
|
|
243
|
-
/* @__PURE__ */ jsx("div", {
|
|
217
|
+
/* @__PURE__ */ jsx("div", { className: "uix-favourites__link", children: item.link }),
|
|
244
218
|
/* @__PURE__ */ jsx(
|
|
245
219
|
"button",
|
|
246
220
|
{
|
|
247
221
|
ref: (el) => {
|
|
248
222
|
triggerRef.current = el;
|
|
249
|
-
registerTrigger(el);
|
|
223
|
+
registerTrigger(item.id, el);
|
|
250
224
|
},
|
|
251
225
|
type: "button",
|
|
252
226
|
"aria-haspopup": "menu",
|
|
@@ -254,22 +228,7 @@ function FavouriteRow({
|
|
|
254
228
|
"aria-controls": open ? menuId : void 0,
|
|
255
229
|
"aria-label": optionsTemplate.replace("{label}", item.label),
|
|
256
230
|
onClick: () => setOpen((v) => !v),
|
|
257
|
-
|
|
258
|
-
marginLeft: "auto",
|
|
259
|
-
display: "inline-flex",
|
|
260
|
-
height: "20px",
|
|
261
|
-
width: "20px",
|
|
262
|
-
flexShrink: 0,
|
|
263
|
-
alignItems: "center",
|
|
264
|
-
justifyContent: "center",
|
|
265
|
-
border: 0,
|
|
266
|
-
borderRadius: "2px",
|
|
267
|
-
background: "transparent",
|
|
268
|
-
cursor: "pointer",
|
|
269
|
-
color: MUTED,
|
|
270
|
-
opacity: triggerVisible ? 1 : 0,
|
|
271
|
-
transition: "opacity var(--uix-dur-fast) var(--uix-ease-out)"
|
|
272
|
-
},
|
|
231
|
+
className: "uix-favourites__menu-trigger",
|
|
273
232
|
children: /* @__PURE__ */ jsx(MoreIcon, {})
|
|
274
233
|
}
|
|
275
234
|
),
|
|
@@ -280,7 +239,7 @@ function FavouriteRow({
|
|
|
280
239
|
"aria-hidden": "true",
|
|
281
240
|
role: "presentation",
|
|
282
241
|
onClick: () => setOpen(false),
|
|
283
|
-
|
|
242
|
+
className: "uix-favourites__backdrop"
|
|
284
243
|
}
|
|
285
244
|
),
|
|
286
245
|
/* @__PURE__ */ jsx(
|
|
@@ -289,21 +248,7 @@ function FavouriteRow({
|
|
|
289
248
|
id: menuId,
|
|
290
249
|
role: "menu",
|
|
291
250
|
onKeyDown: onMenuKeyDown,
|
|
292
|
-
|
|
293
|
-
position: "absolute",
|
|
294
|
-
right: 0,
|
|
295
|
-
top: "calc(100% + 2px)",
|
|
296
|
-
zIndex: 50,
|
|
297
|
-
minWidth: "160px",
|
|
298
|
-
overflow: "hidden",
|
|
299
|
-
margin: 0,
|
|
300
|
-
padding: "4px 0",
|
|
301
|
-
listStyle: "none",
|
|
302
|
-
borderRadius: "6px",
|
|
303
|
-
border: "1px solid var(--uix-border)",
|
|
304
|
-
background: "var(--uix-surface)",
|
|
305
|
-
boxShadow: "var(--uix-shadow-popover)"
|
|
306
|
-
},
|
|
251
|
+
className: "uix-favourites__menu",
|
|
307
252
|
children: actions.map((action, i) => /* @__PURE__ */ jsx("li", { role: "presentation", children: /* @__PURE__ */ jsx(
|
|
308
253
|
"button",
|
|
309
254
|
{
|
|
@@ -317,25 +262,7 @@ function FavouriteRow({
|
|
|
317
262
|
setOpen(false);
|
|
318
263
|
action.onSelect();
|
|
319
264
|
},
|
|
320
|
-
|
|
321
|
-
display: "flex",
|
|
322
|
-
width: "100%",
|
|
323
|
-
alignItems: "center",
|
|
324
|
-
padding: "6px 12px",
|
|
325
|
-
border: 0,
|
|
326
|
-
background: "transparent",
|
|
327
|
-
cursor: "pointer",
|
|
328
|
-
textAlign: "left",
|
|
329
|
-
fontSize: "13px",
|
|
330
|
-
color: "color-mix(in srgb, var(--uix-text) 85%, transparent)",
|
|
331
|
-
transition: "background-color var(--uix-dur-fast) var(--uix-ease-out)"
|
|
332
|
-
},
|
|
333
|
-
onMouseEnter: (e) => {
|
|
334
|
-
e.currentTarget.style.background = "var(--uix-bg-hover)";
|
|
335
|
-
},
|
|
336
|
-
onMouseLeave: (e) => {
|
|
337
|
-
e.currentTarget.style.background = "transparent";
|
|
338
|
-
},
|
|
265
|
+
className: "uix-favourites__menu-item",
|
|
339
266
|
children: action.label
|
|
340
267
|
}
|
|
341
268
|
) }, action.label))
|
|
@@ -346,7 +273,7 @@ function FavouriteRow({
|
|
|
346
273
|
}
|
|
347
274
|
)
|
|
348
275
|
);
|
|
349
|
-
}
|
|
276
|
+
});
|
|
350
277
|
export {
|
|
351
278
|
NavFavourites
|
|
352
279
|
};
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
|
-
import { createContext, useContext } from "react";
|
|
3
|
+
import { createContext, useContext, useMemo } from "react";
|
|
4
4
|
import { cx } from "../cx.js";
|
|
5
5
|
const SegmentedCtx = createContext({});
|
|
6
6
|
function Segmented({ value, onChange, children, className, ...props }) {
|
|
7
|
-
|
|
7
|
+
const contextValue = useMemo(() => ({ value, onChange }), [value, onChange]);
|
|
8
|
+
return /* @__PURE__ */ jsx(SegmentedCtx.Provider, { value: contextValue, children: /* @__PURE__ */ jsx("div", { role: "group", className: cx("uix-segmented", className), ...props, children }) });
|
|
8
9
|
}
|
|
9
10
|
function SegmentedOption({
|
|
10
11
|
value,
|
|
@@ -4,7 +4,7 @@ import { HTMLAttributes, ReactNode, ButtonHTMLAttributes, TdHTMLAttributes, Tabl
|
|
|
4
4
|
interface TableWrapProps extends HTMLAttributes<HTMLDivElement> {
|
|
5
5
|
children?: ReactNode;
|
|
6
6
|
}
|
|
7
|
-
declare
|
|
7
|
+
declare const TableWrap: react.ForwardRefExoticComponent<TableWrapProps & react.RefAttributes<HTMLDivElement>>;
|
|
8
8
|
type TableDensity = 'compact' | 'standard' | 'comfortable';
|
|
9
9
|
type SortDirection = 'ascending' | 'descending' | 'none';
|
|
10
10
|
interface TableProps extends TableHTMLAttributes<HTMLTableElement> {
|
package/dist/components/Table.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef } from "react";
|
|
2
3
|
import { cx } from "../cx.js";
|
|
3
4
|
import { highlightSegments } from "../table-engine.js";
|
|
4
|
-
|
|
5
|
-
return /* @__PURE__ */ jsx("div", { className: cx("uix-table-wrap", className), ...props, children });
|
|
6
|
-
}
|
|
5
|
+
const TableWrap = forwardRef(function TableWrap2({ children, className, ...props }, ref) {
|
|
6
|
+
return /* @__PURE__ */ jsx("div", { ref, className: cx("uix-table-wrap", className), ...props, children });
|
|
7
|
+
});
|
|
7
8
|
function Table({
|
|
8
9
|
density = "standard",
|
|
9
10
|
zebra = true,
|
package/dist/components/Tabs.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
|
-
import { createContext, useContext } from "react";
|
|
3
|
+
import { createContext, useContext, useMemo } from "react";
|
|
4
4
|
import { cx } from "../cx.js";
|
|
5
5
|
const TabsCtx = createContext({});
|
|
6
6
|
function Tabs({ variant = "line", value, onChange, children, className }) {
|
|
7
|
-
|
|
7
|
+
const contextValue = useMemo(() => ({ value, onChange }), [value, onChange]);
|
|
8
|
+
return /* @__PURE__ */ jsx(TabsCtx.Provider, { value: contextValue, children: /* @__PURE__ */ jsx("div", { role: "tablist", className: cx("uix-tabs", `uix-tabs--${variant}`, className), children }) });
|
|
8
9
|
}
|
|
9
10
|
function Tab({ value, children, disabled, className, onClick, ...props }) {
|
|
10
11
|
const ctx = useContext(TabsCtx);
|
package/dist/hooks/useTable.js
CHANGED
|
@@ -19,18 +19,22 @@ function useTable(data, options = {}) {
|
|
|
19
19
|
const [query, setQuery] = useState(() => options.initialQuery ?? "");
|
|
20
20
|
const [selected, setSelected] = useState(() => /* @__PURE__ */ new Set());
|
|
21
21
|
const [pinned, setPinned] = useState(() => new Set(options.initialPinned ?? []));
|
|
22
|
-
const
|
|
23
|
-
const filtered = applyFilters(data, filters);
|
|
24
|
-
const searched = searchRows(filtered, query, searchFields);
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
const visibleRows = useMemo(() => {
|
|
23
|
+
const filtered = filters.length ? applyFilters(data, filters) : data;
|
|
24
|
+
const searched = query.trim() ? searchRows(filtered, query, searchFields) : filtered;
|
|
25
|
+
return sort.length ? multiSort(searched, sort) : searched;
|
|
26
|
+
}, [data, filters, query, searchFields, sort]);
|
|
27
|
+
const rows = useMemo(
|
|
28
|
+
() => pinned.size ? mergePinned(data, visibleRows, pinned, idField) : visibleRows.slice(),
|
|
29
|
+
[data, visibleRows, pinned, idField]
|
|
30
|
+
);
|
|
28
31
|
const pageIds = useMemo(() => rows.map((r) => String(r[idField])), [rows, idField]);
|
|
29
32
|
const selectAll = selectAllState(selected, pageIds);
|
|
30
33
|
const doToggleSort = useCallback((field, additive = false) => {
|
|
31
34
|
setSort((keys) => toggleSort(keys, field, additive));
|
|
32
35
|
}, []);
|
|
33
36
|
const toggleRow = useCallback((id) => setSelected((s) => toggleId(s, id)), []);
|
|
37
|
+
const isSelected = useCallback((id) => selected.has(id), [selected]);
|
|
34
38
|
const toggleAllRows = useCallback(() => setSelected((s) => togglePage(s, pageIds)), [pageIds]);
|
|
35
39
|
const clearSelection = useCallback(() => setSelected(/* @__PURE__ */ new Set()), []);
|
|
36
40
|
const togglePin = useCallback((id) => setPinned((s) => toggleId(s, id)), []);
|
|
@@ -54,7 +58,7 @@ function useTable(data, options = {}) {
|
|
|
54
58
|
setSort,
|
|
55
59
|
setFilters,
|
|
56
60
|
setQuery,
|
|
57
|
-
isSelected
|
|
61
|
+
isSelected,
|
|
58
62
|
toggleRow,
|
|
59
63
|
toggleAllRows,
|
|
60
64
|
clearSelection,
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { RefObject } from 'react';
|
|
2
|
+
|
|
3
|
+
interface UseVirtualRowsOptions {
|
|
4
|
+
/** Fixed rendered row height in pixels. */
|
|
5
|
+
rowHeight: number;
|
|
6
|
+
/** Rows rendered beyond each viewport edge. Default: 6. */
|
|
7
|
+
overscan?: number;
|
|
8
|
+
/** Render all rows at or below this count. Default: 100. */
|
|
9
|
+
threshold?: number;
|
|
10
|
+
}
|
|
11
|
+
interface UseVirtualRowsResult<T> {
|
|
12
|
+
/** Attach to the scrolling TableWrap. */
|
|
13
|
+
containerRef: RefObject<HTMLDivElement>;
|
|
14
|
+
rows: readonly T[];
|
|
15
|
+
startIndex: number;
|
|
16
|
+
padTop: number;
|
|
17
|
+
padBottom: number;
|
|
18
|
+
totalHeight: number;
|
|
19
|
+
virtualized: boolean;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Owns scroll/resize observation and exposes the small visible row window.
|
|
23
|
+
* Consumers render padTop/padBottom spacer rows inside their existing tbody.
|
|
24
|
+
*/
|
|
25
|
+
declare function useVirtualRows<T>(rows: readonly T[], { rowHeight, overscan, threshold }: UseVirtualRowsOptions): UseVirtualRowsResult<T>;
|
|
26
|
+
|
|
27
|
+
export { type UseVirtualRowsOptions, type UseVirtualRowsResult, useVirtualRows };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
3
|
+
import { shouldVirtualize, virtualWindow } from "../table-engine.js";
|
|
4
|
+
function useVirtualRows(rows, { rowHeight, overscan = 6, threshold = 100 }) {
|
|
5
|
+
const containerRef = useRef(null);
|
|
6
|
+
const [viewport, setViewport] = useState({ scrollTop: 0, height: 0 });
|
|
7
|
+
useLayoutEffect(() => {
|
|
8
|
+
const element = containerRef.current;
|
|
9
|
+
if (!element) return;
|
|
10
|
+
let frame = 0;
|
|
11
|
+
const commitMeasurement = () => {
|
|
12
|
+
const next = { scrollTop: element.scrollTop, height: element.clientHeight };
|
|
13
|
+
setViewport((current) => current.scrollTop === next.scrollTop && current.height === next.height ? current : next);
|
|
14
|
+
};
|
|
15
|
+
const measure = () => {
|
|
16
|
+
if (frame) return;
|
|
17
|
+
frame = requestAnimationFrame(() => {
|
|
18
|
+
frame = 0;
|
|
19
|
+
commitMeasurement();
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
commitMeasurement();
|
|
23
|
+
element.addEventListener("scroll", measure, { passive: true });
|
|
24
|
+
const observer = new ResizeObserver(measure);
|
|
25
|
+
observer.observe(element);
|
|
26
|
+
return () => {
|
|
27
|
+
element.removeEventListener("scroll", measure);
|
|
28
|
+
observer.disconnect();
|
|
29
|
+
if (frame) cancelAnimationFrame(frame);
|
|
30
|
+
};
|
|
31
|
+
}, []);
|
|
32
|
+
const enabled = viewport.height > 0 && shouldVirtualize(rows.length, threshold);
|
|
33
|
+
const window = useMemo(
|
|
34
|
+
() => enabled ? virtualWindow(viewport.scrollTop, viewport.height, rowHeight, rows.length, overscan) : { start: 0, end: rows.length, padTop: 0, padBottom: 0, total: rows.length * rowHeight },
|
|
35
|
+
[enabled, overscan, rowHeight, rows.length, viewport.height, viewport.scrollTop]
|
|
36
|
+
);
|
|
37
|
+
const visibleRows = useMemo(() => rows.slice(window.start, window.end), [rows, window.start, window.end]);
|
|
38
|
+
return {
|
|
39
|
+
containerRef,
|
|
40
|
+
rows: visibleRows,
|
|
41
|
+
startIndex: window.start,
|
|
42
|
+
padTop: window.padTop,
|
|
43
|
+
padBottom: window.padBottom,
|
|
44
|
+
totalHeight: window.total,
|
|
45
|
+
virtualized: enabled
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export {
|
|
49
|
+
useVirtualRows
|
|
50
|
+
};
|