@carto/ps-react-ui 4.4.3 → 4.5.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/dist/{download-config-Dqu78h2a.js → download-config-DemuQ3Jm.js} +9 -10
- package/dist/{download-config-Dqu78h2a.js.map → download-config-DemuQ3Jm.js.map} +1 -1
- package/dist/error-Cj8eUMrl.js +40 -0
- package/dist/error-Cj8eUMrl.js.map +1 -0
- package/dist/no-data-DkIt7Qt1.js +61 -0
- package/dist/no-data-DkIt7Qt1.js.map +1 -0
- package/dist/row-D4VOhcNI.js +34 -0
- package/dist/row-D4VOhcNI.js.map +1 -0
- package/dist/series-Bola3CmD.js +90 -0
- package/dist/series-Bola3CmD.js.map +1 -0
- package/dist/types/widgets/echart/shared-resize-observer.d.ts +12 -0
- package/dist/types/widgets/stores/index.d.ts +2 -1
- package/dist/types/widgets/stores/use-widget-selector.d.ts +35 -0
- package/dist/types/widgets/stores/widget-store-performance.test.d.ts +1 -0
- package/dist/types/widgets/stores/widget-store.d.ts +49 -27
- package/dist/types/widgets/table/types.d.ts +1 -1
- package/dist/use-widget-ref-BFazQvJK.js +22 -0
- package/dist/use-widget-ref-BFazQvJK.js.map +1 -0
- package/dist/use-widget-selector-DqRmWQ1K.js +12 -0
- package/dist/use-widget-selector-DqRmWQ1K.js.map +1 -0
- package/dist/widget-store-CIrb9RKP.js +263 -0
- package/dist/widget-store-CIrb9RKP.js.map +1 -0
- package/dist/widgets/actions.js +783 -817
- package/dist/widgets/actions.js.map +1 -1
- package/dist/widgets/bar.js +2 -2
- package/dist/widgets/category.js +254 -257
- package/dist/widgets/category.js.map +1 -1
- package/dist/widgets/echart.js +109 -99
- package/dist/widgets/echart.js.map +1 -1
- package/dist/widgets/error.js +1 -1
- package/dist/widgets/formula.js +71 -63
- package/dist/widgets/formula.js.map +1 -1
- package/dist/widgets/histogram.js +7 -8
- package/dist/widgets/histogram.js.map +1 -1
- package/dist/widgets/loader.js +53 -60
- package/dist/widgets/loader.js.map +1 -1
- package/dist/widgets/markdown.js +51 -50
- package/dist/widgets/markdown.js.map +1 -1
- package/dist/widgets/no-data.js +1 -1
- package/dist/widgets/pie.js +2 -2
- package/dist/widgets/range.js +146 -144
- package/dist/widgets/range.js.map +1 -1
- package/dist/widgets/scatterplot.js +2 -2
- package/dist/widgets/skeleton-loader.js +18 -17
- package/dist/widgets/skeleton-loader.js.map +1 -1
- package/dist/widgets/spread.js +110 -94
- package/dist/widgets/spread.js.map +1 -1
- package/dist/widgets/stores.js +5 -2
- package/dist/widgets/stores.js.map +1 -1
- package/dist/widgets/table.js +422 -436
- package/dist/widgets/table.js.map +1 -1
- package/dist/widgets/timeseries.js +2 -2
- package/dist/widgets/utils.js +1 -1
- package/dist/widgets/wrapper.js +156 -158
- package/dist/widgets/wrapper.js.map +1 -1
- package/dist/widgets.js +4 -4
- package/package.json +1 -1
- package/src/hooks/use-widget-ref.ts +3 -4
- package/src/widgets/actions/brush-toggle/brush-toggle.tsx +18 -32
- package/src/widgets/actions/change-column/change-column.tsx +15 -15
- package/src/widgets/actions/change-column/sortable-column-item.tsx +3 -1
- package/src/widgets/actions/download/download.tsx +4 -3
- package/src/widgets/actions/fullscreen/fullscreen.tsx +7 -11
- package/src/widgets/actions/lock-selection/lock-selection.tsx +12 -15
- package/src/widgets/actions/relative-data/relative-data.tsx +22 -26
- package/src/widgets/actions/searcher/searcher-toggle.tsx +11 -12
- package/src/widgets/actions/searcher/searcher.tsx +20 -21
- package/src/widgets/actions/stack-toggle/stack-toggle.tsx +15 -21
- package/src/widgets/actions/zoom-toggle/zoom-toggle.tsx +27 -43
- package/src/widgets/category/category-ui.tsx +27 -31
- package/src/widgets/echart/echart-ui.test.tsx +20 -16
- package/src/widgets/echart/echart-ui.tsx +6 -12
- package/src/widgets/echart/echart.tsx +13 -27
- package/src/widgets/echart/shared-resize-observer.ts +45 -0
- package/src/widgets/error/error.tsx +7 -9
- package/src/widgets/formula/components/prefix.tsx +4 -6
- package/src/widgets/formula/components/row.tsx +4 -4
- package/src/widgets/formula/components/series.tsx +4 -6
- package/src/widgets/formula/components/suffix.tsx +4 -6
- package/src/widgets/formula/components/value.tsx +9 -16
- package/src/widgets/loader/loader.tsx +31 -44
- package/src/widgets/markdown/markdown.tsx +4 -7
- package/src/widgets/no-data/no-data.tsx +7 -10
- package/src/widgets/range/components/range-item.tsx +20 -18
- package/src/widgets/skeleton-loader/skeleton-loader.tsx +2 -5
- package/src/widgets/spread/components/max-value.tsx +14 -16
- package/src/widgets/spread/components/min-value.tsx +14 -16
- package/src/widgets/stores/index.ts +2 -1
- package/src/widgets/stores/use-widget-selector.ts +47 -0
- package/src/widgets/stores/widget-store-performance.test.ts +750 -0
- package/src/widgets/stores/widget-store.test.ts +81 -0
- package/src/widgets/stores/widget-store.ts +225 -44
- package/src/widgets/table/config.ts +0 -1
- package/src/widgets/table/hooks/use-pagination.ts +28 -52
- package/src/widgets/table/hooks/use-selection.ts +20 -24
- package/src/widgets/table/hooks/use-sort.ts +22 -39
- package/src/widgets/table/types.ts +1 -1
- package/src/widgets/wrapper/wrapper-ui.tsx +12 -13
- package/src/widgets/wrapper/wrapper.tsx +4 -6
- package/dist/error-CEkRPccv.js +0 -39
- package/dist/error-CEkRPccv.js.map +0 -1
- package/dist/no-data-hR3KcJ-_.js +0 -60
- package/dist/no-data-hR3KcJ-_.js.map +0 -1
- package/dist/row-DTCV0Ocm.js +0 -35
- package/dist/row-DTCV0Ocm.js.map +0 -1
- package/dist/series-CYNOu2Ju.js +0 -91
- package/dist/series-CYNOu2Ju.js.map +0 -1
- package/dist/use-widget-ref-wtFLDFCD.js +0 -25
- package/dist/use-widget-ref-wtFLDFCD.js.map +0 -1
- package/dist/widget-store-CzDt8oSK.js +0 -163
- package/dist/widget-store-CzDt8oSK.js.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { useEffect, useRef
|
|
1
|
+
import { useEffect, useRef } from 'react'
|
|
2
2
|
import type { WidgetLoaderProps } from './types'
|
|
3
|
-
import { useWidgetStore } from '../stores/widget-store'
|
|
3
|
+
import { useWidgetStore, widgetStoreActions } from '../stores/widget-store'
|
|
4
4
|
import type { WrapperState } from '../wrapper'
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -28,17 +28,11 @@ import type { WrapperState } from '../wrapper'
|
|
|
28
28
|
export function WidgetLoader<T extends object = Record<string, unknown>>(
|
|
29
29
|
props: WidgetLoaderProps<T>,
|
|
30
30
|
) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
(state) => state.executeConfigPipeline,
|
|
37
|
-
)
|
|
38
|
-
|
|
39
|
-
const registeredTools = useSyncExternalStore(
|
|
40
|
-
useWidgetStore.subscribe,
|
|
41
|
-
() => useWidgetStore.getState().widgets[props.id]?.registeredTools,
|
|
31
|
+
// Subscribe only to this widget's registeredTools via Zustand selector.
|
|
32
|
+
// The selector returns a stable reference when other widgets change,
|
|
33
|
+
// avoiding unnecessary re-evaluations of Effect 4.
|
|
34
|
+
const registeredTools = useWidgetStore(
|
|
35
|
+
(state) => state.widgets[props.id]?.registeredTools,
|
|
42
36
|
)
|
|
43
37
|
|
|
44
38
|
const dataRef = useRef(props.data)
|
|
@@ -50,56 +44,49 @@ export function WidgetLoader<T extends object = Record<string, unknown>>(
|
|
|
50
44
|
configRef.current = props.config
|
|
51
45
|
})
|
|
52
46
|
|
|
53
|
-
//
|
|
54
|
-
//
|
|
55
|
-
// accidentally resetting other properties.
|
|
56
|
-
//
|
|
57
|
-
// - Effect 1: Type (can be modified by tools that change visualization type)
|
|
58
|
-
// - Effect 2: Loading/Error states (change during fetch lifecycle)
|
|
59
|
-
// - Effect 3: Config (can be modified by tools that change widget configuration)
|
|
60
|
-
// - Effect 4: Data pipeline execution (transforms data through registered tools)
|
|
61
|
-
// - Effect 5: Re-execute pipeline when tool state changes
|
|
62
|
-
|
|
63
|
-
// Effect 1: Type updates
|
|
47
|
+
// Effect 1: Metadata — type, loading, and error states in a single setWidget call.
|
|
48
|
+
// Merged to reduce store updates from 2 to 1 per widget during initialization.
|
|
64
49
|
useEffect(() => {
|
|
65
|
-
setWidget<WrapperState>(props.id, {
|
|
50
|
+
widgetStoreActions.setWidget<WrapperState>(props.id, {
|
|
66
51
|
type: props.type,
|
|
67
|
-
})
|
|
68
|
-
}, [props.id, props.type, setWidget])
|
|
69
|
-
|
|
70
|
-
// Effect 2: Loading and error states
|
|
71
|
-
useEffect(() => {
|
|
72
|
-
setWidget<WrapperState>(props.id, {
|
|
73
52
|
isLoading: props.isLoading ?? false,
|
|
74
53
|
isFetching: props.isFetching ?? false,
|
|
75
54
|
error: props.error,
|
|
76
55
|
})
|
|
77
|
-
}, [props.id, props.isLoading, props.isFetching, props.error
|
|
56
|
+
}, [props.id, props.type, props.isLoading, props.isFetching, props.error])
|
|
78
57
|
|
|
79
|
-
// Effect
|
|
58
|
+
// Effect 2: Config updates — run through config pipeline
|
|
80
59
|
useEffect(() => {
|
|
81
60
|
if (props.config) {
|
|
82
|
-
void executeConfigPipeline(props.id, props.config)
|
|
61
|
+
void widgetStoreActions.executeConfigPipeline(props.id, props.config)
|
|
83
62
|
}
|
|
84
|
-
}, [props.id, props.config
|
|
63
|
+
}, [props.id, props.config])
|
|
85
64
|
|
|
86
|
-
// Effect
|
|
65
|
+
// Effect 3: Execute tool pipeline when props.data changes
|
|
87
66
|
useEffect(() => {
|
|
88
|
-
void executeToolPipeline(props.id, props.data)
|
|
89
|
-
}, [props.id, props.data
|
|
67
|
+
void widgetStoreActions.executeToolPipeline(props.id, props.data)
|
|
68
|
+
}, [props.id, props.data])
|
|
90
69
|
|
|
91
|
-
// Effect
|
|
70
|
+
// Effect 4: Re-execute pipelines when registered tools change.
|
|
71
|
+
// Uses requestAnimationFrame to coalesce rapid successive registeredTools
|
|
72
|
+
// changes (e.g., 6 action components each calling registerTool on mount)
|
|
73
|
+
// into a single pipeline execution instead of 6 pairs.
|
|
92
74
|
useEffect(() => {
|
|
93
75
|
if (!isMountedRef.current) {
|
|
94
76
|
isMountedRef.current = true
|
|
95
77
|
return
|
|
96
78
|
}
|
|
97
79
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
void
|
|
101
|
-
|
|
102
|
-
|
|
80
|
+
const rafId = requestAnimationFrame(() => {
|
|
81
|
+
const { executeToolPipeline, executeConfigPipeline } = widgetStoreActions
|
|
82
|
+
void executeToolPipeline(props.id, dataRef.current)
|
|
83
|
+
if (configRef.current) {
|
|
84
|
+
void executeConfigPipeline(props.id, configRef.current)
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
return () => cancelAnimationFrame(rafId)
|
|
89
|
+
}, [registeredTools, props.id])
|
|
103
90
|
|
|
104
91
|
return props.children
|
|
105
92
|
}
|
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
import type { MarkdownUIProps } from './types'
|
|
2
|
-
import { useWidgetStore } from '../stores/widget-store'
|
|
3
2
|
import type { MarkdownWidgetData } from './types'
|
|
4
3
|
import { MarkdownUI } from '.'
|
|
5
|
-
import {
|
|
4
|
+
import { useWidgetSelector } from '../stores/use-widget-selector'
|
|
6
5
|
|
|
7
6
|
/**
|
|
8
7
|
* Stateful markdown widget component that reads content from the widget store and renders it as formatted rich text.
|
|
9
8
|
*/
|
|
10
9
|
export function Markdown({ id }: MarkdownUIProps) {
|
|
11
|
-
const content =
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
(state.getWidget(id)?.data as MarkdownWidgetData | undefined)?.content,
|
|
15
|
-
),
|
|
10
|
+
const content = useWidgetSelector(
|
|
11
|
+
id,
|
|
12
|
+
(w) => (w?.data as MarkdownWidgetData | undefined)?.content,
|
|
16
13
|
)
|
|
17
14
|
|
|
18
15
|
if (!content) {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Box, Typography } from '@mui/material'
|
|
2
|
-
import {
|
|
3
|
-
import { useWidgetStore } from '../stores/widget-store'
|
|
2
|
+
import { useWidgetSelector } from '../stores/use-widget-selector'
|
|
4
3
|
import type { WidgetNoDataProps } from './types'
|
|
5
4
|
import { styles } from './style'
|
|
6
5
|
|
|
@@ -44,14 +43,12 @@ export function WidgetNoData({
|
|
|
44
43
|
description = 'There are no results for the combination of filters applied to your data. Try tweaking your filters, or zoom and pan the map to adjust filters',
|
|
45
44
|
isEmpty = defaultIsEmpty,
|
|
46
45
|
}: WidgetNoDataProps) {
|
|
47
|
-
//
|
|
48
|
-
const isLoading =
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
)
|
|
54
|
-
const data = useWidgetStore(useShallow((state) => state.widgets[id]?.data))
|
|
46
|
+
// Single consolidated subscription instead of 3 separate ones.
|
|
47
|
+
const { isLoading, isFetching, data } = useWidgetSelector(id, (w) => ({
|
|
48
|
+
isLoading: w?.isLoading,
|
|
49
|
+
isFetching: w?.isFetching,
|
|
50
|
+
data: w?.data,
|
|
51
|
+
}))
|
|
55
52
|
|
|
56
53
|
// If loading or fetching, show children
|
|
57
54
|
// SkeletonLoader handles loading state, this allows proper composition
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Box, Slider, TextField } from '@mui/material'
|
|
2
2
|
import { useState, useMemo, type FocusEvent, type KeyboardEvent } from 'react'
|
|
3
|
-
import {
|
|
3
|
+
import { widgetStoreActions } from '../../stores/widget-store'
|
|
4
|
+
import { useWidgetSelector } from '../../stores/use-widget-selector'
|
|
4
5
|
import type { RangeItemProps, RangeWidgetState } from '../types'
|
|
5
6
|
import { styles } from '../style'
|
|
6
|
-
import { useShallow } from 'zustand/shallow'
|
|
7
7
|
|
|
8
8
|
import { defaultFormatter } from '../../utils/formatter'
|
|
9
9
|
|
|
@@ -13,18 +13,19 @@ type EditingState = '' | 'min' | 'max'
|
|
|
13
13
|
* Renders a single range slider with editable min/max text inputs, reading its configuration from the widget store.
|
|
14
14
|
*/
|
|
15
15
|
export function RangeItem({ id, index }: RangeItemProps) {
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
16
|
+
const {
|
|
17
|
+
item,
|
|
18
|
+
onChange,
|
|
19
|
+
formatter: _formatter,
|
|
20
|
+
} = useWidgetSelector(id, (w) => {
|
|
21
|
+
const rw = w as RangeWidgetState | undefined
|
|
22
|
+
return {
|
|
23
|
+
item: rw?.data[index],
|
|
24
|
+
onChange: rw?.onChange,
|
|
25
|
+
formatter: rw?.formatter,
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
const formatter = _formatter ?? defaultFormatter
|
|
28
29
|
|
|
29
30
|
const currentValue = useMemo(
|
|
30
31
|
() => (item ? (item.value ?? [item.min, item.max]) : [0, 0]),
|
|
@@ -39,14 +40,15 @@ export function RangeItem({ id, index }: RangeItemProps) {
|
|
|
39
40
|
const handleSliderChange = (_: Event, newValue: number | number[]) => {
|
|
40
41
|
if (Array.isArray(newValue)) {
|
|
41
42
|
const [min, max] = newValue
|
|
42
|
-
const data =
|
|
43
|
+
const data =
|
|
44
|
+
widgetStoreActions.getWidget<RangeWidgetState>(id)?.data ?? []
|
|
43
45
|
|
|
44
46
|
data[index] = {
|
|
45
47
|
...item,
|
|
46
48
|
value: newValue,
|
|
47
49
|
}
|
|
48
50
|
|
|
49
|
-
setWidget(id, {
|
|
51
|
+
widgetStoreActions.setWidget(id, {
|
|
50
52
|
data,
|
|
51
53
|
})
|
|
52
54
|
|
|
@@ -80,9 +82,9 @@ export function RangeItem({ id, index }: RangeItemProps) {
|
|
|
80
82
|
]
|
|
81
83
|
}
|
|
82
84
|
|
|
83
|
-
const data = getWidget<RangeWidgetState>(id)?.data ?? []
|
|
85
|
+
const data = widgetStoreActions.getWidget<RangeWidgetState>(id)?.data ?? []
|
|
84
86
|
|
|
85
|
-
setWidget(id, {
|
|
87
|
+
widgetStoreActions.setWidget(id, {
|
|
86
88
|
data: data.map((d: RangeWidgetState['data'][number], i: number) =>
|
|
87
89
|
i === index
|
|
88
90
|
? {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { SkeletonLoaderProps } from './types'
|
|
2
|
-
import {
|
|
2
|
+
import { useWidgetSelector } from '../stores/use-widget-selector'
|
|
3
3
|
import { Suspense } from 'react'
|
|
4
|
-
import { useShallow } from 'zustand/shallow'
|
|
5
4
|
|
|
6
5
|
/**
|
|
7
6
|
* Displays a skeleton loading placeholder while widget data is loading. Subscribes to widget loading state in the store and renders the provided Skeleton component or children accordingly.
|
|
@@ -18,9 +17,7 @@ export function SkeletonLoader({
|
|
|
18
17
|
children,
|
|
19
18
|
Skeleton,
|
|
20
19
|
}: SkeletonLoaderProps) {
|
|
21
|
-
const isLoading =
|
|
22
|
-
useShallow((state) => state.widgets[id]?.isLoading),
|
|
23
|
-
)
|
|
20
|
+
const isLoading = useWidgetSelector(id, (w) => w?.isLoading)
|
|
24
21
|
|
|
25
22
|
if (isLoading) {
|
|
26
23
|
if (!Skeleton) {
|
|
@@ -1,27 +1,25 @@
|
|
|
1
1
|
import { type SpreadWidgetState, type ValueProps } from '../types'
|
|
2
|
-
import {
|
|
2
|
+
import { useWidgetSelector } from '../../stores/use-widget-selector'
|
|
3
3
|
import { Item } from '../../formula/components/item'
|
|
4
|
-
import { useShallow } from 'zustand/shallow'
|
|
5
4
|
import { defaultFormatter } from '../../utils/formatter'
|
|
6
5
|
|
|
7
6
|
/**
|
|
8
7
|
* Displays the formatted maximum value for a spread widget data item.
|
|
9
8
|
*/
|
|
10
9
|
export function MaxValue({ id, index = 0, ...props }: ValueProps) {
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
) ?? defaultFormatter
|
|
10
|
+
const {
|
|
11
|
+
max,
|
|
12
|
+
color,
|
|
13
|
+
formatter: _formatter,
|
|
14
|
+
} = useWidgetSelector(id, (w) => {
|
|
15
|
+
const sw = w as SpreadWidgetState | undefined
|
|
16
|
+
return {
|
|
17
|
+
max: sw?.data[index]?.max,
|
|
18
|
+
color: sw?.data[index]?.color,
|
|
19
|
+
formatter: sw?.formatter,
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
const formatter = _formatter ?? defaultFormatter
|
|
25
23
|
|
|
26
24
|
return (
|
|
27
25
|
<Item TypographyProps={{ color }} {...props}>
|
|
@@ -1,27 +1,25 @@
|
|
|
1
1
|
import { type SpreadWidgetState, type ValueProps } from '../types'
|
|
2
|
-
import {
|
|
2
|
+
import { useWidgetSelector } from '../../stores/use-widget-selector'
|
|
3
3
|
import { Item } from '../../formula/components/item'
|
|
4
|
-
import { useShallow } from 'zustand/shallow'
|
|
5
4
|
import { defaultFormatter } from '../../utils/formatter'
|
|
6
5
|
|
|
7
6
|
/**
|
|
8
7
|
* Displays the formatted minimum value for a spread widget data item.
|
|
9
8
|
*/
|
|
10
9
|
export function MinValue({ id, index = 0, ...props }: ValueProps) {
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
) ?? defaultFormatter
|
|
10
|
+
const {
|
|
11
|
+
min,
|
|
12
|
+
color,
|
|
13
|
+
formatter: _formatter,
|
|
14
|
+
} = useWidgetSelector(id, (w) => {
|
|
15
|
+
const sw = w as SpreadWidgetState | undefined
|
|
16
|
+
return {
|
|
17
|
+
min: sw?.data[index]?.min,
|
|
18
|
+
color: sw?.data[index]?.color,
|
|
19
|
+
formatter: sw?.formatter,
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
const formatter = _formatter ?? defaultFormatter
|
|
25
23
|
|
|
26
24
|
return (
|
|
27
25
|
<Item TypographyProps={{ color }} {...props}>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { useWidgetStore } from './widget-store'
|
|
2
|
+
import type { WidgetState } from './types'
|
|
3
|
+
import { useShallow } from 'zustand/shallow'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Scoped selector hook for reading a single widget's state from the store.
|
|
7
|
+
*
|
|
8
|
+
* Consolidates multiple `useWidgetStore(useShallow(...))` calls into a single
|
|
9
|
+
* subscription per component. The selector receives only this widget's state
|
|
10
|
+
* (or undefined if not yet registered), and uses shallow comparison to avoid
|
|
11
|
+
* re-renders when unrelated properties change.
|
|
12
|
+
*
|
|
13
|
+
* @param widgetId - The widget ID to subscribe to.
|
|
14
|
+
* @param selector - A function that extracts the needed properties from the widget state.
|
|
15
|
+
* Must be a stable reference (inline arrow is fine due to useCallback wrapping).
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```tsx
|
|
19
|
+
* // Before: 4 separate subscriptions
|
|
20
|
+
* const title = useWidgetStore(useShallow((s) => s.getWidget(id)?.title))
|
|
21
|
+
* const collapsed = useWidgetStore(useShallow((s) => s.getWidget(id)?.collapsed))
|
|
22
|
+
* const disabled = useWidgetStore(useShallow((s) => s.getWidget(id)?.disabled))
|
|
23
|
+
* const isFetching = useWidgetStore(useShallow((s) => s.getWidget(id)?.isFetching))
|
|
24
|
+
*
|
|
25
|
+
* // After: 1 subscription
|
|
26
|
+
* const { title, collapsed, disabled, isFetching } = useWidgetSelector(id, (w) => ({
|
|
27
|
+
* title: w?.title, collapsed: w?.collapsed, disabled: w?.disabled, isFetching: w?.isFetching,
|
|
28
|
+
* }))
|
|
29
|
+
*
|
|
30
|
+
* // With extra dependencies (e.g., index prop):
|
|
31
|
+
* const value = useWidgetSelector(
|
|
32
|
+
* id,
|
|
33
|
+
* (w) => (w as MyState | undefined)?.data?.[index]?.value,
|
|
34
|
+
* [index],
|
|
35
|
+
* )
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export function useWidgetSelector<T>(
|
|
39
|
+
widgetId: string,
|
|
40
|
+
selector: (widget: WidgetState | undefined) => T,
|
|
41
|
+
): T {
|
|
42
|
+
return useWidgetStore(
|
|
43
|
+
useShallow((state: { widgets: Record<string, WidgetState> }) =>
|
|
44
|
+
selector(state.widgets[widgetId]),
|
|
45
|
+
),
|
|
46
|
+
)
|
|
47
|
+
}
|