@flood-md/react 0.2.12 → 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.
@@ -0,0 +1,2 @@
1
+ import { type HistogramProps } from "./index.js";
2
+ export declare const HistogramPlot: import("react").ForwardRefExoticComponent<HistogramProps & import("react").RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,78 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { forwardRef, useId, useState } from "react";
3
+ import { Inline, Surface, Text, VisuallyHidden } from "./index.js";
4
+ const defaultBoundary = new Intl.NumberFormat("en", { maximumSignificantDigits: 12 });
5
+ const defaultCount = new Intl.NumberFormat("en", { maximumFractionDigits: 0 });
6
+ export const HistogramPlot = /* @__PURE__ */ forwardRef(function HistogramPlot({ label, xLabel, showXAxisLabel = true, start, binWidth, counts, maxCount, countLabel = "Count", formatBoundary = defaultBoundary.format, formatCount = defaultCount.format, emptyLabel = "No observations", navigationLabel = "Use arrow keys to explore intervals, or Home and End to jump.", className, ...props }, ref) {
7
+ const instructions = useId();
8
+ const [cursor, setCursor] = useState(0);
9
+ const [hovered, setHovered] = useState();
10
+ const [tapped, setTapped] = useState();
11
+ const [focused, setFocused] = useState(false);
12
+ if (!Number.isFinite(start) || !Number.isFinite(binWidth) || binWidth <= 0) {
13
+ throw new RangeError("Flood Histogram requires a finite start and positive finite bin width");
14
+ }
15
+ let largest = 0;
16
+ let previous = start;
17
+ const bins = counts.map((count, i) => {
18
+ const end = start + (i + 1) * binWidth;
19
+ if (!Number.isSafeInteger(count) || count < 0 || !Number.isFinite(end) || end <= previous) {
20
+ throw new RangeError("Flood Histogram requires nonnegative integer counts and increasing finite boundaries");
21
+ }
22
+ const bin = { start: previous, end, count };
23
+ previous = end;
24
+ largest = Math.max(largest, count);
25
+ return bin;
26
+ });
27
+ const limit = maxCount ?? (largest || 1);
28
+ if (!Number.isFinite(limit) || limit <= 0 || limit < largest) {
29
+ throw new RangeError("Flood Histogram maxCount must be finite, positive and at least the largest count");
30
+ }
31
+ const last = bins.length - 1;
32
+ const active = Math.min(last, hovered ?? tapped ?? cursor);
33
+ const selected = bins[active];
34
+ const inspecting = hovered !== undefined || tapped !== undefined || focused;
35
+ const interval = selected && `${formatBoundary(selected.start)}–${formatBoundary(selected.end)}`;
36
+ const value = selected && formatCount(selected.count);
37
+ const valueText = selected && `${xLabel}: ${interval}, ${countLabel}: ${value}`;
38
+ const middle = bins[Math.floor(bins.length / 2)];
39
+ const navigate = (event) => {
40
+ let next;
41
+ switch (event.key) {
42
+ case "ArrowLeft":
43
+ case "ArrowDown":
44
+ next = active - 1;
45
+ break;
46
+ case "ArrowRight":
47
+ case "ArrowUp":
48
+ next = active + 1;
49
+ break;
50
+ case "Home":
51
+ next = 0;
52
+ break;
53
+ case "End":
54
+ next = last;
55
+ break;
56
+ case "Escape":
57
+ if (inspecting)
58
+ event.preventDefault();
59
+ setHovered(undefined);
60
+ setTapped(undefined);
61
+ setFocused(false);
62
+ return;
63
+ default: return;
64
+ }
65
+ event.preventDefault();
66
+ setHovered(undefined);
67
+ setTapped(undefined);
68
+ setFocused(true);
69
+ setCursor(Math.max(0, Math.min(last, next)));
70
+ };
71
+ return _jsx("div", { ...props, ref: ref, className: ["flood-histogram", className].filter(Boolean).join(" "), role: "group", "aria-label": label, children: selected ? _jsxs(_Fragment, { children: [last > 0 && _jsx(VisuallyHidden, { id: instructions, children: navigationLabel }), _jsxs("div", { className: "flood-histogram__plot", dir: "ltr", role: last > 0 ? "slider" : "img", tabIndex: last > 0 ? 0 : undefined, "aria-label": last > 0 ? label : valueText, "aria-describedby": last > 0 ? instructions : undefined, "aria-valuemin": last > 0 ? 1 : undefined, "aria-valuemax": last > 0 ? bins.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: () => setHovered(undefined), children: [_jsx("div", { className: "flood-histogram__columns", "aria-hidden": "true", children: bins.map((bin, i) => _jsx("div", { className: "flood-histogram__bin", "data-active": inspecting && i === active || undefined, onPointerEnter: event => { if (event.pointerType !== "touch")
72
+ setHovered(i); }, onPointerUp: event => { if (event.pointerType === "touch")
73
+ setTapped(current => current === i ? undefined : i); }, children: _jsx("span", { className: "flood-histogram__bar", "data-zero": bin.count === 0 || undefined, style: { "--flood-histogram-bin-height": `${bin.count / limit * 100}%` } }) }, i)) }), inspecting && _jsxs(Surface, { className: "flood-chart__tooltip flood-histogram__tooltip", padding: "2", elevation: "raised", "aria-hidden": "true", style: {
74
+ "--flood-histogram-active": `${(active + .5) / bins.length * 100}%`,
75
+ "--flood-histogram-half-bin": `${50 / bins.length}%`,
76
+ "--flood-histogram-tip-side": active >= bins.length / 2 ? -1 : 0,
77
+ }, children: [_jsx(Text, { size: "helper", children: interval }), _jsxs(Inline, { gap: "2", align: "baseline", className: "flood-histogram__tooltip-count", children: [_jsx(Text, { size: "helper", tone: "subtle", children: countLabel }), _jsx(Text, { size: "helper", children: value })] })] })] }), _jsxs("div", { className: "flood-histogram__axis", dir: "ltr", "aria-hidden": "true", children: [_jsx(Text, { size: "helper", tone: "subtle", children: formatBoundary(start) }), last > 0 && _jsx(Text, { className: "flood-histogram__mid-label", size: "helper", tone: "subtle", style: { left: `${Math.floor(bins.length / 2) / bins.length * 100}%` }, children: formatBoundary(middle.start) }), _jsx(Text, { size: "helper", tone: "subtle", children: formatBoundary(previous) })] }), showXAxisLabel && _jsx(Text, { className: "flood-histogram__axis-label", size: "helper", tone: "subtle", dir: "auto", "aria-hidden": "true", children: xLabel })] }) : _jsx(Text, { className: "flood-histogram__empty", size: "compact", tone: "subtle", children: emptyLabel }) });
78
+ });
package/dist/index.d.ts CHANGED
@@ -80,6 +80,8 @@ export interface CollectionToolbarProps extends Omit<FieldsetProps, "children" |
80
80
  onRemoveFilter?: (id: string) => void;
