@flood-md/react 0.2.11 → 0.2.13
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 +39 -7
- package/dist/app-shell.js +2 -2
- package/dist/box-plot.d.ts +2 -0
- package/dist/box-plot.js +148 -0
- package/dist/bullet-chart.d.ts +2 -0
- package/dist/bullet-chart.js +27 -0
- package/dist/charts.d.ts +6 -0
- package/dist/charts.js +561 -0
- package/dist/collection-toolbar.js +4 -4
- package/dist/data-display.js +27 -3
- package/dist/data-table-columns.d.ts +25 -0
- package/dist/data-table-columns.js +271 -0
- package/dist/data-table-expansion.d.ts +21 -0
- package/dist/data-table-expansion.js +79 -0
- package/dist/data-table.js +100 -7
- package/dist/dumbbell-chart.d.ts +2 -0
- package/dist/dumbbell-chart.js +80 -0
- package/dist/editable.js +57 -18
- package/dist/feedback-actions.js +18 -3
- package/dist/foundations.css +2006 -227
- package/dist/histogram.d.ts +2 -0
- package/dist/histogram.js +78 -0
- package/dist/index.d.ts +328 -3
- package/dist/index.js +57 -4
- package/dist/menu.d.ts +1 -0
- package/dist/menu.js +6 -6
- package/dist/overlay-runtime.d.ts +5 -4
- package/dist/overlay-runtime.js +35 -12
- package/dist/overlays.d.ts +1 -0
- package/dist/overlays.js +5 -3
- package/dist/range-chart.d.ts +2 -0
- package/dist/range-chart.js +97 -0
- package/dist/scatter-chart.d.ts +2 -0
- package/dist/scatter-chart.js +170 -0
- package/dist/sparkline.d.ts +2 -0
- package/dist/sparkline.js +65 -0
- package/dist/waterfall-chart.d.ts +2 -0
- package/dist/waterfall-chart.js +89 -0
- package/package.json +2 -2
package/dist/data-display.js
CHANGED
|
@@ -1,11 +1,35 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { forwardRef } from "react";
|
|
2
|
+
import { forwardRef, useEffect, useRef } from "react";
|
|
3
3
|
import { ScrollArea } from "./index.js";
|
|
4
4
|
export const DescribedData = /* @__PURE__ */ forwardRef(function DescribedData({ items, layout, density, dividers, className, ...props }, ref) {
|
|
5
5
|
if (items.some(item => typeof item.id !== "string" || !item.id.trim()) || new Set(items.map(item => item.id)).size !== items.length)
|
|
6
6
|
throw new Error("Flood description items require unique nonempty IDs");
|
|
7
7
|
return _jsx("dl", { ...props, ref: ref, className: ["flood-description-list", className].filter(Boolean).join(" "), "data-layout": layout, "data-density": density, "data-dividers": dividers, children: items.map(item => _jsxs("div", { className: "flood-description-list__item", children: [_jsx("dt", { children: item.term }), _jsx("dd", { children: item.description })] }, item.id)) });
|
|
8
8
|
});
|
|
9
|
-
export const TabularData = /* @__PURE__ */ forwardRef(function TabularData({ caption, children, hideCaption, density, striped, scrollLabel, cellVerticalAlign, className, hidden, dir, lang, ...props }, ref) {
|
|
10
|
-
|
|
9
|
+
export const TabularData = /* @__PURE__ */ forwardRef(function TabularData({ caption, children, hideCaption, density, striped, scrollLabel, cellVerticalAlign, maxViewportHeight, stickyHeader, className, hidden, dir, lang, ...props }, ref) {
|
|
10
|
+
const viewport = useRef(null);
|
|
11
|
+
const scrollVertically = maxViewportHeight !== undefined;
|
|
12
|
+
const pinHeader = scrollVertically && stickyHeader;
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
const node = viewport.current;
|
|
15
|
+
const header = node?.querySelector("table")?.tHead;
|
|
16
|
+
if (!pinHeader || !node || !header)
|
|
17
|
+
return;
|
|
18
|
+
const update = () => { node.style.scrollPaddingBlockStart = `${header.offsetHeight}px`; };
|
|
19
|
+
update();
|
|
20
|
+
const observer = new ResizeObserver(update);
|
|
21
|
+
observer.observe(header);
|
|
22
|
+
return () => {
|
|
23
|
+
observer.disconnect();
|
|
24
|
+
node.style.removeProperty("scroll-padding-block-start");
|
|
25
|
+
};
|
|
26
|
+
}, [pinHeader, children]);
|
|
27
|
+
return _jsx("div", { className: "flood-table-container", hidden: hidden, dir: dir, lang: lang, children: _jsx(ScrollArea, { ref: viewport, orientation: scrollVertically ? "both" : "horizontal", contentInset: scrollVertically ? "scrollbar" : undefined, tabIndex: scrollVertically ? 0 : undefined, style: scrollVertically ? { maxBlockSize: maxViewportHeight } : undefined, "aria-label": scrollLabel ?? caption, onFocus: pinHeader ? event => {
|
|
28
|
+
const target = event.target;
|
|
29
|
+
const header = event.currentTarget.querySelector("table")?.tHead;
|
|
30
|
+
if (header && target !== event.currentTarget && !header.contains(target) &&
|
|
31
|
+
!target.closest("[popover], dialog[open]") && target.getBoundingClientRect().top < header.getBoundingClientRect().bottom) {
|
|
32
|
+
target.scrollIntoView({ block: "nearest", inline: "nearest" });
|
|
33
|
+
}
|
|
34
|
+
} : undefined, children: _jsxs("table", { ...props, ref: ref, hidden: hidden, dir: dir, lang: lang, className: ["flood-table", className].filter(Boolean).join(" "), "data-density": density, "data-striped": striped, "data-cell-vertical-align": cellVerticalAlign, "data-sticky-header": pinHeader || undefined, "data-bounded": scrollVertically || undefined, children: [_jsx("caption", { className: hideCaption ? "flood-visually-hidden" : undefined, children: caption }), children] }) }) });
|
|
11
35
|
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type CSSProperties } from "react";
|
|
2
|
+
import { type DataTableColumn, type DataTableProps } from "./index.js";
|
|
3
|
+
export declare function useDataTableColumnLayout(columns: readonly DataTableColumn[], visibleColumns: readonly DataTableColumn[], { columnWidths, defaultColumnWidths, onColumnWidthsChange, measureViewport, }: Pick<DataTableProps, "columnWidths" | "defaultColumnWidths" | "onColumnWidthsChange"> & {
|
|
4
|
+
measureViewport?: boolean;
|
|
5
|
+
}): {
|
|
6
|
+
table: import("react").RefObject<HTMLTableElement | null>;
|
|
7
|
+
fixedWidth: number | undefined;
|
|
8
|
+
viewportWidth: number | undefined;
|
|
9
|
+
disclosureWidth: number | undefined;
|
|
10
|
+
atDefaults: boolean;
|
|
11
|
+
reset: () => void;
|
|
12
|
+
utilityCell: (id: string, header?: boolean) => {
|
|
13
|
+
"data-pin-edge": "end" | "start" | undefined;
|
|
14
|
+
"data-pin-boundary": true | undefined;
|
|
15
|
+
style: CSSProperties | undefined;
|
|
16
|
+
"data-data-table-utility": string;
|
|
17
|
+
};
|
|
18
|
+
columnCell: (column: DataTableColumn, header?: boolean) => {
|
|
19
|
+
"data-pin-edge": "end" | "start" | undefined;
|
|
20
|
+
"data-pin-boundary": true | undefined;
|
|
21
|
+
style: CSSProperties | undefined;
|
|
22
|
+
"data-data-table-column": string;
|
|
23
|
+
};
|
|
24
|
+
renderHandle: (column: DataTableColumn, label: string, disabled: boolean) => false | import("react").JSX.Element | undefined;
|
|
25
|
+
};
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
3
|
+
import { IconSlot } from "./index.js";
|
|
4
|
+
const defaultMinWidth = 64;
|
|
5
|
+
const defaultMaxWidth = 960;
|
|
6
|
+
const columnKey = (id) => `column:${id}`;
|
|
7
|
+
const utilityKey = (id) => `utility:${id}`;
|
|
8
|
+
const validWidth = (value) => typeof value === "number" && Number.isFinite(value) && value > 0;
|
|
9
|
+
function bounds(column) {
|
|
10
|
+
const min = validWidth(column.minWidth) ? Math.ceil(column.minWidth) : defaultMinWidth;
|
|
11
|
+
const max = validWidth(column.maxWidth) ? Math.floor(column.maxWidth) : defaultMaxWidth;
|
|
12
|
+
return { min, max: Math.max(min, max) };
|
|
13
|
+
}
|
|
14
|
+
function clampWidth(value, { min, max }) {
|
|
15
|
+
return Math.max(min, Math.min(max, Math.round(value)));
|
|
16
|
+
}
|
|
17
|
+
function normalizeWidths(columns, widths) {
|
|
18
|
+
return new Map(columns.flatMap(column => {
|
|
19
|
+
const value = widths && Object.prototype.hasOwnProperty.call(widths, column.id) ? widths[column.id] : undefined;
|
|
20
|
+
return column.resizable && validWidth(value) ? [[column.id, clampWidth(value, bounds(column))]] : [];
|
|
21
|
+
}));
|
|
22
|
+
}
|
|
23
|
+
function equalWidths(a, b) {
|
|
24
|
+
return a.size === b.size && [...a].every(([key, width]) => b.get(key) === width);
|
|
25
|
+
}
|
|
26
|
+
function measuredColumnWidth(header) {
|
|
27
|
+
const style = header.ownerDocument.defaultView?.getComputedStyle(header);
|
|
28
|
+
if (!style)
|
|
29
|
+
return header.offsetWidth;
|
|
30
|
+
const inlineSize = Number.parseFloat(style.inlineSize);
|
|
31
|
+
if (!Number.isFinite(inlineSize))
|
|
32
|
+
return header.offsetWidth;
|
|
33
|
+
const borderBox = style.boxSizing === "border-box" ? inlineSize : inlineSize +
|
|
34
|
+
Number.parseFloat(style.paddingInlineStart) + Number.parseFloat(style.paddingInlineEnd) +
|
|
35
|
+
Number.parseFloat(style.borderInlineStartWidth) + Number.parseFloat(style.borderInlineEndWidth);
|
|
36
|
+
// Avoid shaving a fraction of a pixel from untouched labels when switching to fixed tracks.
|
|
37
|
+
return Math.ceil(borderBox * 100) / 100;
|
|
38
|
+
}
|
|
39
|
+
function ColumnResizeHandle({ column, label, width, disabled, onPreview, onResize }) {
|
|
40
|
+
const gesture = useRef(null);
|
|
41
|
+
const [dragging, setDragging] = useState(false);
|
|
42
|
+
const limits = bounds(column);
|
|
43
|
+
const direction = (node) => {
|
|
44
|
+
const rtl = node.ownerDocument.defaultView?.getComputedStyle(node).direction === "rtl";
|
|
45
|
+
return (rtl ? -1 : 1) * (column.pinned === "end" ? -1 : 1);
|
|
46
|
+
};
|
|
47
|
+
const cancel = () => {
|
|
48
|
+
const active = gesture.current;
|
|
49
|
+
if (!active)
|
|
50
|
+
return;
|
|
51
|
+
gesture.current = null;
|
|
52
|
+
if (active.handle.hasPointerCapture(active.id))
|
|
53
|
+
active.handle.releasePointerCapture(active.id);
|
|
54
|
+
setDragging(false);
|
|
55
|
+
onPreview(undefined);
|
|
56
|
+
};
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
if (disabled)
|
|
59
|
+
cancel();
|
|
60
|
+
}, [disabled]);
|
|
61
|
+
useEffect(() => () => {
|
|
62
|
+
if (gesture.current)
|
|
63
|
+
onPreview(undefined);
|
|
64
|
+
}, []);
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
if (!dragging)
|
|
67
|
+
return;
|
|
68
|
+
const document = gesture.current?.handle.ownerDocument;
|
|
69
|
+
const view = document?.defaultView;
|
|
70
|
+
const hide = () => { if (document?.hidden)
|
|
71
|
+
cancel(); };
|
|
72
|
+
view?.addEventListener("blur", cancel);
|
|
73
|
+
document?.addEventListener("visibilitychange", hide);
|
|
74
|
+
return () => {
|
|
75
|
+
view?.removeEventListener("blur", cancel);
|
|
76
|
+
document?.removeEventListener("visibilitychange", hide);
|
|
77
|
+
};
|
|
78
|
+
}, [dragging]);
|
|
79
|
+
const requestedWidth = (event) => {
|
|
80
|
+
const active = gesture.current;
|
|
81
|
+
if (active?.id !== event.pointerId)
|
|
82
|
+
return undefined;
|
|
83
|
+
return clampWidth(active.width + (event.clientX - active.origin) * active.direction / active.scale, limits);
|
|
84
|
+
};
|
|
85
|
+
const value = width === undefined ? limits.min : clampWidth(width, limits);
|
|
86
|
+
return _jsx("div", { role: "separator", "aria-label": label, title: label, "aria-orientation": "vertical", "aria-valuemin": limits.min, "aria-valuemax": limits.max, "aria-valuenow": value, "aria-valuetext": `${value} px`, tabIndex: disabled ? -1 : 0, "aria-disabled": disabled || undefined, className: "flood-data-table__resize", "data-dragging": dragging || undefined, onKeyDown: event => {
|
|
87
|
+
if (event.defaultPrevented || disabled || event.altKey || event.ctrlKey || event.metaKey || event.nativeEvent.isComposing)
|
|
88
|
+
return;
|
|
89
|
+
if (event.key === "Escape" && gesture.current) {
|
|
90
|
+
event.preventDefault();
|
|
91
|
+
cancel();
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (gesture.current)
|
|
95
|
+
return;
|
|
96
|
+
let next;
|
|
97
|
+
if (event.key === "Home")
|
|
98
|
+
next = limits.min;
|
|
99
|
+
else if (event.key === "End")
|
|
100
|
+
next = limits.max;
|
|
101
|
+
else if (event.key === "ArrowLeft" || event.key === "ArrowRight") {
|
|
102
|
+
const step = event.shiftKey ? 24 : 8;
|
|
103
|
+
next = clampWidth((width ?? value) + (event.key === "ArrowRight" ? step : -step) * direction(event.currentTarget), limits);
|
|
104
|
+
}
|
|
105
|
+
else
|
|
106
|
+
return;
|
|
107
|
+
event.preventDefault();
|
|
108
|
+
if (next !== width)
|
|
109
|
+
onResize(next);
|
|
110
|
+
}, onPointerDown: event => {
|
|
111
|
+
const handle = event.currentTarget;
|
|
112
|
+
const header = handle.closest("th");
|
|
113
|
+
if (event.defaultPrevented || disabled || gesture.current || event.button !== 0 || !event.isPrimary || !header?.offsetWidth)
|
|
114
|
+
return;
|
|
115
|
+
try {
|
|
116
|
+
handle.setPointerCapture(event.pointerId);
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
event.preventDefault();
|
|
122
|
+
handle.focus({ preventScroll: true });
|
|
123
|
+
gesture.current = {
|
|
124
|
+
handle, id: event.pointerId, origin: event.clientX, width: header.offsetWidth,
|
|
125
|
+
direction: direction(handle), scale: header.getBoundingClientRect().width / header.offsetWidth || 1,
|
|
126
|
+
};
|
|
127
|
+
setDragging(true);
|
|
128
|
+
}, onPointerMove: event => {
|
|
129
|
+
const next = requestedWidth(event);
|
|
130
|
+
if (next !== undefined && !disabled)
|
|
131
|
+
onPreview(event.clientX === gesture.current?.origin ? undefined : next);
|
|
132
|
+
}, onPointerUp: event => {
|
|
133
|
+
const next = requestedWidth(event);
|
|
134
|
+
if (next === undefined)
|
|
135
|
+
return;
|
|
136
|
+
const moved = event.clientX !== gesture.current?.origin && next !== gesture.current?.width;
|
|
137
|
+
cancel();
|
|
138
|
+
if (!disabled && moved)
|
|
139
|
+
onResize(next);
|
|
140
|
+
}, onPointerCancel: event => { if (gesture.current?.id === event.pointerId)
|
|
141
|
+
cancel(); }, onLostPointerCapture: event => { if (gesture.current?.id === event.pointerId)
|
|
142
|
+
cancel(); }, children: _jsx("span", { className: "flood-data-table__resize-icon", children: _jsx(IconSlot, { size: "sm", children: _jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: _jsx("path", { d: "M3 12h18m-14-4-4 4 4 4m10-8 4 4-4 4" }) }) }) }) });
|
|
143
|
+
}
|
|
144
|
+
export function useDataTableColumnLayout(columns, visibleColumns, { columnWidths, defaultColumnWidths, onColumnWidthsChange, measureViewport = false, }) {
|
|
145
|
+
const [local, setLocal] = useState(defaultColumnWidths);
|
|
146
|
+
const [draft, setDraft] = useState();
|
|
147
|
+
const [metrics, setMetrics] = useState({ widths: new Map(), utilityKeys: [], viewport: 0 });
|
|
148
|
+
const table = useRef(null);
|
|
149
|
+
const baseline = useRef(new Map());
|
|
150
|
+
const accepted = normalizeWidths(columns, columnWidths ?? local);
|
|
151
|
+
const defaults = normalizeWidths(columns, defaultColumnWidths);
|
|
152
|
+
const activeDraft = draft && visibleColumns.some(column => column.resizable && column.id === draft.id) ? draft : undefined;
|
|
153
|
+
const visibleSizes = new Map(visibleColumns.flatMap(column => {
|
|
154
|
+
const width = activeDraft?.id === column.id ? activeDraft.width : accepted.get(column.id);
|
|
155
|
+
return width === undefined ? [] : [[columnKey(column.id), width]];
|
|
156
|
+
}));
|
|
157
|
+
const active = measureViewport || columns.some(column => column.resizable || column.pinned);
|
|
158
|
+
const hasSizes = visibleSizes.size > 0;
|
|
159
|
+
const columnOrder = JSON.stringify(visibleColumns.map(column => column.id));
|
|
160
|
+
useLayoutEffect(() => {
|
|
161
|
+
const node = table.current;
|
|
162
|
+
const viewport = node?.closest(".flood-scroll-area__viewport");
|
|
163
|
+
const headerRow = node?.tHead?.rows[0];
|
|
164
|
+
if (!active || !node || !viewport || !headerRow)
|
|
165
|
+
return;
|
|
166
|
+
let headers = Array.from(headerRow.cells);
|
|
167
|
+
const measure = () => {
|
|
168
|
+
const widths = new Map();
|
|
169
|
+
const utilityKeys = [];
|
|
170
|
+
for (const header of headers) {
|
|
171
|
+
const id = header.dataset.dataTableColumn;
|
|
172
|
+
const utility = header.dataset.dataTableUtility;
|
|
173
|
+
const key = id !== undefined ? columnKey(id) : utilityKey(utility ?? `unknown-${header.cellIndex}`);
|
|
174
|
+
if (id === undefined)
|
|
175
|
+
utilityKeys.push(key);
|
|
176
|
+
if (header.offsetWidth === 0)
|
|
177
|
+
continue;
|
|
178
|
+
widths.set(key, header.offsetWidth);
|
|
179
|
+
if (!hasSizes || !baseline.current.has(key))
|
|
180
|
+
baseline.current.set(key, measuredColumnWidth(header));
|
|
181
|
+
}
|
|
182
|
+
setMetrics(previous => previous.viewport === viewport.clientWidth && equalWidths(previous.widths, widths) &&
|
|
183
|
+
previous.utilityKeys.join("|") === utilityKeys.join("|")
|
|
184
|
+
? previous : { widths, utilityKeys, viewport: viewport.clientWidth });
|
|
185
|
+
};
|
|
186
|
+
measure();
|
|
187
|
+
const Observer = node.ownerDocument.defaultView?.ResizeObserver;
|
|
188
|
+
if (!Observer)
|
|
189
|
+
return;
|
|
190
|
+
const observer = new Observer(measure);
|
|
191
|
+
const observeHeaders = () => {
|
|
192
|
+
observer.disconnect();
|
|
193
|
+
headers = Array.from(headerRow.cells);
|
|
194
|
+
observer.observe(viewport);
|
|
195
|
+
for (const header of headers)
|
|
196
|
+
observer.observe(header, { box: "border-box" });
|
|
197
|
+
measure();
|
|
198
|
+
};
|
|
199
|
+
observeHeaders();
|
|
200
|
+
const HeaderObserver = node.ownerDocument.defaultView?.MutationObserver;
|
|
201
|
+
const headerObserver = HeaderObserver && new HeaderObserver(observeHeaders);
|
|
202
|
+
headerObserver?.observe(headerRow, { childList: true });
|
|
203
|
+
return () => {
|
|
204
|
+
observer.disconnect();
|
|
205
|
+
headerObserver?.disconnect();
|
|
206
|
+
};
|
|
207
|
+
}, [active, hasSizes, columnOrder]);
|
|
208
|
+
const sizes = hasSizes ? new Map([...baseline.current, ...visibleSizes]) : metrics.widths;
|
|
209
|
+
const keys = [...metrics.utilityKeys, ...visibleColumns.map(column => columnKey(column.id))];
|
|
210
|
+
const fixedWidth = hasSizes && keys.every(key => sizes.has(key))
|
|
211
|
+
? keys.reduce((total, key) => total + sizes.get(key), 0)
|
|
212
|
+
: undefined;
|
|
213
|
+
const start = visibleColumns.filter(column => column.pinned === "start").map(column => columnKey(column.id));
|
|
214
|
+
const end = visibleColumns.filter(column => column.pinned === "end").map(column => columnKey(column.id)).reverse();
|
|
215
|
+
if (start.length)
|
|
216
|
+
start.unshift(...metrics.utilityKeys);
|
|
217
|
+
const pinned = new Map();
|
|
218
|
+
const pinnedKeys = [...start, ...end];
|
|
219
|
+
const pinnedWidth = pinnedKeys.reduce((total, key) => total + (metrics.widths.get(key) ?? 0), 0);
|
|
220
|
+
if (pinnedKeys.every(key => metrics.widths.has(key)) && pinnedWidth + defaultMinWidth <= metrics.viewport) {
|
|
221
|
+
for (const [edge, group] of [["start", start], ["end", end]]) {
|
|
222
|
+
let offset = 0;
|
|
223
|
+
group.forEach((key, index) => {
|
|
224
|
+
pinned.set(key, { edge, offset, boundary: index === group.length - 1 });
|
|
225
|
+
offset += metrics.widths.get(key);
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
function cell(key, header, width) {
|
|
230
|
+
const pin = pinned.get(key);
|
|
231
|
+
const inlineSize = header ? (fixedWidth === undefined ? width : sizes.get(key)) : undefined;
|
|
232
|
+
return {
|
|
233
|
+
"data-pin-edge": pin?.edge,
|
|
234
|
+
"data-pin-boundary": pin?.boundary || undefined,
|
|
235
|
+
style: inlineSize !== undefined || pin
|
|
236
|
+
? { inlineSize, "--flood-data-table-pin-offset": pin ? `${pin.offset}px` : undefined }
|
|
237
|
+
: undefined,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
function publish(next) {
|
|
241
|
+
if (equalWidths(accepted, next))
|
|
242
|
+
return;
|
|
243
|
+
const widths = Object.fromEntries(next);
|
|
244
|
+
if (columnWidths === undefined)
|
|
245
|
+
setLocal(widths);
|
|
246
|
+
onColumnWidthsChange?.(widths);
|
|
247
|
+
}
|
|
248
|
+
return {
|
|
249
|
+
table, fixedWidth,
|
|
250
|
+
viewportWidth: metrics.viewport || undefined,
|
|
251
|
+
disclosureWidth: metrics.widths.get(utilityKey("expansion")),
|
|
252
|
+
atDefaults: equalWidths(accepted, defaults),
|
|
253
|
+
reset: () => { setDraft(undefined); publish(defaults); },
|
|
254
|
+
utilityCell: (id, header = false) => ({
|
|
255
|
+
...cell(utilityKey(id), header),
|
|
256
|
+
"data-data-table-utility": id,
|
|
257
|
+
}),
|
|
258
|
+
columnCell: (column, header = false) => ({
|
|
259
|
+
...cell(columnKey(column.id), header, header ? (accepted.get(column.id) ?? column.width) : undefined),
|
|
260
|
+
"data-data-table-column": column.id,
|
|
261
|
+
}),
|
|
262
|
+
renderHandle: (column, label, disabled) => column.resizable &&
|
|
263
|
+
_jsx(ColumnResizeHandle, { column: column, label: label, disabled: disabled, width: visibleSizes.get(columnKey(column.id)) ?? metrics.widths.get(columnKey(column.id)), onPreview: width => setDraft(previous => {
|
|
264
|
+
if (width === undefined)
|
|
265
|
+
return previous?.id === column.id ? undefined : previous;
|
|
266
|
+
if (previous?.id === column.id && previous.width === width)
|
|
267
|
+
return previous;
|
|
268
|
+
return { id: column.id, width };
|
|
269
|
+
}), onResize: width => publish(new Map(accepted).set(column.id, width)) }),
|
|
270
|
+
};
|
|
271
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type ReactNode, type TdHTMLAttributes } from "react";
|
|
2
|
+
import { type DataTableLabels, type DataTableRow } from "./index.js";
|
|
3
|
+
export declare function useDataTableExpansion(rows: readonly DataTableRow[], expandedIds: readonly string[] | undefined, defaultExpandedIds: readonly string[] | undefined, onExpandedChange: ((ids: string[]) => void) | undefined): {
|
|
4
|
+
enabled: boolean;
|
|
5
|
+
expanded: Set<string>;
|
|
6
|
+
toggle: (id: string) => void;
|
|
7
|
+
};
|
|
8
|
+
interface DataTableExpandableRowProps {
|
|
9
|
+
row: DataTableRow;
|
|
10
|
+
showDisclosure: boolean;
|
|
11
|
+
expanded: boolean;
|
|
12
|
+
colSpan: number;
|
|
13
|
+
labels: DataTableLabels;
|
|
14
|
+
onToggle: () => void;
|
|
15
|
+
disclosureCellProps?: TdHTMLAttributes<HTMLTableCellElement>;
|
|
16
|
+
viewportWidth?: number;
|
|
17
|
+
disclosureWidth?: number;
|
|
18
|
+
children: (disclosureCell: ReactNode) => ReactNode;
|
|
19
|
+
}
|
|
20
|
+
export declare function DataTableExpandableRow({ row, showDisclosure, expanded, colSpan, labels, onToggle, disclosureCellProps, viewportWidth, disclosureWidth, children }: DataTableExpandableRowProps): import("react").JSX.Element;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useId, useRef, useState } from "react";
|
|
3
|
+
import { IconButton } from "./index.js";
|
|
4
|
+
import { useLayerExitContent } from "./overlay-runtime.js";
|
|
5
|
+
function hasExpandedContent(row) {
|
|
6
|
+
return row.expandedContent != null && typeof row.expandedContent !== "boolean";
|
|
7
|
+
}
|
|
8
|
+
export function useDataTableExpansion(rows, expandedIds, defaultExpandedIds, onExpandedChange) {
|
|
9
|
+
const [localExpandedIds, setLocalExpandedIds] = useState(defaultExpandedIds ?? []);
|
|
10
|
+
const expanded = new Set(expandedIds ?? localExpandedIds);
|
|
11
|
+
const enabled = expandedIds !== undefined || defaultExpandedIds !== undefined || rows.some(hasExpandedContent);
|
|
12
|
+
function toggle(id) {
|
|
13
|
+
const next = new Set(expanded);
|
|
14
|
+
if (next.has(id))
|
|
15
|
+
next.delete(id);
|
|
16
|
+
else
|
|
17
|
+
next.add(id);
|
|
18
|
+
const ids = [...next];
|
|
19
|
+
if (expandedIds === undefined)
|
|
20
|
+
setLocalExpandedIds(ids);
|
|
21
|
+
onExpandedChange?.(ids);
|
|
22
|
+
}
|
|
23
|
+
return { enabled, expanded, toggle };
|
|
24
|
+
}
|
|
25
|
+
export function DataTableExpandableRow({ row, showDisclosure, expanded, colSpan, labels, onToggle, disclosureCellProps, viewportWidth, disclosureWidth, children }) {
|
|
26
|
+
const detailId = useId();
|
|
27
|
+
const toggle = useRef(null);
|
|
28
|
+
const reveal = useRef(null);
|
|
29
|
+
const detail = useRef(null);
|
|
30
|
+
const lastFocusedDetail = useRef(null);
|
|
31
|
+
const expandable = showDisclosure && hasExpandedContent(row);
|
|
32
|
+
const open = expandable && expanded;
|
|
33
|
+
const showDetail = useLayerExitContent(reveal, open, expandable, "grid-template-rows");
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
const content = detail.current;
|
|
36
|
+
const header = content?.querySelector("table")?.tHead?.rows[0];
|
|
37
|
+
if (!content || !header)
|
|
38
|
+
return;
|
|
39
|
+
const alignBranch = () => {
|
|
40
|
+
const bounds = content.getBoundingClientRect();
|
|
41
|
+
if (!bounds.height)
|
|
42
|
+
return;
|
|
43
|
+
const heading = header.getBoundingClientRect();
|
|
44
|
+
const center = (heading.top + heading.height / 2 - bounds.top) * content.offsetHeight / bounds.height;
|
|
45
|
+
const value = `${center}px`;
|
|
46
|
+
if (content.style.getPropertyValue("--flood-data-table-branch-target") !== value) {
|
|
47
|
+
content.style.setProperty("--flood-data-table-branch-target", value);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
alignBranch();
|
|
51
|
+
const observer = new ResizeObserver(alignBranch);
|
|
52
|
+
observer.observe(content);
|
|
53
|
+
observer.observe(header);
|
|
54
|
+
return () => {
|
|
55
|
+
observer.disconnect();
|
|
56
|
+
content.style.removeProperty("--flood-data-table-branch-target");
|
|
57
|
+
};
|
|
58
|
+
}, [showDetail, row.expandedContent]);
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
if (open)
|
|
61
|
+
return;
|
|
62
|
+
const previousFocus = lastFocusedDetail.current;
|
|
63
|
+
lastFocusedDetail.current = null;
|
|
64
|
+
const button = toggle.current;
|
|
65
|
+
const active = button?.ownerDocument.activeElement;
|
|
66
|
+
if (button && previousFocus && (active === button.ownerDocument.body || reveal.current?.contains(active ?? null))) {
|
|
67
|
+
button.focus({ preventScroll: true });
|
|
68
|
+
}
|
|
69
|
+
}, [open]);
|
|
70
|
+
const label = row.label ?? row.primaryAction?.title ?? row.id;
|
|
71
|
+
const actionLabel = open ? labels.collapseRow ?? (name => `Collapse ${name}`) : labels.expandRow ?? (name => `Expand ${name}`);
|
|
72
|
+
return _jsxs(_Fragment, { children: [children(showDisclosure ? _jsx("td", { ...disclosureCellProps, "data-data-table-utility": "expansion", "data-detail-visible": showDetail || undefined, className: "flood-data-table__expand", children: expandable && _jsx(IconButton, { ref: toggle, label: actionLabel(label), size: "sm", iconSize: "xs", variant: "ghost", className: "flood-data-table__disclosure", type: "button", "aria-expanded": open, "aria-controls": open ? detailId : undefined, onClick: onToggle, children: _jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: _jsx("path", { d: "m6 9 6 6 6-6" }) }) }) }) : null), showDetail && _jsx("tr", { id: detailId, className: "flood-data-table__expanded-row", "aria-hidden": open ? undefined : true, onFocusCapture: event => {
|
|
73
|
+
if (event.currentTarget.contains(event.target))
|
|
74
|
+
lastFocusedDetail.current = event.target;
|
|
75
|
+
}, children: _jsx("td", { colSpan: colSpan, children: _jsx("div", { ref: reveal, className: "flood-data-table__detail-reveal", "data-open": open || undefined, children: _jsx("div", { className: "flood-data-table__detail-clip", children: _jsx("div", { ref: detail, className: "flood-data-table__expanded-content", style: {
|
|
76
|
+
maxInlineSize: viewportWidth ? `max(0px, calc(${viewportWidth}px - 2 * var(--flood-data-table-edge) - var(--flood-scroll-content-inline, 0px)))` : undefined,
|
|
77
|
+
"--flood-data-table-disclosure-center": disclosureWidth === undefined ? undefined : `${disclosureWidth / 2}px`,
|
|
78
|
+
}, children: row.expandedContent }) }) }) }) })] });
|
|
79
|
+
}
|
package/dist/data-table.js
CHANGED
|
@@ -1,6 +1,32 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { createElement as _createElement } from "react";
|
|
2
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { forwardRef, useEffect, useMemo, useRef, useState } from "react";
|
|
4
|
+
import { Table, Checkbox, Button, Badge, IconSlot, VisuallyHidden, Pagination, Text, Spinner, Alert, Stack, Inline, Menu, Tooltip } from "./index.js";
|
|
5
|
+
import { useDataTableColumnLayout } from "./data-table-columns.js";
|
|
6
|
+
import { DataTableExpandableRow, useDataTableExpansion } from "./data-table-expansion.js";
|
|
7
|
+
const columnName = (column) => column.unit ? `${column.label} ${column.unit}` : column.label;
|
|
8
|
+
function DataTableHeading({ column, sortLabel, disabled, descending, onSort }) {
|
|
9
|
+
const label = useRef(null);
|
|
10
|
+
const [truncated, setTruncated] = useState(false);
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
const node = label.current;
|
|
13
|
+
if (!node)
|
|
14
|
+
return;
|
|
15
|
+
const measure = () => setTruncated(node.scrollWidth > node.clientWidth);
|
|
16
|
+
measure();
|
|
17
|
+
const Observer = node.ownerDocument.defaultView?.ResizeObserver;
|
|
18
|
+
const observer = Observer && new Observer(measure);
|
|
19
|
+
observer?.observe(node);
|
|
20
|
+
const fonts = node.ownerDocument.fonts;
|
|
21
|
+
fonts?.addEventListener("loadingdone", measure);
|
|
22
|
+
return () => {
|
|
23
|
+
observer?.disconnect();
|
|
24
|
+
fonts?.removeEventListener("loadingdone", measure);
|
|
25
|
+
};
|
|
26
|
+
}, [column.label, column.unit]);
|
|
27
|
+
const content = _jsxs(_Fragment, { children: [_jsx("span", { ref: label, className: "flood-data-table__header-label", children: column.label }), " ", column.unit && _jsx(Badge, { size: "sm", variant: "outline", className: "flood-data-table__header-unit", "aria-hidden": "true", children: column.unit })] });
|
|
28
|
+
return _jsx(Tooltip, { content: columnName(column), disabled: !truncated, trigger: trigger => column.sortable ? _jsxs(Button, { ...trigger, "aria-describedby": undefined, variant: "ghost", size: "sm", disabled: disabled, className: "flood-data-table__sort", "aria-label": sortLabel, onClick: onSort, children: [content, _jsx(IconSlot, { size: "sm", children: _jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: descending ? _jsx("path", { d: "m7 14 5-5 5 5" }) : _jsx("path", { d: "m8 8 4-4 4 4M8 16l4 4 4-4" }) }) })] }) : _jsx("span", { ...trigger, "aria-describedby": undefined, className: "flood-data-table__header-content", tabIndex: truncated ? 0 : undefined, children: content }) });
|
|
29
|
+
}
|
|
4
30
|
function activatePrimaryRowAction(event) {
|
|
5
31
|
const target = event.target;
|
|
6
32
|
const row = event.currentTarget;
|
|
@@ -19,7 +45,7 @@ function activatePrimaryRowAction(event) {
|
|
|
19
45
|
button.focus({ preventScroll: true });
|
|
20
46
|
button.click();
|
|
21
47
|
}
|
|
22
|
-
export const ManagedDataTable = /* @__PURE__ */ forwardRef(function ManagedDataTable({ caption, hideCaption = false, columns, rows, density = "comfortable", striped = false, minWidth = "40rem", toolbar, inset = "standard", sort: controlledSort, defaultSort = null, onSortChange, locale = "en", selectable = false, selectedIds, defaultSelectedIds = [], onSelectionChange, page: controlledPage, defaultPage = 1, pageSize = 10, onPageChange, showResults = true, paginationMode = "client", totalRows, selectionScope, state = "ready", onRetry, emptyContent, labels = {}, className, ...props }, ref) {
|
|
48
|
+
export const ManagedDataTable = /* @__PURE__ */ forwardRef(function ManagedDataTable({ caption, hideCaption = false, columns, rows, density = "comfortable", striped = false, minWidth = "40rem", toolbar, inset = "standard", maxViewportHeight, stickyHeader, showColumnVisibility = false, hiddenColumnIds, defaultHiddenColumnIds = [], onHiddenColumnIdsChange, columnWidths, defaultColumnWidths, onColumnWidthsChange, expandedIds, defaultExpandedIds, onExpandedChange, sort: controlledSort, defaultSort = null, onSortChange, locale = "en", selectable = false, selectedIds, defaultSelectedIds = [], onSelectionChange, page: controlledPage, defaultPage = 1, pageSize = 10, onPageChange, showResults = true, showSelection = true, paginationMode = "client", totalRows, selectionScope, state = "ready", onRetry, emptyContent, labels = {}, className, ...props }, ref) {
|
|
23
49
|
if (!caption.trim() || columns.length === 0 || columns.some(column => !column.id.trim() || !column.label.trim()) || new Set(columns.map(column => column.id)).size !== columns.length)
|
|
24
50
|
throw new Error("Flood DataTable requires a caption and unique named columns");
|
|
25
51
|
if (rows.some(row => !row.id.trim() || (row.label !== undefined && !row.label.trim())) || new Set(rows.map(row => row.id)).size !== rows.length)
|
|
@@ -39,6 +65,55 @@ export const ManagedDataTable = /* @__PURE__ */ forwardRef(function ManagedDataT
|
|
|
39
65
|
const [localSort, setSort] = useState(defaultSort), [localSelected, setSelected] = useState(defaultSelectedIds), [localPage, setPage] = useState(defaultPage);
|
|
40
66
|
const requestedSort = controlledSort === undefined ? localSort : controlledSort;
|
|
41
67
|
const sorting = requestedSort && columns.some(column => column.id === requestedSort.column && column.sortable) ? requestedSort : null;
|
|
68
|
+
const [localHiddenIds, setHiddenIds] = useState(defaultHiddenColumnIds);
|
|
69
|
+
const hidden = new Set(hiddenColumnIds ?? localHiddenIds);
|
|
70
|
+
const required = new Set(rows.flatMap(row => row.primaryAction ? [row.primaryAction.column] : []));
|
|
71
|
+
if (sorting)
|
|
72
|
+
required.add(sorting.column);
|
|
73
|
+
const displayed = columns.filter(column => column.hideable === false || required.has(column.id) || !hidden.has(column.id));
|
|
74
|
+
const retained = displayed.length > 0 ? displayed : columns.slice(0, 1);
|
|
75
|
+
const visibleColumns = [
|
|
76
|
+
...retained.filter(column => column.pinned === "start"),
|
|
77
|
+
...retained.filter(column => !column.pinned),
|
|
78
|
+
...retained.filter(column => column.pinned === "end"),
|
|
79
|
+
];
|
|
80
|
+
const expansion = useDataTableExpansion(rows, expandedIds, defaultExpandedIds, onExpandedChange);
|
|
81
|
+
const columnLayout = useDataTableColumnLayout(columns, visibleColumns, {
|
|
82
|
+
columnWidths, defaultColumnWidths, onColumnWidthsChange, measureViewport: expansion.enabled,
|
|
83
|
+
});
|
|
84
|
+
const columnCount = visibleColumns.length + Number(selectable) + Number(expansion.enabled);
|
|
85
|
+
const visibleIds = new Set(visibleColumns.map(column => column.id));
|
|
86
|
+
const normalizeHiddenIds = (ids) => columns.filter(column => column.hideable !== false && ids.has(column.id)).map(column => column.id);
|
|
87
|
+
const defaultIds = normalizeHiddenIds(new Set(defaultHiddenColumnIds));
|
|
88
|
+
const requestedIds = normalizeHiddenIds(hidden);
|
|
89
|
+
const visibilityAtDefaults = requestedIds.length === defaultIds.length && defaultIds.every(id => hidden.has(id));
|
|
90
|
+
const changeHiddenIds = (next) => {
|
|
91
|
+
if (hiddenColumnIds === undefined)
|
|
92
|
+
setHiddenIds(next);
|
|
93
|
+
onHiddenColumnIdsChange?.(next);
|
|
94
|
+
};
|
|
95
|
+
const columnItems = showColumnVisibility ? [
|
|
96
|
+
...columns.map((column) => ({
|
|
97
|
+
id: `column:${column.id}`,
|
|
98
|
+
type: "checkbox",
|
|
99
|
+
label: sorting?.column === column.id
|
|
100
|
+
? (labels.sortedColumn ?? (label => `${label} (sorted)`))(columnName(column))
|
|
101
|
+
: columnName(column),
|
|
102
|
+
checked: visibleIds.has(column.id),
|
|
103
|
+
disabled: column.hideable === false || required.has(column.id) || (visibleColumns.length === 1 && visibleIds.has(column.id)),
|
|
104
|
+
onCheckedChange: checked => {
|
|
105
|
+
const next = new Set(hidden);
|
|
106
|
+
if (checked)
|
|
107
|
+
next.delete(column.id);
|
|
108
|
+
else
|
|
109
|
+
next.add(column.id);
|
|
110
|
+
changeHiddenIds(normalizeHiddenIds(next));
|
|
111
|
+
},
|
|
112
|
+
})),
|
|
113
|
+
{ id: "column-layout-separator", type: "separator" },
|
|
114
|
+
{ id: "reset-columns", label: labels.resetColumns ?? "Reset columns",
|
|
115
|
+
disabled: visibilityAtDefaults && columnLayout.atDefaults },
|
|
116
|
+
] : [];
|
|
42
117
|
const sorted = useMemo(() => {
|
|
43
118
|
if (server || !sorting)
|
|
44
119
|
return [...rows];
|
|
@@ -86,8 +161,26 @@ export const ManagedDataTable = /* @__PURE__ */ forwardRef(function ManagedDataT
|
|
|
86
161
|
changePage(1);
|
|
87
162
|
};
|
|
88
163
|
const resultLabel = labels.results ?? ((from, to, total) => `${from}–${to} of ${total}`);
|
|
89
|
-
|
|
90
|
-
|
|
164
|
+
const selectionLabel = selectable
|
|
165
|
+
? (labels.selection ?? ((count, total) => `${count} of ${total} selected`))(selection.length, server && scope === "dataset" ? total : eligible.size)
|
|
166
|
+
: null;
|
|
167
|
+
const renderToolbar = typeof toolbar === "function" ? toolbar : null;
|
|
168
|
+
const columnsControl = showColumnVisibility ? (_jsx(Menu, { label: labels.columns ?? "Columns", items: columnItems, align: "end", onAction: id => {
|
|
169
|
+
if (id !== "reset-columns")
|
|
170
|
+
return;
|
|
171
|
+
if (!visibilityAtDefaults)
|
|
172
|
+
changeHiddenIds(defaultIds);
|
|
173
|
+
columnLayout.reset();
|
|
174
|
+
}, trigger: trigger => _jsx(Button, { ...trigger, className: renderToolbar ? undefined : "flood-data-table__columns", variant: "subtle", size: "sm", children: labels.columns ?? "Columns" }) })) : null;
|
|
175
|
+
const toolbarContent = typeof toolbar === "function" ? toolbar({ columns: columnsControl }) : toolbar;
|
|
176
|
+
const sortLabel = labels.sort ?? ((label) => `Sort by ${label}`);
|
|
177
|
+
return _jsxs("div", { ...props, ref: ref, className: ["flood-data-table", className].filter(Boolean).join(" "), "data-inset": inset, "aria-busy": state === "loading" || undefined, children: [columnsControl && !renderToolbar ? _jsxs(Inline, { gap: "3", className: "flood-data-table__toolbar", children: [toolbarContent != null && _jsx("div", { className: "flood-data-table__toolbar-content", children: toolbarContent }), columnsControl] }) : toolbarContent != null && _jsx("div", { className: "flood-data-table__toolbar", children: toolbarContent }), _jsxs(Table, { ref: columnLayout.table, className: "flood-data-table__table", caption: caption, hideCaption: hideCaption, density: density, striped: striped, maxViewportHeight: maxViewportHeight, stickyHeader: stickyHeader, "data-inset": inset, "data-fixed-columns": columnLayout.fixedWidth !== undefined || undefined, style: columnLayout.fixedWidth === undefined ? { minWidth } : { minWidth, inlineSize: columnLayout.fixedWidth, tableLayout: "fixed" }, children: [_jsx("thead", { children: _jsxs("tr", { children: [expansion.enabled && _jsx("th", { ...columnLayout.utilityCell("expansion", true), scope: "col", "data-data-table-utility": "expansion", className: "flood-data-table__expand", children: _jsx(VisuallyHidden, { children: labels.rowDetails ?? "Row details" }) }), selectable && _jsx("th", { ...columnLayout.utilityCell("selection", true), scope: "col", className: "flood-data-table__choice", children: _jsx(Checkbox, { label: _jsx(VisuallyHidden, { children: labels.selectPage ?? "Select rows on this page" }), checked: all, indeterminate: some && !all, disabled: blocked || pageChoices.length === 0, onChange: event => { const ids = new Set(selection); for (const row of pageChoices)
|
|
178
|
+
event.currentTarget.checked ? ids.add(row.id) : ids.delete(row.id); select([...ids]); } }) }), visibleColumns.map(column => {
|
|
179
|
+
const name = columnName(column);
|
|
180
|
+
return _createElement("th", { ...columnLayout.columnCell(column, true), key: column.id, scope: "col", "data-numeric": column.numeric || undefined, "data-align": column.align, "data-sortable": column.sortable || undefined, "data-resize-edge": column.resizable ? column.pinned === "end" ? "start" : "end" : undefined, "aria-label": column.sortable ? sortLabel(name) : name, "aria-sort": sorting?.column === column.id ? sorting.direction : undefined },
|
|
181
|
+
_jsx(DataTableHeading, { column: column, sortLabel: sortLabel(name), disabled: blocked, descending: sorting?.column === column.id, onSort: () => changeSort(column.id) }),
|
|
182
|
+
column.resizable && columnLayout.renderHandle(column, (labels.resizeColumn ?? (label => `Resize ${label} column`))(name), blocked));
|
|
183
|
+
})] }) }), _jsx("tbody", { children: state === "ready" && visible.length > 0 ? visible.map(row => _jsx(DataTableExpandableRow, { row: row, showDisclosure: expansion.enabled, expanded: expansion.expanded.has(row.id), colSpan: columnCount, labels: labels, onToggle: () => expansion.toggle(row.id), disclosureCellProps: columnLayout.utilityCell("expansion"), viewportWidth: columnLayout.viewportWidth, disclosureWidth: columnLayout.disclosureWidth, children: disclosure => _jsxs("tr", { "data-selected": selectable && chosen.has(row.id) || undefined, "data-primary-action": row.primaryAction && !row.primaryAction.buttonProps.disabled && row.primaryAction.buttonProps["aria-disabled"] !== "true" || undefined, onClick: row.primaryAction ? activatePrimaryRowAction : undefined, children: [disclosure, selectable && _jsx("td", { ...columnLayout.utilityCell("selection"), className: "flood-data-table__choice", children: _jsx(Checkbox, { label: _jsx(VisuallyHidden, { children: (labels.selectRow ?? ((label) => `Select ${label}`))(row.label ?? row.id) }), checked: chosen.has(row.id), disabled: Boolean(row.selectionDisabled), onChange: event => select(event.currentTarget.checked ? [...selection, row.id] : selection.filter(id => id !== row.id)) }) }), visibleColumns.map(column => _createElement("td", { ...columnLayout.columnCell(column), key: column.id, "data-numeric": column.numeric || undefined, "data-align": column.align }, row.primaryAction?.column === column.id
|
|
91
184
|
? _jsxs(Stack, { gap: "1", children: [_jsx(Button, { ...row.primaryAction.buttonProps, type: "button", variant: "ghost", size: "sm", className: ["flood-data-table__primary-action", row.primaryAction.buttonProps.className].filter(Boolean).join(" "), children: row.primaryAction.title }), row.primaryAction.description != null && _jsx(Text, { as: "div", size: "helper", tone: "subtle", children: row.primaryAction.description })] })
|
|
92
|
-
: row.cells[column.id] ?? null
|
|
185
|
+
: row.cells[column.id] ?? null))] }) }, row.id)) : _jsx("tr", { children: _jsx("td", { colSpan: columnCount, className: "flood-data-table__state", children: state === "loading" ? _jsx(Spinner, { label: labels.loading ?? "Loading rows…" }) : state === "error" ? _jsxs(Stack, { gap: "3", children: [_jsx(Alert, { tone: "error", announcement: "polite", children: labels.error ?? "Could not load the rows." }), onRetry && _jsx(Button, { variant: "subtle", size: "sm", onClick: onRetry, children: labels.retry ?? "Try again" })] }) : emptyContent ?? _jsx(Text, { as: "p", size: "compact", tone: "subtle", role: "status", children: labels.empty ?? "No results found." }) }) }) })] }), selectable && !showSelection && _jsx(VisuallyHidden, { role: "status", children: selectionLabel }), (showResults || (selectable && showSelection) || pageCount > 1) && _jsxs("div", { className: "flood-data-table__footer", children: [showResults && _jsx(Text, { as: "p", size: "helper", tone: "subtle", children: state === "ready" ? resultLabel(visible.length ? (page - 1) * pageSize + 1 : 0, visible.length ? (page - 1) * pageSize + visible.length : 0, total) : "" }), selectable && showSelection && _jsx(Text, { as: "p", size: "helper", tone: "subtle", role: "status", children: selectionLabel }), pageCount > 1 && _jsx(Pagination, { page: Math.min(page, pageCount), pageCount: pageCount, onPageChange: changePage, disabled: blocked, label: labels.pagination, previousLabel: labels.previousPage, nextLabel: labels.nextPage, pageLabel: labels.page })] })] });
|
|
93
186
|
});
|