@flood-md/react 0.2.11 → 0.2.12
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/collection-toolbar.js +3 -3
- 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/editable.js +57 -18
- package/dist/foundations.css +393 -31
- package/dist/index.d.ts +71 -3
- package/dist/index.js +11 -2
- package/dist/overlay-runtime.d.ts +4 -3
- package/dist/overlay-runtime.js +10 -8
- package/dist/overlays.d.ts +1 -0
- package/dist/overlays.js +2 -2
- package/dist/sparkline.d.ts +2 -0
- package/dist/sparkline.js +65 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -6,11 +6,22 @@ HTML semantics and refs, no third-party component library or Tailwind dependency
|
|
|
6
6
|
Named imports support tree shaking in optimizing ES-module bundlers. Import the
|
|
7
7
|
shared stylesheet explicitly; styles are not split by component.
|
|
8
8
|
|
|
9
|
+
Sheet is a bottom-aligned Drawer for short tasks, navigation and touch-friendly
|
|
10
|
+
choices. It fits short content, caps long content to the viewport, and keeps
|
|
11
|
+
Drawer dismissal, focus restoration, body scrolling and the optional footer.
|
|
12
|
+
|
|
9
13
|
SegmentedControl options can display a `count` alongside the label, for example
|
|
10
14
|
`{value: "pending", label: "Pending", count: 2}`. Use this for a small, mutually
|
|
11
15
|
exclusive choice that updates one collection. The count is read with the visible
|
|
12
16
|
label and does not change the submitted value.
|
|
13
17
|
|
|
18
|
+
Version 0.2.12 adds opt-in DataTable column visibility. Set
|
|
19
|
+
`showColumnVisibility` for the Columns menu, and supply `hiddenColumnIds` and
|
|
20
|
+
`onHiddenColumnIdsChange` when the host saves preferences. The active sort and
|
|
21
|
+
record-opening columns stay visible. See [DataTable](../../docs/components/data-table.md).
|
|
22
|
+
Pass the Columns control from `toolbar={({ columns }) => …}` into
|
|
23
|
+
`CollectionToolbar.actions` to keep it alongside search and filters.
|
|
24
|
+
|
|
14
25
|
The 0.2.11 candidate adds optional DataTable row `primaryAction` for opening an
|
|
15
26
|
in-place detail panel, plus `toolbar` and `inset="roomy"` for a shared table edge.
|
|
16
27
|
The title is a native button and the rest of the row also responds to pointer
|
|
@@ -46,7 +57,7 @@ the interactive size. It defaults to size; shrinking a supporting glyph must
|
|
|
46
57
|
not shrink its target or bypass IconSlot with a custom SVG width. Existing glyph
|
|
47
58
|
roles at a 16px root are 2xs=14px, xs/sm=16px and md/lg=20px.
|
|
48
59
|
|
|
49
|
-
This package documents the **0.2.
|
|
60
|
+
This package documents the **0.2.12** source API. Keep Flood package versions aligned and follow the guide matching
|
|
50
61
|
your installed declarations; source changes do not modify an existing npm release.
|
|
51
62
|
|
|
52
63
|
NavigationItem (0.2.11 candidate) is the shared small row for destinations,
|
|
@@ -431,6 +442,10 @@ edge-to-edge content when the host already provides safe padding. The default
|
|
|
431
442
|
padding can wrap text and change content height. Native
|
|
432
443
|
fallback scrollbars retain their platform behavior.
|
|
433
444
|
|
|
445
|
+
AppShell content mode uses the same ScrollArea. For a workspace whose layout
|
|
446
|
+
already provides clearance, `scrollContentInset="none"` keeps the content
|
|
447
|
+
width stable when rails appear. The default protects edge-to-edge content.
|
|
448
|
+
|
|
434
449
|
```tsx
|
|
435
450
|
<ScrollArea aria-label="Activity" style={{ maxHeight: "12rem" }}>
|
|
436
451
|
<Stack gap="4">{/* Long activity content */}</Stack>
|
|
@@ -604,8 +619,9 @@ Available in 0.2.3. Import Flood CSS and use FloodRoot for dark foundations.
|
|
|
604
619
|
|
|
605
620
|
Disclosure corner geometry: Accordion headers have square internal seams. Only
|
|
606
621
|
the first header has upper shell corners, and only a closed last header has lower
|
|
607
|
-
shell corners.
|
|
608
|
-
|
|
622
|
+
shell corners. A standalone Collapsible has an inset, rounded trigger and a
|
|
623
|
+
separate gap before the revealed content. The trigger label and panel share
|
|
624
|
+
an inline edge.
|
|
609
625
|
|
|
610
626
|
|
|
611
627
|
## Tooltip, Popover and Dialog
|
|
@@ -804,7 +820,12 @@ assertive uses role=alert. Choose urgency deliberately for dynamically inserted
|
|
|
804
820
|
content. Announcements depend on actual assistive technology; initial static
|
|
805
821
|
markup is not guaranteed to be read aloud. No focus movement, auto-dismiss,
|
|
806
822
|
timeout or built-in close/action button is supplied. Compose named host actions
|
|
807
|
-
inside children when needed.
|
|
823
|
+
inside children when needed. A titleless Alert centers its glyph alongside a
|
|
824
|
+
single Inline row. Subtle and ghost actions inherit the feedback tint on
|
|
825
|
+
interactive states. Use a floating ToastRegion for brief operation results.
|
|
826
|
+
ToastRegion defaults to `placement="bottom-end"`. Choose `bottom-start`,
|
|
827
|
+
`top-end` or `top-start` for a different logical corner, or `inline` to
|
|
828
|
+
embed it in a layout. New floating cards enter from their placement edge.
|
|
808
829
|
|
|
809
830
|
See the [WAI Meter pattern](https://www.w3.org/WAI/ARIA/apg/patterns/meter/),
|
|
810
831
|
[Alert pattern](https://www.w3.org/WAI/ARIA/apg/patterns/alert/) and
|
|
@@ -953,6 +974,9 @@ visually while retaining their full text/title. A hidden counter frees its slot.
|
|
|
953
974
|
The host owns accepted changes, captured IDs, confirmation and focus after the
|
|
954
975
|
bar disappears. Positioning is host-owned: use a stable bounded width for a
|
|
955
976
|
floating rail, plus viewport safe-area spacing and measured scroll clearance.
|
|
977
|
+
For table selection, place the rail at the viewport's bottom center. Set
|
|
978
|
+
`showSelection={false}` on DataTable to keep its count available to assistive
|
|
979
|
+
technology while BulkActionBar displays the visible count.
|
|
956
980
|
|
|
957
981
|
OverflowActions also accepts leading/trailing slots and maxVisibleActions. Slot
|
|
958
982
|
width is reserved before measuring commands; use action owners in those slots
|
|
@@ -963,6 +987,9 @@ under one named action panel. After hydration it uses one Tab entry, directional
|
|
|
963
987
|
arrows, Home/End and optional looping. Disabled/hidden actions are skipped. Arrows
|
|
964
988
|
move focus, not activation. Horizontal order follows writing direction. In vertical
|
|
965
989
|
toolbars open Menu with Enter/Space; horizontal menus retain Up/Down opening.
|
|
990
|
+
A vertical pill centers its actions on a single axis. Use same-size icon controls
|
|
991
|
+
with accessible names for a compact rail. Use a rounded toolbar for a vertical
|
|
992
|
+
group of labeled actions.
|
|
966
993
|
Keep text inputs and independent selection widgets outside this action-only API.
|
|
967
994
|
Native child handlers and refs remain attached; Toolbar owns their tabIndex while
|
|
968
995
|
mounted and restores original attributes on cleanup. Without JavaScript, native
|
|
@@ -1109,6 +1136,7 @@ relative path, without reading their contents. Single mode replaces its selectio
|
|
|
1109
1136
|
|
|
1110
1137
|
AlertDialog (0.2.5) is a concise confirmation, composed from Flood's
|
|
1111
1138
|
native modal layer and Button. Supply title, description, trigger and onConfirm.
|
|
1139
|
+
Line breaks in the description separate distinct thoughts while allowing each line to wrap.
|
|
1112
1140
|
Cancel receives initial focus; Escape cancels; outside clicks do not dismiss.
|
|
1113
1141
|
Tab containment, background inertness, scroll lock and return focus reuse Dialog.
|
|
1114
1142
|
Translate confirmLabel/cancelLabel. onConfirm runs before requesting close, while
|
|
@@ -1145,7 +1173,8 @@ The CSS build assembles the original component styles into the shared stylesheet
|
|
|
1145
1173
|
|
|
1146
1174
|
|
|
1147
1175
|
Section headingLevel controls native h2–h6 semantics. Its independent headingSize
|
|
1148
|
-
uses the
|
|
1176
|
+
uses the Heading roles (group/section/page/large/display), defaulting to section.
|
|
1177
|
+
Choose group for a compact section inside a Drawer or another constrained panel.
|
|
1149
1178
|
Keep document hierarchy meaningful when changing visual emphasis.
|
|
1150
1179
|
|
|
1151
1180
|
## Editable and TagInput · 0.2.7
|
|
@@ -1192,8 +1221,11 @@ when a query changes; ready rows must match pageSize/totalRows. Selection defaul
|
|
|
1192
1221
|
to the supplied page in server mode and the full collection in client mode.
|
|
1193
1222
|
Server selectionScope="dataset" requires controlled selectedIds/onSelectionChange;
|
|
1194
1223
|
the host validates IDs outside the page. Checkboxes only select the current page.
|
|
1195
|
-
|
|
1196
|
-
|
|
1224
|
+
The host owns request execution and cancellation, remote query selection and
|
|
1225
|
+
virtualization. DataTable supports optional column visibility, width preferences,
|
|
1226
|
+
logical edge pinning and expandable record details. Use Sparkline for a compact
|
|
1227
|
+
trend cell. See [DataTable](../../docs/components/data-table.md) and
|
|
1228
|
+
[Sparkline](../../docs/components/sparkline.md).
|
|
1197
1229
|
|
|
1198
1230
|
DatePicker and DateRangePicker pass viewLabels to their calendars. Calendar panels
|
|
1199
1231
|
use minimal Popover chrome, with Escape/selection/trigger dismissal; there is no
|
package/dist/app-shell.js
CHANGED
|
@@ -5,7 +5,7 @@ const useBrowserLayoutEffect = typeof window === "undefined" ? useEffect : useLa
|
|
|
5
5
|
export const ApplicationTopBar = /* @__PURE__ */ forwardRef(function ApplicationTopBar({ start, end, bordered, className, ...props }, ref) {
|
|
6
6
|
return _jsxs("header", { ...props, ref: ref, className: ["flood-app-top-bar", className].filter(Boolean).join(" "), "data-bordered": bordered || undefined, children: [start != null && _jsx(Inline, { align: "center", gap: "2", className: "flood-app-top-bar__start", children: start }), end != null && _jsx(Inline, { align: "center", gap: "2", className: "flood-app-top-bar__end", children: end })] });
|
|
7
7
|
});
|
|
8
|
-
export const ApplicationShell = /* @__PURE__ */ forwardRef(function ApplicationShell({ navigation, navigationSide, navigationDrawer, topBar, footer, scroll, mainId, mainAs: Main = "main", mainLabel, mainRef, scrollLabel, scrollOrientation = "both", skipLabel, children, className, ...props }, ref) {
|
|
8
|
+
export const ApplicationShell = /* @__PURE__ */ forwardRef(function ApplicationShell({ navigation, navigationSide, navigationDrawer, topBar, footer, scroll, mainId, mainAs: Main = "main", mainLabel, mainRef, scrollLabel, scrollOrientation = "both", scrollContentInset = "scrollbar", skipLabel, children, className, ...props }, ref) {
|
|
9
9
|
const generatedId = useId();
|
|
10
10
|
const contentId = mainId ?? `flood-app-${generatedId}`;
|
|
11
11
|
const root = useRef(null);
|
|
@@ -61,6 +61,6 @@ export const ApplicationShell = /* @__PURE__ */ forwardRef(function ApplicationS
|
|
|
61
61
|
? topBar(navigationTrigger)
|
|
62
62
|
: _jsxs(_Fragment, { children: [navigationTrigger && _jsx(AppTopBar, { start: navigationTrigger }), topBar] });
|
|
63
63
|
return _jsxs("div", { ...props, ref: root, className: ["flood-app-shell", className].filter(Boolean).join(" "), "data-scroll": scroll, children: [_jsx(SkipLink, { href: `#${contentId}`, children: skipLabel }), navigationSide === "start" && navigationSlot, _jsxs("div", { className: "flood-app-shell__workspace", children: [chrome, _jsx(Main, { ref: mainRef, id: contentId, "aria-label": mainLabel, tabIndex: -1, className: "flood-app-shell__main", children: scroll === "content"
|
|
64
|
-
? _jsx(ScrollArea, { orientation: scrollOrientation, "aria-label": scrollLabel, className: "flood-app-shell__scroll", children: children })
|
|
64
|
+
? _jsx(ScrollArea, { orientation: scrollOrientation, contentInset: scrollContentInset, "aria-label": scrollLabel, className: "flood-app-shell__scroll", children: children })
|
|
65
65
|
: children }), footer] }), navigationSide === "end" && navigationSlot] });
|
|
66
66
|
});
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { forwardRef, useId, useImperativeHandle, useRef } from 'react';
|
|
3
3
|
import { Button, Field, Fieldset, IconSlot, Inline, Popover, SearchInput, Tag, Text } from './index.js';
|
|
4
4
|
/** Query chrome only: the host supplies filter drafts, accepted query and saved views. */
|
|
5
|
-
export const CollectionQueryBar = /* @__PURE__ */ forwardRef(function CollectionQueryBar({ label, searchValue, onSearchValueChange, searchLabel = 'Search', searchPlaceholder = 'Search…', clearSearchLabel = 'Clear search', appliedFilters = [], onRemoveFilter, appliedFiltersLabel = 'Applied filters', summaryPrefix, quickFilters, filterContent, filtersLabel = 'Filters', filterTitle = filtersLabel, filtersOpen, onFiltersOpenChange, filterPanelRef, filterPanelClassName, onClear, canClear = Boolean(searchValue || appliedFilters.length), clearLabel = 'Clear filters', controlSize = 'sm', disabled = false, hideLabel = true, className, ...props }, forwardedRef) {
|
|
5
|
+
export const CollectionQueryBar = /* @__PURE__ */ forwardRef(function CollectionQueryBar({ label, searchValue, onSearchValueChange, searchLabel = 'Search', searchPlaceholder = 'Search…', clearSearchLabel = 'Clear search', appliedFilters = [], onRemoveFilter, appliedFiltersLabel = 'Applied filters', summaryPrefix, quickFilters, actions, filterContent, filtersLabel = 'Filters', filterTitle = filtersLabel, filtersOpen, onFiltersOpenChange, filterPanelRef, filterPanelClassName, onClear, canClear = Boolean(searchValue || appliedFilters.length), clearLabel = 'Clear filters', controlSize = 'sm', disabled = false, hideLabel = true, className, ...props }, forwardedRef) {
|
|
6
6
|
const root = useRef(null);
|
|
7
7
|
useImperativeHandle(forwardedRef, () => root.current, []);
|
|
8
8
|
const generated = useId(), triggerId = `flood-collection-filters-${generated}`, searchId = `flood-collection-search-${generated}`;
|
|
@@ -21,6 +21,6 @@ export const CollectionQueryBar = /* @__PURE__ */ forwardRef(function Collection
|
|
|
21
21
|
return;
|
|
22
22
|
focusPersistent();
|
|
23
23
|
onRemoveFilter(filter.id);
|
|
24
|
-
} : undefined, children: filter.label }, filter.id))] }), (onClear || hasPanel) && _jsxs(Inline, { gap: "2", align: "center", className: "flood-collection-toolbar__commands", children: [onClear && _jsx(Button, { size: controlSize, variant: "outline", className: "flood-collection-toolbar__clear", "data-visible": canClear, disabled: disabled || !canClear, "aria-hidden": !canClear || undefined, onClick: () => { if (disabled || !canClear)
|
|
25
|
-
return; focusPersistent(); onClear(); }, children: clearLabel }), hasPanel && _jsx(Popover, { title: filterTitle, ref: filterPanelRef, className: filterPanelClassName, open: filtersOpen, onOpenChange: onFiltersOpenChange, disabled: disabled, align: "end", trigger: trigger => _jsxs(Button, { ...trigger, id: triggerId, variant: "outline", size: controlSize, children: [_jsx(IconSlot, { size: controlSize, children: _jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", "aria-hidden": "true", children: _jsx("path", { d: "M4 6h16M7 12h10m-7 6h4" }) }) }), filtersLabel] }), children: filterContent })] })] }) });
|
|
24
|
+
} : undefined, children: filter.label }, filter.id))] }), (onClear || hasPanel || actions) && _jsxs(Inline, { gap: "2", align: "center", className: "flood-collection-toolbar__commands", children: [onClear && _jsx(Button, { size: controlSize, variant: "outline", className: "flood-collection-toolbar__clear", "data-visible": canClear, disabled: disabled || !canClear, "aria-hidden": !canClear || undefined, onClick: () => { if (disabled || !canClear)
|
|
25
|
+
return; focusPersistent(); onClear(); }, children: clearLabel }), hasPanel && _jsx(Popover, { title: filterTitle, ref: filterPanelRef, className: filterPanelClassName, open: filtersOpen, onOpenChange: onFiltersOpenChange, disabled: disabled, align: "end", trigger: trigger => _jsxs(Button, { ...trigger, id: triggerId, variant: "outline", size: controlSize, children: [_jsx(IconSlot, { size: controlSize, children: _jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", "aria-hidden": "true", children: _jsx("path", { d: "M4 6h16M7 12h10m-7 6h4" }) }) }), filtersLabel] }), children: filterContent }), actions] })] }) });
|
|
26
26
|
});
|
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, 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
|
+
}
|