81
81
  appliedFiltersLabel?: string;
82
82
  summaryPrefix?: ReactNode;
83
+ /** Align search and filters as a group to the end of a shared control row. */
84
+ align?: "stretch" | "end";
83
85
  /** At most two task-important quick filters; move additional properties into filterContent. */
84
86
  quickFilters?: ReactNode;
85
87
  /** Additional commands beside Filters in the same tool row. */
@@ -409,6 +411,253 @@ export interface SparklineProps extends Omit<HTMLAttributes<HTMLDivElement>, "ch
409
411
  }
410
412
  /** @since 0.2.11 */
411
413
  export declare const Sparkline: import("react").ForwardRefExoticComponent<SparklineProps & import("react").RefAttributes<HTMLDivElement>>;
414
+ export interface LineChartSeries {
415
+ id: string;
416
+ label: string;
417
+ /** One observation per category. Missing and nonfinite values break the line. */
418
+ values: readonly (number | null)[];
419
+ }
420
+ export type ChartSeries = LineChartSeries;
421
+ export interface LineChartProps extends Omit<HTMLAttributes<HTMLDivElement>, "children" | "role" | "aria-label"> {
422
+ label: string;
423
+ /** Equally spaced categories in the order supplied. */
424
+ categories: readonly string[];
425
+ /** One to four comparable series sharing a scale. An empty array is also supported. */
426
+ series: readonly LineChartSeries[];
427
+ /** Shared finite bounds containing every observation. */
428
+ domain?: readonly [number, number];
429
+ variant?: "line" | "area";
430
+ /** Shape-preserving interpolation between observations. */
431
+ curve?: "linear" | "smooth";
432
+ /** Show the numeric scale and horizontal reference lines. */
433
+ showYAxis?: boolean;
434
+ /** Show the series key below the plot. Defaults to false. */
435
+ showLegend?: boolean;
436
+ formatValue?: (value: number) => string;
437
+ emptyLabel?: string;
438
+ missingLabel?: string;
439
+ navigationLabel?: string;
440
+ }
441
+ export declare const LineChart: import("react").ForwardRefExoticComponent<LineChartProps & import("react").RefAttributes<HTMLDivElement>>;
442
+ export interface BarChartProps extends Omit<HTMLAttributes<HTMLDivElement>, "children" | "role" | "aria-label"> {
443
+ label: string;
444
+ categories: readonly string[];
445
+ /** Compare series side by side, by cumulative magnitude, or by share of each total. Defaults to grouped. */
446
+ layout?: "grouped" | "stacked" | "percent";
447
+ /** One to four grouped series. Use null for a missing value. */
448
+ series: readonly ChartSeries[];
449
+ /** Finite bounds containing zero and each observation or signed stack extent. Percent uses a fixed scale. */
450
+ domain?: readonly [number, number];
451
+ /** Show the series key below the plot. Defaults to false. */
452
+ showLegend?: boolean;
453
+ formatValue?: (value: number) => string;
454
+ /** Format a fraction from 0 to 1 in percent layout. */
455
+ formatShare?: (share: number) => string;
456
+ emptyLabel?: string;
457
+ missingLabel?: string;
458
+ navigationLabel?: string;
459
+ }
460
+ export declare const BarChart: import("react").ForwardRefExoticComponent<BarChartProps & import("react").RefAttributes<HTMLDivElement>>;
461
+ export interface DonutChartItem {
462
+ id: string;
463
+ label: string;
464
+ /** A finite nonnegative part of the whole. */
465
+ value: number;
466
+ }
467
+ export interface DonutChartProps extends Omit<HTMLAttributes<HTMLDivElement>, "children" | "role" | "aria-label"> {
468
+ label: string;
469
+ /** Up to six categories in display order. */
470
+ items: readonly DonutChartItem[];
471
+ totalLabel?: string;
472
+ emptyLabel?: string;
473
+ navigationLabel?: string;
474
+ showLegend?: boolean;
475
+ formatValue?: (value: number) => string;
476
+ /** Receives the share as a fraction from 0 to 1. */
477
+ formatShare?: (share: number) => string;
478
+ }
479
+ export declare const DonutChart: import("react").ForwardRefExoticComponent<DonutChartProps & import("react").RefAttributes<HTMLDivElement>>;
480
+ export interface HeatmapProps extends Omit<HTMLAttributes<HTMLDivElement>, "children"> {
481
+ label: string;
482
+ columns: readonly string[];
483
+ rows: readonly {
484
+ id: string;
485
+ label: string;
486
+ values: readonly (number | null)[];
487
+ }[];
488
+ /** Shared zero-based scale. Defaults to the largest observed value. */
489
+ max?: number;
490
+ formatValue?: (value: number) => string;
491
+ missingLabel?: string;
492
+ emptyLabel?: string;
493
+ }
494
+ export declare const Heatmap: import("react").ForwardRefExoticComponent<HeatmapProps & import("react").RefAttributes<HTMLDivElement>>;
495
+ export interface ScatterChartPoint {
496
+ id: string;
497
+ label?: string;
498
+ x: number;
499
+ y: number;
500
+ }
501
+ export interface ScatterChartProps extends Omit<HTMLAttributes<HTMLDivElement>, "children" | "role" | "aria-label"> {
502
+ label: string;
503
+ xLabel: string;
504
+ yLabel: string;
505
+ /** Observations in keyboard navigation order, with stable, unique IDs. */
506
+ points: readonly ScatterChartPoint[];
507
+ /** Finite, increasing bounds that contain all x observations. */
508
+ xDomain?: readonly [number, number];
509
+ /** Finite, increasing bounds that contain all y observations. */
510
+ yDomain?: readonly [number, number];
511
+ formatX?: (value: number) => string;
512
+ formatY?: (value: number) => string;
513
+ emptyLabel?: string;
514
+ navigationLabel?: string;
515
+ }
516
+ export declare const ScatterChart: import("react").ForwardRefExoticComponent<ScatterChartProps & import("react").RefAttributes<HTMLDivElement>>;
517
+ export interface RangeChartItem {
518
+ id: string;
519
+ label: string;
520
+ /** A missing bound leaves an empty category. */
521
+ low: number | null;
522
+ high: number | null;
523
+ /** Optional observed value within the interval, such as a median. */
524
+ midpoint?: number;
525
+ }
526
+ export interface RangeChartProps extends Omit<HTMLAttributes<HTMLDivElement>, "children" | "role" | "aria-label"> {
527
+ label: string;
528
+ items: readonly RangeChartItem[];
529
+ /** Finite increasing bounds containing all complete intervals. */
530
+ domain?: readonly [number, number];
531
+ formatValue?: (value: number) => string;
532
+ lowLabel?: string;
533
+ highLabel?: string;
534
+ midpointLabel?: string;
535
+ emptyLabel?: string;
536
+ missingLabel?: string;
537
+ navigationLabel?: string;
538
+ }
539
+ export declare const RangeChart: import("react").ForwardRefExoticComponent<RangeChartProps & import("react").RefAttributes<HTMLDivElement>>;
540
+ export interface DumbbellChartItem {
541
+ id: string;
542
+ label: string;
543
+ before: number | null;
544
+ after: number | null;
545
+ }
546
+ export interface DumbbellChartProps extends Omit<HTMLAttributes<HTMLDivElement>, "children" | "role" | "aria-label"> {
547
+ label: string;
548
+ items: readonly DumbbellChartItem[];
549
+ /** Finite, increasing bounds containing all observed endpoints. */
550
+ domain?: readonly [number, number];
551
+ beforeLabel?: string;
552
+ afterLabel?: string;
553
+ missingLabel?: string;
554
+ emptyLabel?: string;
555
+ formatValue?: (value: number) => string;
556
+ navigationLabel?: string;
557
+ }
558
+ export declare const DumbbellChart: import("react").ForwardRefExoticComponent<DumbbellChartProps & import("react").RefAttributes<HTMLDivElement>>;
559
+ export interface BoxPlotSummary {
560
+ low: number;
561
+ q1: number;
562
+ median: number;
563
+ q3: number;
564
+ high: number;
565
+ }
566
+ export interface BoxPlotItem {
567
+ id: string;
568
+ label: string;
569
+ /** Ordered whiskers, quartiles and median. Null preserves an empty row. */
570
+ summary: BoxPlotSummary | null;
571
+ /** Optional observations beyond the supplied whiskers. */
572
+ outliers?: readonly number[];
573
+ }
574
+ export interface BoxPlotProps extends Omit<HTMLAttributes<HTMLDivElement>, "children" | "role" | "aria-label"> {
575
+ label: string;
576
+ items: readonly BoxPlotItem[];
577
+ /** Finite increasing bounds containing every observation. */
578
+ domain?: readonly [number, number];
579
+ formatValue?: (value: number) => string;
580
+ /** Format axis ticks independently of exact values. Defaults to formatValue. */
581
+ formatAxisValue?: (value: number) => string;
582
+ emptyLabel?: string;
583
+ missingLabel?: string;
584
+ navigationLabel?: string;
585
+ statisticLabels?: Partial<Record<keyof BoxPlotSummary | "outliers" | "whiskers", string>>;
586
+ }
587
+ export declare const BoxPlot: import("react").ForwardRefExoticComponent<BoxPlotProps & import("react").RefAttributes<HTMLDivElement>>;
588
+ export interface BulletChartItem {
589
+ id: string;
590
+ label: string;
591
+ value: number;
592
+ target: number;
593
+ }
594
+ export interface BulletChartProps extends Omit<HTMLAttributes<HTMLDivElement>, "children" | "role" | "aria-label"> {
595
+ label: string;
596
+ items: readonly BulletChartItem[];
597
+ /** Shared scale maximum. Defaults to the largest actual or target. */
598
+ max?: number;
599
+ formatValue?: (value: number) => string;
600
+ valueLabel?: string;
601
+ targetLabel?: string;
602
+ emptyLabel?: string;
603
+ }
604
+ export declare const BulletChart: import("react").ForwardRefExoticComponent<BulletChartProps & import("react").RefAttributes<HTMLDivElement>>;
605
+ export interface WaterfallChartStep {
606
+ id: string;
607
+ label: string;
608
+ value: number;
609
+ /** A change adjusts the running total. A total is an absolute checkpoint. */
610
+ kind?: "change" | "total";
611
+ }
612
+ export interface WaterfallChartProps extends Omit<HTMLAttributes<HTMLDivElement>, "children" | "role" | "aria-label"> {
613
+ label: string;
614
+ steps: readonly WaterfallChartStep[];
615
+ /** Finite increasing bounds containing zero and every running total. */
616
+ domain?: readonly [number, number];
617
+ formatValue?: (value: number) => string;
618
+ changeLabel?: string;
619
+ totalLabel?: string;
620
+ emptyLabel?: string;
621
+ navigationLabel?: string;
622
+ }
623
+ export declare const WaterfallChart: import("react").ForwardRefExoticComponent<WaterfallChartProps & import("react").RefAttributes<HTMLDivElement>>;
624
+ export interface HistogramProps extends Omit<HTMLAttributes<HTMLDivElement>, "children" | "role" | "aria-label"> {
625
+ label: string;
626
+ /** Name and unit for the numeric axis. */
627
+ xLabel: string;
628
+ /** Show the axis title below the numeric ticks. Defaults to true. */
629
+ showXAxisLabel?: boolean;
630
+ /** Inclusive lower boundary of the first bin. */
631
+ start: number;
632
+ /** Positive width shared by all bins. */
633
+ binWidth: number;
634
+ /** Counts in ascending bin order. Zero is a measured empty bin. */
635
+ counts: readonly number[];
636
+ /** Common count scale for comparing histograms. Defaults to the largest count or one. */
637
+ maxCount?: number;
638
+ countLabel?: string;
639
+ formatBoundary?: (value: number) => string;
640
+ formatCount?: (count: number) => string;
641
+ emptyLabel?: string;
642
+ navigationLabel?: string;
643
+ }
644
+ export declare const Histogram: import("react").ForwardRefExoticComponent<HistogramProps & import("react").RefAttributes<HTMLDivElement>>;
645
+ export interface BarListItem {
646
+ id: string;
647
+ label: string;
648
+ value: number;
649
+ valueText?: string;
650
+ }
651
+ export interface BarListProps extends Omit<HTMLAttributes<HTMLDivElement>, "children" | "role" | "aria-label"> {
652
+ label: string;
653
+ /** Nonnegative values in display order. */
654
+ items: readonly BarListItem[];
655
+ /** Shared positive upper bound. Defaults to the largest value, or 1 when all are zero. */
656
+ max?: number;
657
+ tone?: SemanticTone;
658
+ emptyLabel?: string;
659
+ }
660
+ export declare const BarList: import("react").ForwardRefExoticComponent<BarListProps & import("react").RefAttributes<HTMLDivElement>>;
412
661
  export interface DataListProps extends Omit<HTMLAttributes<HTMLUListElement>, "role"> {
413
662
  density?: "compact" | "comfortable";
414
663
  dividers?: boolean;
@@ -1197,6 +1446,8 @@ export interface MenuProps extends Omit<HTMLAttributes<HTMLDivElement>, "childre
1197
1446
  open?: boolean;
1198
1447
  defaultOpen?: boolean;
1199
1448
  onOpenChange?: (open: boolean) => void;
1449
+ /** Resolve a fallback after close if the opener can no longer receive focus. */
1450
+ getFallbackFocus?: () => HTMLElement | null;
1200
1451
  disabled?: boolean;
1201
1452
  side?: "top" | "bottom" | "left" | "right";
1202
1453
  align?: "start" | "center" | "end";
@@ -1246,6 +1497,8 @@ export interface DialogProps extends Omit<HTMLAttributes<HTMLDialogElement>, "ch
1246
1497
  closeLabel?: string;
1247
1498
  closeOnOutsideClick?: boolean;
1248
1499
  initialFocusRef?: RefObject<HTMLElement | null>;
1500
+ /** Resolve a fallback after close if the opener can no longer receive focus. */
1501
+ getFallbackFocus?: () => HTMLElement | null;
1249
1502
  }
