@galaxy-io/dls 1.3.4 → 1.3.6

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.
@@ -14,7 +14,6 @@ export interface AccordionProps {
14
14
  variant?: AccordionVariant;
15
15
  size?: AccordionSize;
16
16
  icon?: PhosphorIcon;
17
- isTrailingIcon?: boolean;
18
17
  header: string;
19
18
  subheader?: string;
20
19
  metric?: React.ReactNode;
@@ -26,5 +25,5 @@ export interface AccordionProps {
26
25
  /** Controlled mode: callback when toggle is clicked */
27
26
  onToggle?: () => void;
28
27
  }
29
- declare const Accordion: ({ children, variant, size, icon, isTrailingIcon, header, subheader, metric, borderRadius, padding, isOpenInitial, isOpen: controlledIsOpen, onToggle: controlledOnToggle, }: PropsWithChildren<AccordionProps>) => React.JSX.Element;
28
+ declare const Accordion: ({ children, variant, size, icon, header, subheader, metric, borderRadius, padding, isOpenInitial, isOpen: controlledIsOpen, onToggle: controlledOnToggle, }: PropsWithChildren<AccordionProps>) => React.JSX.Element;
30
29
  export default Accordion;
@@ -65,7 +65,7 @@ var AccordionContentInner = withTheme(/*#__PURE__*/ styled("div")({
65
65
  class: "a1f5gwwu",
66
66
  propsAsIs: false
67
67
  }));
68
- var Accordion = ({ children, variant = "PRIMARY", size = "MEDIUM", icon, isTrailingIcon = false, header, subheader, metric, borderRadius, padding = "12px", isOpenInitial = false, isOpen: controlledIsOpen, onToggle: controlledOnToggle }) => {
68
+ var Accordion = ({ children, variant = "PRIMARY", size = "MEDIUM", icon, header, subheader, metric, borderRadius, padding = "12px", isOpenInitial = false, isOpen: controlledIsOpen, onToggle: controlledOnToggle }) => {
69
69
  const [internalIsOpen, setInternalIsOpen] = useState(isOpenInitial);
70
70
  const isControlled = controlledIsOpen !== void 0;
71
71
  const isOpen = isControlled ? controlledIsOpen : internalIsOpen;
@@ -87,31 +87,23 @@ var Accordion = ({ children, variant = "PRIMARY", size = "MEDIUM", icon, isTrail
87
87
  children: [/* @__PURE__ */ jsxs(FlexWrapper, {
88
88
  alignItems: AlignItems.CENTER,
89
89
  gap: 8,
90
- children: [
91
- icon && !isTrailingIcon && /* @__PURE__ */ jsx(Icon, {
92
- component: icon,
93
- variant: isOpen ? IconVariant.PRIMARY : IconVariant.SECONDARY,
94
- size: 16
95
- }),
96
- /* @__PURE__ */ jsxs(FlexWrapper, {
97
- direction: FlexDirection.COLUMN,
98
- gap: 2,
99
- children: [/* @__PURE__ */ jsx(Text, {
100
- variant: isOpen ? TextVariant.PRIMARY : TextVariant.SECONDARY,
101
- weight: TextWeight.MEDIUM,
102
- children: header
103
- }), subheader && /* @__PURE__ */ jsx(Text, {
104
- size: TextSize.BODY_SM,
105
- variant: isOpen ? TextVariant.SECONDARY : TextVariant.TERTIARY,
106
- children: subheader
107
- })]
108
- }),
109
- icon && isTrailingIcon && /* @__PURE__ */ jsx(Icon, {
110
- component: icon,
111
- variant: isOpen ? IconVariant.PRIMARY : IconVariant.SECONDARY,
112
- size: 16
113
- })
114
- ]
90
+ children: [icon && /* @__PURE__ */ jsx(Icon, {
91
+ component: icon,
92
+ variant: isOpen ? IconVariant.PRIMARY : IconVariant.SECONDARY,
93
+ size: 16
94
+ }), /* @__PURE__ */ jsxs(FlexWrapper, {
95
+ direction: FlexDirection.COLUMN,
96
+ gap: 2,
97
+ children: [/* @__PURE__ */ jsx(Text, {
98
+ variant: isOpen ? TextVariant.PRIMARY : TextVariant.SECONDARY,
99
+ weight: TextWeight.MEDIUM,
100
+ children: header
101
+ }), subheader && /* @__PURE__ */ jsx(Text, {
102
+ size: TextSize.BODY_SM,
103
+ variant: isOpen ? TextVariant.SECONDARY : TextVariant.TERTIARY,
104
+ children: subheader
105
+ })]
106
+ })]
115
107
  }), /* @__PURE__ */ jsxs(FlexWrapper, {
116
108
  alignItems: AlignItems.CENTER,
117
109
  gap: 8,
@@ -13,12 +13,6 @@ export type BarChartProps<TMetric extends string, TComponentKey extends string =
13
13
  groups: BarChartGroupDatum<NoInfer<TMetric>, NoInfer<TComponentKey>>[];
14
14
  /** @default BarChartNormalization.NONE */
15
15
  normalization?: BarChartNormalization;
16
- /**
17
- * Notified when a group is pinned or unpinned by a click.
18
- *
19
- * The index is a position in `groups`.
20
- */
21
- onPinnedChange?: (groupIndex: number | null) => void;
22
16
  };
23
17
  /**
24
18
  * Bar chart with a three-level data model.
@@ -27,5 +21,5 @@ export type BarChartProps<TMetric extends string, TComponentKey extends string =
27
21
  * `bars` in a group render side by side, several `components` in a bar stack,
28
22
  * and a chart doing both at once needs no extra configuration.
29
23
  */
30
- declare const BarChart: <TMetric extends string, TComponentKey extends string = string>({ series, groups, width, height, fillWidth, fillHeight, aspectRatio, margin, normalization, valueUnit, valueFormatter, labelFormatter, valueDomain, categoryLabelMode, categoryAxisTitle, valueAxisTitle, showGrid, showLegend, noTooltip, tooltipRenderer, isLoading, contentWhenEmpty, ariaLabel, ariaDescription, onPinnedChange, }: BarChartProps<TMetric, TComponentKey>) => import("react").JSX.Element;
24
+ declare const BarChart: <TMetric extends string, TComponentKey extends string = string>({ series, groups, width, height, fillWidth, fillHeight, aspectRatio, margin, normalization, valueUnit, valueFormatter, labelFormatter, valueDomain, categoryLabelMode, categoryAxisTitle, valueAxisTitle, showGrid, showLegend, noTooltip, tooltipRenderer, isLoading, contentWhenEmpty, ariaLabel, ariaDescription, }: BarChartProps<TMetric, TComponentKey>) => import("react").JSX.Element;
31
25
  export default BarChart;
@@ -1,10 +1,11 @@
1
1
  import { BarChartNormalization, ChartKind, ChartTargetShape } from "./types.js";
2
+ import { SKELETON_BAR_GROUPS } from "./constants.js";
2
3
  import { useSeriesColorResolver } from "./useSeriesColorResolver.js";
3
4
  import { useChartInteraction } from "./useChartInteraction.js";
4
5
  import { useCartesianChartLayout } from "./useCartesianChartLayout.js";
5
6
  import { getBarLegendItems } from "./barChartLegend.js";
6
7
  import { buildBarChartGeometry, getBarValueDomain, roundedRectPath } from "./barChartGeometry.js";
7
- import { ChartBarPath } from "./ChartPrimitives.js";
8
+ import { ChartBarPath, ChartSkeletonGroup } from "./ChartPrimitives.js";
8
9
  import ChartFrame from "./ChartFrame.js";
9
10
  import { ChartCategoryAxis, ChartValueAxis } from "./ChartAxis.js";
10
11
  import { useCallback, useMemo, useRef } from "react";
@@ -17,7 +18,7 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
17
18
  * `bars` in a group render side by side, several `components` in a bar stack,
18
19
  * and a chart doing both at once needs no extra configuration.
19
20
  */
20
- var BarChart = ({ series, groups, width, height, fillWidth, fillHeight, aspectRatio, margin, normalization = BarChartNormalization.NONE, valueUnit, valueFormatter, labelFormatter, valueDomain, categoryLabelMode, categoryAxisTitle, valueAxisTitle, showGrid = true, showLegend = true, noTooltip = false, tooltipRenderer, isLoading = false, contentWhenEmpty, ariaLabel, ariaDescription, onPinnedChange }) => {
21
+ var BarChart = ({ series, groups, width, height, fillWidth, fillHeight, aspectRatio, margin, normalization = BarChartNormalization.NONE, valueUnit, valueFormatter, labelFormatter, valueDomain, categoryLabelMode, categoryAxisTitle, valueAxisTitle, showGrid = true, showLegend = true, noTooltip = false, tooltipRenderer, isLoading = false, contentWhenEmpty, ariaLabel, ariaDescription }) => {
21
22
  const containerRef = useRef(null);
22
23
  const colors = useSeriesColorResolver(series);
23
24
  /**
@@ -42,9 +43,29 @@ var BarChart = ({ series, groups, width, height, fillWidth, fillHeight, aspectRa
42
43
  valueFormatter,
43
44
  valueUnit
44
45
  ]);
46
+ /**
47
+ * The data actually rendered: the caller's, or the skeleton's while loading.
48
+ *
49
+ * Loading replaces the chart wholesale with ghost bars pushed through the
50
+ * real geometry pipeline, so the skeleton gets the true radii and spacing.
51
+ * The cast is safe because nothing derived from skeleton data ever surfaces:
52
+ * labels are hidden, targets are off, the legend shows stubs, and the
53
+ * accessible name says "loading" instead of counting.
54
+ */
55
+ const displayGroups = useMemo(() => isLoading ? SKELETON_BAR_GROUPS.map((values, groupIndex) => ({
56
+ label: `skeleton-${groupIndex}`,
57
+ bars: values.map((value, barIndex) => ({
58
+ metric: `skeleton-${barIndex}`,
59
+ components: [{
60
+ key: "skeleton",
61
+ label: "",
62
+ value
63
+ }]
64
+ }))
65
+ })) : groups, [isLoading, groups]);
45
66
  const { dimensions, domain, valueTicks, formatValue, formatLabel, formatAxisValue } = useCartesianChartLayout({
46
67
  containerRef,
47
- rawDomain: useMemo(() => getBarValueDomain(groups, normalization), [groups, normalization]),
68
+ rawDomain: useMemo(() => getBarValueDomain(displayGroups, normalization), [displayGroups, normalization]),
48
69
  valueDomain,
49
70
  width,
50
71
  height,
@@ -69,10 +90,10 @@ var BarChart = ({ series, groups, width, height, fillWidth, fillHeight, aspectRa
69
90
  * takes its metric's colour. Applying the metric colour to a stack would
70
91
  * paint every segment identically.
71
92
  */
72
- const isStacked = useMemo(() => groups.some((group) => group.bars.some((bar) => bar.components.length > 1)), [groups]);
73
- const resolveColor = useCallback(({ metric, componentIndex, isStacked: barIsStacked, explicit }) => barIsStacked ? colors.resolveByIndex(componentIndex, explicit) : colors.resolve(metric, explicit), [colors]);
93
+ const isStacked = useMemo(() => displayGroups.some((group) => group.bars.some((bar) => bar.components.length > 1)), [displayGroups]);
94
+ const resolveColor = useCallback(({ metric, componentIndex, isStacked: barIsStacked, explicit }) => isLoading ? colors.placeholder : barIsStacked ? colors.resolveByIndex(componentIndex, explicit) : colors.resolve(metric, explicit), [colors, isLoading]);
74
95
  const geometry = useMemo(() => buildBarChartGeometry({
75
- groups,
96
+ groups: displayGroups,
76
97
  plotWidth,
77
98
  plotHeight,
78
99
  normalization,
@@ -80,7 +101,7 @@ var BarChart = ({ series, groups, width, height, fillWidth, fillHeight, aspectRa
80
101
  domain,
81
102
  resolveColor
82
103
  }), [
83
- groups,
104
+ displayGroups,
84
105
  plotWidth,
85
106
  plotHeight,
86
107
  normalization,
@@ -88,7 +109,7 @@ var BarChart = ({ series, groups, width, height, fillWidth, fillHeight, aspectRa
88
109
  domain,
89
110
  resolveColor
90
111
  ]);
91
- const isEmpty = groups.length === 0;
112
+ const isEmpty = displayGroups.length === 0;
92
113
  const legendItems = useMemo(() => getBarLegendItems({
93
114
  isStacked,
94
115
  series,
@@ -104,9 +125,7 @@ var BarChart = ({ series, groups, width, height, fillWidth, fillHeight, aspectRa
104
125
  const interaction = useChartInteraction({
105
126
  categoryCount: geometry.bands.length,
106
127
  seriesKeys: legendKeys,
107
- disabled: noTooltip || isLoading || isEmpty,
108
- isLoading,
109
- onPinnedChange
128
+ disabled: noTooltip || isLoading || isEmpty
110
129
  });
111
130
  const buildCategoryTooltip = useCallback((index) => {
112
131
  const band = geometry.bands[index];
@@ -141,6 +160,14 @@ var BarChart = ({ series, groups, width, height, fillWidth, fillHeight, aspectRa
141
160
  width: band.width,
142
161
  height: band.height
143
162
  })), [geometry.bands]);
163
+ const marks = geometry.rects.map((rect) => /* @__PURE__ */ jsx(ChartBarPath, {
164
+ d: roundedRectPath(rect.x, rect.y, rect.width, rect.height, rect.radius, rect.corners),
165
+ fill: rect.color,
166
+ "data-state": interaction.getMarkState({
167
+ categoryIndex: rect.groupIndex,
168
+ seriesKey: isStacked ? rect.componentKey : rect.metric
169
+ })
170
+ }, rect.id));
144
171
  return /* @__PURE__ */ jsx(ChartFrame, {
145
172
  containerRef,
146
173
  dimensions,
@@ -159,15 +186,17 @@ var BarChart = ({ series, groups, width, height, fillWidth, fillHeight, aspectRa
159
186
  marginLeft: dimensions.margin.left,
160
187
  formatter: formatAxisValue,
161
188
  showGrid,
189
+ showLabels: !isLoading,
162
190
  title: valueAxisTitle
163
191
  }), /* @__PURE__ */ jsx(ChartCategoryAxis, {
164
- labels: groups.map((group) => group.label),
192
+ labels: displayGroups.map((group) => group.label),
165
193
  positions: categoryPositions,
166
194
  step: categoryStep,
167
195
  plotWidth,
168
196
  plotHeight,
169
197
  formatter: formatLabel,
170
198
  mode: categoryLabelMode,
199
+ showLabels: !isLoading,
171
200
  title: categoryAxisTitle
172
201
  })] }),
173
202
  noTooltip,
@@ -180,14 +209,7 @@ var BarChart = ({ series, groups, width, height, fillWidth, fillHeight, aspectRa
180
209
  contentWhenEmpty,
181
210
  showLegend,
182
211
  legendItems,
183
- children: geometry.rects.map((rect) => /* @__PURE__ */ jsx(ChartBarPath, {
184
- d: roundedRectPath(rect.x, rect.y, rect.width, rect.height, rect.radius, rect.corners),
185
- fill: rect.color,
186
- "data-state": interaction.getMarkState({
187
- categoryIndex: rect.groupIndex,
188
- seriesKey: isStacked ? rect.componentKey : rect.metric
189
- })
190
- }, rect.id))
212
+ children: isLoading ? /* @__PURE__ */ jsx(ChartSkeletonGroup, { children: marks }) : marks
191
213
  });
192
214
  };
193
215
  //#endregion
@@ -22,6 +22,13 @@ interface ChartValueAxisProps {
22
22
  plotHeight: number;
23
23
  formatter: ChartValueFormatter;
24
24
  showGrid?: boolean;
25
+ /**
26
+ * Render the tick labels and title.
27
+ *
28
+ * Off while the chart is loading: the grid keeps the plot's structure, but
29
+ * numbers derived from placeholder data would read as data.
30
+ */
31
+ showLabels?: boolean;
25
32
  /**
26
33
  * Rotated title in the left gutter.
27
34
  *
@@ -30,7 +37,7 @@ interface ChartValueAxisProps {
30
37
  */
31
38
  title?: string;
32
39
  }
33
- export declare const ChartValueAxis: ({ ticks, scale, plotWidth, plotHeight, marginLeft, formatter, showGrid, title, }: ChartValueAxisProps) => import("react").JSX.Element;
40
+ export declare const ChartValueAxis: ({ ticks, scale, plotWidth, plotHeight, marginLeft, formatter, showGrid, showLabels, title, }: ChartValueAxisProps) => import("react").JSX.Element;
34
41
  interface ChartCategoryAxisProps {
35
42
  labels: string[];
36
43
  /** Left edge of each category's slot, in plot coordinates. */
@@ -42,6 +49,8 @@ interface ChartCategoryAxisProps {
42
49
  plotHeight: number;
43
50
  formatter: ChartLabelFormatter;
44
51
  mode?: ChartCategoryLabelMode;
52
+ /** Render the labels and title. Off while loading — see the value axis. */
53
+ showLabels?: boolean;
45
54
  /**
46
55
  * Title below the tick labels.
47
56
  *
@@ -49,5 +58,5 @@ interface ChartCategoryAxisProps {
49
58
  */
50
59
  title?: string;
51
60
  }
52
- export declare const ChartCategoryAxis: ({ labels, positions, step, plotWidth, plotHeight, formatter, mode, title, }: ChartCategoryAxisProps) => import("react").JSX.Element;
61
+ export declare const ChartCategoryAxis: ({ labels, positions, step, plotWidth, plotHeight, formatter, mode, showLabels, title, }: ChartCategoryAxisProps) => import("react").JSX.Element | null;
53
62
  export {};
@@ -6,7 +6,7 @@ import { getCategoryLabelStride } from "./chartScales.js";
6
6
  import { ChartAxisLabelBox, ChartGridLine } from "./ChartPrimitives.js";
7
7
  import { jsx, jsxs } from "react/jsx-runtime";
8
8
  //#region src/charts/ChartAxis.tsx
9
- var ChartValueAxis = ({ ticks, scale, plotWidth, plotHeight, marginLeft, formatter, showGrid = true, title }) => {
9
+ var ChartValueAxis = ({ ticks, scale, plotWidth, plotHeight, marginLeft, formatter, showGrid = true, showLabels = true, title }) => {
10
10
  const titleBand = title ? 26 : 0;
11
11
  const labelWidth = Math.max(0, marginLeft - titleBand - 12);
12
12
  return /* @__PURE__ */ jsxs("g", {
@@ -18,7 +18,7 @@ var ChartValueAxis = ({ ticks, scale, plotWidth, plotHeight, marginLeft, formatt
18
18
  x2: plotWidth + 12,
19
19
  y1: 0,
20
20
  y2: 0
21
- }), labelWidth > 0 && /* @__PURE__ */ jsx(ChartAxisLabelBox, {
21
+ }), showLabels && labelWidth > 0 && /* @__PURE__ */ jsx(ChartAxisLabelBox, {
22
22
  x: -marginLeft + titleBand,
23
23
  y: -20 / 2,
24
24
  width: labelWidth,
@@ -37,7 +37,7 @@ var ChartValueAxis = ({ ticks, scale, plotWidth, plotHeight, marginLeft, formatt
37
37
  })
38
38
  })
39
39
  })]
40
- }, tick)), title && /* @__PURE__ */ jsx("g", {
40
+ }, tick)), showLabels && title && /* @__PURE__ */ jsx("g", {
41
41
  transform: `translate(${-marginLeft + 18 / 2}, ${plotHeight / 2}) rotate(-90)`,
42
42
  children: /* @__PURE__ */ jsx(ChartAxisLabelBox, {
43
43
  x: -plotHeight / 2,
@@ -61,7 +61,8 @@ var ChartValueAxis = ({ ticks, scale, plotWidth, plotHeight, marginLeft, formatt
61
61
  })]
62
62
  });
63
63
  };
64
- var ChartCategoryAxis = ({ labels, positions, step, plotWidth, plotHeight, formatter, mode = ChartCategoryLabelMode.AUTO, title }) => {
64
+ var ChartCategoryAxis = ({ labels, positions, step, plotWidth, plotHeight, formatter, mode = ChartCategoryLabelMode.AUTO, showLabels = true, title }) => {
65
+ if (!showLabels) return null;
65
66
  const formatted = labels.map(formatter);
66
67
  const stride = mode === ChartCategoryLabelMode.AUTO ? getCategoryLabelStride(formatted, step) : 1;
67
68
  const labelWidth = step * stride;
@@ -1,27 +1,27 @@
1
1
  import type { ChartCategoryTarget } from "./types";
2
2
  import type { ChartInteraction } from "./useChartInteraction";
3
3
  /**
4
- * The interactive layer: one focusable target per category, above the marks.
4
+ * The hover layer: one transparent target per category, above the marks.
5
5
  *
6
- * Shared by all three charts so pointer and keyboard behave identically. Before
7
- * this existed each chart spelled hit testing its own way two rendered targets
8
- * conditionally, the pie attached handlers permanently and computed
9
- * `data-interactive` instead — and none of them was reachable by keyboard at
10
- * all.
6
+ * Shared by all three charts so hit testing is spelled once. Targets carry no
7
+ * roles and no tab stops the plot is `role="img"` and its accessible story is
8
+ * the frame's label and description, while pointer users get the tooltip.
11
9
  *
12
- * **Focus rides the existing hover channel.** `onFocus` calls
13
- * `onCategoryEnter` and `onBlur` calls `onCategoryLeave`, so the tooltip, the
14
- * cursor line, the legend dimming and every `getMarkState` light up for a
15
- * keyboard reader exactly as they do for a pointer — without
16
- * `useChartInteraction` growing a third state axis to keep in sync.
10
+ * A category with nothing to show gets no target at all: hovering an all-zero
11
+ * bar group or an all-null line column must not open a tooltip with a heading
12
+ * and no rows, so the empty stretch of plot is simply not hoverable.
17
13
  */
18
14
  interface ChartCategoryTargetsProps {
19
15
  targets: ChartCategoryTarget[];
20
16
  interaction: ChartInteraction;
21
- /** Accessible name for one category. Derived from the tooltip, so they agree. */
22
- getAriaLabel: (index: number) => string;
23
- /** Arrow keys wrap at the ends. True for radial charts — a circle has no ends. */
24
- wrap?: boolean;
17
+ /**
18
+ * Category indices whose tooltip would have no rows.
19
+ *
20
+ * Skipped entirely rather than rendered inert, so the pointer falls through
21
+ * to nothing. Indexing is unaffected: `targets.map` still passes the true
22
+ * category index for every target that does render.
23
+ */
24
+ inertIndices?: ReadonlySet<number>;
25
25
  }
26
- declare const ChartCategoryTargets: ({ targets, interaction, getAriaLabel, wrap, }: ChartCategoryTargetsProps) => import("react").JSX.Element;
26
+ declare const ChartCategoryTargets: ({ targets, interaction, inertIndices, }: ChartCategoryTargetsProps) => import("react").JSX.Element;
27
27
  export default ChartCategoryTargets;
@@ -1,75 +1,24 @@
1
1
  import { ChartTargetShape } from "./types.js";
2
- import { clamp } from "./chartScales.js";
3
- import { ChartCategoryTargetLayer, ChartHitTarget, ChartHitTargetPath } from "./ChartPrimitives.js";
2
+ import { ChartHitTarget, ChartHitTargetPath } from "./ChartPrimitives.js";
4
3
  import { match } from "ts-pattern";
5
- import { useCallback, useRef, useState } from "react";
6
- import { jsx } from "react/jsx-runtime";
4
+ import { Fragment, jsx } from "react/jsx-runtime";
7
5
  //#region src/charts/ChartCategoryTargets.tsx
8
- var ChartCategoryTargets = ({ targets, interaction, getAriaLabel, wrap = false }) => {
9
- const [focusIndex, setFocusIndex] = useState(0);
10
- const nodeRefs = useRef([]);
11
- /**
12
- * Roving tabindex, preferring whatever is pinned.
13
- *
14
- * Tabbing away and back returns to the category the reader was holding rather
15
- * than to the start, which is the difference between the pin being a place
16
- * you are and a thing that merely happened.
17
- */
18
- const rovingIndex = clamp(interaction.pinnedIndex ?? focusIndex, 0, targets.length - 1);
19
- const moveTo = useCallback((next) => {
20
- const last = targets.length - 1;
21
- const bounded = wrap ? (next % targets.length + targets.length) % targets.length : clamp(next, 0, last);
22
- setFocusIndex(bounded);
23
- nodeRefs.current[bounded]?.focus();
24
- }, [targets.length, wrap]);
25
- const onKeyDown = useCallback((event, index) => {
26
- if (match(event.key).with("ArrowRight", "ArrowDown", () => moveTo(index + 1)).with("ArrowLeft", "ArrowUp", () => moveTo(index - 1)).with("Home", () => moveTo(0)).with("End", () => moveTo(targets.length - 1)).with("Enter", " ", () => interaction.onCategoryClick(index)).with("Escape", () => interaction.onBackdropClick()).otherwise(() => void 0) !== void 0 || [
27
- "ArrowRight",
28
- "ArrowDown",
29
- "ArrowLeft",
30
- "ArrowUp",
31
- "Home",
32
- "End",
33
- "Enter",
34
- " ",
35
- "Escape"
36
- ].includes(event.key)) event.preventDefault();
37
- }, [
38
- moveTo,
39
- interaction,
40
- targets.length
41
- ]);
42
- return /* @__PURE__ */ jsx(ChartCategoryTargetLayer, { children: targets.map((target, index) => {
43
- const shared = {
44
- tabIndex: index === rovingIndex ? 0 : -1,
45
- role: "button",
46
- "aria-label": getAriaLabel(index),
47
- "aria-pressed": interaction.pinnedIndex === index,
48
- "data-interactive": "true",
49
- onFocus: () => interaction.onCategoryEnter(index),
50
- onBlur: () => interaction.onCategoryLeave(),
51
- onMouseEnter: () => interaction.onCategoryEnter(index),
52
- onMouseLeave: () => interaction.onCategoryLeave(),
53
- onClick: () => interaction.onCategoryClick(index),
54
- onKeyDown: (event) => onKeyDown(event, index)
55
- };
56
- return match(target).with({ shape: ChartTargetShape.RECT }, (rect) => /* @__PURE__ */ jsx(ChartHitTarget, {
57
- ref: (node) => {
58
- nodeRefs.current[index] = node;
59
- },
60
- x: rect.x,
61
- y: rect.y,
62
- width: rect.width,
63
- height: rect.height,
64
- ...shared
65
- }, rect.key)).with({ shape: ChartTargetShape.PATH }, (path) => /* @__PURE__ */ jsx(ChartHitTargetPath, {
66
- ref: (node) => {
67
- nodeRefs.current[index] = node;
68
- },
69
- d: path.d,
70
- ...shared
71
- }, path.key)).exhaustive();
72
- }) });
73
- };
6
+ var ChartCategoryTargets = ({ targets, interaction, inertIndices }) => /* @__PURE__ */ jsx(Fragment, { children: targets.map((target, index) => {
7
+ if (inertIndices?.has(index)) return null;
8
+ const shared = {
9
+ onMouseEnter: () => interaction.onCategoryEnter(index),
10
+ onMouseLeave: () => interaction.onCategoryLeave()
11
+ };
12
+ return match(target).with({ shape: ChartTargetShape.RECT }, (rect) => /* @__PURE__ */ jsx(ChartHitTarget, {
13
+ x: rect.x,
14
+ y: rect.y,
15
+ width: rect.width,
16
+ height: rect.height,
17
+ ...shared
18
+ }, rect.key)).with({ shape: ChartTargetShape.PATH }, (path) => /* @__PURE__ */ jsx(ChartHitTargetPath, {
19
+ d: path.d,
20
+ ...shared
21
+ }, path.key)).exhaustive();
22
+ }) });
74
23
  //#endregion
75
24
  export { ChartCategoryTargets as default };
@@ -1,13 +1,13 @@
1
1
  import type { ReactNode } from "react";
2
- import { ChartKind } from "./types";
2
+ import { ChartKind, ChartLegendPlacement } from "./types";
3
3
  import type { ChartCategoryTarget, ChartLegendItem, ChartPlotTooltipModel, ChartTooltipRenderer } from "./types";
4
4
  import type { ChartDimensions } from "./useChartDimensions";
5
5
  import type { ChartInteraction } from "./useChartInteraction";
6
6
  import type { ChartTooltipVerticalAlign } from "./useChartTooltipPosition";
7
7
  /**
8
8
  * The shell every chart renders into: measured root, reserved plot box, SVG and
9
- * its margin translate, the interactive plot group, the backdrop, the tooltip,
10
- * the empty state and the legend.
9
+ * its margin translate, the plot group, the hover targets, the tooltip, the
10
+ * empty state and the legend.
11
11
  *
12
12
  * **The frame owns the plot translate.** Marks are drawn inside a
13
13
  * `translate(margin.left, margin.top)` group, and every coordinate handed to
@@ -56,7 +56,7 @@ export interface ChartFrameProps {
56
56
  */
57
57
  axes?: ReactNode;
58
58
  /**
59
- * Marks, drawn inside the plot group above the backdrop.
59
+ * Marks, drawn inside the plot group beneath the hover targets.
60
60
  *
61
61
  * Rendered only once measured, so no caller writes that guard. Note it is
62
62
  * still *evaluated* on the unmeasured pass — as is the geometry it maps over,
@@ -72,8 +72,8 @@ export interface ChartFrameProps {
72
72
  *
73
73
  * Taken as a function rather than a finished model because the frame needs it
74
74
  * twice: once for the category under the pointer, and once per category to
75
- * name it for a screen reader. Deriving both from the same builder is what
76
- * makes the accessible name incapable of disagreeing with the tooltip.
75
+ * decide which categories are inert. Deriving both from the same builder is
76
+ * what makes "no tooltip rows" and "not hoverable" the same fact.
77
77
  *
78
78
  * Must be stable; memoize it at the call site.
79
79
  */
@@ -88,10 +88,6 @@ export interface ChartFrameProps {
88
88
  categoryTargets?: ChartCategoryTarget[];
89
89
  /** Applied to the target layer. Radial charts pass their centre translate. */
90
90
  categoryTargetsTransform?: string;
91
- /** Arrow keys wrap at the ends. True for radial charts. */
92
- wrapCategoryNavigation?: boolean;
93
- /** Overrides the accessible name of a category, which defaults to its tooltip. */
94
- describeCategory?: (index: number) => string;
95
91
  /** Half a category's width, so the tooltip sits beside the marks. */
96
92
  anchorHalfWidth?: number;
97
93
  /** Radial charts pass their centre, in plot coordinates. */
@@ -103,8 +99,14 @@ export interface ChartFrameProps {
103
99
  contentWhenEmpty?: ReactNode;
104
100
  showLegend?: boolean;
105
101
  legendItems: ChartLegendItem[];
106
- /** Defaults to the left margin, aligning the legend with the plot, not the gutter. */
102
+ /** Where the legend sits. RIGHT stacks it beside the plot, vertically centred. */
103
+ legendPlacement?: ChartLegendPlacement;
104
+ /**
105
+ * BOTTOM: left padding, defaulting to the left margin so the legend aligns
106
+ * with the plot rather than the axis gutter. RIGHT: the gap between the plot
107
+ * box and the legend, defaulting to {@link CHART_LEGEND_SIDE_GAP}.
108
+ */
107
109
  legendInset?: number;
108
110
  }
109
- declare const ChartFrame: ({ containerRef, dimensions, fillWidth, fillHeight, interaction, kind, categoryCount, ariaLabel, ariaDescription, axes, children, plotOverlay, noTooltip, buildCategoryTooltip, tooltipRenderer, categoryTargets, categoryTargetsTransform, wrapCategoryNavigation, describeCategory, anchorHalfWidth, placeAwayFromX, verticalAlign, isEmpty, isLoading, contentWhenEmpty, showLegend, legendItems, legendInset, }: ChartFrameProps) => import("react").JSX.Element;
111
+ declare const ChartFrame: ({ containerRef, dimensions, fillWidth, fillHeight, interaction, kind, categoryCount, ariaLabel, ariaDescription, axes, children, plotOverlay, noTooltip, buildCategoryTooltip, tooltipRenderer, categoryTargets, categoryTargetsTransform, anchorHalfWidth, placeAwayFromX, verticalAlign, isEmpty, isLoading, contentWhenEmpty, showLegend, legendItems, legendPlacement, legendInset, }: ChartFrameProps) => import("react").JSX.Element;
110
112
  export default ChartFrame;