@carto/ps-react-ui 4.3.3 → 4.3.5
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/components.js +3 -3
- package/dist/components.js.map +1 -1
- package/dist/{lasso-tool-BwRzEW7k.js → lasso-tool-wFqOD6wk.js} +13 -13
- package/dist/lasso-tool-wFqOD6wk.js.map +1 -0
- package/dist/types/components/common-types.d.ts +41 -0
- package/dist/types/components/types.d.ts +1 -1
- package/dist/types/widgets/echart/echart-ui.d.ts +1 -1
- package/dist/types/widgets/echart/types.d.ts +4 -0
- package/dist/widgets/actions.js +1 -1
- package/dist/widgets/bar.js +1 -1
- package/dist/widgets/category.js +1 -1
- package/dist/widgets/echart.js +96 -85
- package/dist/widgets/echart.js.map +1 -1
- package/dist/widgets/formula.js +1 -1
- package/dist/widgets/histogram.js +1 -1
- package/dist/widgets/markdown.js +1 -1
- package/dist/widgets/pie.js +1 -1
- package/dist/widgets/scatterplot.js +1 -1
- package/dist/widgets/spread.js +1 -1
- package/dist/widgets/table.js +1 -1
- package/dist/widgets/timeseries.js +1 -1
- package/dist/widgets/toolbar-actions.js.map +1 -1
- package/dist/widgets/wrapper.js +1 -1
- package/package.json +8 -3
- package/src/components/basemaps/basemaps.test.tsx +196 -0
- package/src/components/basemaps/basemaps.tsx +128 -0
- package/src/components/basemaps/const.ts +13 -0
- package/src/components/basemaps/group-wrapper.test.tsx +38 -0
- package/src/components/basemaps/group-wrapper.tsx +28 -0
- package/src/components/basemaps/group.test.tsx +52 -0
- package/src/components/basemaps/group.tsx +42 -0
- package/src/components/basemaps/header.test.tsx +54 -0
- package/src/components/basemaps/header.tsx +36 -0
- package/src/components/basemaps/styles.ts +76 -0
- package/src/components/basemaps/types.ts +30 -0
- package/src/components/common-types.ts +1 -0
- package/src/components/geolocation-controls/const.ts +6 -0
- package/src/components/geolocation-controls/geolocation-controls.test.tsx +133 -0
- package/src/components/geolocation-controls/geolocation-controls.tsx +95 -0
- package/src/components/geolocation-controls/types.ts +17 -0
- package/src/components/index.ts +64 -0
- package/src/components/lasso-tool/chip.tsx +37 -0
- package/src/components/lasso-tool/const.tsx +70 -0
- package/src/components/lasso-tool/icons.tsx +91 -0
- package/src/components/lasso-tool/lasso-tool-inline.test.tsx +168 -0
- package/src/components/lasso-tool/lasso-tool-inline.tsx +245 -0
- package/src/components/lasso-tool/lasso-tool.test.tsx +212 -0
- package/src/components/lasso-tool/lasso-tool.tsx +479 -0
- package/src/components/lasso-tool/styles.ts +143 -0
- package/src/components/lasso-tool/types.ts +114 -0
- package/src/components/list-data/list-data-skeleton.test.tsx +10 -0
- package/src/components/list-data/list-data-skeleton.tsx +40 -0
- package/src/components/list-data/list-data.test.tsx +94 -0
- package/src/components/list-data/list-data.tsx +106 -0
- package/src/components/list-data/styles.ts +37 -0
- package/src/components/list-data/types.ts +25 -0
- package/src/components/measurement-tools/const.tsx +108 -0
- package/src/components/measurement-tools/icons.tsx +54 -0
- package/src/components/measurement-tools/measurement-tools.test.tsx +165 -0
- package/src/components/measurement-tools/measurement-tools.tsx +443 -0
- package/src/components/measurement-tools/styles.ts +91 -0
- package/src/components/measurement-tools/types.ts +77 -0
- package/src/components/no-data-alert/no-data-alert.test.tsx +31 -0
- package/src/components/no-data-alert/no-data-alert.tsx +59 -0
- package/src/components/responsive-drawer/responsive-drawer.test.tsx +91 -0
- package/src/components/responsive-drawer/responsive-drawer.tsx +53 -0
- package/src/components/smart-tooltip/smart-tooltip.test.tsx +168 -0
- package/src/components/smart-tooltip/smart-tooltip.tsx +40 -0
- package/src/components/tooltip/tooltip.test.tsx +86 -0
- package/src/components/tooltip/tooltip.tsx +30 -0
- package/src/components/types.ts +1 -0
- package/src/components/zoom-controls/styles.ts +27 -0
- package/src/components/zoom-controls/types.ts +19 -0
- package/src/components/zoom-controls/zoom-controls.test.tsx +101 -0
- package/src/components/zoom-controls/zoom-controls.tsx +114 -0
- package/src/hooks/index.ts +2 -0
- package/src/hooks/use-debounce.ts +55 -0
- package/src/hooks/use-skeleton.test.tsx +32 -0
- package/src/hooks/use-skeleton.ts +19 -0
- package/src/hooks/use-widget-ref.ts +33 -0
- package/src/widgets/README.md +277 -0
- package/src/widgets/_shared/chart-config/config-factory.ts +67 -0
- package/src/widgets/_shared/chart-config/csv-modifiers.ts +56 -0
- package/src/widgets/_shared/chart-config/index.ts +21 -0
- package/src/widgets/_shared/chart-config/option-builders.ts +203 -0
- package/src/widgets/_shared/skeleton/index.ts +5 -0
- package/src/widgets/_shared/skeleton/styles.ts +20 -0
- package/src/widgets/actions/change-column/change-column-icon.tsx +10 -0
- package/src/widgets/actions/change-column/change-column.test.tsx +163 -0
- package/src/widgets/actions/change-column/change-column.tsx +141 -0
- package/src/widgets/actions/change-column/sortable-column-item.tsx +49 -0
- package/src/widgets/actions/change-column/types.ts +20 -0
- package/src/widgets/actions/download/download.test.tsx +322 -0
- package/src/widgets/actions/download/download.tsx +118 -0
- package/src/widgets/actions/download/exports.test.tsx +275 -0
- package/src/widgets/actions/download/exports.tsx +103 -0
- package/src/widgets/actions/download/types.ts +21 -0
- package/src/widgets/actions/fullscreen/fullscreen.test.tsx +269 -0
- package/src/widgets/actions/fullscreen/fullscreen.tsx +82 -0
- package/src/widgets/actions/fullscreen/styles.ts +17 -0
- package/src/widgets/actions/fullscreen/types.ts +27 -0
- package/src/widgets/actions/index.ts +51 -0
- package/src/widgets/actions/lock-selection/lock-selection.test.tsx +186 -0
- package/src/widgets/actions/lock-selection/lock-selection.tsx +133 -0
- package/src/widgets/actions/lock-selection/types.ts +41 -0
- package/src/widgets/actions/relative-data/relative-data.test.tsx +267 -0
- package/src/widgets/actions/relative-data/relative-data.tsx +133 -0
- package/src/widgets/actions/relative-data/style.ts +9 -0
- package/src/widgets/actions/relative-data/types.ts +31 -0
- package/src/widgets/actions/relative-data/utils.test.ts +223 -0
- package/src/widgets/actions/relative-data/utils.ts +58 -0
- package/src/widgets/actions/searcher/searcher-toggle.test.tsx +354 -0
- package/src/widgets/actions/searcher/searcher-toggle.tsx +73 -0
- package/src/widgets/actions/searcher/searcher.tsx +205 -0
- package/src/widgets/actions/searcher/types.ts +72 -0
- package/src/widgets/actions/shared/styles.ts +12 -0
- package/src/widgets/actions/stack-toggle/grouped-bar-chart-icon.tsx +14 -0
- package/src/widgets/actions/stack-toggle/stack-toggle.test.tsx +270 -0
- package/src/widgets/actions/stack-toggle/stack-toggle.tsx +146 -0
- package/src/widgets/actions/stack-toggle/types.ts +29 -0
- package/src/widgets/actions/zoom-toggle/index.ts +2 -0
- package/src/widgets/actions/zoom-toggle/style.ts +14 -0
- package/src/widgets/actions/zoom-toggle/types.ts +44 -0
- package/src/widgets/actions/zoom-toggle/zoom-toggle.tsx +186 -0
- package/src/widgets/bar/config.ts +122 -0
- package/src/widgets/bar/index.ts +9 -0
- package/src/widgets/bar/skeleton.tsx +60 -0
- package/src/widgets/bar/style.ts +33 -0
- package/src/widgets/bar/types.ts +16 -0
- package/src/widgets/category/category-ui.test.tsx +399 -0
- package/src/widgets/category/category-ui.tsx +156 -0
- package/src/widgets/category/components/category-bar.tsx +28 -0
- package/src/widgets/category/components/category-legend.tsx +30 -0
- package/src/widgets/category/components/category-row-multi.tsx +50 -0
- package/src/widgets/category/components/category-row-other.tsx +23 -0
- package/src/widgets/category/components/category-row-single.tsx +47 -0
- package/src/widgets/category/components/index.ts +14 -0
- package/src/widgets/category/config.ts +85 -0
- package/src/widgets/category/index.ts +30 -0
- package/src/widgets/category/skeleton.tsx +24 -0
- package/src/widgets/category/style.ts +133 -0
- package/src/widgets/category/types.ts +40 -0
- package/src/widgets/echart/const.ts +1 -0
- package/src/widgets/echart/echart-ui.test.tsx +537 -0
- package/src/widgets/echart/echart-ui.tsx +92 -0
- package/src/widgets/echart/echart.test.tsx +562 -0
- package/src/widgets/echart/echart.tsx +68 -0
- package/src/widgets/echart/index.ts +16 -0
- package/src/widgets/echart/options.ts +53 -0
- package/src/widgets/echart/types.ts +45 -0
- package/src/widgets/echart/utils.ts +169 -0
- package/src/widgets/error/error.test.tsx +331 -0
- package/src/widgets/error/error.tsx +40 -0
- package/src/widgets/error/index.ts +2 -0
- package/src/widgets/error/types.ts +14 -0
- package/src/widgets/formula/components/item.test.tsx +249 -0
- package/src/widgets/formula/components/item.tsx +18 -0
- package/src/widgets/formula/components/prefix.test.tsx +341 -0
- package/src/widgets/formula/components/prefix.tsx +18 -0
- package/src/widgets/formula/components/row.test.tsx +364 -0
- package/src/widgets/formula/components/row.tsx +21 -0
- package/src/widgets/formula/components/series.tsx +34 -0
- package/src/widgets/formula/components/suffix.test.tsx +383 -0
- package/src/widgets/formula/components/suffix.tsx +28 -0
- package/src/widgets/formula/components/value.test.tsx +329 -0
- package/src/widgets/formula/components/value.tsx +29 -0
- package/src/widgets/formula/config.ts +27 -0
- package/src/widgets/formula/formula-ui.test.tsx +399 -0
- package/src/widgets/formula/formula-ui.tsx +27 -0
- package/src/widgets/formula/index.ts +24 -0
- package/src/widgets/formula/serializer.test.tsx +144 -0
- package/src/widgets/formula/serializer.ts +28 -0
- package/src/widgets/formula/skeleton.tsx +10 -0
- package/src/widgets/formula/style.ts +23 -0
- package/src/widgets/formula/types.ts +50 -0
- package/src/widgets/histogram/config.ts +143 -0
- package/src/widgets/histogram/index.ts +8 -0
- package/src/widgets/histogram/skeleton.tsx +52 -0
- package/src/widgets/histogram/style.ts +8 -0
- package/src/widgets/histogram/types.ts +17 -0
- package/src/widgets/index.ts +25 -0
- package/src/widgets/loader/index.ts +4 -0
- package/src/widgets/loader/loader.tsx +70 -0
- package/src/widgets/loader/types.ts +11 -0
- package/src/widgets/loader/utils.test.ts +112 -0
- package/src/widgets/loader/utils.ts +35 -0
- package/src/widgets/markdown/config.ts +18 -0
- package/src/widgets/markdown/index.ts +14 -0
- package/src/widgets/markdown/markdown-ui.test.tsx +341 -0
- package/src/widgets/markdown/markdown-ui.tsx +52 -0
- package/src/widgets/markdown/markdown.tsx +20 -0
- package/src/widgets/markdown/skeleton.tsx +12 -0
- package/src/widgets/markdown/style.ts +28 -0
- package/src/widgets/markdown/types.ts +28 -0
- package/src/widgets/no-data/index.ts +2 -0
- package/src/widgets/no-data/no-data.test.tsx +447 -0
- package/src/widgets/no-data/no-data.tsx +116 -0
- package/src/widgets/no-data/style.ts +18 -0
- package/src/widgets/no-data/types.ts +72 -0
- package/src/widgets/note/index.ts +2 -0
- package/src/widgets/note/note.test.tsx +391 -0
- package/src/widgets/note/note.tsx +114 -0
- package/src/widgets/note/style.ts +29 -0
- package/src/widgets/note/types.ts +9 -0
- package/src/widgets/pie/config.ts +177 -0
- package/src/widgets/pie/index.ts +8 -0
- package/src/widgets/pie/skeleton.tsx +70 -0
- package/src/widgets/pie/style.ts +8 -0
- package/src/widgets/pie/types.ts +16 -0
- package/src/widgets/range/components/range-item.tsx +213 -0
- package/src/widgets/range/config.ts +10 -0
- package/src/widgets/range/index.ts +16 -0
- package/src/widgets/range/range-ui.test.tsx +203 -0
- package/src/widgets/range/range-ui.tsx +11 -0
- package/src/widgets/range/serializer.test.ts +70 -0
- package/src/widgets/range/serializer.ts +27 -0
- package/src/widgets/range/skeleton.tsx +14 -0
- package/src/widgets/range/style.ts +37 -0
- package/src/widgets/range/types.ts +39 -0
- package/src/widgets/scatterplot/config.ts +138 -0
- package/src/widgets/scatterplot/index.ts +8 -0
- package/src/widgets/scatterplot/skeleton.tsx +59 -0
- package/src/widgets/scatterplot/style.ts +21 -0
- package/src/widgets/scatterplot/types.ts +17 -0
- package/src/widgets/selection-summary/index.ts +6 -0
- package/src/widgets/selection-summary/selection-summary.tsx +46 -0
- package/src/widgets/selection-summary/style.ts +10 -0
- package/src/widgets/selection-summary/types.ts +14 -0
- package/src/widgets/skeleton-loader/index.ts +2 -0
- package/src/widgets/skeleton-loader/skeleton-loader.test.tsx +139 -0
- package/src/widgets/skeleton-loader/skeleton-loader.tsx +28 -0
- package/src/widgets/skeleton-loader/types.ts +8 -0
- package/src/widgets/spread/components/max-value.tsx +29 -0
- package/src/widgets/spread/components/min-value.tsx +29 -0
- package/src/widgets/spread/components/separator.tsx +6 -0
- package/src/widgets/spread/config.ts +34 -0
- package/src/widgets/spread/index.ts +23 -0
- package/src/widgets/spread/skeleton.tsx +10 -0
- package/src/widgets/spread/spread-ui.test.tsx +368 -0
- package/src/widgets/spread/spread-ui.tsx +29 -0
- package/src/widgets/spread/style.ts +22 -0
- package/src/widgets/spread/types.ts +47 -0
- package/src/widgets/stores/index.ts +9 -0
- package/src/widgets/stores/types.ts +192 -0
- package/src/widgets/stores/widget-store.test.ts +601 -0
- package/src/widgets/stores/widget-store.ts +239 -0
- package/src/widgets/subheader/index.ts +3 -0
- package/src/widgets/subheader/style.ts +20 -0
- package/src/widgets/subheader/subheader.test.tsx +45 -0
- package/src/widgets/subheader/subheader.tsx +16 -0
- package/src/widgets/subheader/types.ts +11 -0
- package/src/widgets/table/components/cell-header.tsx +58 -0
- package/src/widgets/table/components/cell.tsx +80 -0
- package/src/widgets/table/components/index.ts +4 -0
- package/src/widgets/table/components/pagination-actions.tsx +67 -0
- package/src/widgets/table/components/pagination.tsx +41 -0
- package/src/widgets/table/components/row.tsx +60 -0
- package/src/widgets/table/config.ts +71 -0
- package/src/widgets/table/helpers.test.ts +244 -0
- package/src/widgets/table/helpers.ts +107 -0
- package/src/widgets/table/hooks/index.ts +7 -0
- package/src/widgets/table/hooks/use-pagination.test.ts +294 -0
- package/src/widgets/table/hooks/use-pagination.ts +155 -0
- package/src/widgets/table/hooks/use-selection.test.ts +504 -0
- package/src/widgets/table/hooks/use-selection.ts +189 -0
- package/src/widgets/table/hooks/use-sort.test.ts +296 -0
- package/src/widgets/table/hooks/use-sort.ts +138 -0
- package/src/widgets/table/index.ts +53 -0
- package/src/widgets/table/serializer.ts +54 -0
- package/src/widgets/table/skeleton.tsx +48 -0
- package/src/widgets/table/style.ts +34 -0
- package/src/widgets/table/table-ui.tsx +64 -0
- package/src/widgets/table/types.ts +223 -0
- package/src/widgets/timeseries/config.ts +135 -0
- package/src/widgets/timeseries/index.ts +8 -0
- package/src/widgets/timeseries/skeleton.tsx +55 -0
- package/src/widgets/timeseries/style.ts +36 -0
- package/src/widgets/timeseries/types.ts +17 -0
- package/src/widgets/toolbar-actions/index.ts +6 -0
- package/src/widgets/toolbar-actions/styles.ts +38 -0
- package/src/widgets/toolbar-actions/toolbar-actions.test.tsx +691 -0
- package/src/widgets/toolbar-actions/toolbar-actions.tsx +145 -0
- package/src/widgets/toolbar-actions/types.ts +60 -0
- package/src/widgets/wrapper/components/actions.test.tsx +101 -0
- package/src/widgets/wrapper/components/actions.tsx +30 -0
- package/src/widgets/wrapper/components/options.test.tsx +323 -0
- package/src/widgets/wrapper/components/options.tsx +73 -0
- package/src/widgets/wrapper/components/title.test.tsx +126 -0
- package/src/widgets/wrapper/components/title.tsx +32 -0
- package/src/widgets/wrapper/index.ts +16 -0
- package/src/widgets/wrapper/styles.ts +98 -0
- package/src/widgets/wrapper/types.ts +55 -0
- package/src/widgets/wrapper/wrapper-ui.test.tsx +232 -0
- package/src/widgets/wrapper/wrapper-ui.tsx +57 -0
- package/src/widgets/wrapper/wrapper.test.tsx +365 -0
- package/src/widgets/wrapper/wrapper.tsx +50 -0
- package/dist/lasso-tool-BwRzEW7k.js.map +0 -1
- package/dist/types/common/common.d.ts +0 -3
- package/dist/types/common/index.d.ts +0 -26
- package/dist/types/common/lasso-tools.d.ts +0 -36
- package/dist/types/common/measurement-tools.d.ts +0 -65
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { describe, test, expect, vi } from 'vitest'
|
|
2
|
+
import { render, screen, fireEvent } from '@testing-library/react'
|
|
3
|
+
import { ZoomControlsUI } from './zoom-controls'
|
|
4
|
+
|
|
5
|
+
describe('ZoomControlsUI', () => {
|
|
6
|
+
const defaultProps = {
|
|
7
|
+
zoom: 10,
|
|
8
|
+
onChange: vi.fn(),
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
test('renders correctly with default props', () => {
|
|
12
|
+
render(<ZoomControlsUI {...defaultProps} />)
|
|
13
|
+
|
|
14
|
+
expect(screen.getByLabelText('Increase zoom')).toBeTruthy()
|
|
15
|
+
expect(screen.getByLabelText('Decrease zoom')).toBeTruthy()
|
|
16
|
+
expect(screen.getByText('10')).toBeTruthy()
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
test('handles zoom increase correctly', () => {
|
|
20
|
+
render(<ZoomControlsUI {...defaultProps} />)
|
|
21
|
+
|
|
22
|
+
fireEvent.click(screen.getByLabelText('Increase zoom'))
|
|
23
|
+
expect(defaultProps.onChange).toHaveBeenCalledWith(11)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
test('handles zoom decrease correctly', () => {
|
|
27
|
+
render(<ZoomControlsUI {...defaultProps} />)
|
|
28
|
+
|
|
29
|
+
fireEvent.click(screen.getByLabelText('Decrease zoom'))
|
|
30
|
+
expect(defaultProps.onChange).toHaveBeenCalledWith(9)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
test('respects min and max zoom limits', () => {
|
|
34
|
+
const props = {
|
|
35
|
+
...defaultProps,
|
|
36
|
+
zoom: 24,
|
|
37
|
+
maxZoom: 24,
|
|
38
|
+
minZoom: 5,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const { rerender } = render(<ZoomControlsUI {...props} />)
|
|
42
|
+
|
|
43
|
+
fireEvent.click(screen.getByLabelText('Increase zoom'))
|
|
44
|
+
expect(props.onChange).toHaveBeenCalledWith(24) // Should not exceed maxZoom
|
|
45
|
+
|
|
46
|
+
props.zoom = 5
|
|
47
|
+
rerender(<ZoomControlsUI {...props} />)
|
|
48
|
+
|
|
49
|
+
fireEvent.click(screen.getByLabelText('Decrease zoom'))
|
|
50
|
+
expect(props.onChange).toHaveBeenCalledWith(5) // Should not go below minZoom
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
test('renders reset button when onReset is provided', () => {
|
|
54
|
+
const props = {
|
|
55
|
+
...defaultProps,
|
|
56
|
+
onReset: vi.fn(),
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
render(<ZoomControlsUI {...props} />)
|
|
60
|
+
|
|
61
|
+
const resetButton = screen.getByLabelText('Reset action')
|
|
62
|
+
expect(resetButton).toBeTruthy()
|
|
63
|
+
|
|
64
|
+
fireEvent.click(resetButton)
|
|
65
|
+
expect(props.onReset).toHaveBeenCalledTimes(1)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
test('does not render reset button when onReset is not provided', () => {
|
|
69
|
+
render(<ZoomControlsUI {...defaultProps} />)
|
|
70
|
+
|
|
71
|
+
expect(screen.queryByLabelText('Reset action')).toBeNull()
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
test('hides zoom display when showZoom is false', () => {
|
|
75
|
+
render(<ZoomControlsUI {...defaultProps} showZoom={false} />)
|
|
76
|
+
|
|
77
|
+
expect(screen.queryByText('10')).toBeNull()
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
test('shows loading indicator when isLoading is true', () => {
|
|
81
|
+
render(<ZoomControlsUI {...defaultProps} isLoading={true} />)
|
|
82
|
+
|
|
83
|
+
expect(screen.getByRole('progressbar')).toBeTruthy()
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
test('renders in horizontal direction when specified', () => {
|
|
87
|
+
render(<ZoomControlsUI {...defaultProps} direction='horizontal' />)
|
|
88
|
+
|
|
89
|
+
// We can't check styles directly without jest-dom, so we'll skip this assertion
|
|
90
|
+
expect(screen.getByText('10')).toBeTruthy()
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
test('disables buttons when disabled prop is true', () => {
|
|
94
|
+
render(<ZoomControlsUI {...defaultProps} disabled={true} />)
|
|
95
|
+
|
|
96
|
+
const increaseButton = screen.getByLabelText('Increase zoom')
|
|
97
|
+
const decreaseButton = screen.getByLabelText('Decrease zoom')
|
|
98
|
+
expect(increaseButton.hasAttribute('disabled')).toBeTruthy()
|
|
99
|
+
expect(decreaseButton.hasAttribute('disabled')).toBeTruthy()
|
|
100
|
+
})
|
|
101
|
+
})
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import {
|
|
2
|
+
IconButton,
|
|
3
|
+
Divider,
|
|
4
|
+
Box,
|
|
5
|
+
Typography,
|
|
6
|
+
CircularProgress,
|
|
7
|
+
Paper,
|
|
8
|
+
} from '@mui/material'
|
|
9
|
+
import {
|
|
10
|
+
AddOutlined,
|
|
11
|
+
RemoveOutlined,
|
|
12
|
+
CropFreeOutlined,
|
|
13
|
+
} from '@mui/icons-material'
|
|
14
|
+
import { styles } from './styles'
|
|
15
|
+
import type { ZoomControlProps } from './types'
|
|
16
|
+
import { useCallback, type JSX } from 'react'
|
|
17
|
+
|
|
18
|
+
export function ZoomControlsUI({
|
|
19
|
+
zoom,
|
|
20
|
+
disabled,
|
|
21
|
+
direction = 'vertical',
|
|
22
|
+
reverse = false,
|
|
23
|
+
isLoading,
|
|
24
|
+
maxZoom = 24,
|
|
25
|
+
minZoom = 0,
|
|
26
|
+
PaperProps,
|
|
27
|
+
ResetViewProps = {
|
|
28
|
+
Icon: CropFreeOutlined,
|
|
29
|
+
},
|
|
30
|
+
showZoom = true,
|
|
31
|
+
onChange,
|
|
32
|
+
onReset,
|
|
33
|
+
}: ZoomControlProps): JSX.Element {
|
|
34
|
+
const increaseZoom = useCallback(() => {
|
|
35
|
+
const newZoom = Math.min(maxZoom, zoom + 1)
|
|
36
|
+
onChange(newZoom)
|
|
37
|
+
}, [maxZoom, onChange, zoom])
|
|
38
|
+
|
|
39
|
+
const decreaseZoom = useCallback(() => {
|
|
40
|
+
const newZoom = Math.max(minZoom, zoom - 1)
|
|
41
|
+
onChange(newZoom)
|
|
42
|
+
}, [minZoom, onChange, zoom])
|
|
43
|
+
|
|
44
|
+
const displayZoom = Math.floor(zoom)
|
|
45
|
+
|
|
46
|
+
const dividerOrientation =
|
|
47
|
+
direction === 'vertical' ? 'horizontal' : 'vertical'
|
|
48
|
+
|
|
49
|
+
let flexDirection = direction === 'vertical' ? 'column' : 'row'
|
|
50
|
+
|
|
51
|
+
if (reverse) {
|
|
52
|
+
flexDirection += '-reverse'
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<Paper
|
|
57
|
+
sx={{
|
|
58
|
+
...styles.paper,
|
|
59
|
+
flexDirection,
|
|
60
|
+
}}
|
|
61
|
+
{...PaperProps}
|
|
62
|
+
>
|
|
63
|
+
{onReset && (
|
|
64
|
+
<>
|
|
65
|
+
<IconButton
|
|
66
|
+
onClick={onReset}
|
|
67
|
+
aria-label='Reset action'
|
|
68
|
+
disabled={disabled}
|
|
69
|
+
>
|
|
70
|
+
<ResetViewProps.Icon />
|
|
71
|
+
</IconButton>
|
|
72
|
+
<Divider orientation={dividerOrientation} flexItem />
|
|
73
|
+
</>
|
|
74
|
+
)}
|
|
75
|
+
<IconButton
|
|
76
|
+
onClick={increaseZoom}
|
|
77
|
+
aria-label='Increase zoom'
|
|
78
|
+
disabled={disabled}
|
|
79
|
+
>
|
|
80
|
+
<AddOutlined />
|
|
81
|
+
</IconButton>
|
|
82
|
+
<Divider orientation={dividerOrientation} flexItem />
|
|
83
|
+
{showZoom && (
|
|
84
|
+
<>
|
|
85
|
+
<Box sx={styles.zoom}>
|
|
86
|
+
<Typography
|
|
87
|
+
display='block'
|
|
88
|
+
align='center'
|
|
89
|
+
color='textSecondary'
|
|
90
|
+
variant='overline'
|
|
91
|
+
>
|
|
92
|
+
{displayZoom}
|
|
93
|
+
</Typography>
|
|
94
|
+
{isLoading && (
|
|
95
|
+
<CircularProgress
|
|
96
|
+
sx={styles.circularProgress}
|
|
97
|
+
variant='indeterminate'
|
|
98
|
+
size={24}
|
|
99
|
+
/>
|
|
100
|
+
)}
|
|
101
|
+
</Box>
|
|
102
|
+
<Divider orientation={dividerOrientation} flexItem />
|
|
103
|
+
</>
|
|
104
|
+
)}
|
|
105
|
+
<IconButton
|
|
106
|
+
onClick={decreaseZoom}
|
|
107
|
+
aria-label='Decrease zoom'
|
|
108
|
+
disabled={disabled}
|
|
109
|
+
>
|
|
110
|
+
<RemoveOutlined />
|
|
111
|
+
</IconButton>
|
|
112
|
+
</Paper>
|
|
113
|
+
)
|
|
114
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { useEffect, useRef, useCallback } from 'react'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Custom hook for debouncing a callback function.
|
|
5
|
+
*
|
|
6
|
+
* @param callback - The function to debounce
|
|
7
|
+
* @param delay - The delay in milliseconds (default: 300ms)
|
|
8
|
+
* @returns A debounced version of the callback
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```tsx
|
|
12
|
+
* const debouncedSearch = useDebounce((searchText: string) => {
|
|
13
|
+
* // Perform search
|
|
14
|
+
* console.log('Searching for:', searchText)
|
|
15
|
+
* }, 300)
|
|
16
|
+
*
|
|
17
|
+
* // Call it anywhere
|
|
18
|
+
* debouncedSearch('react')
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export function useDebounce<TArgs extends unknown[], TReturn = void>(
|
|
22
|
+
callback: (...args: TArgs) => TReturn,
|
|
23
|
+
delay = 300,
|
|
24
|
+
): (...args: TArgs) => void {
|
|
25
|
+
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
26
|
+
const callbackRef = useRef(callback)
|
|
27
|
+
|
|
28
|
+
// Update callback ref when callback changes
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
callbackRef.current = callback
|
|
31
|
+
}, [callback])
|
|
32
|
+
|
|
33
|
+
// Cleanup timeout on unmount
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
return () => {
|
|
36
|
+
if (timeoutRef.current) {
|
|
37
|
+
clearTimeout(timeoutRef.current)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}, [])
|
|
41
|
+
|
|
42
|
+
const debouncedCallback = useCallback(
|
|
43
|
+
(...args: TArgs) => {
|
|
44
|
+
if (timeoutRef.current) {
|
|
45
|
+
clearTimeout(timeoutRef.current)
|
|
46
|
+
}
|
|
47
|
+
timeoutRef.current = setTimeout(() => {
|
|
48
|
+
callbackRef.current(...args)
|
|
49
|
+
}, delay)
|
|
50
|
+
},
|
|
51
|
+
[delay],
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
return debouncedCallback
|
|
55
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest'
|
|
2
|
+
import { renderHook } from '@testing-library/react'
|
|
3
|
+
import { useSkeleton } from './use-skeleton'
|
|
4
|
+
|
|
5
|
+
describe('useSkeleton', () => {
|
|
6
|
+
test('should return true if loading', () => {
|
|
7
|
+
const { result } = renderHook(() => useSkeleton(true))
|
|
8
|
+
|
|
9
|
+
expect(result.current).toBe(true)
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
test('should return false if not loading', () => {
|
|
13
|
+
const { result } = renderHook(() => useSkeleton(false))
|
|
14
|
+
|
|
15
|
+
expect(result.current).toBe(false)
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
test('should return false if skeleton was already rendered', () => {
|
|
19
|
+
const { result, rerender } = renderHook(
|
|
20
|
+
({ showSkeleton }) => useSkeleton(showSkeleton),
|
|
21
|
+
{ initialProps: { showSkeleton: true } },
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
expect(result.current).toBe(true)
|
|
25
|
+
|
|
26
|
+
rerender({ showSkeleton: false })
|
|
27
|
+
expect(result.current).toBe(false)
|
|
28
|
+
|
|
29
|
+
rerender({ showSkeleton: true })
|
|
30
|
+
expect(result.current).toBe(false)
|
|
31
|
+
})
|
|
32
|
+
})
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
|
|
3
|
+
export function useSkeleton(isLoading: boolean) {
|
|
4
|
+
// Track render phase: 0=never shown, 1=showing now, 2=already shown
|
|
5
|
+
const [phase, setPhase] = useState(() => (isLoading ? 1 : 0))
|
|
6
|
+
|
|
7
|
+
// Transition from phase 0 to 1 when loading starts
|
|
8
|
+
if (isLoading && phase === 0) {
|
|
9
|
+
setPhase(1)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Transition from phase 1 to 2 when loading ends (skeleton was shown, now done)
|
|
13
|
+
if (!isLoading && phase === 1) {
|
|
14
|
+
setPhase(2)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Show skeleton only in phase 1 (first time loading)
|
|
18
|
+
return phase === 1
|
|
19
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react'
|
|
2
|
+
import { useWidgetStore } from '../widgets/stores/widget-store'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Custom hook for registering a DOM element ref with the widget store.
|
|
6
|
+
* This allows other parts of the application to access the widget's DOM element.
|
|
7
|
+
*
|
|
8
|
+
* @param widgetId - The widget ID to register the ref under
|
|
9
|
+
* @returns A ref object to attach to the DOM element
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```tsx
|
|
13
|
+
* function MyWidget({ id }: { id: string }) {
|
|
14
|
+
* const ref = useWidgetRef<HTMLDivElement>(id)
|
|
15
|
+
*
|
|
16
|
+
* return <div ref={ref}>Widget content</div>
|
|
17
|
+
* }
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export function useWidgetRef<T extends HTMLElement = HTMLElement>(
|
|
21
|
+
widgetId: string,
|
|
22
|
+
) {
|
|
23
|
+
const ref = useRef<T | null>(null)
|
|
24
|
+
const setWidget = useWidgetStore((store) => store.setWidget)
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
if (ref.current) {
|
|
28
|
+
setWidget(widgetId, { refUI: ref })
|
|
29
|
+
}
|
|
30
|
+
}, [widgetId, setWidget])
|
|
31
|
+
|
|
32
|
+
return ref
|
|
33
|
+
}
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
# Widgets Architecture
|
|
2
|
+
|
|
3
|
+
> **⚠️ Project-Side Implementation Required**
|
|
4
|
+
>
|
|
5
|
+
> Starting with v4.3.x, widgets should be implemented on the **project side** using the building blocks provided by this library. The library exports UI components, configuration helpers, and state management hooks—you compose them in your project to create complete widgets.
|
|
6
|
+
>
|
|
7
|
+
> **Benefits:**
|
|
8
|
+
>
|
|
9
|
+
> - Full customization tailored to your project's specific needs
|
|
10
|
+
> - Direct integration with your data sources and state management
|
|
11
|
+
> - Independent updates without waiting for library releases
|
|
12
|
+
> - Include only the widget features you actually need
|
|
13
|
+
>
|
|
14
|
+
> **See the internal Storybook for complete implementation examples** of all widget types (Bar, Pie, Histogram, Timeseries, Scatterplot, Formula, etc.).
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
This directory contains the CARTO PS widget system with an optimized architecture for code reuse and maintainability.
|
|
19
|
+
|
|
20
|
+
## Directory Structure
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
widgets/
|
|
24
|
+
├── _shared/ # Shared utilities (internal, not exported)
|
|
25
|
+
│ ├── chart-config/ # Chart widget configuration utilities
|
|
26
|
+
│ │ ├── config-factory.ts # Factory for creating chart widget configs
|
|
27
|
+
│ │ ├── csv-modifiers.ts # CSV export utilities
|
|
28
|
+
│ │ ├── option-builders.ts # EChart option builders
|
|
29
|
+
│ │ └── index.ts # Exports
|
|
30
|
+
│ └── skeleton/ # Shared skeleton styles
|
|
31
|
+
│ ├── styles.ts # Base skeleton container styles
|
|
32
|
+
│ └── index.ts # Exports
|
|
33
|
+
├── bar/ # Bar chart widget
|
|
34
|
+
├── histogram/ # Histogram widget
|
|
35
|
+
├── pie/ # Pie chart widget
|
|
36
|
+
├── scatterplot/ # Scatterplot widget
|
|
37
|
+
├── timeseries/ # Timeseries/line chart widget
|
|
38
|
+
├── formula/ # Formula/KPI widget
|
|
39
|
+
├── note/ # Note widget
|
|
40
|
+
├── markdown/ # Markdown renderer
|
|
41
|
+
├── echart/ # Base EChart component
|
|
42
|
+
├── wrapper/ # Widget wrapper with header/actions
|
|
43
|
+
├── actions/ # Download, fullscreen actions
|
|
44
|
+
├── config-loader/ # Config loading system
|
|
45
|
+
├── skeleton-loader/ # Skeleton components
|
|
46
|
+
├── stores/ # Zustand widget state
|
|
47
|
+
├── loader/ # WidgetLoader container
|
|
48
|
+
└── widget/ # Main widget orchestrator
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Shared Utilities (`_shared/`)
|
|
52
|
+
|
|
53
|
+
The `_shared` directory contains internal utilities used by multiple widgets. These are **not exported** from the package and are only for internal use.
|
|
54
|
+
|
|
55
|
+
### Chart Config Factory
|
|
56
|
+
|
|
57
|
+
**File:** `_shared/chart-config/config-factory.ts`
|
|
58
|
+
|
|
59
|
+
Creates standardized chart widget configurations, eliminating code duplication.
|
|
60
|
+
|
|
61
|
+
**Example:**
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
import {
|
|
65
|
+
createChartWidgetConfig,
|
|
66
|
+
flattenObjectArrayToCSV,
|
|
67
|
+
} from '../_shared/chart-config'
|
|
68
|
+
|
|
69
|
+
export const myWidgetConfig = createChartWidgetConfig({
|
|
70
|
+
type: 'my-widget',
|
|
71
|
+
getOptions: ({ data, theme }) => ({
|
|
72
|
+
// EChart configuration
|
|
73
|
+
legend: buildLegendConfig(hasLegend),
|
|
74
|
+
// ...
|
|
75
|
+
}),
|
|
76
|
+
csvModifier: (data) => flattenObjectArrayToCSV(data),
|
|
77
|
+
})
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### CSV Modifiers
|
|
81
|
+
|
|
82
|
+
**File:** `_shared/chart-config/csv-modifiers.ts`
|
|
83
|
+
|
|
84
|
+
- `flattenObjectArrayToCSV()` - For widgets with object-based data (bar, pie, histogram, timeseries)
|
|
85
|
+
- `scatterplotDataToCSV()` - For scatterplot with array-based data
|
|
86
|
+
|
|
87
|
+
### EChart Option Builders
|
|
88
|
+
|
|
89
|
+
**File:** `_shared/chart-config/option-builders.ts`
|
|
90
|
+
|
|
91
|
+
- `buildLegendConfig(hasLegend)` - Standard legend configuration
|
|
92
|
+
- `buildGridConfig(hasLegend, theme, additionalConfig?)` - Grid with legend-aware spacing
|
|
93
|
+
- `createTooltipPositioner(theme)` - Tooltip positioning with overflow handling
|
|
94
|
+
|
|
95
|
+
### Skeleton Styles
|
|
96
|
+
|
|
97
|
+
**File:** `_shared/skeleton/styles.ts`
|
|
98
|
+
|
|
99
|
+
- `baseSkeletonStyles.graph.container` - Base container styles for all chart skeletons
|
|
100
|
+
|
|
101
|
+
## Creating a New Chart Widget
|
|
102
|
+
|
|
103
|
+
1. **Create widget directory:**
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
mkdir src/widgets/my-widget
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
2. **Create `types.ts`:**
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
import type { BaseWidgetProps } from '../widget/types'
|
|
113
|
+
import type { BaseConfig } from '../config-loader'
|
|
114
|
+
import type {
|
|
115
|
+
EchartWidgetConfig,
|
|
116
|
+
EchartWidgetData,
|
|
117
|
+
EchartWidgetState,
|
|
118
|
+
} from '../echart'
|
|
119
|
+
|
|
120
|
+
export interface MyWidgetProps extends BaseWidgetProps<MyWidgetConfig> {
|
|
121
|
+
type: 'my-widget'
|
|
122
|
+
data: MyWidgetData | undefined
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export type MyWidgetData = EchartWidgetData
|
|
126
|
+
export type MyWidgetState = EchartWidgetState
|
|
127
|
+
export type MyWidgetConfig = MyConfig &
|
|
128
|
+
EchartWidgetConfig & { type: MyWidgetProps['type'] }
|
|
129
|
+
|
|
130
|
+
export interface MyConfig extends Omit<BaseConfig, 'data'> {
|
|
131
|
+
data?: MyWidgetData
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
3. **Create `config.ts` using shared factory:**
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
import type { EchartOptionsProps } from '../echart'
|
|
139
|
+
import type { MyConfig, MyWidgetConfig, MyWidgetData } from './types'
|
|
140
|
+
import {
|
|
141
|
+
createChartWidgetConfig,
|
|
142
|
+
flattenObjectArrayToCSV,
|
|
143
|
+
buildLegendConfig,
|
|
144
|
+
buildGridConfig,
|
|
145
|
+
} from '../_shared/chart-config'
|
|
146
|
+
|
|
147
|
+
export const myWidgetConfig = createChartWidgetConfig<
|
|
148
|
+
MyWidgetData,
|
|
149
|
+
MyConfig
|
|
150
|
+
>({
|
|
151
|
+
type: 'my-widget',
|
|
152
|
+
getOptions,
|
|
153
|
+
csvModifier: (data) => flattenObjectArrayToCSV(data),
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
function getOptions({
|
|
157
|
+
data = [],
|
|
158
|
+
theme,
|
|
159
|
+
}: Omit<MyConfig, 'refUI'>): EchartOptionsProps {
|
|
160
|
+
const hasLegend = (data?.length ?? 0) > 1
|
|
161
|
+
return {
|
|
162
|
+
legend: buildLegendConfig(hasLegend),
|
|
163
|
+
grid: buildGridConfig(hasLegend, theme),
|
|
164
|
+
// ... widget-specific configuration
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
4. **Create `skeleton.tsx` and `style.ts`:**
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
// style.ts
|
|
173
|
+
import type { SxProps, Theme } from '@mui/material'
|
|
174
|
+
import { baseSkeletonStyles } from '../_shared/skeleton'
|
|
175
|
+
|
|
176
|
+
export const styles = {
|
|
177
|
+
skeleton: {
|
|
178
|
+
graph: baseSkeletonStyles.graph,
|
|
179
|
+
// Add widget-specific styles here
|
|
180
|
+
},
|
|
181
|
+
} satisfies Record<string, SxProps<Theme>>
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
5. **Create `index.ts`:**
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
export type {
|
|
188
|
+
MyWidgetProps,
|
|
189
|
+
MyWidgetData,
|
|
190
|
+
MyWidgetState,
|
|
191
|
+
MyWidgetConfig,
|
|
192
|
+
MyConfig,
|
|
193
|
+
} from './types'
|
|
194
|
+
export { myWidgetConfig } from './config'
|
|
195
|
+
export { MyWidgetSkeleton as Skeleton } from './skeleton'
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
6. **Run build automation:**
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
pnpm generate:exports
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
This will automatically:
|
|
205
|
+
- Add the widget to vite.config.ts entry points
|
|
206
|
+
- Add the widget to package.json exports
|
|
207
|
+
- Validate all exports
|
|
208
|
+
|
|
209
|
+
## Build Automation
|
|
210
|
+
|
|
211
|
+
### Scripts
|
|
212
|
+
|
|
213
|
+
- `pnpm exports:validate` - Validate all package.json exports have corresponding source files
|
|
214
|
+
|
|
215
|
+
### How It Works
|
|
216
|
+
|
|
217
|
+
**package.json is the source of truth!**
|
|
218
|
+
|
|
219
|
+
The `scripts/generate-widget-config.ts` script:
|
|
220
|
+
|
|
221
|
+
1. Reads widget exports from `package.json`
|
|
222
|
+
2. Converts export paths to source file paths
|
|
223
|
+
3. Generates vite entry points dynamically
|
|
224
|
+
4. Validates that all exports have corresponding source files
|
|
225
|
+
|
|
226
|
+
The script uses **Node 22's native TypeScript support** with the `--experimental-strip-types` flag, so no compilation step is needed!
|
|
227
|
+
|
|
228
|
+
### Adding a New Widget
|
|
229
|
+
|
|
230
|
+
1. **Create widget folder and files:**
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
mkdir src/widgets/my-widget
|
|
234
|
+
# Create index.ts, config.ts, types.ts, etc.
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
2. **Add export to package.json:**
|
|
238
|
+
|
|
239
|
+
```json
|
|
240
|
+
{
|
|
241
|
+
"exports": {
|
|
242
|
+
"./widgets/my-widget": {
|
|
243
|
+
"import": "./dist/widgets/my-widget.js",
|
|
244
|
+
"types": "./dist/types/widgets/my-widget/index.d.ts"
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
3. **Build:**
|
|
251
|
+
```bash
|
|
252
|
+
pnpm build
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
The vite config automatically reads from package.json and builds all declared exports!
|
|
256
|
+
|
|
257
|
+
## Code Reduction Achieved
|
|
258
|
+
|
|
259
|
+
| Widget | Before | After | Reduction |
|
|
260
|
+
| --------------------- | ---------- | --------- | --------- |
|
|
261
|
+
| bar/config.ts | ~150 lines | ~65 lines | ~57% |
|
|
262
|
+
| histogram/config.ts | ~155 lines | ~68 lines | ~56% |
|
|
263
|
+
| pie/config.ts | ~160 lines | ~50 lines | ~69% |
|
|
264
|
+
| scatterplot/config.ts | ~145 lines | ~90 lines | ~38% |
|
|
265
|
+
| timeseries/config.ts | ~130 lines | ~70 lines | ~46% |
|
|
266
|
+
|
|
267
|
+
**Total:** ~400-500 lines of duplicated code eliminated
|
|
268
|
+
|
|
269
|
+
## Benefits
|
|
270
|
+
|
|
271
|
+
✅ **Reduced Duplication** - Shared utilities eliminate repeated code
|
|
272
|
+
✅ **Better Maintainability** - Changes to common patterns update all widgets
|
|
273
|
+
✅ **Automated Builds** - No manual vite/package.json updates needed
|
|
274
|
+
✅ **Consistent Patterns** - All widgets follow the same structure
|
|
275
|
+
✅ **Type Safety** - Full TypeScript support throughout
|
|
276
|
+
✅ **Easy to Extend** - Adding new widgets is straightforward
|
|
277
|
+
✅ **Backwards Compatible** - All external imports remain unchanged
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { EchartOptionsProps, EchartWidgetData } from '../../echart'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Base configuration interface for chart widgets
|
|
5
|
+
*/
|
|
6
|
+
export interface ChartWidgetBaseConfig<TData = EchartWidgetData> {
|
|
7
|
+
data?: TData
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Parameters for creating a chart widget configuration
|
|
12
|
+
*/
|
|
13
|
+
export interface CreateChartWidgetConfigParams<
|
|
14
|
+
TData = EchartWidgetData,
|
|
15
|
+
TConfig extends ChartWidgetBaseConfig<TData> = ChartWidgetBaseConfig<TData>,
|
|
16
|
+
TType extends string = string,
|
|
17
|
+
> {
|
|
18
|
+
/** Widget type identifier (e.g., 'bar', 'pie', 'histogram') */
|
|
19
|
+
type: TType
|
|
20
|
+
/** Function to get EChart options from config */
|
|
21
|
+
getOptions: (config: TConfig) => EchartOptionsProps
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Return type of the chart widget config function
|
|
26
|
+
*/
|
|
27
|
+
export type ChartWidgetConfigResult<
|
|
28
|
+
TData = EchartWidgetData,
|
|
29
|
+
TConfig extends ChartWidgetBaseConfig<TData> = ChartWidgetBaseConfig<TData>,
|
|
30
|
+
TType extends string = string,
|
|
31
|
+
> = TConfig & {
|
|
32
|
+
type: TType
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Factory function to create a standardized chart widget config function.
|
|
37
|
+
* This eliminates duplication across chart widgets by providing a common structure.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```ts
|
|
41
|
+
* export const barConfig = createChartWidgetConfig({
|
|
42
|
+
* type: 'bar' as const,
|
|
43
|
+
* getOptions: ({ data, theme }) => ({
|
|
44
|
+
* // EChart configuration
|
|
45
|
+
* }),
|
|
46
|
+
* csvModifier: (data) => flattenObjectArrayToCSV(data),
|
|
47
|
+
* })
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export function createChartWidgetConfig<
|
|
51
|
+
TData = EchartWidgetData,
|
|
52
|
+
TConfig extends ChartWidgetBaseConfig<TData> = ChartWidgetBaseConfig<TData>,
|
|
53
|
+
TType extends string = string,
|
|
54
|
+
>({
|
|
55
|
+
type,
|
|
56
|
+
getOptions,
|
|
57
|
+
}: CreateChartWidgetConfigParams<TData, TConfig, TType>): (
|
|
58
|
+
config: TConfig,
|
|
59
|
+
) => ChartWidgetConfigResult<TData, TConfig, TType> {
|
|
60
|
+
return function (config: TConfig) {
|
|
61
|
+
return {
|
|
62
|
+
...config,
|
|
63
|
+
option: getOptions(config),
|
|
64
|
+
type: type,
|
|
65
|
+
} as ChartWidgetConfigResult<TData, TConfig, TType>
|
|
66
|
+
}
|
|
67
|
+
}
|