@dashforge/tw 1.1.0 → 1.1.1
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/CHANGELOG.md +21 -1
- package/dist/index.esm.js +1 -1
- package/dist/src/index.d.ts +1 -1
- package/package.json +8 -3
- package/.swcrc +0 -29
- package/A11Y.md +0 -130
- package/CONSUMER-VALIDATION.md +0 -130
- package/COVERAGE.md +0 -63
- package/PARITY.md +0 -481
- package/PERFORMANCE.md +0 -227
- package/REFINEMENT-NOTES.md +0 -50
- package/THEME-AUDIT.md +0 -174
- package/dashforge-tw-1.0.0.tgz +0 -0
- package/eslint.config.mjs +0 -62
- package/project.json +0 -15
- package/rollup.config.cjs +0 -16
- package/src/components/Accordion/Accordion.test.tsx +0 -95
- package/src/components/Accordion/Accordion.tsx +0 -97
- package/src/components/Accordion/accordion.types.ts +0 -66
- package/src/components/Accordion/accordion.variants.ts +0 -32
- package/src/components/Alert/Alert.test.tsx +0 -302
- package/src/components/Alert/Alert.tsx +0 -186
- package/src/components/Alert/alert.types.ts +0 -144
- package/src/components/Alert/alert.variants.ts +0 -71
- package/src/components/AppShell/AppShell.test.tsx +0 -223
- package/src/components/AppShell/AppShell.tsx +0 -239
- package/src/components/AppShell/appShell.types.ts +0 -49
- package/src/components/AppShell/appShell.variants.ts +0 -54
- package/src/components/AspectRatio/AspectRatio.test.tsx +0 -173
- package/src/components/AspectRatio/AspectRatio.tsx +0 -73
- package/src/components/AspectRatio/aspectRatio.types.ts +0 -54
- package/src/components/Autocomplete/Autocomplete.perf.test.tsx +0 -225
- package/src/components/Autocomplete/Autocomplete.test.tsx +0 -556
- package/src/components/Autocomplete/Autocomplete.tsx +0 -1069
- package/src/components/Autocomplete/autocomplete.types.ts +0 -198
- package/src/components/Autocomplete/autocomplete.variants.ts +0 -141
- package/src/components/Avatar/Avatar.test.tsx +0 -287
- package/src/components/Avatar/Avatar.tsx +0 -304
- package/src/components/Avatar/avatar.types.ts +0 -205
- package/src/components/Avatar/avatar.variants.ts +0 -194
- package/src/components/Badge/Badge.test.tsx +0 -385
- package/src/components/Badge/Badge.tsx +0 -174
- package/src/components/Badge/badge.types.ts +0 -154
- package/src/components/Badge/badge.variants.ts +0 -161
- package/src/components/Box/Box.test.tsx +0 -391
- package/src/components/Box/Box.tsx +0 -112
- package/src/components/Box/box.types.ts +0 -116
- package/src/components/Box/box.variants.ts +0 -230
- package/src/components/Breadcrumbs/Breadcrumbs.perf.test.tsx +0 -128
- package/src/components/Breadcrumbs/Breadcrumbs.test.tsx +0 -231
- package/src/components/Breadcrumbs/Breadcrumbs.tsx +0 -203
- package/src/components/Breadcrumbs/breadcrumbs.types.ts +0 -87
- package/src/components/Breadcrumbs/breadcrumbs.variants.ts +0 -54
- package/src/components/Button/Button.test.tsx +0 -204
- package/src/components/Button/Button.tsx +0 -151
- package/src/components/Button/button.types.ts +0 -110
- package/src/components/Button/button.variants.ts +0 -159
- package/src/components/Calendar/Calendar.test.tsx +0 -75
- package/src/components/Calendar/Calendar.tsx +0 -264
- package/src/components/Calendar/calendar.types.ts +0 -65
- package/src/components/Calendar/calendar.variants.ts +0 -64
- package/src/components/Card/Card.test.tsx +0 -291
- package/src/components/Card/Card.tsx +0 -241
- package/src/components/Card/card.types.ts +0 -132
- package/src/components/Checkbox/Checkbox.test.tsx +0 -146
- package/src/components/Checkbox/Checkbox.tsx +0 -286
- package/src/components/Checkbox/checkbox.types.ts +0 -88
- package/src/components/Checkbox/checkbox.variants.ts +0 -68
- package/src/components/Chip/Chip.test.tsx +0 -316
- package/src/components/Chip/Chip.tsx +0 -224
- package/src/components/Chip/chip.types.ts +0 -128
- package/src/components/Chip/chip.variants.ts +0 -173
- package/src/components/ConfirmDialog/ConfirmDialog.perf.test.tsx +0 -172
- package/src/components/ConfirmDialog/ConfirmDialog.test.tsx +0 -311
- package/src/components/ConfirmDialog/ConfirmDialog.tsx +0 -252
- package/src/components/ConfirmDialog/confirmDialog.types.ts +0 -70
- package/src/components/ConfirmDialog/confirmDialog.variants.ts +0 -76
- package/src/components/Container/Container.test.tsx +0 -209
- package/src/components/Container/Container.tsx +0 -74
- package/src/components/Container/container.types.ts +0 -45
- package/src/components/Container/container.variants.ts +0 -81
- package/src/components/DataGrid/DataGrid.perf.test.tsx +0 -114
- package/src/components/DataGrid/DataGrid.test.tsx +0 -1153
- package/src/components/DataGrid/DataGrid.tsx +0 -1282
- package/src/components/DataGrid/dataGrid.types.ts +0 -238
- package/src/components/DataGrid/dataGrid.variants.ts +0 -152
- package/src/components/DataGrid/filters/ColumnFilters.test.tsx +0 -330
- package/src/components/DataGrid/filters/ColumnFilters.tsx +0 -456
- package/src/components/DataGrid/reorder/useColumnReorder.test.ts +0 -130
- package/src/components/DataGrid/reorder/useColumnReorder.ts +0 -144
- package/src/components/DataGrid/resize/useColumnResize.test.ts +0 -144
- package/src/components/DataGrid/resize/useColumnResize.ts +0 -135
- package/src/components/DataGrid/visibility/ColumnVisibilityMenu.tsx +0 -186
- package/src/components/DatePicker/DatePicker.test.tsx +0 -76
- package/src/components/DatePicker/DatePicker.tsx +0 -305
- package/src/components/DatePicker/datePicker.types.ts +0 -77
- package/src/components/DatePicker/datePicker.variants.ts +0 -50
- package/src/components/DateRangePicker/DateRangePicker.test.tsx +0 -92
- package/src/components/DateRangePicker/DateRangePicker.tsx +0 -510
- package/src/components/DateRangePicker/dateRangePicker.types.ts +0 -74
- package/src/components/DateRangePicker/dateRangePicker.variants.ts +0 -101
- package/src/components/DateTimePicker/DateTimePicker.test.tsx +0 -78
- package/src/components/DateTimePicker/DateTimePicker.tsx +0 -347
- package/src/components/DateTimePicker/dateTimePicker.types.ts +0 -79
- package/src/components/DateTimePicker/dateTimePicker.variants.ts +0 -55
- package/src/components/Dialog/Dialog.test.tsx +0 -96
- package/src/components/Dialog/Dialog.tsx +0 -102
- package/src/components/Dialog/dialog.types.ts +0 -62
- package/src/components/Dialog/dialog.variants.ts +0 -66
- package/src/components/Divider/Divider.test.tsx +0 -243
- package/src/components/Divider/Divider.tsx +0 -140
- package/src/components/Divider/divider.types.ts +0 -52
- package/src/components/Divider/divider.variants.ts +0 -116
- package/src/components/Grid/Grid.test.tsx +0 -321
- package/src/components/Grid/Grid.tsx +0 -148
- package/src/components/Grid/grid.types.ts +0 -112
- package/src/components/Grid/grid.variants.ts +0 -133
- package/src/components/IconButton/IconButton.test.tsx +0 -367
- package/src/components/IconButton/IconButton.tsx +0 -159
- package/src/components/IconButton/iconButton.types.ts +0 -106
- package/src/components/IconButton/iconButton.variants.ts +0 -31
- package/src/components/LeftNav/LeftNav.perf.test.tsx +0 -172
- package/src/components/LeftNav/LeftNav.test.tsx +0 -302
- package/src/components/LeftNav/LeftNav.tsx +0 -330
- package/src/components/LeftNav/leftNav.types.ts +0 -120
- package/src/components/LeftNav/leftNav.variants.ts +0 -107
- package/src/components/Menu/Menu.test.tsx +0 -408
- package/src/components/Menu/Menu.tsx +0 -335
- package/src/components/Menu/menu.types.ts +0 -221
- package/src/components/Menu/menu.variants.ts +0 -132
- package/src/components/NumberField/NumberField.test.tsx +0 -83
- package/src/components/NumberField/NumberField.tsx +0 -321
- package/src/components/NumberField/numberField.types.ts +0 -72
- package/src/components/NumberField/numberField.variants.ts +0 -94
- package/src/components/OTPField/OTPField.test.tsx +0 -121
- package/src/components/OTPField/OTPField.tsx +0 -250
- package/src/components/OTPField/otpField.types.ts +0 -58
- package/src/components/OTPField/otpField.variants.ts +0 -72
- package/src/components/Pagination/Pagination.test.tsx +0 -314
- package/src/components/Pagination/Pagination.tsx +0 -277
- package/src/components/Pagination/pagination.helpers.ts +0 -102
- package/src/components/Pagination/pagination.types.ts +0 -117
- package/src/components/Pagination/pagination.variants.ts +0 -117
- package/src/components/Popover/Popover.test.tsx +0 -72
- package/src/components/Popover/Popover.tsx +0 -55
- package/src/components/Popover/popover.types.ts +0 -42
- package/src/components/Popover/popover.variants.ts +0 -24
- package/src/components/RadioGroup/RadioGroup.test.tsx +0 -140
- package/src/components/RadioGroup/RadioGroup.tsx +0 -283
- package/src/components/RadioGroup/radioGroup.types.ts +0 -85
- package/src/components/RadioGroup/radioGroup.variants.ts +0 -87
- package/src/components/Skeleton/Skeleton.test.tsx +0 -127
- package/src/components/Skeleton/Skeleton.tsx +0 -49
- package/src/components/Skeleton/skeleton.types.ts +0 -59
- package/src/components/Skeleton/skeleton.variants.ts +0 -87
- package/src/components/Snackbar/Snackbar.perf.test.tsx +0 -144
- package/src/components/Snackbar/Snackbar.test.tsx +0 -327
- package/src/components/Snackbar/Snackbar.tsx +0 -316
- package/src/components/Snackbar/snackbar.types.ts +0 -127
- package/src/components/Snackbar/snackbar.variants.ts +0 -81
- package/src/components/Spinner/Spinner.test.tsx +0 -199
- package/src/components/Spinner/Spinner.tsx +0 -158
- package/src/components/Spinner/spinner.types.ts +0 -113
- package/src/components/Spinner/spinner.variants.ts +0 -83
- package/src/components/Stack/Stack.test.tsx +0 -309
- package/src/components/Stack/Stack.tsx +0 -115
- package/src/components/Stack/stack.types.ts +0 -68
- package/src/components/Stack/stack.variants.ts +0 -80
- package/src/components/Switch/Switch.test.tsx +0 -131
- package/src/components/Switch/Switch.tsx +0 -158
- package/src/components/Switch/switch.types.ts +0 -36
- package/src/components/Switch/switch.variants.ts +0 -70
- package/src/components/Table/Table.perf.test.tsx +0 -81
- package/src/components/Table/Table.test.tsx +0 -674
- package/src/components/Table/Table.tsx +0 -863
- package/src/components/Table/cells/RenderButton.tsx +0 -23
- package/src/components/Table/cells/RenderChip.tsx +0 -37
- package/src/components/Table/cells/RenderText.tsx +0 -37
- package/src/components/Table/cells/RenderTwoLine.tsx +0 -34
- package/src/components/Table/cells/RowActionsMenu.tsx +0 -147
- package/src/components/Table/cells/cells.test.tsx +0 -247
- package/src/components/Table/cells/index.ts +0 -14
- package/src/components/Table/table.types.ts +0 -438
- package/src/components/Table/table.variants.ts +0 -207
- package/src/components/Tabs/Tabs.test.tsx +0 -145
- package/src/components/Tabs/Tabs.tsx +0 -77
- package/src/components/Tabs/tabs.types.ts +0 -55
- package/src/components/Tabs/tabs.variants.ts +0 -69
- package/src/components/Tabs/useTabs.ts +0 -167
- package/src/components/TextField/TextField.test.tsx +0 -198
- package/src/components/TextField/TextField.tsx +0 -245
- package/src/components/TextField/textField.types.ts +0 -90
- package/src/components/TextField/textField.variants.ts +0 -105
- package/src/components/Textarea/Textarea.test.tsx +0 -75
- package/src/components/Textarea/Textarea.tsx +0 -190
- package/src/components/Textarea/textarea.types.ts +0 -60
- package/src/components/Textarea/textarea.variants.ts +0 -85
- package/src/components/TimePicker/TimePicker.test.tsx +0 -65
- package/src/components/TimePicker/TimePicker.tsx +0 -274
- package/src/components/TimePicker/timePicker.types.ts +0 -69
- package/src/components/TimePicker/timePicker.variants.ts +0 -53
- package/src/components/Tooltip/Tooltip.test.tsx +0 -73
- package/src/components/Tooltip/Tooltip.tsx +0 -70
- package/src/components/Tooltip/tooltip.types.ts +0 -44
- package/src/components/Tooltip/tooltip.variants.ts +0 -28
- package/src/components/TopBar/TopBar.test.tsx +0 -129
- package/src/components/TopBar/TopBar.tsx +0 -66
- package/src/components/TopBar/topBar.types.ts +0 -42
- package/src/components/TopBar/topBar.variants.ts +0 -38
- package/src/components/Typography/Typography.test.tsx +0 -247
- package/src/components/Typography/Typography.tsx +0 -112
- package/src/components/Typography/typography.types.ts +0 -65
- package/src/components/Typography/typography.variants.ts +0 -130
- package/src/components/VisuallyHidden/VisuallyHidden.test.tsx +0 -86
- package/src/components/VisuallyHidden/VisuallyHidden.tsx +0 -59
- package/src/components/VisuallyHidden/visuallyHidden.types.ts +0 -45
- package/src/components/_shared/data/getNestedValue.test.ts +0 -50
- package/src/components/_shared/data/getNestedValue.ts +0 -32
- package/src/components/_shared/data/useColumnAutoDetect.test.ts +0 -123
- package/src/components/_shared/data/useColumnAutoDetect.ts +0 -100
- package/src/components/_shared/data/useControllableState.ts +0 -48
- package/src/components/_shared/data/useDebouncedValue.ts +0 -24
- package/src/components/_shared/data/useTableFilter.test.ts +0 -166
- package/src/components/_shared/data/useTableFilter.ts +0 -118
- package/src/components/_shared/data/useTableSearch.test.ts +0 -115
- package/src/components/_shared/data/useTableSearch.ts +0 -66
- package/src/components/_shared/data/useTableSelection.test.ts +0 -100
- package/src/components/_shared/data/useTableSelection.ts +0 -100
- package/src/components/_shared/data/useTableSort.test.ts +0 -150
- package/src/components/_shared/data/useTableSort.ts +0 -141
- package/src/components/_shared/data/useVirtualizer.test.ts +0 -158
- package/src/components/_shared/data/useVirtualizer.ts +0 -199
- package/src/components/_shared/resolveValidationState.ts +0 -60
- package/src/components/_shared/severity/index.ts +0 -33
- package/src/components/_shared/severity/severity.types.ts +0 -50
- package/src/components/_shared/severity/severityIcons.tsx +0 -104
- package/src/components/_shared/severity/severityVariants.test.ts +0 -136
- package/src/components/_shared/severity/severityVariants.ts +0 -115
- package/src/components/_shared/themeIdentity.test.ts +0 -113
- package/src/hooks/useAccessState.spec.tsx +0 -161
- package/src/hooks/useAccessState.ts +0 -81
- package/src/index.ts +0 -550
- package/src/utils/cn.spec.ts +0 -16
- package/src/utils/cn.ts +0 -29
- package/test-output/vitest/coverage/base.css +0 -224
- package/test-output/vitest/coverage/block-navigation.js +0 -87
- package/test-output/vitest/coverage/clover.xml +0 -2418
- package/test-output/vitest/coverage/coverage-final.json +0 -89
- package/test-output/vitest/coverage/favicon.png +0 -0
- package/test-output/vitest/coverage/index.html +0 -746
- package/test-output/vitest/coverage/prettify.css +0 -1
- package/test-output/vitest/coverage/prettify.js +0 -2
- package/test-output/vitest/coverage/sort-arrow-sprite.png +0 -0
- package/test-output/vitest/coverage/sorter.js +0 -210
- package/test-output/vitest/coverage/src/components/Accordion/Accordion.tsx.html +0 -376
- package/test-output/vitest/coverage/src/components/Accordion/accordion.variants.ts.html +0 -181
- package/test-output/vitest/coverage/src/components/Accordion/index.html +0 -131
- package/test-output/vitest/coverage/src/components/AppShell/AppShell.tsx.html +0 -802
- package/test-output/vitest/coverage/src/components/AppShell/appShell.variants.ts.html +0 -247
- package/test-output/vitest/coverage/src/components/AppShell/index.html +0 -131
- package/test-output/vitest/coverage/src/components/AspectRatio/AspectRatio.tsx.html +0 -304
- package/test-output/vitest/coverage/src/components/AspectRatio/index.html +0 -116
- package/test-output/vitest/coverage/src/components/Autocomplete/Autocomplete.tsx.html +0 -3292
- package/test-output/vitest/coverage/src/components/Autocomplete/autocomplete.variants.ts.html +0 -508
- package/test-output/vitest/coverage/src/components/Autocomplete/index.html +0 -131
- package/test-output/vitest/coverage/src/components/Box/Box.tsx.html +0 -334
- package/test-output/vitest/coverage/src/components/Box/box.variants.ts.html +0 -775
- package/test-output/vitest/coverage/src/components/Box/index.html +0 -131
- package/test-output/vitest/coverage/src/components/Breadcrumbs/Breadcrumbs.tsx.html +0 -694
- package/test-output/vitest/coverage/src/components/Breadcrumbs/breadcrumbs.variants.ts.html +0 -247
- package/test-output/vitest/coverage/src/components/Breadcrumbs/index.html +0 -131
- package/test-output/vitest/coverage/src/components/Button/Button.tsx.html +0 -535
- package/test-output/vitest/coverage/src/components/Button/button.variants.ts.html +0 -562
- package/test-output/vitest/coverage/src/components/Button/index.html +0 -131
- package/test-output/vitest/coverage/src/components/Checkbox/Checkbox.tsx.html +0 -943
- package/test-output/vitest/coverage/src/components/Checkbox/checkbox.variants.ts.html +0 -289
- package/test-output/vitest/coverage/src/components/Checkbox/index.html +0 -131
- package/test-output/vitest/coverage/src/components/ConfirmDialog/ConfirmDialog.tsx.html +0 -841
- package/test-output/vitest/coverage/src/components/ConfirmDialog/confirmDialog.variants.ts.html +0 -313
- package/test-output/vitest/coverage/src/components/ConfirmDialog/index.html +0 -131
- package/test-output/vitest/coverage/src/components/Container/Container.tsx.html +0 -307
- package/test-output/vitest/coverage/src/components/Container/container.variants.ts.html +0 -328
- package/test-output/vitest/coverage/src/components/Container/index.html +0 -131
- package/test-output/vitest/coverage/src/components/DataGrid/DataGrid.tsx.html +0 -3937
- package/test-output/vitest/coverage/src/components/DataGrid/dataGrid.variants.ts.html +0 -541
- package/test-output/vitest/coverage/src/components/DataGrid/filters/ColumnFilters.tsx.html +0 -1453
- package/test-output/vitest/coverage/src/components/DataGrid/filters/index.html +0 -116
- package/test-output/vitest/coverage/src/components/DataGrid/index.html +0 -131
- package/test-output/vitest/coverage/src/components/DataGrid/reorder/index.html +0 -116
- package/test-output/vitest/coverage/src/components/DataGrid/reorder/useColumnReorder.ts.html +0 -517
- package/test-output/vitest/coverage/src/components/DataGrid/resize/index.html +0 -116
- package/test-output/vitest/coverage/src/components/DataGrid/resize/useColumnResize.ts.html +0 -490
- package/test-output/vitest/coverage/src/components/DataGrid/visibility/ColumnVisibilityMenu.tsx.html +0 -592
- package/test-output/vitest/coverage/src/components/DataGrid/visibility/index.html +0 -116
- package/test-output/vitest/coverage/src/components/DateTimePicker/DateTimePicker.tsx.html +0 -1042
- package/test-output/vitest/coverage/src/components/DateTimePicker/dateTimePicker.variants.ts.html +0 -337
- package/test-output/vitest/coverage/src/components/DateTimePicker/index.html +0 -131
- package/test-output/vitest/coverage/src/components/Dialog/Dialog.tsx.html +0 -391
- package/test-output/vitest/coverage/src/components/Dialog/dialog.variants.ts.html +0 -283
- package/test-output/vitest/coverage/src/components/Dialog/index.html +0 -131
- package/test-output/vitest/coverage/src/components/Divider/Divider.tsx.html +0 -505
- package/test-output/vitest/coverage/src/components/Divider/divider.variants.ts.html +0 -433
- package/test-output/vitest/coverage/src/components/Divider/index.html +0 -131
- package/test-output/vitest/coverage/src/components/Grid/Grid.tsx.html +0 -529
- package/test-output/vitest/coverage/src/components/Grid/grid.variants.ts.html +0 -484
- package/test-output/vitest/coverage/src/components/Grid/index.html +0 -131
- package/test-output/vitest/coverage/src/components/LeftNav/LeftNav.tsx.html +0 -1075
- package/test-output/vitest/coverage/src/components/LeftNav/index.html +0 -131
- package/test-output/vitest/coverage/src/components/LeftNav/leftNav.variants.ts.html +0 -406
- package/test-output/vitest/coverage/src/components/NumberField/NumberField.tsx.html +0 -1048
- package/test-output/vitest/coverage/src/components/NumberField/index.html +0 -131
- package/test-output/vitest/coverage/src/components/NumberField/numberField.variants.ts.html +0 -367
- package/test-output/vitest/coverage/src/components/OTPField/OTPField.tsx.html +0 -835
- package/test-output/vitest/coverage/src/components/OTPField/index.html +0 -131
- package/test-output/vitest/coverage/src/components/OTPField/otpField.variants.ts.html +0 -301
- package/test-output/vitest/coverage/src/components/Pagination/Pagination.tsx.html +0 -916
- package/test-output/vitest/coverage/src/components/Pagination/index.html +0 -146
- package/test-output/vitest/coverage/src/components/Pagination/pagination.helpers.ts.html +0 -391
- package/test-output/vitest/coverage/src/components/Pagination/pagination.variants.ts.html +0 -436
- package/test-output/vitest/coverage/src/components/Popover/Popover.tsx.html +0 -250
- package/test-output/vitest/coverage/src/components/Popover/index.html +0 -131
- package/test-output/vitest/coverage/src/components/Popover/popover.variants.ts.html +0 -157
- package/test-output/vitest/coverage/src/components/RadioGroup/RadioGroup.tsx.html +0 -934
- package/test-output/vitest/coverage/src/components/RadioGroup/index.html +0 -131
- package/test-output/vitest/coverage/src/components/RadioGroup/radioGroup.variants.ts.html +0 -346
- package/test-output/vitest/coverage/src/components/Skeleton/Skeleton.tsx.html +0 -232
- package/test-output/vitest/coverage/src/components/Skeleton/index.html +0 -131
- package/test-output/vitest/coverage/src/components/Skeleton/skeleton.variants.ts.html +0 -346
- package/test-output/vitest/coverage/src/components/Snackbar/Snackbar.tsx.html +0 -979
- package/test-output/vitest/coverage/src/components/Snackbar/index.html +0 -131
- package/test-output/vitest/coverage/src/components/Snackbar/snackbar.variants.ts.html +0 -307
- package/test-output/vitest/coverage/src/components/Stack/Stack.tsx.html +0 -430
- package/test-output/vitest/coverage/src/components/Stack/index.html +0 -131
- package/test-output/vitest/coverage/src/components/Stack/stack.variants.ts.html +0 -325
- package/test-output/vitest/coverage/src/components/Switch/Switch.tsx.html +0 -559
- package/test-output/vitest/coverage/src/components/Switch/index.html +0 -131
- package/test-output/vitest/coverage/src/components/Switch/switch.variants.ts.html +0 -295
- package/test-output/vitest/coverage/src/components/Table/Table.tsx.html +0 -2677
- package/test-output/vitest/coverage/src/components/Table/cells/RenderButton.tsx.html +0 -154
- package/test-output/vitest/coverage/src/components/Table/cells/RenderChip.tsx.html +0 -385
- package/test-output/vitest/coverage/src/components/Table/cells/RenderText.tsx.html +0 -196
- package/test-output/vitest/coverage/src/components/Table/cells/RenderTwoLine.tsx.html +0 -187
- package/test-output/vitest/coverage/src/components/Table/cells/RowActionsMenu.tsx.html +0 -526
- package/test-output/vitest/coverage/src/components/Table/cells/index.html +0 -191
- package/test-output/vitest/coverage/src/components/Table/cells/index.ts.html +0 -127
- package/test-output/vitest/coverage/src/components/Table/index.html +0 -131
- package/test-output/vitest/coverage/src/components/Table/table.variants.ts.html +0 -706
- package/test-output/vitest/coverage/src/components/Tabs/Tabs.tsx.html +0 -283
- package/test-output/vitest/coverage/src/components/Tabs/index.html +0 -131
- package/test-output/vitest/coverage/src/components/Tabs/tabs.variants.ts.html +0 -292
- package/test-output/vitest/coverage/src/components/TextField/TextField.tsx.html +0 -820
- package/test-output/vitest/coverage/src/components/TextField/index.html +0 -131
- package/test-output/vitest/coverage/src/components/TextField/textField.variants.ts.html +0 -400
- package/test-output/vitest/coverage/src/components/Textarea/Textarea.tsx.html +0 -655
- package/test-output/vitest/coverage/src/components/Textarea/index.html +0 -131
- package/test-output/vitest/coverage/src/components/Textarea/textarea.variants.ts.html +0 -340
- package/test-output/vitest/coverage/src/components/Tooltip/Tooltip.tsx.html +0 -295
- package/test-output/vitest/coverage/src/components/Tooltip/index.html +0 -131
- package/test-output/vitest/coverage/src/components/Tooltip/tooltip.variants.ts.html +0 -169
- package/test-output/vitest/coverage/src/components/TopBar/TopBar.tsx.html +0 -283
- package/test-output/vitest/coverage/src/components/TopBar/index.html +0 -131
- package/test-output/vitest/coverage/src/components/TopBar/topBar.variants.ts.html +0 -199
- package/test-output/vitest/coverage/src/components/Typography/Typography.tsx.html +0 -421
- package/test-output/vitest/coverage/src/components/Typography/index.html +0 -131
- package/test-output/vitest/coverage/src/components/Typography/typography.variants.ts.html +0 -436
- package/test-output/vitest/coverage/src/components/VisuallyHidden/VisuallyHidden.tsx.html +0 -262
- package/test-output/vitest/coverage/src/components/VisuallyHidden/index.html +0 -116
- package/test-output/vitest/coverage/src/components/_shared/data/getNestedValue.ts.html +0 -181
- package/test-output/vitest/coverage/src/components/_shared/data/index.html +0 -236
- package/test-output/vitest/coverage/src/components/_shared/data/useColumnAutoDetect.ts.html +0 -385
- package/test-output/vitest/coverage/src/components/_shared/data/useControllableState.ts.html +0 -229
- package/test-output/vitest/coverage/src/components/_shared/data/useDebouncedValue.ts.html +0 -157
- package/test-output/vitest/coverage/src/components/_shared/data/useTableFilter.ts.html +0 -439
- package/test-output/vitest/coverage/src/components/_shared/data/useTableSearch.ts.html +0 -283
- package/test-output/vitest/coverage/src/components/_shared/data/useTableSelection.ts.html +0 -385
- package/test-output/vitest/coverage/src/components/_shared/data/useTableSort.ts.html +0 -508
- package/test-output/vitest/coverage/src/components/_shared/data/useVirtualizer.ts.html +0 -682
- package/test-output/vitest/coverage/src/components/_shared/index.html +0 -116
- package/test-output/vitest/coverage/src/components/_shared/resolveValidationState.ts.html +0 -265
- package/test-output/vitest/coverage/src/hooks/index.html +0 -116
- package/test-output/vitest/coverage/src/hooks/useAccessState.ts.html +0 -328
- package/test-output/vitest/coverage/src/index.html +0 -116
- package/test-output/vitest/coverage/src/index.ts.html +0 -1153
- package/test-output/vitest/coverage/src/utils/cn.ts.html +0 -172
- package/test-output/vitest/coverage/src/utils/index.html +0 -116
- package/tsconfig.json +0 -13
- package/tsconfig.lib.json +0 -58
- package/tsconfig.spec.json +0 -34
- package/vite.config.ts +0 -33
- package/vitest.config.mts +0 -29
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import { useMemo } from 'react';
|
|
2
|
-
import { getNestedValue } from './getNestedValue.js';
|
|
3
|
-
import type { TableColumn, TableColumnInferredType } from '../../Table/table.types.js';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Auto-detects the primitive type of each column by scanning the
|
|
7
|
-
* first non-null value across the supplied rows. Drives smart
|
|
8
|
-
* defaults for `align` and `monospace` on the cell render path.
|
|
9
|
-
*
|
|
10
|
-
* Scans only the first N rows (default 50) for performance — if the
|
|
11
|
-
* dataset's first 50 rows are unrepresentative, the consumer can
|
|
12
|
-
* override per-column via explicit `align` / `monospace` props.
|
|
13
|
-
*
|
|
14
|
-
* Returns `Map<columnField, inferredType>`.
|
|
15
|
-
*/
|
|
16
|
-
export function useColumnAutoDetect<T extends object>(
|
|
17
|
-
rows: T[],
|
|
18
|
-
cols: TableColumn<T>[],
|
|
19
|
-
sampleSize = 50,
|
|
20
|
-
): Map<string, TableColumnInferredType> {
|
|
21
|
-
return useMemo(() => {
|
|
22
|
-
const result = new Map<string, TableColumnInferredType>();
|
|
23
|
-
const sample = rows.slice(0, sampleSize);
|
|
24
|
-
for (const col of cols) {
|
|
25
|
-
const field = col.field as string;
|
|
26
|
-
let inferred: TableColumnInferredType = 'unknown';
|
|
27
|
-
for (const row of sample) {
|
|
28
|
-
const value = getNestedValue(row, field);
|
|
29
|
-
if (value == null) continue;
|
|
30
|
-
inferred = inferType(value);
|
|
31
|
-
break; // first non-null wins
|
|
32
|
-
}
|
|
33
|
-
result.set(field, inferred);
|
|
34
|
-
}
|
|
35
|
-
return result;
|
|
36
|
-
}, [rows, cols, sampleSize]);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function inferType(value: unknown): TableColumnInferredType {
|
|
40
|
-
if (typeof value === 'number') return 'number';
|
|
41
|
-
if (typeof value === 'boolean') return 'boolean';
|
|
42
|
-
if (value instanceof Date) return 'date';
|
|
43
|
-
if (typeof value === 'string') {
|
|
44
|
-
// Heuristic: ISO date string (YYYY-MM-DD[T...]) → 'date'
|
|
45
|
-
if (/^\d{4}-\d{2}-\d{2}/.test(value)) return 'date';
|
|
46
|
-
return 'string';
|
|
47
|
-
}
|
|
48
|
-
return 'unknown';
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Resolves the effective `align` for a column, honoring explicit
|
|
53
|
-
* `col.align` first and falling back to the inferred type.
|
|
54
|
-
*/
|
|
55
|
-
export function resolveAlign(
|
|
56
|
-
col: { align?: 'left' | 'right' | 'center' },
|
|
57
|
-
inferred: TableColumnInferredType | undefined,
|
|
58
|
-
): 'left' | 'right' | 'center' {
|
|
59
|
-
if (col.align) return col.align;
|
|
60
|
-
switch (inferred) {
|
|
61
|
-
case 'number':
|
|
62
|
-
return 'right';
|
|
63
|
-
case 'boolean':
|
|
64
|
-
return 'center';
|
|
65
|
-
default:
|
|
66
|
-
return 'left';
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Resolves the effective `tabularNums` flag for a column.
|
|
72
|
-
*
|
|
73
|
-
* `tabular-nums` is a `font-variant-numeric` rule that makes every
|
|
74
|
-
* digit occupy the same width — it does NOT change the font family.
|
|
75
|
-
* Auto-applied for `number` and `date` columns so currency values
|
|
76
|
-
* and ISO dates align cleanly. Explicit `col.tabularNums` wins.
|
|
77
|
-
*/
|
|
78
|
-
export function resolveTabularNums(
|
|
79
|
-
col: { tabularNums?: boolean },
|
|
80
|
-
inferred: TableColumnInferredType | undefined,
|
|
81
|
-
): boolean {
|
|
82
|
-
if (col.tabularNums !== undefined) return col.tabularNums;
|
|
83
|
-
return inferred === 'number' || inferred === 'date';
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Resolves the effective `monospace` flag for a column.
|
|
88
|
-
*
|
|
89
|
-
* `font-mono` changes the font FAMILY to a monospace stack, which
|
|
90
|
-
* would override the consumer's theme `font-sans`. The library
|
|
91
|
-
* never auto-applies it — only an explicit `col.monospace: true`
|
|
92
|
-
* opts in. Consumers wanting a custom mono stack configure it
|
|
93
|
-
* through Tailwind's `theme.extend.fontFamily.mono` in their own
|
|
94
|
-
* preset (the dashforgePreset does not own the `fontFamily` axis).
|
|
95
|
-
*/
|
|
96
|
-
export function resolveMonospace(
|
|
97
|
-
col: { monospace?: boolean },
|
|
98
|
-
): boolean {
|
|
99
|
-
return col.monospace === true;
|
|
100
|
-
}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { useCallback, useRef, useState } from 'react';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Generic "controllable" state hook — switches between controlled
|
|
5
|
-
* (via parent-supplied value + setter) and uncontrolled (internal
|
|
6
|
-
* `useState`).
|
|
7
|
-
*
|
|
8
|
-
* - If `controlledValue !== undefined`, the hook is in **controlled**
|
|
9
|
-
* mode: the internal state is ignored; the returned setter calls
|
|
10
|
-
* `onControlledChange` only.
|
|
11
|
-
* - Otherwise it's in **uncontrolled** mode: internal `useState`,
|
|
12
|
-
* defaulting to `defaultValue`. The optional `onControlledChange`
|
|
13
|
-
* is still fired on every change so the parent can react.
|
|
14
|
-
*
|
|
15
|
-
* The control mode is determined per-render. Mixing modes (passing
|
|
16
|
-
* `controlledValue` undefined sometimes) is a consumer bug — we don't
|
|
17
|
-
* try to "remember" the last mode (React's rules-of-hooks make that
|
|
18
|
-
* unsafe).
|
|
19
|
-
*/
|
|
20
|
-
export function useControllableState<T>(args: {
|
|
21
|
-
controlledValue: T | undefined;
|
|
22
|
-
defaultValue: T;
|
|
23
|
-
onChange?: (value: T) => void;
|
|
24
|
-
}): [T, (next: T | ((prev: T) => T)) => void] {
|
|
25
|
-
const { controlledValue, defaultValue, onChange } = args;
|
|
26
|
-
const [uncontrolledValue, setUncontrolledValue] = useState<T>(defaultValue);
|
|
27
|
-
const isControlled = controlledValue !== undefined;
|
|
28
|
-
const value = isControlled ? (controlledValue as T) : uncontrolledValue;
|
|
29
|
-
|
|
30
|
-
// Stable ref to `onChange` so the setter doesn't change identity
|
|
31
|
-
// every render (consumer can pass an inline lambda safely).
|
|
32
|
-
const onChangeRef = useRef(onChange);
|
|
33
|
-
onChangeRef.current = onChange;
|
|
34
|
-
|
|
35
|
-
const setValue = useCallback(
|
|
36
|
-
(next: T | ((prev: T) => T)) => {
|
|
37
|
-
const resolved =
|
|
38
|
-
typeof next === 'function' ? (next as (prev: T) => T)(value) : next;
|
|
39
|
-
if (!isControlled) {
|
|
40
|
-
setUncontrolledValue(resolved);
|
|
41
|
-
}
|
|
42
|
-
onChangeRef.current?.(resolved);
|
|
43
|
-
},
|
|
44
|
-
[isControlled, value],
|
|
45
|
-
);
|
|
46
|
-
|
|
47
|
-
return [value, setValue];
|
|
48
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { useEffect, useState } from 'react';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Returns a debounced version of `value` — only updates after `delayMs`
|
|
5
|
-
* of no further changes. Used by the search input so we don't re-filter
|
|
6
|
-
* the entire row set on every keystroke.
|
|
7
|
-
*
|
|
8
|
-
* `delayMs <= 0` short-circuits to the input value (no debounce —
|
|
9
|
-
* useful for tests).
|
|
10
|
-
*/
|
|
11
|
-
export function useDebouncedValue<T>(value: T, delayMs: number): T {
|
|
12
|
-
const [debounced, setDebounced] = useState<T>(value);
|
|
13
|
-
|
|
14
|
-
useEffect(() => {
|
|
15
|
-
if (delayMs <= 0) {
|
|
16
|
-
setDebounced(value);
|
|
17
|
-
return;
|
|
18
|
-
}
|
|
19
|
-
const timer = setTimeout(() => setDebounced(value), delayMs);
|
|
20
|
-
return () => clearTimeout(timer);
|
|
21
|
-
}, [value, delayMs]);
|
|
22
|
-
|
|
23
|
-
return debounced;
|
|
24
|
-
}
|
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
// @vitest-environment node
|
|
2
|
-
import { describe, it, expect } from 'vitest';
|
|
3
|
-
import { passesFilter, toNumber, toDateMs } from './useTableFilter.js';
|
|
4
|
-
|
|
5
|
-
describe('useTableFilter — toNumber', () => {
|
|
6
|
-
it('returns null for null and undefined', () => {
|
|
7
|
-
expect(toNumber(null)).toBeNull();
|
|
8
|
-
expect(toNumber(undefined)).toBeNull();
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
it('returns the value for finite numbers', () => {
|
|
12
|
-
expect(toNumber(42)).toBe(42);
|
|
13
|
-
expect(toNumber(0)).toBe(0);
|
|
14
|
-
expect(toNumber(-3.14)).toBe(-3.14);
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it('returns null for non-finite numbers', () => {
|
|
18
|
-
expect(toNumber(Number.NaN)).toBeNull();
|
|
19
|
-
expect(toNumber(Number.POSITIVE_INFINITY)).toBeNull();
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it('coerces numeric strings', () => {
|
|
23
|
-
expect(toNumber('42')).toBe(42);
|
|
24
|
-
expect(toNumber('0.5')).toBe(0.5);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it('returns null for non-numeric strings', () => {
|
|
28
|
-
expect(toNumber('abc')).toBeNull();
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
// JS quirk: Number('') === 0 (finite). Empty-string is treated as 0
|
|
32
|
-
// by toNumber. Callers that need open-range semantics (e.g. the
|
|
33
|
-
// NumberRangeFilter UI) explicitly null-out empty inputs before
|
|
34
|
-
// passing them through.
|
|
35
|
-
it('returns 0 for empty string (JS Number("") === 0)', () => {
|
|
36
|
-
expect(toNumber('')).toBe(0);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it('coerces bigint to number', () => {
|
|
40
|
-
expect(toNumber(10n)).toBe(10);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it('returns null for objects/arrays/booleans', () => {
|
|
44
|
-
expect(toNumber({})).toBeNull();
|
|
45
|
-
expect(toNumber([])).toBeNull();
|
|
46
|
-
expect(toNumber(true)).toBeNull();
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
describe('useTableFilter — toDateMs', () => {
|
|
51
|
-
it('returns null for null and undefined', () => {
|
|
52
|
-
expect(toDateMs(null)).toBeNull();
|
|
53
|
-
expect(toDateMs(undefined)).toBeNull();
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it('returns epoch ms for Date instances', () => {
|
|
57
|
-
const d = new Date('2026-05-19T00:00:00Z');
|
|
58
|
-
expect(toDateMs(d)).toBe(d.getTime());
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it('parses ISO strings', () => {
|
|
62
|
-
const ms = toDateMs('2026-05-19');
|
|
63
|
-
expect(ms).not.toBeNull();
|
|
64
|
-
expect(new Date(ms!).toISOString()).toContain('2026-05-19');
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it('returns null for invalid date strings', () => {
|
|
68
|
-
expect(toDateMs('not-a-date')).toBeNull();
|
|
69
|
-
});
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
describe('useTableFilter — passesFilter (contains)', () => {
|
|
73
|
-
it('returns true for empty needle (no-op filter)', () => {
|
|
74
|
-
expect(passesFilter('Hello', 'contains', '')).toBe(true);
|
|
75
|
-
expect(passesFilter('Hello', 'contains', ' ')).toBe(true);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it('matches substring case-insensitively', () => {
|
|
79
|
-
expect(passesFilter('Hello World', 'contains', 'hello')).toBe(true);
|
|
80
|
-
expect(passesFilter('Hello World', 'contains', 'WORLD')).toBe(true);
|
|
81
|
-
expect(passesFilter('Hello World', 'contains', 'xyz')).toBe(false);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it('stringifies non-string row values for searching', () => {
|
|
85
|
-
expect(passesFilter(42, 'contains', '42')).toBe(true);
|
|
86
|
-
expect(passesFilter(true, 'contains', 'true')).toBe(true);
|
|
87
|
-
});
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
describe('useTableFilter — passesFilter (equals)', () => {
|
|
91
|
-
it('returns true when filter value is null (no-op)', () => {
|
|
92
|
-
expect(passesFilter('anything', 'equals', null)).toBe(true);
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it('does strict equality for booleans', () => {
|
|
96
|
-
expect(passesFilter(true, 'equals', true)).toBe(true);
|
|
97
|
-
expect(passesFilter(false, 'equals', false)).toBe(true);
|
|
98
|
-
expect(passesFilter(true, 'equals', false)).toBe(false);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it('does strict equality for primitives', () => {
|
|
102
|
-
expect(passesFilter('a', 'equals', 'a')).toBe(true);
|
|
103
|
-
expect(passesFilter('a', 'equals', 'b')).toBe(false);
|
|
104
|
-
expect(passesFilter(1, 'equals', 1)).toBe(true);
|
|
105
|
-
});
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
describe('useTableFilter — passesFilter (between, number range)', () => {
|
|
109
|
-
it('returns true when range is fully open', () => {
|
|
110
|
-
expect(passesFilter(42, 'between', [null, null])).toBe(true);
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it('respects min bound', () => {
|
|
114
|
-
expect(passesFilter(5, 'between', [10, null])).toBe(false);
|
|
115
|
-
expect(passesFilter(10, 'between', [10, null])).toBe(true);
|
|
116
|
-
expect(passesFilter(15, 'between', [10, null])).toBe(true);
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
it('respects max bound', () => {
|
|
120
|
-
expect(passesFilter(5, 'between', [null, 10])).toBe(true);
|
|
121
|
-
expect(passesFilter(10, 'between', [null, 10])).toBe(true);
|
|
122
|
-
expect(passesFilter(15, 'between', [null, 10])).toBe(false);
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
it('respects both bounds inclusively', () => {
|
|
126
|
-
expect(passesFilter(7, 'between', [5, 10])).toBe(true);
|
|
127
|
-
expect(passesFilter(5, 'between', [5, 10])).toBe(true);
|
|
128
|
-
expect(passesFilter(10, 'between', [5, 10])).toBe(true);
|
|
129
|
-
expect(passesFilter(4, 'between', [5, 10])).toBe(false);
|
|
130
|
-
expect(passesFilter(11, 'between', [5, 10])).toBe(false);
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it('coerces string row values to number', () => {
|
|
134
|
-
expect(passesFilter('7', 'between', [5, 10])).toBe(true);
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
it('returns true for malformed filter value (defensive)', () => {
|
|
138
|
-
expect(passesFilter(42, 'between', 'not a tuple')).toBe(true);
|
|
139
|
-
expect(passesFilter(42, 'between', [1])).toBe(true);
|
|
140
|
-
});
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
describe('useTableFilter — passesFilter (between, date range)', () => {
|
|
144
|
-
it('respects ISO date range bounds', () => {
|
|
145
|
-
const row = '2026-05-19';
|
|
146
|
-
expect(passesFilter(row, 'between', ['2026-01-01', '2026-12-31'])).toBe(true);
|
|
147
|
-
expect(passesFilter(row, 'between', ['2026-06-01', '2026-12-31'])).toBe(false);
|
|
148
|
-
expect(passesFilter(row, 'between', ['2026-01-01', '2026-05-01'])).toBe(false);
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
it('accepts Date instances as row values', () => {
|
|
152
|
-
const row = new Date('2026-05-19T00:00:00Z');
|
|
153
|
-
expect(passesFilter(row, 'between', ['2026-01-01', '2026-12-31'])).toBe(true);
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
it('treats null bounds as open ended', () => {
|
|
157
|
-
expect(passesFilter('2026-05-19', 'between', [null, '2026-12-31'])).toBe(true);
|
|
158
|
-
expect(passesFilter('2026-05-19', 'between', ['2026-01-01', null])).toBe(true);
|
|
159
|
-
});
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
describe('useTableFilter — passesFilter (unknown operator)', () => {
|
|
163
|
-
it('returns true for unrecognized operator (defensive no-op)', () => {
|
|
164
|
-
expect(passesFilter('anything', 'unknownOp', 'anything')).toBe(true);
|
|
165
|
-
});
|
|
166
|
-
});
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
import { useMemo } from 'react';
|
|
2
|
-
import { getNestedValue } from './getNestedValue.js';
|
|
3
|
-
import { stringifyForSearch } from './useTableSearch.js';
|
|
4
|
-
import type { TableFilterModel } from '../../Table/table.types.js';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Coerce a value to a number for range comparison. Returns
|
|
8
|
-
* `null` if not coercible.
|
|
9
|
-
*/
|
|
10
|
-
function toNumber(value: unknown): number | null {
|
|
11
|
-
if (value == null) return null;
|
|
12
|
-
if (typeof value === 'number') return Number.isFinite(value) ? value : null;
|
|
13
|
-
if (typeof value === 'bigint') return Number(value);
|
|
14
|
-
if (typeof value === 'string') {
|
|
15
|
-
const n = Number(value);
|
|
16
|
-
return Number.isFinite(n) ? n : null;
|
|
17
|
-
}
|
|
18
|
-
return null;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Coerce a value to a Date's epoch ms for date-range comparison.
|
|
23
|
-
* Accepts `Date` instances + ISO 8601 strings. Returns `null` if
|
|
24
|
-
* not coercible.
|
|
25
|
-
*/
|
|
26
|
-
function toDateMs(value: unknown): number | null {
|
|
27
|
-
if (value == null) return null;
|
|
28
|
-
if (value instanceof Date) return value.getTime();
|
|
29
|
-
if (typeof value === 'string') {
|
|
30
|
-
const d = new Date(value);
|
|
31
|
-
const t = d.getTime();
|
|
32
|
-
return Number.isNaN(t) ? null : t;
|
|
33
|
-
}
|
|
34
|
-
return null;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Test one row × one filter. Returns `true` if the row passes the
|
|
39
|
-
* filter. The operator decides the predicate:
|
|
40
|
-
*
|
|
41
|
-
* - `contains` → substring match on stringified value (text filter)
|
|
42
|
-
* - `equals` → strict equality (boolean filter mostly)
|
|
43
|
-
* - `between` → `value: [min, max]` tuple. Each end can be `null`
|
|
44
|
-
* for an open range. Used by number range + date range filters.
|
|
45
|
-
*
|
|
46
|
-
* Empty filter is a no-op (passes every row).
|
|
47
|
-
*/
|
|
48
|
-
function passesFilter(rowValue: unknown, op: string, filterValue: unknown): boolean {
|
|
49
|
-
switch (op) {
|
|
50
|
-
case 'contains': {
|
|
51
|
-
const needle =
|
|
52
|
-
typeof filterValue === 'string' ? filterValue.trim().toLowerCase() : '';
|
|
53
|
-
if (!needle) return true;
|
|
54
|
-
return stringifyForSearch(rowValue).toLowerCase().includes(needle);
|
|
55
|
-
}
|
|
56
|
-
case 'equals': {
|
|
57
|
-
if (filterValue == null) return true;
|
|
58
|
-
return rowValue === filterValue;
|
|
59
|
-
}
|
|
60
|
-
case 'between': {
|
|
61
|
-
if (!Array.isArray(filterValue) || filterValue.length !== 2) return true;
|
|
62
|
-
const [min, max] = filterValue as [unknown, unknown];
|
|
63
|
-
if (min == null && max == null) return true;
|
|
64
|
-
|
|
65
|
-
// Try number range first.
|
|
66
|
-
const rowNum = toNumber(rowValue);
|
|
67
|
-
const minNum = min == null ? null : toNumber(min);
|
|
68
|
-
const maxNum = max == null ? null : toNumber(max);
|
|
69
|
-
if (rowNum != null && (minNum != null || maxNum != null)) {
|
|
70
|
-
if (minNum != null && rowNum < minNum) return false;
|
|
71
|
-
if (maxNum != null && rowNum > maxNum) return false;
|
|
72
|
-
return true;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Fall through to date range.
|
|
76
|
-
const rowMs = toDateMs(rowValue);
|
|
77
|
-
const minMs = min == null ? null : toDateMs(min);
|
|
78
|
-
const maxMs = max == null ? null : toDateMs(max);
|
|
79
|
-
if (rowMs != null && (minMs != null || maxMs != null)) {
|
|
80
|
-
if (minMs != null && rowMs < minMs) return false;
|
|
81
|
-
if (maxMs != null && rowMs > maxMs) return false;
|
|
82
|
-
return true;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// If neither coercion worked, let the row pass (defensive).
|
|
86
|
-
return true;
|
|
87
|
-
}
|
|
88
|
-
default:
|
|
89
|
-
return true;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Filters `rows` by the per-column filter model. AND semantics
|
|
95
|
-
* across columns — a row must pass EVERY active filter.
|
|
96
|
-
*
|
|
97
|
-
* Each filter item specifies an `op` (`'contains'` / `'equals'` /
|
|
98
|
-
* `'between'`) and a `value` whose shape matches the operator
|
|
99
|
-
* (string for contains; primitive for equals; `[min, max]` tuple
|
|
100
|
-
* for between).
|
|
101
|
-
*/
|
|
102
|
-
export function useTableFilter<T extends object>(
|
|
103
|
-
rows: T[],
|
|
104
|
-
filterModel: TableFilterModel,
|
|
105
|
-
): T[] {
|
|
106
|
-
return useMemo(() => {
|
|
107
|
-
if (!filterModel || filterModel.length === 0) return rows;
|
|
108
|
-
return rows.filter((row) =>
|
|
109
|
-
filterModel.every((filter) => {
|
|
110
|
-
const value = getNestedValue(row, filter.field);
|
|
111
|
-
return passesFilter(value, filter.op, filter.value);
|
|
112
|
-
}),
|
|
113
|
-
);
|
|
114
|
-
}, [rows, filterModel]);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Exposed for testing.
|
|
118
|
-
export { passesFilter, toNumber, toDateMs };
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
// @vitest-environment jsdom
|
|
2
|
-
import { describe, it, expect } from 'vitest';
|
|
3
|
-
import { renderHook } from '@testing-library/react';
|
|
4
|
-
import { useTableSearch, stringifyForSearch } from './useTableSearch.js';
|
|
5
|
-
import type { TableColumn } from '../../Table/table.types.js';
|
|
6
|
-
|
|
7
|
-
interface User {
|
|
8
|
-
name: string;
|
|
9
|
-
email: string;
|
|
10
|
-
age: number;
|
|
11
|
-
active: boolean;
|
|
12
|
-
joinedAt: Date;
|
|
13
|
-
meta: { city: string };
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const cols: TableColumn<User>[] = [
|
|
17
|
-
{ field: 'name', header: 'Name', searchable: true },
|
|
18
|
-
{ field: 'email', header: 'Email', searchable: true },
|
|
19
|
-
{ field: 'age', header: 'Age', searchable: true },
|
|
20
|
-
{ field: 'active', header: 'Active', searchable: true },
|
|
21
|
-
{ field: 'joinedAt' as never, header: 'Joined', searchable: true },
|
|
22
|
-
{ field: 'meta.city' as never, header: 'City', searchable: true },
|
|
23
|
-
];
|
|
24
|
-
|
|
25
|
-
const rows: User[] = [
|
|
26
|
-
{
|
|
27
|
-
name: 'Jane', email: 'jane@example.com', age: 30, active: true,
|
|
28
|
-
joinedAt: new Date('2024-01-15'), meta: { city: 'Roma' },
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
name: 'Bob', email: 'bob@example.org', age: 45, active: false,
|
|
32
|
-
joinedAt: new Date('2023-06-20'), meta: { city: 'Milano' },
|
|
33
|
-
},
|
|
34
|
-
];
|
|
35
|
-
|
|
36
|
-
describe('stringifyForSearch', () => {
|
|
37
|
-
it('handles null/undefined', () => {
|
|
38
|
-
expect(stringifyForSearch(null)).toBe('');
|
|
39
|
-
expect(stringifyForSearch(undefined)).toBe('');
|
|
40
|
-
});
|
|
41
|
-
it('returns strings as-is', () => {
|
|
42
|
-
expect(stringifyForSearch('hello')).toBe('hello');
|
|
43
|
-
});
|
|
44
|
-
it('stringifies numbers', () => {
|
|
45
|
-
expect(stringifyForSearch(42)).toBe('42');
|
|
46
|
-
});
|
|
47
|
-
it('stringifies booleans', () => {
|
|
48
|
-
expect(stringifyForSearch(true)).toBe('true');
|
|
49
|
-
expect(stringifyForSearch(false)).toBe('false');
|
|
50
|
-
});
|
|
51
|
-
it('stringifies Date as ISO', () => {
|
|
52
|
-
expect(stringifyForSearch(new Date('2024-01-15T00:00:00.000Z'))).toBe(
|
|
53
|
-
'2024-01-15T00:00:00.000Z',
|
|
54
|
-
);
|
|
55
|
-
});
|
|
56
|
-
it('stringifies objects via JSON.stringify', () => {
|
|
57
|
-
expect(stringifyForSearch({ a: 1 })).toBe('{"a":1}');
|
|
58
|
-
});
|
|
59
|
-
it('stringifies arrays via join-space of stringified elements', () => {
|
|
60
|
-
expect(stringifyForSearch(['a', 'b', 1])).toBe('a b 1');
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
describe('useTableSearch', () => {
|
|
65
|
-
it('returns all rows when query is empty', () => {
|
|
66
|
-
const { result } = renderHook(() => useTableSearch(rows, cols, ''));
|
|
67
|
-
expect(result.current).toHaveLength(2);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('returns all rows when query is only whitespace', () => {
|
|
71
|
-
const { result } = renderHook(() => useTableSearch(rows, cols, ' '));
|
|
72
|
-
expect(result.current).toHaveLength(2);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it('filters by string match (case-insensitive)', () => {
|
|
76
|
-
const { result } = renderHook(() => useTableSearch(rows, cols, 'jane'));
|
|
77
|
-
expect(result.current).toHaveLength(1);
|
|
78
|
-
expect(result.current[0]!.name).toBe('Jane');
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it('filters by number coerced to string', () => {
|
|
82
|
-
const { result } = renderHook(() => useTableSearch(rows, cols, '45'));
|
|
83
|
-
expect(result.current).toHaveLength(1);
|
|
84
|
-
expect(result.current[0]!.age).toBe(45);
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it('filters by boolean', () => {
|
|
88
|
-
const { result } = renderHook(() => useTableSearch(rows, cols, 'false'));
|
|
89
|
-
expect(result.current).toHaveLength(1);
|
|
90
|
-
expect(result.current[0]!.active).toBe(false);
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it('filters by Date (ISO substring)', () => {
|
|
94
|
-
const { result } = renderHook(() => useTableSearch(rows, cols, '2024-01'));
|
|
95
|
-
expect(result.current).toHaveLength(1);
|
|
96
|
-
expect(result.current[0]!.name).toBe('Jane');
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it('filters by NESTED key', () => {
|
|
100
|
-
const { result } = renderHook(() => useTableSearch(rows, cols, 'roma'));
|
|
101
|
-
expect(result.current).toHaveLength(1);
|
|
102
|
-
expect(result.current[0]!.name).toBe('Jane');
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
it('returns empty when nothing matches', () => {
|
|
106
|
-
const { result } = renderHook(() => useTableSearch(rows, cols, 'xxxxxxx'));
|
|
107
|
-
expect(result.current).toHaveLength(0);
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it('returns all rows if no column is searchable', () => {
|
|
111
|
-
const noneSearchable: TableColumn<User>[] = cols.map((c) => ({ ...c, searchable: false }));
|
|
112
|
-
const { result } = renderHook(() => useTableSearch(rows, noneSearchable, 'jane'));
|
|
113
|
-
expect(result.current).toHaveLength(2);
|
|
114
|
-
});
|
|
115
|
-
});
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { useMemo } from 'react';
|
|
2
|
-
import { getNestedValue } from './getNestedValue.js';
|
|
3
|
-
import type { TableColumn } from '../../Table/table.types.js';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Stringify any value into a search-comparable form. Handles all
|
|
7
|
-
* the edge cases that bit the MUI-side Table:
|
|
8
|
-
*
|
|
9
|
-
* - `null` / `undefined` → `''`
|
|
10
|
-
* - `string` → as-is
|
|
11
|
-
* - `number` / `bigint` → `String(value)`
|
|
12
|
-
* - `boolean` → `'true'` / `'false'`
|
|
13
|
-
* - `Date` → ISO 8601
|
|
14
|
-
* - `Array` / object → `JSON.stringify` (best effort)
|
|
15
|
-
*/
|
|
16
|
-
export function stringifyForSearch(value: unknown): string {
|
|
17
|
-
if (value == null) return '';
|
|
18
|
-
if (typeof value === 'string') return value;
|
|
19
|
-
if (typeof value === 'number' || typeof value === 'bigint') return String(value);
|
|
20
|
-
if (typeof value === 'boolean') return value ? 'true' : 'false';
|
|
21
|
-
if (value instanceof Date) return value.toISOString();
|
|
22
|
-
if (Array.isArray(value)) {
|
|
23
|
-
try {
|
|
24
|
-
return value.map(stringifyForSearch).join(' ');
|
|
25
|
-
} catch {
|
|
26
|
-
return '';
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
if (typeof value === 'object') {
|
|
30
|
-
try {
|
|
31
|
-
return JSON.stringify(value);
|
|
32
|
-
} catch {
|
|
33
|
-
return '';
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
return String(value);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Filters `rows` by `query` against the columns flagged
|
|
41
|
-
* `searchable: true`. Empty / whitespace-only query is a no-op
|
|
42
|
-
* (returns the original rows).
|
|
43
|
-
*
|
|
44
|
-
* Case-insensitive substring match. The caller is responsible for
|
|
45
|
-
* debouncing the input (use `useDebouncedValue`).
|
|
46
|
-
*/
|
|
47
|
-
export function useTableSearch<T extends object>(
|
|
48
|
-
rows: T[],
|
|
49
|
-
cols: TableColumn<T>[],
|
|
50
|
-
query: string,
|
|
51
|
-
): T[] {
|
|
52
|
-
return useMemo(() => {
|
|
53
|
-
const trimmed = query.trim();
|
|
54
|
-
if (!trimmed) return rows;
|
|
55
|
-
const needle = trimmed.toLowerCase();
|
|
56
|
-
const searchableCols = cols.filter((c) => c.searchable);
|
|
57
|
-
if (searchableCols.length === 0) return rows;
|
|
58
|
-
return rows.filter((row) =>
|
|
59
|
-
searchableCols.some((col) =>
|
|
60
|
-
stringifyForSearch(getNestedValue(row, col.field as string))
|
|
61
|
-
.toLowerCase()
|
|
62
|
-
.includes(needle),
|
|
63
|
-
),
|
|
64
|
-
);
|
|
65
|
-
}, [rows, cols, query]);
|
|
66
|
-
}
|