@carto/ps-react-ui 4.3.8 → 4.3.10

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 (95) hide show
  1. package/dist/components.js +692 -700
  2. package/dist/components.js.map +1 -1
  3. package/dist/{lasso-tool-jl4YK02H.js → lasso-tool-BYbxrJ-7.js} +184 -190
  4. package/dist/lasso-tool-BYbxrJ-7.js.map +1 -0
  5. package/dist/{row-BKmVAUN5.js → row-DTCV0Ocm.js} +2 -2
  6. package/dist/row-DTCV0Ocm.js.map +1 -0
  7. package/dist/{series-D1pynfeh.js → series-CYNOu2Ju.js} +2 -2
  8. package/dist/{series-D1pynfeh.js.map → series-CYNOu2Ju.js.map} +1 -1
  9. package/dist/smart-tooltip-D4vwQpFf.js +37 -0
  10. package/dist/smart-tooltip-D4vwQpFf.js.map +1 -0
  11. package/dist/{styles-DrPyd0y5.js → styles-CAroD5Rc.js} +12 -12
  12. package/dist/styles-CAroD5Rc.js.map +1 -0
  13. package/dist/types/widgets/_shared/chart-config/option-builders.d.ts +1 -1
  14. package/dist/types/widgets/category/config.d.ts +3 -10
  15. package/dist/types/widgets/range/config.d.ts +0 -4
  16. package/dist/types/widgets/spread/config.d.ts +0 -5
  17. package/dist/types/widgets/table/components/cell.d.ts +4 -2
  18. package/dist/types/widgets/table/config.d.ts +1 -2
  19. package/dist/types/widgets/table/table-ui.d.ts +1 -1
  20. package/dist/types/widgets/table/types.d.ts +1 -1
  21. package/dist/types/widgets/wrapper/components/options.d.ts +1 -1
  22. package/dist/widgets/actions.js +607 -621
  23. package/dist/widgets/actions.js.map +1 -1
  24. package/dist/widgets/bar.js +48 -49
  25. package/dist/widgets/bar.js.map +1 -1
  26. package/dist/widgets/category.js +28 -28
  27. package/dist/widgets/category.js.map +1 -1
  28. package/dist/widgets/formula.js +3 -3
  29. package/dist/widgets/histogram.js +50 -50
  30. package/dist/widgets/histogram.js.map +1 -1
  31. package/dist/widgets/markdown.js +1 -1
  32. package/dist/widgets/pie.js +9 -9
  33. package/dist/widgets/pie.js.map +1 -1
  34. package/dist/widgets/range.js +1 -1
  35. package/dist/widgets/range.js.map +1 -1
  36. package/dist/widgets/scatterplot.js +13 -13
  37. package/dist/widgets/scatterplot.js.map +1 -1
  38. package/dist/widgets/spread.js +3 -3
  39. package/dist/widgets/spread.js.map +1 -1
  40. package/dist/widgets/table.js +242 -241
  41. package/dist/widgets/table.js.map +1 -1
  42. package/dist/widgets/timeseries.js +3 -3
  43. package/dist/widgets/timeseries.js.map +1 -1
  44. package/dist/widgets/wrapper.js +152 -162
  45. package/dist/widgets/wrapper.js.map +1 -1
  46. package/package.json +1 -1
  47. package/src/components/basemaps/basemaps.tsx +3 -1
  48. package/src/components/geolocation-controls/geolocation-controls.tsx +10 -6
  49. package/src/components/lasso-tool/lasso-tool-inline.tsx +6 -2
  50. package/src/components/lasso-tool/lasso-tool.tsx +9 -3
  51. package/src/components/list-data/list-data-skeleton.tsx +1 -1
  52. package/src/components/list-data/list-data.tsx +5 -3
  53. package/src/components/measurement-tools/measurement-tools.tsx +5 -1
  54. package/src/components/smart-tooltip/smart-tooltip.tsx +3 -1
  55. package/src/widgets/_shared/chart-config/option-builders.test.ts +2 -2
  56. package/src/widgets/_shared/chart-config/option-builders.ts +6 -4
  57. package/src/widgets/actions/download/download.test.tsx +6 -2
  58. package/src/widgets/actions/download/download.tsx +3 -1
  59. package/src/widgets/actions/fullscreen/fullscreen.tsx +8 -1
  60. package/src/widgets/actions/relative-data/relative-data.tsx +2 -6
  61. package/src/widgets/actions/searcher/searcher.tsx +0 -6
  62. package/src/widgets/bar/config.ts +8 -4
  63. package/src/widgets/bar/skeleton.tsx +1 -1
  64. package/src/widgets/category/components/category-row-multi.tsx +1 -1
  65. package/src/widgets/category/config.ts +1 -11
  66. package/src/widgets/formula/components/row.tsx +1 -1
  67. package/src/widgets/histogram/config.ts +7 -2
  68. package/src/widgets/histogram/skeleton.tsx +2 -2
  69. package/src/widgets/pie/skeleton.tsx +1 -1
  70. package/src/widgets/range/config.ts +0 -5
  71. package/src/widgets/scatterplot/skeleton.tsx +2 -2
  72. package/src/widgets/spread/config.ts +0 -6
  73. package/src/widgets/table/components/cell.tsx +5 -3
  74. package/src/widgets/table/components/row.tsx +6 -1
  75. package/src/widgets/table/config.ts +1 -1
  76. package/src/widgets/table/table-ui.tsx +1 -1
  77. package/src/widgets/table/types.ts +1 -1
  78. package/src/widgets/timeseries/skeleton.tsx +1 -1
  79. package/src/widgets/wrapper/components/actions.test.tsx +6 -2
  80. package/src/widgets/wrapper/components/actions.tsx +3 -1
  81. package/src/widgets/wrapper/components/options.test.tsx +12 -4
  82. package/src/widgets/wrapper/components/options.tsx +8 -3
  83. package/src/widgets/wrapper/wrapper-ui.tsx +5 -2
  84. package/src/widgets/wrapper/wrapper.tsx +2 -4
  85. package/dist/lasso-tool-jl4YK02H.js.map +0 -1
  86. package/dist/row-BKmVAUN5.js.map +0 -1
  87. package/dist/smart-tooltip-BEtBaIdz.js +0 -39
  88. package/dist/smart-tooltip-BEtBaIdz.js.map +0 -1
  89. package/dist/styles-DrPyd0y5.js.map +0 -1
  90. package/dist/types/widgets/actions/relative-data/style.d.ts +0 -8
  91. package/dist/types/widgets/actions/zoom-toggle/index.d.ts +0 -2
  92. package/dist/types/widgets/table/components/index.d.ts +0 -4
  93. package/src/widgets/actions/relative-data/style.ts +0 -9
  94. package/src/widgets/actions/zoom-toggle/index.ts +0 -2
  95. package/src/widgets/table/components/index.ts +0 -4
