@galaxy-io/dls 1.3.0 → 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/charts/BarChart.d.ts +3 -3
- package/dist/charts/BarChart.js +68 -104
- package/dist/charts/ChartCategoryTargets.d.ts +27 -0
- package/dist/charts/ChartCategoryTargets.js +75 -0
- package/dist/charts/ChartFrame.d.ts +34 -6
- package/dist/charts/ChartFrame.js +72 -15
- package/dist/charts/ChartPrimitives.d.ts +35 -0
- package/dist/charts/ChartPrimitives.js +75 -38
- package/dist/charts/ChartTooltip.d.ts +3 -3
- package/dist/charts/ChartTooltip.js +2 -2
- package/dist/charts/LineChart.js +47 -62
- package/dist/charts/PieChart.js +48 -26
- package/dist/charts/barChartGeometry.d.ts +66 -33
- package/dist/charts/barChartGeometry.js +7 -15
- package/dist/charts/barChartLegend.d.ts +25 -0
- package/dist/charts/barChartLegend.js +23 -0
- package/dist/charts/chartFormat.d.ts +9 -1
- package/dist/charts/chartFormat.js +13 -1
- package/dist/charts/chartScales.d.ts +58 -22
- package/dist/charts/chartScales.js +92 -38
- package/dist/charts/constants.d.ts +22 -6
- package/dist/charts/constants.js +22 -1
- package/dist/charts/lineChartGeometry.d.ts +53 -15
- package/dist/charts/lineChartGeometry.js +6 -15
- package/dist/charts/pieChartGeometry.d.ts +20 -8
- package/dist/charts/pieChartGeometry.js +3 -13
- package/dist/charts/types.d.ts +51 -145
- package/dist/charts/types.js +22 -3
- package/dist/charts/useCartesianChartLayout.d.ts +70 -0
- package/dist/charts/useCartesianChartLayout.js +56 -0
- package/dist/charts/useChartDimensions.d.ts +10 -3
- package/dist/charts/useChartDimensions.js +14 -4
- package/dist/charts/useChartInteraction.d.ts +13 -8
- package/dist/charts/useChartInteraction.js +7 -11
- package/dist/charts/useSeriesColorResolver.d.ts +37 -0
- package/dist/charts/useSeriesColorResolver.js +27 -0
- package/dist/inputs/DateInput.js +3 -3
- package/dist/inputs/MultiSelectInput.d.ts +3 -2
- package/dist/inputs/MultiSelectInput.js +8 -8
- package/dist/inputs/SelectInput.d.ts +4 -8
- package/dist/inputs/SelectInput.js +14 -20
- package/dist/styles.css +13 -11
- package/package.json +1 -1
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { ChartLabelFormatter, ChartMargin, ChartValueDomain, ChartValueFormatter, ChartValueUnit } from "./types";
|
|
2
|
+
import type { ChartDimensions } from "./useChartDimensions";
|
|
3
|
+
/**
|
|
4
|
+
* Everything a cartesian chart resolves *before* it can be measured.
|
|
5
|
+
*
|
|
6
|
+
* There is a hard ordering inside bar and line alike:
|
|
7
|
+
*
|
|
8
|
+
* ```
|
|
9
|
+
* domain → ticks → left margin → dimensions → plot size → geometry
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* The gutter has to fit the tick labels, the ticks come from the domain, and
|
|
13
|
+
* the domain comes from the data — none of which needs the plot. Everything
|
|
14
|
+
* after `geometry` does need it, which is why this hook stops there rather than
|
|
15
|
+
* trying to own the whole chart: the geometry builders are generic over the
|
|
16
|
+
* chart's own key types, and threading those through here would buy nothing.
|
|
17
|
+
*
|
|
18
|
+
* Deliberately **not generic**. `getBarValueDomain` and `getLineValueDomain`
|
|
19
|
+
* have already erased the key types by the time they return an extent, so
|
|
20
|
+
* nothing in this phase needs them.
|
|
21
|
+
*/
|
|
22
|
+
interface UseCartesianChartLayoutOptions {
|
|
23
|
+
containerRef: React.RefObject<HTMLDivElement | null>;
|
|
24
|
+
/**
|
|
25
|
+
* The data's own extent, before any override or nicing.
|
|
26
|
+
*
|
|
27
|
+
* Free to be a fresh array each render — the memo below keys on the two
|
|
28
|
+
* numbers, not the array's identity.
|
|
29
|
+
*/
|
|
30
|
+
rawDomain: [number, number];
|
|
31
|
+
valueDomain?: Partial<ChartValueDomain>;
|
|
32
|
+
width?: number;
|
|
33
|
+
height?: number;
|
|
34
|
+
fillWidth?: boolean;
|
|
35
|
+
fillHeight?: boolean;
|
|
36
|
+
aspectRatio?: number;
|
|
37
|
+
margin?: Partial<ChartMargin>;
|
|
38
|
+
valueUnit?: ChartValueUnit;
|
|
39
|
+
valueFormatter?: ChartValueFormatter;
|
|
40
|
+
labelFormatter?: ChartLabelFormatter;
|
|
41
|
+
/**
|
|
42
|
+
* Rewrites the resolved value formatter for the axis alone.
|
|
43
|
+
*
|
|
44
|
+
* Takes the resolved formatter rather than replacing it, because the only
|
|
45
|
+
* caller — a percent-normalized bar chart — falls back to it whenever the
|
|
46
|
+
* consumer asked for something specific. Applies to the tick labels *and* the
|
|
47
|
+
* gutter estimate, which is the point: sizing the gutter with a different
|
|
48
|
+
* formatter than the one that renders into it is how a gutter ends up too
|
|
49
|
+
* narrow for its own labels.
|
|
50
|
+
*
|
|
51
|
+
* Must be stable; memoize it at the call site.
|
|
52
|
+
*/
|
|
53
|
+
deriveAxisFormatter?: (formatValue: ChartValueFormatter) => ChartValueFormatter;
|
|
54
|
+
valueAxisTitle?: string;
|
|
55
|
+
categoryAxisTitle?: string;
|
|
56
|
+
}
|
|
57
|
+
export interface CartesianChartLayout {
|
|
58
|
+
dimensions: ChartDimensions;
|
|
59
|
+
/** Override applied, zero included, degenerate widened, `nice()` applied. */
|
|
60
|
+
domain: [number, number];
|
|
61
|
+
valueTicks: number[];
|
|
62
|
+
/** Tooltip rows and value labels. */
|
|
63
|
+
formatValue: ChartValueFormatter;
|
|
64
|
+
/** Category names — the axis and the tooltip heading. */
|
|
65
|
+
formatLabel: ChartLabelFormatter;
|
|
66
|
+
/** Axis ticks. Identical to `formatValue` unless `deriveAxisFormatter` said otherwise. */
|
|
67
|
+
formatAxisValue: ChartValueFormatter;
|
|
68
|
+
}
|
|
69
|
+
export declare function useCartesianChartLayout({ containerRef, rawDomain, valueDomain, width, height, fillWidth, fillHeight, aspectRatio, margin, valueUnit, valueFormatter, labelFormatter, deriveAxisFormatter, valueAxisTitle, categoryAxisTitle, }: UseCartesianChartLayoutOptions): CartesianChartLayout;
|
|
70
|
+
export {};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { addAxisTitleSpace, estimateValueAxisMargin, getValueTicks, resolveValueDomain } from "./chartScales.js";
|
|
2
|
+
import { useChartFormatters } from "./useChartFormatters.js";
|
|
3
|
+
import { useChartDimensions } from "./useChartDimensions.js";
|
|
4
|
+
import { useMemo } from "react";
|
|
5
|
+
//#region src/charts/useCartesianChartLayout.ts
|
|
6
|
+
function useCartesianChartLayout({ containerRef, rawDomain, valueDomain, width, height, fillWidth, fillHeight, aspectRatio, margin, valueUnit, valueFormatter, labelFormatter, deriveAxisFormatter, valueAxisTitle, categoryAxisTitle }) {
|
|
7
|
+
const { formatValue, formatLabel } = useChartFormatters({
|
|
8
|
+
valueFormatter,
|
|
9
|
+
valueUnit,
|
|
10
|
+
labelFormatter
|
|
11
|
+
});
|
|
12
|
+
const formatAxisValue = useMemo(() => deriveAxisFormatter?.(formatValue) ?? formatValue, [deriveAxisFormatter, formatValue]);
|
|
13
|
+
const [rawMin, rawMax] = rawDomain;
|
|
14
|
+
const domain = useMemo(() => resolveValueDomain([rawMin, rawMax], valueDomain), [
|
|
15
|
+
rawMin,
|
|
16
|
+
rawMax,
|
|
17
|
+
valueDomain
|
|
18
|
+
]);
|
|
19
|
+
const valueTicks = useMemo(() => getValueTicks(domain, valueDomain), [domain, valueDomain]);
|
|
20
|
+
const dimensions = useChartDimensions({
|
|
21
|
+
containerRef,
|
|
22
|
+
width,
|
|
23
|
+
height,
|
|
24
|
+
fillWidth,
|
|
25
|
+
fillHeight,
|
|
26
|
+
aspectRatio,
|
|
27
|
+
margin: addAxisTitleSpace(useMemo(() => ({
|
|
28
|
+
...margin,
|
|
29
|
+
left: margin?.left ?? estimateValueAxisMargin(valueTicks, formatAxisValue)
|
|
30
|
+
}), [
|
|
31
|
+
margin,
|
|
32
|
+
valueTicks,
|
|
33
|
+
formatAxisValue
|
|
34
|
+
]), {
|
|
35
|
+
hasValueTitle: Boolean(valueAxisTitle),
|
|
36
|
+
hasCategoryTitle: Boolean(categoryAxisTitle)
|
|
37
|
+
})
|
|
38
|
+
});
|
|
39
|
+
return useMemo(() => ({
|
|
40
|
+
dimensions,
|
|
41
|
+
domain,
|
|
42
|
+
valueTicks,
|
|
43
|
+
formatValue,
|
|
44
|
+
formatLabel,
|
|
45
|
+
formatAxisValue
|
|
46
|
+
}), [
|
|
47
|
+
dimensions,
|
|
48
|
+
domain,
|
|
49
|
+
valueTicks,
|
|
50
|
+
formatValue,
|
|
51
|
+
formatLabel,
|
|
52
|
+
formatAxisValue
|
|
53
|
+
]);
|
|
54
|
+
}
|
|
55
|
+
//#endregion
|
|
56
|
+
export { useCartesianChartLayout };
|
|
@@ -21,6 +21,15 @@ interface UseChartDimensionsOptions {
|
|
|
21
21
|
fillHeight?: boolean;
|
|
22
22
|
aspectRatio?: number;
|
|
23
23
|
margin?: Partial<ChartMargin>;
|
|
24
|
+
/**
|
|
25
|
+
* Fallback for whichever fields `margin` leaves out.
|
|
26
|
+
*
|
|
27
|
+
* Radial charts pass their own, since the cartesian default reserves gutters
|
|
28
|
+
* for axes they do not draw. Merged per field, so a caller's partial margin
|
|
29
|
+
* lands on top of *this* rather than reverting the chart to the cartesian
|
|
30
|
+
* defaults.
|
|
31
|
+
*/
|
|
32
|
+
defaultMargin?: ChartMargin;
|
|
24
33
|
}
|
|
25
34
|
export interface ChartDimensions {
|
|
26
35
|
width: number;
|
|
@@ -33,8 +42,6 @@ export interface ChartDimensions {
|
|
|
33
42
|
/** Handed to the plot box so it reserves space before measurement lands. */
|
|
34
43
|
reservedHeight?: number;
|
|
35
44
|
reservedAspectRatio?: number;
|
|
36
|
-
/** Makes the plot box claim the flex track instead of reserving a height. */
|
|
37
|
-
fillHeight?: boolean;
|
|
38
45
|
}
|
|
39
|
-
export declare function useChartDimensions({ containerRef, width, height, fillWidth, fillHeight, aspectRatio, margin: marginOverride, }: UseChartDimensionsOptions): ChartDimensions;
|
|
46
|
+
export declare function useChartDimensions({ containerRef, width, height, fillWidth, fillHeight, aspectRatio, margin: marginOverride, defaultMargin, }: UseChartDimensionsOptions): ChartDimensions;
|
|
40
47
|
export {};
|
|
@@ -1,22 +1,33 @@
|
|
|
1
1
|
import { useElementClientSize } from "../hooks/useElementClientSize.js";
|
|
2
|
+
import { DEFAULT_CHART_MARGIN } from "./constants.js";
|
|
2
3
|
import { getPlotDimensions, resolveMargin } from "./chartScales.js";
|
|
3
4
|
import { useEffect, useMemo } from "react";
|
|
4
5
|
//#region src/charts/useChartDimensions.ts
|
|
5
|
-
function useChartDimensions({ containerRef, width, height, fillWidth = false, fillHeight = false, aspectRatio, margin: marginOverride }) {
|
|
6
|
+
function useChartDimensions({ containerRef, width, height, fillWidth = false, fillHeight = false, aspectRatio, margin: marginOverride, defaultMargin }) {
|
|
6
7
|
const { width: measuredWidth, height: measuredHeight } = useElementClientSize(containerRef);
|
|
7
8
|
const resolvedWidth = fillWidth ? measuredWidth ?? 0 : width ?? 0;
|
|
8
9
|
const resolvedHeight = fillHeight ? measuredHeight ?? 0 : aspectRatio ? resolvedWidth / aspectRatio : height ?? 0;
|
|
9
10
|
const { top: marginTop, right: marginRight, bottom: marginBottom, left: marginLeft } = marginOverride ?? {};
|
|
11
|
+
const { top: defaultTop, right: defaultRight, bottom: defaultBottom, left: defaultLeft } = defaultMargin ?? DEFAULT_CHART_MARGIN;
|
|
10
12
|
const margin = useMemo(() => resolveMargin({
|
|
11
13
|
top: marginTop,
|
|
12
14
|
right: marginRight,
|
|
13
15
|
bottom: marginBottom,
|
|
14
16
|
left: marginLeft
|
|
17
|
+
}, {
|
|
18
|
+
top: defaultTop,
|
|
19
|
+
right: defaultRight,
|
|
20
|
+
bottom: defaultBottom,
|
|
21
|
+
left: defaultLeft
|
|
15
22
|
}), [
|
|
16
23
|
marginTop,
|
|
17
24
|
marginRight,
|
|
18
25
|
marginBottom,
|
|
19
|
-
marginLeft
|
|
26
|
+
marginLeft,
|
|
27
|
+
defaultTop,
|
|
28
|
+
defaultRight,
|
|
29
|
+
defaultBottom,
|
|
30
|
+
defaultLeft
|
|
20
31
|
]);
|
|
21
32
|
const { plotWidth, plotHeight } = useMemo(() => getPlotDimensions(resolvedWidth, resolvedHeight, margin), [
|
|
22
33
|
resolvedWidth,
|
|
@@ -36,8 +47,7 @@ function useChartDimensions({ containerRef, width, height, fillWidth = false, fi
|
|
|
36
47
|
margin,
|
|
37
48
|
isMeasured: resolvedWidth > 0 && resolvedHeight > 0,
|
|
38
49
|
reservedHeight: fillHeight || aspectRatio ? void 0 : height,
|
|
39
|
-
reservedAspectRatio: fillHeight ? void 0 : aspectRatio
|
|
40
|
-
fillHeight
|
|
50
|
+
reservedAspectRatio: fillHeight ? void 0 : aspectRatio
|
|
41
51
|
}), [
|
|
42
52
|
resolvedWidth,
|
|
43
53
|
resolvedHeight,
|
|
@@ -11,8 +11,8 @@ import { ChartMarkState } from "./types";
|
|
|
11
11
|
*
|
|
12
12
|
* Both live here rather than one of them in the charts, so there is a single
|
|
13
13
|
* `isEngaged` boolean. That matters more than it looks: `ChartPlotGroup` uses it
|
|
14
|
-
* to cancel
|
|
15
|
-
* would sit through that delay before muting anything and feel broken.
|
|
14
|
+
* to cancel the mark restore delay, and a second hover source that didn't feed
|
|
15
|
+
* it would sit through that delay before muting anything and feel broken.
|
|
16
16
|
*
|
|
17
17
|
* Escape or a click on the plot background clears both pins.
|
|
18
18
|
*/
|
|
@@ -23,10 +23,18 @@ interface UseChartInteractionOptions {
|
|
|
23
23
|
seriesKeys?: string[];
|
|
24
24
|
/** Suppress all interaction — used while loading and when the tooltip is off. */
|
|
25
25
|
disabled?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Render every mark muted, whatever the two axes say.
|
|
28
|
+
*
|
|
29
|
+
* Distinct from {@link disabled}, which suppresses *input*. Loading suppresses
|
|
30
|
+
* *emphasis*: there is nothing yet worth drawing attention to. Folded in here
|
|
31
|
+
* rather than left to each mark, because the alternative is the same
|
|
32
|
+
* `isLoading ? MUTED : getMarkState(…)` ternary hand-copied at every mark —
|
|
33
|
+
* which is exactly how one of them came to be missing it.
|
|
34
|
+
*/
|
|
35
|
+
isLoading?: boolean;
|
|
26
36
|
/** Notified when the pinned category changes. */
|
|
27
37
|
onPinnedChange?: (index: number | null) => void;
|
|
28
|
-
/** Notified when the pinned series changes. */
|
|
29
|
-
onPinnedSeriesChange?: (seriesKey: string | null) => void;
|
|
30
38
|
}
|
|
31
39
|
interface MarkStateArgs {
|
|
32
40
|
/** Omit for marks that belong to no particular category, such as a line. */
|
|
@@ -35,14 +43,11 @@ interface MarkStateArgs {
|
|
|
35
43
|
seriesKey?: string;
|
|
36
44
|
}
|
|
37
45
|
export interface ChartInteraction {
|
|
38
|
-
hoveredIndex: number | null;
|
|
39
46
|
pinnedIndex: number | null;
|
|
40
47
|
/** Pinned wins over hovered — this is what the tooltip follows. */
|
|
41
48
|
activeIndex: number | null;
|
|
42
49
|
hoveredSeriesKey: string | null;
|
|
43
50
|
pinnedSeriesKey: string | null;
|
|
44
|
-
/** Pinned wins over hovered, matching the category axis. */
|
|
45
|
-
activeSeriesKey: string | null;
|
|
46
51
|
/** True while *either* axis is engaged; cancels the mark restore delay. */
|
|
47
52
|
isEngaged: boolean;
|
|
48
53
|
/**
|
|
@@ -67,5 +72,5 @@ export interface ChartInteraction {
|
|
|
67
72
|
/** Attach to the plot backdrop; clears both pins. */
|
|
68
73
|
onBackdropClick: () => void;
|
|
69
74
|
}
|
|
70
|
-
export declare function useChartInteraction({ categoryCount, seriesKeys, disabled,
|
|
75
|
+
export declare function useChartInteraction({ categoryCount, seriesKeys, disabled, isLoading, onPinnedChange, }: UseChartInteractionOptions): ChartInteraction;
|
|
71
76
|
export {};
|
|
@@ -1,21 +1,19 @@
|
|
|
1
1
|
import { ChartMarkState } from "./types.js";
|
|
2
|
+
import { match } from "ts-pattern";
|
|
2
3
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
3
4
|
//#region src/charts/useChartInteraction.ts
|
|
4
5
|
/** How far back a state sits, so two axes can be compared and the worse taken. */
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
9
|
-
function useChartInteraction({ categoryCount, seriesKeys, disabled = false, onPinnedChange, onPinnedSeriesChange }) {
|
|
6
|
+
function severity(state) {
|
|
7
|
+
return match(state).with(ChartMarkState.ACTIVE, () => 0).with(ChartMarkState.MUTED, () => 1).with(ChartMarkState.FADED, () => 2).exhaustive();
|
|
8
|
+
}
|
|
9
|
+
function useChartInteraction({ categoryCount, seriesKeys, disabled = false, isLoading = false, onPinnedChange }) {
|
|
10
10
|
const [hoveredIndex, setHoveredIndex] = useState(null);
|
|
11
11
|
const [pinnedIndex, setPinnedIndex] = useState(null);
|
|
12
12
|
const [hoveredSeriesKey, setHoveredSeriesKey] = useState(null);
|
|
13
13
|
const [pinnedSeriesKey, setPinnedSeriesKey] = useState(null);
|
|
14
14
|
const onPinnedChangeRef = useRef(onPinnedChange);
|
|
15
|
-
const onPinnedSeriesChangeRef = useRef(onPinnedSeriesChange);
|
|
16
15
|
useEffect(() => {
|
|
17
16
|
onPinnedChangeRef.current = onPinnedChange;
|
|
18
|
-
onPinnedSeriesChangeRef.current = onPinnedSeriesChange;
|
|
19
17
|
});
|
|
20
18
|
const safeHovered = hoveredIndex !== null && hoveredIndex < categoryCount ? hoveredIndex : null;
|
|
21
19
|
const safePinned = pinnedIndex !== null && pinnedIndex < categoryCount ? pinnedIndex : null;
|
|
@@ -26,7 +24,6 @@ function useChartInteraction({ categoryCount, seriesKeys, disabled = false, onPi
|
|
|
26
24
|
setPinnedIndex(null);
|
|
27
25
|
setPinnedSeriesKey(null);
|
|
28
26
|
onPinnedChangeRef.current?.(null);
|
|
29
|
-
onPinnedSeriesChangeRef.current?.(null);
|
|
30
27
|
}, []);
|
|
31
28
|
const hasPin = safePinned !== null || safePinnedSeries !== null;
|
|
32
29
|
useEffect(() => {
|
|
@@ -65,9 +62,9 @@ function useChartInteraction({ categoryCount, seriesKeys, disabled = false, onPi
|
|
|
65
62
|
const next = pinnedSeriesKey === seriesKey ? null : seriesKey;
|
|
66
63
|
setPinnedSeriesKey(next);
|
|
67
64
|
if (next === null) setHoveredSeriesKey(null);
|
|
68
|
-
onPinnedSeriesChangeRef.current?.(next);
|
|
69
65
|
}, [pinnedSeriesKey]);
|
|
70
66
|
const getMarkState = useCallback(({ categoryIndex, seriesKey }) => {
|
|
67
|
+
if (isLoading) return ChartMarkState.MUTED;
|
|
71
68
|
const categoryState = (() => {
|
|
72
69
|
if (categoryIndex === void 0) return ChartMarkState.ACTIVE;
|
|
73
70
|
if (safePinned !== null) {
|
|
@@ -90,18 +87,17 @@ function useChartInteraction({ categoryCount, seriesKeys, disabled = false, onPi
|
|
|
90
87
|
})();
|
|
91
88
|
return severity(categoryState) >= severity(seriesState) ? categoryState : seriesState;
|
|
92
89
|
}, [
|
|
90
|
+
isLoading,
|
|
93
91
|
safePinned,
|
|
94
92
|
safeHovered,
|
|
95
93
|
safePinnedSeries,
|
|
96
94
|
safeHoveredSeries
|
|
97
95
|
]);
|
|
98
96
|
return useMemo(() => ({
|
|
99
|
-
hoveredIndex: safeHovered,
|
|
100
97
|
pinnedIndex: safePinned,
|
|
101
98
|
activeIndex: safePinned ?? safeHovered,
|
|
102
99
|
hoveredSeriesKey: safeHoveredSeries,
|
|
103
100
|
pinnedSeriesKey: safePinnedSeries,
|
|
104
|
-
activeSeriesKey: safePinnedSeries ?? safeHoveredSeries,
|
|
105
101
|
isEngaged: safePinned !== null || safeHovered !== null || safePinnedSeries !== null || safeHoveredSeries !== null,
|
|
106
102
|
getMarkState,
|
|
107
103
|
onCategoryEnter,
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { ChartPalette, ChartSeriesStyles } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Palette assignment for a chart's series, shared by all three charts.
|
|
4
|
+
*
|
|
5
|
+
* Every chart resolved colour the same way — look the key up in the `series`
|
|
6
|
+
* record, take its slot, fall back to the palette — but each spelled it
|
|
7
|
+
* differently and each rebuilt the key list itself. Doing it once here is what
|
|
8
|
+
* makes "adding a series never recolours the existing ones" a property of the
|
|
9
|
+
* design rather than of three separate call sites remembering to use
|
|
10
|
+
* `indexOf` instead of the loop counter.
|
|
11
|
+
*/
|
|
12
|
+
export interface SeriesColorResolver<TKey extends string> {
|
|
13
|
+
/**
|
|
14
|
+
* Series keys in `series` record order.
|
|
15
|
+
*
|
|
16
|
+
* Insertion order is what makes a key's palette slot stable regardless of
|
|
17
|
+
* which data happens to contain it, so this is never sorted.
|
|
18
|
+
*/
|
|
19
|
+
keys: TKey[];
|
|
20
|
+
/** Colour for a series. The record's own `color` wins over the slot. */
|
|
21
|
+
resolve: (key: TKey, explicit?: ChartPalette) => string;
|
|
22
|
+
/**
|
|
23
|
+
* Colour by palette position, for marks that are not series.
|
|
24
|
+
*
|
|
25
|
+
* A stacked bar's segments are a second dimension with no record of their
|
|
26
|
+
* own, so they take the ramp by position rather than by key.
|
|
27
|
+
*/
|
|
28
|
+
resolveByIndex: (index: number, explicit?: ChartPalette) => string;
|
|
29
|
+
/**
|
|
30
|
+
* Neutral, for a mark that names no series at all.
|
|
31
|
+
*
|
|
32
|
+
* The pie's folded bucket is not one of the slices, so borrowing a hue would
|
|
33
|
+
* imply it is.
|
|
34
|
+
*/
|
|
35
|
+
neutral: string;
|
|
36
|
+
}
|
|
37
|
+
export declare function useSeriesColorResolver<TKey extends string>(series: ChartSeriesStyles<TKey>): SeriesColorResolver<TKey>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { useGalaxyTheme } from "../theme/GalaxyTheme.js";
|
|
2
|
+
import { resolveChartPaletteColor, resolveSeriesColor } from "./chartTheme.js";
|
|
3
|
+
import { useCallback, useMemo } from "react";
|
|
4
|
+
//#region src/charts/useSeriesColorResolver.ts
|
|
5
|
+
function useSeriesColorResolver(series) {
|
|
6
|
+
const { theme } = useGalaxyTheme();
|
|
7
|
+
const keys = useMemo(() => Object.keys(series), [series]);
|
|
8
|
+
const resolveByIndex = useCallback((index, explicit) => explicit ? resolveChartPaletteColor(theme, explicit) : resolveSeriesColor(theme, index), [theme]);
|
|
9
|
+
const resolve = useCallback((key, explicit) => resolveSeriesColor(theme, keys.indexOf(key), explicit ?? series[key]?.color), [
|
|
10
|
+
theme,
|
|
11
|
+
keys,
|
|
12
|
+
series
|
|
13
|
+
]);
|
|
14
|
+
return useMemo(() => ({
|
|
15
|
+
keys,
|
|
16
|
+
resolve,
|
|
17
|
+
resolveByIndex,
|
|
18
|
+
neutral: theme.color.text.tertiary
|
|
19
|
+
}), [
|
|
20
|
+
keys,
|
|
21
|
+
resolve,
|
|
22
|
+
resolveByIndex,
|
|
23
|
+
theme
|
|
24
|
+
]);
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
export { useSeriesColorResolver };
|
package/dist/inputs/DateInput.js
CHANGED
|
@@ -7,7 +7,7 @@ import Dropdown, { DropdownVariant } from "../dropdown/Dropdown.js";
|
|
|
7
7
|
import GridWrapper from "../containers/GridWrapper.js";
|
|
8
8
|
import Spacing from "../containers/Spacing.js";
|
|
9
9
|
import { InputLabel, InputSize } from "./Input.js";
|
|
10
|
-
import SelectInput, {
|
|
10
|
+
import SelectInput, { SelectInputVariant } from "./SelectInput.js";
|
|
11
11
|
/* empty css */
|
|
12
12
|
import { styled } from "@linaria/react";
|
|
13
13
|
import { match } from "ts-pattern";
|
|
@@ -379,7 +379,7 @@ var DateInput = ({ label, size = InputSize.MEDIUM, value, placeholder = "Select
|
|
|
379
379
|
onMouseDown: (e) => e.stopPropagation(),
|
|
380
380
|
children: [/* @__PURE__ */ jsx(SelectInput, {
|
|
381
381
|
variant: SelectInputVariant.SECONDARY,
|
|
382
|
-
size:
|
|
382
|
+
size: InputSize.SMALL,
|
|
383
383
|
options: monthOptions,
|
|
384
384
|
value: selectedMonthOption,
|
|
385
385
|
onChange: handleMonthChange,
|
|
@@ -388,7 +388,7 @@ var DateInput = ({ label, size = InputSize.MEDIUM, value, placeholder = "Select
|
|
|
388
388
|
width: 120
|
|
389
389
|
}), /* @__PURE__ */ jsx(SelectInput, {
|
|
390
390
|
variant: SelectInputVariant.SECONDARY,
|
|
391
|
-
size:
|
|
391
|
+
size: InputSize.SMALL,
|
|
392
392
|
options: yearOptions,
|
|
393
393
|
value: selectedYearOption,
|
|
394
394
|
onChange: handleYearChange,
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
2
|
import type { Icon as PhosphorIcon } from "@phosphor-icons/react";
|
|
3
3
|
import { DropdownPosition } from "../dropdown/Dropdown";
|
|
4
|
-
import {
|
|
4
|
+
import { InputSize } from "./Input";
|
|
5
|
+
import { SelectInputVariant, type SelectInputOption } from "./SelectInput";
|
|
5
6
|
export interface PinnedOptions {
|
|
6
7
|
id: string;
|
|
7
8
|
label: string;
|
|
@@ -11,7 +12,7 @@ export interface MultiSelectInputProps {
|
|
|
11
12
|
label?: string;
|
|
12
13
|
icon?: PhosphorIcon;
|
|
13
14
|
variant?: SelectInputVariant;
|
|
14
|
-
size?:
|
|
15
|
+
size?: InputSize;
|
|
15
16
|
options: SelectInputOption[];
|
|
16
17
|
value: SelectInputOption[];
|
|
17
18
|
placeholder?: string;
|
|
@@ -6,12 +6,12 @@ import Icon, { IconVariant } from "../icons/Icon.js";
|
|
|
6
6
|
import FlexWrapper, { AlignItems, FlexDirection } from "../containers/FlexWrapper.js";
|
|
7
7
|
import CheckboxInput, { CheckboxInputVariant } from "./CheckboxInput.js";
|
|
8
8
|
import Dropdown, { DropdownPosition, DropdownVariant } from "../dropdown/Dropdown.js";
|
|
9
|
-
import { InputLabel } from "./Input.js";
|
|
9
|
+
import { InputLabel, InputSize } from "./Input.js";
|
|
10
10
|
import RotateWithTransition from "../animations/RotateWithTransition.js";
|
|
11
11
|
import HorizontalDivider from "../dividers/HorizontalDivider.js";
|
|
12
12
|
import { useElementWidth } from "../hooks/useElementWidth.js";
|
|
13
13
|
import { useControlledOpenState, useDebouncedValue } from "./hooks.js";
|
|
14
|
-
import { SELECT_INPUT_SIZE_TO_ICON_SIZE, SELECT_INPUT_SIZE_TO_TEXT_SIZE_MAP, SELECT_INPUT_VARIANT_TO_ICON_VARIANT_MAP,
|
|
14
|
+
import { SELECT_INPUT_SIZE_TO_ICON_SIZE, SELECT_INPUT_SIZE_TO_TEXT_SIZE_MAP, SELECT_INPUT_VARIANT_TO_ICON_VARIANT_MAP, SelectInputVariant } from "./SelectInput.js";
|
|
15
15
|
/* empty css */
|
|
16
16
|
import { styled } from "@linaria/react";
|
|
17
17
|
import { match } from "ts-pattern";
|
|
@@ -20,10 +20,10 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
|
20
20
|
import { ArrowCounterClockwiseIcon, CaretDownIcon } from "@phosphor-icons/react";
|
|
21
21
|
//#region src/inputs/MultiSelectInput.tsx
|
|
22
22
|
var _exp = () => ({ $size }) => {
|
|
23
|
-
return match($size).with(
|
|
23
|
+
return match($size).with(InputSize.SMALL, () => "0 6px").with(InputSize.MEDIUM, () => "0 8px").with(InputSize.LARGE, () => "0 10px").exhaustive();
|
|
24
24
|
};
|
|
25
25
|
var _exp2 = () => ({ $size }) => {
|
|
26
|
-
return match($size).with(
|
|
26
|
+
return match($size).with(InputSize.SMALL, () => "24px").with(InputSize.MEDIUM, () => "28px").with(InputSize.LARGE, () => "32px").exhaustive();
|
|
27
27
|
};
|
|
28
28
|
var _exp3 = () => ({ $fillWidth, $width }) => $fillWidth ? "100%" : $width ? getCssSizeValue($width) : "auto";
|
|
29
29
|
var _exp4 = () => ({ $isDisabled }) => $isDisabled ? "not-allowed" : "pointer";
|
|
@@ -51,14 +51,14 @@ var StyledMultiSelectInput = withTheme(/*#__PURE__*/ styled("div")({
|
|
|
51
51
|
}
|
|
52
52
|
}));
|
|
53
53
|
var _exp9 = () => ({ $size }) => {
|
|
54
|
-
return match($size).with(
|
|
54
|
+
return match($size).with(InputSize.SMALL, () => "24px").with(InputSize.MEDIUM, () => "28px").with(InputSize.LARGE, () => "32px").exhaustive();
|
|
55
55
|
};
|
|
56
56
|
var _exp0 = () => ({ theme }) => theme.color.text.primary;
|
|
57
57
|
var _exp1 = () => ({ theme, $size }) => {
|
|
58
|
-
return match($size).with(
|
|
58
|
+
return match($size).with(InputSize.SMALL, () => theme.font.sans.size.body_sm).with(InputSize.MEDIUM, () => theme.font.sans.size.body_md).with(InputSize.LARGE, () => theme.font.sans.size.body_lg).exhaustive();
|
|
59
59
|
};
|
|
60
60
|
var _exp10 = () => ({ theme, $size }) => {
|
|
61
|
-
return match($size).with(
|
|
61
|
+
return match($size).with(InputSize.SMALL, () => theme.font.sans.height.body_sm).with(InputSize.MEDIUM, () => theme.font.sans.height.body_md).with(InputSize.LARGE, () => theme.font.sans.height.body_lg).exhaustive();
|
|
62
62
|
};
|
|
63
63
|
var _exp11 = () => ({ theme }) => theme.font.sans.weight.medium;
|
|
64
64
|
var _exp12 = () => ({ theme }) => theme.font.sans.family;
|
|
@@ -131,7 +131,7 @@ var SELECT_INPUT_VARIANT_TO_CHECKBOX_INPUT_VARIANT = {
|
|
|
131
131
|
[SelectInputVariant.SECONDARY]: CheckboxInputVariant.SECONDARY,
|
|
132
132
|
[SelectInputVariant.TERTIARY]: CheckboxInputVariant.PRIMARY
|
|
133
133
|
};
|
|
134
|
-
var MultiSelectInput = ({ label, icon, variant = SelectInputVariant.PRIMARY, size =
|
|
134
|
+
var MultiSelectInput = ({ label, icon, variant = SelectInputVariant.PRIMARY, size = InputSize.MEDIUM, options, value, placeholder = "Select options...", onChange, width, fillWidth, dropdownWidth, dropdownPlacement = DropdownPosition.BOTTOM_START, isDisabled = false, isRequired = false, isOpen: controlledIsOpen, isIconTrailing, onOpenChange, onReset, pinnedOptions = [], maxDisplayItems = 3, renderSelectedText, renderSelectedContent, error, onSearch, debounceMs = 300 }) => {
|
|
135
135
|
const isSearchable = !!onSearch;
|
|
136
136
|
const [isHovered, setIsHovered] = useState(false);
|
|
137
137
|
const [searchTerm, setSearchTerm] = useState("");
|
|
@@ -1,16 +1,12 @@
|
|
|
1
1
|
import { IconVariant } from "../icons/Icon";
|
|
2
2
|
import { TextSize, TextVariant } from "../text/Text";
|
|
3
|
+
import { InputSize } from "./Input";
|
|
3
4
|
export declare enum SelectInputVariant {
|
|
4
5
|
BASE = "BASE",
|
|
5
6
|
PRIMARY = "PRIMARY",
|
|
6
7
|
SECONDARY = "SECONDARY",
|
|
7
8
|
TERTIARY = "TERTIARY"
|
|
8
9
|
}
|
|
9
|
-
export declare enum SelectInputSize {
|
|
10
|
-
SMALL = "SMALL",
|
|
11
|
-
MEDIUM = "MEDIUM",
|
|
12
|
-
LARGE = "LARGE"
|
|
13
|
-
}
|
|
14
10
|
export interface SelectInputOption {
|
|
15
11
|
id: string;
|
|
16
12
|
label: string;
|
|
@@ -26,7 +22,7 @@ export interface PinnedOption {
|
|
|
26
22
|
export interface SelectInputProps {
|
|
27
23
|
label?: string;
|
|
28
24
|
variant?: SelectInputVariant;
|
|
29
|
-
size?:
|
|
25
|
+
size?: InputSize;
|
|
30
26
|
options: SelectInputOption[];
|
|
31
27
|
value?: SelectInputOption | null;
|
|
32
28
|
placeholder?: string;
|
|
@@ -45,7 +41,7 @@ export interface SelectInputProps {
|
|
|
45
41
|
debounceMs?: number;
|
|
46
42
|
}
|
|
47
43
|
export declare const SELECT_INPUT_VARIANT_TO_ICON_VARIANT_MAP: Record<SelectInputVariant, IconVariant>;
|
|
48
|
-
export declare const SELECT_INPUT_SIZE_TO_ICON_SIZE: Record<
|
|
49
|
-
export declare const SELECT_INPUT_SIZE_TO_TEXT_SIZE_MAP: Record<
|
|
44
|
+
export declare const SELECT_INPUT_SIZE_TO_ICON_SIZE: Record<InputSize, number>;
|
|
45
|
+
export declare const SELECT_INPUT_SIZE_TO_TEXT_SIZE_MAP: Record<InputSize, TextSize>;
|
|
50
46
|
declare const SelectInput: ({ label, variant, size, options, value, placeholder, onChange, width, fillWidth, dropdownWidth, isDisabled, isRequired, isOpen: controlledIsOpen, onOpenChange, onReset, pinnedOptions, error, onSearch, debounceMs, }: SelectInputProps) => import("react").JSX.Element;
|
|
51
47
|
export default SelectInput;
|
|
@@ -6,7 +6,7 @@ import Icon, { IconVariant, IconWeight } from "../icons/Icon.js";
|
|
|
6
6
|
import FlexWrapper, { AlignItems, FlexDirection } from "../containers/FlexWrapper.js";
|
|
7
7
|
import Dropdown, { DropdownVariant } from "../dropdown/Dropdown.js";
|
|
8
8
|
import Spacing from "../containers/Spacing.js";
|
|
9
|
-
import { InputLabel } from "./Input.js";
|
|
9
|
+
import { InputLabel, InputSize } from "./Input.js";
|
|
10
10
|
import RotateWithTransition from "../animations/RotateWithTransition.js";
|
|
11
11
|
import HorizontalDivider from "../dividers/HorizontalDivider.js";
|
|
12
12
|
import { useElementWidth } from "../hooks/useElementWidth.js";
|
|
@@ -25,17 +25,11 @@ var SelectInputVariant = /* @__PURE__ */ function(SelectInputVariant) {
|
|
|
25
25
|
SelectInputVariant["TERTIARY"] = "TERTIARY";
|
|
26
26
|
return SelectInputVariant;
|
|
27
27
|
}({});
|
|
28
|
-
var SelectInputSize = /* @__PURE__ */ function(SelectInputSize) {
|
|
29
|
-
SelectInputSize["SMALL"] = "SMALL";
|
|
30
|
-
SelectInputSize["MEDIUM"] = "MEDIUM";
|
|
31
|
-
SelectInputSize["LARGE"] = "LARGE";
|
|
32
|
-
return SelectInputSize;
|
|
33
|
-
}({});
|
|
34
28
|
var _exp = () => ({ $size }) => {
|
|
35
|
-
return match($size).with(
|
|
29
|
+
return match($size).with(InputSize.SMALL, () => "0 6px").with(InputSize.MEDIUM, () => "0 8px").with(InputSize.LARGE, () => "0 10px").exhaustive();
|
|
36
30
|
};
|
|
37
31
|
var _exp2 = () => ({ $size }) => {
|
|
38
|
-
return match($size).with(
|
|
32
|
+
return match($size).with(InputSize.SMALL, () => "24px").with(InputSize.MEDIUM, () => "28px").with(InputSize.LARGE, () => "32px").exhaustive();
|
|
39
33
|
};
|
|
40
34
|
var _exp3 = () => ({ $fillWidth, $width }) => $fillWidth ? "100%" : $width ? getCssSizeValue($width) : "auto";
|
|
41
35
|
var _exp4 = () => ({ $isDisabled }) => $isDisabled ? "not-allowed" : "pointer";
|
|
@@ -63,14 +57,14 @@ var StyledSelectInput = withTheme(/*#__PURE__*/ styled("div")({
|
|
|
63
57
|
}
|
|
64
58
|
}));
|
|
65
59
|
var _exp9 = () => ({ $size }) => {
|
|
66
|
-
return match($size).with(
|
|
60
|
+
return match($size).with(InputSize.SMALL, () => "24px").with(InputSize.MEDIUM, () => "28px").with(InputSize.LARGE, () => "32px").exhaustive();
|
|
67
61
|
};
|
|
68
62
|
var _exp0 = () => ({ theme }) => theme.color.text.primary;
|
|
69
63
|
var _exp1 = () => ({ theme, $size }) => {
|
|
70
|
-
return match($size).with(
|
|
64
|
+
return match($size).with(InputSize.SMALL, () => theme.font.sans.size.body_sm).with(InputSize.MEDIUM, () => theme.font.sans.size.body_md).with(InputSize.LARGE, () => theme.font.sans.size.body_lg).exhaustive();
|
|
71
65
|
};
|
|
72
66
|
var _exp10 = () => ({ theme, $size }) => {
|
|
73
|
-
return match($size).with(
|
|
67
|
+
return match($size).with(InputSize.SMALL, () => theme.font.sans.height.body_sm).with(InputSize.MEDIUM, () => theme.font.sans.height.body_md).with(InputSize.LARGE, () => theme.font.sans.height.body_lg).exhaustive();
|
|
74
68
|
};
|
|
75
69
|
var _exp11 = () => ({ theme }) => theme.font.sans.weight.medium;
|
|
76
70
|
var _exp12 = () => ({ theme }) => theme.font.sans.family;
|
|
@@ -150,16 +144,16 @@ var SELECT_INPUT_VARIANT_TO_ICON_VARIANT_MAP = {
|
|
|
150
144
|
["TERTIARY"]: IconVariant.PRIMARY
|
|
151
145
|
};
|
|
152
146
|
var SELECT_INPUT_SIZE_TO_ICON_SIZE = {
|
|
153
|
-
[
|
|
154
|
-
[
|
|
155
|
-
[
|
|
147
|
+
[InputSize.SMALL]: 12,
|
|
148
|
+
[InputSize.MEDIUM]: 14,
|
|
149
|
+
[InputSize.LARGE]: 16
|
|
156
150
|
};
|
|
157
151
|
var SELECT_INPUT_SIZE_TO_TEXT_SIZE_MAP = {
|
|
158
|
-
[
|
|
159
|
-
[
|
|
160
|
-
[
|
|
152
|
+
[InputSize.SMALL]: TextSize.BODY_MD,
|
|
153
|
+
[InputSize.MEDIUM]: TextSize.BODY_MD,
|
|
154
|
+
[InputSize.LARGE]: TextSize.BODY_MD
|
|
161
155
|
};
|
|
162
|
-
var SelectInput = ({ label, variant = "PRIMARY", size =
|
|
156
|
+
var SelectInput = ({ label, variant = "PRIMARY", size = InputSize.MEDIUM, options, value, placeholder = "Select an option...", onChange, width, fillWidth, dropdownWidth, isDisabled = false, isRequired = false, isOpen: controlledIsOpen, onOpenChange, onReset, pinnedOptions = [], error, onSearch, debounceMs = 300 }) => {
|
|
163
157
|
const isSearchable = !!onSearch;
|
|
164
158
|
const [isHovered, setIsHovered] = useState(false);
|
|
165
159
|
const [searchTerm, setSearchTerm] = useState("");
|
|
@@ -525,4 +519,4 @@ var SelectInput = ({ label, variant = "PRIMARY", size = "MEDIUM", options, value
|
|
|
525
519
|
});
|
|
526
520
|
};
|
|
527
521
|
//#endregion
|
|
528
|
-
export { SELECT_INPUT_SIZE_TO_ICON_SIZE, SELECT_INPUT_SIZE_TO_TEXT_SIZE_MAP, SELECT_INPUT_VARIANT_TO_ICON_VARIANT_MAP,
|
|
522
|
+
export { SELECT_INPUT_SIZE_TO_ICON_SIZE, SELECT_INPUT_SIZE_TO_TEXT_SIZE_MAP, SELECT_INPUT_VARIANT_TO_ICON_VARIANT_MAP, SelectInputVariant, SelectInput as default };
|