@galaxy-io/dls 1.5.10 → 1.5.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import { BarChartNormalization } from "./types";
2
- import type { BarChartGroupDatum, BaseCartesianChartProps, ChartSeriesStyles, ChartSize } from "./types";
2
+ import type { BarChartGroupDatum, BaseCartesianChartProps, ChartSelectionEvent, ChartSelectionInput, ChartSeriesStyles, ChartSize } from "./types";
3
3
  export type BarChartProps<TMetric extends string, TComponentKey extends string = string> = BaseCartesianChartProps & ChartSize & {
4
4
  /**
5
5
  * Presentation for every metric, keyed by metric.
@@ -16,6 +16,30 @@ export type BarChartProps<TMetric extends string, TComponentKey extends string =
16
16
  /** Tint the active category's full band, marking the hover position even
17
17
  * where there is no data. @default true */
18
18
  showCursor?: boolean;
19
+ /**
20
+ * Fires when a bar segment or a category is clicked. Supplying it is what
21
+ * makes the plot clickable.
22
+ *
23
+ * A click resolves to the finest thing under the pointer: a segment reports
24
+ * its own key and value, while the gutters, the plot above a short bar and
25
+ * the hairline seams between segments fall back to the category alone.
26
+ *
27
+ * `seriesKey` follows the same rule the legend and the mute axis do —
28
+ * components when the chart is stacked, metrics when it is not — so all
29
+ * three always name the same dimension.
30
+ */
31
+ onSelect?: (event: ChartSelectionEvent<NoInfer<TMetric> | NoInfer<TComponentKey>>) => void;
32
+ /**
33
+ * Which marks read as selected. Matching bars hold full strength, the rest
34
+ * recede, and each selected category takes a persistent band — the hover
35
+ * tint one step firmer, held rather than previewed.
36
+ *
37
+ * Presentational and never written by the chart: the state belongs to
38
+ * whatever the selection is filtering, so the two cannot drift apart. Feed
39
+ * an {@link onSelect} event straight back, or describe a broader set —
40
+ * `{ seriesKey: "failed" }` selects every failure on the axis.
41
+ */
42
+ selection?: ChartSelectionInput<NoInfer<TMetric> | NoInfer<TComponentKey>>;
19
43
  };
20
44
  /**
21
45
  * Bar chart with a three-level data model.
@@ -24,5 +48,5 @@ export type BarChartProps<TMetric extends string, TComponentKey extends string =
24
48
  * `bars` in a group render side by side, several `components` in a bar stack,
25
49
  * and a chart doing both at once needs no extra configuration.
26
50
  */
27
- declare const BarChart: <TMetric extends string, TComponentKey extends string = string>({ series, groups, width, height, fillWidth, fillHeight, aspectRatio, margin, normalization, showCursor, valueUnit, valueFormatter, labelFormatter, valueDomain, categoryLabelMode, categoryAxisTitle, valueAxisTitle, showGrid, showLegend, legendMaxItems, noTooltip, tooltipRenderer, tooltipMaxItems, isLoading, contentWhenEmpty, ariaLabel, ariaDescription, }: BarChartProps<TMetric, TComponentKey>) => import("react").JSX.Element;
51
+ declare const BarChart: <TMetric extends string, TComponentKey extends string = string>({ series, groups, width, height, fillWidth, fillHeight, aspectRatio, margin, normalization, showCursor, onSelect, selection, valueUnit, valueFormatter, labelFormatter, valueDomain, categoryLabelMode, categoryAxisTitle, valueAxisTitle, showGrid, showLegend, legendMaxItems, noTooltip, tooltipRenderer, tooltipMaxItems, isLoading, contentWhenEmpty, ariaLabel, ariaDescription, }: BarChartProps<TMetric, TComponentKey>) => import("react").JSX.Element;
28
52
  export default BarChart;
@@ -17,7 +17,7 @@ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
17
17
  * `bars` in a group render side by side, several `components` in a bar stack,
18
18
  * and a chart doing both at once needs no extra configuration.
19
19
  */