@@ -12,8 +12,10 @@ const DEFAULT_LABELS = {
12
12
  showLess: 'Show Less',
13
13
  } as const
14
14
 
15
+ const EMPTY_DATA: ListDataProps['data'] = []
16
+
15
17
  export function ListDataUI({
16
- data = [],
18
+ data = EMPTY_DATA,
17
19
  isLoading = false,
18
20
  maxItems = 5,
19
21
  labels = DEFAULT_LABELS,
@@ -47,11 +49,11 @@ export function ListDataUI({
47
49
  return (
48
50
  <>
49
51
  <List id='expandable-list' role='list'>
50
- {items.map((item, idx) => (
52
+ {items.map((item) => (
51
53
  <SmartTooltip<HTMLLIElement>
52
54
  // Default tooltip props
53
55
  followCursor={false}
54
- key={`list-item-${idx}`}
56
+ key={item.id}
55
57
  placement='top'
56
58
  arrow
57
59
  title={item.tooltipTitle}
@@ -49,6 +49,10 @@ import type {
49
49
  } from '../types'
50
50
  import { Tooltip } from '../tooltip/tooltip'
51
51
 
52
+ const EMPTY_PAPER_PROPS: NonNullable<
53
+ MeasurementToolsComponentProps['PaperProps']
54
+ > = {}
55
+
52
56
  export function MeasurementToolsUI({
53
57
  enabled,
54
58
  actionProps,
@@ -57,7 +61,7 @@ export function MeasurementToolsUI({
57
61
  modesMapping = DEFAULT_MEASUREMENT_TOOLS_MODES_MAPPING,
58
62
  unitsMapping = DEFAULT_MEASUREMENT_TOOLS_UNITS_MAPPING,
59
63
  modeSelected,
60
- PaperProps: { sx, ...PaperProps } = {},
64
+ PaperProps: { sx, ...PaperProps } = EMPTY_PAPER_PROPS,
61
65
  units,
62
66
  unitSelected,
63
67
  onActionToggle,
@@ -2,9 +2,11 @@ import type { TooltipProps } from '@mui/material'
2
2
  import { useLayoutEffect, useRef, useState, type JSX, type Ref } from 'react'
3
3
  import { Tooltip } from '../tooltip/tooltip'
4
4
 
5
+ const EMPTY_DEPENDENCIES: unknown[] = []
6
+
5
7
  export function SmartTooltip<T extends HTMLElement>({
6
8
  title,
7
- dependencies = [],
9
+ dependencies = EMPTY_DEPENDENCIES,
8
10
  timeout = 500,
9
11
  TooltipProps,
10
12
  children,
@@ -6,8 +6,8 @@ describe('niceNum', () => {
6
6
  expect(niceNum(0)).toBe(0)
7
7
  })
8
8
 
9
- it('should return 0 for negative values', () => {
10
- expect(niceNum(-5)).toBe(0)
9
+ it('should return the same negative value for negative numbers', () => {
10
+ expect(niceNum(-5)).toBe(-5)
11
11
  })
12
12
 
13
13
  it('should round 547 to 600', () => {
@@ -13,12 +13,14 @@ import type {
13
13
  * Rounds a value up to the nearest "nice" number.
14
14
  * A nice number is a multiple of 10^floor(log10(value)).
15
15
  *
16
- * Examples: 547 → 600, 200 → 200, 1200 → 2000, 18 → 20, 5 → 5
16
+ * Examples: 547 → 600, 200 → 200, 1200 → 2000, 18 → 20, 5 → 5, -547 → -500
17
17
  */
18
18
  export function niceNum(value: number): number {
19
- if (value <= 0) return 0
20
- const base = Math.pow(10, Math.floor(Math.log10(value)))
21
- return Math.ceil(value / base) * base
19
+ if (value === 0) return 0
20
+ const absValue = Math.abs(value)
21
+ const base = Math.pow(10, Math.floor(Math.log10(absValue)))
22
+ const rounded = Math.ceil(absValue / base) * base
23
+ return value < 0 ? -rounded : rounded
22
24
  }
23
25
 
24
26
  /**
@@ -271,8 +271,12 @@ describe('Download', () => {
271
271
  const parentClickHandler = vi.fn()
272
272
 
273
273
  render(
274
- // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
275
- <div onClick={parentClickHandler}>
274
+ <div
275
+ role='button'
276
+ tabIndex={0}
277
+ onClick={parentClickHandler}
278
+ onKeyDown={parentClickHandler}
279
+ >
276
280
  <Download id={widgetId} items={mockDownload} />
277
281
  </div>,
278
282
  )
@@ -12,10 +12,12 @@ import { useState, type MouseEvent } from 'react'
12
12
  import { useWidgetStore } from '../../../widgets'
13
13
  import { useShallow } from 'zustand/shallow'
14
14
 
15
+ const EMPTY_LABELS: NonNullable<DownloadProps['labels']> = {}
16
+
15
17
  export function Download({
16
18
  id,
17
19
  items,
18
- labels = {},
20
+ labels = EMPTY_LABELS,
19
21
  Icon,
20
22
  IconButtonProps,
21
23
  }: DownloadProps) {
@@ -15,13 +15,20 @@ import type {
15
15
  import { styles } from './styles'
16
16
  import { useShallow } from 'zustand/shallow'
17
17
 
18
+ const EMPTY_DIALOG_CONTENT_PROPS: NonNullable<
19
+ FullScreenProps['DialogContentProps']
20
+ > = {}
21
+
18
22
  export function FullScreen({
19
23
  id,
20
24
  labels,
21
25
  children,
22
26
  Icon,
23
27
  IconButtonProps,
24
- DialogContentProps: { sx, ...DialogContentProps } = {},
28
+ DialogContentProps: {
29
+ sx,
30
+ ...DialogContentProps
31
+ } = EMPTY_DIALOG_CONTENT_PROPS,
25
32
  DialogProps,
26
33
  }: FullScreenProps) {
27
34
  const isFullScreen = useWidgetStore(
@@ -73,13 +73,9 @@ export function RelativeData({
73
73
  return () => unregisterTool(id, RELATIVE_DATA_TOOL_ID)
74
74
  }, [id, order, registerTool, unregisterTool, isRelative])
75
75
 
76
- // Update enabled flag when toggle changes
77
- useEffect(() => {
78
- setToolEnabled(id, RELATIVE_DATA_TOOL_ID, isRelative)
79
- }, [id, isRelative, setToolEnabled])
80
-
81
76
  const handleToggle = useCallback(() => {
82
77
  const newIsRelative = !isRelative
78
+ setToolEnabled(id, RELATIVE_DATA_TOOL_ID, newIsRelative)
83
79
  let max = previousMaxValue.current
84
80
 
85
81
  if (newIsRelative) {
@@ -110,7 +106,7 @@ export function RelativeData({
110
106
  }
111
107
  : originalFormatter.current,
112
108
  })
113
- }, [isRelative, setWidget, id, getWidget])
109
+ }, [isRelative, setWidget, id, getWidget, setToolEnabled])
114
110
 
115
111
  const tooltipLabel = isRelative
116
112
  ? (labels?.absolute ?? 'Show absolute values')
@@ -53,7 +53,6 @@ export function Searcher({
53
53
  const setWidget = useWidgetStore((state) => state.setWidget)
54
54
  const registerTool = useWidgetStore((state) => state.registerTool)
55
55
  const unregisterTool = useWidgetStore((state) => state.unregisterTool)
56
- const setToolEnabled = useWidgetStore((state) => state.setToolEnabled)
57
56
  const updateToolConfig = useWidgetStore((state) => state.updateToolConfig)
58
57
 
59
58
  const setSearchText = useCallback(
@@ -85,11 +84,6 @@ export function Searcher({
85
84
  return () => unregisterTool(id, SEARCHER_TOOL_ID)
86
85
  }, [id, order, filter, registerTool, unregisterTool, enabled, searchText])
87
86
 
88
- // Update enabled flag when it changes
89
- useEffect(() => {
90
- setToolEnabled(id, SEARCHER_TOOL_ID, enabled)
91
- }, [id, enabled, setToolEnabled])
92
-
93
87
  // Update config when search text changes (debounced)
94
88
  const debouncedUpdateConfig = useCallback(
95
89
  (text: string) => {
@@ -47,6 +47,7 @@ function getOption({
47
47
  }: BarConfig): EchartOptionsProps {
48
48
  const hasLegend = (data?.length ?? 0) > 1
49
49
 
50
+ let niceMin = 0
50
51
  let niceMax = 1
51
52
 
52
53
  return {
@@ -67,23 +68,26 @@ function getOption({
67
68
  },
68
69
  yAxis: {
69
70
  type: 'value' as const,
70
- min: 0,
71
+ min: (extent: { min: number }) => {
72
+ niceMin = extent.min < 0 ? niceNum(extent.min) : 0
73
+ return niceMin
74
+ },
71
75
  max: (extent: { min: number; max: number }) => {
72
76
  niceMax = extent.max <= 0 ? 1 : niceNum(extent.max)
73
77
  return niceMax
74
78
  },
75
- splitNumber: 1,
76
79
  axisLabel: {
77
80
  fontSize: theme.typography.overlineDelicate.fontSize,
78
81
  fontFamily: theme.typography.overlineDelicate.fontFamily,
79
82
  margin: parseInt(theme.spacing(1)),
80
83
  show: true,
81
84
  showMaxLabel: true,
82
- showMinLabel: false,
85
+ showMinLabel: true,
83
86
  verticalAlign: 'bottom' as const,
84
87
  inside: true,
85
88
  formatter: (value: number) => {
86
- if (value !== niceMax) return ''
89
+ if (value !== niceMax && value !== niceMin) return ''
90
+ if (value === 0) return ''
87
91
  return formatter ? formatter(value) : String(value)
88
92
  },
89
93
  },
@@ -43,7 +43,7 @@ export function BarSkeleton() {
43
43
  .fill(0)
44
44
  .map((_, i) => (
45
45
  <Box
46
- key={i}
46
+ key={`skeleton-${i}`}
47
47
  sx={{
48
48
  display: 'flex',
49
49
  alignItems: 'center',
@@ -30,7 +30,7 @@ export function CategoryRowMulti({
30
30
  <Typography sx={styles.rowLabel}>{name}</Typography>
31
31
  <Box sx={styles.barContainer}>
32
32
  {values.map((value, index) => (
33
- <Box key={`${name}-${value}-${index}`} sx={styles.multiBarRow}>
33
+ <Box key={`${name}-${value}`} sx={styles.multiBarRow}>
34
34
  <Box sx={styles.multiBarContainer}>
35
35
  <CategoryBar
36
36
  value={value}
@@ -4,22 +4,12 @@ import type {
4
4
  CategoryWidgetConfig,
5
5
  CategoryWidgetData,
6
6
  CategorySeriesConfig,
7
- CategoryLabels,
8
7
  } from './types'
9
8
 
10
- export interface CategoryDownloadConfigProps extends ConfigProps {
9
+ interface CategoryDownloadConfigProps extends ConfigProps {
11
10
  series?: CategorySeriesConfig[]
12
11
  }
13
12
 
14
- export interface CategoryConfigProps {
15
- data?: CategoryWidgetData
16
- series?: CategorySeriesConfig[]
17
- formatter?: (value: number) => string
18
- maxItems?: number
19
- labels?: CategoryLabels
20
- max?: number
21
- }
22
-
23
13
  export function categoryDownloadConfig({
24
14
  refUI,
25
15
  series,
@@ -11,7 +11,7 @@ export function Row(props: RowProps) {
11
11
 
12
12
  return data?.map((_, index) => {
13
13
  return (
14
- <Box sx={styles.row} key={index}>
14
+ <Box sx={styles.row} key={`row-${index}`}>
15
15
  {typeof props.children === 'function'
16
16
  ? props.children({ index })
17
17
  : props.children}
@@ -49,6 +49,7 @@ function getOption({
49
49
  }: HistogramConfig): EchartOptionsProps {
50
50
  const hasLegend = (data?.length ?? 0) > 1
51
51
 
52
+ let niceMin = 0
52
53
  let niceMax = 1
53
54
 
54
55
  return {
@@ -86,7 +87,10 @@ function getOption({
86
87
  },
87
88
  yAxis: {
88
89
  type: 'value' as const,
89
- min: 0,
90
+ min: (extent: { min: number }) => {
91
+ niceMin = extent.min < 0 ? niceNum(extent.min) : 0
92
+ return niceMin
93
+ },
90
94
  max: (extent: { min: number; max: number }) => {
91
95
  niceMax = extent.max <= 0 ? 1 : niceNum(extent.max)
92
96
  return niceMax
@@ -101,7 +105,8 @@ function getOption({
101
105
  showMinLabel: true,
102
106
  verticalAlign: 'bottom' as const,
103
107
  formatter: (value: number) => {
104
- if (value !== niceMax) return ''
108
+ if (value !== niceMax && value !== niceMin) return ''
109
+ if (value === 0) return ''
105
110
  return formatter ? formatter(value) : String(value)
106
111
  },
107
112
  },
@@ -22,7 +22,7 @@ export function HistogramSkeleton() {
22
22
  const height = `${heights[i]}%`
23
23
  return (
24
24
  <Skeleton
25
- key={i}
25
+ key={`skeleton-bar-${i}`}
26
26
  variant='rectangular'
27
27
  sx={{
28
28
  flex: 1,
@@ -44,7 +44,7 @@ export function HistogramSkeleton() {
44
44
  {Array(4)
45
45
  .fill(0)
46
46
  .map((_, i) => (
47
- <Skeleton key={i} width={32} height={8} />
47
+ <Skeleton key={`skeleton-label-${i}`} width={32} height={8} />
48
48
  ))}
49
49
  </Box>
50
50
  </Box>
@@ -53,7 +53,7 @@ export function PieSkeleton() {
53
53
  .fill(0)
54
54
  .map((_, i) => (
55
55
  <Box
56
- key={i}
56
+ key={`skeleton-${i}`}
57
57
  sx={{
58
58
  display: 'flex',
59
59
  alignItems: 'center',
@@ -1,10 +1,5 @@
1
1
  import type { RangeWidgetConfig } from './types'
2
2
 
3
- export interface RangeConfigProps {
4
- formatter?: (value: number) => string
5
- onChange?: (value: number[], index: number) => void
6
- }
7
-
8
3
  export function rangeConfig(): RangeWidgetConfig {
9
4
  return {}
10
5
  }
@@ -24,7 +24,7 @@ export function ScatterplotSkeleton() {
24
24
  <Box sx={styles.skeleton.graph.container}>
25
25
  {SCATTER_POINTS.map((point, index) => (
26
26
  <Skeleton
27
- key={index}
27
+ key={`skeleton-point-${index}`}
28
28
  variant='circular'
29
29
  width={12}
30
30
  height={12}
@@ -42,7 +42,7 @@ export function ScatterplotSkeleton() {
42
42
  .fill(0)
43
43
  .map((_, i) => (
44
44
  <Box
45
- key={i}
45
+ key={`skeleton-${i}`}
46
46
  sx={{
47
47
  display: 'flex',
48
48
  alignItems: 'center',
@@ -1,12 +1,6 @@
1
1
  import { downloadToCSV, downloadToPNG, type DownloadItem } from '../actions'
2
2
  import type { ConfigProps } from '../loader/types'
3
3
  import type { SpreadWidgetConfig, SpreadWidgetData } from './types'
4
- import type { SeriesConfig } from '../formula/types'
5
-
6
- export interface SpreadConfigProps {
7
- formatter?: (value: number) => string
8
- series?: SeriesConfig[]
9
- }
10
4
 
11
5
  export function spreadDownloadConfig({
12
6
  refUI,
@@ -1,6 +1,6 @@
1
1
  import { TableCell as MuiTableCell, Link, Typography } from '@mui/material'
2
2
  import ReactMarkdown, { type Components } from 'react-markdown'
3
- import type { TableColumn } from '../types'
3
+ import type { TableColumn, TableRow } from '../types'
4
4
  import { getCellValue } from '../helpers'
5
5
  import type { ReactNode } from 'react'
6
6
 
@@ -10,6 +10,8 @@ import type { ReactNode } from 'react'
10
10
  export interface CellProps {
11
11
  /** Column definition */
12
12
  column: TableColumn
13
+ /** Full table row for context */
14
+ row: TableRow
13
15
  /** Cell value */
14
16
  value: unknown
15
17
  }
@@ -54,12 +56,12 @@ const CELL_MARKDOWN_COMPONENTS: Components = {
54
56
  * Markdown is rendered when the raw value is a string and no formatter is defined.
55
57
  * If a formatter is used, its output is rendered directly without markdown parsing.
56
58
  */
57
- export function Cell({ column, value }: CellProps) {
59
+ export function Cell({ column, row, value }: CellProps) {
58
60
  // If formatter is defined, use it directly without markdown
59
61
  if (column.formatter) {
60
62
  return (
61
63
  <MuiTableCell align={column.align}>
62
- {column.formatter(value)}
64
+ {column.formatter(value, row)}
63
65
  </MuiTableCell>
64
66
  )
65
67
  }
@@ -53,7 +53,12 @@ export function Row({
53
53
  </TableCell>
54
54
  )}
55
55
  {columns.map((column) => (
56
- <Cell key={column.id} column={column} value={row[column.id]} />
56
+ <Cell
57
+ key={column.id}
58
+ row={row}
59
+ column={column}
60
+ value={row[column.id]}
61
+ />
57
62
  ))}
58
63
  </MuiTableRow>
59
64
  )
@@ -8,7 +8,7 @@ import type {
8
8
  SortDirection,
9
9
  } from './types'
10
10
 
11
- export const DEFAULT_MODE: Mode = 'local'
11
+ const DEFAULT_MODE: Mode = 'local'
12
12
  export const DEFAULT_PAGE = 0
13
13
  export const DEFAULT_ROWS_PER_PAGE = 10
14
14
  export const DEFAULT_ROWS_PER_PAGE_OPTIONS = [5, 10, 25, 50]
@@ -18,7 +18,7 @@ import type { TableUIProps } from './types'
18
18
  /**
19
19
  * Props for the base Table component
20
20
  */
21
- export interface TableComponentProps extends MuiTableProps {
21
+ interface TableComponentProps extends MuiTableProps {
22
22
  /** Table element ref */
23
23
  ref?: Ref<HTMLTableElement>
24
24
  }
@@ -27,7 +27,7 @@ export interface TableColumn {
27
27
  /** Enable sorting for this column */
28
28
  sortable?: boolean
29
29
  /** Custom cell value formatter */
30
- formatter?: (value: unknown) => ReactNode
30
+ formatter?: (value: unknown, row: TableRow) => ReactNode
31
31
  }
32
32
 
33
33
  /**
@@ -38,7 +38,7 @@ export function TimeseriesSkeleton() {
38
38
  .fill(0)
39
39
  .map((_, i) => (
40
40
  <Box
41
- key={i}
41
+ key={`skeleton-${i}`}
42
42
  sx={{
43
43
  display: 'flex',
44
44
  alignItems: 'center',
@@ -42,8 +42,12 @@ describe('Actions', () => {
42
42
  ]
43
43
 
44
44
  render(
45
- // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
46
- <div onClick={parentOnClick}>
45
+ <div
46
+ role='button'
47
+ tabIndex={0}
48
+ onClick={parentOnClick}
49
+ onKeyDown={parentOnClick}
50
+ >
47
51
  <Actions actions={actions} />
48
52
  </div>,
49
53
  )
@@ -2,7 +2,9 @@ import { Box } from '@mui/material'
2
2
  import type { WrapperActionsProps } from '../types'
3
3
  import { styles } from '../styles'
4
4
 
5
- export function Actions({ actions = [] }: WrapperActionsProps) {
5
+ const EMPTY_ACTIONS: NonNullable<WrapperActionsProps['actions']> = []
6
+
7
+ export function Actions({ actions = EMPTY_ACTIONS }: WrapperActionsProps) {
6
8
  return (
7
9
  <Box sx={styles.actions} className='widget-wrapper-actions'>
8
10
  {actions.map((action, index) => {
@@ -88,8 +88,12 @@ describe('Options', () => {
88
88
  ]
89
89
 
90
90
  render(
91
- // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
92
- <div onClick={parentOnClick}>
91
+ <div
92
+ role='button'
93
+ tabIndex={0}
94
+ onClick={parentOnClick}
95
+ onKeyDown={parentOnClick}
96
+ >
93
97
  <Options options={options} />
94
98
  </div>,
95
99
  )
@@ -112,8 +116,12 @@ describe('Options', () => {
112
116
  ]
113
117
 
114
118
  render(
115
- // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
116
- <div onClick={parentOnClick}>
119
+ <div
120
+ role='button'
121
+ tabIndex={0}
122
+ onClick={parentOnClick}
123
+ onKeyDown={parentOnClick}
124
+ >
117
125
  <Options options={options} />
118
126
  </div>,
119
127
  )
@@ -10,7 +10,12 @@ import type { WrapperOptionsProps } from '../types'
10
10
  import { useState } from 'react'
11
11
  import { styles } from '../styles'
12
12
 
13
- export function Options({ labels, options = [] }: WrapperOptionsProps) {
13
+ const EMPTY_OPTIONS: NonNullable<WrapperOptionsProps['options']> = []
14
+
15
+ export function Options({
16
+ labels,
17
+ options = EMPTY_OPTIONS,
18
+ }: WrapperOptionsProps) {
14
19
  const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null)
15
20
 
16
21
  const handleOptionAction = (
@@ -57,9 +62,9 @@ export function Options({ labels, options = [] }: WrapperOptionsProps) {
57
62
  },
58
63
  }}
59
64
  >
60
- {options.map((option, idx) => (
65
+ {options.map((option) => (
61
66
  <MenuItem
62
- key={`${option.label}-${idx}`}
67
+ key={option.label}
63
68
  disabled={option.disabled}
64
69
  onClick={(e) => handleOptionAction(e, option)}
65
70
  >
@@ -12,13 +12,16 @@ import { Actions } from './components/actions'
12
12
  import { useWidgetStore } from '../stores/widget-store'
13
13
  import { useShallow } from 'zustand/shallow'
14
14
 
15
+ const EMPTY_ACTIONS: NonNullable<WrapperUIProps['actions']> = []
16
+ const EMPTY_OPTIONS: NonNullable<WrapperUIProps['options']> = []
17
+
15
18
  export function WrapperUI({
16
19
  children,
17
20
  id,
18
- actions = [],
21
+ actions = EMPTY_ACTIONS,
19
22
  sx,
20
23
  labels,
21
- options = [],
24
+ options = EMPTY_OPTIONS,
22
25
  onChangeCollapsed,
23
26
  }: WrapperUIProps) {
24
27
  const title = useWidgetStore(
@@ -1,7 +1,7 @@
1
1
  import type { WrapperProps, WrapperState } from './types'
2
2
  import { WrapperUI } from './wrapper-ui'
3
3
  import { useWidgetStore } from '../stores/widget-store'
4
- import { useEffect } from 'react'
4
+ import { useLayoutEffect } from 'react'
5
5
 
6
6
  export function WidgetWrapper({
7
7
  id,
@@ -17,9 +17,7 @@ export function WidgetWrapper({
17
17
  }: WrapperProps) {
18
18
  const setWidget = useWidgetStore((state) => state.setWidget)
19
19
 
20
- // Consolidated effect: batch all wrapper state updates into a single store update
21
- // This prevents 3 separate re-render cycles on mount and prop changes
22
- useEffect(() => {
20
+ useLayoutEffect(() => {
23
21
  setWidget<WrapperState>(id, {
24
22
  collapsed: defaultCollapsed,
25
23
  disabled,