1250
1503
  export declare const Dialog: import("react").ForwardRefExoticComponent<DialogProps & import("react").RefAttributes<HTMLDialogElement>>;
1251
1504
  export interface CommandPaletteProps extends Omit<DialogProps, "children" | "description" | "initialFocusRef"> {
@@ -1663,6 +1916,8 @@ export declare const Divider: import("react").ForwardRefExoticComponent<HTMLAttr
1663
1916
  export interface CodeSnippetProps extends Omit<HTMLAttributes<HTMLDivElement>, "children"> {
1664
1917
  code: string;
1665
1918
  codeDirection?: "ltr" | "rtl" | "auto";
1919
+ /** A decorative prompt placed beside the code, outside the selectable text. @since 0.2.13 */
1920
+ prompt?: string;
1666
1921
  action?: ReactNode;
1667
1922
  }
1668
1923
  /** Selectable code with an optional host-owned action; no clipboard side effects. */
@@ -1675,6 +1930,8 @@ export interface ScrollAreaProps extends HTMLAttributes<HTMLDivElement> {
1675
1930
  export declare const ScrollArea: import("react").ForwardRefExoticComponent<ScrollAreaProps & import("react").RefAttributes<HTMLDivElement>>;
1676
1931
  export interface CopyButtonProps extends Omit<ButtonProps, "children" | "value" | "type" | "onClick" | "loading"> {
1677
1932
  value: string;
1933
+ /** Use a named square icon action with the same clipboard feedback. @since 0.2.13 */
1934
+ iconOnly?: boolean;
1678
1935
  copyLabel?: string;
1679
1936
  copiedLabel?: string;
1680
1937
  errorLabel?: string;
package/dist/index.js CHANGED
@@ -52,6 +52,14 @@ import { CivilDateTimeRangePicker } from "./date-time-range-picker.js";
52
52
  import { FileRecord, UploadRecord, ModalImagePreview, FileMetadata } from "./file-records.js";
53
53
  import { ManagedDataTable } from "./data-table.js";
54
54
  import { TrendPlot } from "./sparkline.js";
55
+ import { SeriesPlot, GroupedBars, RankedBars, ShareRing, HeatmapGrid } from "./charts.js";
56
+ import { ScatterPlot } from "./scatter-chart.js";
57
+ import { RangePlot } from "./range-chart.js";
58
+ import { DumbbellPlot } from "./dumbbell-chart.js";
59
+ import { DistributionBoxes } from "./box-plot.js";
60
+ import { BulletPlot } from "./bullet-chart.js";
61
+ import { HistogramPlot } from "./histogram.js";
62
+ import { WaterfallPlot } from "./waterfall-chart.js";
55
63
  import { CheckableCard, ExclusiveCard } from "./collection-cards.js";
56
64
  import { ColorPreview, ColorEditor } from "./color-controls.js";
57
65
  import { HoverPreview } from "./hover-card.js";
@@ -101,6 +109,42 @@ export const DataTable = /* @__PURE__ */ forwardRef(function DataTable(props, re
101
109
  export const Sparkline = /* @__PURE__ */ forwardRef(function Sparkline({ showLabel = false, size = "md", tone = "accent", variant = "area", ...props }, ref) {
102
110
  return _jsx(TrendPlot, { ...props, showLabel: showLabel, size: size, tone: tone, variant: variant, ref: ref });
103
111
  });
112
+ export const LineChart = /* @__PURE__ */ forwardRef(function LineChart({ variant = "area", curve = "smooth", showYAxis = false, emptyLabel = "No data", missingLabel = "No value", navigationLabel = "Use the arrow keys to explore values, or Home and End to jump.", ...props }, ref) {
113
+ return _jsx(SeriesPlot, { ...props, variant: variant, curve: curve, showYAxis: showYAxis, emptyLabel: emptyLabel, missingLabel: missingLabel, navigationLabel: navigationLabel, ref: ref });
114
+ });
115
+ export const BarChart = /* @__PURE__ */ forwardRef(function BarChart({ emptyLabel = "No data", missingLabel = "No value", navigationLabel = "Use the arrow keys to explore categories, or Home and End to jump.", ...props }, ref) {
116
+ return _jsx(GroupedBars, { ...props, emptyLabel: emptyLabel, missingLabel: missingLabel, navigationLabel: navigationLabel, ref: ref });
117
+ });
118
+ export const DonutChart = /* @__PURE__ */ forwardRef(function DonutChart({ totalLabel = "Total", emptyLabel = "No data", navigationLabel = "Use the arrow keys to explore segments, or Home and End to jump.", ...props }, ref) {
119
+ return _jsx(ShareRing, { ...props, totalLabel: totalLabel, emptyLabel: emptyLabel, navigationLabel: navigationLabel, ref: ref });
120
+ });
121
+ export const Heatmap = /* @__PURE__ */ forwardRef(function Heatmap({ missingLabel = "No data", emptyLabel = "No observations", ...props }, ref) {
122
+ return _jsx(HeatmapGrid, { ...props, missingLabel: missingLabel, emptyLabel: emptyLabel, ref: ref });
123
+ });
124
+ export const ScatterChart = /* @__PURE__ */ forwardRef(function ScatterChart(props, ref) {
125
+ return _jsx(ScatterPlot, { ...props, ref: ref });
126
+ });
127
+ export const RangeChart = /* @__PURE__ */ forwardRef(function RangeChart(props, ref) {
128
+ return _jsx(RangePlot, { ...props, ref: ref });
129
+ });
130
+ export const DumbbellChart = /* @__PURE__ */ forwardRef(function DumbbellChart(props, ref) {
131
+ return _jsx(DumbbellPlot, { ...props, ref: ref });
132
+ });
133
+ export const BoxPlot = /* @__PURE__ */ forwardRef(function BoxPlot(props, ref) {
134
+ return _jsx(DistributionBoxes, { ...props, ref: ref });
135
+ });
136
+ export const BulletChart = /* @__PURE__ */ forwardRef(function BulletChart(props, ref) {
137
+ return _jsx(BulletPlot, { ...props, ref: ref });
138
+ });
139
+ export const WaterfallChart = /* @__PURE__ */ forwardRef(function WaterfallChart(props, ref) {
140
+ return _jsx(WaterfallPlot, { ...props, ref: ref });
141
+ });
142
+ export const Histogram = /* @__PURE__ */ forwardRef(function Histogram(props, ref) {
143
+ return _jsx(HistogramPlot, { ...props, ref: ref });
144
+ });
145
+ export const BarList = /* @__PURE__ */ forwardRef(function BarList({ tone = "accent", emptyLabel = "No data", ...props }, ref) {
146
+ return _jsx(RankedBars, { ...props, tone: tone, emptyLabel: emptyLabel, ref: ref });
147
+ });
104
148
  /** A native list; links and actions stay independent. @since 0.2.8 */
105
149
  export const DataList = /* @__PURE__ */ forwardRef(function DataList({ density = "comfortable", dividers = true, className, ...props }, ref) {
106
150
  return _jsx("ul", { ...props, ref: ref, role: "list", "data-density": density, "data-dividers": dividers, className: classes("flood-data-list", className) });
@@ -540,8 +584,8 @@ export const Divider = /* @__PURE__ */ forwardRef(function Divider({ className,
540
584
  return _jsx("hr", { ...props, ref: ref, className: classes("flood-divider", className) });
541
585
  });
542
586
  /** Selectable code with an optional host-owned action; no clipboard side effects. */
543
- export const CodeSnippet = /* @__PURE__ */ forwardRef(function CodeSnippet({ code, codeDirection = "ltr", action, className, ...props }, ref) {
544
- return (_jsxs("div", { ...props, ref: ref, className: classes("flood-code-snippet", className), children: [_jsx("code", { dir: codeDirection, className: "flood-code-snippet__code", children: code }), action != null && _jsx("div", { className: "flood-code-snippet__action", children: action })] }));
587
+ export const CodeSnippet = /* @__PURE__ */ forwardRef(function CodeSnippet({ code, codeDirection = "ltr", prompt, action, className, ...props }, ref) {
588
+ return (_jsxs("div", { ...props, ref: ref, className: classes("flood-code-snippet", className), children: [_jsxs("div", { dir: codeDirection === "rtl" ? "rtl" : "ltr", className: "flood-code-snippet__line", children: [prompt && _jsx("span", { "aria-hidden": "true", className: "flood-code-snippet__prompt", children: prompt }), _jsx("code", { dir: codeDirection, className: "flood-code-snippet__code", children: code })] }), action != null && _jsx("div", { className: "flood-code-snippet__action", children: action })] }));
545
589
  });
546
590
  /** Native scrolling with inset overlay tracks; the ref targets the scrollable viewport. */
547
591
  export const ScrollArea = /* @__PURE__ */ forwardRef(function ScrollArea({ orientation = "vertical", contentInset = "scrollbar", ...props }, ref) {
package/dist/menu.d.ts CHANGED
@@ -15,6 +15,7 @@ interface MenuLayerProps extends Omit<HTMLAttributes<HTMLDivElement>, "children"
15
15
  rootAnchor: () => HTMLElement | null;
16
16
  rootPanel?: () => HTMLDivElement | null;
17
17
  onAction?: (id: string) => void;
18
+ getFallbackFocus?: MenuProps["getFallbackFocus"];
18
19
  edge?: () => Edge;
19
20
  nested?: boolean;
20
21
  position?: (panel: HTMLDivElement) => void;
package/dist/menu.js CHANGED
@@ -40,7 +40,7 @@ export function focusMenuEdge(panel, edge = "first") {
40
40
  ((edge === "last" ? all.at(-1) : all[0]) ?? panel)?.focus({ preventScroll: true });
41
41
  }
42
42
  /** Internal native top-layer engine shared by Menu and ContextMenu. */
43
- export const MenuLayer = /* @__PURE__ */ forwardRef(function MenuLayerRender({ label, items, open, requestOpen, closeTree, anchor, rootAnchor, rootPanel, onAction, edge, nested = false, position, side = "bottom", align = "start", controlSize = "sm", id: explicitId, className, onKeyDown, ...props }, forwardedRef) {
43
+ export const MenuLayer = /* @__PURE__ */ forwardRef(function MenuLayerRender({ label, items, open, requestOpen, closeTree, anchor, rootAnchor, rootPanel, onAction, getFallbackFocus, edge, nested = false, position, side = "bottom", align = "start", controlSize = "sm", id: explicitId, className, onKeyDown, ...props }, forwardedRef) {
44
44
  const generated = useId(), id = explicitId ?? `flood-menu-${generated}`;
45
45
  const panel = useRef(null), anchors = useRef(new Map()), children = useRef(new Map());
46
46
  const anchorRefs = useRef(new Map());
@@ -50,8 +50,8 @@ export const MenuLayer = /* @__PURE__ */ forwardRef(function MenuLayerRender({ l
50
50
  const hoverIntent = useRef(undefined);
51
51
  const latestItems = useRef(new Map());
52
52
  const search = useRef({ text: "", time: 0 }), pointerTarget = useRef(null);
53
- const current = useRef({ open, requestOpen, closeTree, anchor, rootAnchor, rootPanel, position, edge });
54
- current.current = { open, requestOpen, closeTree, anchor, rootAnchor, rootPanel, position, edge };
53
+ const current = useRef({ open, requestOpen, closeTree, anchor, rootAnchor, rootPanel, position, edge, getFallbackFocus });
54
+ current.current = { open, requestOpen, closeTree, anchor, rootAnchor, rootPanel, position, edge, getFallbackFocus };
55
55
  useImperativeHandle(forwardedRef, () => panel.current, []);
56
56
  const clearHover = () => { clearTimeout(hover.current); hover.current = undefined; hoverIntent.current = undefined; };
57
57
  useEffect(() => () => clearHover(), []);
@@ -166,7 +166,7 @@ export const MenuLayer = /* @__PURE__ */ forwardRef(function MenuLayerRender({ l
166
166
  unregister();
167
167
  document.removeEventListener("pointerdown", trackPointer, true);
168
168
  element.removeEventListener("toggle", toggle);
169
- returnFocus(element, current.current.anchor());
169
+ returnFocus(element, current.current.anchor(), current.current.getFallbackFocus);
170
170
  if (element.matches(":popover-open"))
171
171
  element.hidePopover();
172
172
  };
@@ -328,7 +328,7 @@ export const MenuLayer = /* @__PURE__ */ forwardRef(function MenuLayerRender({ l
328
328
  }
329
329
  }, children: showContent && _jsx(ScrollArea, { orientation: "vertical", role: "group", tabIndex: -1, className: "flood-menu__viewport", children: rows }) });
330
330
  });
331
- export const ActionMenu = /* @__PURE__ */ forwardRef(function ActionMenu({ label, trigger, items, onAction, open: controlled, defaultOpen, onOpenChange, disabled, side = "bottom", align = "start", controlSize = "sm", ...props }, forwardedRef) {
331
+ export const ActionMenu = /* @__PURE__ */ forwardRef(function ActionMenu({ label, trigger, items, onAction, open: controlled, defaultOpen, onOpenChange, disabled, getFallbackFocus, side = "bottom", align = "start", controlSize = "sm", ...props }, forwardedRef) {
332
332
  if (!label.trim())
333
333
  throw new Error("Flood Menu requires a nonempty label");
334
334
  validateMenuTree(items);
@@ -365,5 +365,5 @@ export const ActionMenu = /* @__PURE__ */ forwardRef(function ActionMenu({ label
365
365
  } };
366
366
  const generated = useId(), id = props.id ?? `flood-menu-${generated}`;
367
367
  triggerProps["aria-controls"] = id;
368
- return _jsxs(_Fragment, { children: [trigger(triggerProps), _jsx(MenuLayer, { ...props, id: id, ref: panel, label: label, items: items, open: open, requestOpen: request, closeTree: () => request(false), anchor: () => anchor.current, rootAnchor: () => anchor.current, onAction: onAction, edge: () => edge.current, side: side, align: align, controlSize: controlSize })] });
368
+ return _jsxs(_Fragment, { children: [trigger(triggerProps), _jsx(MenuLayer, { ...props, id: id, ref: panel, label: label, items: items, open: open, requestOpen: request, closeTree: () => request(false), anchor: () => anchor.current, rootAnchor: () => anchor.current, onAction: onAction, getFallbackFocus: getFallbackFocus, edge: () => edge.current, side: side, align: align, controlSize: controlSize })] });
369
369
  });
@@ -36,7 +36,7 @@ type Layer = {
36
36
  export declare function registerLayer(document: Document, layer: Layer): () => void;
37
37
  export declare function lockScroll(document: Document): () => void;
38
38
  export declare function positionLayer(anchor: HTMLElement, panel: HTMLElement, side: Side, align: Align, overlap?: boolean): void;
39
- export declare function returnFocus(panel: HTMLElement, anchor: HTMLElement | null): void;
39
+ export declare function returnFocus(panel: HTMLElement, anchor: HTMLElement | null, fallback?: () => HTMLElement | null): void;
40
40
  /** Retain pixels for a CSS exit after the native layer has released focus. */
41
41
  export declare function useLayerExitContent(panel: RefObject<HTMLElement | null>, open: boolean, enabled?: boolean, transitionProperty?: string): boolean;
42
42
  export {};
@@ -132,10 +132,31 @@ export function positionLayer(anchor, panel, side, align, overlap = false) {
132
132
  panel.dataset.side = position.side;
133
133
  panel.style.visibility = "visible";
134
134
  }
135
- export function returnFocus(panel, anchor) {
136
- const active = panel.ownerDocument.activeElement;
137
- if (anchor?.isConnected && !anchor.matches(":disabled") && (panel.contains(active) || active === panel.ownerDocument.body))
138
- anchor.focus({ preventScroll: true });
135
+ export function returnFocus(panel, anchor, fallback) {
136
+ const document = panel.ownerDocument;
137
+ const needsFocus = () => {
138
+ const active = document.activeElement;
139
+ return !active || active === document.body || panel.contains(active);
140
+ };
141
+ const focus = (target) => {
142
+ if (!target?.isConnected || target.ownerDocument !== document || panel.contains(target) ||
143
+ target.matches(':disabled,[aria-disabled="true"]') || target.closest("[hidden],[inert]") ||
144
+ !target.getClientRects().length || document.defaultView?.getComputedStyle(target).visibility !== "visible")
145
+ return;
146
+ target.focus({ preventScroll: true });
147
+ };
148
+ if (needsFocus())
149
+ focus(anchor);
150
+ else if (document.activeElement !== anchor)
151
+ return;
152
+ if (!fallback)
153
+ return;
154
+ // A surviving opener can still be removed in the same React commit.
155
+ queueMicrotask(() => {
156
+ if (panel.matches("dialog[open]") || panel.matches(":popover-open") || !needsFocus())
157
+ return;
158
+ focus(fallback());
159
+ });
139
160
  }
140
161
  /** Retain pixels for a CSS exit after the native layer has released focus. */
141
162
  export function useLayerExitContent(panel, open, enabled = true, transitionProperty = "opacity") {
package/dist/overlays.js CHANGED
@@ -126,12 +126,14 @@ export const AnchoredOverlay = /* @__PURE__ */ forwardRef(function AnchoredOverl
126
126
  leave();
127
127
  } }, children: kind === "tooltip" ? tip.content : _jsxs(_Fragment, { children: [pop.chrome === "minimal" ? _jsx(VisuallyHidden, { children: _jsx("h2", { id: `${id}-title`, children: pop.title }) }) : _jsxs("div", { className: "flood-overlay-header", children: [_jsx("h2", { id: `${id}-title`, className: "flood-overlay-title", children: pop.title }), !titleOnly && _jsx(OverlayClose, { label: pop.closeLabel ?? "Close", onClick: close })] }), showContent && _jsx("div", { className: "flood-overlay-body", children: pop.children })] }) })] });
128
128
  });
129
- export const ModalOverlay = /* @__PURE__ */ forwardRef(function ModalOverlay({ showCloseButton = true, scrollBody = false, bodyContentInset, footer, title, description, trigger, children, open: controlled, defaultOpen = false, onOpenChange, disabled = false, closeLabel = "Close", closeOnOutsideClick = true, initialFocusRef, id: explicitId, className, onKeyDown, "aria-describedby": describedBy, ...props }, forwardedRef) {
129
+ export const ModalOverlay = /* @__PURE__ */ forwardRef(function ModalOverlay({ showCloseButton = true, scrollBody = false, bodyContentInset, footer, title, description, trigger, children, open: controlled, defaultOpen = false, onOpenChange, disabled = false, closeLabel = "Close", closeOnOutsideClick = true, initialFocusRef, getFallbackFocus, id: explicitId, className, onKeyDown, "aria-describedby": describedBy, ...props }, forwardedRef) {
130
130
  const [local, setLocal] = useState(defaultOpen), open = controlled ?? local;
131
131
  const dialog = useRef(null), anchor = useRef(null), generated = useId(), id = explicitId ?? `flood-dialog-${generated}`;
132
132
  const request = (next) => { if (controlled === undefined)
133
133
  setLocal(next); onOpenChange?.(next); };
134
134
  const current = useCurrent({ open, request, closeOnOutsideClick, initialFocusRef });
135
+ const fallbackFocus = useRef(getFallbackFocus);
136
+ fallbackFocus.current = getFallbackFocus;
135
137
  const setAnchor = useCallback((node) => { anchor.current = node; }, []);
136
138
  useImperativeHandle(forwardedRef, () => dialog.current, []);
137
139
  useBrowserLayoutEffect(() => {
@@ -168,7 +170,7 @@ export const ModalOverlay = /* @__PURE__ */ forwardRef(function ModalOverlay({ s
168
170
  element.addEventListener("close", closed);
169
171
  focusContent(element, current.current.initialFocusRef?.current);
170
172
  dispose = () => { unregister(); unlock(); element.removeEventListener("cancel", cancel); element.removeEventListener("close", closed); if (element.open)
171
- element.close(); returnFocus(element, anchor.current ?? previous); };
173
+ element.close(); returnFocus(element, anchor.current ?? previous, fallbackFocus.current ? () => fallbackFocus.current?.() ?? null : undefined); };
172
174
  };
173
175
  start();
174
176
  return () => { alive = false; view.cancelAnimationFrame(frame); dispose(); };
@@ -0,0 +1,2 @@
1
+ import { type RangeChartProps } from "./index.js";
2
+ export declare const RangePlot: import("react").ForwardRefExoticComponent<RangeChartProps & import("react").RefAttributes<HTMLDivElement>>;