20
- var BarChart = ({ series, groups, width, height, fillWidth, fillHeight, aspectRatio, margin, normalization = BarChartNormalization.NONE, showCursor = true, valueUnit, valueFormatter, labelFormatter, valueDomain, categoryLabelMode, categoryAxisTitle, valueAxisTitle, showGrid = true, showLegend = true, legendMaxItems, noTooltip = false, tooltipRenderer, tooltipMaxItems, isLoading = false, contentWhenEmpty, ariaLabel, ariaDescription }) => {
20
+ var BarChart = ({ series, groups, width, height, fillWidth, fillHeight, aspectRatio, margin, normalization = BarChartNormalization.NONE, showCursor = true, onSelect, selection, valueUnit, valueFormatter, labelFormatter, valueDomain, categoryLabelMode, categoryAxisTitle, valueAxisTitle, showGrid = true, showLegend = true, legendMaxItems, noTooltip = false, tooltipRenderer, tooltipMaxItems, isLoading = false, contentWhenEmpty, ariaLabel, ariaDescription }) => {
21
21
  const containerRef = useRef(null);
22
22
  const colors = useSeriesColorResolver(series);
23
23
  /**
@@ -107,6 +107,7 @@ var BarChart = ({ series, groups, width, height, fillWidth, fillHeight, aspectRa
107
107
  seriesKeys: legendKeys,
108
108
  categoryKeys,
109
109
  categoryMuteState: ChartMarkState.DIMMED,
110
+ selection,
110
111
  isDisabled: noTooltip || isLoading || isEmpty,
111
112
  isLoading
112
113
  });
@@ -144,6 +145,47 @@ var BarChart = ({ series, groups, width, height, fillWidth, fillHeight, aspectRa
144
145
  width: band.width,
145
146
  height: band.height
146
147
  })), [geometry.bands]);
148
+ /**
149
+ * One clickable target per drawn rectangle, over the bands.
150
+ *
151
+ * The rects already carry everything a click needs, so the target is exactly
152
+ * the mark — no inflation to make a thin segment easier to hit. Growing one
153
+ * would overlap the neighbour whose edge the geometry snapped to the same
154
+ * pixel, and a segment too small to hit still lands on the band beneath and
155
+ * selects its category, which is the honest answer for a sliver.
156
+ */
157
+ const markTargets = useMemo(() => geometry.rects.map((rect) => ({
158
+ key: rect.id,
159
+ categoryIndex: rect.groupIndex,
160
+ shape: ChartTargetShape.RECT,
161
+ x: rect.x,
162
+ y: rect.y,
163
+ width: rect.width,
164
+ height: rect.height
165
+ })), [geometry.rects]);
166
+ const handleCategorySelect = useCallback((index) => {
167
+ const group = groups[index];
168
+ if (!group || !onSelect) return;
169
+ onSelect({
170
+ categoryIndex: index,
171
+ categoryKey: group.label
172
+ });
173
+ }, [groups, onSelect]);
174
+ const handleMarkSelect = useCallback((index) => {
175
+ const rect = geometry.rects[index];
176
+ if (!rect || !onSelect) return;
177
+ onSelect({
178
+ categoryIndex: rect.groupIndex,
179
+ categoryKey: groups[rect.groupIndex]?.label ?? rect.metric,
180
+ seriesKey: isStacked ? rect.componentKey : rect.metric,
181
+ value: rect.value
182
+ });
183
+ }, [
184
+ geometry.rects,
185
+ groups,
186
+ isStacked,
187
+ onSelect
188
+ ]);
147
189
  return /* @__PURE__ */ jsxs(ChartFrame, {
148
190
  containerRef,
149
191
  dimensions,
@@ -178,6 +220,9 @@ var BarChart = ({ series, groups, width, height, fillWidth, fillHeight, aspectRa
178
220
  tooltipRenderer,
179
221
  tooltipMaxItems,
180
222
  categoryTargets,
223
+ markTargets,
224
+ onCategorySelect: onSelect && handleCategorySelect,
225
+ onMarkSelect: onSelect && handleMarkSelect,
181
226
  anchorHalfWidth: categoryStep / 2,
182
227
  isEmpty,
183
228
  isLoading,
@@ -185,19 +230,29 @@ var BarChart = ({ series, groups, width, height, fillWidth, fillHeight, aspectRa
185
230
  showLegend,
186
231
  legendItems,
187
232
  legendMaxItems,
188
- children: [showCursor && activeBand && /* @__PURE__ */ jsx(ChartCursorBand, {
189
- x: activeBand.x,
190
- y: activeBand.y,
191
- width: activeBand.width,
192
- height: activeBand.height
193
- }), geometry.rects.map((rect) => /* @__PURE__ */ jsx(ChartBarPath, {
194
- d: roundedRectPath(rect.x, rect.y, rect.width, rect.height, rect.radius, rect.corners),
195
- fill: rect.color,
196
- "data-state": interaction.getMarkState({
197
- categoryIndex: rect.groupIndex,
198
- seriesKey: isStacked ? rect.componentKey : rect.metric
199
- })
200
- }, rect.id))]
233
+ children: [
234
+ geometry.bands.filter((band) => interaction.selectedCategoryIndices.has(band.groupIndex)).map((band) => /* @__PURE__ */ jsx(ChartCursorBand, {
235
+ x: band.x,
236
+ y: band.y,
237
+ width: band.width,
238
+ height: band.height,
239
+ "data-selected": "true"
240
+ }, `selected:${band.groupIndex}`)),
241
+ showCursor && activeBand && /* @__PURE__ */ jsx(ChartCursorBand, {
242
+ x: activeBand.x,
243
+ y: activeBand.y,
244
+ width: activeBand.width,
245
+ height: activeBand.height
246
+ }),
247
+ geometry.rects.map((rect) => /* @__PURE__ */ jsx(ChartBarPath, {
248
+ d: roundedRectPath(rect.x, rect.y, rect.width, rect.height, rect.radius, rect.corners),
249
+ fill: rect.color,
250
+ "data-state": interaction.getMarkState({
251
+ categoryIndex: rect.groupIndex,
252
+ seriesKey: isStacked ? rect.componentKey : rect.metric
253
+ })
254
+ }, rect.id))
255
+ ]
201
256
  });
202
257
  };
203
258
  //#endregion
@@ -1,7 +1,7 @@
1
1
  import type { ChartCategoryTarget } from "./types";
2
2
  import type { ChartInteraction } from "./useChartInteraction";
3
3
  /**
4
- * The hover layer: one transparent target per category, above the marks.
4
+ * The base hit layer: one transparent target per category, above the marks.
5
5
  *
6
6
  * Shared by all three charts so hit testing is spelled once. Targets carry no
7
7
  * roles and no tab stops — the plot is `role="img"` and its accessible story is
@@ -10,10 +10,19 @@ import type { ChartInteraction } from "./useChartInteraction";
10
10
  * Every category is hoverable, including one with nothing to show: the cursor
11
11
  * still marks where the pointer is over an empty stretch of axis, and the
12
12
  * tooltip opens with a "No data" body so the hover always gets an answer.
13
+ *
14
+ * **Only `onMouseEnter` lives here; leaving is the target group's job.** Per-target
15
+ * leave handlers fire on every crossing *within* the plot, so the state fell to
16
+ * null and back on each one — a wasted render between neighbouring bands, and a
17
+ * visible flicker once the mark layer above added a second boundary inside a
18
+ * single category. The group's leave fires exactly once, when the pointer
19
+ * actually leaves the plot.
13
20
  */
14
21
  interface ChartCategoryTargetsProps {
15
22
  targets: ChartCategoryTarget[];
16
23
  interaction: ChartInteraction;
24
+ /** Supplying this makes the category clickable, cursor included. */
25
+ onSelect?: (categoryIndex: number) => void;
17
26
  }
18
- declare const ChartCategoryTargets: ({ targets, interaction }: ChartCategoryTargetsProps) => import("react").JSX.Element;
27
+ declare const ChartCategoryTargets: ({ targets, interaction, onSelect }: ChartCategoryTargetsProps) => import("react").JSX.Element;
19
28
  export default ChartCategoryTargets;
@@ -3,10 +3,11 @@ import { ChartHitTarget, ChartHitTargetPath } from "./ChartPrimitives.js";
3
3
  import { match } from "ts-pattern";
4
4
  import { Fragment, jsx } from "react/jsx-runtime";
5
5
  //#region src/charts/ChartCategoryTargets.tsx
6
- var ChartCategoryTargets = ({ targets, interaction }) => /* @__PURE__ */ jsx(Fragment, { children: targets.map((target, index) => {
6
+ var ChartCategoryTargets = ({ targets, interaction, onSelect }) => /* @__PURE__ */ jsx(Fragment, { children: targets.map((target, index) => {
7
7
  const shared = {
8
8
  onMouseEnter: () => interaction.onCategoryEnter(index),
9
- onMouseLeave: () => interaction.onCategoryLeave()
9
+ onClick: onSelect === void 0 ? void 0 : () => onSelect(index),
10
+ "data-clickable": onSelect === void 0 ? void 0 : "true"
10
11
  };
11
12
  return match(target).with({ shape: ChartTargetShape.RECT }, (rect) => /* @__PURE__ */ jsx(ChartHitTarget, {
12
13
  x: rect.x,
@@ -1,6 +1,6 @@
1
1
  import type { ReactNode } from "react";
2
2
  import { ChartKind, ChartLegendPlacement } from "./types";
3
- import type { ChartCategoryTarget, ChartLegendItem, ChartPlotTooltipModel, ChartTooltipRenderer } from "./types";
3
+ import type { ChartCategoryTarget, ChartLegendItem, ChartMarkTarget, 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";
@@ -87,8 +87,25 @@ export interface ChartFrameProps {
87
87
  * plot passes.
88
88
  */
89
89
  categoryTargets?: ChartCategoryTarget[];
90
- /** Applied to the target layer. Radial charts pass their centre translate. */
90
+ /**
91
+ * One target per rendered mark, painted over {@link categoryTargets} so a
92
+ * click can name a segment rather than only its column. Omitted by the charts
93
+ * whose marks are not separately clickable — see `ChartMarkTargets`.
94
+ */
95
+ markTargets?: ChartMarkTarget[];
96
+ /** Applied to both target layers. Radial charts pass their centre translate. */
91
97
  categoryTargetsTransform?: string;
98
+ /**
99
+ * Click on a category, by index. Supplying it is what makes the plot
100
+ * clickable at all — the cursor and the mark layer's handler both follow it.
101
+ *
102
+ * Index-based, because building the event needs the chart's own geometry and
103
+ * vocabulary. The frame knows neither, exactly as with
104
+ * {@link buildCategoryTooltip}.
105
+ */
106
+ onCategorySelect?: (categoryIndex: number) => void;
107
+ /** Click on one mark, by its index in {@link markTargets}. */
108
+ onMarkSelect?: (markIndex: number) => void;
92
109
  /** Half a category's width, so the tooltip sits beside the marks. */
93
110
  anchorHalfWidth?: number;
94
111
  /** Radial charts pass their centre, in plot coordinates. */
@@ -111,5 +128,5 @@ export interface ChartFrameProps {
111
128
  */
112
129
  legendInset?: number;
113
130
  }
114
- declare const ChartFrame: ({ containerRef, dimensions, fillWidth, fillHeight, interaction, kind, categoryCount, ariaLabel, ariaDescription, axes, children, plotOverlay, noTooltip, buildCategoryTooltip, tooltipRenderer, tooltipMaxItems, categoryTargets, categoryTargetsTransform, anchorHalfWidth, placeAwayFromX, verticalAlign, isEmpty, isLoading, contentWhenEmpty, showLegend, legendItems, legendMaxItems, legendPlacement, legendInset, }: ChartFrameProps) => import("react").JSX.Element;
131
+ declare const ChartFrame: ({ containerRef, dimensions, fillWidth, fillHeight, interaction, kind, categoryCount, ariaLabel, ariaDescription, axes, children, plotOverlay, noTooltip, buildCategoryTooltip, tooltipRenderer, tooltipMaxItems, categoryTargets, markTargets, categoryTargetsTransform, onCategorySelect, onMarkSelect, anchorHalfWidth, placeAwayFromX, verticalAlign, isEmpty, isLoading, contentWhenEmpty, showLegend, legendItems, legendMaxItems, legendPlacement, legendInset, }: ChartFrameProps) => import("react").JSX.Element;
115
132
  export default ChartFrame;
@@ -4,12 +4,13 @@ import { ChartPlotBox, ChartRoot, ChartSvg } from "./ChartPrimitives.js";
4
4
  import ChartCategoryTargets from "./ChartCategoryTargets.js";
5
5
  import ChartEmptyState from "./ChartEmptyState.js";
6
6
  import ChartLegend from "./ChartLegend.js";
7
+ import ChartMarkTargets from "./ChartMarkTargets.js";
7
8
  import ChartTooltip from "./ChartTooltip.js";
8
9
  import { match } from "ts-pattern";
9
10
  import { useId, useMemo } from "react";
10
11
  import { jsx, jsxs } from "react/jsx-runtime";
11
12
  //#region src/charts/ChartFrame.tsx
12
- var ChartFrame = ({ containerRef, dimensions, fillWidth, fillHeight, interaction, kind, categoryCount, ariaLabel, ariaDescription, axes, children, plotOverlay, noTooltip = false, buildCategoryTooltip, tooltipRenderer, tooltipMaxItems, categoryTargets, categoryTargetsTransform, anchorHalfWidth, placeAwayFromX, verticalAlign, isEmpty, isLoading = false, contentWhenEmpty, showLegend = false, legendItems, legendMaxItems, legendPlacement = ChartLegendPlacement.BOTTOM, legendInset }) => {
13
+ var ChartFrame = ({ containerRef, dimensions, fillWidth, fillHeight, interaction, kind, categoryCount, ariaLabel, ariaDescription, axes, children, plotOverlay, noTooltip = false, buildCategoryTooltip, tooltipRenderer, tooltipMaxItems, categoryTargets, markTargets, categoryTargetsTransform, onCategorySelect, onMarkSelect, anchorHalfWidth, placeAwayFromX, verticalAlign, isEmpty, isLoading = false, contentWhenEmpty, showLegend = false, legendItems, legendMaxItems, legendPlacement = ChartLegendPlacement.BOTTOM, legendInset }) => {
13
14
  const { width, height, margin, isMeasured, reservedHeight, reservedAspectRatio } = dimensions;
14
15
  const generatedId = useId();
15
16
  const descriptionId = ariaDescription ? `${generatedId}-desc` : void 0;
@@ -83,12 +84,18 @@ var ChartFrame = ({ containerRef, dimensions, fillWidth, fillHeight, interaction
83
84
  children: [
84
85
  axes,
85
86
  /* @__PURE__ */ jsx("g", { children }),
86
- hasTargets && categoryTargets && /* @__PURE__ */ jsx("g", {
87
+ hasTargets && categoryTargets && /* @__PURE__ */ jsxs("g", {
87
88
  transform: categoryTargetsTransform,
88
- children: /* @__PURE__ */ jsx(ChartCategoryTargets, {
89
+ onMouseLeave: interaction.onCategoryLeave,
90
+ children: [/* @__PURE__ */ jsx(ChartCategoryTargets, {
89
91
  targets: categoryTargets,
90
- interaction
91
- })
92
+ interaction,
93
+ onSelect: onCategorySelect
94
+ }), onMarkSelect && markTargets && markTargets.length > 0 && /* @__PURE__ */ jsx(ChartMarkTargets, {
95
+ targets: markTargets,
96
+ interaction,
97
+ onSelect: onMarkSelect
98
+ })]
92
99
  })
93
100
  ]
94
101
  })]
@@ -0,0 +1,30 @@
1
+ import type { ChartMarkTarget } from "./types";
2
+ import type { ChartInteraction } from "./useChartInteraction";
3
+ /**
4
+ * The mark hit layer: one transparent target per rendered mark, above the
5
+ * category targets.
6
+ *
7
+ * Exists so a click can name a single segment — the red block of the 00:00 bar
8
+ * — where the category layer beneath can only name the column. Rendered by the
9
+ * charts that have marks worth distinguishing: BarChart does, LineChart has no
10
+ * segments to hit, and PieChart's category targets already *are* its arcs.
11
+ *
12
+ * **Hover is not refined here.** Each target reports its own category on enter,
13
+ * exactly as the band underneath would, so the tooltip reads the same index
14
+ * whichever layer the pointer is over and holds still while crossing a stack.
15
+ * The banded hover is deliberate — it makes a two-pixel segment as easy to
16
+ * hover as a two-hundred-pixel one — and this layer only adds a finer *click*.
17
+ *
18
+ * Anything the marks do not cover falls through to the category target: the
19
+ * gutters, the plot above a short bar, and the hairline gaps between stack
20
+ * segments. That is the documented fallback, and it is why a segment too small
21
+ * to hit still selects something sensible.
22
+ */
23
+ interface ChartMarkTargetsProps {
24
+ targets: ChartMarkTarget[];
25
+ interaction: ChartInteraction;
26
+ /** Supplying this makes the marks clickable, cursor included. */
27
+ onSelect?: (markIndex: number) => void;
28
+ }
29
+ declare const ChartMarkTargets: ({ targets, interaction, onSelect }: ChartMarkTargetsProps) => import("react").JSX.Element;
30
+ export default ChartMarkTargets;
@@ -0,0 +1,24 @@
1
+ import { ChartTargetShape } from "./types.js";
2
+ import { ChartHitTarget, ChartHitTargetPath } from "./ChartPrimitives.js";
3
+ import { match } from "ts-pattern";
4
+ import { Fragment, jsx } from "react/jsx-runtime";
5
+ //#region src/charts/ChartMarkTargets.tsx
6
+ var ChartMarkTargets = ({ targets, interaction, onSelect }) => /* @__PURE__ */ jsx(Fragment, { children: targets.map((target, index) => {
7
+ const shared = {
8
+ onMouseEnter: () => interaction.onCategoryEnter(target.categoryIndex),
9
+ onClick: onSelect === void 0 ? void 0 : () => onSelect(index),
10
+ "data-clickable": onSelect === void 0 ? void 0 : "true"
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
+ }) });
23
+ //#endregion
24
+ export { ChartMarkTargets as default };
@@ -51,15 +51,19 @@ export declare const ChartAreaPath: import("@linaria/react").StyledComponent<imp
51
51
  export declare const ChartPointCircle: import("@linaria/react").StyledComponent<import("react").SVGProps<SVGCircleElement> & Record<never, unknown>>;
52
52
  export declare const ChartArcPath: import("@linaria/react").StyledComponent<import("react").SVGProps<SVGPathElement> & Record<never, unknown>>;
53
53
  /**
54
- * Per-category hit target, rendered above the marks.
54
+ * Hit target, rendered above the marks. Used for both layers — one per category
55
+ * beneath, one per mark above.
55
56
  *
56
57
  * Hit testing the full column rather than the bars themselves means a two-pixel
57
58
  * bar is as easy to hover as a two-hundred-pixel one, and crossing between
58
- * segments inside a category fires no events at all — so the tooltip holds
59
+ * segments inside a category changes nothing the tooltip reads — so it holds
59
60
  * still instead of chasing each segment.
60
61
  *
61
- * No cursor and no focus treatment: the target only previews on hover, and a
62
- * pointer cursor would promise a click that does nothing.
62
+ * The pointer cursor is conditional because the promise is: it appears only
63
+ * where a click actually reports something, and a chart with no `onSelect`
64
+ * keeps the default cursor rather than advertising an interaction it does not
65
+ * have. No focus treatment either — the plot is `role="img"` and its marks are
66
+ * not in the accessibility tree, so there is nothing here to focus yet.
63
67
  */
64
68
  export declare const ChartHitTarget: import("@linaria/react").StyledComponent<import("react").SVGProps<SVGRectElement> & Record<never, unknown>>;
65
69
  /**
@@ -93,7 +97,14 @@ export declare const ChartGridLine: import("react").ComponentType<Pick<import("r
93
97
  } & {
94
98
  as?: React.ElementType;
95
99
  }>, {}>;
96
- /** Vertical rule marking the hovered category on a line chart. */
100
+ /**
101
+ * Vertical rule marking the hovered category on a line chart.
102
+ *
103
+ * `data-selected` steps it up the same neutral ramp rather than introducing a
104
+ * hue: a selection is the *same* mark held rather than previewed, and a colour
105
+ * change would read as a different kind of thing. The state is an attribute
106
+ * rather than a `$` prop for the reason at the top of this file.
107
+ */
97
108
  export declare const ChartCursorLine: import("react").ComponentType<Pick<import("react").SVGLineElementAttributes<SVGLineElement> & {
98
109
  theme: import("../theme").Theme;
99
110
  } & {
@@ -115,7 +126,11 @@ export declare const ChartCursorLine: import("react").ComponentType<Pick<import(
115
126
  * Full-height tint over the hovered category's band on a bar chart — the bar
116
127
  * counterpart to {@link ChartCursorLine}. A rule reads oddly threaded through
117
128
  * wide filled marks; a quiet band behind them marks the same position without
118
- * touching them. 8% of the foreground, so it survives both themes.
129
+ * touching them.
130
+ *
131
+ * 4% of the foreground, so it survives both themes. Deliberately fainter than
132
+ * the line chart's rule: a band is a large area where a rule is a hairline, and
133
+ * the same value that reads as quiet on one shouts on the other.
119
134
  */
120
135
  export declare const ChartCursorBand: import("react").ComponentType<Pick<import("react").SVGProps<SVGRectElement> & {
121
136
  theme: import("../theme").Theme;
@@ -128,15 +128,19 @@ var ChartArcPath = /*#__PURE__*/ styled("path")({
128
128
  propsAsIs: true
129
129
  });
130
130
  /**
131
- * Per-category hit target, rendered above the marks.
131
+ * Hit target, rendered above the marks. Used for both layers — one per category
132
+ * beneath, one per mark above.
132
133
  *
133
134
  * Hit testing the full column rather than the bars themselves means a two-pixel
134
135
  * bar is as easy to hover as a two-hundred-pixel one, and crossing between
135
- * segments inside a category fires no events at all — so the tooltip holds
136
+ * segments inside a category changes nothing the tooltip reads — so it holds
136
137
  * still instead of chasing each segment.
137
138
  *
138
- * No cursor and no focus treatment: the target only previews on hover, and a
139
- * pointer cursor would promise a click that does nothing.
139
+ * The pointer cursor is conditional because the promise is: it appears only
140
+ * where a click actually reports something, and a chart with no `onSelect`
141
+ * keeps the default cursor rather than advertising an interaction it does not
142
+ * have. No focus treatment either — the plot is `role="img"` and its marks are
143
+ * not in the accessibility tree, so there is nothing here to focus yet.
140
144
  */
141
145
  var ChartHitTarget = /*#__PURE__*/ styled("rect")({
142
146
  name: "ChartHitTarget",
@@ -168,26 +172,45 @@ var ChartGridLine = withTheme(/*#__PURE__*/ styled("line")({
168
172
  propsAsIs: true,
169
173
  vars: { "c17guewt-0": [_exp15()] }
170
174
  }));
171
- /** Vertical rule marking the hovered category on a line chart. */
175
+ /**
176
+ * Vertical rule marking the hovered category on a line chart.
177
+ *
178
+ * `data-selected` steps it up the same neutral ramp rather than introducing a
179
+ * hue: a selection is the *same* mark held rather than previewed, and a colour
180
+ * change would read as a different kind of thing. The state is an attribute
181
+ * rather than a `$` prop for the reason at the top of this file.
182
+ */
172
183
  var _exp16 = () => ({ theme }) => theme.color.border.secondary;
184
+ var _exp17 = () => ({ theme }) => theme.color.opacity.bw32Alt;
173
185
  var ChartCursorLine = withTheme(/*#__PURE__*/ styled("line")({
174
186
  name: "ChartCursorLine",
175
187
  class: "cblgiyf",
176
188
  propsAsIs: true,
177
- vars: { "cblgiyf-0": [_exp16()] }
189
+ vars: {
190
+ "cblgiyf-0": [_exp16()],
191
+ "cblgiyf-1": [_exp17()]
192
+ }
178
193
  }));
179
194
  /**
180
195
  * Full-height tint over the hovered category's band on a bar chart — the bar
181
196
  * counterpart to {@link ChartCursorLine}. A rule reads oddly threaded through
182
197
  * wide filled marks; a quiet band behind them marks the same position without
183
- * touching them. 8% of the foreground, so it survives both themes.
198
+ * touching them.
199
+ *
200
+ * 4% of the foreground, so it survives both themes. Deliberately fainter than
201
+ * the line chart's rule: a band is a large area where a rule is a hairline, and
202
+ * the same value that reads as quiet on one shouts on the other.
184
203
  */
185
- var _exp17 = () => ({ theme }) => theme.color.opacity.bw8Alt;
204
+ var _exp18 = () => ({ theme }) => theme.color.opacity.b4Alt;
205
+ var _exp19 = () => ({ theme }) => theme.color.opacity.bw8Alt;
186
206
  var ChartCursorBand = withTheme(/*#__PURE__*/ styled("rect")({
187
207
  name: "ChartCursorBand",
188
208
  class: "c1lr03cc",
189
209
  propsAsIs: true,
190
- vars: { "c1lr03cc-0": [_exp17()] }
210
+ vars: {
211
+ "c1lr03cc-0": [_exp18()],
212
+ "c1lr03cc-1": [_exp19()]
213
+ }
191
214
  }));
192
215
  var ChartAxisLabelBox = /*#__PURE__*/ styled("foreignObject")({
193
216
  name: "ChartAxisLabelBox",
@@ -201,21 +224,21 @@ var ChartAxisLabelBox = /*#__PURE__*/ styled("foreignObject")({
201
224
  * than teleporting, and it stays mounted while hidden so the fade can actually
202
225
  * run — unmounting on hide is why a fade-out never appears.
203
226
  */
204
- var _exp18 = () => ({ $x }) => `${$x}px`;
205
- var _exp19 = () => ({ $y }) => `${$y}px`;
206
- var _exp22 = () => ({ theme }) => theme.color.border.primary;
207
- var _exp23 = () => ({ theme }) => theme.color.background.tertiary;
208
- var _exp24 = () => ({ $isVisible }) => $isVisible ? 1 : 0;
227
+ var _exp20 = () => ({ $x }) => `${$x}px`;
228
+ var _exp21 = () => ({ $y }) => `${$y}px`;
229
+ var _exp24 = () => ({ theme }) => theme.color.border.primary;
230
+ var _exp25 = () => ({ theme }) => theme.color.background.tertiary;
231
+ var _exp26 = () => ({ $isVisible }) => $isVisible ? 1 : 0;
209
232
  var ChartTooltipPositioner = withTheme(/*#__PURE__*/ styled("div")({
210
233
  name: "ChartTooltipPositioner",
211
234
  class: "c1ukq11d",
212
235
  propsAsIs: false,
213
236
  vars: {
214
- "c1ukq11d-0": [_exp18()],
215
- "c1ukq11d-1": [_exp19()],
216
- "c1ukq11d-2": [_exp22()],
217
- "c1ukq11d-3": [_exp23()],
218
- "c1ukq11d-4": [_exp24()]
237
+ "c1ukq11d-0": [_exp20()],
238
+ "c1ukq11d-1": [_exp21()],
239
+ "c1ukq11d-2": [_exp24()],
240
+ "c1ukq11d-3": [_exp25()],
241
+ "c1ukq11d-4": [_exp26()]
219
242
  }
220
243
  }));
221
244
  /**
@@ -252,58 +275,58 @@ var ChartTooltipValueCell = /*#__PURE__*/ styled("div")({
252
275
  * of the DLS. `pointer-events: none` keeps it from stealing hover from the arc
253
276
  * behind it.
254
277
  */
255
- var _exp28 = () => ({ $centerX }) => `${$centerX}px`;
256
- var _exp29 = () => ({ $centerY }) => `${$centerY}px`;
257
- var _exp31 = () => ({ $diameter }) => `${$diameter}px`;
278
+ var _exp30 = () => ({ $centerX }) => `${$centerX}px`;
279
+ var _exp31 = () => ({ $centerY }) => `${$centerY}px`;
280
+ var _exp33 = () => ({ $diameter }) => `${$diameter}px`;
258
281
  var ChartCenterBox = /*#__PURE__*/ styled("div")({
259
282
  name: "ChartCenterBox",
260
283
  class: "cz6jod1",
261
284
  propsAsIs: false,
262
285
  vars: {
263
- "cz6jod1-0": [_exp28()],
264
- "cz6jod1-1": [_exp29()],
265
- "cz6jod1-2": [_exp31()]
286
+ "cz6jod1-0": [_exp30()],
287
+ "cz6jod1-1": [_exp31()],
288
+ "cz6jod1-2": [_exp33()]
266
289
  }
267
290
  });
268
291
  /**
269
292
  * Sits slightly above centre, over where the bars would be, so the axes stay
270
293
  * legible underneath.
271
294
  */
272
- var _exp32 = () => ({ theme }) => theme.color.border.primary;
273
- var _exp33 = () => ({ theme }) => theme.color.background.primary;
295
+ var _exp34 = () => ({ theme }) => theme.color.border.primary;
296
+ var _exp35 = () => ({ theme }) => theme.color.background.primary;
274
297
  var ChartEmptyOverlay = withTheme(/*#__PURE__*/ styled("div")({
275
298
  name: "ChartEmptyOverlay",
276
299
  class: "c1de5mqk",
277
300
  propsAsIs: false,
278
301
  vars: {
279
- "c1de5mqk-0": [_exp32()],
280
- "c1de5mqk-1": [_exp33()]
302
+ "c1de5mqk-0": [_exp34()],
303
+ "c1de5mqk-1": [_exp35()]
281
304
  }
282
305
  }));
283
306
  /**
284
307
  * Aligns the legend under the plot rather than the value-axis gutter.
285
308
  */
286
- var _exp34 = () => ({ $vertical }) => $vertical ? "column" : "row";
287
- var _exp35 = () => ({ $vertical }) => $vertical ? "flex-start" : "center";
288
- var _exp36 = () => ({ $vertical }) => $vertical ? "center" : "flex-start";
289
- var _exp37 = () => ({ $vertical }) => $vertical ? "auto" : "0";
290
- var _exp38 = () => ({ $vertical }) => $vertical ? "0" : "100%";
291
- var _exp39 = () => ({ $vertical, $extent }) => $vertical && $extent ? `${$extent}px` : "auto";
292
- var _exp40 = () => ({ $vertical }) => $vertical ? "0" : "10px";
293
- var _exp41 = () => ({ $inset }) => `${$inset}px`;
309
+ var _exp36 = () => ({ $vertical }) => $vertical ? "column" : "row";
310
+ var _exp37 = () => ({ $vertical }) => $vertical ? "flex-start" : "center";
311
+ var _exp38 = () => ({ $vertical }) => $vertical ? "center" : "flex-start";
312
+ var _exp39 = () => ({ $vertical }) => $vertical ? "auto" : "0";
313
+ var _exp40 = () => ({ $vertical }) => $vertical ? "0" : "100%";
314
+ var _exp41 = () => ({ $vertical, $extent }) => $vertical && $extent ? `${$extent}px` : "auto";
315
+ var _exp42 = () => ({ $vertical }) => $vertical ? "0" : "10px";
316
+ var _exp43 = () => ({ $inset }) => `${$inset}px`;
294
317
  var ChartLegendRow = /*#__PURE__*/ styled("div")({
295
318
  name: "ChartLegendRow",
296
319
  class: "cjirv6a",
297
320
  propsAsIs: false,
298
321
  vars: {
299
- "cjirv6a-0": [_exp34()],
300
- "cjirv6a-1": [_exp35()],
301
- "cjirv6a-2": [_exp36()],
302
- "cjirv6a-3": [_exp37()],
303
- "cjirv6a-4": [_exp38()],
304
- "cjirv6a-5": [_exp39()],
305
- "cjirv6a-6": [_exp40()],
306
- "cjirv6a-7": [_exp41()]
322
+ "cjirv6a-0": [_exp36()],
323
+ "cjirv6a-1": [_exp37()],
324
+ "cjirv6a-2": [_exp38()],
325
+ "cjirv6a-3": [_exp39()],
326
+ "cjirv6a-4": [_exp40()],
327
+ "cjirv6a-5": [_exp41()],
328
+ "cjirv6a-6": [_exp42()],
329
+ "cjirv6a-7": [_exp43()]
307
330
  }
308
331
  });
309
332
  /**
@@ -333,15 +356,15 @@ var ChartLegendMeasureClip = /*#__PURE__*/ styled("div")({
333
356
  * Absolute and hidden: it must not affect the row's layout, and measuring live
334
357
  * entries instead would conflate "what fits" with "what is currently shown".
335
358
  */
336
- var _exp42 = () => ({ $vertical }) => $vertical ? "column" : "row";
337
- var _exp43 = () => ({ $vertical }) => $vertical ? "flex-start" : "center";
359
+ var _exp44 = () => ({ $vertical }) => $vertical ? "column" : "row";
360
+ var _exp45 = () => ({ $vertical }) => $vertical ? "flex-start" : "center";
338
361
  var ChartLegendMeasure = /*#__PURE__*/ styled("div")({
339
362
  name: "ChartLegendMeasure",
340
363
  class: "cgd77rv",
341
364
  propsAsIs: false,
342
365
  vars: {
343
- "cgd77rv-0": [_exp42()],
344
- "cgd77rv-1": [_exp43()]
366
+ "cgd77rv-0": [_exp44()],
367
+ "cgd77rv-1": [_exp45()]
345
368
  }
346
369
  });
347
370
  /**
@@ -374,21 +397,21 @@ var ChartLegendOverflowList = /*#__PURE__*/ styled("div")({
374
397
  * series to the next. Trailing only, so the first entry stays flush with the
375
398
  * plot's left edge (or the column's top).
376
399
  */
377
- var _exp46 = () => ({ $vertical }) => $vertical ? `0 0 8px 0` : `0 16px 0 0`;
400
+ var _exp48 = () => ({ $vertical }) => $vertical ? `0 0 8px 0` : `0 16px 0 0`;
378
401
  var ChartLegendItemButton = /*#__PURE__*/ styled("button")({
379
402
  name: "ChartLegendItemButton",
380
403
  class: "ccpczg",
381
404
  propsAsIs: false,
382
- vars: { "ccpczg-0": [_exp46()] }
405
+ vars: { "ccpczg-0": [_exp48()] }
383
406
  });
384
407
  /** The same row, when the legend is inert — no button semantics to announce.
385
408
  * Carries the same trailing padding; see {@link ChartLegendItemButton}. */
386
- var _exp48 = () => ({ $vertical }) => $vertical ? `0 0 8px 0` : `0 16px 0 0`;
409
+ var _exp50 = () => ({ $vertical }) => $vertical ? `0 0 8px 0` : `0 16px 0 0`;
387
410
  var ChartLegendItemStatic = /*#__PURE__*/ styled("div")({
388
411
  name: "ChartLegendItemStatic",
389
412
  class: "c2re25h",
390
413
  propsAsIs: false,
391
- vars: { "c2re25h-0": [_exp48()] }
414
+ vars: { "c2re25h-0": [_exp50()] }
392
415
  });
393
416
  //#endregion
394
417
  export { ChartArcPath, ChartAreaPath, ChartAxisLabelBox, ChartBarPath, ChartCenterBox, ChartCursorBand, ChartCursorLine, ChartEmptyOverlay, ChartGridLine, ChartHitTarget, ChartHitTargetPath, ChartLegendItemButton, ChartLegendItemStatic, ChartLegendMeasure, ChartLegendMeasureClip, ChartLegendOverflowList, ChartLegendRow, ChartLinePath, ChartPlotBox, ChartPointCircle, ChartRoot, ChartSvg, ChartTooltipGrid, ChartTooltipLabelCell, ChartTooltipPositioner, ChartTooltipValueCell };
@@ -1,5 +1,5 @@
1
1
  import { LineChartCurve } from "./types";
2
- import type { BaseCartesianChartProps, ChartSeriesStyles, ChartSize, LineChartLineDatum } from "./types";
2
+ import type { BaseCartesianChartProps, ChartSelectionEvent, ChartSelectionInput, ChartSeriesStyles, ChartSize, LineChartLineDatum } from "./types";
3
3
  export type LineChartProps<TMetric extends string> = BaseCartesianChartProps & ChartSize & {
4
4
  /**
5
5
  * Presentation for every metric, keyed by metric.
@@ -25,6 +25,25 @@ export type LineChartProps<TMetric extends string> = BaseCartesianChartProps & C
25
25
  strokeWidth?: number;
26
26
  /** Draw a vertical rule at the active category. @default true */
27
27
  showCursor?: boolean;
28
+ /**
29
+ * Fires when a category is clicked. Supplying it is what makes the plot
30
+ * clickable.
31
+ *
32
+ * Always the category, never a single line: hit testing is per column here,
33
+ * and a line has no segment to land on — the reading at a category belongs
34
+ * to every line at once, which is exactly what the tooltip reports. Use
35
+ * `seriesKey` in {@link selection} to narrow to one line.
36
+ */
37
+ onSelect?: (event: ChartSelectionEvent<NoInfer<TMetric>>) => void;
38
+ /**
39
+ * Which marks read as selected. Matching lines hold full strength, the rest
40
+ * recede, and each selected category keeps a persistent cursor with its
41
+ * dots — the hover rule one step firmer, held rather than previewed.
42
+ *
43
+ * Presentational and never written by the chart: the state belongs to
44
+ * whatever the selection is filtering, so the two cannot drift apart.
45
+ */
46
+ selection?: ChartSelectionInput<NoInfer<TMetric>>;
28
47
  };
29
48
  /**
30
49
  * Line chart over a categorical x-axis.
@@ -34,5 +53,5 @@ export type LineChartProps<TMetric extends string> = BaseCartesianChartProps & C
34
53
  * what a reader wants, and it avoids the proximity search a
35
54
  * nearest-point-to-cursor model needs.
36
55
  */
37
- declare const LineChart: <TMetric extends string>({ series, lines, categories, width, height, fillWidth, fillHeight, aspectRatio, margin, curve, strokeWidth, showCursor, valueUnit, valueFormatter, labelFormatter, valueDomain, categoryLabelMode, categoryAxisTitle, valueAxisTitle, showGrid, showLegend, legendMaxItems, noTooltip, tooltipRenderer, tooltipMaxItems, isLoading, contentWhenEmpty, ariaLabel, ariaDescription, }: LineChartProps<TMetric>) => import("react").JSX.Element;
56
+ declare const LineChart: <TMetric extends string>({ series, lines, categories, width, height, fillWidth, fillHeight, aspectRatio, margin, curve, strokeWidth, showCursor, onSelect, selection, valueUnit, valueFormatter, labelFormatter, valueDomain, categoryLabelMode, categoryAxisTitle, valueAxisTitle, showGrid, showLegend, legendMaxItems, noTooltip, tooltipRenderer, tooltipMaxItems, isLoading, contentWhenEmpty, ariaLabel, ariaDescription, }: LineChartProps<TMetric>) => import("react").JSX.Element;
38
57
  export default LineChart;
@@ -18,7 +18,7 @@ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
18
18
  * what a reader wants, and it avoids the proximity search a
19
19
  * nearest-point-to-cursor model needs.
20
20
  */
21
- var LineChart = ({ series, lines, categories, width, height, fillWidth, fillHeight, aspectRatio, margin, curve = LineChartCurve.SMOOTH, strokeWidth, showCursor = true, valueUnit, valueFormatter, labelFormatter, valueDomain, categoryLabelMode, categoryAxisTitle, valueAxisTitle, showGrid = true, showLegend = true, legendMaxItems, noTooltip = false, tooltipRenderer, tooltipMaxItems, isLoading = false, contentWhenEmpty, ariaLabel, ariaDescription }) => {
21
+ var LineChart = ({ series, lines, categories, width, height, fillWidth, fillHeight, aspectRatio, margin, curve = LineChartCurve.SMOOTH, strokeWidth, showCursor = true, onSelect, selection, valueUnit, valueFormatter, labelFormatter, valueDomain, categoryLabelMode, categoryAxisTitle, valueAxisTitle, showGrid = true, showLegend = true, legendMaxItems, noTooltip = false, tooltipRenderer, tooltipMaxItems, isLoading = false, contentWhenEmpty, ariaLabel, ariaDescription }) => {
22
22
  const containerRef = useRef(null);
23
23
  const colors = useSeriesColorResolver(series);
24
24
  const { dimensions, domain, valueTicks, formatValue, formatLabel } = useCartesianChartLayout({
@@ -69,6 +69,7 @@ var LineChart = ({ series, lines, categories, width, height, fillWidth, fillHeig
69
69
  categoryCount: geometry.columns.length,
70
70
  seriesKeys: legendKeys,
71
71
  categoryKeys: geometry.categories,
72
+ selection,
72
73
  isDisabled: noTooltip || isLoading || isEmpty,
73
74
  isLoading
74
75
  });
@@ -104,6 +105,22 @@ var LineChart = ({ series, lines, categories, width, height, fillWidth, fillHeig
104
105
  width: column.bandWidth,
105
106
  height: plotHeight
106
107
  })), [geometry.columns, plotHeight]);
108
+ const handleCategorySelect = useCallback((index) => {
109
+ const category = geometry.categories[index];
110
+ if (category === void 0 || !onSelect) return;
111
+ onSelect({
112
+ categoryIndex: index,
113
+ categoryKey: category
114
+ });
115
+ }, [geometry.categories, onSelect]);
116
+ /**
117
+ * Columns the selection names, resolved once.
118
+ *
119
+ * These get the cursor and the dots permanently — the same treatment hovering
120
+ * gives, one step firmer on the same neutral ramp, which is the whole
121
+ * difference the held stroke has to communicate.
122
+ */
123
+ const selectedColumns = useMemo(() => geometry.columns.filter((column) => interaction.selectedCategoryIndices.has(column.categoryIndex)), [geometry.columns, interaction.selectedCategoryIndices]);
107
124
  return /* @__PURE__ */ jsxs(ChartFrame, {
108
125
  containerRef,
109
126
  dimensions,
@@ -138,6 +155,7 @@ var LineChart = ({ series, lines, categories, width, height, fillWidth, fillHeig
138
155
  tooltipRenderer,
139
156
  tooltipMaxItems,
140
157
  categoryTargets,
158
+ onCategorySelect: onSelect && handleCategorySelect,
141
159
  anchorHalfWidth: geometry.step / 2,
142
160
  isEmpty,
143
161
  isLoading,
@@ -152,6 +170,13 @@ var LineChart = ({ series, lines, categories, width, height, fillWidth, fillHeig
152
170
  fillOpacity: LINE_AREA_OPACITY,
153
171
  "data-state": interaction.getMarkState({ seriesKey: linePath.metric })
154
172
  }, `${linePath.id}:area`) : null),
173
+ selectedColumns.map((column) => /* @__PURE__ */ jsx(ChartCursorLine, {
174
+ x1: column.x,
175
+ x2: column.x,
176
+ y1: 0,
177
+ y2: plotHeight,
178
+ "data-selected": "true"
179
+ }, `selected:${column.categoryIndex}`)),
155
180
  showCursor && activeColumn && /* @__PURE__ */ jsx(ChartCursorLine, {
156
181
  x1: activeColumn.x,
157
182
  x2: activeColumn.x,
@@ -178,7 +203,14 @@ var LineChart = ({ series, lines, categories, width, height, fillWidth, fillHeig
178
203
  r: LINE_POINT_RADIUS,
179
204
  fill: point.color,
180
205
  "data-state": interaction.getMarkState({ seriesKey: point.metric })
181
- }, point.id))
206
+ }, point.id)),
207
+ selectedColumns.filter((column) => column.categoryIndex !== activeColumn?.categoryIndex).flatMap((column) => column.points.map((point) => /* @__PURE__ */ jsx(ChartPointCircle, {
208
+ cx: point.x,
209
+ cy: point.y,
210
+ r: LINE_POINT_RADIUS,
211
+ fill: point.color,
212
+ "data-state": interaction.getMarkState({ seriesKey: point.metric })
213
+ }, `selected:${point.id}`)))
182
214
  ]
183
215
  });
184
216
  };
@@ -1,5 +1,6 @@
1
1
  import type { ReactNode } from "react";
2
- import type { BaseChartProps, ChartSeriesStyles, ChartSize, PieChartSliceDatum } from "./types";
2
+ import { AGGREGATED_SLICE_KEY } from "./constants";
3
+ import type { BaseChartProps, ChartSelectionEvent, ChartSelectionInput, ChartSeriesStyles, ChartSize, PieChartSliceDatum } from "./types";
3
4
  export type PieChartProps<TSlice extends string> = BaseChartProps & ChartSize & {
4
5
  /**
5
6
  * Presentation for every slice, keyed by slice.
@@ -32,6 +33,26 @@ export type PieChartProps<TSlice extends string> = BaseChartProps & ChartSize &
32
33
  * Ignored when `innerRadiusRatio` is 0.
33
34
  */
34
35
  centerContent?: ReactNode;
36
+ /**
37
+ * Fires when a slice is clicked. Supplying it is what makes the plot
38
+ * clickable.
39
+ *
40
+ * `categoryKey` and `seriesKey` are always the same value here: a pie's
41
+ * category axis *is* its series axis, and the arcs are already its hit
42
+ * targets, so there is no coarser fallback to take. Clicking the folded
43
+ * bucket reports {@link AGGREGATED_SLICE_KEY}, which the key union says
44
+ * outright rather than widening to `string`.
45
+ */
46
+ onSelect?: (event: ChartSelectionEvent<NoInfer<TSlice> | typeof AGGREGATED_SLICE_KEY>) => void;
47
+ /**
48
+ * Which arcs read as selected. Matching arcs hold full strength and the
49
+ * rest recede — the lone lit slice is unambiguous, so there is no band or
50
+ * outline on top of it.
51
+ *
52
+ * Presentational and never written by the chart: the state belongs to
53
+ * whatever the selection is filtering, so the two cannot drift apart.
54
+ */
55
+ selection?: ChartSelectionInput<NoInfer<TSlice> | typeof AGGREGATED_SLICE_KEY>;
35
56
  };
36
57
  /**
37
58
  * Pie and donut chart.
@@ -40,5 +61,5 @@ export type PieChartProps<TSlice extends string> = BaseChartProps & ChartSize &
40
61
  * each arc is its own target. Small slices are folded into an "Other" bucket by
41
62
  * default, which keeps every remaining arc large enough to actually hit.
42
63
  */
43
- declare const PieChart: <TSlice extends string>({ series, slices, width, height, fillWidth, fillHeight, aspectRatio, margin, innerRadiusRatio, padAngle, cornerRadius, minSliceShare, aggregatedLabel, centerContent, valueUnit, valueFormatter, labelFormatter, showLegend, legendMaxItems, noTooltip, tooltipRenderer, tooltipMaxItems, isLoading, contentWhenEmpty, ariaLabel, ariaDescription, }: PieChartProps<TSlice>) => import("react").JSX.Element;
64
+ declare const PieChart: <TSlice extends string>({ series, slices, width, height, fillWidth, fillHeight, aspectRatio, margin, innerRadiusRatio, padAngle, cornerRadius, minSliceShare, aggregatedLabel, centerContent, onSelect, selection, valueUnit, valueFormatter, labelFormatter, showLegend, legendMaxItems, noTooltip, tooltipRenderer, tooltipMaxItems, isLoading, contentWhenEmpty, ariaLabel, ariaDescription, }: PieChartProps<TSlice>) => import("react").JSX.Element;
44
65
  export default PieChart;
@@ -19,7 +19,7 @@ import { jsx } from "react/jsx-runtime";
19
19
  * each arc is its own target. Small slices are folded into an "Other" bucket by
20
20
  * default, which keeps every remaining arc large enough to actually hit.
21
21
  */
22
- var PieChart = ({ series, slices, width, height, fillWidth, fillHeight, aspectRatio, margin, innerRadiusRatio, padAngle, cornerRadius, minSliceShare = PIE_MIN_SLICE_SHARE, aggregatedLabel, centerContent, valueUnit, valueFormatter, labelFormatter, showLegend = true, legendMaxItems, noTooltip = false, tooltipRenderer, tooltipMaxItems, isLoading = false, contentWhenEmpty, ariaLabel, ariaDescription }) => {
22
+ var PieChart = ({ series, slices, width, height, fillWidth, fillHeight, aspectRatio, margin, innerRadiusRatio, padAngle, cornerRadius, minSliceShare = PIE_MIN_SLICE_SHARE, aggregatedLabel, centerContent, onSelect, selection, valueUnit, valueFormatter, labelFormatter, showLegend = true, legendMaxItems, noTooltip = false, tooltipRenderer, tooltipMaxItems, isLoading = false, contentWhenEmpty, ariaLabel, ariaDescription }) => {
23
23
  const containerRef = useRef(null);
24
24
  const colors = useSeriesColorResolver(series);
25
25
  const dimensions = useChartDimensions({
@@ -76,6 +76,9 @@ var PieChart = ({ series, slices, width, height, fillWidth, fillHeight, aspectRa
76
76
  const interaction = useChartInteraction({
77
77
  categoryCount: geometry.arcs.length,
78
78
  seriesKeys: legendKeys,
79
+ categoryKeys: legendKeys,
80
+ enableGroupSync: false,
81
+ selection,
79
82
  isDisabled: noTooltip || isLoading || isEmpty,
80
83
  isLoading
81
84
  });
@@ -110,6 +113,17 @@ var PieChart = ({ series, slices, width, height, fillWidth, fillHeight, aspectRa
110
113
  shape: ChartTargetShape.PATH,
111
114
  d: chartArc.path
112
115
  })), [geometry.arcs]);
116
+ const handleCategorySelect = useCallback((index) => {
117
+ const chartArc = geometry.arcs[index];
118
+ if (!chartArc || !onSelect) return;
119
+ const key = chartArc.id;
120
+ onSelect({
121
+ categoryIndex: index,
122
+ categoryKey: chartArc.id,
123
+ seriesKey: key,
124
+ value: chartArc.value
125
+ });
126
+ }, [geometry.arcs, onSelect]);
113
127
  const showCenter = centerContent && (innerRadiusRatio ?? .62) > 0;
114
128
  return /* @__PURE__ */ jsx(ChartFrame, {
115
129
  containerRef,
@@ -126,6 +140,7 @@ var PieChart = ({ series, slices, width, height, fillWidth, fillHeight, aspectRa
126
140
  tooltipRenderer,
127
141
  tooltipMaxItems,
128
142
  categoryTargets,
143
+ onCategorySelect: onSelect && handleCategorySelect,
129
144
  categoryTargetsTransform: `translate(${geometry.centerX}, ${geometry.centerY})`,
130
145
  placeAwayFromX: geometry.centerX,
131
146
  verticalAlign: ChartTooltipVerticalAlign.CENTERED,
@@ -13,7 +13,7 @@ import { match } from "ts-pattern";
13
13
  */
14
14
  /** Resolve a palette slot to a concrete color for the active theme. */
15
15
  function resolveChartPaletteColor(theme, palette) {
16
- return match(palette).with(ChartPalette.PURPLE, () => theme.color.icon.purple).with(ChartPalette.PINK, () => theme.color.icon.pink).with(ChartPalette.BLUE, () => theme.color.icon.blue).with(ChartPalette.TEAL, () => theme.color.icon.teal).with(ChartPalette.LIME, () => theme.color.icon.lime).with(ChartPalette.ORANGE, () => theme.color.icon.orange).with(ChartPalette.YELLOW, () => theme.color.icon.yellow).with(ChartPalette.GREEN, () => theme.color.icon.success).with(ChartPalette.RED, () => theme.color.icon.error).exhaustive();
16
+ return match(palette).with(ChartPalette.PURPLE, () => theme.color.icon.purple).with(ChartPalette.PINK, () => theme.color.icon.pink).with(ChartPalette.BLUE, () => theme.color.icon.blue).with(ChartPalette.TEAL, () => theme.color.icon.teal).with(ChartPalette.LIME, () => theme.color.icon.lime).with(ChartPalette.ORANGE, () => theme.color.icon.orange).with(ChartPalette.YELLOW, () => theme.color.icon.yellow).with(ChartPalette.GREEN, () => theme.color.icon.success).with(ChartPalette.RED, () => theme.color.icon.error).with(ChartPalette.PRIMARY, () => theme.color.icon.primary).with(ChartPalette.SECONDARY, () => theme.color.icon.secondary).with(ChartPalette.TERTIARY, () => theme.color.icon.tertiary).with(ChartPalette.WARNING, () => theme.color.icon.warning).exhaustive();
17
17
  }
18
18
  /**
19
19
  * Resolve the color for a series, honoring an explicit slot and otherwise
@@ -46,7 +46,11 @@ export declare enum ChartPalette {
46
46
  ORANGE = "ORANGE",
47
47
  YELLOW = "YELLOW",
48
48
  GREEN = "GREEN",
49
- RED = "RED"
49
+ RED = "RED",
50
+ PRIMARY = "PRIMARY",
51
+ SECONDARY = "SECONDARY",
52
+ TERTIARY = "TERTIARY",
53
+ WARNING = "WARNING"
50
54
  }
51
55
  /**
52
56
  * Built-in value formats. Supplies an `Intl`-based default for axis ticks,
@@ -358,6 +362,84 @@ export type ChartCategoryTarget = {
358
362
  shape: ChartTargetShape.PATH;
359
363
  d: string;
360
364
  };
365
+ /**
366
+ * One rendered mark's own target, in plot coordinates.
367
+ *
368
+ * Drawn above the category targets and bound to *clicks* only. Hover stays
369
+ * per category — crossing between two segments of one stack re-enters the
370
+ * category the pointer already had, so the tooltip holds still. That banding is
371
+ * why a two-pixel segment is as easy to hover as a two-hundred-pixel one, and a
372
+ * click layer laid over it must not undo it.
373
+ *
374
+ * Unlike {@link ChartCategoryTarget}, array position says nothing about the
375
+ * category here, so each target names its own.
376
+ */
377
+ export type ChartMarkTarget = {
378
+ key: string;
379
+ /** The category this mark sits in — its hover, and its selection event. */
380
+ categoryIndex: number;
381
+ shape: ChartTargetShape.RECT;
382
+ x: number;
383
+ y: number;
384
+ width: number;
385
+ height: number;
386
+ } | {
387
+ key: string;
388
+ categoryIndex: number;
389
+ shape: ChartTargetShape.PATH;
390
+ d: string;
391
+ };
392
+ /**
393
+ * Which marks a chart's `selection` covers.
394
+ *
395
+ * A *descriptor* rather than a coordinate: a mark matches when every field
396
+ * present matches it, and a field the mark has no notion of never excludes it.
397
+ * One shape therefore spells every granularity without a mode flag —
398
+ *
399
+ * ```ts
400
+ * { categoryKey: "00:00" } // the whole 00:00 column
401
+ * { seriesKey: "failed" } // every failure, all columns
402
+ * { categoryKey: "00:00", seriesKey: "failed" } // one segment
403
+ * ```
404
+ *
405
+ * — and that last rule is what stops a category selection from muting a
406
+ * LineChart's strokes, which span every category and belong to none.
407
+ */
408
+ export interface ChartSelection<TSeriesKey extends string = string> {
409
+ /**
410
+ * Raw category label, never `labelFormatter` output — the same vocabulary
411
+ * `ChartGroupProvider` matches on, for the same reason: two charts formatting
412
+ * one label differently must still agree on what it names.
413
+ */
414
+ categoryKey?: string;
415
+ /**
416
+ * Whatever this chart's legend names: components in a stacked bar, metrics in
417
+ * an unstacked one or a line chart, slices in a pie. The same key the mute
418
+ * axis uses, so a selection and the legend cannot describe different things.
419
+ */
420
+ seriesKey?: TSeriesKey;
421
+ }
422
+ /** One descriptor, several, or none. */
423
+ export type ChartSelectionInput<TSeriesKey extends string = string> = ChartSelection<TSeriesKey> | ChartSelection<TSeriesKey>[] | null;
424
+ /**
425
+ * What a click reports.
426
+ *
427
+ * Deliberately thin — no rows, no colors, no formatted strings. The caller
428
+ * already holds the data it passed in and can look the rest up by key, and a
429
+ * presentation-shaped payload is the kind that becomes load-bearing and then
430
+ * cannot be changed. The fields are exactly those a {@link ChartSelection} is
431
+ * built from, so feeding an event straight back as one is the common case.
432
+ */
433
+ export interface ChartSelectionEvent<TSeriesKey extends string = string> {
434
+ categoryIndex: number;
435
+ /** Raw label, matching {@link ChartSelection.categoryKey}. */
436
+ categoryKey: string;
437
+ /** Absent when the click landed on empty plot rather than on a mark. */
438
+ seriesKey?: TSeriesKey;
439
+ /** The mark's own value, before any normalization. Absent whenever
440
+ * {@link seriesKey} is. */
441
+ value?: number;
442
+ }
361
443
  /** One legend entry. */
362
444
  export interface ChartLegendItem {
363
445
  key: string;
@@ -47,6 +47,10 @@ var ChartPalette = /* @__PURE__ */ function(ChartPalette) {
47
47
  ChartPalette["YELLOW"] = "YELLOW";
48
48
  ChartPalette["GREEN"] = "GREEN";
49
49
  ChartPalette["RED"] = "RED";
50
+ ChartPalette["PRIMARY"] = "PRIMARY";
51
+ ChartPalette["SECONDARY"] = "SECONDARY";
52
+ ChartPalette["TERTIARY"] = "TERTIARY";
53
+ ChartPalette["WARNING"] = "WARNING";
50
54
  return ChartPalette;
51
55
  }({});
52
56
  /**
@@ -1,4 +1,5 @@
1
1
  import { ChartMarkState } from "./types";
2
+ import type { ChartSelectionInput } from "./types";
2
3
  /**
3
4
  * Hover interaction shared by every chart.
4
5
  *
@@ -18,9 +19,14 @@ import { ChartMarkState } from "./types";
18
19
  * isolating "Delivered" in a chart that happens to share the name would
19
20
  * correlate nothing.
20
21
  *
21
- * Deliberately no click layer. Clicking used to pin the hover so it survived
22
- * the pointer leaving, and it read as the chart getting stuck the emphasis
23
- * now always follows the pointer and releases with it.
22
+ * Plus one axis that is not hover at all: **selection**, supplied by the caller
23
+ * and never changed here. A selection is a commitment rather than a preview
24
+ * it outranks both hover filters, and it releases only when the caller says so.
25
+ *
26
+ * Clicking deliberately does *not* pin the hover. That is what it used to do,
27
+ * with no effect outside the chart, and it read as the plot getting stuck. A
28
+ * click now only reports what was hit; whether anything lights up afterwards is
29
+ * the caller's `selection` to decide.
24
30
  */
25
31
  interface UseChartInteractionOptions {
26
32
  /** Number of hoverable categories. A shrinking count clears stale indices. */
@@ -28,12 +34,36 @@ interface UseChartInteractionOptions {
28
34
  /** Legend keys currently rendered. A key that disappears clears stale hover. */
29
35
  seriesKeys?: string[];
30
36
  /**
31
- * Raw category labels, index-aligned with {@link categoryCount}. Supplying
32
- * them opts the chart into `ChartGroupProvider` crosshair sync; omit (as
33
- * PieChart does) to stay out. Raw labels, never `labelFormatter` output —
34
- * two charts formatting the same label differently must still match.
37
+ * Raw category labels, index-aligned with {@link categoryCount}. Raw labels,
38
+ * never `labelFormatter` output two charts formatting the same label
39
+ * differently must still match, and so must a caller's `selection`.
40
+ *
41
+ * Identity only. Opting into `ChartGroupProvider` sync is
42
+ * {@link enableGroupSync}, which used to be implied by supplying these — the
43
+ * two were split once selection needed the labels in a chart (PieChart) that
44
+ * must stay out of the sync group.
35
45
  */
36
46
  categoryKeys?: string[];
47
+ /**
48
+ * Follow a `ChartGroupProvider` sibling's hovered category. PieChart passes
49
+ * `false`: it has no category axis, so there is nothing to correlate.
50
+ * @default true
51
+ */
52
+ enableGroupSync?: boolean;
53
+ /**
54
+ * Marks the caller has selected. Matching marks hold full strength and the
55
+ * rest recede — the same "soften everything else" grammar the hover axes use,
56
+ * which is why selection needs no visual vocabulary of its own.
57
+ *
58
+ * Presentational and strictly read-only: this hook never writes it. The state
59
+ * lives with whatever the selection is filtering, so the chart and that thing
60
+ * cannot drift apart, and the DLS never has to pick toggle or multi-select
61
+ * semantics.
62
+ *
63
+ * Normalized here rather than by each chart, so "one, several, or none" is
64
+ * spelled once.
65
+ */
66
+ selection?: ChartSelectionInput;
37
67
  /**
38
68
  * How marks outside the active category recede. Defaults to
39
69
  * {@link ChartMarkState.MUTED}; BarChart passes
@@ -75,6 +105,15 @@ export interface ChartInteraction {
75
105
  */
76
106
  showTooltip: boolean;
77
107
  hoveredSeriesKey: string | null;
108
+ /**
109
+ * Categories a {@link UseChartInteractionOptions.selection} names outright.
110
+ *
111
+ * Drives the persistent cursor a cartesian chart draws at a selected
112
+ * category. A descriptor carrying only a `seriesKey` names no category and
113
+ * contributes nothing here — "every failure" should mute the other series,
114
+ * not band every column on the axis.
115
+ */
116
+ selectedCategoryIndices: ReadonlySet<number>;
78
117
  /**
79
118
  * Emphasis for one mark, composed from both axes.
80
119
  *
@@ -92,5 +131,5 @@ export interface ChartInteraction {
92
131
  onSeriesEnter: (seriesKey: string) => void;
93
132
  onSeriesLeave: () => void;
94
133
  }
95
- export declare function useChartInteraction({ categoryCount, seriesKeys, categoryKeys, categoryMuteState, isDisabled, isLoading, }: UseChartInteractionOptions): ChartInteraction;
134
+ export declare function useChartInteraction({ categoryCount, seriesKeys, categoryKeys, enableGroupSync, selection, categoryMuteState, isDisabled, isLoading, }: UseChartInteractionOptions): ChartInteraction;
96
135
  export {};
@@ -2,12 +2,15 @@ import { ChartMarkState } from "./types.js";
2
2
  import { ChartGroupContext } from "./ChartGroupProvider.js";
3
3
  import { useCallback, useContext, useEffect, useId, useMemo, useState } from "react";
4
4
  //#region src/charts/useChartInteraction.ts
5
- function useChartInteraction({ categoryCount, seriesKeys, categoryKeys, categoryMuteState = ChartMarkState.MUTED, isDisabled = false, isLoading = false }) {
5
+ /** Stable identity, so an absent selection never re-memoizes anything. */
6
+ var NO_SELECTION = [];
7
+ var NO_SELECTED_INDICES = /* @__PURE__ */ new Set();
8
+ function useChartInteraction({ categoryCount, seriesKeys, categoryKeys, enableGroupSync = true, selection, categoryMuteState = ChartMarkState.MUTED, isDisabled = false, isLoading = false }) {
6
9
  const [hoveredIndex, setHoveredIndex] = useState(null);
7
10
  const [hoveredSeriesKey, setHoveredSeriesKey] = useState(null);
8
11
  const sync = useContext(ChartGroupContext);
9
12
  const syncId = useId();
10
- const canSync = sync !== null && categoryKeys !== void 0;
13
+ const canSync = sync !== null && categoryKeys !== void 0 && enableGroupSync;
11
14
  const setActive = sync?.setActive;
12
15
  const clearActive = sync?.clearActive;
13
16
  const safeHovered = hoveredIndex !== null && hoveredIndex < categoryCount ? hoveredIndex : null;
@@ -75,14 +78,72 @@ function useChartInteraction({ categoryCount, seriesKeys, categoryKeys, category
75
78
  const onSeriesLeave = useCallback(() => {
76
79
  setHoveredSeriesKey(null);
77
80
  }, []);
81
+ const selectionList = useMemo(() => {
82
+ if (selection === void 0 || selection === null) return NO_SELECTION;
83
+ if (!Array.isArray(selection)) return [selection];
84
+ return selection.length === 0 ? NO_SELECTION : selection;
85
+ }, [selection]);
86
+ const hasSelection = selectionList.length > 0;
87
+ /**
88
+ * Whether one mark falls inside the selection.
89
+ *
90
+ * A descriptor is a conjunction of the fields it *carries*: an absent field
91
+ * constrains nothing, and a field the mark itself has no notion of cannot
92
+ * exclude it. The second half is what keeps `{ categoryKey: "00:00" }` from
93
+ * muting every LineChart stroke — a stroke spans all the categories and is
94
+ * asked about none — while still muting the bars outside that column.
95
+ *
96
+ * Several descriptors union: a mark inside any of them is selected.
97
+ */
98
+ const matchesSelection = useCallback(({ categoryIndex, seriesKey }) => {
99
+ if (!hasSelection) return true;
100
+ return selectionList.some((descriptor) => {
101
+ if (descriptor.categoryKey !== void 0 && categoryIndex !== void 0 && categoryKeys !== void 0 && categoryKeys[categoryIndex] !== descriptor.categoryKey) return false;
102
+ if (descriptor.seriesKey !== void 0 && seriesKey !== void 0 && seriesKey !== descriptor.seriesKey) return false;
103
+ return true;
104
+ });
105
+ }, [
106
+ hasSelection,
107
+ selectionList,
108
+ categoryKeys
109
+ ]);
110
+ /**
111
+ * Resolved once rather than per mark: the cursor a cartesian chart draws at a
112
+ * selected category needs the set, and every mark would otherwise re-scan
113
+ * every descriptor.
114
+ *
115
+ * Deliberately not pruned when the data shrinks under it, unlike the hover
116
+ * indices above. A selection is the caller's state and describes what they
117
+ * asked for; a key that is momentarily absent from the data — mid-refetch,
118
+ * say — simply matches nothing until it returns.
119
+ */
120
+ const selectedCategoryIndices = useMemo(() => {
121
+ if (!hasSelection || categoryKeys === void 0) return NO_SELECTED_INDICES;
122
+ const selectedKeys = new Set(selectionList.map((descriptor) => descriptor.categoryKey).filter((key) => key !== void 0));
123
+ if (selectedKeys.size === 0) return NO_SELECTED_INDICES;
124
+ const indices = /* @__PURE__ */ new Set();
125
+ categoryKeys.forEach((key, index) => {
126
+ if (selectedKeys.has(key)) indices.add(index);
127
+ });
128
+ return indices;
129
+ }, [
130
+ hasSelection,
131
+ selectionList,
132
+ categoryKeys
133
+ ]);
78
134
  const getMarkState = useCallback(({ categoryIndex, seriesKey }) => {
79
135
  if (isLoading) return ChartMarkState.MUTED;
136
+ if (!matchesSelection({
137
+ categoryIndex,
138
+ seriesKey
139
+ })) return ChartMarkState.MUTED;
80
140
  const categoryMuted = categoryIndex !== void 0 && effectiveIndex !== null && categoryIndex !== effectiveIndex;
81
141
  if (seriesKey !== void 0 && safeHoveredSeries !== null && seriesKey !== safeHoveredSeries) return ChartMarkState.MUTED;
82
142
  if (categoryMuted) return categoryMuteState;
83
143
  return ChartMarkState.ACTIVE;
84
144
  }, [
85
145
  isLoading,
146
+ matchesSelection,
86
147
  effectiveIndex,
87
148
  safeHoveredSeries,
88
149
  categoryMuteState
@@ -91,6 +152,7 @@ function useChartInteraction({ categoryCount, seriesKeys, categoryKeys, category
91
152
  activeIndex: effectiveIndex,
92
153
  showTooltip,
93
154
  hoveredSeriesKey: safeHoveredSeries,
155
+ selectedCategoryIndices,
94
156
  getMarkState,
95
157
  onCategoryEnter,
96
158
  onCategoryLeave,
@@ -100,6 +162,7 @@ function useChartInteraction({ categoryCount, seriesKeys, categoryKeys, category
100
162
  effectiveIndex,
101
163
  showTooltip,
102
164
  safeHoveredSeries,
165
+ selectedCategoryIndices,
103
166
  getMarkState,
104
167
  onCategoryEnter,
105
168
  onCategoryLeave,
package/dist/styles.css CHANGED
@@ -33,11 +33,11 @@
33
33
  .c8c506o{stroke:none;pointer-events:none;opacity:1;}.c8c506o[data-state="muted"]{opacity:0.1;}.c8c506o[data-state="dimmed"]{opacity:0.4;}
34
34
  .ceqlpek{pointer-events:none;opacity:1;}.ceqlpek[data-state="muted"]{opacity:0.1;}.ceqlpek[data-state="dimmed"]{opacity:0.4;}
35
35
  .c1fcbddl{opacity:1;}.c1fcbddl[data-state="muted"]{opacity:0.1;}.c1fcbddl[data-state="dimmed"]{opacity:0.4;}
36
- .cpcxr59{fill:transparent;outline:none;}
37
- .c1wvzvu4{fill:transparent;outline:none;}
36
+ .cpcxr59{fill:transparent;outline:none;}.cpcxr59[data-clickable="true"]{cursor:pointer;}
37
+ .c1wvzvu4{fill:transparent;outline:none;}.c1wvzvu4[data-clickable="true"]{cursor:pointer;}
38
38
  .c17guewt{stroke:var(--c17guewt-0);shape-rendering:crispEdges;}
39
- .cblgiyf{stroke:var(--cblgiyf-0);pointer-events:none;}
40
- .c1lr03cc{fill:var(--c1lr03cc-0);pointer-events:none;}
39
+ .cblgiyf{stroke:var(--cblgiyf-0);pointer-events:none;}.cblgiyf[data-selected="true"]{stroke:var(--cblgiyf-1);}
40
+ .c1lr03cc{fill:var(--c1lr03cc-0);pointer-events:none;}.c1lr03cc[data-selected="true"]{fill:var(--c1lr03cc-1);}
41
41
  .cizy1s8{overflow:visible;pointer-events:none;}
42
42
  .c1ukq11d{position:absolute;z-index:10;pointer-events:none;left:var(--c1ukq11d-0);top:var(--c1ukq11d-1);min-width:200px;max-width:320px;padding:10px 12px;border:0.5px solid var(--c1ukq11d-2);border-radius:6px;background-color:var(--c1ukq11d-3);opacity:var(--c1ukq11d-4);-webkit-transition:opacity 100ms ease-in-out,left 100ms ease-out,top 100ms ease-out;transition:opacity 100ms ease-in-out,left 100ms ease-out,top 100ms ease-out;}@media (prefers-reduced-motion: reduce){.c1ukq11d{-webkit-transition:none;transition:none;}}
43
43
  .cwcko2m{display:-ms-grid;display:grid;-ms-grid-columns:minmax(0, 1fr) auto;grid-template-columns:minmax(0, 1fr) auto;-webkit-column-gap:16px;column-gap:16px;row-gap:6px;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;width:100%;}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@galaxy-io/dls",
3
- "version": "1.5.10",
3
+ "version": "1.5.12",
4
4
  "description": "Galaxy Design Language System",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",