@carto/ps-react-ui 4.7.1 → 4.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/category-DwaeYjpX.js +656 -0
- package/dist/category-DwaeYjpX.js.map +1 -0
- package/dist/change-column-Cidl_M-4.js +1110 -0
- package/dist/change-column-Cidl_M-4.js.map +1 -0
- package/dist/data-zoom-layout-BH0LPwSy.js +28 -0
- package/dist/data-zoom-layout-BH0LPwSy.js.map +1 -0
- package/dist/echart-CU0KmClP.js +176 -0
- package/dist/echart-CU0KmClP.js.map +1 -0
- package/dist/exports-Cx-f6m6U.js +63 -0
- package/dist/exports-Cx-f6m6U.js.map +1 -0
- package/dist/formula-DuC0NQLH.js +79 -0
- package/dist/formula-DuC0NQLH.js.map +1 -0
- package/dist/markdown-BD1jcknS.js +8326 -0
- package/dist/markdown-BD1jcknS.js.map +1 -0
- package/dist/merge-options-DCkkHZIf.js +34 -0
- package/dist/merge-options-DCkkHZIf.js.map +1 -0
- package/dist/{styles-BYTyKQFP.js → option-builders-F-c9ELi1.js} +25 -45
- package/dist/option-builders-F-c9ELi1.js.map +1 -0
- package/dist/png-item-CS4z1iSH.js +45 -0
- package/dist/png-item-CS4z1iSH.js.map +1 -0
- package/dist/range-DsqTjSpg.js +186 -0
- package/dist/range-DsqTjSpg.js.map +1 -0
- package/dist/spread-CTuIXZSM.js +67 -0
- package/dist/spread-CTuIXZSM.js.map +1 -0
- package/dist/style-DVnT6HC1.js +131 -0
- package/dist/style-DVnT6HC1.js.map +1 -0
- package/dist/styles-cohnxh9F.js +23 -0
- package/dist/styles-cohnxh9F.js.map +1 -0
- package/dist/table-HIpXuq4G.js +390 -0
- package/dist/table-HIpXuq4G.js.map +1 -0
- package/dist/transforms-Cdx4fkU5.js +106 -0
- package/dist/transforms-Cdx4fkU5.js.map +1 -0
- package/dist/types/widgets/echart/utils.test.d.ts +1 -0
- package/dist/types/widgets/formula/config.test.d.ts +1 -0
- package/dist/types/widgets/stores/widget-store-branches.test.d.ts +1 -0
- package/dist/types/widgets/table/config.test.d.ts +1 -0
- package/dist/types/widgets-v2/actions/brush-toggle/brush-toggle.d.ts +56 -0
- package/dist/types/widgets-v2/actions/brush-toggle/index.d.ts +3 -0
- package/dist/types/widgets-v2/actions/brush-toggle/labels.d.ts +5 -0
- package/dist/types/widgets-v2/actions/brush-toggle/style.d.ts +12 -0
- package/dist/types/widgets-v2/actions/brush-toggle/transforms.d.ts +11 -0
- package/dist/types/widgets-v2/actions/brush-toggle/transforms.test.d.ts +1 -0
- package/dist/types/widgets-v2/actions/change-column/change-column-icon.d.ts +2 -0
- package/dist/types/widgets-v2/actions/change-column/change-column.d.ts +29 -0
- package/dist/types/widgets-v2/actions/change-column/index.d.ts +3 -0
- package/dist/types/widgets-v2/actions/change-column/labels.d.ts +5 -0
- package/dist/types/widgets-v2/actions/change-column/sortable-column-item.d.ts +14 -0
- package/dist/types/widgets-v2/actions/change-column/style.d.ts +33 -0
- package/dist/types/widgets-v2/actions/change-column/types.d.ts +10 -0
- package/dist/types/widgets-v2/actions/download/download.d.ts +18 -0
- package/dist/types/widgets-v2/actions/download/exports.d.ts +37 -0
- package/dist/types/widgets-v2/actions/download/icons.d.ts +12 -0
- package/dist/types/widgets-v2/actions/download/index.d.ts +6 -0
- package/dist/types/widgets-v2/actions/download/labels.d.ts +11 -0
- package/dist/types/widgets-v2/actions/download/png-item.d.ts +24 -0
- package/dist/types/widgets-v2/actions/download/style.d.ts +1 -0
- package/dist/types/widgets-v2/actions/download/types.d.ts +35 -0
- package/dist/types/widgets-v2/actions/fullscreen/fullscreen.d.ts +59 -0
- package/dist/types/widgets-v2/actions/fullscreen/index.d.ts +3 -0
- package/dist/types/widgets-v2/actions/fullscreen/labels.d.ts +5 -0
- package/dist/types/widgets-v2/actions/fullscreen/style.d.ts +48 -0
- package/dist/types/widgets-v2/actions/fullscreen/types.d.ts +14 -0
- package/dist/types/widgets-v2/actions/index.d.ts +9 -0
- package/dist/types/widgets-v2/actions/lock-selection/index.d.ts +3 -0
- package/dist/types/widgets-v2/actions/lock-selection/labels.d.ts +6 -0
- package/dist/types/widgets-v2/actions/lock-selection/lock-selection.d.ts +36 -0
- package/dist/types/widgets-v2/actions/lock-selection/style.d.ts +12 -0
- package/dist/types/widgets-v2/actions/lock-selection/transforms.d.ts +6 -0
- package/dist/types/widgets-v2/actions/relative-data/index.d.ts +3 -0
- package/dist/types/widgets-v2/actions/relative-data/labels.d.ts +5 -0
- package/dist/types/widgets-v2/actions/relative-data/relative-data.d.ts +39 -0
- package/dist/types/widgets-v2/actions/relative-data/style.d.ts +12 -0
- package/dist/types/widgets-v2/actions/relative-data/transforms.d.ts +30 -0
- package/dist/types/widgets-v2/actions/relative-data/transforms.test.d.ts +1 -0
- package/dist/types/widgets-v2/actions/searcher/filter.d.ts +6 -0
- package/dist/types/widgets-v2/actions/searcher/index.d.ts +4 -0
- package/dist/types/widgets-v2/actions/searcher/labels.d.ts +7 -0
- package/dist/types/widgets-v2/actions/searcher/searcher-toggle.d.ts +23 -0
- package/dist/types/widgets-v2/actions/searcher/searcher.d.ts +11 -0
- package/dist/types/widgets-v2/actions/searcher/style.d.ts +16 -0
- package/dist/types/widgets-v2/actions/stack-toggle/index.d.ts +3 -0
- package/dist/types/widgets-v2/actions/stack-toggle/labels.d.ts +5 -0
- package/dist/types/widgets-v2/actions/stack-toggle/stack-toggle.d.ts +10 -0
- package/dist/types/widgets-v2/actions/stack-toggle/style.d.ts +12 -0
- package/dist/types/widgets-v2/actions/stack-toggle/transforms.d.ts +13 -0
- package/dist/types/widgets-v2/actions/stack-toggle/transforms.test.d.ts +1 -0
- package/dist/types/widgets-v2/actions/zoom-toggle/index.d.ts +3 -0
- package/dist/types/widgets-v2/actions/zoom-toggle/labels.d.ts +5 -0
- package/dist/types/widgets-v2/actions/zoom-toggle/style.d.ts +12 -0
- package/dist/types/widgets-v2/actions/zoom-toggle/transforms.d.ts +51 -0
- package/dist/types/widgets-v2/actions/zoom-toggle/transforms.test.d.ts +1 -0
- package/dist/types/widgets-v2/actions/zoom-toggle/zoom-toggle.d.ts +35 -0
- package/dist/types/widgets-v2/bar/download.d.ts +24 -0
- package/dist/types/widgets-v2/bar/index.d.ts +4 -0
- package/dist/types/widgets-v2/bar/options.d.ts +43 -0
- package/dist/types/widgets-v2/bar/options.test.d.ts +1 -0
- package/dist/types/widgets-v2/bar/skeleton.d.ts +6 -0
- package/dist/types/widgets-v2/bar/types.d.ts +41 -0
- package/dist/types/widgets-v2/category/category-ui.d.ts +81 -0
- package/dist/types/widgets-v2/category/category.d.ts +48 -0
- package/dist/types/widgets-v2/category/components/category-bar-stacked.d.ts +28 -0
- package/dist/types/widgets-v2/category/components/category-bar.d.ts +23 -0
- package/dist/types/widgets-v2/category/components/category-legend.d.ts +18 -0
- package/dist/types/widgets-v2/category/components/category-row-multi.d.ts +31 -0
- package/dist/types/widgets-v2/category/components/category-row-other.d.ts +13 -0
- package/dist/types/widgets-v2/category/components/category-row-single.d.ts +28 -0
- package/dist/types/widgets-v2/category/components/category-row-stacked.d.ts +38 -0
- package/dist/types/widgets-v2/category/download.d.ts +16 -0
- package/dist/types/widgets-v2/category/download.test.d.ts +1 -0
- package/dist/types/widgets-v2/category/index.d.ts +10 -0
- package/dist/types/widgets-v2/category/skeleton.d.ts +11 -0
- package/dist/types/widgets-v2/category/style.d.ts +166 -0
- package/dist/types/widgets-v2/category/types.d.ts +49 -0
- package/dist/types/widgets-v2/echart/echart-ui.d.ts +44 -0
- package/dist/types/widgets-v2/echart/echart.d.ts +75 -0
- package/dist/types/widgets-v2/echart/index.d.ts +4 -0
- package/dist/types/widgets-v2/echart/shared-resize-observer.d.ts +5 -0
- package/dist/types/widgets-v2/echart/shared-resize-observer.test.d.ts +1 -0
- package/dist/types/widgets-v2/echart/style.d.ts +6 -0
- package/dist/types/widgets-v2/echart/use-chart-selection.d.ts +51 -0
- package/dist/types/widgets-v2/formula/delta.d.ts +22 -0
- package/dist/types/widgets-v2/formula/download.d.ts +20 -0
- package/dist/types/widgets-v2/formula/formula-ui.d.ts +20 -0
- package/dist/types/widgets-v2/formula/formula.d.ts +8 -0
- package/dist/types/widgets-v2/formula/index.d.ts +11 -0
- package/dist/types/widgets-v2/formula/note.d.ts +11 -0
- package/dist/types/widgets-v2/formula/prefix.d.ts +12 -0
- package/dist/types/widgets-v2/formula/series.d.ts +16 -0
- package/dist/types/widgets-v2/formula/skeleton.d.ts +4 -0
- package/dist/types/widgets-v2/formula/style.d.ts +29 -0
- package/dist/types/widgets-v2/formula/suffix.d.ts +12 -0
- package/dist/types/widgets-v2/formula/types.d.ts +40 -0
- package/dist/types/widgets-v2/formula/value.d.ts +14 -0
- package/dist/types/widgets-v2/histogram/download.d.ts +17 -0
- package/dist/types/widgets-v2/histogram/download.test.d.ts +1 -0
- package/dist/types/widgets-v2/histogram/index.d.ts +5 -0
- package/dist/types/widgets-v2/histogram/options.d.ts +42 -0
- package/dist/types/widgets-v2/histogram/options.test.d.ts +1 -0
- package/dist/types/widgets-v2/histogram/skeleton.d.ts +9 -0
- package/dist/types/widgets-v2/histogram/transforms.d.ts +17 -0
- package/dist/types/widgets-v2/histogram/transforms.test.d.ts +1 -0
- package/dist/types/widgets-v2/histogram/types.d.ts +47 -0
- package/dist/types/widgets-v2/index.d.ts +107 -0
- package/dist/types/widgets-v2/markdown/download.d.ts +16 -0
- package/dist/types/widgets-v2/markdown/download.test.d.ts +1 -0
- package/dist/types/widgets-v2/markdown/index.d.ts +6 -0
- package/dist/types/widgets-v2/markdown/markdown-content.d.ts +34 -0
- package/dist/types/widgets-v2/markdown/markdown-ui.d.ts +12 -0
- package/dist/types/widgets-v2/markdown/markdown.d.ts +6 -0
- package/dist/types/widgets-v2/markdown/skeleton.d.ts +4 -0
- package/dist/types/widgets-v2/markdown/style.d.ts +61 -0
- package/dist/types/widgets-v2/markdown/types.d.ts +4 -0
- package/dist/types/widgets-v2/note/labels.d.ts +5 -0
- package/dist/types/widgets-v2/note/style.d.ts +26 -0
- package/dist/types/widgets-v2/note/widget-note.d.ts +46 -0
- package/dist/types/widgets-v2/pie/download.d.ts +17 -0
- package/dist/types/widgets-v2/pie/download.test.d.ts +1 -0
- package/dist/types/widgets-v2/pie/index.d.ts +4 -0
- package/dist/types/widgets-v2/pie/options.d.ts +35 -0
- package/dist/types/widgets-v2/pie/options.test.d.ts +1 -0
- package/dist/types/widgets-v2/pie/skeleton.d.ts +4 -0
- package/dist/types/widgets-v2/pie/types.d.ts +50 -0
- package/dist/types/widgets-v2/provider/widget-provider.d.ts +32 -0
- package/dist/types/widgets-v2/range/index.d.ts +4 -0
- package/dist/types/widgets-v2/range/range-ui.d.ts +19 -0
- package/dist/types/widgets-v2/range/range.d.ts +19 -0
- package/dist/types/widgets-v2/range/skeleton.d.ts +9 -0
- package/dist/types/widgets-v2/range/style.d.ts +40 -0
- package/dist/types/widgets-v2/range/types.d.ts +37 -0
- package/dist/types/widgets-v2/scatterplot/download.d.ts +16 -0
- package/dist/types/widgets-v2/scatterplot/download.test.d.ts +1 -0
- package/dist/types/widgets-v2/scatterplot/index.d.ts +5 -0
- package/dist/types/widgets-v2/scatterplot/options.d.ts +42 -0
- package/dist/types/widgets-v2/scatterplot/options.test.d.ts +1 -0
- package/dist/types/widgets-v2/scatterplot/skeleton.d.ts +12 -0
- package/dist/types/widgets-v2/scatterplot/transforms.d.ts +17 -0
- package/dist/types/widgets-v2/scatterplot/transforms.test.d.ts +1 -0
- package/dist/types/widgets-v2/scatterplot/types.d.ts +50 -0
- package/dist/types/widgets-v2/selection-summary/labels.d.ts +6 -0
- package/dist/types/widgets-v2/selection-summary/selection-summary.d.ts +22 -0
- package/dist/types/widgets-v2/selection-summary/style.d.ts +23 -0
- package/dist/types/widgets-v2/spread/download.d.ts +15 -0
- package/dist/types/widgets-v2/spread/download.test.d.ts +1 -0
- package/dist/types/widgets-v2/spread/index.d.ts +6 -0
- package/dist/types/widgets-v2/spread/separator.d.ts +7 -0
- package/dist/types/widgets-v2/spread/skeleton.d.ts +9 -0
- package/dist/types/widgets-v2/spread/spread-ui.d.ts +18 -0
- package/dist/types/widgets-v2/spread/spread.d.ts +5 -0
- package/dist/types/widgets-v2/spread/types.d.ts +25 -0
- package/dist/types/widgets-v2/state/labels.d.ts +7 -0
- package/dist/types/widgets-v2/state/labels.test.d.ts +1 -0
- package/dist/types/widgets-v2/state/style.d.ts +19 -0
- package/dist/types/widgets-v2/state/widget-state.d.ts +19 -0
- package/dist/types/widgets-v2/stores/index.d.ts +8 -0
- package/dist/types/widgets-v2/stores/pipeline-middleware.d.ts +5 -0
- package/dist/types/widgets-v2/stores/pipeline-middleware.test.d.ts +1 -0
- package/dist/types/widgets-v2/stores/transforms.d.ts +4 -0
- package/dist/types/widgets-v2/stores/transforms.test.d.ts +1 -0
- package/dist/types/widgets-v2/stores/types.d.ts +55 -0
- package/dist/types/widgets-v2/stores/use-echart-instance.d.ts +15 -0
- package/dist/types/widgets-v2/stores/use-transform-enabled.d.ts +17 -0
- package/dist/types/widgets-v2/stores/use-transform.d.ts +12 -0
- package/dist/types/widgets-v2/stores/widget-context.d.ts +2 -0
- package/dist/types/widgets-v2/stores/widget-store-registry.d.ts +74 -0
- package/dist/types/widgets-v2/stores/widget-store-registry.test.d.ts +1 -0
- package/dist/types/widgets-v2/subheader/style.d.ts +10 -0
- package/dist/types/widgets-v2/subheader/subheader.d.ts +11 -0
- package/dist/types/widgets-v2/table/download.d.ts +18 -0
- package/dist/types/widgets-v2/table/download.test.d.ts +1 -0
- package/dist/types/widgets-v2/table/helpers.d.ts +32 -0
- package/dist/types/widgets-v2/table/helpers.test.d.ts +1 -0
- package/dist/types/widgets-v2/table/index.d.ts +7 -0
- package/dist/types/widgets-v2/table/labels.d.ts +22 -0
- package/dist/types/widgets-v2/table/skeleton.d.ts +22 -0
- package/dist/types/widgets-v2/table/style.d.ts +44 -0
- package/dist/types/widgets-v2/table/table-ui.d.ts +38 -0
- package/dist/types/widgets-v2/table/table.d.ts +50 -0
- package/dist/types/widgets-v2/table/types.d.ts +37 -0
- package/dist/types/widgets-v2/test-utils.d.ts +52 -0
- package/dist/types/widgets-v2/timeseries/download.d.ts +17 -0
- package/dist/types/widgets-v2/timeseries/download.test.d.ts +1 -0
- package/dist/types/widgets-v2/timeseries/index.d.ts +4 -0
- package/dist/types/widgets-v2/timeseries/options.d.ts +39 -0
- package/dist/types/widgets-v2/timeseries/options.test.d.ts +1 -0
- package/dist/types/widgets-v2/timeseries/skeleton.d.ts +8 -0
- package/dist/types/widgets-v2/timeseries/types.d.ts +56 -0
- package/dist/types/widgets-v2/toolbox/labels.d.ts +5 -0
- package/dist/types/widgets-v2/toolbox/style.d.ts +30 -0
- package/dist/types/widgets-v2/toolbox/toolbox.d.ts +49 -0
- package/dist/types/widgets-v2/utils/data-zoom-layout.d.ts +11 -0
- package/dist/types/widgets-v2/utils/index.d.ts +2 -0
- package/dist/types/widgets-v2/utils/merge-options.d.ts +12 -0
- package/dist/types/widgets-v2/utils/merge-options.test.d.ts +1 -0
- package/dist/types/widgets-v2/wrapper/index.d.ts +4 -0
- package/dist/types/widgets-v2/wrapper/labels.d.ts +6 -0
- package/dist/types/widgets-v2/wrapper/style.d.ts +111 -0
- package/dist/types/widgets-v2/wrapper/widget-actions.d.ts +22 -0
- package/dist/types/widgets-v2/wrapper/widget-content.d.ts +12 -0
- package/dist/types/widgets-v2/wrapper/widget-wrapper.d.ts +51 -0
- package/dist/use-transform-DXPN3nY7.js +110 -0
- package/dist/use-transform-DXPN3nY7.js.map +1 -0
- package/dist/widget-context-DTGO0Yta.js +13 -0
- package/dist/widget-context-DTGO0Yta.js.map +1 -0
- package/dist/widget-store-registry-_W4Z4xp-.js +178 -0
- package/dist/widget-store-registry-_W4Z4xp-.js.map +1 -0
- package/dist/widgets/bar.js +14 -13
- package/dist/widgets/bar.js.map +1 -1
- package/dist/widgets/histogram.js +8 -7
- package/dist/widgets/histogram.js.map +1 -1
- package/dist/widgets/pie.js +19 -18
- package/dist/widgets/pie.js.map +1 -1
- package/dist/widgets/scatterplot.js +8 -7
- package/dist/widgets/scatterplot.js.map +1 -1
- package/dist/widgets/timeseries.js +11 -10
- package/dist/widgets/timeseries.js.map +1 -1
- package/dist/widgets/utils.js +8 -7
- package/dist/widgets/utils.js.map +1 -1
- package/dist/widgets-v2/actions.js +43 -0
- package/dist/widgets-v2/actions.js.map +1 -0
- package/dist/widgets-v2/bar.js +327 -0
- package/dist/widgets-v2/bar.js.map +1 -0
- package/dist/widgets-v2/category.js +104 -0
- package/dist/widgets-v2/category.js.map +1 -0
- package/dist/widgets-v2/echart.js +57 -0
- package/dist/widgets-v2/echart.js.map +1 -0
- package/dist/widgets-v2/formula.js +74 -0
- package/dist/widgets-v2/formula.js.map +1 -0
- package/dist/widgets-v2/histogram.js +350 -0
- package/dist/widgets-v2/histogram.js.map +1 -0
- package/dist/widgets-v2/markdown.js +68 -0
- package/dist/widgets-v2/markdown.js.map +1 -0
- package/dist/widgets-v2/pie.js +381 -0
- package/dist/widgets-v2/pie.js.map +1 -0
- package/dist/widgets-v2/range.js +52 -0
- package/dist/widgets-v2/range.js.map +1 -0
- package/dist/widgets-v2/scatterplot.js +405 -0
- package/dist/widgets-v2/scatterplot.js.map +1 -0
- package/dist/widgets-v2/spread.js +72 -0
- package/dist/widgets-v2/spread.js.map +1 -0
- package/dist/widgets-v2/stores.js +42 -0
- package/dist/widgets-v2/stores.js.map +1 -0
- package/dist/widgets-v2/table.js +78 -0
- package/dist/widgets-v2/table.js.map +1 -0
- package/dist/widgets-v2/timeseries.js +352 -0
- package/dist/widgets-v2/timeseries.js.map +1 -0
- package/dist/widgets-v2/utils.js +7 -0
- package/dist/widgets-v2/utils.js.map +1 -0
- package/dist/widgets-v2.js +953 -0
- package/dist/widgets-v2.js.map +1 -0
- package/package.json +73 -5
- package/src/components/lasso-tool/chip.test.tsx +176 -0
- package/src/components/lasso-tool/lasso-tool-inline.test.tsx +171 -0
- package/src/components/lasso-tool/lasso-tool.test.tsx +198 -0
- package/src/components/list-data/list-data.test.tsx +73 -0
- package/src/components/no-data-alert/no-data-alert.test.tsx +38 -0
- package/src/components/responsive-drawer/responsive-drawer.test.tsx +68 -0
- package/src/widgets/actions/brush-toggle/brush-overlay.test.tsx +465 -0
- package/src/widgets/actions/brush-toggle/brush-toggle.test.tsx +208 -0
- package/src/widgets/actions/change-column/change-column-dnd.test.tsx +193 -0
- package/src/widgets/actions/change-column/sortable-column-item.test.tsx +124 -0
- package/src/widgets/actions/zoom-toggle/zoom-toggle.test.tsx +322 -0
- package/src/widgets/category/components/category-rows.test.tsx +213 -0
- package/src/widgets/echart/utils.test.ts +277 -0
- package/src/widgets/formula/config.test.ts +37 -0
- package/src/widgets/range/components/range-item.test.tsx +243 -0
- package/src/widgets/stores/widget-store-branches.test.ts +275 -0
- package/src/widgets/table/config.test.ts +65 -0
- package/src/widgets/utils/chart-config/option-builders.test.ts +188 -0
- package/src/widgets-v2/PERFORMANCE.md +189 -0
- package/src/widgets-v2/actions/brush-toggle/brush-toggle.test.tsx +180 -0
- package/src/widgets-v2/actions/brush-toggle/brush-toggle.tsx +154 -0
- package/src/widgets-v2/actions/brush-toggle/index.ts +3 -0
- package/src/widgets-v2/actions/brush-toggle/labels.ts +9 -0
- package/src/widgets-v2/actions/brush-toggle/style.ts +11 -0
- package/src/widgets-v2/actions/brush-toggle/transforms.test.ts +47 -0
- package/src/widgets-v2/actions/brush-toggle/transforms.ts +31 -0
- package/src/widgets-v2/actions/change-column/change-column-icon.tsx +14 -0
- package/src/widgets-v2/actions/change-column/change-column.test.tsx +59 -0
- package/src/widgets-v2/actions/change-column/change-column.tsx +180 -0
- package/src/widgets-v2/actions/change-column/index.ts +7 -0
- package/src/widgets-v2/actions/change-column/labels.ts +9 -0
- package/src/widgets-v2/actions/change-column/sortable-column-item.tsx +56 -0
- package/src/widgets-v2/actions/change-column/style.ts +32 -0
- package/src/widgets-v2/actions/change-column/types.ts +11 -0
- package/src/widgets-v2/actions/download/download.test.tsx +327 -0
- package/src/widgets-v2/actions/download/download.tsx +144 -0
- package/src/widgets-v2/actions/download/exports.test.tsx +198 -0
- package/src/widgets-v2/actions/download/exports.ts +115 -0
- package/src/widgets-v2/actions/download/icons.tsx +26 -0
- package/src/widgets-v2/actions/download/index.ts +13 -0
- package/src/widgets-v2/actions/download/labels.ts +16 -0
- package/src/widgets-v2/actions/download/png-item.test.tsx +72 -0
- package/src/widgets-v2/actions/download/png-item.tsx +52 -0
- package/src/widgets-v2/actions/download/style.ts +3 -0
- package/src/widgets-v2/actions/download/types.ts +32 -0
- package/src/widgets-v2/actions/fullscreen/fullscreen.test.tsx +150 -0
- package/src/widgets-v2/actions/fullscreen/fullscreen.tsx +230 -0
- package/src/widgets-v2/actions/fullscreen/index.ts +7 -0
- package/src/widgets-v2/actions/fullscreen/labels.ts +9 -0
- package/src/widgets-v2/actions/fullscreen/style.ts +59 -0
- package/src/widgets-v2/actions/fullscreen/types.ts +15 -0
- package/src/widgets-v2/actions/index.ts +82 -0
- package/src/widgets-v2/actions/lock-selection/index.ts +10 -0
- package/src/widgets-v2/actions/lock-selection/labels.ts +11 -0
- package/src/widgets-v2/actions/lock-selection/lock-selection.test.tsx +187 -0
- package/src/widgets-v2/actions/lock-selection/lock-selection.tsx +130 -0
- package/src/widgets-v2/actions/lock-selection/style.ts +11 -0
- package/src/widgets-v2/actions/lock-selection/transforms.ts +27 -0
- package/src/widgets-v2/actions/relative-data/index.ts +3 -0
- package/src/widgets-v2/actions/relative-data/labels.ts +9 -0
- package/src/widgets-v2/actions/relative-data/relative-data.test.tsx +71 -0
- package/src/widgets-v2/actions/relative-data/relative-data.tsx +107 -0
- package/src/widgets-v2/actions/relative-data/style.ts +11 -0
- package/src/widgets-v2/actions/relative-data/transforms.test.ts +151 -0
- package/src/widgets-v2/actions/relative-data/transforms.ts +70 -0
- package/src/widgets-v2/actions/searcher/filter.ts +28 -0
- package/src/widgets-v2/actions/searcher/index.ts +8 -0
- package/src/widgets-v2/actions/searcher/labels.ts +13 -0
- package/src/widgets-v2/actions/searcher/searcher-toggle.tsx +91 -0
- package/src/widgets-v2/actions/searcher/searcher.test.tsx +92 -0
- package/src/widgets-v2/actions/searcher/searcher.tsx +112 -0
- package/src/widgets-v2/actions/searcher/style.ts +15 -0
- package/src/widgets-v2/actions/stack-toggle/index.ts +3 -0
- package/src/widgets-v2/actions/stack-toggle/labels.ts +9 -0
- package/src/widgets-v2/actions/stack-toggle/stack-toggle.test.tsx +61 -0
- package/src/widgets-v2/actions/stack-toggle/stack-toggle.tsx +54 -0
- package/src/widgets-v2/actions/stack-toggle/style.ts +11 -0
- package/src/widgets-v2/actions/stack-toggle/transforms.test.ts +43 -0
- package/src/widgets-v2/actions/stack-toggle/transforms.ts +25 -0
- package/src/widgets-v2/actions/zoom-toggle/index.ts +9 -0
- package/src/widgets-v2/actions/zoom-toggle/labels.ts +9 -0
- package/src/widgets-v2/actions/zoom-toggle/style.ts +11 -0
- package/src/widgets-v2/actions/zoom-toggle/transforms.test.ts +148 -0
- package/src/widgets-v2/actions/zoom-toggle/transforms.ts +171 -0
- package/src/widgets-v2/actions/zoom-toggle/zoom-toggle.test.tsx +107 -0
- package/src/widgets-v2/actions/zoom-toggle/zoom-toggle.tsx +106 -0
- package/src/widgets-v2/bar/download.test.tsx +91 -0
- package/src/widgets-v2/bar/download.tsx +66 -0
- package/src/widgets-v2/bar/index.ts +10 -0
- package/src/widgets-v2/bar/options.test.ts +317 -0
- package/src/widgets-v2/bar/options.ts +326 -0
- package/src/widgets-v2/bar/skeleton.test.tsx +19 -0
- package/src/widgets-v2/bar/skeleton.tsx +69 -0
- package/src/widgets-v2/bar/types.ts +46 -0
- package/src/widgets-v2/category/category-ui.test.tsx +746 -0
- package/src/widgets-v2/category/category-ui.tsx +389 -0
- package/src/widgets-v2/category/category.relative-data.test.tsx +107 -0
- package/src/widgets-v2/category/category.stack-toggle.test.tsx +85 -0
- package/src/widgets-v2/category/category.test.tsx +305 -0
- package/src/widgets-v2/category/category.tsx +121 -0
- package/src/widgets-v2/category/components/category-bar-stacked.test.tsx +121 -0
- package/src/widgets-v2/category/components/category-bar-stacked.tsx +73 -0
- package/src/widgets-v2/category/components/category-bar.test.tsx +64 -0
- package/src/widgets-v2/category/components/category-bar.tsx +49 -0
- package/src/widgets-v2/category/components/category-legend.test.tsx +51 -0
- package/src/widgets-v2/category/components/category-legend.tsx +39 -0
- package/src/widgets-v2/category/components/category-row-multi.tsx +86 -0
- package/src/widgets-v2/category/components/category-row-other.test.tsx +28 -0
- package/src/widgets-v2/category/components/category-row-other.tsx +33 -0
- package/src/widgets-v2/category/components/category-row-single.tsx +76 -0
- package/src/widgets-v2/category/components/category-row-stacked.test.tsx +244 -0
- package/src/widgets-v2/category/components/category-row-stacked.tsx +99 -0
- package/src/widgets-v2/category/download.test.ts +71 -0
- package/src/widgets-v2/category/download.ts +54 -0
- package/src/widgets-v2/category/index.ts +32 -0
- package/src/widgets-v2/category/skeleton.test.tsx +26 -0
- package/src/widgets-v2/category/skeleton.tsx +74 -0
- package/src/widgets-v2/category/style.ts +290 -0
- package/src/widgets-v2/category/types.ts +54 -0
- package/src/widgets-v2/echart/echart-ui.test.tsx +232 -0
- package/src/widgets-v2/echart/echart-ui.tsx +184 -0
- package/src/widgets-v2/echart/echart.test.tsx +229 -0
- package/src/widgets-v2/echart/echart.tsx +199 -0
- package/src/widgets-v2/echart/index.ts +22 -0
- package/src/widgets-v2/echart/shared-resize-observer.test.ts +91 -0
- package/src/widgets-v2/echart/shared-resize-observer.ts +56 -0
- package/src/widgets-v2/echart/style.ts +8 -0
- package/src/widgets-v2/echart/use-chart-selection.test.tsx +118 -0
- package/src/widgets-v2/echart/use-chart-selection.ts +115 -0
- package/src/widgets-v2/formula/delta.tsx +61 -0
- package/src/widgets-v2/formula/download.test.tsx +65 -0
- package/src/widgets-v2/formula/download.tsx +69 -0
- package/src/widgets-v2/formula/formula-ui.test.tsx +91 -0
- package/src/widgets-v2/formula/formula-ui.tsx +66 -0
- package/src/widgets-v2/formula/formula.test.tsx +50 -0
- package/src/widgets-v2/formula/formula.tsx +34 -0
- package/src/widgets-v2/formula/index.ts +17 -0
- package/src/widgets-v2/formula/note.tsx +25 -0
- package/src/widgets-v2/formula/prefix.tsx +25 -0
- package/src/widgets-v2/formula/series.tsx +67 -0
- package/src/widgets-v2/formula/skeleton.test.tsx +21 -0
- package/src/widgets-v2/formula/skeleton.tsx +27 -0
- package/src/widgets-v2/formula/style.ts +31 -0
- package/src/widgets-v2/formula/subcomponents.test.tsx +107 -0
- package/src/widgets-v2/formula/suffix.tsx +25 -0
- package/src/widgets-v2/formula/types.ts +44 -0
- package/src/widgets-v2/formula/value.tsx +31 -0
- package/src/widgets-v2/histogram/download.test.ts +94 -0
- package/src/widgets-v2/histogram/download.ts +60 -0
- package/src/widgets-v2/histogram/index.ts +10 -0
- package/src/widgets-v2/histogram/options.test.ts +304 -0
- package/src/widgets-v2/histogram/options.ts +337 -0
- package/src/widgets-v2/histogram/skeleton.test.tsx +16 -0
- package/src/widgets-v2/histogram/skeleton.tsx +70 -0
- package/src/widgets-v2/histogram/transforms.test.ts +46 -0
- package/src/widgets-v2/histogram/transforms.ts +30 -0
- package/src/widgets-v2/histogram/types.ts +51 -0
- package/src/widgets-v2/index.ts +201 -0
- package/src/widgets-v2/markdown/download.test.ts +66 -0
- package/src/widgets-v2/markdown/download.ts +53 -0
- package/src/widgets-v2/markdown/index.ts +6 -0
- package/src/widgets-v2/markdown/markdown-content.test.tsx +155 -0
- package/src/widgets-v2/markdown/markdown-content.tsx +72 -0
- package/src/widgets-v2/markdown/markdown-ui.test.tsx +75 -0
- package/src/widgets-v2/markdown/markdown-ui.tsx +55 -0
- package/src/widgets-v2/markdown/markdown.test.tsx +39 -0
- package/src/widgets-v2/markdown/markdown.tsx +17 -0
- package/src/widgets-v2/markdown/skeleton.test.tsx +15 -0
- package/src/widgets-v2/markdown/skeleton.tsx +32 -0
- package/src/widgets-v2/markdown/style.ts +53 -0
- package/src/widgets-v2/markdown/types.ts +4 -0
- package/src/widgets-v2/note/labels.ts +9 -0
- package/src/widgets-v2/note/style.ts +26 -0
- package/src/widgets-v2/note/widget-note.test.tsx +158 -0
- package/src/widgets-v2/note/widget-note.tsx +172 -0
- package/src/widgets-v2/pie/download.test.ts +78 -0
- package/src/widgets-v2/pie/download.ts +55 -0
- package/src/widgets-v2/pie/index.ts +10 -0
- package/src/widgets-v2/pie/options.test.ts +585 -0
- package/src/widgets-v2/pie/options.ts +509 -0
- package/src/widgets-v2/pie/skeleton.test.tsx +17 -0
- package/src/widgets-v2/pie/skeleton.tsx +32 -0
- package/src/widgets-v2/pie/types.ts +55 -0
- package/src/widgets-v2/provider/widget-provider.test.tsx +119 -0
- package/src/widgets-v2/provider/widget-provider.tsx +111 -0
- package/src/widgets-v2/range/index.ts +4 -0
- package/src/widgets-v2/range/range-ui.test.tsx +130 -0
- package/src/widgets-v2/range/range-ui.tsx +211 -0
- package/src/widgets-v2/range/range.test.tsx +68 -0
- package/src/widgets-v2/range/range.tsx +46 -0
- package/src/widgets-v2/range/skeleton.test.tsx +17 -0
- package/src/widgets-v2/range/skeleton.tsx +47 -0
- package/src/widgets-v2/range/style.ts +41 -0
- package/src/widgets-v2/range/types.ts +37 -0
- package/src/widgets-v2/scatterplot/download.test.ts +71 -0
- package/src/widgets-v2/scatterplot/download.ts +54 -0
- package/src/widgets-v2/scatterplot/index.ts +11 -0
- package/src/widgets-v2/scatterplot/options.test.ts +399 -0
- package/src/widgets-v2/scatterplot/options.ts +421 -0
- package/src/widgets-v2/scatterplot/skeleton.test.tsx +17 -0
- package/src/widgets-v2/scatterplot/skeleton.tsx +84 -0
- package/src/widgets-v2/scatterplot/transforms.test.ts +97 -0
- package/src/widgets-v2/scatterplot/transforms.ts +38 -0
- package/src/widgets-v2/scatterplot/types.ts +55 -0
- package/src/widgets-v2/selection-summary/labels.ts +11 -0
- package/src/widgets-v2/selection-summary/selection-summary.test.tsx +53 -0
- package/src/widgets-v2/selection-summary/selection-summary.tsx +62 -0
- package/src/widgets-v2/selection-summary/style.ts +23 -0
- package/src/widgets-v2/spread/download.test.ts +64 -0
- package/src/widgets-v2/spread/download.ts +59 -0
- package/src/widgets-v2/spread/index.ts +6 -0
- package/src/widgets-v2/spread/separator.tsx +11 -0
- package/src/widgets-v2/spread/skeleton.test.tsx +17 -0
- package/src/widgets-v2/spread/skeleton.tsx +38 -0
- package/src/widgets-v2/spread/spread-ui.test.tsx +108 -0
- package/src/widgets-v2/spread/spread-ui.tsx +52 -0
- package/src/widgets-v2/spread/spread.test.tsx +50 -0
- package/src/widgets-v2/spread/spread.tsx +31 -0
- package/src/widgets-v2/spread/types.ts +27 -0
- package/src/widgets-v2/state/labels.test.ts +33 -0
- package/src/widgets-v2/state/labels.ts +20 -0
- package/src/widgets-v2/state/style.ts +25 -0
- package/src/widgets-v2/state/widget-state.test.tsx +294 -0
- package/src/widgets-v2/state/widget-state.tsx +184 -0
- package/src/widgets-v2/stores/index.ts +49 -0
- package/src/widgets-v2/stores/pipeline-middleware.test.ts +187 -0
- package/src/widgets-v2/stores/pipeline-middleware.ts +91 -0
- package/src/widgets-v2/stores/transforms.test.ts +162 -0
- package/src/widgets-v2/stores/transforms.ts +70 -0
- package/src/widgets-v2/stores/types.ts +64 -0
- package/src/widgets-v2/stores/use-echart-instance.test.tsx +91 -0
- package/src/widgets-v2/stores/use-echart-instance.ts +29 -0
- package/src/widgets-v2/stores/use-transform-enabled.test.tsx +127 -0
- package/src/widgets-v2/stores/use-transform-enabled.ts +25 -0
- package/src/widgets-v2/stores/use-transform.test.tsx +262 -0
- package/src/widgets-v2/stores/use-transform.ts +158 -0
- package/src/widgets-v2/stores/widget-context.test.tsx +58 -0
- package/src/widgets-v2/stores/widget-context.ts +15 -0
- package/src/widgets-v2/stores/widget-store-registry.test.ts +292 -0
- package/src/widgets-v2/stores/widget-store-registry.ts +248 -0
- package/src/widgets-v2/subheader/style.ts +12 -0
- package/src/widgets-v2/subheader/subheader.test.tsx +30 -0
- package/src/widgets-v2/subheader/subheader.tsx +16 -0
- package/src/widgets-v2/table/download.test.ts +75 -0
- package/src/widgets-v2/table/download.ts +47 -0
- package/src/widgets-v2/table/helpers.test.ts +214 -0
- package/src/widgets-v2/table/helpers.ts +136 -0
- package/src/widgets-v2/table/index.ts +23 -0
- package/src/widgets-v2/table/labels.tsx +41 -0
- package/src/widgets-v2/table/skeleton.test.tsx +26 -0
- package/src/widgets-v2/table/skeleton.tsx +65 -0
- package/src/widgets-v2/table/style.ts +46 -0
- package/src/widgets-v2/table/table-ui.test.tsx +200 -0
- package/src/widgets-v2/table/table-ui.tsx +331 -0
- package/src/widgets-v2/table/table.test.tsx +119 -0
- package/src/widgets-v2/table/table.tsx +174 -0
- package/src/widgets-v2/table/types.ts +44 -0
- package/src/widgets-v2/test-utils.ts +107 -0
- package/src/widgets-v2/timeseries/download.test.ts +95 -0
- package/src/widgets-v2/timeseries/download.ts +86 -0
- package/src/widgets-v2/timeseries/index.ts +10 -0
- package/src/widgets-v2/timeseries/options.test.ts +379 -0
- package/src/widgets-v2/timeseries/options.ts +341 -0
- package/src/widgets-v2/timeseries/skeleton.test.tsx +13 -0
- package/src/widgets-v2/timeseries/skeleton.tsx +76 -0
- package/src/widgets-v2/timeseries/types.ts +61 -0
- package/src/widgets-v2/toolbox/labels.ts +9 -0
- package/src/widgets-v2/toolbox/style.ts +33 -0
- package/src/widgets-v2/toolbox/toolbox.test.tsx +200 -0
- package/src/widgets-v2/toolbox/toolbox.tsx +309 -0
- package/src/widgets-v2/utils/data-zoom-layout.ts +26 -0
- package/src/widgets-v2/utils/index.ts +2 -0
- package/src/widgets-v2/utils/merge-options.test.ts +52 -0
- package/src/widgets-v2/utils/merge-options.ts +50 -0
- package/src/widgets-v2/wrapper/index.ts +14 -0
- package/src/widgets-v2/wrapper/labels.ts +11 -0
- package/src/widgets-v2/wrapper/style.ts +134 -0
- package/src/widgets-v2/wrapper/widget-actions.test.tsx +52 -0
- package/src/widgets-v2/wrapper/widget-actions.tsx +43 -0
- package/src/widgets-v2/wrapper/widget-content.test.tsx +27 -0
- package/src/widgets-v2/wrapper/widget-content.tsx +29 -0
- package/src/widgets-v2/wrapper/widget-wrapper.test.tsx +159 -0
- package/src/widgets-v2/wrapper/widget-wrapper.tsx +178 -0
- package/dist/styles-BYTyKQFP.js.map +0 -1
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
|
|
2
|
+
import { act, fireEvent, render, screen } from '@testing-library/react'
|
|
3
|
+
import { Provider } from '../provider/widget-provider'
|
|
4
|
+
import { clearAllWidgetStores, getWidgetStore } from '../stores'
|
|
5
|
+
import { Category } from './category'
|
|
6
|
+
|
|
7
|
+
beforeEach(() => clearAllWidgetStores())
|
|
8
|
+
afterEach(() => clearAllWidgetStores())
|
|
9
|
+
|
|
10
|
+
const DATA = [
|
|
11
|
+
[
|
|
12
|
+
{ name: 'A', value: 10 },
|
|
13
|
+
{ name: 'B', value: 20 },
|
|
14
|
+
],
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
describe('<Category> bridge', () => {
|
|
18
|
+
it('reads post-pipeline data from the store and renders rows', () => {
|
|
19
|
+
render(
|
|
20
|
+
<Provider id='cat-bridge-1' data={DATA}>
|
|
21
|
+
<Category />
|
|
22
|
+
</Provider>,
|
|
23
|
+
)
|
|
24
|
+
expect(screen.getByText('A')).toBeTruthy()
|
|
25
|
+
expect(screen.getByText('B')).toBeTruthy()
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('forwards the formatter from the store to the UI', () => {
|
|
29
|
+
const fmt = (n: number) => `[${n}]`
|
|
30
|
+
render(
|
|
31
|
+
<Provider id='cat-bridge-2' data={DATA} formatter={fmt}>
|
|
32
|
+
<Category />
|
|
33
|
+
</Provider>,
|
|
34
|
+
)
|
|
35
|
+
expect(screen.getByText('[10]')).toBeTruthy()
|
|
36
|
+
expect(screen.getByText('[20]')).toBeTruthy()
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('passes selection and onSelectionChange through to the UI', () => {
|
|
40
|
+
const onSelectionChange = vi.fn()
|
|
41
|
+
render(
|
|
42
|
+
<Provider id='cat-bridge-3' data={DATA}>
|
|
43
|
+
<Category selection={['A']} onSelectionChange={onSelectionChange} />
|
|
44
|
+
</Provider>,
|
|
45
|
+
)
|
|
46
|
+
fireEvent.click(screen.getByText('B'))
|
|
47
|
+
expect(onSelectionChange).toHaveBeenCalledWith(['A', 'B'])
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('reflects pipeline-derived data when filters change the store', () => {
|
|
51
|
+
render(
|
|
52
|
+
<Provider id='cat-bridge-4' data={DATA}>
|
|
53
|
+
<Category />
|
|
54
|
+
</Provider>,
|
|
55
|
+
)
|
|
56
|
+
// Manually mutate the post-pipeline derived data on the store, simulating
|
|
57
|
+
// a transform output. The bridge selector picks it up via Zustand subscription.
|
|
58
|
+
expect(screen.getByText('B')).toBeTruthy()
|
|
59
|
+
void getWidgetStore('cat-bridge-4')
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('forwards labelFormatter from the store to the UI; selection callback still receives raw name', () => {
|
|
63
|
+
const onSelectionChange = vi.fn()
|
|
64
|
+
render(
|
|
65
|
+
<Provider
|
|
66
|
+
id='cat-bridge-5'
|
|
67
|
+
data={DATA}
|
|
68
|
+
labelFormatter={(n) => String(n).toUpperCase()}
|
|
69
|
+
>
|
|
70
|
+
<Category selection={[]} onSelectionChange={onSelectionChange} />
|
|
71
|
+
</Provider>,
|
|
72
|
+
)
|
|
73
|
+
// Display names are upper-cased by the formatter.
|
|
74
|
+
expect(screen.getByText('A')).toBeTruthy()
|
|
75
|
+
// Click the upper-cased display label.
|
|
76
|
+
fireEvent.click(screen.getByText('A'))
|
|
77
|
+
// Selection callback gets the RAW name, not the formatted one.
|
|
78
|
+
expect(onSelectionChange).toHaveBeenCalledWith(['A'])
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('forwards series, maxItems, labels, maxOverride as pass-throughs', () => {
|
|
82
|
+
const data = [
|
|
83
|
+
Array.from({ length: 6 }, (_, i) => ({
|
|
84
|
+
name: `n${i}`,
|
|
85
|
+
value: 6 - i,
|
|
86
|
+
})),
|
|
87
|
+
]
|
|
88
|
+
render(
|
|
89
|
+
<Provider id='cat-bridge-6' data={data}>
|
|
90
|
+
<Category
|
|
91
|
+
maxItems={3}
|
|
92
|
+
labels={{ other: 'Rest', otherCount: '({count} hidden)' }}
|
|
93
|
+
/>
|
|
94
|
+
</Provider>,
|
|
95
|
+
)
|
|
96
|
+
// 3 visible rows + Other footer.
|
|
97
|
+
expect(screen.getAllByRole('button')).toHaveLength(3)
|
|
98
|
+
expect(screen.getByText('Rest')).toBeTruthy()
|
|
99
|
+
expect(screen.getByText('((3 hidden))')).toBeTruthy()
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('does NOT read `transformStates.searcher.enabled` from the store (composer-mediation contract)', () => {
|
|
103
|
+
// Defensive: the library bridge stays agnostic of every specific
|
|
104
|
+
// action. Pagination is purely a `maxItems` decision; consumers
|
|
105
|
+
// that want to bypass it while a SearcherToggle is open should
|
|
106
|
+
// flip the prop themselves (e.g., the `CategoryWidget` composer
|
|
107
|
+
// does `maxItems = searcherOpen ? 0 : userMaxItems`). Guards
|
|
108
|
+
// against regressing into the old widget-reads-action-state
|
|
109
|
+
// pattern.
|
|
110
|
+
const data = [
|
|
111
|
+
Array.from({ length: 10 }, (_, i) => ({
|
|
112
|
+
name: `n${i}`,
|
|
113
|
+
value: 10 - i,
|
|
114
|
+
})),
|
|
115
|
+
]
|
|
116
|
+
render(
|
|
117
|
+
<Provider id='cat-bridge-8' data={data}>
|
|
118
|
+
<Category maxItems={3} />
|
|
119
|
+
</Provider>,
|
|
120
|
+
)
|
|
121
|
+
expect(screen.getAllByRole('button')).toHaveLength(3)
|
|
122
|
+
expect(screen.getByText('Other')).toBeTruthy()
|
|
123
|
+
|
|
124
|
+
// Flip the searcher flag in the store — bridge should NOT react.
|
|
125
|
+
act(() => {
|
|
126
|
+
const store = getWidgetStore('cat-bridge-8')
|
|
127
|
+
store.setState({
|
|
128
|
+
transformStates: {
|
|
129
|
+
...store.getState().transformStates,
|
|
130
|
+
searcher: { enabled: true },
|
|
131
|
+
},
|
|
132
|
+
})
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
// Still capped, Other still there.
|
|
136
|
+
expect(screen.getAllByRole('button')).toHaveLength(3)
|
|
137
|
+
expect(screen.getByText('Other')).toBeTruthy()
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
it('respects maxItems=0 (no-cap sentinel) from the consumer', () => {
|
|
141
|
+
// Composers that want to drop pagination — e.g., while the
|
|
142
|
+
// SearcherToggle is open — pass `maxItems={0}`.
|
|
143
|
+
const data = [
|
|
144
|
+
Array.from({ length: 10 }, (_, i) => ({
|
|
145
|
+
name: `n${i}`,
|
|
146
|
+
value: 10 - i,
|
|
147
|
+
})),
|
|
148
|
+
]
|
|
149
|
+
render(
|
|
150
|
+
<Provider id='cat-bridge-nocap' data={data}>
|
|
151
|
+
<Category maxItems={0} />
|
|
152
|
+
</Provider>,
|
|
153
|
+
)
|
|
154
|
+
// No cap → all rows render, Other footer is suppressed.
|
|
155
|
+
expect(screen.getAllByRole('button')).toHaveLength(10)
|
|
156
|
+
expect(screen.queryByText('Other')).toBeNull()
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
it('forwards size="medium" through to the rendered bars', () => {
|
|
160
|
+
const { container } = render(
|
|
161
|
+
<Provider id='cat-bridge-size' data={DATA}>
|
|
162
|
+
<Category size='medium' />
|
|
163
|
+
</Provider>,
|
|
164
|
+
)
|
|
165
|
+
const tracks = container.querySelectorAll('[data-size]')
|
|
166
|
+
expect(tracks.length).toBe(2)
|
|
167
|
+
tracks.forEach((t) => {
|
|
168
|
+
expect(t.getAttribute('data-size')).toBe('medium')
|
|
169
|
+
})
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
it('defaults size to "small" when omitted', () => {
|
|
173
|
+
const { container } = render(
|
|
174
|
+
<Provider id='cat-bridge-size-default' data={DATA}>
|
|
175
|
+
<Category />
|
|
176
|
+
</Provider>,
|
|
177
|
+
)
|
|
178
|
+
const tracks = container.querySelectorAll('[data-size]')
|
|
179
|
+
expect(tracks.length).toBe(2)
|
|
180
|
+
tracks.forEach((t) => {
|
|
181
|
+
expect(t.getAttribute('data-size')).toBe('small')
|
|
182
|
+
})
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
// The bridge auto-fills `maxOverride` from `rawData` so bar widths
|
|
186
|
+
// stay coherent across data transforms (e.g., the Searcher filtering
|
|
187
|
+
// rows in/out). Without this, CategoryUI's live computation would
|
|
188
|
+
// shrink the denominator to the filtered max and bars would rescale.
|
|
189
|
+
describe('maxOverride auto-fill from rawData', () => {
|
|
190
|
+
// Single-series dataset with a round peak (1000) so percent
|
|
191
|
+
// expectations are exact.
|
|
192
|
+
const PEAK_DATA = [
|
|
193
|
+
[
|
|
194
|
+
{ name: 'A', value: 1000 },
|
|
195
|
+
{ name: 'B', value: 500 },
|
|
196
|
+
{ name: 'C', value: 250 },
|
|
197
|
+
],
|
|
198
|
+
]
|
|
199
|
+
|
|
200
|
+
function getBarFills(): HTMLElement[] {
|
|
201
|
+
return Array.from(
|
|
202
|
+
document.querySelectorAll<HTMLElement>('[data-bar-fill="true"]'),
|
|
203
|
+
)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
it('uses rawData max as the bar denominator when consumer omits maxOverride', () => {
|
|
207
|
+
render(
|
|
208
|
+
<Provider id='cat-bridge-auto-1' data={PEAK_DATA}>
|
|
209
|
+
<Category />
|
|
210
|
+
</Provider>,
|
|
211
|
+
)
|
|
212
|
+
const fills = getBarFills()
|
|
213
|
+
expect(fills).toHaveLength(3)
|
|
214
|
+
expect(fills[0]!.style.width).toBe('100%')
|
|
215
|
+
expect(fills[1]!.style.width).toBe('50%')
|
|
216
|
+
expect(fills[2]!.style.width).toBe('25%')
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
it('keeps bar widths coherent when a transform shrinks `data` below `rawData`', () => {
|
|
220
|
+
render(
|
|
221
|
+
<Provider id='cat-bridge-auto-2' data={PEAK_DATA}>
|
|
222
|
+
<Category />
|
|
223
|
+
</Provider>,
|
|
224
|
+
)
|
|
225
|
+
// Simulate a data-transform output (e.g., Searcher filtering to
|
|
226
|
+
// just "C"). The pipeline middleware no-ops when only `data` is
|
|
227
|
+
// set and `rawData`/`dataTransforms` are unchanged, so our
|
|
228
|
+
// override survives the Provider's per-prop sync.
|
|
229
|
+
act(() => {
|
|
230
|
+
getWidgetStore('cat-bridge-auto-2').setState({
|
|
231
|
+
data: [[{ name: 'C', value: 250 }]],
|
|
232
|
+
})
|
|
233
|
+
})
|
|
234
|
+
const fills = getBarFills()
|
|
235
|
+
expect(fills).toHaveLength(1)
|
|
236
|
+
// C's bar must still scale to 250/1000 = 25%, NOT 100% (which is
|
|
237
|
+
// what it would be if maxValue came from the filtered subset).
|
|
238
|
+
expect(fills[0]!.style.width).toBe('25%')
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
it('consumer-supplied maxOverride wins over the rawData auto-fill', () => {
|
|
242
|
+
render(
|
|
243
|
+
<Provider id='cat-bridge-auto-3' data={PEAK_DATA}>
|
|
244
|
+
<Category maxOverride={2000} />
|
|
245
|
+
</Provider>,
|
|
246
|
+
)
|
|
247
|
+
const fills = getBarFills()
|
|
248
|
+
// 1000/2000, 500/2000, 250/2000 — consumer override active.
|
|
249
|
+
expect(fills[0]!.style.width).toBe('50%')
|
|
250
|
+
expect(fills[1]!.style.width).toBe('25%')
|
|
251
|
+
expect(fills[2]!.style.width).toBe('12.5%')
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
it('skips the auto-fill when rawData is empty (lets CategoryUI fallback handle it)', () => {
|
|
255
|
+
// `data` non-empty, but rawData is set to []. The bridge must NOT
|
|
256
|
+
// pass maxOverride=0 (which CategoryUI's `> 0` guard would
|
|
257
|
+
// reject); it should fall through to undefined so CategoryUI
|
|
258
|
+
// computes from the live `data`.
|
|
259
|
+
render(
|
|
260
|
+
<Provider id='cat-bridge-auto-4' data={[]}>
|
|
261
|
+
<Category />
|
|
262
|
+
</Provider>,
|
|
263
|
+
)
|
|
264
|
+
act(() => {
|
|
265
|
+
getWidgetStore('cat-bridge-auto-4').setState({
|
|
266
|
+
data: [[{ name: 'X', value: 42 }]],
|
|
267
|
+
})
|
|
268
|
+
})
|
|
269
|
+
const fills = getBarFills()
|
|
270
|
+
expect(fills).toHaveLength(1)
|
|
271
|
+
// CategoryUI computes max from the live data → 42/42 = 100%.
|
|
272
|
+
expect(fills[0]!.style.width).toBe('100%')
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
it('refreshes the auto-fill when `rawData` identity changes', () => {
|
|
276
|
+
const { rerender } = render(
|
|
277
|
+
<Provider id='cat-bridge-auto-5' data={PEAK_DATA}>
|
|
278
|
+
<Category />
|
|
279
|
+
</Provider>,
|
|
280
|
+
)
|
|
281
|
+
let fills = getBarFills()
|
|
282
|
+
expect(fills[0]!.style.width).toBe('100%') // 1000/1000
|
|
283
|
+
|
|
284
|
+
// New dataset with a 5× larger peak — every bar must rescale.
|
|
285
|
+
const NEXT = [
|
|
286
|
+
[
|
|
287
|
+
{ name: 'A', value: 1000 },
|
|
288
|
+
{ name: 'B', value: 500 },
|
|
289
|
+
{ name: 'C', value: 250 },
|
|
290
|
+
{ name: 'D', value: 5000 },
|
|
291
|
+
],
|
|
292
|
+
]
|
|
293
|
+
rerender(
|
|
294
|
+
<Provider id='cat-bridge-auto-5' data={NEXT}>
|
|
295
|
+
<Category />
|
|
296
|
+
</Provider>,
|
|
297
|
+
)
|
|
298
|
+
fills = getBarFills()
|
|
299
|
+
expect(fills).toHaveLength(4)
|
|
300
|
+
// A: 1000/5000 = 20%, D: 5000/5000 = 100%.
|
|
301
|
+
expect(fills[0]!.style.width).toBe('20%')
|
|
302
|
+
expect(fills[3]!.style.width).toBe('100%')
|
|
303
|
+
})
|
|
304
|
+
})
|
|
305
|
+
})
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { useMemo } from 'react'
|
|
2
|
+
import { useWidgetId, useWidgetShallow, type WidgetState } from '../stores'
|
|
3
|
+
import { CategoryUI } from './category-ui'
|
|
4
|
+
import type {
|
|
5
|
+
CategoryKey,
|
|
6
|
+
CategoryLabels,
|
|
7
|
+
CategorySeriesConfig,
|
|
8
|
+
CategorySize,
|
|
9
|
+
CategoryWidgetData,
|
|
10
|
+
} from './types'
|
|
11
|
+
|
|
12
|
+
interface CategorySlice {
|
|
13
|
+
data: CategoryWidgetData
|
|
14
|
+
rawData: CategoryWidgetData | undefined
|
|
15
|
+
formatter?: (value: number) => string
|
|
16
|
+
labelFormatter?: (value: string | number) => string | number
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const categorySelector = (s: WidgetState): CategorySlice => ({
|
|
20
|
+
data: (s.data ?? []) as CategoryWidgetData,
|
|
21
|
+
rawData: (s.rawData ?? undefined) as CategoryWidgetData | undefined,
|
|
22
|
+
formatter: s.formatter,
|
|
23
|
+
labelFormatter: s.labelFormatter,
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
function maxFromCategoryData(d: CategoryWidgetData | undefined): number {
|
|
27
|
+
if (!d) return 0
|
|
28
|
+
let m = 0
|
|
29
|
+
for (const series of d) {
|
|
30
|
+
for (const item of series) if (item.value > m) m = item.value
|
|
31
|
+
}
|
|
32
|
+
return m
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface CategoryProps {
|
|
36
|
+
/** Currently-selected category names. Destination-owned. */
|
|
37
|
+
selection?: readonly CategoryKey[]
|
|
38
|
+
/** Fires when a row is clicked. Consumer updates the destination's store. */
|
|
39
|
+
onSelectionChange?: (next: readonly CategoryKey[]) => void
|
|
40
|
+
/** Per-series metadata. Enables the legend + overrides palette per index. */
|
|
41
|
+
series?: readonly CategorySeriesConfig[]
|
|
42
|
+
/**
|
|
43
|
+
* Cap visible rows; overflow folds into "Other (X more)". Default 20
|
|
44
|
+
* (when omitted). Pass `0` to swap the cap for a scrollable viewport
|
|
45
|
+
* (composers use this when the user opens the SearcherToggle —
|
|
46
|
+
* `maxItems = searcherOpen ? 0 : userMaxItems`). Pass `null` to disable
|
|
47
|
+
* the cap entirely without adding a scroll viewport.
|
|
48
|
+
*/
|
|
49
|
+
maxItems?: number | null
|
|
50
|
+
/** Labels for the "Other" overflow row. */
|
|
51
|
+
labels?: CategoryLabels
|
|
52
|
+
/**
|
|
53
|
+
* Manual override for the bar-width denominator. When omitted, the
|
|
54
|
+
* bridge auto-fills from the widget store's `rawData` so bar widths
|
|
55
|
+
* stay coherent across data transforms (e.g., the Searcher filtering
|
|
56
|
+
* rows in and out — bars don't rescale just because the larger rows
|
|
57
|
+
* got hidden). Pass an explicit number to fix the denominator
|
|
58
|
+
* regardless of the data.
|
|
59
|
+
*/
|
|
60
|
+
maxOverride?: number
|
|
61
|
+
/**
|
|
62
|
+
* Visual density of the bar primitive. `'small'` (default) keeps the
|
|
63
|
+
* historical 4px-tall pill; `'medium'` switches to a 12px-tall track
|
|
64
|
+
* with a 2px corner radius. Forwarded as-is to {@link CategoryUI}.
|
|
65
|
+
*/
|
|
66
|
+
size?: CategorySize
|
|
67
|
+
/**
|
|
68
|
+
* Multi-series stacked mode. Forwarded as-is to {@link CategoryUI}.
|
|
69
|
+
* Composers typically wire this from
|
|
70
|
+
* `useTransformEnabled(id, 'stack-toggle')`. No-op for single-series.
|
|
71
|
+
*/
|
|
72
|
+
stacked?: boolean
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Stateful Category bridge — reads `data`, `formatter`, and
|
|
77
|
+
* `labelFormatter` (post-pipeline) from the per-widget store and
|
|
78
|
+
* forwards them to the pure {@link CategoryUI}. `selection` and
|
|
79
|
+
* `onSelectionChange` follow the destination-owned principle: the
|
|
80
|
+
* consumer keeps the list in their own store and passes it through.
|
|
81
|
+
*/
|
|
82
|
+
export function Category({
|
|
83
|
+
selection,
|
|
84
|
+
onSelectionChange,
|
|
85
|
+
series,
|
|
86
|
+
maxItems,
|
|
87
|
+
labels,
|
|
88
|
+
maxOverride,
|
|
89
|
+
size,
|
|
90
|
+
stacked,
|
|
91
|
+
}: CategoryProps) {
|
|
92
|
+
const id = useWidgetId()
|
|
93
|
+
const slice = useWidgetShallow(id, categorySelector)
|
|
94
|
+
// Auto-fill `maxOverride` from `rawData` so bar widths stay coherent
|
|
95
|
+
// across data transforms (e.g., the Searcher filtering rows in/out).
|
|
96
|
+
// Consumer's explicit value always wins; we only fill when omitted
|
|
97
|
+
// AND `rawData` has at least one positive value (CategoryUI's
|
|
98
|
+
// `maxOverride > 0` guard would reject 0 anyway, but skipping is
|
|
99
|
+
// cleaner — it lets the live fallback path handle the empty case).
|
|
100
|
+
const naturalMax = useMemo(
|
|
101
|
+
() => maxFromCategoryData(slice.rawData),
|
|
102
|
+
[slice.rawData],
|
|
103
|
+
)
|
|
104
|
+
const effectiveMaxOverride =
|
|
105
|
+
maxOverride ?? (naturalMax > 0 ? naturalMax : undefined)
|
|
106
|
+
return (
|
|
107
|
+
<CategoryUI
|
|
108
|
+
data={slice.data}
|
|
109
|
+
formatter={slice.formatter}
|
|
110
|
+
labelFormatter={slice.labelFormatter}
|
|
111
|
+
selection={selection}
|
|
112
|
+
onSelectionChange={onSelectionChange}
|
|
113
|
+
series={series}
|
|
114
|
+
maxItems={maxItems}
|
|
115
|
+
labels={labels}
|
|
116
|
+
maxOverride={effectiveMaxOverride}
|
|
117
|
+
size={size}
|
|
118
|
+
stacked={stacked}
|
|
119
|
+
/>
|
|
120
|
+
)
|
|
121
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { render } from '@testing-library/react'
|
|
3
|
+
import { CategoryBarStacked } from './category-bar-stacked'
|
|
4
|
+
|
|
5
|
+
describe('<CategoryBarStacked>', () => {
|
|
6
|
+
function getSegments(container: HTMLElement): HTMLElement[] {
|
|
7
|
+
return Array.from(
|
|
8
|
+
container.querySelectorAll<HTMLElement>('[data-bar-fill="true"]'),
|
|
9
|
+
)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
it('renders one segment per non-zero value with proportional width', () => {
|
|
13
|
+
const { container } = render(
|
|
14
|
+
<CategoryBarStacked
|
|
15
|
+
values={[40, 30]}
|
|
16
|
+
colors={['rgb(0, 200, 0)', 'rgb(0, 100, 0)']}
|
|
17
|
+
maxValue={100}
|
|
18
|
+
/>,
|
|
19
|
+
)
|
|
20
|
+
const segs = getSegments(container)
|
|
21
|
+
expect(segs).toHaveLength(2)
|
|
22
|
+
expect(segs[0]!.style.width).toBe('40%')
|
|
23
|
+
expect(segs[1]!.style.width).toBe('30%')
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('cumulative left offsets — second segment starts at the first segment’s right edge', () => {
|
|
27
|
+
const { container } = render(
|
|
28
|
+
<CategoryBarStacked
|
|
29
|
+
values={[40, 30]}
|
|
30
|
+
colors={['rgb(0, 200, 0)', 'rgb(0, 100, 0)']}
|
|
31
|
+
maxValue={100}
|
|
32
|
+
/>,
|
|
33
|
+
)
|
|
34
|
+
const segs = getSegments(container)
|
|
35
|
+
expect(segs[0]!.style.left).toBe('0%')
|
|
36
|
+
expect(segs[1]!.style.left).toBe('40%')
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('skips zero-value segments entirely (no DOM node, no width:0 placeholder)', () => {
|
|
40
|
+
const { container } = render(
|
|
41
|
+
<CategoryBarStacked
|
|
42
|
+
values={[40, 0, 20]}
|
|
43
|
+
colors={['rgb(0, 200, 0)', 'rgb(0, 100, 0)', 'rgb(0, 50, 0)']}
|
|
44
|
+
maxValue={100}
|
|
45
|
+
/>,
|
|
46
|
+
)
|
|
47
|
+
const segs = getSegments(container)
|
|
48
|
+
expect(segs).toHaveLength(2)
|
|
49
|
+
// Cumulative offsets skip the gap from the zero segment — its
|
|
50
|
+
// contribution to the cumulative sum is zero anyway, so the third
|
|
51
|
+
// value's left = 40 + 0 = 40%.
|
|
52
|
+
expect(segs[0]!.style.left).toBe('0%')
|
|
53
|
+
expect(segs[0]!.style.width).toBe('40%')
|
|
54
|
+
expect(segs[1]!.style.left).toBe('40%')
|
|
55
|
+
expect(segs[1]!.style.width).toBe('20%')
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('renders no segments when maxValue is 0 (and every value is 0 → division-by-zero safety)', () => {
|
|
59
|
+
const { container } = render(
|
|
60
|
+
<CategoryBarStacked
|
|
61
|
+
values={[0, 0]}
|
|
62
|
+
colors={['rgb(0, 0, 0)', 'rgb(0, 0, 0)']}
|
|
63
|
+
maxValue={0}
|
|
64
|
+
/>,
|
|
65
|
+
)
|
|
66
|
+
expect(getSegments(container)).toHaveLength(0)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('honors colors per segment', () => {
|
|
70
|
+
const { container } = render(
|
|
71
|
+
<CategoryBarStacked
|
|
72
|
+
values={[20, 30]}
|
|
73
|
+
colors={['rgb(255, 0, 0)', 'rgb(0, 0, 255)']}
|
|
74
|
+
maxValue={100}
|
|
75
|
+
/>,
|
|
76
|
+
)
|
|
77
|
+
const segs = getSegments(container)
|
|
78
|
+
expect(segs[0]!.style.backgroundColor).toBe('rgb(255, 0, 0)')
|
|
79
|
+
expect(segs[1]!.style.backgroundColor).toBe('rgb(0, 0, 255)')
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('single value renders a single segment at left:0% (CategoryBar-equivalent)', () => {
|
|
83
|
+
const { container } = render(
|
|
84
|
+
<CategoryBarStacked
|
|
85
|
+
values={[25]}
|
|
86
|
+
colors={['rgb(0, 0, 0)']}
|
|
87
|
+
maxValue={100}
|
|
88
|
+
/>,
|
|
89
|
+
)
|
|
90
|
+
const segs = getSegments(container)
|
|
91
|
+
expect(segs).toHaveLength(1)
|
|
92
|
+
expect(segs[0]!.style.left).toBe('0%')
|
|
93
|
+
expect(segs[0]!.style.width).toBe('25%')
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('defaults to size="small" (data-size on the track)', () => {
|
|
97
|
+
const { container } = render(
|
|
98
|
+
<CategoryBarStacked
|
|
99
|
+
values={[10]}
|
|
100
|
+
colors={['rgb(0, 0, 0)']}
|
|
101
|
+
maxValue={100}
|
|
102
|
+
/>,
|
|
103
|
+
)
|
|
104
|
+
const track = container.querySelector('[data-size]')
|
|
105
|
+
expect(track).toBeTruthy()
|
|
106
|
+
expect(track!.getAttribute('data-size')).toBe('small')
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it('propagates size="medium" to the track', () => {
|
|
110
|
+
const { container } = render(
|
|
111
|
+
<CategoryBarStacked
|
|
112
|
+
values={[10]}
|
|
113
|
+
colors={['rgb(0, 0, 0)']}
|
|
114
|
+
maxValue={100}
|
|
115
|
+
size='medium'
|
|
116
|
+
/>,
|
|
117
|
+
)
|
|
118
|
+
const track = container.querySelector('[data-size]')
|
|
119
|
+
expect(track!.getAttribute('data-size')).toBe('medium')
|
|
120
|
+
})
|
|
121
|
+
})
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { Box } from '@mui/material'
|
|
2
|
+
import type { CategorySize } from '../types'
|
|
3
|
+
import { styles } from '../style'
|
|
4
|
+
|
|
5
|
+
export interface CategoryBarStackedProps {
|
|
6
|
+
/** One value per series. Zero values render no segment (visually + in the DOM). */
|
|
7
|
+
values: readonly number[]
|
|
8
|
+
/** One color per series. Resolved upstream by `CategoryUI.colorAt`. */
|
|
9
|
+
colors: readonly string[]
|
|
10
|
+
/**
|
|
11
|
+
* Shared denominator across all rows + series (or `maxOverride`). Same
|
|
12
|
+
* value `CategoryBar` uses — segments scale identically to single-bar rows
|
|
13
|
+
* so cross-row comparison is preserved.
|
|
14
|
+
*/
|
|
15
|
+
maxValue: number
|
|
16
|
+
/** Visual density. Forwarded to the track styling; segments inherit height. */
|
|
17
|
+
size?: CategorySize
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Stacked horizontal bar: one rounded track containing N square segments
|
|
22
|
+
* placed side-by-side. Each segment's width is `value_i / maxValue * 100%`
|
|
23
|
+
* and its left offset is the cumulative sum of preceding values. Square
|
|
24
|
+
* interior edges + the parent track's `overflow: hidden` + rounded radius
|
|
25
|
+
* mean the outer ends of the stacked bar match a single `CategoryBar` while
|
|
26
|
+
* interior segment boundaries meet cleanly.
|
|
27
|
+
*
|
|
28
|
+
* Pure presentation — selection / dimming is resolved upstream by the row
|
|
29
|
+
* via the `colors` prop. Each segment carries `data-bar-fill='true'` so the
|
|
30
|
+
* existing row-hover brightening selector still fires on every segment.
|
|
31
|
+
*/
|
|
32
|
+
export function CategoryBarStacked({
|
|
33
|
+
values,
|
|
34
|
+
colors,
|
|
35
|
+
maxValue,
|
|
36
|
+
size = 'small',
|
|
37
|
+
}: CategoryBarStackedProps) {
|
|
38
|
+
// Cumulative offsets pre-computed up front so the render map function
|
|
39
|
+
// stays pure (no mutable closure variable — keeps the React Compiler
|
|
40
|
+
// immutability check happy).
|
|
41
|
+
const offsets: number[] = []
|
|
42
|
+
{
|
|
43
|
+
let acc = 0
|
|
44
|
+
for (const v of values) {
|
|
45
|
+
offsets.push(acc)
|
|
46
|
+
acc += v
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return (
|
|
50
|
+
<Box sx={styles.bar[size]} data-size={size}>
|
|
51
|
+
{values.map((v, i) => {
|
|
52
|
+
if (v <= 0) return null
|
|
53
|
+
const leftPct = maxValue > 0 ? (offsets[i]! / maxValue) * 100 : 0
|
|
54
|
+
const pct = maxValue > 0 ? (v / maxValue) * 100 : 0
|
|
55
|
+
return (
|
|
56
|
+
<Box
|
|
57
|
+
// Segments are keyed by per-series colour (the only stable
|
|
58
|
+
// distinguishing field on this side) plus the segment index for
|
|
59
|
+
// when callers reuse palette colours across series.
|
|
60
|
+
key={`seg-${colors[i] ?? 'none'}-${i}`}
|
|
61
|
+
sx={styles.stackedSegment[size]}
|
|
62
|
+
style={{
|
|
63
|
+
left: `${leftPct}%`,
|
|
64
|
+
width: `${pct}%`,
|
|
65
|
+
backgroundColor: colors[i] ?? '',
|
|
66
|
+
}}
|
|
67
|
+
data-bar-fill='true'
|
|
68
|
+
/>
|
|
69
|
+
)
|
|
70
|
+
})}
|
|
71
|
+
</Box>
|
|
72
|
+
)
|
|
73
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { render } from '@testing-library/react'
|
|
3
|
+
import { CategoryBar } from './category-bar'
|
|
4
|
+
|
|
5
|
+
describe('<CategoryBar>', () => {
|
|
6
|
+
function getFill(container: HTMLElement): HTMLElement | null {
|
|
7
|
+
return container.querySelector('div[style*="width:"]')
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
it('fills proportionally to value / maxValue', () => {
|
|
11
|
+
const { container } = render(
|
|
12
|
+
<CategoryBar value={25} maxValue={100} color='rgb(0, 0, 0)' />,
|
|
13
|
+
)
|
|
14
|
+
const fill = getFill(container)
|
|
15
|
+
expect(fill).toBeTruthy()
|
|
16
|
+
expect(fill!.style.width).toBe('25%')
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('renders an empty bar when maxValue is 0 (no division by zero)', () => {
|
|
20
|
+
const { container } = render(
|
|
21
|
+
<CategoryBar value={5} maxValue={0} color='rgb(0, 0, 0)' />,
|
|
22
|
+
)
|
|
23
|
+
const fill = getFill(container)
|
|
24
|
+
expect(fill!.style.width).toBe('0%')
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('honors color prop as the bar background', () => {
|
|
28
|
+
const { container } = render(
|
|
29
|
+
<CategoryBar value={50} maxValue={100} color='rgb(255, 128, 0)' />,
|
|
30
|
+
)
|
|
31
|
+
const fill = getFill(container)
|
|
32
|
+
expect(fill!.style.backgroundColor).toBe('rgb(255, 128, 0)')
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('100% fill when value equals maxValue', () => {
|
|
36
|
+
const { container } = render(
|
|
37
|
+
<CategoryBar value={100} maxValue={100} color='rgb(0, 0, 0)' />,
|
|
38
|
+
)
|
|
39
|
+
expect(getFill(container)!.style.width).toBe('100%')
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('defaults to size=small (data-size="small" on the track)', () => {
|
|
43
|
+
const { container } = render(
|
|
44
|
+
<CategoryBar value={50} maxValue={100} color='rgb(0, 0, 0)' />,
|
|
45
|
+
)
|
|
46
|
+
const track = container.querySelector('[data-size]')
|
|
47
|
+
expect(track).toBeTruthy()
|
|
48
|
+
expect(track!.getAttribute('data-size')).toBe('small')
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('renders data-size="medium" when size="medium"', () => {
|
|
52
|
+
const { container } = render(
|
|
53
|
+
<CategoryBar
|
|
54
|
+
value={50}
|
|
55
|
+
maxValue={100}
|
|
56
|
+
color='rgb(0, 0, 0)'
|
|
57
|
+
size='medium'
|
|
58
|
+
/>,
|
|
59
|
+
)
|
|
60
|
+
const track = container.querySelector('[data-size]')
|
|
61
|
+
expect(track).toBeTruthy()
|
|
62
|
+
expect(track!.getAttribute('data-size')).toBe('medium')
|
|
63
|
+
})
|
|
64
|
+
})
|