@carto/ps-react-ui 4.4.2 → 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.
Files changed (157) hide show
  1. package/dist/download-config-DemuQ3Jm.js +56 -0
  2. package/dist/download-config-DemuQ3Jm.js.map +1 -0
  3. package/dist/error-Cj8eUMrl.js +40 -0
  4. package/dist/error-Cj8eUMrl.js.map +1 -0
  5. package/dist/formatter-B9Bxn1k7.js +6 -0
  6. package/dist/formatter-B9Bxn1k7.js.map +1 -0
  7. package/dist/no-data-DkIt7Qt1.js +61 -0
  8. package/dist/no-data-DkIt7Qt1.js.map +1 -0
  9. package/dist/row-D4VOhcNI.js +34 -0
  10. package/dist/row-D4VOhcNI.js.map +1 -0
  11. package/dist/series-Bola3CmD.js +90 -0
  12. package/dist/series-Bola3CmD.js.map +1 -0
  13. package/dist/styles-Y8q7Jff3.js +118 -0
  14. package/dist/styles-Y8q7Jff3.js.map +1 -0
  15. package/dist/types/widgets/actions/brush-toggle/types.d.ts +8 -2
  16. package/dist/types/widgets/category/components/category-row-multi.d.ts +2 -1
  17. package/dist/types/widgets/category/components/category-row-single.d.ts +2 -1
  18. package/dist/types/widgets/category/types.d.ts +1 -0
  19. package/dist/types/widgets/echart/shared-resize-observer.d.ts +12 -0
  20. package/dist/types/widgets/echart/types.d.ts +2 -0
  21. package/dist/types/widgets/histogram/config.d.ts +15 -3
  22. package/dist/types/widgets/histogram/index.d.ts +2 -1
  23. package/dist/types/widgets/histogram/types.d.ts +6 -3
  24. package/dist/types/widgets/stores/index.d.ts +2 -1
  25. package/dist/types/widgets/stores/types.d.ts +2 -0
  26. package/dist/types/widgets/stores/use-widget-selector.d.ts +35 -0
  27. package/dist/types/widgets/stores/widget-store-performance.test.d.ts +1 -0
  28. package/dist/types/widgets/stores/widget-store.d.ts +49 -27
  29. package/dist/types/widgets/table/types.d.ts +1 -1
  30. package/dist/types/widgets/utils/chart-config/index.d.ts +1 -1
  31. package/dist/types/widgets/utils/chart-config/option-builders.d.ts +13 -8
  32. package/dist/types/widgets/utils/formatter.d.ts +1 -0
  33. package/dist/types/widgets/utils/index.d.ts +1 -1
  34. package/dist/use-widget-ref-BFazQvJK.js +22 -0
  35. package/dist/use-widget-ref-BFazQvJK.js.map +1 -0
  36. package/dist/use-widget-selector-DqRmWQ1K.js +12 -0
  37. package/dist/use-widget-selector-DqRmWQ1K.js.map +1 -0
  38. package/dist/widget-store-CIrb9RKP.js +263 -0
  39. package/dist/widget-store-CIrb9RKP.js.map +1 -0
  40. package/dist/widgets/actions.js +799 -817
  41. package/dist/widgets/actions.js.map +1 -1
  42. package/dist/widgets/bar.js +53 -47
  43. package/dist/widgets/bar.js.map +1 -1
  44. package/dist/widgets/category.js +261 -255
  45. package/dist/widgets/category.js.map +1 -1
  46. package/dist/widgets/echart.js +109 -99
  47. package/dist/widgets/echart.js.map +1 -1
  48. package/dist/widgets/error.js +1 -1
  49. package/dist/widgets/formula.js +71 -63
  50. package/dist/widgets/formula.js.map +1 -1
  51. package/dist/widgets/histogram.js +119 -80
  52. package/dist/widgets/histogram.js.map +1 -1
  53. package/dist/widgets/loader.js +53 -60
  54. package/dist/widgets/loader.js.map +1 -1
  55. package/dist/widgets/markdown.js +51 -50
  56. package/dist/widgets/markdown.js.map +1 -1
  57. package/dist/widgets/no-data.js +1 -1
  58. package/dist/widgets/pie.js +111 -99
  59. package/dist/widgets/pie.js.map +1 -1
  60. package/dist/widgets/range.js +146 -144
  61. package/dist/widgets/range.js.map +1 -1
  62. package/dist/widgets/scatterplot.js +50 -44
  63. package/dist/widgets/scatterplot.js.map +1 -1
  64. package/dist/widgets/skeleton-loader.js +18 -17
  65. package/dist/widgets/skeleton-loader.js.map +1 -1
  66. package/dist/widgets/spread.js +110 -94
  67. package/dist/widgets/spread.js.map +1 -1
  68. package/dist/widgets/stores.js +5 -2
  69. package/dist/widgets/stores.js.map +1 -1
  70. package/dist/widgets/table.js +422 -436
  71. package/dist/widgets/table.js.map +1 -1
  72. package/dist/widgets/timeseries.js +52 -46
  73. package/dist/widgets/timeseries.js.map +1 -1
  74. package/dist/widgets/toolbar-actions.js +101 -6693
  75. package/dist/widgets/toolbar-actions.js.map +1 -1
  76. package/dist/widgets/utils.js +16 -14
  77. package/dist/widgets/utils.js.map +1 -1
  78. package/dist/widgets/wrapper.js +156 -158
  79. package/dist/widgets/wrapper.js.map +1 -1
  80. package/dist/widgets.js +4 -4
  81. package/package.json +5 -4
  82. package/src/hooks/use-widget-ref.ts +3 -4
  83. package/src/widgets/README.md +3 -3
  84. package/src/widgets/actions/brush-toggle/brush-toggle.tsx +60 -79
  85. package/src/widgets/actions/brush-toggle/types.ts +8 -2
  86. package/src/widgets/actions/change-column/change-column.tsx +15 -15
  87. package/src/widgets/actions/change-column/sortable-column-item.tsx +3 -1
  88. package/src/widgets/actions/download/download.tsx +4 -3
  89. package/src/widgets/actions/fullscreen/fullscreen.tsx +7 -11
  90. package/src/widgets/actions/lock-selection/lock-selection.tsx +12 -15
  91. package/src/widgets/actions/relative-data/relative-data.tsx +22 -26
  92. package/src/widgets/actions/searcher/searcher-toggle.tsx +11 -12
  93. package/src/widgets/actions/searcher/searcher.tsx +20 -21
  94. package/src/widgets/actions/stack-toggle/stack-toggle.tsx +15 -21
  95. package/src/widgets/actions/zoom-toggle/zoom-toggle.tsx +27 -43
  96. package/src/widgets/bar/config.ts +22 -14
  97. package/src/widgets/category/category-ui.tsx +31 -27
  98. package/src/widgets/category/components/category-row-multi.tsx +6 -2
  99. package/src/widgets/category/components/category-row-single.tsx +5 -1
  100. package/src/widgets/category/types.ts +1 -0
  101. package/src/widgets/echart/echart-ui.test.tsx +20 -16
  102. package/src/widgets/echart/echart-ui.tsx +6 -12
  103. package/src/widgets/echart/echart.tsx +13 -27
  104. package/src/widgets/echart/shared-resize-observer.ts +45 -0
  105. package/src/widgets/echart/types.ts +2 -0
  106. package/src/widgets/error/error.tsx +7 -9
  107. package/src/widgets/formula/components/prefix.tsx +4 -6
  108. package/src/widgets/formula/components/row.tsx +4 -4
  109. package/src/widgets/formula/components/series.tsx +4 -6
  110. package/src/widgets/formula/components/suffix.tsx +4 -6
  111. package/src/widgets/formula/components/value.tsx +9 -16
  112. package/src/widgets/histogram/config.ts +101 -20
  113. package/src/widgets/histogram/index.ts +6 -1
  114. package/src/widgets/histogram/types.ts +9 -3
  115. package/src/widgets/loader/loader.tsx +31 -44
  116. package/src/widgets/markdown/markdown.tsx +4 -7
  117. package/src/widgets/no-data/no-data.tsx +7 -10
  118. package/src/widgets/pie/config.ts +17 -5
  119. package/src/widgets/range/components/range-item.tsx +20 -18
  120. package/src/widgets/scatterplot/config.ts +8 -3
  121. package/src/widgets/skeleton-loader/skeleton-loader.tsx +2 -5
  122. package/src/widgets/spread/components/max-value.tsx +14 -16
  123. package/src/widgets/spread/components/min-value.tsx +14 -16
  124. package/src/widgets/stores/index.ts +2 -1
  125. package/src/widgets/stores/types.ts +2 -0
  126. package/src/widgets/stores/use-widget-selector.ts +47 -0
  127. package/src/widgets/stores/widget-store-performance.test.ts +750 -0
  128. package/src/widgets/stores/widget-store.test.ts +81 -0
  129. package/src/widgets/stores/widget-store.ts +225 -44
  130. package/src/widgets/table/config.ts +0 -1
  131. package/src/widgets/table/hooks/use-pagination.ts +28 -52
  132. package/src/widgets/table/hooks/use-selection.ts +20 -24
  133. package/src/widgets/table/hooks/use-sort.ts +22 -39
  134. package/src/widgets/table/types.ts +1 -1
  135. package/src/widgets/timeseries/config.ts +21 -13
  136. package/src/widgets/utils/chart-config/index.ts +1 -1
  137. package/src/widgets/utils/chart-config/option-builders.ts +22 -12
  138. package/src/widgets/utils/formatter.ts +2 -1
  139. package/src/widgets/utils/index.ts +1 -1
  140. package/src/widgets/wrapper/wrapper-ui.tsx +12 -13
  141. package/src/widgets/wrapper/wrapper.tsx +4 -6
  142. package/dist/error-CEkRPccv.js +0 -39
  143. package/dist/error-CEkRPccv.js.map +0 -1
  144. package/dist/formatter-B1Xh8XDH.js +0 -5
  145. package/dist/formatter-B1Xh8XDH.js.map +0 -1
  146. package/dist/no-data-hR3KcJ-_.js +0 -60
  147. package/dist/no-data-hR3KcJ-_.js.map +0 -1
  148. package/dist/row-DTCV0Ocm.js +0 -35
  149. package/dist/row-DTCV0Ocm.js.map +0 -1
  150. package/dist/series-CYNOu2Ju.js +0 -91
  151. package/dist/series-CYNOu2Ju.js.map +0 -1
  152. package/dist/styles-C_8vOEep.js +0 -167
  153. package/dist/styles-C_8vOEep.js.map +0 -1
  154. package/dist/use-widget-ref-wtFLDFCD.js +0 -25
  155. package/dist/use-widget-ref-wtFLDFCD.js.map +0 -1
  156. package/dist/widget-store-CzDt8oSK.js +0 -163
  157. package/dist/widget-store-CzDt8oSK.js.map +0 -1
