@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/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.13** 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
|
});
|
package/dist/box-plot.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef, useEffect, useId, useLayoutEffect, useRef, useState } from "react";
|
|
3
|
+
import { Surface, Text, VisuallyHidden } from "./index.js";
|
|
4
|
+
import { placeOverlay } from "./overlay-runtime.js";
|
|
5
|
+
const number = new Intl.NumberFormat("en", { maximumSignificantDigits: 21 });
|
|
6
|
+
const useChartLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
|
|
7
|
+
const stats = ["low", "q1", "median", "q3", "high"];
|
|
8
|
+
const defaults = {
|
|
9
|
+
low: "Lower whisker", q1: "Q1", median: "Median", q3: "Q3", high: "Upper whisker", outliers: "Outliers",
|
|
10
|
+
};
|
|
11
|
+
const tooltipRows = [[2], [1, 3], [0, 4], [5]];
|
|
12
|
+
export const DistributionBoxes = /* @__PURE__ */ forwardRef(function DistributionBoxes({ label, items, domain, formatValue = number.format, formatAxisValue = formatValue, statisticLabels, emptyLabel = "No observations", missingLabel = "No data", navigationLabel = "Use the arrow keys to explore groups, or Home and End to jump.", className, ...props }, ref) {
|
|
13
|
+
const instructions = useId();
|
|
14
|
+
const plot = useRef(null);
|
|
15
|
+
const tooltip = useRef(null);
|
|
16
|
+
const [cursor, setCursor] = useState(0);
|
|
17
|
+
const [hover, setHover] = useState();
|
|
18
|
+
const [touch, setTouch] = useState();
|
|
19
|
+
const [focused, setFocused] = useState(false);
|
|
20
|
+
const ids = new Set();
|
|
21
|
+
let min = Infinity;
|
|
22
|
+
let max = -Infinity;
|
|
23
|
+
for (const { id, label: name, summary, outliers = [] } of items) {
|
|
24
|
+
if (!id.trim() || ids.has(id) || !name.trim())
|
|
25
|
+
throw new Error("Flood BoxPlot needs unique IDs and nonempty labels");
|
|
26
|
+
ids.add(id);
|
|
27
|
+
if (!summary) {
|
|
28
|
+
if (outliers.length)
|
|
29
|
+
throw new RangeError("Flood BoxPlot outliers require a summary");
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
const values = stats.map(key => summary[key]);
|
|
33
|
+
if (values.some((value, i) => !Number.isFinite(value) || (i > 0 && value < values[i - 1]))) {
|
|
34
|
+
throw new RangeError("Flood BoxPlot statistics must be finite and ordered from low to high");
|
|
35
|
+
}
|
|
36
|
+
min = Math.min(min, summary.low);
|
|
37
|
+
max = Math.max(max, summary.high);
|
|
38
|
+
for (const value of outliers) {
|
|
39
|
+
if (!Number.isFinite(value) || (value >= summary.low && value <= summary.high)) {
|
|
40
|
+
throw new RangeError("Flood BoxPlot outliers must be finite and outside the whiskers");
|
|
41
|
+
}
|
|
42
|
+
min = Math.min(min, value);
|
|
43
|
+
max = Math.max(max, value);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (domain && (!Number.isFinite(domain[0]) || !Number.isFinite(domain[1]) ||
|
|
47
|
+
domain[0] >= domain[1] || domain[0] > min || domain[1] < max)) {
|
|
48
|
+
throw new RangeError("Flood BoxPlot domain must have finite increasing bounds containing every observation");
|
|
49
|
+
}
|
|
50
|
+
const hasData = min <= max;
|
|
51
|
+
const [floor, ceiling] = domain ?? [min, max];
|
|
52
|
+
const span = ceiling - floor;
|
|
53
|
+
const position = (value) => {
|
|
54
|
+
if (span === 0)
|
|
55
|
+
return 50;
|
|
56
|
+
return 100 * (Number.isFinite(span) ? (value - floor) / span : (value / 2 - floor / 2) / (ceiling / 2 - floor / 2));
|
|
57
|
+
};
|
|
58
|
+
const last = items.length - 1;
|
|
59
|
+
const active = Math.min(hover ?? touch ?? cursor, last);
|
|
60
|
+
const item = items[active];
|
|
61
|
+
const summary = item?.summary;
|
|
62
|
+
const inspecting = hover !== undefined || touch !== undefined || focused;
|
|
63
|
+
const readouts = summary ? stats.map(key => ({
|
|
64
|
+
label: statisticLabels?.[key] ?? defaults[key], value: formatValue(summary[key]),
|
|
65
|
+
})) : [];
|
|
66
|
+
if (item?.outliers?.length) {
|
|
67
|
+
readouts.push({ label: statisticLabels?.outliers ?? defaults.outliers, value: number.format(item.outliers.length) });
|
|
68
|
+
}
|
|
69
|
+
const valueText = [item?.label, ...(summary ? readouts.map(entry => `${entry.label}: ${entry.value}`) : [missingLabel])].join(", ");
|
|
70
|
+
useChartLayoutEffect(() => {
|
|
71
|
+
const panel = tooltip.current;
|
|
72
|
+
const frame = plot.current;
|
|
73
|
+
if (!panel || !frame)
|
|
74
|
+
return;
|
|
75
|
+
const mark = frame.querySelector("[data-active] .flood-box-plot__median, [data-active] .flood-box-plot__missing");
|
|
76
|
+
if (!mark)
|
|
77
|
+
return;
|
|
78
|
+
const positionTooltip = () => {
|
|
79
|
+
const bounds = frame.getBoundingClientRect();
|
|
80
|
+
if (!bounds.width || !frame.offsetWidth)
|
|
81
|
+
return;
|
|
82
|
+
const marker = mark.getBoundingClientRect();
|
|
83
|
+
const scale = bounds.width / frame.offsetWidth;
|
|
84
|
+
const gap = parseFloat(getComputedStyle(panel).columnGap) * scale;
|
|
85
|
+
const size = panel.getBoundingClientRect();
|
|
86
|
+
const x = (marker.left + marker.right) / 2;
|
|
87
|
+
const y = (marker.top + marker.bottom) / 2;
|
|
88
|
+
const anchor = { left: x, right: x, top: y, bottom: y, width: 0, height: 0 };
|
|
89
|
+
const horizontalRoom = size.width + 2 * gap;
|
|
90
|
+
const side = bounds.right - x >= horizontalRoom ? "right"
|
|
91
|
+
: x - bounds.left >= horizontalRoom ? "left"
|
|
92
|
+
: y - bounds.top < bounds.bottom - y ? "bottom" : "top";
|
|
93
|
+
const { left, top } = placeOverlay(anchor, size, bounds, side, "center", gap);
|
|
94
|
+
panel.style.left = `${(left - bounds.left) / scale}px`;
|
|
95
|
+
panel.style.top = `${(top - bounds.top) / scale}px`;
|
|
96
|
+
panel.dataset.positioned = "";
|
|
97
|
+
};
|
|
98
|
+
positionTooltip();
|
|
99
|
+
if (typeof ResizeObserver === "undefined")
|
|
100
|
+
return;
|
|
101
|
+
const observer = new ResizeObserver(positionTooltip);
|
|
102
|
+
observer.observe(frame);
|
|
103
|
+
observer.observe(panel);
|
|
104
|
+
return () => observer.disconnect();
|
|
105
|
+
}, [active, inspecting, item, formatValue, floor, ceiling]);
|
|
106
|
+
const navigate = (event) => {
|
|
107
|
+
let next;
|
|
108
|
+
switch (event.key) {
|
|
109
|
+
case "ArrowRight":
|
|
110
|
+
case "ArrowUp":
|
|
111
|
+
next = active + 1;
|
|
112
|
+
break;
|
|
113
|
+
case "ArrowLeft":
|
|
114
|
+
case "ArrowDown":
|
|
115
|
+
next = active - 1;
|
|
116
|
+
break;
|
|
117
|
+
case "Home":
|
|
118
|
+
next = 0;
|
|
119
|
+
break;
|
|
120
|
+
case "End":
|
|
121
|
+
next = last;
|
|
122
|
+
break;
|
|
123
|
+
case "Escape":
|
|
124
|
+
if (inspecting)
|
|
125
|
+
event.preventDefault();
|
|
126
|
+
setHover(undefined);
|
|
127
|
+
setTouch(undefined);
|
|
128
|
+
setFocused(false);
|
|
129
|
+
return;
|
|
130
|
+
default: return;
|
|
131
|
+
}
|
|
132
|
+
event.preventDefault();
|
|
133
|
+
setHover(undefined);
|
|
134
|
+
setTouch(undefined);
|
|
135
|
+
setFocused(true);
|
|
136
|
+
setCursor(Math.max(0, Math.min(last, next)));
|
|
137
|
+
};
|
|
138
|
+
return _jsx("div", { ...props, ref: ref, className: ["flood-box-plot", className].filter(Boolean).join(" "), role: "group", "aria-label": label, children: hasData ? _jsxs(_Fragment, { children: [last > 0 && _jsx(VisuallyHidden, { id: instructions, children: navigationLabel }), _jsxs("div", { ref: plot, className: "flood-box-plot__plot", dir: "ltr", role: last > 0 ? "slider" : "img", tabIndex: last > 0 ? 0 : undefined, "aria-label": last > 0 ? label : `${label}. ${valueText}`, "aria-describedby": last > 0 ? instructions : undefined, "aria-valuemin": last > 0 ? 1 : undefined, "aria-valuemax": last > 0 ? items.length : undefined, "aria-valuenow": last > 0 ? active + 1 : undefined, "aria-valuetext": last > 0 ? valueText : undefined, onFocus: event => setFocused(event.currentTarget.matches(":focus-visible")), onBlur: () => setFocused(false), onKeyDown: navigate, onPointerLeave: () => setHover(undefined), children: [_jsx("div", { className: "flood-box-plot__rows", "aria-hidden": "true", children: items.map(({ id, label: name, summary: values, outliers }, index) => _jsxs("div", { className: "flood-box-plot__row", "data-active": inspecting && index === active || undefined, onPointerEnter: event => { if (event.pointerType !== "touch")
|
|
139
|
+
setHover(index); }, onPointerUp: event => { if (event.pointerType === "touch")
|
|
140
|
+
setTouch(previous => previous === index ? undefined : index); }, children: [_jsx(Text, { className: "flood-box-plot__category", size: "helper", tone: "subtle", dir: "auto", title: name, children: name }), _jsxs("div", { className: "flood-box-plot__track", children: [values && _jsxs(_Fragment, { children: [_jsx("span", { className: "flood-box-plot__whisker", "data-side": "start", style: { left: `${position(values.low)}%`, right: `${100 - position(values.q1)}%` } }), _jsx("span", { className: "flood-box-plot__whisker", "data-side": "end", style: { left: `${position(values.q3)}%`, right: `${100 - position(values.high)}%` } }), _jsx("span", { className: "flood-box-plot__box", style: { left: `${position(values.q1)}%`, right: `${100 - position(values.q3)}%` } }), _jsx("span", { className: "flood-box-plot__median", style: { left: `${position(values.median)}%` } }), outliers?.map((value, i) => _jsx("span", { className: "flood-box-plot__outlier", style: { left: `${position(value)}%` } }, i))] }), !values && _jsx("span", { className: "flood-box-plot__missing" })] })] }, id)) }), _jsxs("div", { className: "flood-box-plot__axis", "aria-hidden": "true", children: [_jsx(Text, { size: "helper", tone: "subtle", children: formatAxisValue(floor) }), floor !== ceiling && _jsxs(_Fragment, { children: [_jsx(Text, { size: "helper", tone: "subtle", children: formatAxisValue(floor / 2 + ceiling / 2) }), _jsx(Text, { size: "helper", tone: "subtle", children: formatAxisValue(ceiling) })] })] }), inspecting && item && _jsxs(Surface, { ref: tooltip, className: "flood-chart__tooltip flood-box-plot__tooltip", "aria-hidden": "true", padding: "2", elevation: "raised", children: [_jsx(Text, { size: "helper", dir: "auto", children: item.label }), summary ? tooltipRows.map(([start, end]) => {
|
|
141
|
+
const first = readouts[start];
|
|
142
|
+
if (!first)
|
|
143
|
+
return null;
|
|
144
|
+
const last = end === undefined ? undefined : readouts[end];
|
|
145
|
+
return _jsxs("div", { className: "flood-box-plot__reading", children: [_jsx(Text, { size: "helper", tone: "subtle", dir: "auto", children: start === 0 ? statisticLabels?.whiskers ?? "Whiskers" :
|
|
146
|
+
last ? `${first.label}–${last.label}` : first.label }), _jsx(Text, { size: "helper", tone: start === 2 ? "default" : "subtle", children: last ? _jsxs(_Fragment, { children: [_jsxs("span", { children: [first.value, "\u2013"] }), _jsx("wbr", {}), _jsx("span", { children: last.value })] }) : first.value })] }, start);
|
|
147
|
+
}) : _jsx(Text, { size: "helper", tone: "subtle", children: missingLabel })] })] })] }) : _jsx(Text, { className: "flood-box-plot__empty", size: "compact", tone: "subtle", children: emptyLabel }) });
|
|
148
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef } from "react";
|
|
3
|
+
import { Inline, Meter, Stack, Text } from "./index.js";
|
|
4
|
+
const defaultNumber = new Intl.NumberFormat("en", { maximumSignificantDigits: 21 });
|
|
5
|
+
export const BulletPlot = /* @__PURE__ */ forwardRef(function BulletPlot({ label, items, max, formatValue = defaultNumber.format, valueLabel = "Actual", targetLabel = "Target", emptyLabel = "No data", className, ...props }, ref) {
|
|
6
|
+
const ids = new Set();
|
|
7
|
+
let largest = 0;
|
|
8
|
+
for (const { id, label: itemLabel, value, target } of items) {
|
|
9
|
+
if (!id.trim() || ids.has(id) || !itemLabel.trim()) {
|
|
10
|
+
throw new Error("Flood BulletChart needs unique IDs and nonempty row labels");
|
|
11
|
+
}
|
|
12
|
+
if (!Number.isFinite(value) || value < 0 || !Number.isFinite(target) || target < 0) {
|
|
13
|
+
throw new RangeError("Flood BulletChart actual and target values must be finite and nonnegative");
|
|
14
|
+
}
|
|
15
|
+
ids.add(id);
|
|
16
|
+
largest = Math.max(largest, value, target);
|
|
17
|
+
}
|
|
18
|
+
const limit = max ?? (largest || 1);
|
|
19
|
+
if (!Number.isFinite(limit) || limit <= 0 || limit < largest) {
|
|
20
|
+
throw new RangeError("Flood BulletChart max must be finite, positive and at least the largest actual or target");
|
|
21
|
+
}
|
|
22
|
+
return _jsx("div", { ...props, ref: ref, className: ["flood-bullet-chart", className].filter(Boolean).join(" "), role: "group", "aria-label": label, children: items.length ? _jsx("ul", { className: "flood-bullet-chart__items", role: "list", children: items.map(item => {
|
|
23
|
+
const actual = formatValue(item.value);
|
|
24
|
+
const target = formatValue(item.target);
|
|
25
|
+
return _jsx("li", { children: _jsxs(Stack, { gap: "2", children: [_jsxs(Inline, { gap: "2", align: "baseline", className: "flood-bullet-chart__caption", "aria-hidden": "true", children: [_jsx(Text, { size: "compact", children: item.label }), _jsxs(Inline, { gap: "2", align: "baseline", className: "flood-bullet-chart__numbers", children: [_jsx(Text, { size: "compact", className: "flood-bullet-chart__actual", children: actual }), _jsxs(Text, { size: "helper", tone: "subtle", children: [targetLabel, " ", target] })] })] }), _jsxs("div", { className: "flood-bullet-chart__plot", "data-zero": item.value === 0 || undefined, children: [_jsx(Meter, { label: item.label, value: item.value, max: limit, tone: "accent", hideLabel: true, valueText: `${valueLabel}: ${actual}, ${targetLabel}: ${target}` }), _jsx("span", { "aria-hidden": "true", className: "flood-bullet-chart__target", style: { "--flood-bullet-target": `${item.target / limit * 100}%` } })] })] }) }, item.id);
|
|
26
|
+
}) }) : _jsx(Text, { size: "compact", tone: "subtle", children: emptyLabel }) });
|
|
27
|
+
});
|
package/dist/charts.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type BarChartProps, type BarListProps, type DonutChartProps, type HeatmapProps, type LineChartProps } from "./index.js";
|
|
2
|
+
export declare const SeriesPlot: import("react").ForwardRefExoticComponent<LineChartProps & Required<Pick<LineChartProps, "curve" | "emptyLabel" | "missingLabel" | "navigationLabel" | "variant">> & import("react").RefAttributes<HTMLDivElement>>;
|
|
3
|
+
export declare const GroupedBars: import("react").ForwardRefExoticComponent<BarChartProps & Required<Pick<BarChartProps, "emptyLabel" | "missingLabel" | "navigationLabel">> & import("react").RefAttributes<HTMLDivElement>>;
|
|
4
|
+
export declare const ShareRing: import("react").ForwardRefExoticComponent<DonutChartProps & Required<Pick<DonutChartProps, "emptyLabel" | "navigationLabel" | "totalLabel">> & import("react").RefAttributes<HTMLDivElement>>;
|
|
5
|
+
export declare const HeatmapGrid: import("react").ForwardRefExoticComponent<HeatmapProps & Required<Pick<HeatmapProps, "emptyLabel" | "missingLabel">> & import("react").RefAttributes<HTMLDivElement>>;
|
|
6
|
+
export declare const RankedBars: import("react").ForwardRefExoticComponent<BarListProps & import("react").RefAttributes<HTMLDivElement>>;
|