@delacour/react-native-ui 0.1.0-alpha.20260925053522
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +113 -0
- package/package.json +125 -0
- package/src/components/accordion/AGENTS.md +193 -0
- package/src/components/accordion/accordion-content.tsx +125 -0
- package/src/components/accordion/accordion-description.tsx +17 -0
- package/src/components/accordion/accordion-indicator.tsx +104 -0
- package/src/components/accordion/accordion-item.tsx +78 -0
- package/src/components/accordion/accordion-title.tsx +18 -0
- package/src/components/accordion/accordion-trigger.tsx +148 -0
- package/src/components/accordion/accordion.context.tsx +153 -0
- package/src/components/accordion/accordion.tsx +253 -0
- package/src/components/accordion/accordion.types.ts +11 -0
- package/src/components/accordion/accordion.variants.test.ts +434 -0
- package/src/components/accordion/accordion.variants.ts +358 -0
- package/src/components/accordion/index.ts +45 -0
- package/src/components/badge/AGENTS.md +83 -0
- package/src/components/badge/badge-close-button.tsx +48 -0
- package/src/components/badge/badge-end-content.tsx +15 -0
- package/src/components/badge/badge-label.tsx +24 -0
- package/src/components/badge/badge-start-content.tsx +16 -0
- package/src/components/badge/badge.context.tsx +64 -0
- package/src/components/badge/badge.tsx +192 -0
- package/src/components/badge/badge.types.ts +10 -0
- package/src/components/badge/badge.variants.test.ts +269 -0
- package/src/components/badge/badge.variants.ts +238 -0
- package/src/components/badge/index.ts +17 -0
- package/src/components/bottom-sheet/AGENTS.md +242 -0
- package/src/components/bottom-sheet/bottom-sheet-background.tsx +35 -0
- package/src/components/bottom-sheet/bottom-sheet-close.tsx +60 -0
- package/src/components/bottom-sheet/bottom-sheet-container.tsx +246 -0
- package/src/components/bottom-sheet/bottom-sheet-content.tsx +66 -0
- package/src/components/bottom-sheet/bottom-sheet-description.tsx +29 -0
- package/src/components/bottom-sheet/bottom-sheet-footer.tsx +150 -0
- package/src/components/bottom-sheet/bottom-sheet-handle.tsx +47 -0
- package/src/components/bottom-sheet/bottom-sheet-overlay.tsx +180 -0
- package/src/components/bottom-sheet/bottom-sheet-portal.tsx +72 -0
- package/src/components/bottom-sheet/bottom-sheet-scroll-view.tsx +105 -0
- package/src/components/bottom-sheet/bottom-sheet-title.tsx +24 -0
- package/src/components/bottom-sheet/bottom-sheet-trigger.tsx +73 -0
- package/src/components/bottom-sheet/bottom-sheet.context.tsx +135 -0
- package/src/components/bottom-sheet/bottom-sheet.tsx +125 -0
- package/src/components/bottom-sheet/bottom-sheet.variants.test.ts +293 -0
- package/src/components/bottom-sheet/bottom-sheet.variants.ts +220 -0
- package/src/components/bottom-sheet/index.ts +40 -0
- package/src/components/bottom-sheet/use-bottom-sheet-input.ts +125 -0
- package/src/components/button/AGENTS.md +217 -0
- package/src/components/button/button-end-content.tsx +15 -0
- package/src/components/button/button-group-separator.tsx +37 -0
- package/src/components/button/button-group-text.tsx +101 -0
- package/src/components/button/button-group.tsx +158 -0
- package/src/components/button/button-label.tsx +24 -0
- package/src/components/button/button-start-content.tsx +15 -0
- package/src/components/button/button.context.tsx +187 -0
- package/src/components/button/button.tsx +273 -0
- package/src/components/button/button.types.ts +10 -0
- package/src/components/button/button.variants.test.ts +811 -0
- package/src/components/button/button.variants.ts +480 -0
- package/src/components/button/index.ts +50 -0
- package/src/components/chart/AGENTS.md +210 -0
- package/src/components/chart/chart-area.tsx +93 -0
- package/src/components/chart/chart-bar.tsx +103 -0
- package/src/components/chart/chart-bars.tsx +75 -0
- package/src/components/chart/chart-candlestick.tsx +60 -0
- package/src/components/chart/chart-grid.tsx +33 -0
- package/src/components/chart/chart-legend.tsx +41 -0
- package/src/components/chart/chart-line.tsx +50 -0
- package/src/components/chart/chart-scatter.tsx +35 -0
- package/src/components/chart/chart-tooltip-dot.tsx +70 -0
- package/src/components/chart/chart-tooltip-x.tsx +121 -0
- package/src/components/chart/chart-tooltip-y.tsx +55 -0
- package/src/components/chart/chart-tooltip.tsx +133 -0
- package/src/components/chart/chart-x-axis.tsx +27 -0
- package/src/components/chart/chart-y-axis.tsx +21 -0
- package/src/components/chart/chart.context.tsx +127 -0
- package/src/components/chart/chart.tsx +448 -0
- package/src/components/chart/chart.types.ts +108 -0
- package/src/components/chart/chart.variants.test.ts +893 -0
- package/src/components/chart/chart.variants.ts +726 -0
- package/src/components/chart/index.ts +103 -0
- package/src/components/chart/pie-chart-center.tsx +40 -0
- package/src/components/chart/pie-chart-label.tsx +56 -0
- package/src/components/chart/pie-chart-slice.tsx +45 -0
- package/src/components/chart/pie-chart-tooltip.tsx +85 -0
- package/src/components/chart/pie-chart.context.tsx +77 -0
- package/src/components/chart/pie-chart.tsx +247 -0
- package/src/components/chart/use-chart-font.ts +25 -0
- package/src/components/chart/use-chart-palette.ts +38 -0
- package/src/components/checkbox/AGENTS.md +185 -0
- package/src/components/checkbox/checkbox-box.tsx +152 -0
- package/src/components/checkbox/checkbox-group.tsx +107 -0
- package/src/components/checkbox/checkbox-label.tsx +34 -0
- package/src/components/checkbox/checkbox.context.tsx +136 -0
- package/src/components/checkbox/checkbox.tsx +258 -0
- package/src/components/checkbox/checkbox.types.ts +14 -0
- package/src/components/checkbox/checkbox.variants.test.ts +634 -0
- package/src/components/checkbox/checkbox.variants.ts +484 -0
- package/src/components/checkbox/index.ts +42 -0
- package/src/components/field/AGENTS.md +112 -0
- package/src/components/field/field-content.tsx +24 -0
- package/src/components/field/field-description.tsx +29 -0
- package/src/components/field/field-error.tsx +61 -0
- package/src/components/field/field-group.tsx +22 -0
- package/src/components/field/field-label.tsx +47 -0
- package/src/components/field/field-legend.tsx +34 -0
- package/src/components/field/field-separator.tsx +81 -0
- package/src/components/field/field-set.tsx +22 -0
- package/src/components/field/field.context.tsx +95 -0
- package/src/components/field/field.tsx +157 -0
- package/src/components/field/field.types.ts +15 -0
- package/src/components/field/field.variants.test.ts +240 -0
- package/src/components/field/field.variants.ts +156 -0
- package/src/components/field/index.ts +22 -0
- package/src/components/icon/AGENTS.md +104 -0
- package/src/components/icon/icon.context.tsx +29 -0
- package/src/components/icon/icon.tsx +92 -0
- package/src/components/icon/icon.variants.test.ts +111 -0
- package/src/components/icon/icon.variants.ts +79 -0
- package/src/components/icon/index.ts +11 -0
- package/src/components/input/AGENTS.md +126 -0
- package/src/components/input/index.ts +27 -0
- package/src/components/input/input-group-decorator.tsx +84 -0
- package/src/components/input/input-group-prefix.tsx +16 -0
- package/src/components/input/input-group-suffix.tsx +15 -0
- package/src/components/input/input-group.tsx +140 -0
- package/src/components/input/input.context.tsx +87 -0
- package/src/components/input/input.tsx +184 -0
- package/src/components/input/input.types.ts +10 -0
- package/src/components/input/input.variants.test.ts +369 -0
- package/src/components/input/input.variants.ts +329 -0
- package/src/components/list-group/AGENTS.md +67 -0
- package/src/components/list-group/index.ts +20 -0
- package/src/components/list-group/list-group-item-content.tsx +10 -0
- package/src/components/list-group/list-group-item-description.tsx +12 -0
- package/src/components/list-group/list-group-item-prefix.tsx +30 -0
- package/src/components/list-group/list-group-item-suffix.tsx +55 -0
- package/src/components/list-group/list-group-item-title.tsx +17 -0
- package/src/components/list-group/list-group-item.tsx +87 -0
- package/src/components/list-group/list-group.context.tsx +65 -0
- package/src/components/list-group/list-group.tsx +116 -0
- package/src/components/list-group/list-group.types.ts +14 -0
- package/src/components/list-group/list-group.variants.test.ts +223 -0
- package/src/components/list-group/list-group.variants.ts +107 -0
- package/src/components/pressable/AGENTS.md +74 -0
- package/src/components/pressable/index.ts +9 -0
- package/src/components/pressable/pressable.tsx +261 -0
- package/src/components/pressable/pressable.variants.test.ts +128 -0
- package/src/components/pressable/pressable.variants.ts +80 -0
- package/src/components/provider/AGENTS.md +90 -0
- package/src/components/provider/index.ts +1 -0
- package/src/components/provider/provider.tsx +105 -0
- package/src/components/radio/AGENTS.md +245 -0
- package/src/components/radio/index.ts +35 -0
- package/src/components/radio/radio-group.tsx +126 -0
- package/src/components/radio/radio-indicator.tsx +91 -0
- package/src/components/radio/radio-label.tsx +39 -0
- package/src/components/radio/radio.context.tsx +132 -0
- package/src/components/radio/radio.tsx +215 -0
- package/src/components/radio/radio.variants.test.ts +580 -0
- package/src/components/radio/radio.variants.ts +271 -0
- package/src/components/screen/AGENTS.md +287 -0
- package/src/components/screen/index.ts +85 -0
- package/src/components/screen/screen-chat-list.tsx +485 -0
- package/src/components/screen/screen-content.tsx +69 -0
- package/src/components/screen/screen-debug.ts +43 -0
- package/src/components/screen/screen-error.tsx +61 -0
- package/src/components/screen/screen-flat-list.tsx +88 -0
- package/src/components/screen/screen-footer-background.tsx +60 -0
- package/src/components/screen/screen-footer.tsx +170 -0
- package/src/components/screen/screen-header.tsx +36 -0
- package/src/components/screen/screen-legend-list.tsx +105 -0
- package/src/components/screen/screen-list-component.tsx +23 -0
- package/src/components/screen/screen-loading.tsx +50 -0
- package/src/components/screen/screen-navbar-back-button.tsx +79 -0
- package/src/components/screen/screen-navbar-background.tsx +45 -0
- package/src/components/screen/screen-navbar-subtitle.tsx +26 -0
- package/src/components/screen/screen-navbar-title.tsx +31 -0
- package/src/components/screen/screen-navbar.tsx +154 -0
- package/src/components/screen/screen-root.tsx +38 -0
- package/src/components/screen/screen-scroll-area.tsx +116 -0
- package/src/components/screen/screen-scroll-shadow.tsx +186 -0
- package/src/components/screen/screen-section-list.tsx +81 -0
- package/src/components/screen/screen-view.tsx +59 -0
- package/src/components/screen/screen.context.tsx +167 -0
- package/src/components/screen/screen.tsx +114 -0
- package/src/components/screen/screen.types.ts +66 -0
- package/src/components/screen/screen.variants.test.ts +602 -0
- package/src/components/screen/screen.variants.ts +475 -0
- package/src/components/screen/use-screen-scroll-insets.ts +218 -0
- package/src/components/separator/AGENTS.md +30 -0
- package/src/components/separator/index.ts +8 -0
- package/src/components/separator/separator.tsx +85 -0
- package/src/components/slider/AGENTS.md +274 -0
- package/src/components/slider/index.ts +50 -0
- package/src/components/slider/slider-fill.tsx +68 -0
- package/src/components/slider/slider-output.tsx +55 -0
- package/src/components/slider/slider-thumb.tsx +193 -0
- package/src/components/slider/slider-track.tsx +233 -0
- package/src/components/slider/slider.context.tsx +118 -0
- package/src/components/slider/slider.tsx +321 -0
- package/src/components/slider/slider.types.ts +26 -0
- package/src/components/slider/slider.variants.test.ts +856 -0
- package/src/components/slider/slider.variants.ts +661 -0
- package/src/components/spinner/AGENTS.md +73 -0
- package/src/components/spinner/index.ts +17 -0
- package/src/components/spinner/spinner-arc.tsx +90 -0
- package/src/components/spinner/spinner-content.tsx +63 -0
- package/src/components/spinner/spinner.context.tsx +52 -0
- package/src/components/spinner/spinner.tsx +128 -0
- package/src/components/spinner/spinner.variants.test.ts +273 -0
- package/src/components/spinner/spinner.variants.ts +187 -0
- package/src/components/switch/AGENTS.md +213 -0
- package/src/components/switch/index.ts +42 -0
- package/src/components/switch/switch-content.tsx +111 -0
- package/src/components/switch/switch-end-content.tsx +18 -0
- package/src/components/switch/switch-start-content.tsx +20 -0
- package/src/components/switch/switch-thumb.tsx +102 -0
- package/src/components/switch/switch.context.tsx +79 -0
- package/src/components/switch/switch.tsx +423 -0
- package/src/components/switch/switch.types.ts +14 -0
- package/src/components/switch/switch.variants.test.ts +570 -0
- package/src/components/switch/switch.variants.ts +511 -0
- package/src/components/tabs/AGENTS.md +287 -0
- package/src/components/tabs/index.ts +69 -0
- package/src/components/tabs/tabs-content.tsx +65 -0
- package/src/components/tabs/tabs-indicator.tsx +97 -0
- package/src/components/tabs/tabs-label.tsx +62 -0
- package/src/components/tabs/tabs-list.tsx +139 -0
- package/src/components/tabs/tabs-pager.tsx +59 -0
- package/src/components/tabs/tabs-scroll-view.tsx +171 -0
- package/src/components/tabs/tabs-separator.tsx +73 -0
- package/src/components/tabs/tabs-trigger.tsx +210 -0
- package/src/components/tabs/tabs.context.tsx +294 -0
- package/src/components/tabs/tabs.tsx +435 -0
- package/src/components/tabs/tabs.types.ts +13 -0
- package/src/components/tabs/tabs.variants.test.ts +1020 -0
- package/src/components/tabs/tabs.variants.ts +670 -0
- package/src/components/text/AGENTS.md +95 -0
- package/src/components/text/index.ts +25 -0
- package/src/components/text/text.context.tsx +60 -0
- package/src/components/text/text.tsx +251 -0
- package/src/components/text/text.variants.test.ts +422 -0
- package/src/components/text/text.variants.ts +282 -0
- package/src/display-name.test.ts +145 -0
- package/src/docs.test.ts +98 -0
- package/src/expo/navigation-theme.tsx +58 -0
- package/src/hooks/use-controllable-state.ts +45 -0
- package/src/hooks/use-keyboard-state-sync.tsx +147 -0
- package/src/hooks/use-navigation-theme.ts +78 -0
- package/src/hooks/use-theme-color.ts +44 -0
- package/src/icons/central.ts +1 -0
- package/src/lib/cn.test.ts +136 -0
- package/src/lib/cn.ts +29 -0
- package/src/lib/color.test.ts +80 -0
- package/src/lib/color.ts +79 -0
- package/src/lib/compose-refs.test.ts +64 -0
- package/src/lib/compose-refs.ts +37 -0
- package/src/lib/keyboard-animation.test.ts +33 -0
- package/src/lib/keyboard-animation.ts +28 -0
- package/src/lib/merge-props.test.ts +82 -0
- package/src/lib/merge-props.ts +47 -0
- package/src/lib/navigation-theme.test.ts +49 -0
- package/src/lib/navigation-theme.ts +53 -0
- package/src/lib/slot.tsx +45 -0
- package/src/lib/tv.ts +20 -0
- package/src/styles/base.css +2 -0
- package/src/styles/geometry.test.ts +117 -0
- package/src/styles/index.css +3 -0
- package/src/styles/theme-tokens.test.ts +252 -0
- package/src/styles/theme.css +447 -0
- package/src/styles/tokens.css +132 -0
- package/src/styles/tokens.test.ts +181 -0
- package/src/styles/tokens.ts +86 -0
- package/src/uniwind-env.d.ts +1 -0
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
import { CartesianChart, type ChartOrientation, type ScrubConfig, useChartScrub } from "@delacour/react-native-charts";
|
|
2
|
+
import { type CurveType, resolveXValues } from "@delacour/react-native-charts/core";
|
|
3
|
+
import { Children, isValidElement, type ReactElement, type ReactNode, useMemo, useState } from "react";
|
|
4
|
+
import type { LayoutChangeEvent } from "react-native";
|
|
5
|
+
import { View } from "react-native";
|
|
6
|
+
import { useCSSVariable } from "uniwind";
|
|
7
|
+
import { useThemeColor } from "../../hooks/use-theme-color";
|
|
8
|
+
import { type ChartContextValue, ChartProvider } from "./chart.context";
|
|
9
|
+
import type {
|
|
10
|
+
ChartAreaSpec,
|
|
11
|
+
ChartBarLayout,
|
|
12
|
+
ChartBarSpec,
|
|
13
|
+
ChartCandlestickKeys,
|
|
14
|
+
ChartConfig,
|
|
15
|
+
ChartDatum,
|
|
16
|
+
} from "./chart.types";
|
|
17
|
+
import {
|
|
18
|
+
CANDLE_SENTIMENT_TOKENS,
|
|
19
|
+
type ChartSize,
|
|
20
|
+
chartAxisFontSize,
|
|
21
|
+
chartTickCount,
|
|
22
|
+
chartVariants,
|
|
23
|
+
resolveBarLayout,
|
|
24
|
+
resolveBarRadius,
|
|
25
|
+
resolveCategoryTickCount,
|
|
26
|
+
resolveChartKeys,
|
|
27
|
+
resolveChartSeries,
|
|
28
|
+
resolveDomainDefaults,
|
|
29
|
+
resolveStackedAreaKeys,
|
|
30
|
+
resolveXValueFormat,
|
|
31
|
+
} from "./chart.variants";
|
|
32
|
+
import { ChartArea, type ChartAreaProps } from "./chart-area";
|
|
33
|
+
import { ChartBar, type ChartBarProps } from "./chart-bar";
|
|
34
|
+
import { ChartBars } from "./chart-bars";
|
|
35
|
+
import { ChartCandlestick, type ChartCandlestickProps, candlestickKeysOf } from "./chart-candlestick";
|
|
36
|
+
import { ChartGrid } from "./chart-grid";
|
|
37
|
+
import { ChartLegend } from "./chart-legend";
|
|
38
|
+
import { ChartLine, type ChartLineProps } from "./chart-line";
|
|
39
|
+
import { ChartScatter, type ChartScatterProps } from "./chart-scatter";
|
|
40
|
+
import { ChartTooltip } from "./chart-tooltip";
|
|
41
|
+
import { ChartTooltipDot } from "./chart-tooltip-dot";
|
|
42
|
+
import { ChartTooltipX } from "./chart-tooltip-x";
|
|
43
|
+
import { ChartTooltipY } from "./chart-tooltip-y";
|
|
44
|
+
import { ChartXAxis } from "./chart-x-axis";
|
|
45
|
+
import { ChartYAxis } from "./chart-y-axis";
|
|
46
|
+
import { useChartFont } from "./use-chart-font";
|
|
47
|
+
import { useChartPalette } from "./use-chart-palette";
|
|
48
|
+
|
|
49
|
+
export type ChartProps = {
|
|
50
|
+
/** The series, keyed by the data field each reads. Key order is draw order. */
|
|
51
|
+
config: ChartConfig;
|
|
52
|
+
data: readonly ChartDatum[];
|
|
53
|
+
/** The field every series is plotted against. */
|
|
54
|
+
xKey: string;
|
|
55
|
+
/** Show only these series. Defaults to every key of `config`. */
|
|
56
|
+
yKeys?: readonly string[];
|
|
57
|
+
size?: ChartSize;
|
|
58
|
+
/** The interpolator every mark uses unless it overrides it. */
|
|
59
|
+
curve?: CurveType;
|
|
60
|
+
/** Explicit y bounds. Either end may be omitted to keep the data's own. */
|
|
61
|
+
domain?: { y?: readonly [number | undefined, number | undefined] };
|
|
62
|
+
/** Pull zero into the y domain. Defaults on when the chart holds bars. */
|
|
63
|
+
includeZero?: boolean;
|
|
64
|
+
/**
|
|
65
|
+
* Categories along x (the default) or along y.
|
|
66
|
+
*
|
|
67
|
+
* `horizontal` swaps the axis roles, so bars grow rightward from a category
|
|
68
|
+
* axis on the left. `Chart.XAxis` and `Chart.YAxis` keep their names — they
|
|
69
|
+
* label the axis they sit beside, whichever field it now carries.
|
|
70
|
+
*/
|
|
71
|
+
orientation?: ChartOrientation;
|
|
72
|
+
/** How the scrub coexists with a scrolling parent. Defaults to holding. */
|
|
73
|
+
scrubConfig?: ScrubConfig;
|
|
74
|
+
className?: string;
|
|
75
|
+
/** Named on the frame, so a capture flow or a test can find the plot. */
|
|
76
|
+
testID?: string;
|
|
77
|
+
children?: ReactNode;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/** Parts that draw into the canvas. Everything else is layered around it. */
|
|
81
|
+
const CANVAS_PARTS = new Set<unknown>([
|
|
82
|
+
ChartGrid,
|
|
83
|
+
ChartLine,
|
|
84
|
+
ChartArea,
|
|
85
|
+
ChartBar,
|
|
86
|
+
ChartScatter,
|
|
87
|
+
ChartCandlestick,
|
|
88
|
+
ChartXAxis,
|
|
89
|
+
ChartYAxis,
|
|
90
|
+
// The cursor marks are named under `Chart.Tooltip` but drawn in the canvas,
|
|
91
|
+
// not inside the view the tooltip renders.
|
|
92
|
+
ChartTooltipDot,
|
|
93
|
+
ChartTooltipX,
|
|
94
|
+
ChartTooltipY,
|
|
95
|
+
]);
|
|
96
|
+
|
|
97
|
+
function ChartRoot({
|
|
98
|
+
config,
|
|
99
|
+
data,
|
|
100
|
+
xKey,
|
|
101
|
+
yKeys,
|
|
102
|
+
size = "md",
|
|
103
|
+
curve = "monotone",
|
|
104
|
+
domain,
|
|
105
|
+
includeZero,
|
|
106
|
+
orientation = "vertical",
|
|
107
|
+
scrubConfig,
|
|
108
|
+
className,
|
|
109
|
+
testID,
|
|
110
|
+
children,
|
|
111
|
+
}: ChartProps): ReactElement {
|
|
112
|
+
const slots = chartVariants({ size });
|
|
113
|
+
const [frame, setFrame] = useState({ width: 0, height: 0 });
|
|
114
|
+
|
|
115
|
+
const declared = useMemo(() => resolveChartSeries(config, yKeys), [config, yKeys]);
|
|
116
|
+
const { canvas, overlay, axes, bars, stackedAreaKeys, candlestick, pointKeys, scatter } = useMemo(
|
|
117
|
+
() => partitionChildren(children),
|
|
118
|
+
[children]
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
// Every theme lookup happens here, above the canvas. Skia's renderer is a
|
|
122
|
+
// second React reconciler with no Uniwind provider in it, so a hook called
|
|
123
|
+
// inside would resolve nothing — the marks receive resolved strings instead.
|
|
124
|
+
// The series palette goes through `useChartPalette`, which holds the eight
|
|
125
|
+
// fixed `useThemeColor` slots the ramp resolves into.
|
|
126
|
+
const series = useChartPalette(declared);
|
|
127
|
+
const gridColor = useThemeColor("border");
|
|
128
|
+
const axisColor = useThemeColor("muted-foreground");
|
|
129
|
+
const surfaceColor = useThemeColor("background");
|
|
130
|
+
|
|
131
|
+
// The three candle sentiments, as three fixed calls — the same reason the
|
|
132
|
+
// palette hook writes its eight out. Only literal overrides are accepted
|
|
133
|
+
// on the part, so nothing else has to be resolved here.
|
|
134
|
+
const candlePositive = useThemeColor(CANDLE_SENTIMENT_TOKENS.positive);
|
|
135
|
+
const candleNegative = useThemeColor(CANDLE_SENTIMENT_TOKENS.negative);
|
|
136
|
+
const candleNeutral = useThemeColor(CANDLE_SENTIMENT_TOKENS.neutral);
|
|
137
|
+
const candleColors = useMemo(
|
|
138
|
+
() => ({
|
|
139
|
+
positive: candlePositive ?? CANDLE_SENTIMENT_TOKENS.positive,
|
|
140
|
+
negative: candleNegative ?? CANDLE_SENTIMENT_TOKENS.negative,
|
|
141
|
+
neutral: candleNeutral ?? CANDLE_SENTIMENT_TOKENS.neutral,
|
|
142
|
+
}),
|
|
143
|
+
[candlePositive, candleNegative, candleNeutral]
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
// `--radius` is the only step of the corner scale that survives to runtime,
|
|
147
|
+
// so a bar's corner is computed from the base — see `resolveBarRadius`.
|
|
148
|
+
const radius = (useCSSVariable("--radius") as number | undefined) ?? 0;
|
|
149
|
+
const barRadius = resolveBarRadius(size, radius);
|
|
150
|
+
|
|
151
|
+
// The engine plots every series plus the candle's other three fields; the
|
|
152
|
+
// scrub follows the same list so a candle key has a value to read.
|
|
153
|
+
const keys = useMemo(
|
|
154
|
+
() =>
|
|
155
|
+
resolveChartKeys(
|
|
156
|
+
series.map((entry) => entry.key),
|
|
157
|
+
candlestick
|
|
158
|
+
),
|
|
159
|
+
[series, candlestick]
|
|
160
|
+
);
|
|
161
|
+
const stackKeys = bars.mode === "stacked" ? bars.keys : stackedAreaKeys;
|
|
162
|
+
const scrub = useChartScrub(keys);
|
|
163
|
+
const font = useChartFont(chartAxisFontSize(size));
|
|
164
|
+
|
|
165
|
+
const formatXValue = useMemo(() => resolveXValueFormat(data, xKey), [data, xKey]);
|
|
166
|
+
// Only a categorical x has one label per row to protect; a numeric or a
|
|
167
|
+
// time axis is a scale, and its ticks stay a budget.
|
|
168
|
+
const categoryCount = useMemo(() => (resolveXValues(data, xKey).isCategorical ? data.length : 0), [data, xKey]);
|
|
169
|
+
|
|
170
|
+
const value = useMemo<ChartContextValue>(
|
|
171
|
+
() => ({
|
|
172
|
+
series,
|
|
173
|
+
data,
|
|
174
|
+
xKey,
|
|
175
|
+
formatXValue,
|
|
176
|
+
size,
|
|
177
|
+
slots,
|
|
178
|
+
gridColor,
|
|
179
|
+
axisColor,
|
|
180
|
+
surfaceColor,
|
|
181
|
+
scrub,
|
|
182
|
+
frame,
|
|
183
|
+
bars,
|
|
184
|
+
barRadius,
|
|
185
|
+
candlestick,
|
|
186
|
+
candleColors,
|
|
187
|
+
pointKeys,
|
|
188
|
+
orientation,
|
|
189
|
+
}),
|
|
190
|
+
[
|
|
191
|
+
series,
|
|
192
|
+
data,
|
|
193
|
+
xKey,
|
|
194
|
+
formatXValue,
|
|
195
|
+
size,
|
|
196
|
+
slots,
|
|
197
|
+
gridColor,
|
|
198
|
+
axisColor,
|
|
199
|
+
surfaceColor,
|
|
200
|
+
scrub,
|
|
201
|
+
frame,
|
|
202
|
+
bars,
|
|
203
|
+
barRadius,
|
|
204
|
+
candlestick,
|
|
205
|
+
candleColors,
|
|
206
|
+
pointKeys,
|
|
207
|
+
orientation,
|
|
208
|
+
]
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
const onLayout = (event: LayoutChangeEvent): void => {
|
|
212
|
+
const { width, height } = event.nativeEvent.layout;
|
|
213
|
+
setFrame((current) => (current.width === width && current.height === height ? current : { width, height }));
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
// A bar or a candle owns a band; the tooltip's band and the category tick
|
|
217
|
+
// count key on that. What the domain needs — zero pulled in for bars, the
|
|
218
|
+
// data's own extent for candles, half a step of x for anything centred on
|
|
219
|
+
// its datum — is `resolveDomainDefaults`' call.
|
|
220
|
+
const hasBands = bars.mode !== "none" || candlestick !== null;
|
|
221
|
+
const domainDefaults = resolveDomainDefaults({ bars, candlestick, scatter, includeZero, domain });
|
|
222
|
+
|
|
223
|
+
// The category axis labels every bar; the value axis keeps the size's
|
|
224
|
+
// budget. Which axis is which follows the orientation — the engine puts
|
|
225
|
+
// the categories on y when horizontal.
|
|
226
|
+
const tickCount = chartTickCount(size);
|
|
227
|
+
const categoryTickCount = resolveCategoryTickCount(size, categoryCount, hasBands);
|
|
228
|
+
const xTickCount = orientation === "horizontal" ? tickCount : categoryTickCount;
|
|
229
|
+
const yTickCount = orientation === "horizontal" ? categoryTickCount : tickCount;
|
|
230
|
+
|
|
231
|
+
return (
|
|
232
|
+
<ChartProvider value={value}>
|
|
233
|
+
<View className={slots.root({ className })}>
|
|
234
|
+
<View className={slots.frame()} onLayout={onLayout} testID={testID}>
|
|
235
|
+
<CartesianChart
|
|
236
|
+
curve={curve}
|
|
237
|
+
data={data}
|
|
238
|
+
domain={domain}
|
|
239
|
+
domainPadding={domainDefaults.domainPadding}
|
|
240
|
+
font={font}
|
|
241
|
+
includeZero={domainDefaults.includeZero}
|
|
242
|
+
niceDomain
|
|
243
|
+
orientation={orientation}
|
|
244
|
+
scrub={scrub}
|
|
245
|
+
scrubConfig={scrubConfig}
|
|
246
|
+
stackKeys={stackKeys.length === 0 ? undefined : stackKeys}
|
|
247
|
+
xAxis={{ show: axes.x, tickCount: xTickCount }}
|
|
248
|
+
xKey={xKey}
|
|
249
|
+
yAxis={{ show: axes.y, tickCount: yTickCount }}
|
|
250
|
+
yKeys={keys}
|
|
251
|
+
>
|
|
252
|
+
<ChartProvider value={value}>{canvas}</ChartProvider>
|
|
253
|
+
</CartesianChart>
|
|
254
|
+
{overlay.canvasOverlay}
|
|
255
|
+
</View>
|
|
256
|
+
{overlay.below}
|
|
257
|
+
</View>
|
|
258
|
+
</ChartProvider>
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Splits the children by where they have to be mounted.
|
|
264
|
+
*
|
|
265
|
+
* Marks go inside the Skia canvas; a tooltip is absolutely positioned over it;
|
|
266
|
+
* a legend sits under it. The caller writes all three as siblings because that
|
|
267
|
+
* is how a chart reads, and the root puts each where it belongs.
|
|
268
|
+
*
|
|
269
|
+
* It also reports which axes were placed, and that is not bookkeeping: an axis
|
|
270
|
+
* gutter is reserved from the *measured width of its labels*, so a chart with
|
|
271
|
+
* no `Chart.YAxis` would otherwise inset its plot by the width of labels it
|
|
272
|
+
* never draws. On a chart inside a card that reads as the plot failing to line
|
|
273
|
+
* up with the text above it, with nothing on screen to explain the gap.
|
|
274
|
+
*
|
|
275
|
+
* The bars are the other thing it has to see whole. A `Chart.Bar` cannot
|
|
276
|
+
* know how many siblings share its step, so every bar's props are collected,
|
|
277
|
+
* resolved into one layout, and drawn by a single internal `ChartBars`
|
|
278
|
+
* spliced in where the first bar stood — the later ones are dropped from the
|
|
279
|
+
* canvas list. Stacked areas and the candlestick's field names are read the
|
|
280
|
+
* same way, because the engine's `stackKeys` and `yKeys` are root props.
|
|
281
|
+
*
|
|
282
|
+
* Matching is by component identity, the same technique `Spinner` uses on its
|
|
283
|
+
* own children. The limit that comes with it: a part has to be a **direct**
|
|
284
|
+
* child of `<Chart>`. An array from `.map()` is fine — `Children.toArray`
|
|
285
|
+
* flattens it — but a mark wrapped in a caller's own component is not, because
|
|
286
|
+
* there is nothing to match against until it renders.
|
|
287
|
+
*/
|
|
288
|
+
function partitionChildren(children: ReactNode): ChartPartition {
|
|
289
|
+
const collected: PartitionState = {
|
|
290
|
+
canvas: [],
|
|
291
|
+
canvasOverlay: [],
|
|
292
|
+
below: [],
|
|
293
|
+
axes: { x: false, y: false },
|
|
294
|
+
barProps: [],
|
|
295
|
+
areaSpecs: [],
|
|
296
|
+
pointKeys: [],
|
|
297
|
+
candlestick: null,
|
|
298
|
+
barsAt: -1,
|
|
299
|
+
scatter: false,
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
for (const child of Children.toArray(children)) {
|
|
303
|
+
if (isValidElement(child)) collectChild(child, collected);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const { canvas, canvasOverlay, below, axes, barProps, areaSpecs, pointKeys, candlestick, barsAt, scatter } =
|
|
307
|
+
collected;
|
|
308
|
+
const bars = resolveBarLayout(barProps.map(barSpecOf));
|
|
309
|
+
if (barsAt !== -1) canvas.splice(barsAt, 0, <ChartBars bars={barProps} key="bars" />);
|
|
310
|
+
|
|
311
|
+
return {
|
|
312
|
+
canvas,
|
|
313
|
+
overlay: { canvasOverlay, below },
|
|
314
|
+
axes,
|
|
315
|
+
bars,
|
|
316
|
+
stackedAreaKeys: resolveStackedAreaKeys(areaSpecs, bars),
|
|
317
|
+
candlestick,
|
|
318
|
+
pointKeys,
|
|
319
|
+
scatter,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
type ChartPartition = {
|
|
324
|
+
canvas: ReactNode[];
|
|
325
|
+
overlay: { canvasOverlay: ReactNode[]; below: ReactNode[] };
|
|
326
|
+
axes: { x: boolean; y: boolean };
|
|
327
|
+
bars: ChartBarLayout;
|
|
328
|
+
stackedAreaKeys: readonly string[];
|
|
329
|
+
candlestick: ChartCandlestickKeys | null;
|
|
330
|
+
pointKeys: readonly string[];
|
|
331
|
+
/** Whether any `Chart.Scatter` is placed — its outermost dots need the x padding bars get. */
|
|
332
|
+
scatter: boolean;
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
type PartitionState = {
|
|
336
|
+
canvas: ReactNode[];
|
|
337
|
+
canvasOverlay: ReactNode[];
|
|
338
|
+
below: ReactNode[];
|
|
339
|
+
axes: { x: boolean; y: boolean };
|
|
340
|
+
barProps: ChartBarProps[];
|
|
341
|
+
areaSpecs: ChartAreaSpec[];
|
|
342
|
+
pointKeys: string[];
|
|
343
|
+
candlestick: ChartCandlestickKeys | null;
|
|
344
|
+
barsAt: number;
|
|
345
|
+
scatter: boolean;
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
/** Files one child by identity. A bar is recorded and not placed — `ChartBars` stands in for all of them. */
|
|
349
|
+
function collectChild(child: ReactElement, state: PartitionState): void {
|
|
350
|
+
if (child.type === ChartXAxis) state.axes.x = true;
|
|
351
|
+
if (child.type === ChartYAxis) state.axes.y = true;
|
|
352
|
+
|
|
353
|
+
if (child.type === ChartBar) {
|
|
354
|
+
state.barProps.push(child.props as ChartBarProps);
|
|
355
|
+
if (state.barsAt === -1) state.barsAt = state.canvas.length;
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
if (child.type === ChartArea) state.areaSpecs.push(child.props as ChartAreaProps);
|
|
359
|
+
if (child.type === ChartCandlestick && state.candlestick === null) {
|
|
360
|
+
state.candlestick = candlestickKeysOf(child.props as ChartCandlestickProps);
|
|
361
|
+
}
|
|
362
|
+
if (child.type === ChartScatter) state.scatter = true;
|
|
363
|
+
if (child.type === ChartLine || child.type === ChartArea || child.type === ChartScatter) {
|
|
364
|
+
state.pointKeys.push((child.props as ChartLineProps | ChartAreaProps | ChartScatterProps).yKey);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (CANVAS_PARTS.has(child.type)) state.canvas.push(child);
|
|
368
|
+
else if (child.type === ChartTooltip) state.canvasOverlay.push(child);
|
|
369
|
+
else state.below.push(child);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/** The part of a bar's props the layout reads. */
|
|
373
|
+
function barSpecOf(props: ChartBarProps): ChartBarSpec {
|
|
374
|
+
return props.stackId === undefined ? { yKey: props.yKey } : { yKey: props.yKey, stackId: props.stackId };
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Skia charts, wearing the theme's five-colour series ramp.
|
|
379
|
+
*
|
|
380
|
+
* The `config` is the chart: it names each series, decides the draw order, and
|
|
381
|
+
* assigns the ramp by position, so `<Chart.Line yKey="desktop" />` names the
|
|
382
|
+
* data and never the paint. Its shape is shadcn's on purpose — a chart config
|
|
383
|
+
* written for a web dashboard moves across as a copy rather than a translation.
|
|
384
|
+
*
|
|
385
|
+
* Parts are placed, not configured. `Chart.Grid`, `Chart.Line`, `Chart.Area`,
|
|
386
|
+
* `Chart.Bar`, `Chart.Scatter`, `Chart.Candlestick`, `Chart.XAxis` and
|
|
387
|
+
* `Chart.YAxis` draw into the canvas; `Chart.Tooltip` floats over it and
|
|
388
|
+
* `Chart.Legend` sits beneath, both as ordinary React Native views so they can
|
|
389
|
+
* carry the type scale and a colour token. Sibling bars group; bars sharing a
|
|
390
|
+
* `stackId` stack, and so do areas.
|
|
391
|
+
*
|
|
392
|
+
* A colour may be a theme token or a literal — `lib/color`'s `isLiteralColor`
|
|
393
|
+
* tells them apart, so `color: "#EC4899"` works beside `color: "chart-4"` with
|
|
394
|
+
* no special-casing at the call site. At most eight *distinct* tokens can be
|
|
395
|
+
* resolved in one render; past that, give the extras literals.
|
|
396
|
+
*
|
|
397
|
+
* Touch is opt-in through the parts: without a `Chart.Tooltip` the chart takes
|
|
398
|
+
* a scrub but nothing shows it. The scrub activates on a hold by default, so a
|
|
399
|
+
* chart inside a scrolling list does not steal the scroll.
|
|
400
|
+
*
|
|
401
|
+
* @example
|
|
402
|
+
* <Chart config={{ revenue: { label: "Revenue" } }} data={rows} xKey="month">
|
|
403
|
+
* <Chart.Grid />
|
|
404
|
+
* <Chart.YAxis />
|
|
405
|
+
* <Chart.XAxis />
|
|
406
|
+
* <Chart.Area yKey="revenue" />
|
|
407
|
+
* <Chart.Line yKey="revenue" />
|
|
408
|
+
* </Chart>
|
|
409
|
+
*
|
|
410
|
+
* @example
|
|
411
|
+
* <Chart config={config} data={rows} size="lg" xKey="month">
|
|
412
|
+
* <Chart.Line yKey="desktop" />
|
|
413
|
+
* <Chart.Line yKey="mobile" />
|
|
414
|
+
* <Chart.Tooltip />
|
|
415
|
+
* <Chart.Legend />
|
|
416
|
+
* </Chart>
|
|
417
|
+
*
|
|
418
|
+
* @example
|
|
419
|
+
* <Chart config={config} data={rows} xKey="month">
|
|
420
|
+
* <Chart.Bar stackId="traffic" yKey="desktop" />
|
|
421
|
+
* <Chart.Bar stackId="traffic" yKey="mobile" />
|
|
422
|
+
* <Chart.Tooltip.X />
|
|
423
|
+
* <Chart.Tooltip />
|
|
424
|
+
* </Chart>
|
|
425
|
+
*/
|
|
426
|
+
export const Chart = Object.assign(ChartRoot, {
|
|
427
|
+
/** A hairline rule at each tick, painted with `border`. Draws first, under every mark. */
|
|
428
|
+
Grid: ChartGrid,
|
|
429
|
+
/** A stroked line through one series, in that series' colour. */
|
|
430
|
+
Line: ChartLine,
|
|
431
|
+
/** The region under one series, fading out towards the baseline. `stackId` stacks it. */
|
|
432
|
+
Area: ChartArea,
|
|
433
|
+
/** One bar per datum. Siblings group; a shared `stackId` stacks. */
|
|
434
|
+
Bar: ChartBar,
|
|
435
|
+
/** One dot per datum, in the series' colour. */
|
|
436
|
+
Scatter: ChartScatter,
|
|
437
|
+
/** Open-high-low-close candles in `chart-1`, `chart-2` and `muted-foreground`. */
|
|
438
|
+
Candlestick: ChartCandlestick,
|
|
439
|
+
/** Tick labels below the plot, in `muted-foreground`. */
|
|
440
|
+
XAxis: ChartXAxis,
|
|
441
|
+
/** Tick labels beside the plot, right-aligned into a column. */
|
|
442
|
+
YAxis: ChartYAxis,
|
|
443
|
+
/** A floating readout that follows the scrub. A React Native view, over the canvas. */
|
|
444
|
+
Tooltip: ChartTooltip,
|
|
445
|
+
/** A swatch and a label per series, under the chart. */
|
|
446
|
+
Legend: ChartLegend,
|
|
447
|
+
displayName: "DelacourUI.Chart",
|
|
448
|
+
});
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type { ChartRow, Sentiment } from "@delacour/react-native-charts/core";
|
|
2
|
+
|
|
3
|
+
/** One series' name and colour. */
|
|
4
|
+
export type ChartSeriesConfig = {
|
|
5
|
+
/** Shown in the legend and the tooltip. */
|
|
6
|
+
label: string;
|
|
7
|
+
/**
|
|
8
|
+
* A theme token (`chart-3`, `primary`) or a literal (`#EC4899`).
|
|
9
|
+
*
|
|
10
|
+
* Defaults to this series' position on the ramp, so the first series in the
|
|
11
|
+
* config is `chart-1` and nothing has to be named at all.
|
|
12
|
+
*/
|
|
13
|
+
color?: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The chart's series, keyed by the data field each reads.
|
|
18
|
+
*
|
|
19
|
+
* Key order is draw order and ramp order both, so reordering the object
|
|
20
|
+
* reorders the chart. This is shadcn's shape deliberately — a config written
|
|
21
|
+
* for a web dashboard moves across as a copy rather than a translation.
|
|
22
|
+
*/
|
|
23
|
+
export type ChartConfig = Record<string, ChartSeriesConfig>;
|
|
24
|
+
|
|
25
|
+
/** A series after its colour has been decided and resolved. */
|
|
26
|
+
export type ChartResolvedSeries = {
|
|
27
|
+
readonly key: string;
|
|
28
|
+
readonly label: string;
|
|
29
|
+
/** A colour value by the time a mark sees it, never a token name. */
|
|
30
|
+
readonly color: string;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/** A row of the caller's data. */
|
|
34
|
+
export type ChartDatum = ChartRow;
|
|
35
|
+
|
|
36
|
+
/** What `chartTooltipOffset` needs to place a tooltip inside its frame. */
|
|
37
|
+
export type ChartTooltipInput = {
|
|
38
|
+
/** The cursor, in frame coordinates. */
|
|
39
|
+
readonly x: number;
|
|
40
|
+
readonly y: number;
|
|
41
|
+
readonly width: number;
|
|
42
|
+
readonly height: number;
|
|
43
|
+
readonly frameWidth: number;
|
|
44
|
+
readonly frameHeight: number;
|
|
45
|
+
/** Space between the cursor and the tooltip. Defaults to 12. */
|
|
46
|
+
readonly gap?: number;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/** What one `Chart.Bar` declares about where it stands, before any bar is laid out. */
|
|
50
|
+
export type ChartBarSpec = {
|
|
51
|
+
readonly yKey: string;
|
|
52
|
+
/** Bars sharing a stack id stack on one another; bars without one group side by side. */
|
|
53
|
+
readonly stackId?: string;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export type ChartBarLayoutMode = "none" | "single" | "grouped" | "stacked";
|
|
57
|
+
|
|
58
|
+
/** Where one bar series sits in the layout. */
|
|
59
|
+
export type ChartBarSlot = {
|
|
60
|
+
/** Its position among the side-by-side bars of one step. Zero in a stack. */
|
|
61
|
+
readonly groupIndex: number;
|
|
62
|
+
/** How many bars share one step. One in a stack. */
|
|
63
|
+
readonly groupCount: number;
|
|
64
|
+
/** Its position from the bottom of the stack. Absent unless stacked. */
|
|
65
|
+
readonly stackIndex?: number;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Every `Chart.Bar` in the chart, resolved into one arrangement.
|
|
70
|
+
*
|
|
71
|
+
* Bars group by being siblings and stack by sharing a `stackId`, so the root
|
|
72
|
+
* has to see them all before any of them can know its width or its base. One
|
|
73
|
+
* stack per chart: the layout is either all stacked or all side by side.
|
|
74
|
+
*/
|
|
75
|
+
export type ChartBarLayout = {
|
|
76
|
+
readonly mode: ChartBarLayoutMode;
|
|
77
|
+
/** The bar series, in the order they were placed — bottom first for a stack. */
|
|
78
|
+
readonly keys: readonly string[];
|
|
79
|
+
readonly stackId?: string;
|
|
80
|
+
readonly slotOf: Readonly<Record<string, ChartBarSlot>>;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/** The four fields a candlestick reads from each row. */
|
|
84
|
+
export type ChartCandlestickKeys = {
|
|
85
|
+
readonly open: string;
|
|
86
|
+
readonly high: string;
|
|
87
|
+
readonly low: string;
|
|
88
|
+
readonly close: string;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/** One resolved colour per candle sentiment. */
|
|
92
|
+
export type ChartCandleColors = Readonly<Record<Sentiment, string>>;
|
|
93
|
+
|
|
94
|
+
/** What one `Chart.Area` declares about stacking. */
|
|
95
|
+
export type ChartAreaSpec = {
|
|
96
|
+
readonly yKey: string;
|
|
97
|
+
readonly stackId?: string;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/** One line of the tooltip's readout. */
|
|
101
|
+
export type ChartTooltipRow = {
|
|
102
|
+
readonly key: string;
|
|
103
|
+
readonly label: string;
|
|
104
|
+
/** Resolved, or `undefined` when the theme emits nothing for it. */
|
|
105
|
+
readonly color: string | undefined;
|
|
106
|
+
/** The row's value as written, for the caller's formatter. */
|
|
107
|
+
readonly value: unknown;
|
|
108
|
+
};
|