panelui-native 0.71.0 → 0.75.0
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 +5 -2
- package/lib/module/components/accordion/index.js +10 -4
- package/lib/module/components/accordion/index.js.map +1 -1
- package/lib/module/components/area-chart/index.js +1 -0
- package/lib/module/components/area-chart/index.js.map +1 -1
- package/lib/module/components/avatar/avatar-group.js +64 -0
- package/lib/module/components/avatar/avatar-group.js.map +1 -0
- package/lib/module/components/avatar/index.js +84 -2
- package/lib/module/components/avatar/index.js.map +1 -1
- package/lib/module/components/breadcrumb/index.js +2 -1
- package/lib/module/components/breadcrumb/index.js.map +1 -1
- package/lib/module/components/button/index.js +7 -5
- package/lib/module/components/button/index.js.map +1 -1
- package/lib/module/components/carousel/index.js +1 -1
- package/lib/module/components/carousel/index.js.map +1 -1
- package/lib/module/components/chip/index.js +2 -2
- package/lib/module/components/chip/index.js.map +1 -1
- package/lib/module/components/code-block/index.js.map +1 -1
- package/lib/module/components/collapsible/index.js +5 -1
- package/lib/module/components/collapsible/index.js.map +1 -1
- package/lib/module/components/color-picker/color-picker-accessibility.js +12 -0
- package/lib/module/components/color-picker/color-picker-accessibility.js.map +1 -0
- package/lib/module/components/color-picker/index.js +9 -5
- package/lib/module/components/color-picker/index.js.map +1 -1
- package/lib/module/components/date-time-picker/index.js +11 -3
- package/lib/module/components/date-time-picker/index.js.map +1 -1
- package/lib/module/components/drawer/index.js +58 -13
- package/lib/module/components/drawer/index.js.map +1 -1
- package/lib/module/components/fab/index.js +7 -6
- package/lib/module/components/fab/index.js.map +1 -1
- package/lib/module/components/flow/index.js +1 -1
- package/lib/module/components/flow/index.js.map +1 -1
- package/lib/module/components/frame/index.js +2 -2
- package/lib/module/components/frame/index.js.map +1 -1
- package/lib/module/components/grid-item/index.js +2 -2
- package/lib/module/components/grid-item/index.js.map +1 -1
- package/lib/module/components/heatmap-chart/index.js +1 -0
- package/lib/module/components/heatmap-chart/index.js.map +1 -1
- package/lib/module/components/hex-chart/index.js +1 -0
- package/lib/module/components/hex-chart/index.js.map +1 -1
- package/lib/module/components/input/index.js +7 -4
- package/lib/module/components/input/index.js.map +1 -1
- package/lib/module/components/input/input-content-padding.js +6 -0
- package/lib/module/components/input/input-content-padding.js.map +1 -0
- package/lib/module/components/input-group/index.js +21 -11
- package/lib/module/components/input-group/index.js.map +1 -1
- package/lib/module/components/input-group/input-group-measurements.js +60 -0
- package/lib/module/components/input-group/input-group-measurements.js.map +1 -0
- package/lib/module/components/item/index.js +2 -2
- package/lib/module/components/item/index.js.map +1 -1
- package/lib/module/components/line-chart/index.js +1 -0
- package/lib/module/components/line-chart/index.js.map +1 -1
- package/lib/module/components/marker/index.js +2 -2
- package/lib/module/components/marker/index.js.map +1 -1
- package/lib/module/components/marquee/index.js +9 -12
- package/lib/module/components/marquee/index.js.map +1 -1
- package/lib/module/components/marquee/marquee-math.js +39 -0
- package/lib/module/components/marquee/marquee-math.js.map +1 -0
- package/lib/module/components/menu/index.js +11 -3
- package/lib/module/components/menu/index.js.map +1 -1
- package/lib/module/components/otp-input/index.js +3 -1
- package/lib/module/components/otp-input/index.js.map +1 -1
- package/lib/module/components/otp-input/otp-input-state.js +7 -0
- package/lib/module/components/otp-input/otp-input-state.js.map +1 -0
- package/lib/module/components/pagination/index.js +27 -11
- package/lib/module/components/pagination/index.js.map +1 -1
- package/lib/module/components/panelside/index.js +7 -2
- package/lib/module/components/panelside/index.js.map +1 -1
- package/lib/module/components/planner/index.js +46 -11
- package/lib/module/components/planner/index.js.map +1 -1
- package/lib/module/components/planner/planner-today.js +8 -0
- package/lib/module/components/planner/planner-today.js.map +1 -0
- package/lib/module/components/plot/index.js +113 -53
- package/lib/module/components/plot/index.js.map +1 -1
- package/lib/module/components/plot/plot-series-registry.js +32 -0
- package/lib/module/components/plot/plot-series-registry.js.map +1 -0
- package/lib/module/components/post/index.js +4 -4
- package/lib/module/components/post/index.js.map +1 -1
- package/lib/module/components/radio-group/index.js +2 -1
- package/lib/module/components/radio-group/index.js.map +1 -1
- package/lib/module/components/radio-group/radio-group-value.js +8 -0
- package/lib/module/components/radio-group/radio-group-value.js.map +1 -0
- package/lib/module/components/rating/index.js +7 -4
- package/lib/module/components/rating/index.js.map +1 -1
- package/lib/module/components/rating/rating-inputs.js +15 -0
- package/lib/module/components/rating/rating-inputs.js.map +1 -0
- package/lib/module/components/search-bar/index.js +331 -0
- package/lib/module/components/search-bar/index.js.map +1 -0
- package/lib/module/components/search-bar/search-bar-debounce.js +23 -0
- package/lib/module/components/search-bar/search-bar-debounce.js.map +1 -0
- package/lib/module/components/select/index.js +9 -1
- package/lib/module/components/select/index.js.map +1 -1
- package/lib/module/components/select/native-select-contract.js +11 -0
- package/lib/module/components/select/native-select-contract.js.map +1 -0
- package/lib/module/components/split-view/index.js +439 -0
- package/lib/module/components/split-view/index.js.map +1 -0
- package/lib/module/components/split-view/split-view-math.js +132 -0
- package/lib/module/components/split-view/split-view-math.js.map +1 -0
- package/lib/module/components/splitter/index.js +433 -0
- package/lib/module/components/splitter/index.js.map +1 -0
- package/lib/module/components/splitter/splitter-math.js +206 -0
- package/lib/module/components/splitter/splitter-math.js.map +1 -0
- package/lib/module/components/steps/index.js +16 -5
- package/lib/module/components/steps/index.js.map +1 -1
- package/lib/module/components/steps/steps-trigger.js +6 -0
- package/lib/module/components/steps/steps-trigger.js.map +1 -0
- package/lib/module/components/table/index.js +3 -4
- package/lib/module/components/table/index.js.map +1 -1
- package/lib/module/components/tag-input/index.js +15 -10
- package/lib/module/components/tag-input/index.js.map +1 -1
- package/lib/module/components/tag-input/tag-input-state.js +14 -0
- package/lib/module/components/tag-input/tag-input-state.js.map +1 -0
- package/lib/module/components/textarea/index.js +9 -4
- package/lib/module/components/textarea/index.js.map +1 -1
- package/lib/module/components/textarea/textarea-disabled.js +6 -0
- package/lib/module/components/textarea/textarea-disabled.js.map +1 -0
- package/lib/module/components/timeline/index.js +352 -47
- package/lib/module/components/timeline/index.js.map +1 -1
- package/lib/module/components/timeline/timeline-geometry.js +11 -0
- package/lib/module/components/timeline/timeline-geometry.js.map +1 -0
- package/lib/module/components/toast/index.js +18 -1
- package/lib/module/components/toast/index.js.map +1 -1
- package/lib/module/components/toast/toast-store.js +36 -4
- package/lib/module/components/toast/toast-store.js.map +1 -1
- package/lib/module/components/toggle-button/index.js +1 -1
- package/lib/module/components/toggle-button/index.js.map +1 -1
- package/lib/module/components/tour/index.js +4 -2
- package/lib/module/components/tour/index.js.map +1 -1
- package/lib/module/components/tour/tour-card-measurement.js +24 -0
- package/lib/module/components/tour/tour-card-measurement.js.map +1 -0
- package/lib/module/components/tree/index.js +13 -8
- package/lib/module/components/tree/index.js.map +1 -1
- package/lib/module/hooks/use-keyboard-avoidance.js.map +1 -1
- package/lib/module/index.js +3 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/utils/chart.js +50 -7
- package/lib/module/utils/chart.js.map +1 -1
- package/lib/typescript/src/components/accordion/index.d.ts +2 -2
- package/lib/typescript/src/components/accordion/index.d.ts.map +1 -1
- package/lib/typescript/src/components/area-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/components/avatar/avatar-group.d.ts +46 -0
- package/lib/typescript/src/components/avatar/avatar-group.d.ts.map +1 -0
- package/lib/typescript/src/components/avatar/index.d.ts +23 -1
- package/lib/typescript/src/components/avatar/index.d.ts.map +1 -1
- package/lib/typescript/src/components/breadcrumb/index.d.ts.map +1 -1
- package/lib/typescript/src/components/button/index.d.ts.map +1 -1
- package/lib/typescript/src/components/code-block/index.d.ts +3 -3
- package/lib/typescript/src/components/code-block/index.d.ts.map +1 -1
- package/lib/typescript/src/components/collapsible/index.d.ts +2 -2
- package/lib/typescript/src/components/collapsible/index.d.ts.map +1 -1
- package/lib/typescript/src/components/color-picker/color-picker-accessibility.d.ts +8 -0
- package/lib/typescript/src/components/color-picker/color-picker-accessibility.d.ts.map +1 -0
- package/lib/typescript/src/components/color-picker/index.d.ts.map +1 -1
- package/lib/typescript/src/components/date-time-picker/index.d.ts.map +1 -1
- package/lib/typescript/src/components/drawer/index.d.ts.map +1 -1
- package/lib/typescript/src/components/fab/index.d.ts +4 -2
- package/lib/typescript/src/components/fab/index.d.ts.map +1 -1
- package/lib/typescript/src/components/heatmap-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/components/hex-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/components/input/index.d.ts.map +1 -1
- package/lib/typescript/src/components/input/input-content-padding.d.ts +2 -0
- package/lib/typescript/src/components/input/input-content-padding.d.ts.map +1 -0
- package/lib/typescript/src/components/input-group/index.d.ts.map +1 -1
- package/lib/typescript/src/components/input-group/input-group-measurements.d.ts +16 -0
- package/lib/typescript/src/components/input-group/input-group-measurements.d.ts.map +1 -0
- package/lib/typescript/src/components/line-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/components/marquee/index.d.ts +1 -1
- package/lib/typescript/src/components/marquee/index.d.ts.map +1 -1
- package/lib/typescript/src/components/marquee/marquee-math.d.ts +21 -0
- package/lib/typescript/src/components/marquee/marquee-math.d.ts.map +1 -0
- package/lib/typescript/src/components/menu/index.d.ts +1 -1
- package/lib/typescript/src/components/menu/index.d.ts.map +1 -1
- package/lib/typescript/src/components/otp-input/index.d.ts.map +1 -1
- package/lib/typescript/src/components/otp-input/otp-input-state.d.ts +3 -0
- package/lib/typescript/src/components/otp-input/otp-input-state.d.ts.map +1 -0
- package/lib/typescript/src/components/pagination/index.d.ts.map +1 -1
- package/lib/typescript/src/components/panelside/index.d.ts +1 -1
- package/lib/typescript/src/components/panelside/index.d.ts.map +1 -1
- package/lib/typescript/src/components/plan/index.d.ts +2 -2
- package/lib/typescript/src/components/planner/index.d.ts.map +1 -1
- package/lib/typescript/src/components/planner/planner-today.d.ts +2 -0
- package/lib/typescript/src/components/planner/planner-today.d.ts.map +1 -0
- package/lib/typescript/src/components/plot/index.d.ts +60 -6
- package/lib/typescript/src/components/plot/index.d.ts.map +1 -1
- package/lib/typescript/src/components/plot/plot-series-registry.d.ts +8 -0
- package/lib/typescript/src/components/plot/plot-series-registry.d.ts.map +1 -0
- package/lib/typescript/src/components/radio-group/index.d.ts.map +1 -1
- package/lib/typescript/src/components/radio-group/radio-group-value.d.ts +2 -0
- package/lib/typescript/src/components/radio-group/radio-group-value.d.ts.map +1 -0
- package/lib/typescript/src/components/rating/index.d.ts.map +1 -1
- package/lib/typescript/src/components/rating/rating-inputs.d.ts +4 -0
- package/lib/typescript/src/components/rating/rating-inputs.d.ts.map +1 -0
- package/lib/typescript/src/components/search-bar/index.d.ts +216 -0
- package/lib/typescript/src/components/search-bar/index.d.ts.map +1 -0
- package/lib/typescript/src/components/search-bar/search-bar-debounce.d.ts +7 -0
- package/lib/typescript/src/components/search-bar/search-bar-debounce.d.ts.map +1 -0
- package/lib/typescript/src/components/select/index.d.ts.map +1 -1
- package/lib/typescript/src/components/select/native-select-contract.d.ts +11 -0
- package/lib/typescript/src/components/select/native-select-contract.d.ts.map +1 -0
- package/lib/typescript/src/components/split-view/index.d.ts +159 -0
- package/lib/typescript/src/components/split-view/index.d.ts.map +1 -0
- package/lib/typescript/src/components/split-view/split-view-math.d.ts +54 -0
- package/lib/typescript/src/components/split-view/split-view-math.d.ts.map +1 -0
- package/lib/typescript/src/components/splitter/index.d.ts +78 -0
- package/lib/typescript/src/components/splitter/index.d.ts.map +1 -0
- package/lib/typescript/src/components/splitter/splitter-math.d.ts +67 -0
- package/lib/typescript/src/components/splitter/splitter-math.d.ts.map +1 -0
- package/lib/typescript/src/components/steps/index.d.ts +2 -2
- package/lib/typescript/src/components/steps/index.d.ts.map +1 -1
- package/lib/typescript/src/components/steps/steps-trigger.d.ts +2 -0
- package/lib/typescript/src/components/steps/steps-trigger.d.ts.map +1 -0
- package/lib/typescript/src/components/tag-input/index.d.ts.map +1 -1
- package/lib/typescript/src/components/tag-input/tag-input-state.d.ts +12 -0
- package/lib/typescript/src/components/tag-input/tag-input-state.d.ts.map +1 -0
- package/lib/typescript/src/components/textarea/index.d.ts.map +1 -1
- package/lib/typescript/src/components/textarea/textarea-disabled.d.ts +2 -0
- package/lib/typescript/src/components/textarea/textarea-disabled.d.ts.map +1 -0
- package/lib/typescript/src/components/timeline/index.d.ts +36 -2
- package/lib/typescript/src/components/timeline/index.d.ts.map +1 -1
- package/lib/typescript/src/components/timeline/timeline-geometry.d.ts +4 -0
- package/lib/typescript/src/components/timeline/timeline-geometry.d.ts.map +1 -0
- package/lib/typescript/src/components/toast/index.d.ts.map +1 -1
- package/lib/typescript/src/components/toast/toast-store.d.ts +7 -2
- package/lib/typescript/src/components/toast/toast-store.d.ts.map +1 -1
- package/lib/typescript/src/components/tour/index.d.ts.map +1 -1
- package/lib/typescript/src/components/tour/tour-card-measurement.d.ts +14 -0
- package/lib/typescript/src/components/tour/tour-card-measurement.d.ts.map +1 -0
- package/lib/typescript/src/components/tree/index.d.ts +3 -4
- package/lib/typescript/src/components/tree/index.d.ts.map +1 -1
- package/lib/typescript/src/hooks/use-keyboard-avoidance.d.ts +2 -2
- package/lib/typescript/src/hooks/use-keyboard-avoidance.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +5 -2
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/utils/chart.d.ts +28 -3
- package/lib/typescript/src/utils/chart.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/accordion/index.tsx +17 -7
- package/src/components/area-chart/index.tsx +8 -0
- package/src/components/avatar/avatar-group.ts +78 -0
- package/src/components/avatar/index.tsx +121 -1
- package/src/components/breadcrumb/index.tsx +2 -1
- package/src/components/button/index.tsx +3 -2
- package/src/components/carousel/index.tsx +1 -1
- package/src/components/chip/index.tsx +2 -2
- package/src/components/code-block/index.tsx +3 -3
- package/src/components/collapsible/index.tsx +13 -4
- package/src/components/color-picker/color-picker-accessibility.ts +12 -0
- package/src/components/color-picker/index.tsx +30 -10
- package/src/components/date-time-picker/index.tsx +11 -3
- package/src/components/drawer/index.tsx +42 -14
- package/src/components/fab/index.tsx +21 -11
- package/src/components/flow/index.tsx +1 -1
- package/src/components/frame/index.tsx +2 -2
- package/src/components/grid-item/index.tsx +2 -2
- package/src/components/heatmap-chart/index.tsx +7 -1
- package/src/components/hex-chart/index.tsx +7 -1
- package/src/components/input/index.tsx +6 -2
- package/src/components/input/input-content-padding.ts +3 -0
- package/src/components/input-group/index.tsx +45 -12
- package/src/components/input-group/input-group-measurements.ts +98 -0
- package/src/components/item/index.tsx +2 -2
- package/src/components/line-chart/index.tsx +9 -0
- package/src/components/marker/index.tsx +2 -2
- package/src/components/marquee/index.tsx +14 -12
- package/src/components/marquee/marquee-math.ts +37 -0
- package/src/components/menu/index.tsx +11 -3
- package/src/components/otp-input/index.tsx +3 -1
- package/src/components/otp-input/otp-input-state.ts +7 -0
- package/src/components/pagination/index.tsx +26 -13
- package/src/components/panelside/index.tsx +7 -2
- package/src/components/plan/index.tsx +2 -2
- package/src/components/planner/index.tsx +44 -11
- package/src/components/planner/planner-today.ts +5 -0
- package/src/components/plot/index.tsx +199 -51
- package/src/components/plot/plot-series-registry.ts +41 -0
- package/src/components/post/index.tsx +4 -4
- package/src/components/radio-group/index.tsx +4 -1
- package/src/components/radio-group/radio-group-value.ts +9 -0
- package/src/components/rating/index.tsx +11 -4
- package/src/components/rating/rating-inputs.ts +19 -0
- package/src/components/search-bar/index.tsx +441 -0
- package/src/components/search-bar/search-bar-debounce.ts +35 -0
- package/src/components/select/index.tsx +10 -1
- package/src/components/select/native-select-contract.ts +12 -0
- package/src/components/split-view/index.tsx +565 -0
- package/src/components/split-view/split-view-math.ts +146 -0
- package/src/components/splitter/index.tsx +589 -0
- package/src/components/splitter/splitter-math.ts +244 -0
- package/src/components/steps/index.tsx +35 -9
- package/src/components/steps/steps-trigger.ts +6 -0
- package/src/components/table/index.tsx +2 -2
- package/src/components/tag-input/index.tsx +13 -11
- package/src/components/tag-input/tag-input-state.ts +18 -0
- package/src/components/textarea/index.tsx +8 -4
- package/src/components/textarea/textarea-disabled.ts +6 -0
- package/src/components/timeline/index.tsx +405 -46
- package/src/components/timeline/timeline-geometry.ts +9 -0
- package/src/components/toast/index.tsx +26 -1
- package/src/components/toast/toast-store.ts +43 -8
- package/src/components/toggle-button/index.tsx +1 -1
- package/src/components/tour/index.tsx +11 -3
- package/src/components/tour/tour-card-measurement.ts +29 -0
- package/src/components/tree/index.tsx +17 -12
- package/src/hooks/use-keyboard-avoidance.ts +7 -2
- package/src/index.ts +19 -0
- package/src/utils/chart.ts +66 -7
|
@@ -105,12 +105,19 @@ import {
|
|
|
105
105
|
columnValues,
|
|
106
106
|
compactNumber,
|
|
107
107
|
linePath,
|
|
108
|
+
niceDomain,
|
|
108
109
|
useSeriesColor,
|
|
109
110
|
xOf,
|
|
110
111
|
yOf,
|
|
111
112
|
type Plot as PlotBox,
|
|
112
113
|
} from '../../utils/chart';
|
|
113
114
|
import { cn } from '../../utils/cn';
|
|
115
|
+
import {
|
|
116
|
+
registerPlotSeries,
|
|
117
|
+
unregisterPlotSeries,
|
|
118
|
+
visiblePlotSeries,
|
|
119
|
+
type PlotSeriesRegistration,
|
|
120
|
+
} from './plot-series-registry';
|
|
114
121
|
|
|
115
122
|
/*
|
|
116
123
|
* The scale functions, re-exported so a mark written outside this file can sit
|
|
@@ -213,7 +220,7 @@ export interface PlotGeometry {
|
|
|
213
220
|
/** Every mark that registered itself, as `[dataKey, colour]`. */
|
|
214
221
|
series: [string, string][];
|
|
215
222
|
registerSeries: (key: string, color: string) => void;
|
|
216
|
-
unregisterSeries: (key: string) => void;
|
|
223
|
+
unregisterSeries: (key: string, color?: string) => void;
|
|
217
224
|
/** Row under the cursor, or `-1`. On the UI thread. */
|
|
218
225
|
activeIndex: SharedValue<number>;
|
|
219
226
|
/** The same index on the JS thread, for anything that has to re-render. */
|
|
@@ -281,6 +288,17 @@ export interface PlotProps extends ViewProps {
|
|
|
281
288
|
* arrives.
|
|
282
289
|
*/
|
|
283
290
|
yDomain?: [PlotBound, PlotBound];
|
|
291
|
+
/**
|
|
292
|
+
* Round the derived ends of the y-domain out to whole numbers.
|
|
293
|
+
*
|
|
294
|
+
* Left off, an axis ends a tenth of the span past the largest value, so it
|
|
295
|
+
* gets labelled 34,650 — true, and not a number anybody was looking for. On,
|
|
296
|
+
* the ends move out to a step of 1, 2 or 5 times a power of ten, and the
|
|
297
|
+
* labels become values a reader can measure against.
|
|
298
|
+
*
|
|
299
|
+
* It only ever widens the axis, and a pinned end is left alone.
|
|
300
|
+
*/
|
|
301
|
+
nice?: boolean;
|
|
284
302
|
/**
|
|
285
303
|
* How an index becomes an x. Derived from the marks when left out: a plot
|
|
286
304
|
* with bars in it is banded, and anything else is on points.
|
|
@@ -317,6 +335,7 @@ const PlotRoot = forwardRef<PlotHandle, PlotProps>(function PlotRoot(
|
|
|
317
335
|
animationDuration = 700,
|
|
318
336
|
domainDuration = 500,
|
|
319
337
|
yDomain,
|
|
338
|
+
nice = false,
|
|
320
339
|
xScale: xScaleProp,
|
|
321
340
|
curve = 'monotone',
|
|
322
341
|
onActiveIndexChange,
|
|
@@ -327,7 +346,7 @@ const PlotRoot = forwardRef<PlotHandle, PlotProps>(function PlotRoot(
|
|
|
327
346
|
ref
|
|
328
347
|
) {
|
|
329
348
|
const [size, setSize] = useState({ width: 0, height: 0 });
|
|
330
|
-
const [
|
|
349
|
+
const [registrations, setRegistrations] = useState<PlotSeriesRegistration[]>([]);
|
|
331
350
|
const [activeIndexJS, setActiveIndexJS] = useState(-1);
|
|
332
351
|
const clipId = `panelui-plot-${useId().replace(/[^a-zA-Z0-9]/g, '')}`;
|
|
333
352
|
|
|
@@ -339,19 +358,18 @@ const PlotRoot = forwardRef<PlotHandle, PlotProps>(function PlotRoot(
|
|
|
339
358
|
|
|
340
359
|
const registerSeries = useMemo(
|
|
341
360
|
() => (key: string, color: string) =>
|
|
342
|
-
|
|
343
|
-
const existing = current.find(([k]) => k === key);
|
|
344
|
-
if (existing?.[1] === color) return current;
|
|
345
|
-
return [...current.filter(([k]) => k !== key), [key, color]];
|
|
346
|
-
}),
|
|
361
|
+
setRegistrations((current) => registerPlotSeries(current, key, color)),
|
|
347
362
|
[]
|
|
348
363
|
);
|
|
349
364
|
|
|
350
365
|
const unregisterSeries = useMemo(
|
|
351
|
-
() => (key: string
|
|
366
|
+
() => (key: string, color?: string) =>
|
|
367
|
+
setRegistrations((current) => unregisterPlotSeries(current, key, color)),
|
|
352
368
|
[]
|
|
353
369
|
);
|
|
354
370
|
|
|
371
|
+
const series = useMemo(() => visiblePlotSeries(registrations), [registrations]);
|
|
372
|
+
|
|
355
373
|
/*
|
|
356
374
|
* What the children need decided before anything is laid out.
|
|
357
375
|
*
|
|
@@ -391,7 +409,7 @@ const PlotRoot = forwardRef<PlotHandle, PlotProps>(function PlotRoot(
|
|
|
391
409
|
* magnitude apart look alike, which is the chart lying rather than the chart
|
|
392
410
|
* being convenient.
|
|
393
411
|
*/
|
|
394
|
-
const seriesKeys = series.map(([key]) => key)
|
|
412
|
+
const seriesKeys = useMemo(() => series.map(([key]) => key), [series]);
|
|
395
413
|
const extent = useMemo<[number, number]>(() => {
|
|
396
414
|
const lowPin = yDomain?.[0];
|
|
397
415
|
const highPin = yDomain?.[1];
|
|
@@ -399,11 +417,10 @@ const PlotRoot = forwardRef<PlotHandle, PlotProps>(function PlotRoot(
|
|
|
399
417
|
return [lowPin, highPin];
|
|
400
418
|
}
|
|
401
419
|
|
|
402
|
-
const keys = seriesKeys ? seriesKeys.split('|') : [];
|
|
403
420
|
let min = Infinity;
|
|
404
421
|
let max = -Infinity;
|
|
405
422
|
for (const row of data) {
|
|
406
|
-
for (const key of
|
|
423
|
+
for (const key of seriesKeys) {
|
|
407
424
|
const value = row[key];
|
|
408
425
|
if (typeof value !== 'number' || Number.isNaN(value)) continue;
|
|
409
426
|
if (value < min) min = value;
|
|
@@ -450,11 +467,20 @@ const PlotRoot = forwardRef<PlotHandle, PlotProps>(function PlotRoot(
|
|
|
450
467
|
* drifting off the axis is exactly what pinning it was for.
|
|
451
468
|
*/
|
|
452
469
|
const headroom = (max - min) * 0.1;
|
|
470
|
+
/*
|
|
471
|
+
* `nice` replaces the headroom rather than adding to it: rounding out to a
|
|
472
|
+
* whole step is already room, and doing both would leave a tenth of the
|
|
473
|
+
* span of empty axis above a top the reader was told is round.
|
|
474
|
+
*/
|
|
475
|
+
const [niceLow, niceHigh] = nice ? niceDomain(min, max) : [min, max];
|
|
476
|
+
const derivedLow = nice ? niceLow : min - headroom * lowRoom;
|
|
477
|
+
const derivedHigh = nice ? niceHigh : max + headroom * highRoom;
|
|
478
|
+
|
|
453
479
|
return [
|
|
454
|
-
typeof lowPin === 'number' ? lowPin :
|
|
455
|
-
typeof highPin === 'number' ? highPin :
|
|
480
|
+
typeof lowPin === 'number' ? lowPin : derivedLow,
|
|
481
|
+
typeof highPin === 'number' ? highPin : derivedHigh,
|
|
456
482
|
];
|
|
457
|
-
}, [data, yDomain, seriesKeys, hasBars]);
|
|
483
|
+
}, [data, yDomain, seriesKeys, hasBars, nice]);
|
|
458
484
|
|
|
459
485
|
const loading = status === 'loading';
|
|
460
486
|
// Nothing has said what the axis is yet — no mark has registered and no
|
|
@@ -603,11 +629,19 @@ const PlotRoot = forwardRef<PlotHandle, PlotProps>(function PlotRoot(
|
|
|
603
629
|
* read as the chart arriving, rather than as four separate
|
|
604
630
|
* things animating in at once — which is the thing a composed
|
|
605
631
|
* chart is most at risk of looking like.
|
|
632
|
+
*
|
|
633
|
+
* `width` is set statically as well as animated, and it has
|
|
634
|
+
* to be. Animated props on an element inside `Defs` do not
|
|
635
|
+
* reach the native clip on every platform, and with the width
|
|
636
|
+
* coming only from the animation there is no width at all
|
|
637
|
+
* when they do not — an empty clip, and marks that never
|
|
638
|
+
* appear while the axes draw normally.
|
|
606
639
|
*/}
|
|
607
640
|
<ClipPath id={clipId}>
|
|
608
641
|
<AnimatedRect
|
|
609
642
|
x={plot.left}
|
|
610
643
|
y={0}
|
|
644
|
+
width={plot.width}
|
|
611
645
|
height={size.height}
|
|
612
646
|
animatedProps={clipProps}
|
|
613
647
|
/>
|
|
@@ -662,7 +696,7 @@ function useMark(dataKey: string, color: string | undefined, colorIndex: number)
|
|
|
662
696
|
|
|
663
697
|
useEffect(() => {
|
|
664
698
|
registerSeries(dataKey, resolved);
|
|
665
|
-
return () => unregisterSeries(dataKey);
|
|
699
|
+
return () => unregisterSeries(dataKey, resolved);
|
|
666
700
|
}, [dataKey, resolved, registerSeries, unregisterSeries]);
|
|
667
701
|
|
|
668
702
|
return resolved;
|
|
@@ -759,28 +793,12 @@ function PlotLine({
|
|
|
759
793
|
const max = domainMax.value;
|
|
760
794
|
if (max === min || plot.width <= 0) return { d: '' };
|
|
761
795
|
|
|
762
|
-
if (!banded) return { d: linePath(values, plot, min, max, curve, false) };
|
|
763
|
-
|
|
764
796
|
/*
|
|
765
|
-
*
|
|
766
|
-
*
|
|
767
|
-
*
|
|
768
|
-
* a slice left of the bar it is describing.
|
|
797
|
+
* `banded` moves the points to the middle of their own slice, which is
|
|
798
|
+
* where a line drawn over columns has to sit — spread edge to edge it
|
|
799
|
+
* starts half a slice left of the bar it is describing.
|
|
769
800
|
*/
|
|
770
|
-
|
|
771
|
-
let drawn = 0;
|
|
772
|
-
for (let index = 0; index < values.length; index += 1) {
|
|
773
|
-
const value = values[index];
|
|
774
|
-
if (value === null || value === undefined) {
|
|
775
|
-
drawn = 0;
|
|
776
|
-
continue;
|
|
777
|
-
}
|
|
778
|
-
const x = bandOf(index, values.length, plot);
|
|
779
|
-
const y = yOf(value, plot, min, max);
|
|
780
|
-
d += `${drawn === 0 ? 'M' : 'L'}${x},${y}`;
|
|
781
|
-
drawn += 1;
|
|
782
|
-
}
|
|
783
|
-
return { d };
|
|
801
|
+
return { d: linePath(values, plot, min, max, curve, false, undefined, banded) };
|
|
784
802
|
});
|
|
785
803
|
|
|
786
804
|
if (status === 'loading') return null;
|
|
@@ -820,15 +838,19 @@ function PlotArea({
|
|
|
820
838
|
opacity = 0.18,
|
|
821
839
|
curve = 'monotone',
|
|
822
840
|
}: PlotAreaProps) {
|
|
823
|
-
const { data, plot, domainMin, domainMax, status, clipId } =
|
|
841
|
+
const { data, plot, xScale, domainMin, domainMax, status, clipId } =
|
|
842
|
+
useChart('Plot.Area');
|
|
824
843
|
const fill = useMark(dataKey, color, colorIndex);
|
|
825
844
|
const values = useMemo(() => columnValues(data, dataKey), [data, dataKey]);
|
|
845
|
+
const banded = xScale === 'band';
|
|
826
846
|
|
|
827
847
|
const animatedProps = useAnimatedProps(() => {
|
|
828
848
|
const min = domainMin.value;
|
|
829
849
|
const max = domainMax.value;
|
|
830
850
|
if (max === min || plot.width <= 0) return { d: '' };
|
|
831
|
-
|
|
851
|
+
// Band-aware for the same reason the line is: an area on a plot that also
|
|
852
|
+
// carries columns has to close over the same slices they occupy.
|
|
853
|
+
return { d: areaPath(values, plot, min, max, curve, false, undefined, banded) };
|
|
832
854
|
});
|
|
833
855
|
|
|
834
856
|
if (status === 'loading') return null;
|
|
@@ -848,6 +870,18 @@ export interface PlotBarsProps extends PlotSeriesProps {
|
|
|
848
870
|
/** Rounds the end the bar grows towards, in points. */
|
|
849
871
|
radius?: number;
|
|
850
872
|
opacity?: number;
|
|
873
|
+
/**
|
|
874
|
+
* The value the columns grow from. Zero by default, and zero is nearly always
|
|
875
|
+
* right — a bar is a length, and a length has to start where the quantity
|
|
876
|
+
* does.
|
|
877
|
+
*
|
|
878
|
+
* Set it for the case where the reader is being shown movement rather than
|
|
879
|
+
* size: temperatures against a seasonal average, a score against a pass mark.
|
|
880
|
+
* Columns then run up and down from that line instead of all standing on the
|
|
881
|
+
* floor. It is clamped into the axis, so a baseline the domain does not cover
|
|
882
|
+
* falls back to the nearer edge.
|
|
883
|
+
*/
|
|
884
|
+
baseline?: number;
|
|
851
885
|
}
|
|
852
886
|
|
|
853
887
|
/**
|
|
@@ -866,6 +900,7 @@ function PlotBars({
|
|
|
866
900
|
gap = 0.35,
|
|
867
901
|
radius = 4,
|
|
868
902
|
opacity = 1,
|
|
903
|
+
baseline = 0,
|
|
869
904
|
}: PlotBarsProps) {
|
|
870
905
|
const { data, plot, domainMin, domainMax, status, clipId } = useChart('Plot.Bars');
|
|
871
906
|
const fill = useMark(dataKey, color, colorIndex);
|
|
@@ -881,12 +916,12 @@ function PlotBars({
|
|
|
881
916
|
if (max === min || plot.width <= 0 || total === 0) return { d: '' };
|
|
882
917
|
|
|
883
918
|
/*
|
|
884
|
-
* Bars grow from the baseline
|
|
885
|
-
*
|
|
919
|
+
* Bars grow from the baseline — zero unless the caller moved it — clamped
|
|
920
|
+
* into the domain where the axis does not reach it. A bar drawn from the
|
|
886
921
|
* bottom of a plot whose axis starts at 40 is a length that is not in the
|
|
887
922
|
* data.
|
|
888
923
|
*/
|
|
889
|
-
const base = yOf(Math.min(Math.max(
|
|
924
|
+
const base = yOf(Math.min(Math.max(baseline, min), max), plot, min, max);
|
|
890
925
|
let path = '';
|
|
891
926
|
|
|
892
927
|
for (let index = 0; index < total; index += 1) {
|
|
@@ -1059,11 +1094,36 @@ PlotOverlay.displayName = 'Plot.Overlay';
|
|
|
1059
1094
|
PlotOverlay.layer = 'overlay' as Layer;
|
|
1060
1095
|
|
|
1061
1096
|
export interface PlotRuleProps {
|
|
1062
|
-
/**
|
|
1063
|
-
|
|
1097
|
+
/**
|
|
1098
|
+
* Where to draw it, in the data's own units. Omit it and pass `x` instead for
|
|
1099
|
+
* a rule down the plot rather than across it.
|
|
1100
|
+
*/
|
|
1101
|
+
y?: number;
|
|
1102
|
+
/**
|
|
1103
|
+
* A row to draw a vertical rule at, by index — the release the numbers are
|
|
1104
|
+
* read against, the day a change landed.
|
|
1105
|
+
*
|
|
1106
|
+
* The x axis here carries positions rather than quantities, so this is which
|
|
1107
|
+
* row rather than what value. Exactly one of `y` and `x` is drawn; `y` wins
|
|
1108
|
+
* if both are given.
|
|
1109
|
+
*/
|
|
1110
|
+
x?: number;
|
|
1064
1111
|
/** A name for what the line means. Nothing is drawn without one. */
|
|
1065
1112
|
label?: string;
|
|
1113
|
+
/** Overrides the line *and* its caption, so the two cannot drift apart. */
|
|
1066
1114
|
color?: string;
|
|
1115
|
+
/** Thickness in points. */
|
|
1116
|
+
strokeWidth?: number;
|
|
1117
|
+
/**
|
|
1118
|
+
* Break the line into dashes, for a rule that should read as an annotation
|
|
1119
|
+
* rather than as a series the chart is drawing.
|
|
1120
|
+
*/
|
|
1121
|
+
dashed?: boolean;
|
|
1122
|
+
/** Fades the line and its caption together. */
|
|
1123
|
+
opacity?: number;
|
|
1124
|
+
/** Which end of the rule the caption sits at. */
|
|
1125
|
+
labelPlacement?: 'start' | 'end';
|
|
1126
|
+
labelClassName?: string;
|
|
1067
1127
|
className?: string;
|
|
1068
1128
|
}
|
|
1069
1129
|
|
|
@@ -1071,15 +1131,34 @@ export interface PlotRuleProps {
|
|
|
1071
1131
|
* A reference line across the plot — a target, a limit, an average.
|
|
1072
1132
|
*
|
|
1073
1133
|
* A view rather than an SVG line, so its label is real text and follows the
|
|
1074
|
-
* theme. Its
|
|
1134
|
+
* theme. Its thickness is a border and its position is a transform, so it costs
|
|
1075
1135
|
* no more than the SVG line would while gaining a legible caption.
|
|
1136
|
+
*
|
|
1137
|
+
* It is drawn at full strength in the foreground colour, and it is meant to be.
|
|
1138
|
+
* A reference line is the number the series is being judged against — a target
|
|
1139
|
+
* nobody can read is a target the chart is not actually stating — so what keeps
|
|
1140
|
+
* it from being mistaken for a series is that it is neutral and optionally
|
|
1141
|
+
* dashed, not that it is faint.
|
|
1076
1142
|
*/
|
|
1077
|
-
function PlotRule({
|
|
1078
|
-
|
|
1079
|
-
|
|
1143
|
+
function PlotRule({
|
|
1144
|
+
y,
|
|
1145
|
+
x,
|
|
1146
|
+
label,
|
|
1147
|
+
color,
|
|
1148
|
+
strokeWidth = 1,
|
|
1149
|
+
dashed = false,
|
|
1150
|
+
opacity = 1,
|
|
1151
|
+
labelPlacement = 'end',
|
|
1152
|
+
labelClassName,
|
|
1153
|
+
className,
|
|
1154
|
+
}: PlotRuleProps) {
|
|
1155
|
+
const { plot, domainMin, domainMax, status, data } = useChart('Plot.Rule');
|
|
1156
|
+
const token = useCSSVariable('--color-foreground');
|
|
1080
1157
|
const stroke = color ?? (typeof token === 'string' ? token : '#888888');
|
|
1158
|
+
const vertical = y === undefined && x !== undefined;
|
|
1081
1159
|
|
|
1082
|
-
const
|
|
1160
|
+
const horizontalStyle = useAnimatedStyle(() => {
|
|
1161
|
+
if (vertical || y === undefined) return { opacity: 0 };
|
|
1083
1162
|
const min = domainMin.value;
|
|
1084
1163
|
const max = domainMax.value;
|
|
1085
1164
|
if (max === min) return { opacity: 0 };
|
|
@@ -1087,23 +1166,92 @@ function PlotRule({ y, label, color, className }: PlotRuleProps) {
|
|
|
1087
1166
|
// Off the top or bottom of the plot is out of the chart's range, and a rule
|
|
1088
1167
|
// pinned to the edge would claim a value the axis does not cover.
|
|
1089
1168
|
const inside = at >= plot.top - 0.5 && at <= plot.top + plot.height + 0.5;
|
|
1090
|
-
return { opacity: inside ?
|
|
1169
|
+
return { opacity: inside ? opacity : 0, transform: [{ translateY: at }] };
|
|
1091
1170
|
});
|
|
1092
1171
|
|
|
1093
1172
|
if (status === 'loading') return null;
|
|
1094
1173
|
|
|
1174
|
+
const line = dashed
|
|
1175
|
+
? {
|
|
1176
|
+
borderColor: stroke,
|
|
1177
|
+
borderStyle: 'dashed' as const,
|
|
1178
|
+
borderTopWidth: strokeWidth,
|
|
1179
|
+
}
|
|
1180
|
+
: { backgroundColor: stroke, height: strokeWidth };
|
|
1181
|
+
|
|
1182
|
+
if (vertical) {
|
|
1183
|
+
/*
|
|
1184
|
+
* The x axis carries positions, not quantities, so a vertical rule is
|
|
1185
|
+
* placed from the row's own geometry rather than from the tweening domain —
|
|
1186
|
+
* there is nothing on this axis for it to tween against.
|
|
1187
|
+
*/
|
|
1188
|
+
const total = data.length;
|
|
1189
|
+
const at =
|
|
1190
|
+
total > 0
|
|
1191
|
+
? xOf(Math.max(0, Math.min(total - 1, x!)), total, plot)
|
|
1192
|
+
: plot.left + plot.width / 2;
|
|
1193
|
+
|
|
1194
|
+
return (
|
|
1195
|
+
<View
|
|
1196
|
+
pointerEvents="none"
|
|
1197
|
+
style={{
|
|
1198
|
+
position: 'absolute',
|
|
1199
|
+
left: at,
|
|
1200
|
+
top: plot.top,
|
|
1201
|
+
height: plot.height,
|
|
1202
|
+
opacity,
|
|
1203
|
+
}}
|
|
1204
|
+
className={className}
|
|
1205
|
+
>
|
|
1206
|
+
<View
|
|
1207
|
+
style={
|
|
1208
|
+
dashed
|
|
1209
|
+
? {
|
|
1210
|
+
borderColor: stroke,
|
|
1211
|
+
borderStyle: 'dashed',
|
|
1212
|
+
borderLeftWidth: strokeWidth,
|
|
1213
|
+
flex: 1,
|
|
1214
|
+
}
|
|
1215
|
+
: { backgroundColor: stroke, width: strokeWidth, flex: 1 }
|
|
1216
|
+
}
|
|
1217
|
+
/>
|
|
1218
|
+
{label ? (
|
|
1219
|
+
<Text
|
|
1220
|
+
size="xs"
|
|
1221
|
+
weight="medium"
|
|
1222
|
+
numberOfLines={1}
|
|
1223
|
+
style={color ? { color } : undefined}
|
|
1224
|
+
className={cn('absolute left-1 top-0', labelClassName)}
|
|
1225
|
+
>
|
|
1226
|
+
{label}
|
|
1227
|
+
</Text>
|
|
1228
|
+
) : null}
|
|
1229
|
+
</View>
|
|
1230
|
+
);
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1095
1233
|
return (
|
|
1096
1234
|
<Animated.View
|
|
1097
1235
|
pointerEvents="none"
|
|
1098
1236
|
style={[
|
|
1099
1237
|
{ position: 'absolute', left: plot.left, width: plot.width, top: 0 },
|
|
1100
|
-
|
|
1238
|
+
horizontalStyle,
|
|
1101
1239
|
]}
|
|
1102
1240
|
className={className}
|
|
1103
1241
|
>
|
|
1104
|
-
<View style={
|
|
1242
|
+
<View style={line} />
|
|
1105
1243
|
{label ? (
|
|
1106
|
-
<Text
|
|
1244
|
+
<Text
|
|
1245
|
+
size="xs"
|
|
1246
|
+
weight="medium"
|
|
1247
|
+
numberOfLines={1}
|
|
1248
|
+
style={color ? { color } : undefined}
|
|
1249
|
+
className={cn(
|
|
1250
|
+
'pt-0.5',
|
|
1251
|
+
labelPlacement === 'start' ? 'self-start' : 'self-end',
|
|
1252
|
+
labelClassName
|
|
1253
|
+
)}
|
|
1254
|
+
>
|
|
1107
1255
|
{label}
|
|
1108
1256
|
</Text>
|
|
1109
1257
|
) : null}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export interface PlotSeriesRegistration {
|
|
2
|
+
key: string;
|
|
3
|
+
colors: readonly string[];
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function registerPlotSeries(
|
|
7
|
+
current: readonly PlotSeriesRegistration[],
|
|
8
|
+
key: string,
|
|
9
|
+
color: string
|
|
10
|
+
): PlotSeriesRegistration[] {
|
|
11
|
+
const existing = current.find((entry) => entry.key === key);
|
|
12
|
+
if (!existing) return [...current, { key, colors: [color] }];
|
|
13
|
+
|
|
14
|
+
return current.map((entry) =>
|
|
15
|
+
entry === existing ? { ...entry, colors: [...entry.colors, color] } : entry
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function unregisterPlotSeries(
|
|
20
|
+
current: readonly PlotSeriesRegistration[],
|
|
21
|
+
key: string,
|
|
22
|
+
color?: string
|
|
23
|
+
): PlotSeriesRegistration[] {
|
|
24
|
+
const existing = current.find((entry) => entry.key === key);
|
|
25
|
+
if (!existing) return current as PlotSeriesRegistration[];
|
|
26
|
+
|
|
27
|
+
const index =
|
|
28
|
+
color === undefined ? existing.colors.length - 1 : existing.colors.lastIndexOf(color);
|
|
29
|
+
if (index < 0) return current as PlotSeriesRegistration[];
|
|
30
|
+
|
|
31
|
+
const colors = existing.colors.filter((_, candidate) => candidate !== index);
|
|
32
|
+
if (colors.length === 0) return current.filter((entry) => entry !== existing);
|
|
33
|
+
|
|
34
|
+
return current.map((entry) => (entry === existing ? { ...entry, colors } : entry));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function visiblePlotSeries(
|
|
38
|
+
registrations: readonly PlotSeriesRegistration[]
|
|
39
|
+
): [string, string][] {
|
|
40
|
+
return registrations.map(({ key, colors }) => [key, colors[colors.length - 1]!]);
|
|
41
|
+
}
|
|
@@ -198,7 +198,7 @@ const PostRoot = forwardRef<View, PostProps>(
|
|
|
198
198
|
|
|
199
199
|
if (!onPress) {
|
|
200
200
|
return (
|
|
201
|
-
<View ref={ref} className={root({ className })}
|
|
201
|
+
<View ref={ref} {...props} className={root({ className })}>
|
|
202
202
|
{body}
|
|
203
203
|
</View>
|
|
204
204
|
);
|
|
@@ -210,10 +210,10 @@ const PostRoot = forwardRef<View, PostProps>(
|
|
|
210
210
|
// as one button buries them.
|
|
211
211
|
<AnimatedPressable
|
|
212
212
|
ref={ref}
|
|
213
|
+
{...props}
|
|
213
214
|
accessibilityRole="link"
|
|
214
215
|
onPress={onPress}
|
|
215
216
|
className={root({ className })}
|
|
216
|
-
{...props}
|
|
217
217
|
>
|
|
218
218
|
{body}
|
|
219
219
|
</AnimatedPressable>
|
|
@@ -681,7 +681,7 @@ const PostMedia = forwardRef<View, PostMediaProps>(
|
|
|
681
681
|
|
|
682
682
|
if (!onPress) {
|
|
683
683
|
return (
|
|
684
|
-
<View ref={ref} className={media({ className })} style={{ aspectRatio }}
|
|
684
|
+
<View ref={ref} {...props} className={media({ className })} style={{ aspectRatio }}>
|
|
685
685
|
{picture}
|
|
686
686
|
</View>
|
|
687
687
|
);
|
|
@@ -690,12 +690,12 @@ const PostMedia = forwardRef<View, PostMediaProps>(
|
|
|
690
690
|
return (
|
|
691
691
|
<Pressable
|
|
692
692
|
ref={ref}
|
|
693
|
+
{...props}
|
|
693
694
|
accessibilityRole="imagebutton"
|
|
694
695
|
accessibilityLabel={alt}
|
|
695
696
|
onPress={onPress}
|
|
696
697
|
className={media({ className })}
|
|
697
698
|
style={{ aspectRatio }}
|
|
698
|
-
{...props}
|
|
699
699
|
>
|
|
700
700
|
{picture}
|
|
701
701
|
</Pressable>
|
|
@@ -36,6 +36,7 @@ import Animated, {
|
|
|
36
36
|
import { tv, type VariantProps } from 'tailwind-variants';
|
|
37
37
|
import { cn } from '../../utils/cn';
|
|
38
38
|
import { Text, textChildren } from '../../primitives/text';
|
|
39
|
+
import { requestRadioValueChange } from './radio-group-value';
|
|
39
40
|
|
|
40
41
|
type RadioVariant = 'dot' | 'card';
|
|
41
42
|
type RadioOrientation = 'vertical' | 'horizontal';
|
|
@@ -247,7 +248,9 @@ const RadioGroupItem = forwardRef<View, RadioGroupItemProps>(
|
|
|
247
248
|
accessibilityState={{ selected, disabled: !!disabled }}
|
|
248
249
|
accessibilityLabel={label}
|
|
249
250
|
disabled={disabled}
|
|
250
|
-
onPress={() =>
|
|
251
|
+
onPress={() =>
|
|
252
|
+
requestRadioValueChange(context.value, value, context.onValueChange)
|
|
253
|
+
}
|
|
251
254
|
className={slots.row({ className })}
|
|
252
255
|
>
|
|
253
256
|
{body}
|
|
@@ -43,6 +43,11 @@ import { StarIcon } from '../../icons';
|
|
|
43
43
|
import { Text } from '../../primitives/text';
|
|
44
44
|
import { cn } from '../../utils/cn';
|
|
45
45
|
import { selectionTick } from '../../utils/haptics';
|
|
46
|
+
import {
|
|
47
|
+
normalizeRatingMax,
|
|
48
|
+
normalizeRatingPrecision,
|
|
49
|
+
normalizeRatingValue,
|
|
50
|
+
} from './rating-inputs';
|
|
46
51
|
|
|
47
52
|
/** Springs the fill onto its resting value after a tap or the end of a drag. */
|
|
48
53
|
const SPRING = { damping: 18, stiffness: 220, mass: 0.6 } as const;
|
|
@@ -197,8 +202,8 @@ export const Rating = forwardRef<View, RatingProps>(
|
|
|
197
202
|
rowClassName,
|
|
198
203
|
value: valueProp,
|
|
199
204
|
defaultValue = 0,
|
|
200
|
-
max = 5,
|
|
201
|
-
precision = 1,
|
|
205
|
+
max: maxProp = 5,
|
|
206
|
+
precision: precisionProp = 1,
|
|
202
207
|
onValueChange,
|
|
203
208
|
onValueCommit,
|
|
204
209
|
readOnly = false,
|
|
@@ -213,9 +218,11 @@ export const Rating = forwardRef<View, RatingProps>(
|
|
|
213
218
|
},
|
|
214
219
|
ref
|
|
215
220
|
) => {
|
|
221
|
+
const max = normalizeRatingMax(maxProp);
|
|
222
|
+
const precision = normalizeRatingPrecision(precisionProp);
|
|
216
223
|
const isControlled = valueProp !== undefined;
|
|
217
|
-
const [internal, setInternal] = useState(defaultValue);
|
|
218
|
-
const value =
|
|
224
|
+
const [internal, setInternal] = useState(() => normalizeRatingValue(defaultValue, max));
|
|
225
|
+
const value = normalizeRatingValue(isControlled ? valueProp : internal, max);
|
|
219
226
|
|
|
220
227
|
const interactive = !readOnly && !disabled;
|
|
221
228
|
const slots = ratingVariants({ size, disabled });
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const DEFAULT_MAX = 5;
|
|
2
|
+
const DEFAULT_PRECISION = 1;
|
|
3
|
+
|
|
4
|
+
export function normalizeRatingMax(max: number | undefined): number {
|
|
5
|
+
return typeof max === 'number' && Number.isFinite(max) && max >= 1
|
|
6
|
+
? Math.floor(max)
|
|
7
|
+
: DEFAULT_MAX;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function normalizeRatingPrecision(precision: number | undefined): number {
|
|
11
|
+
return typeof precision === 'number' && Number.isFinite(precision) && precision > 0
|
|
12
|
+
? precision
|
|
13
|
+
: DEFAULT_PRECISION;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function normalizeRatingValue(value: number | undefined, max: number): number {
|
|
17
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) return 0;
|
|
18
|
+
return Math.min(Math.max(value, 0), max);
|
|
19
|
+
}
|