@@ -29,6 +29,7 @@ export function pieConfig(props: PieConfig): PieWidgetConfig {
29
29
  type: 'pie',
30
30
  option: mergeEchartWidgetConfig(getCommonOptions(props), getOption(props)),
31
31
  formatter: props.formatter,
32
+ labelFormatter: props.labelFormatter,
32
33
  }
33
34
  }
34
35
 
@@ -36,6 +37,7 @@ function getOption({
36
37
  data = [],
37
38
  theme,
38
39
  formatter,
40
+ labelFormatter,
39
41
  }: PieConfig): EchartOptionsProps {
40
42
  const multiSeries = (data?.length ?? 0) > 1
41
43
 
@@ -44,7 +46,7 @@ function getOption({
44
46
  let niceMax = 1
45
47
 
46
48
  return {
47
- legend: buildLegendConfig(true),
49
+ legend: buildLegendConfig({ hasLegend: true, labelFormatter }),
48
50
  grid: {
49
51
  ...buildGridConfig(true, theme),
50
52
  right: parseInt(theme.spacing(4)),
@@ -97,6 +99,10 @@ function getOption({
97
99
  },
98
100
  axisLabel: {
99
101
  padding: [parseInt(theme.spacing(0.5)), 0, 0, 0],
102
+ ...(labelFormatter && {
103
+ formatter: (value: string | number) =>
104
+ String(labelFormatter(value)),
105
+ }),
100
106
  },
101
107
  },
102
108
  tooltip: {
@@ -113,7 +119,9 @@ function getOption({
113
119
 
114
120
  const marker = typeof item.marker === 'string' ? item.marker : ''
115
121
  const seriesName = item.seriesName ? `${item.seriesName}: ` : ''
116
- const name = item.name ?? ''
122
+ const name = labelFormatter
123
+ ? String(labelFormatter(item.name ?? ''))
124
+ : (item.name ?? '')
117
125
 
118
126
  return { name, seriesName, marker, value: formattedValue }
119
127
  }),
@@ -132,7 +140,7 @@ function getOption({
132
140
  const hasLegend = true
133
141
 
134
142
  return {
135
- legend: buildLegendConfig(hasLegend),
143
+ legend: buildLegendConfig({ hasLegend, labelFormatter }),
136
144
  grid: {
137
145
  ...buildGridConfig(hasLegend, theme),
138
146
  left: 0,
@@ -164,7 +172,9 @@ function getOption({
164
172
 
165
173
  const marker = typeof item.marker === 'string' ? item.marker : ''
166
174
  const seriesName = item.seriesName ? `${item.seriesName}: ` : ''
167
- const name = item.name ?? ''
175
+ const name = labelFormatter
176
+ ? String(labelFormatter(item.name ?? ''))
177
+ : (item.name ?? '')
168
178
 
169
179
  return {
170
180
  name: seriesName,
@@ -203,7 +213,9 @@ function getOption({
203
213
  ? formatter(value)
204
214
  : (value ?? '')
205
215
 
206
- const nameFormatted = name
216
+ const nameFormatted = labelFormatter
217
+ ? String(labelFormatter(name ?? ''))
218
+ : (name ?? '')
207
219
 
208
220
  return `{c|${formattedValue}}\n\n{b|${nameFormatted}}`
209
221
  },
@@ -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 { useWidgetStore } from '../../stores/widget-store'
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 item = useWidgetStore(
17
- useShallow((state) => state.getWidget<RangeWidgetState>(id)?.data[index]),
18
- )
19
- const onChange = useWidgetStore(
20
- useShallow((state) => state.getWidget<RangeWidgetState>(id)?.onChange),
21
- )
22
- const formatter =
23
- useWidgetStore(
24
- useShallow((state) => state.getWidget<RangeWidgetState>(id)?.formatter),
25
- ) ?? defaultFormatter
26
- const getWidget = useWidgetStore((store) => store.getWidget)
27
- const setWidget = useWidgetStore((store) => store.setWidget)
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 = getWidget<RangeWidgetState>(id)?.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
  ? {
@@ -14,6 +14,7 @@ import {
14
14
  createTooltipFormatter,
15
15
  createChartDownloadConfig,
16
16
  applyYAxisFormatter,
17
+ applyXAxisFormatter,
17
18
  } from '../utils/chart-config'
18
19
 
19
20
  export const scatterplotDownloadConfig =
@@ -32,6 +33,7 @@ export function scatterplotConfig(
32
33
  type: 'scatterplot',
33
34
  option: mergeEchartWidgetConfig(getCommonOptions(props), getOption(props)),
34
35
  formatter: props.formatter,
36
+ labelFormatter: props.labelFormatter,
35
37
  }
36
38
  }
37
39
 
@@ -39,6 +41,7 @@ function getOption({
39
41
  data = [],
40
42
  theme,
41
43
  formatter,
44
+ labelFormatter,
42
45
  }: ScatterplotConfig): EchartOptionsProps {
43
46
  const hasLegend = data.length > 1
44
47
 
@@ -94,12 +97,12 @@ function getOption({
94
97
  }
95
98
 
96
99
  return {
97
- legend: buildLegendConfig(hasLegend),
100
+ legend: buildLegendConfig({ hasLegend, labelFormatter }),
98
101
  grid: {
99
102
  ...(!hasLegend && { bottom: parseInt(theme.spacing(1)) }),
100
103
  ...(hasLegend && { bottom: parseInt(theme.spacing(10)) }),
101
104
  },
102
- xAxis,
105
+ xAxis: applyXAxisFormatter(xAxis, labelFormatter),
103
106
  yAxis: applyYAxisFormatter(yAxis, formatter),
104
107
  tooltip: {
105
108
  trigger: 'item',
@@ -114,7 +117,9 @@ function getOption({
114
117
  ? formatter(_value)
115
118
  : (_value ?? '')
116
119
  const marker = typeof item.marker === 'string' ? item.marker : ''
117
- const name = item.seriesName ?? ''
120
+ const name = labelFormatter
121
+ ? String(labelFormatter(item.seriesName ?? ''))
122
+ : (item.seriesName ?? '')
118
123
 
119
124
  return { name, seriesName: '', marker, value: formattedValue }
120
125
  }),
@@ -1,7 +1,6 @@
1
1
  import type { SkeletonLoaderProps } from './types'
2
- import { useWidgetStore } from '../stores/widget-store'
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 = useWidgetStore(
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 { useWidgetStore } from '../../stores/widget-store'
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 max = useWidgetStore(
12
- useShallow(
13
- (state) => state.getWidget<SpreadWidgetState>(id)?.data[index]?.max,
14
- ),
15
- )
16
- const color = useWidgetStore(
17
- useShallow(
18
- (state) => state.getWidget<SpreadWidgetState>(id)?.data[index]?.color,
19
- ),
20
- )
21
- const formatter =
22
- useWidgetStore(
23
- useShallow((state) => state.getWidget<SpreadWidgetState>(id)?.formatter),
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 { useWidgetStore } from '../../stores/widget-store'
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 min = useWidgetStore(
12
- useShallow(
13
- (state) => state.getWidget<SpreadWidgetState>(id)?.data[index]?.min,
14
- ),
15
- )
16
- const color = useWidgetStore(
17
- useShallow(
18
- (state) => state.getWidget<SpreadWidgetState>(id)?.data[index]?.color,
19
- ),
20
- )
21
- const formatter =
22
- useWidgetStore(
23
- useShallow((state) => state.getWidget<SpreadWidgetState>(id)?.formatter),
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}>
@@ -1,4 +1,5 @@
1
- export { useWidgetStore } from './widget-store'
1
+ export { useWidgetStore, widgetStoreActions } from './widget-store'
2
+ export { useWidgetSelector } from './use-widget-selector'
2
3
  export type {
3
4
  BaseWidgetState,
4
5
  ToolType,
@@ -31,6 +31,8 @@ export interface WidgetsStoreProps {
31
31
  registeredTools?: ToolRegistration[]
32
32
  /** Formatter function for widget values */
33
33
  formatter?: (value: number) => string
34
+ /** Formatter function for widget label/category values */
35
+ labelFormatter?: (value: string | number) => string | number
34
36
  /** Locale for number formatting (e.g., 'en-US', 'es-ES', 'fr-FR') */
35
37
  locale?: string
36
38
  }
@@ -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
+ }