@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,354 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach, vi, afterEach } from 'vitest'
|
|
2
|
+
import { render, screen, fireEvent, act } from '@testing-library/react'
|
|
3
|
+
import { SearcherToggle } from './searcher-toggle'
|
|
4
|
+
import { Searcher } from './searcher'
|
|
5
|
+
import { useWidgetStore } from '../../stores/widget-store'
|
|
6
|
+
import type { EchartWidgetData } from '../../echart/types'
|
|
7
|
+
import type { SearcherState } from './types'
|
|
8
|
+
|
|
9
|
+
// Test data
|
|
10
|
+
const mockData: EchartWidgetData = [
|
|
11
|
+
[
|
|
12
|
+
{ name: 'Electronics', value: 440 },
|
|
13
|
+
{ name: 'Clothing', value: 280 },
|
|
14
|
+
{ name: 'Books', value: 100 },
|
|
15
|
+
],
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
describe('SearcherToggle', () => {
|
|
19
|
+
const widgetId = 'test-searcher-toggle'
|
|
20
|
+
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
useWidgetStore.getState().clearWidgets()
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
test('renders search toggle button', () => {
|
|
26
|
+
render(<SearcherToggle id={widgetId} />)
|
|
27
|
+
|
|
28
|
+
const button = screen.getByRole('button')
|
|
29
|
+
expect(button).toBeTruthy()
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
test('shows enable tooltip when search is disabled', () => {
|
|
33
|
+
render(<SearcherToggle id={widgetId} />)
|
|
34
|
+
|
|
35
|
+
const button = screen.getByRole('button', { name: 'Enable search' })
|
|
36
|
+
expect(button).toBeTruthy()
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
test('shows disable tooltip when search is enabled', () => {
|
|
40
|
+
render(<SearcherToggle id={widgetId} defaultEnabled />)
|
|
41
|
+
|
|
42
|
+
const button = screen.getByRole('button', { name: 'Disable search' })
|
|
43
|
+
expect(button).toBeTruthy()
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
test('toggles isSearchEnabled in store when clicked', () => {
|
|
47
|
+
render(<SearcherToggle id={widgetId} />)
|
|
48
|
+
|
|
49
|
+
const button = screen.getByRole('button')
|
|
50
|
+
fireEvent.click(button)
|
|
51
|
+
|
|
52
|
+
const widget = useWidgetStore.getState().getWidget<SearcherState>(widgetId)
|
|
53
|
+
expect(widget?.isSearchEnabled).toBe(true)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
test('has active state when enabled via store', () => {
|
|
57
|
+
render(<SearcherToggle id={widgetId} defaultEnabled />)
|
|
58
|
+
|
|
59
|
+
const button = screen.getByRole('button')
|
|
60
|
+
expect(button.getAttribute('data-active')).toBe('true')
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
test('has inactive state when disabled', () => {
|
|
64
|
+
render(<SearcherToggle id={widgetId} />)
|
|
65
|
+
|
|
66
|
+
const button = screen.getByRole('button')
|
|
67
|
+
expect(button.getAttribute('data-active')).toBe('false')
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
test('uses custom labels', () => {
|
|
71
|
+
render(
|
|
72
|
+
<SearcherToggle
|
|
73
|
+
id={widgetId}
|
|
74
|
+
labels={{
|
|
75
|
+
enable: 'Activar búsqueda',
|
|
76
|
+
disable: 'Desactivar búsqueda',
|
|
77
|
+
ariaLabel: 'Toggle search',
|
|
78
|
+
}}
|
|
79
|
+
/>,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
const button = screen.getByRole('button', { name: 'Toggle search' })
|
|
83
|
+
expect(button).toBeTruthy()
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
test('initializes store with defaultEnabled value', () => {
|
|
87
|
+
render(<SearcherToggle id={widgetId} defaultEnabled />)
|
|
88
|
+
|
|
89
|
+
const widget = useWidgetStore.getState().getWidget<SearcherState>(widgetId)
|
|
90
|
+
expect(widget?.isSearchEnabled).toBe(true)
|
|
91
|
+
})
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
describe('Searcher', () => {
|
|
95
|
+
const widgetId = 'test-searcher-input'
|
|
96
|
+
|
|
97
|
+
beforeEach(() => {
|
|
98
|
+
useWidgetStore.getState().clearWidgets()
|
|
99
|
+
vi.useFakeTimers()
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
afterEach(() => {
|
|
103
|
+
vi.useRealTimers()
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
test('does not render when search is disabled in store', () => {
|
|
107
|
+
// Set isSearchEnabled to false in store
|
|
108
|
+
useWidgetStore.getState().setWidget(widgetId, { isSearchEnabled: false })
|
|
109
|
+
|
|
110
|
+
render(<Searcher id={widgetId} />)
|
|
111
|
+
|
|
112
|
+
const input = screen.queryByRole('textbox')
|
|
113
|
+
expect(input).toBeNull()
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
test('renders input when search is enabled in store', () => {
|
|
117
|
+
// Set isSearchEnabled to true in store
|
|
118
|
+
useWidgetStore.getState().setWidget(widgetId, { isSearchEnabled: true })
|
|
119
|
+
|
|
120
|
+
render(<Searcher id={widgetId} />)
|
|
121
|
+
|
|
122
|
+
const input = screen.getByRole('textbox')
|
|
123
|
+
expect(input).toBeTruthy()
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
test('shows placeholder text', () => {
|
|
127
|
+
useWidgetStore.getState().setWidget(widgetId, { isSearchEnabled: true })
|
|
128
|
+
|
|
129
|
+
render(<Searcher id={widgetId} labels={{ placeholder: 'Search...' }} />)
|
|
130
|
+
|
|
131
|
+
const input = screen.getByPlaceholderText('Search...')
|
|
132
|
+
expect(input).toBeTruthy()
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
test('updates store with debounced filtered data', async () => {
|
|
136
|
+
// Initialize widget with data and enabled state
|
|
137
|
+
useWidgetStore
|
|
138
|
+
.getState()
|
|
139
|
+
.setWidget(widgetId, { data: mockData, isSearchEnabled: true })
|
|
140
|
+
|
|
141
|
+
render(<Searcher id={widgetId} />)
|
|
142
|
+
|
|
143
|
+
const input = screen.getByRole('textbox')
|
|
144
|
+
fireEvent.change(input, { target: { value: 'Electronics' } })
|
|
145
|
+
|
|
146
|
+
// Wait for debounce (300ms) and flush all pending promises
|
|
147
|
+
await act(async () => {
|
|
148
|
+
await vi.advanceTimersByTimeAsync(300)
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
// Execute the tool pipeline to apply the filter transformation
|
|
152
|
+
await useWidgetStore.getState().executeToolPipeline(widgetId, mockData)
|
|
153
|
+
|
|
154
|
+
const widget = useWidgetStore.getState().getWidget(widgetId)
|
|
155
|
+
const filteredData = widget?.data as EchartWidgetData | undefined
|
|
156
|
+
|
|
157
|
+
// Should only have Electronics item
|
|
158
|
+
expect(filteredData?.[0]?.length).toBe(1)
|
|
159
|
+
expect(filteredData?.[0]?.[0]?.name).toBe('Electronics')
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
test('shows clear button when there is text', () => {
|
|
163
|
+
useWidgetStore.getState().setWidget(widgetId, { isSearchEnabled: true })
|
|
164
|
+
|
|
165
|
+
render(<Searcher id={widgetId} />)
|
|
166
|
+
|
|
167
|
+
const input = screen.getByRole('textbox')
|
|
168
|
+
fireEvent.change(input, { target: { value: 'test' } })
|
|
169
|
+
|
|
170
|
+
const clearButton = screen.getByRole('button', { name: 'Clear search' })
|
|
171
|
+
expect(clearButton).toBeTruthy()
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
test('does not show clear button when input is empty', () => {
|
|
175
|
+
useWidgetStore.getState().setWidget(widgetId, { isSearchEnabled: true })
|
|
176
|
+
|
|
177
|
+
render(<Searcher id={widgetId} />)
|
|
178
|
+
|
|
179
|
+
const clearButton = screen.queryByRole('button', { name: 'Clear search' })
|
|
180
|
+
expect(clearButton).toBeNull()
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
test('clears search text and restores original data when clear button is clicked', () => {
|
|
184
|
+
// Initialize widget with data and enabled state
|
|
185
|
+
useWidgetStore
|
|
186
|
+
.getState()
|
|
187
|
+
.setWidget(widgetId, { data: mockData, isSearchEnabled: true })
|
|
188
|
+
|
|
189
|
+
render(<Searcher id={widgetId} />)
|
|
190
|
+
|
|
191
|
+
const input = screen.getByRole('textbox')
|
|
192
|
+
fireEvent.change(input, { target: { value: 'Electronics' } })
|
|
193
|
+
|
|
194
|
+
// Wait for debounce
|
|
195
|
+
act(() => {
|
|
196
|
+
vi.advanceTimersByTime(300)
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
// Click clear
|
|
200
|
+
const clearButton = screen.getByRole('button', { name: 'Clear search' })
|
|
201
|
+
fireEvent.click(clearButton)
|
|
202
|
+
|
|
203
|
+
// Data should be restored to original
|
|
204
|
+
const widget = useWidgetStore.getState().getWidget(widgetId)
|
|
205
|
+
const restoredData = widget?.data as EchartWidgetData | undefined
|
|
206
|
+
expect(restoredData?.[0]?.length).toBe(3)
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
test('uses custom clear button aria label', () => {
|
|
210
|
+
useWidgetStore.getState().setWidget(widgetId, { isSearchEnabled: true })
|
|
211
|
+
|
|
212
|
+
render(
|
|
213
|
+
<Searcher
|
|
214
|
+
id={widgetId}
|
|
215
|
+
labels={{ clearAriaLabel: 'Limpiar búsqueda' }}
|
|
216
|
+
/>,
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
const input = screen.getByRole('textbox')
|
|
220
|
+
fireEvent.change(input, { target: { value: 'text' } })
|
|
221
|
+
|
|
222
|
+
const clearButton = screen.getByRole('button', {
|
|
223
|
+
name: 'Limpiar búsqueda',
|
|
224
|
+
})
|
|
225
|
+
expect(clearButton).toBeTruthy()
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
test('uses custom filter function', async () => {
|
|
229
|
+
// Custom filter that only matches exact values
|
|
230
|
+
const customFilterFn = async (
|
|
231
|
+
data: EchartWidgetData,
|
|
232
|
+
searchText: string,
|
|
233
|
+
) => {
|
|
234
|
+
return Promise.resolve(
|
|
235
|
+
data.map((series) =>
|
|
236
|
+
series.filter((item) => item.value === parseInt(searchText, 10)),
|
|
237
|
+
),
|
|
238
|
+
)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Initialize widget with data and enabled state
|
|
242
|
+
useWidgetStore
|
|
243
|
+
.getState()
|
|
244
|
+
.setWidget(widgetId, { data: mockData, isSearchEnabled: true })
|
|
245
|
+
|
|
246
|
+
render(<Searcher id={widgetId} filterFn={customFilterFn} />)
|
|
247
|
+
|
|
248
|
+
const input = screen.getByRole('textbox')
|
|
249
|
+
fireEvent.change(input, { target: { value: '440' } })
|
|
250
|
+
|
|
251
|
+
// Wait for debounce and flush all pending promises
|
|
252
|
+
await act(async () => {
|
|
253
|
+
await vi.advanceTimersByTimeAsync(300)
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
// Execute the tool pipeline to apply the filter transformation
|
|
257
|
+
await useWidgetStore.getState().executeToolPipeline(widgetId, mockData)
|
|
258
|
+
|
|
259
|
+
// Should only have Electronics (value: 440)
|
|
260
|
+
const widget = useWidgetStore.getState().getWidget(widgetId)
|
|
261
|
+
const filteredData = widget?.data as EchartWidgetData | undefined
|
|
262
|
+
expect(filteredData?.[0]?.length).toBe(1)
|
|
263
|
+
expect(filteredData?.[0]?.[0]?.value).toBe(440)
|
|
264
|
+
})
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
describe('SearcherToggle + Searcher integration', () => {
|
|
268
|
+
const widgetId = 'test-integration'
|
|
269
|
+
|
|
270
|
+
beforeEach(() => {
|
|
271
|
+
useWidgetStore.getState().clearWidgets()
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
// Helper component that uses both SearcherToggle and Searcher with same id
|
|
275
|
+
function IntegrationTest() {
|
|
276
|
+
return (
|
|
277
|
+
<>
|
|
278
|
+
<SearcherToggle id={widgetId} />
|
|
279
|
+
<Searcher id={widgetId} />
|
|
280
|
+
</>
|
|
281
|
+
)
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
test('searcher becomes visible when toggle is clicked', () => {
|
|
285
|
+
render(<IntegrationTest />)
|
|
286
|
+
|
|
287
|
+
// Initially searcher should not be visible
|
|
288
|
+
expect(screen.queryByRole('textbox')).toBeNull()
|
|
289
|
+
|
|
290
|
+
// Click toggle
|
|
291
|
+
const toggleButton = screen.getByRole('button')
|
|
292
|
+
fireEvent.click(toggleButton)
|
|
293
|
+
|
|
294
|
+
// Searcher should now be visible
|
|
295
|
+
expect(screen.getByRole('textbox')).toBeTruthy()
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
test('searcher is hidden when toggle is clicked again', () => {
|
|
299
|
+
render(<IntegrationTest />)
|
|
300
|
+
|
|
301
|
+
const toggleButton = screen.getByRole('button')
|
|
302
|
+
|
|
303
|
+
// Enable
|
|
304
|
+
fireEvent.click(toggleButton)
|
|
305
|
+
expect(screen.getByRole('textbox')).toBeTruthy()
|
|
306
|
+
|
|
307
|
+
// Disable
|
|
308
|
+
fireEvent.click(toggleButton)
|
|
309
|
+
expect(screen.queryByRole('textbox')).toBeNull()
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
test('toggle button shows correct active state', () => {
|
|
313
|
+
render(<IntegrationTest />)
|
|
314
|
+
|
|
315
|
+
const toggleButton = screen.getByRole('button')
|
|
316
|
+
|
|
317
|
+
// Initially inactive
|
|
318
|
+
expect(toggleButton.getAttribute('data-active')).toBe('false')
|
|
319
|
+
|
|
320
|
+
// Enable - should be active
|
|
321
|
+
fireEvent.click(toggleButton)
|
|
322
|
+
expect(toggleButton.getAttribute('data-active')).toBe('true')
|
|
323
|
+
|
|
324
|
+
// Disable - should be inactive
|
|
325
|
+
fireEvent.click(toggleButton)
|
|
326
|
+
expect(toggleButton.getAttribute('data-active')).toBe('false')
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
test('store state is correctly updated when toggling', () => {
|
|
330
|
+
render(<IntegrationTest />)
|
|
331
|
+
|
|
332
|
+
const toggleButton = screen.getByRole('button')
|
|
333
|
+
|
|
334
|
+
// Initially should be false
|
|
335
|
+
expect(
|
|
336
|
+
useWidgetStore.getState().getWidget<SearcherState>(widgetId)
|
|
337
|
+
?.isSearchEnabled,
|
|
338
|
+
).toBe(false)
|
|
339
|
+
|
|
340
|
+
// Enable
|
|
341
|
+
fireEvent.click(toggleButton)
|
|
342
|
+
expect(
|
|
343
|
+
useWidgetStore.getState().getWidget<SearcherState>(widgetId)
|
|
344
|
+
?.isSearchEnabled,
|
|
345
|
+
).toBe(true)
|
|
346
|
+
|
|
347
|
+
// Disable
|
|
348
|
+
fireEvent.click(toggleButton)
|
|
349
|
+
expect(
|
|
350
|
+
useWidgetStore.getState().getWidget<SearcherState>(widgetId)
|
|
351
|
+
?.isSearchEnabled,
|
|
352
|
+
).toBe(false)
|
|
353
|
+
})
|
|
354
|
+
})
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { IconButton } from '@mui/material'
|
|
2
|
+
import { SearchOutlined } from '@mui/icons-material'
|
|
3
|
+
import { useCallback, useEffect } from 'react'
|
|
4
|
+
import type { SearcherToggleProps, SearcherState } from './types'
|
|
5
|
+
import { actionButtonStyles } from '../shared/styles'
|
|
6
|
+
import { Tooltip } from '../../../components'
|
|
7
|
+
import { useWidgetStore } from '../../stores/widget-store'
|
|
8
|
+
import { useShallow } from 'zustand/shallow'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Widget action button to toggle search functionality.
|
|
12
|
+
*
|
|
13
|
+
* Stores the enabled state in the widget store using the provided id.
|
|
14
|
+
* When search is active, the button shows an active state with a light background.
|
|
15
|
+
* Use in conjunction with the Searcher component to display the search input.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```tsx
|
|
19
|
+
* <SearcherToggle id="my-widget" />
|
|
20
|
+
* <Searcher
|
|
21
|
+
* id="my-widget"
|
|
22
|
+
* data={widgetData}
|
|
23
|
+
* />
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export function SearcherToggle({
|
|
27
|
+
id,
|
|
28
|
+
defaultEnabled = false,
|
|
29
|
+
labels,
|
|
30
|
+
Icon,
|
|
31
|
+
IconButtonProps,
|
|
32
|
+
}: SearcherToggleProps) {
|
|
33
|
+
const setWidget = useWidgetStore((state) => state.setWidget)
|
|
34
|
+
const getWidget = useWidgetStore((state) => state.getWidget)
|
|
35
|
+
const storeIsEnabled = useWidgetStore(
|
|
36
|
+
useShallow((state) => state.getWidget<SearcherState>(id)?.isSearchEnabled),
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
const isEnabled = storeIsEnabled ?? defaultEnabled
|
|
40
|
+
|
|
41
|
+
// Initialize store with default value on mount
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
const currentValue = getWidget<SearcherState>(id)?.isSearchEnabled
|
|
44
|
+
if (currentValue === undefined) {
|
|
45
|
+
setWidget(id, { isSearchEnabled: defaultEnabled })
|
|
46
|
+
}
|
|
47
|
+
}, [defaultEnabled, getWidget, id, setWidget])
|
|
48
|
+
|
|
49
|
+
const handleToggle = useCallback(() => {
|
|
50
|
+
setWidget(id, { isSearchEnabled: !isEnabled })
|
|
51
|
+
}, [id, isEnabled, setWidget])
|
|
52
|
+
|
|
53
|
+
const tooltipLabel = isEnabled
|
|
54
|
+
? (labels?.disable ?? 'Disable search')
|
|
55
|
+
: (labels?.enable ?? 'Enable search')
|
|
56
|
+
|
|
57
|
+
const ariaLabel = labels?.ariaLabel ?? tooltipLabel
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<Tooltip title={tooltipLabel}>
|
|
61
|
+
<IconButton
|
|
62
|
+
size='small'
|
|
63
|
+
aria-label={ariaLabel}
|
|
64
|
+
onClick={handleToggle}
|
|
65
|
+
sx={actionButtonStyles.trigger}
|
|
66
|
+
data-active={isEnabled}
|
|
67
|
+
{...IconButtonProps}
|
|
68
|
+
>
|
|
69
|
+
{Icon ?? <SearchOutlined fontSize='small' />}
|
|
70
|
+
</IconButton>
|
|
71
|
+
</Tooltip>
|
|
72
|
+
)
|
|
73
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { TextField, InputAdornment, IconButton } from '@mui/material'
|
|
2
|
+
import { ClearOutlined, SearchOutlined } from '@mui/icons-material'
|
|
3
|
+
import { useEffect, useRef, useCallback } from 'react'
|
|
4
|
+
import { useWidgetStore } from '../../stores/widget-store'
|
|
5
|
+
import type { SearcherProps, SearcherFilterFn, SearcherState } from './types'
|
|
6
|
+
import type { EchartWidgetData } from '../../echart/types'
|
|
7
|
+
import { LOCK_SELECTION_TOOL_ID } from '../lock-selection/lock-selection'
|
|
8
|
+
|
|
9
|
+
export const SEARCHER_TOOL_ID = 'searcher'
|
|
10
|
+
|
|
11
|
+
const DEBOUNCE_DELAY = 300
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Search input component that works with SearcherToggle.
|
|
15
|
+
*
|
|
16
|
+
* Registers a transformation tool in the widget pipeline when mounted.
|
|
17
|
+
* Reads the enabled state from the widget store using the provided id.
|
|
18
|
+
* Uses a debounced search to filter data via the transformation pipeline.
|
|
19
|
+
* Auto-focuses when enabled becomes true.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```tsx
|
|
23
|
+
* <SearcherToggle id="my-widget" />
|
|
24
|
+
* <Searcher
|
|
25
|
+
* id="my-widget"
|
|
26
|
+
* order={10}
|
|
27
|
+
* labels={{ placeholder: 'Search categories...' }}
|
|
28
|
+
* />
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export function Searcher({
|
|
32
|
+
id,
|
|
33
|
+
filterFn,
|
|
34
|
+
order = 20,
|
|
35
|
+
labels,
|
|
36
|
+
TextFieldProps,
|
|
37
|
+
ClearIcon,
|
|
38
|
+
debounceDelay = DEBOUNCE_DELAY,
|
|
39
|
+
}: SearcherProps) {
|
|
40
|
+
const inputRef = useRef<HTMLInputElement>(null)
|
|
41
|
+
const debounceTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
42
|
+
|
|
43
|
+
// Read enabled state and search text from widget store
|
|
44
|
+
const widgetState = useWidgetStore((state) =>
|
|
45
|
+
state.getWidget<SearcherState>(id),
|
|
46
|
+
)
|
|
47
|
+
const enabled = widgetState?.isSearchEnabled ?? false
|
|
48
|
+
const searchText = widgetState?.searchText ?? ''
|
|
49
|
+
const prevEnabledRef = useRef(enabled)
|
|
50
|
+
|
|
51
|
+
const filter = filterFn ?? defaultFilterFn
|
|
52
|
+
|
|
53
|
+
const setWidget = useWidgetStore((state) => state.setWidget)
|
|
54
|
+
const registerTool = useWidgetStore((state) => state.registerTool)
|
|
55
|
+
const unregisterTool = useWidgetStore((state) => state.unregisterTool)
|
|
56
|
+
const setToolEnabled = useWidgetStore((state) => state.setToolEnabled)
|
|
57
|
+
const updateToolConfig = useWidgetStore((state) => state.updateToolConfig)
|
|
58
|
+
|
|
59
|
+
const setSearchText = useCallback(
|
|
60
|
+
(text: string) => {
|
|
61
|
+
setWidget(id, { searchText: text })
|
|
62
|
+
},
|
|
63
|
+
[id, setWidget],
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
// Register tool on mount
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
registerTool(id, {
|
|
69
|
+
id: SEARCHER_TOOL_ID,
|
|
70
|
+
order,
|
|
71
|
+
enabled,
|
|
72
|
+
fn: async (data, config) => {
|
|
73
|
+
const searchTextFromConfig = (config?.searchText as string) || ''
|
|
74
|
+
|
|
75
|
+
// Execute filter (can be sync or async)
|
|
76
|
+
const result = filter(data as EchartWidgetData, searchTextFromConfig)
|
|
77
|
+
|
|
78
|
+
// Return result directly (pipeline will handle Promise)
|
|
79
|
+
return result
|
|
80
|
+
},
|
|
81
|
+
config: { searchText },
|
|
82
|
+
disables: [LOCK_SELECTION_TOOL_ID],
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
return () => unregisterTool(id, SEARCHER_TOOL_ID)
|
|
86
|
+
}, [id, order, filter, registerTool, unregisterTool, enabled, searchText])
|
|
87
|
+
|
|
88
|
+
// Update enabled flag when it changes
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
setToolEnabled(id, SEARCHER_TOOL_ID, enabled)
|
|
91
|
+
}, [id, enabled, setToolEnabled])
|
|
92
|
+
|
|
93
|
+
// Update config when search text changes (debounced)
|
|
94
|
+
const debouncedUpdateConfig = useCallback(
|
|
95
|
+
(text: string) => {
|
|
96
|
+
if (debounceTimeoutRef.current) {
|
|
97
|
+
clearTimeout(debounceTimeoutRef.current)
|
|
98
|
+
}
|
|
99
|
+
debounceTimeoutRef.current = setTimeout(() => {
|
|
100
|
+
updateToolConfig(id, SEARCHER_TOOL_ID, { searchText: text })
|
|
101
|
+
}, debounceDelay)
|
|
102
|
+
},
|
|
103
|
+
[id, updateToolConfig, debounceDelay],
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
// Auto-focus when enabled becomes true
|
|
107
|
+
useEffect(() => {
|
|
108
|
+
// Transition from disabled to enabled - focus input
|
|
109
|
+
if (enabled && !prevEnabledRef.current && inputRef.current) {
|
|
110
|
+
inputRef.current.focus()
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
prevEnabledRef.current = enabled
|
|
114
|
+
}, [enabled])
|
|
115
|
+
|
|
116
|
+
// Cleanup debounce timeout on unmount
|
|
117
|
+
useEffect(() => {
|
|
118
|
+
return () => {
|
|
119
|
+
if (debounceTimeoutRef.current) {
|
|
120
|
+
clearTimeout(debounceTimeoutRef.current)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}, [])
|
|
124
|
+
|
|
125
|
+
const handleChange = useCallback(
|
|
126
|
+
(event: React.ChangeEvent<HTMLInputElement>) => {
|
|
127
|
+
const newValue = event.target.value
|
|
128
|
+
setSearchText(newValue)
|
|
129
|
+
debouncedUpdateConfig(newValue)
|
|
130
|
+
},
|
|
131
|
+
[debouncedUpdateConfig, setSearchText],
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
const handleClear = useCallback(() => {
|
|
135
|
+
setSearchText('')
|
|
136
|
+
updateToolConfig(id, SEARCHER_TOOL_ID, { searchText: '' })
|
|
137
|
+
if (inputRef.current) {
|
|
138
|
+
inputRef.current.focus()
|
|
139
|
+
}
|
|
140
|
+
}, [id, setSearchText, updateToolConfig])
|
|
141
|
+
|
|
142
|
+
if (!enabled) {
|
|
143
|
+
return null
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const placeholder = labels?.placeholder ?? 'Search...'
|
|
147
|
+
const clearAriaLabel = labels?.clearAriaLabel ?? 'Clear search'
|
|
148
|
+
|
|
149
|
+
return (
|
|
150
|
+
<TextField
|
|
151
|
+
inputRef={inputRef}
|
|
152
|
+
size='small'
|
|
153
|
+
fullWidth
|
|
154
|
+
variant='filled'
|
|
155
|
+
placeholder={placeholder}
|
|
156
|
+
value={searchText}
|
|
157
|
+
onChange={handleChange}
|
|
158
|
+
InputProps={{
|
|
159
|
+
startAdornment: (
|
|
160
|
+
<InputAdornment position='start'>
|
|
161
|
+
<SearchOutlined />
|
|
162
|
+
</InputAdornment>
|
|
163
|
+
),
|
|
164
|
+
endAdornment: searchText ? (
|
|
165
|
+
<InputAdornment position='end'>
|
|
166
|
+
<IconButton
|
|
167
|
+
size='small'
|
|
168
|
+
aria-label={clearAriaLabel}
|
|
169
|
+
onClick={handleClear}
|
|
170
|
+
edge='end'
|
|
171
|
+
>
|
|
172
|
+
{ClearIcon ?? <ClearOutlined fontSize='small' />}
|
|
173
|
+
</IconButton>
|
|
174
|
+
</InputAdornment>
|
|
175
|
+
) : null,
|
|
176
|
+
}}
|
|
177
|
+
{...TextFieldProps}
|
|
178
|
+
/>
|
|
179
|
+
)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Default filter function that searches all string fields case-insensitively.
|
|
184
|
+
* Note: Should be synchronous for the new pipeline architecture.
|
|
185
|
+
*/
|
|
186
|
+
const defaultFilterFn: SearcherFilterFn = (
|
|
187
|
+
data: EchartWidgetData,
|
|
188
|
+
searchText: string,
|
|
189
|
+
) => {
|
|
190
|
+
if (!searchText.trim()) return Promise.resolve(data)
|
|
191
|
+
|
|
192
|
+
const lowerSearch = searchText.toLowerCase()
|
|
193
|
+
|
|
194
|
+
return Promise.resolve(
|
|
195
|
+
data.map((series) =>
|
|
196
|
+
series.filter((item) =>
|
|
197
|
+
Object.values(item).some(
|
|
198
|
+
(value) =>
|
|
199
|
+
typeof value === 'string' &&
|
|
200
|
+
value.toLowerCase().includes(lowerSearch),
|
|
201
|
+
),
|
|
202
|
+
),
|
|
203
|
+
),
|
|
204
|
+
)
|
|
205
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { IconButtonProps, TextFieldProps } from '@mui/material'
|
|
2
|
+
import type { ReactNode } from 'react'
|
|
3
|
+
import type { EchartWidgetData } from '../../echart/types'
|
|
4
|
+
import type { BaseWidgetState } from '../../stores/types'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Filter function type for custom search filtering logic.
|
|
8
|
+
* Receives the original data and search text, returns filtered data.
|
|
9
|
+
* Can be synchronous or asynchronous to support remote filtering.
|
|
10
|
+
*/
|
|
11
|
+
export type SearcherFilterFn = (
|
|
12
|
+
data: EchartWidgetData,
|
|
13
|
+
searchText: string,
|
|
14
|
+
) => Promise<EchartWidgetData> | EchartWidgetData
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Searcher-specific state properties.
|
|
18
|
+
*/
|
|
19
|
+
export interface SearcherStateProps {
|
|
20
|
+
/** Whether search is currently enabled */
|
|
21
|
+
isSearchEnabled?: boolean
|
|
22
|
+
/** Current search text */
|
|
23
|
+
searchText?: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Widget state extension for searcher functionality.
|
|
28
|
+
* Extends the base widget state with search-specific properties.
|
|
29
|
+
*/
|
|
30
|
+
export type SearcherState<T = object> = BaseWidgetState<T & SearcherStateProps>
|
|
31
|
+
|
|
32
|
+
export interface SearcherToggleProps {
|
|
33
|
+
/** Widget ID to store search enabled state */
|
|
34
|
+
id: string
|
|
35
|
+
/** Initial search enabled state. Defaults to false */
|
|
36
|
+
defaultEnabled?: boolean
|
|
37
|
+
/** Custom labels for the action */
|
|
38
|
+
labels?: {
|
|
39
|
+
/** Tooltip when search is disabled (button will enable search) */
|
|
40
|
+
enable?: string
|
|
41
|
+
/** Tooltip when search is enabled (button will disable search) */
|
|
42
|
+
disable?: string
|
|
43
|
+
/** Accessibility label */
|
|
44
|
+
ariaLabel?: string
|
|
45
|
+
}
|
|
46
|
+
/** Props passed to the IconButton component */
|
|
47
|
+
IconButtonProps?: IconButtonProps
|
|
48
|
+
/** Custom icon to display */
|
|
49
|
+
Icon?: ReactNode
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface SearcherProps {
|
|
53
|
+
/** Widget ID to update filtered data in the widget store */
|
|
54
|
+
id: string
|
|
55
|
+
/** Custom filter function. Defaults to case-insensitive search across all string fields */
|
|
56
|
+
filterFn?: SearcherFilterFn
|
|
57
|
+
/** Execution order in the tool pipeline. Lower values execute first. Defaults to 10. */
|
|
58
|
+
order?: number
|
|
59
|
+
/** Custom labels for the searcher input */
|
|
60
|
+
labels?: {
|
|
61
|
+
/** Placeholder text for the input */
|
|
62
|
+
placeholder?: string
|
|
63
|
+
/** Accessibility label for clear button */
|
|
64
|
+
clearAriaLabel?: string
|
|
65
|
+
}
|
|
66
|
+
/** Props passed to the TextField component */
|
|
67
|
+
TextFieldProps?: Omit<TextFieldProps, 'value' | 'onChange' | 'size'>
|
|
68
|
+
/** Custom icon for clear button */
|
|
69
|
+
ClearIcon?: ReactNode
|
|
70
|
+
/** Debounce delay in milliseconds for search input. Defaults to 300ms */
|
|
71
|
+
debounceDelay?: number
|
|
72
|
+
}
|