@qijenchen/design-system 0.1.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +163 -0
- package/dist/components/Accordion/accordion.d.ts +37 -0
- package/dist/components/Accordion/accordion.d.ts.map +1 -0
- package/dist/components/Accordion/accordion.js +78 -0
- package/dist/components/Accordion/accordion.js.map +1 -0
- package/dist/components/Alert/alert.d.ts +47 -0
- package/dist/components/Alert/alert.d.ts.map +1 -0
- package/dist/components/Alert/alert.js +132 -0
- package/dist/components/Alert/alert.js.map +1 -0
- package/dist/components/AppShell/_demo-helpers.d.ts +49 -0
- package/dist/components/AppShell/_demo-helpers.d.ts.map +1 -0
- package/dist/components/AppShell/app-shell.d.ts +76 -0
- package/dist/components/AppShell/app-shell.d.ts.map +1 -0
- package/dist/components/AppShell/app-shell.js +214 -0
- package/dist/components/AppShell/app-shell.js.map +1 -0
- package/dist/components/AspectRatio/aspect-ratio.d.ts +40 -0
- package/dist/components/AspectRatio/aspect-ratio.d.ts.map +1 -0
- package/dist/components/AspectRatio/aspect-ratio.js +23 -0
- package/dist/components/AspectRatio/aspect-ratio.js.map +1 -0
- package/dist/components/Avatar/avatar.d.ts +85 -0
- package/dist/components/Avatar/avatar.d.ts.map +1 -0
- package/dist/components/Avatar/avatar.js +195 -0
- package/dist/components/Avatar/avatar.js.map +1 -0
- package/dist/components/Badge/badge.d.ts +43 -0
- package/dist/components/Badge/badge.d.ts.map +1 -0
- package/dist/components/Badge/badge.js +69 -0
- package/dist/components/Badge/badge.js.map +1 -0
- package/dist/components/Breadcrumb/breadcrumb.d.ts +163 -0
- package/dist/components/Breadcrumb/breadcrumb.d.ts.map +1 -0
- package/dist/components/Breadcrumb/breadcrumb.js +300 -0
- package/dist/components/Breadcrumb/breadcrumb.js.map +1 -0
- package/dist/components/BulkActionBar/bulk-action-bar.d.ts +46 -0
- package/dist/components/BulkActionBar/bulk-action-bar.d.ts.map +1 -0
- package/dist/components/BulkActionBar/bulk-action-bar.js +78 -0
- package/dist/components/BulkActionBar/bulk-action-bar.js.map +1 -0
- package/dist/components/Button/button-group.d.ts +49 -0
- package/dist/components/Button/button-group.d.ts.map +1 -0
- package/dist/components/Button/button-group.js +46 -0
- package/dist/components/Button/button-group.js.map +1 -0
- package/dist/components/Button/button.d.ts +203 -0
- package/dist/components/Button/button.d.ts.map +1 -0
- package/dist/components/Button/button.js +309 -0
- package/dist/components/Button/button.js.map +1 -0
- package/dist/components/Calendar/calendar.d.ts +81 -0
- package/dist/components/Calendar/calendar.d.ts.map +1 -0
- package/dist/components/Calendar/calendar.js +282 -0
- package/dist/components/Calendar/calendar.js.map +1 -0
- package/dist/components/Carousel/carousel.d.ts +61 -0
- package/dist/components/Carousel/carousel.d.ts.map +1 -0
- package/dist/components/Carousel/carousel.js +276 -0
- package/dist/components/Carousel/carousel.js.map +1 -0
- package/dist/components/Chart/chart.d.ts +94 -0
- package/dist/components/Chart/chart.d.ts.map +1 -0
- package/dist/components/Chart/chart.js +233 -0
- package/dist/components/Chart/chart.js.map +1 -0
- package/dist/components/Checkbox/checkbox-group.d.ts +58 -0
- package/dist/components/Checkbox/checkbox-group.d.ts.map +1 -0
- package/dist/components/Checkbox/checkbox-group.js +28 -0
- package/dist/components/Checkbox/checkbox-group.js.map +1 -0
- package/dist/components/Checkbox/checkbox.d.ts +73 -0
- package/dist/components/Checkbox/checkbox.d.ts.map +1 -0
- package/dist/components/Checkbox/checkbox.js +125 -0
- package/dist/components/Checkbox/checkbox.js.map +1 -0
- package/dist/components/Chip/chip.d.ts +54 -0
- package/dist/components/Chip/chip.d.ts.map +1 -0
- package/dist/components/Chip/chip.js +224 -0
- package/dist/components/Chip/chip.js.map +1 -0
- package/dist/components/CircularProgress/circular-progress.d.ts +40 -0
- package/dist/components/CircularProgress/circular-progress.d.ts.map +1 -0
- package/dist/components/CircularProgress/circular-progress.js +118 -0
- package/dist/components/CircularProgress/circular-progress.js.map +1 -0
- package/dist/components/Coachmark/coachmark.d.ts +100 -0
- package/dist/components/Coachmark/coachmark.d.ts.map +1 -0
- package/dist/components/Coachmark/coachmark.js +107 -0
- package/dist/components/Coachmark/coachmark.js.map +1 -0
- package/dist/components/Combobox/combobox.d.ts +150 -0
- package/dist/components/Combobox/combobox.d.ts.map +1 -0
- package/dist/components/Combobox/combobox.js +595 -0
- package/dist/components/Combobox/combobox.js.map +1 -0
- package/dist/components/Command/command.d.ts +106 -0
- package/dist/components/Command/command.d.ts.map +1 -0
- package/dist/components/Command/command.js +123 -0
- package/dist/components/Command/command.js.map +1 -0
- package/dist/components/DataTable/active-editor-controller.d.ts +66 -0
- package/dist/components/DataTable/active-editor-controller.d.ts.map +1 -0
- package/dist/components/DataTable/cell-registry.d.ts +37 -0
- package/dist/components/DataTable/cell-registry.d.ts.map +1 -0
- package/dist/components/DataTable/cell-registry.js +377 -0
- package/dist/components/DataTable/cell-registry.js.map +1 -0
- package/dist/components/DataTable/column-types.d.ts +145 -0
- package/dist/components/DataTable/column-types.d.ts.map +1 -0
- package/dist/components/DataTable/column-types.js +17 -0
- package/dist/components/DataTable/column-types.js.map +1 -0
- package/dist/components/DataTable/data-table-column-visibility-panel.d.ts +49 -0
- package/dist/components/DataTable/data-table-column-visibility-panel.d.ts.map +1 -0
- package/dist/components/DataTable/data-table-filter-panel.d.ts +30 -0
- package/dist/components/DataTable/data-table-filter-panel.d.ts.map +1 -0
- package/dist/components/DataTable/data-table-interaction-layer.d.ts +78 -0
- package/dist/components/DataTable/data-table-interaction-layer.d.ts.map +1 -0
- package/dist/components/DataTable/data-table-interaction-layer.js +220 -0
- package/dist/components/DataTable/data-table-interaction-layer.js.map +1 -0
- package/dist/components/DataTable/data-table-sort-manager.d.ts +19 -0
- package/dist/components/DataTable/data-table-sort-manager.d.ts.map +1 -0
- package/dist/components/DataTable/data-table.d.ts +181 -0
- package/dist/components/DataTable/data-table.d.ts.map +1 -0
- package/dist/components/DataTable/data-table.js +1851 -0
- package/dist/components/DataTable/data-table.js.map +1 -0
- package/dist/components/DataTable/filter-operators.d.ts +116 -0
- package/dist/components/DataTable/filter-operators.d.ts.map +1 -0
- package/dist/components/DataTable/filter-tree.d.ts +66 -0
- package/dist/components/DataTable/filter-tree.d.ts.map +1 -0
- package/dist/components/DataTable/lib/column-meta.d.ts +49 -0
- package/dist/components/DataTable/lib/column-meta.d.ts.map +1 -0
- package/dist/components/DateGrid/date-grid.d.ts +61 -0
- package/dist/components/DateGrid/date-grid.d.ts.map +1 -0
- package/dist/components/DateGrid/date-grid.js +168 -0
- package/dist/components/DateGrid/date-grid.js.map +1 -0
- package/dist/components/DatePicker/date-picker.d.ts +119 -0
- package/dist/components/DatePicker/date-picker.d.ts.map +1 -0
- package/dist/components/DatePicker/date-picker.js +743 -0
- package/dist/components/DatePicker/date-picker.js.map +1 -0
- package/dist/components/DescriptionList/description-list.d.ts +60 -0
- package/dist/components/DescriptionList/description-list.d.ts.map +1 -0
- package/dist/components/DescriptionList/description-list.js +77 -0
- package/dist/components/DescriptionList/description-list.js.map +1 -0
- package/dist/components/Dialog/dialog.d.ts +54 -0
- package/dist/components/Dialog/dialog.d.ts.map +1 -0
- package/dist/components/Dialog/dialog.js +151 -0
- package/dist/components/Dialog/dialog.js.map +1 -0
- package/dist/components/DropdownMenu/dropdown-menu.d.ts +111 -0
- package/dist/components/DropdownMenu/dropdown-menu.d.ts.map +1 -0
- package/dist/components/DropdownMenu/dropdown-menu.js +288 -0
- package/dist/components/DropdownMenu/dropdown-menu.js.map +1 -0
- package/dist/components/Empty/empty.d.ts +40 -0
- package/dist/components/Empty/empty.d.ts.map +1 -0
- package/dist/components/Empty/empty.js +66 -0
- package/dist/components/Empty/empty.js.map +1 -0
- package/dist/components/Field/field-context.d.ts +77 -0
- package/dist/components/Field/field-context.d.ts.map +1 -0
- package/dist/components/Field/field-context.js +37 -0
- package/dist/components/Field/field-context.js.map +1 -0
- package/dist/components/Field/field-types.d.ts +5 -0
- package/dist/components/Field/field-types.d.ts.map +1 -0
- package/dist/components/Field/field-types.js +13 -0
- package/dist/components/Field/field-types.js.map +1 -0
- package/dist/components/Field/field-wrapper.d.ts +17 -0
- package/dist/components/Field/field-wrapper.d.ts.map +1 -0
- package/dist/components/Field/field-wrapper.js +252 -0
- package/dist/components/Field/field-wrapper.js.map +1 -0
- package/dist/components/Field/field.d.ts +127 -0
- package/dist/components/Field/field.d.ts.map +1 -0
- package/dist/components/Field/field.js +295 -0
- package/dist/components/Field/field.js.map +1 -0
- package/dist/components/FieldControlGroup/field-control-group.d.ts +74 -0
- package/dist/components/FieldControlGroup/field-control-group.d.ts.map +1 -0
- package/dist/components/FieldControlGroup/field-control-group.js +62 -0
- package/dist/components/FieldControlGroup/field-control-group.js.map +1 -0
- package/dist/components/FileItem/file-item.d.ts +44 -0
- package/dist/components/FileItem/file-item.d.ts.map +1 -0
- package/dist/components/FileItem/file-item.js +202 -0
- package/dist/components/FileItem/file-item.js.map +1 -0
- package/dist/components/FileUpload/file-upload.d.ts +97 -0
- package/dist/components/FileUpload/file-upload.d.ts.map +1 -0
- package/dist/components/FileUpload/file-upload.js +231 -0
- package/dist/components/FileUpload/file-upload.js.map +1 -0
- package/dist/components/FileViewer/file-viewer-types.d.ts +73 -0
- package/dist/components/FileViewer/file-viewer-types.d.ts.map +1 -0
- package/dist/components/FileViewer/file-viewer.d.ts +82 -0
- package/dist/components/FileViewer/file-viewer.d.ts.map +1 -0
- package/dist/components/FileViewer/file-viewer.js +752 -0
- package/dist/components/FileViewer/file-viewer.js.map +1 -0
- package/dist/components/FileViewer/image-renderer.d.ts +9 -0
- package/dist/components/FileViewer/image-renderer.d.ts.map +1 -0
- package/dist/components/FileViewer/image-renderer.js +165 -0
- package/dist/components/FileViewer/image-renderer.js.map +1 -0
- package/dist/components/HoverCard/hover-card.d.ts +30 -0
- package/dist/components/HoverCard/hover-card.d.ts.map +1 -0
- package/dist/components/HoverCard/hover-card.js +61 -0
- package/dist/components/HoverCard/hover-card.js.map +1 -0
- package/dist/components/Input/input.d.ts +72 -0
- package/dist/components/Input/input.d.ts.map +1 -0
- package/dist/components/Input/input.js +148 -0
- package/dist/components/Input/input.js.map +1 -0
- package/dist/components/LinkInput/link-input.d.ts +46 -0
- package/dist/components/LinkInput/link-input.d.ts.map +1 -0
- package/dist/components/LinkInput/link-input.js +215 -0
- package/dist/components/LinkInput/link-input.js.map +1 -0
- package/dist/components/Menu/menu-item.d.ts +83 -0
- package/dist/components/Menu/menu-item.d.ts.map +1 -0
- package/dist/components/Menu/menu-item.js +209 -0
- package/dist/components/Menu/menu-item.js.map +1 -0
- package/dist/components/NameCard/name-card.d.ts +85 -0
- package/dist/components/NameCard/name-card.d.ts.map +1 -0
- package/dist/components/NameCard/name-card.js +153 -0
- package/dist/components/NameCard/name-card.js.map +1 -0
- package/dist/components/Notice/notice.d.ts +69 -0
- package/dist/components/Notice/notice.d.ts.map +1 -0
- package/dist/components/Notice/notice.js +121 -0
- package/dist/components/Notice/notice.js.map +1 -0
- package/dist/components/NumberInput/number-input.d.ts +57 -0
- package/dist/components/NumberInput/number-input.d.ts.map +1 -0
- package/dist/components/NumberInput/number-input.js +131 -0
- package/dist/components/NumberInput/number-input.js.map +1 -0
- package/dist/components/OverflowIndicator/overflow-indicator.d.ts +23 -0
- package/dist/components/OverflowIndicator/overflow-indicator.d.ts.map +1 -0
- package/dist/components/OverflowIndicator/overflow-indicator.js +111 -0
- package/dist/components/OverflowIndicator/overflow-indicator.js.map +1 -0
- package/dist/components/PeoplePicker/avatar-stack-overflow.d.ts +57 -0
- package/dist/components/PeoplePicker/avatar-stack-overflow.d.ts.map +1 -0
- package/dist/components/PeoplePicker/avatar-stack-overflow.js +35 -0
- package/dist/components/PeoplePicker/avatar-stack-overflow.js.map +1 -0
- package/dist/components/PeoplePicker/people-picker-helpers.d.ts +7 -0
- package/dist/components/PeoplePicker/people-picker-helpers.d.ts.map +1 -0
- package/dist/components/PeoplePicker/people-picker-helpers.js +25 -0
- package/dist/components/PeoplePicker/people-picker-helpers.js.map +1 -0
- package/dist/components/PeoplePicker/people-picker.d.ts +77 -0
- package/dist/components/PeoplePicker/people-picker.d.ts.map +1 -0
- package/dist/components/PeoplePicker/people-picker.js +263 -0
- package/dist/components/PeoplePicker/people-picker.js.map +1 -0
- package/dist/components/PeoplePicker/person-display.d.ts +66 -0
- package/dist/components/PeoplePicker/person-display.d.ts.map +1 -0
- package/dist/components/PeoplePicker/person-display.js +203 -0
- package/dist/components/PeoplePicker/person-display.js.map +1 -0
- package/dist/components/Popover/popover.d.ts +50 -0
- package/dist/components/Popover/popover.d.ts.map +1 -0
- package/dist/components/Popover/popover.js +113 -0
- package/dist/components/Popover/popover.js.map +1 -0
- package/dist/components/ProgressBar/progress-bar.d.ts +37 -0
- package/dist/components/ProgressBar/progress-bar.d.ts.map +1 -0
- package/dist/components/ProgressBar/progress-bar.js +86 -0
- package/dist/components/ProgressBar/progress-bar.js.map +1 -0
- package/dist/components/RadioGroup/radio-group.d.ts +78 -0
- package/dist/components/RadioGroup/radio-group.d.ts.map +1 -0
- package/dist/components/RadioGroup/radio-group.js +153 -0
- package/dist/components/RadioGroup/radio-group.js.map +1 -0
- package/dist/components/Rating/rating.d.ts +46 -0
- package/dist/components/Rating/rating.d.ts.map +1 -0
- package/dist/components/Rating/rating.js +179 -0
- package/dist/components/Rating/rating.js.map +1 -0
- package/dist/components/ScrollArea/scroll-area.d.ts +45 -0
- package/dist/components/ScrollArea/scroll-area.d.ts.map +1 -0
- package/dist/components/ScrollArea/scroll-area.js +65 -0
- package/dist/components/ScrollArea/scroll-area.js.map +1 -0
- package/dist/components/SegmentedControl/segmented-control.d.ts +102 -0
- package/dist/components/SegmentedControl/segmented-control.d.ts.map +1 -0
- package/dist/components/SegmentedControl/segmented-control.js +171 -0
- package/dist/components/SegmentedControl/segmented-control.js.map +1 -0
- package/dist/components/Select/select.d.ts +102 -0
- package/dist/components/Select/select.d.ts.map +1 -0
- package/dist/components/Select/select.js +435 -0
- package/dist/components/Select/select.js.map +1 -0
- package/dist/components/SelectMenu/select-menu.d.ts +103 -0
- package/dist/components/SelectMenu/select-menu.d.ts.map +1 -0
- package/dist/components/SelectMenu/select-menu.js +239 -0
- package/dist/components/SelectMenu/select-menu.js.map +1 -0
- package/dist/components/SelectionControl/selection-item.d.ts +69 -0
- package/dist/components/SelectionControl/selection-item.d.ts.map +1 -0
- package/dist/components/SelectionControl/selection-item.js +142 -0
- package/dist/components/SelectionControl/selection-item.js.map +1 -0
- package/dist/components/Separator/separator.d.ts +17 -0
- package/dist/components/Separator/separator.d.ts.map +1 -0
- package/dist/components/Separator/separator.js +39 -0
- package/dist/components/Separator/separator.js.map +1 -0
- package/dist/components/Sheet/sheet.d.ts +56 -0
- package/dist/components/Sheet/sheet.d.ts.map +1 -0
- package/dist/components/Sheet/sheet.js +145 -0
- package/dist/components/Sheet/sheet.js.map +1 -0
- package/dist/components/Sidebar/sidebar.d.ts +195 -0
- package/dist/components/Sidebar/sidebar.d.ts.map +1 -0
- package/dist/components/Sidebar/sidebar.js +826 -0
- package/dist/components/Sidebar/sidebar.js.map +1 -0
- package/dist/components/Skeleton/skeleton.d.ts +16 -0
- package/dist/components/Skeleton/skeleton.d.ts.map +1 -0
- package/dist/components/Skeleton/skeleton.js +30 -0
- package/dist/components/Skeleton/skeleton.js.map +1 -0
- package/dist/components/Slider/slider.d.ts +48 -0
- package/dist/components/Slider/slider.d.ts.map +1 -0
- package/dist/components/Slider/slider.js +108 -0
- package/dist/components/Slider/slider.js.map +1 -0
- package/dist/components/Steps/steps.d.ts +71 -0
- package/dist/components/Steps/steps.d.ts.map +1 -0
- package/dist/components/Steps/steps.js +583 -0
- package/dist/components/Steps/steps.js.map +1 -0
- package/dist/components/Switch/switch.d.ts +112 -0
- package/dist/components/Switch/switch.d.ts.map +1 -0
- package/dist/components/Switch/switch.js +179 -0
- package/dist/components/Switch/switch.js.map +1 -0
- package/dist/components/Tabs/tabs.d.ts +104 -0
- package/dist/components/Tabs/tabs.d.ts.map +1 -0
- package/dist/components/Tabs/tabs.js +316 -0
- package/dist/components/Tabs/tabs.js.map +1 -0
- package/dist/components/Tag/tag.d.ts +86 -0
- package/dist/components/Tag/tag.d.ts.map +1 -0
- package/dist/components/Tag/tag.js +172 -0
- package/dist/components/Tag/tag.js.map +1 -0
- package/dist/components/Textarea/textarea.d.ts +74 -0
- package/dist/components/Textarea/textarea.d.ts.map +1 -0
- package/dist/components/Textarea/textarea.js +224 -0
- package/dist/components/Textarea/textarea.js.map +1 -0
- package/dist/components/TimePicker/time-columns.d.ts +46 -0
- package/dist/components/TimePicker/time-columns.d.ts.map +1 -0
- package/dist/components/TimePicker/time-columns.js +173 -0
- package/dist/components/TimePicker/time-columns.js.map +1 -0
- package/dist/components/TimePicker/time-picker.d.ts +94 -0
- package/dist/components/TimePicker/time-picker.d.ts.map +1 -0
- package/dist/components/TimePicker/time-picker.js +253 -0
- package/dist/components/TimePicker/time-picker.js.map +1 -0
- package/dist/components/Toast/toast.d.ts +61 -0
- package/dist/components/Toast/toast.d.ts.map +1 -0
- package/dist/components/Toast/toast.js +76 -0
- package/dist/components/Toast/toast.js.map +1 -0
- package/dist/components/Tooltip/tooltip.d.ts +20 -0
- package/dist/components/Tooltip/tooltip.d.ts.map +1 -0
- package/dist/components/Tooltip/tooltip.js +53 -0
- package/dist/components/Tooltip/tooltip.js.map +1 -0
- package/dist/components/TreeView/tree-view.d.ts +166 -0
- package/dist/components/TreeView/tree-view.d.ts.map +1 -0
- package/dist/components/TreeView/tree-view.js +617 -0
- package/dist/components/TreeView/tree-view.js.map +1 -0
- package/dist/hooks/use-controllable.d.ts +16 -0
- package/dist/hooks/use-controllable.d.ts.map +1 -0
- package/dist/hooks/use-controllable.js +26 -0
- package/dist/hooks/use-controllable.js.map +1 -0
- package/dist/hooks/use-is-narrow-viewport.d.ts +2 -0
- package/dist/hooks/use-is-narrow-viewport.d.ts.map +1 -0
- package/dist/hooks/use-is-narrow-viewport.js +19 -0
- package/dist/hooks/use-is-narrow-viewport.js.map +1 -0
- package/dist/hooks/use-is-touch-device.d.ts +8 -0
- package/dist/hooks/use-is-touch-device.d.ts.map +1 -0
- package/dist/hooks/use-is-touch-device.js +16 -0
- package/dist/hooks/use-is-touch-device.js.map +1 -0
- package/dist/hooks/use-overflow-items.d.ts +124 -0
- package/dist/hooks/use-overflow-items.d.ts.map +1 -0
- package/dist/hooks/use-overflow-items.js +97 -0
- package/dist/hooks/use-overflow-items.js.map +1 -0
- package/dist/index.d.ts +74 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +371 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/drag-visual.d.ts +158 -0
- package/dist/lib/drag-visual.d.ts.map +1 -0
- package/dist/lib/drag-visual.js +96 -0
- package/dist/lib/drag-visual.js.map +1 -0
- package/dist/lib/i18n/i18n-context.d.ts +105 -0
- package/dist/lib/i18n/i18n-context.d.ts.map +1 -0
- package/dist/lib/multi-select-ordering.d.ts +54 -0
- package/dist/lib/multi-select-ordering.d.ts.map +1 -0
- package/dist/lib/multi-select-ordering.js +13 -0
- package/dist/lib/multi-select-ordering.js.map +1 -0
- package/dist/lib/utils.d.ts +12 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +79 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/patterns/element-anatomy/item-anatomy.d.ts +370 -0
- package/dist/patterns/element-anatomy/item-anatomy.d.ts.map +1 -0
- package/dist/patterns/element-anatomy/item-anatomy.js +272 -0
- package/dist/patterns/element-anatomy/item-anatomy.js.map +1 -0
- package/dist/patterns/header-canonical/chrome-header.d.ts +80 -0
- package/dist/patterns/header-canonical/chrome-header.d.ts.map +1 -0
- package/dist/patterns/header-canonical/chrome-header.js +75 -0
- package/dist/patterns/header-canonical/chrome-header.js.map +1 -0
- package/dist/patterns/horizontal-overflow/horizontal-overflow.d.ts +101 -0
- package/dist/patterns/horizontal-overflow/horizontal-overflow.d.ts.map +1 -0
- package/dist/patterns/horizontal-overflow/horizontal-overflow.js +105 -0
- package/dist/patterns/horizontal-overflow/horizontal-overflow.js.map +1 -0
- package/dist/patterns/overlay-surface/overlay-surface.d.ts +28 -0
- package/dist/patterns/overlay-surface/overlay-surface.d.ts.map +1 -0
- package/dist/patterns/overlay-surface/overlay-surface.js +85 -0
- package/dist/patterns/overlay-surface/overlay-surface.js.map +1 -0
- package/dist/patterns/resize-handle/resize-handle.d.ts +102 -0
- package/dist/patterns/resize-handle/resize-handle.d.ts.map +1 -0
- package/dist/patterns/resize-handle/resize-handle.js +74 -0
- package/dist/patterns/resize-handle/resize-handle.js.map +1 -0
- package/dist/react-day-picker.css +457 -0
- package/dist/stories-helpers/anatomy/anatomy-utils.d.ts +40 -0
- package/dist/stories-helpers/anatomy/anatomy-utils.d.ts.map +1 -0
- package/dist/tokens/elevation/overlay-geometry.d.ts +12 -0
- package/dist/tokens/elevation/overlay-geometry.d.ts.map +1 -0
- package/dist/tokens/elevation/overlay-geometry.js +7 -0
- package/dist/tokens/elevation/overlay-geometry.js.map +1 -0
- package/dist/tokens/motion/motion.d.ts +15 -0
- package/dist/tokens/motion/motion.d.ts.map +1 -0
- package/dist/tokens/motion/motion.js +9 -0
- package/dist/tokens/motion/motion.js.map +1 -0
- package/dist/tokens/uiSize/icon-size.d.ts +53 -0
- package/dist/tokens/uiSize/icon-size.d.ts.map +1 -0
- package/package.json +92 -0
- package/src/README.md +32 -0
- package/src/components/Accordion/accordion.tsx +104 -0
- package/src/components/Alert/alert.tsx +188 -0
- package/src/components/AppShell/_demo-helpers.tsx +198 -0
- package/src/components/AppShell/app-shell.tsx +364 -0
- package/src/components/AspectRatio/aspect-ratio.tsx +58 -0
- package/src/components/Avatar/avatar.tsx +368 -0
- package/src/components/Badge/badge.tsx +104 -0
- package/src/components/Breadcrumb/breadcrumb.tsx +619 -0
- package/src/components/BulkActionBar/bulk-action-bar.tsx +156 -0
- package/src/components/Button/button-group.tsx +96 -0
- package/src/components/Button/button.tsx +539 -0
- package/src/components/Calendar/calendar.tsx +411 -0
- package/src/components/Carousel/carousel.tsx +371 -0
- package/src/components/Chart/chart.tsx +376 -0
- package/src/components/Checkbox/checkbox-group.tsx +94 -0
- package/src/components/Checkbox/checkbox.tsx +237 -0
- package/src/components/Chip/chip.tsx +359 -0
- package/src/components/CircularProgress/circular-progress.tsx +204 -0
- package/src/components/Coachmark/coachmark.tsx +255 -0
- package/src/components/Combobox/combobox.tsx +826 -0
- package/src/components/Command/command.tsx +187 -0
- package/src/components/DataTable/active-editor-controller.ts +72 -0
- package/src/components/DataTable/cell-registry.tsx +520 -0
- package/src/components/DataTable/column-types.ts +180 -0
- package/src/components/DataTable/data-table-column-visibility-panel.tsx +261 -0
- package/src/components/DataTable/data-table-filter-panel.tsx +813 -0
- package/src/components/DataTable/data-table-interaction-layer.tsx +483 -0
- package/src/components/DataTable/data-table-sort-manager.tsx +210 -0
- package/src/components/DataTable/data-table.css +165 -0
- package/src/components/DataTable/data-table.tsx +2924 -0
- package/src/components/DataTable/filter-operators.ts +225 -0
- package/src/components/DataTable/filter-tree.ts +313 -0
- package/src/components/DataTable/lib/column-meta.ts +79 -0
- package/src/components/DateGrid/date-grid.tsx +209 -0
- package/src/components/DatePicker/date-picker.tsx +1114 -0
- package/src/components/DescriptionList/description-list.tsx +141 -0
- package/src/components/Dialog/dialog.tsx +267 -0
- package/src/components/DropdownMenu/dropdown-menu.tsx +475 -0
- package/src/components/Empty/empty.tsx +108 -0
- package/src/components/Field/field-context.ts +136 -0
- package/src/components/Field/field-types.ts +52 -0
- package/src/components/Field/field-wrapper.tsx +348 -0
- package/src/components/Field/field.tsx +535 -0
- package/src/components/FieldControlGroup/field-control-group.tsx +136 -0
- package/src/components/FileItem/file-item.tsx +322 -0
- package/src/components/FileUpload/file-upload.tsx +326 -0
- package/src/components/FileViewer/file-viewer-types.ts +76 -0
- package/src/components/FileViewer/file-viewer.tsx +1065 -0
- package/src/components/FileViewer/image-renderer.tsx +256 -0
- package/src/components/HoverCard/hover-card.tsx +79 -0
- package/src/components/Input/input.tsx +233 -0
- package/src/components/LinkInput/link-input.tsx +304 -0
- package/src/components/Menu/menu-item.tsx +334 -0
- package/src/components/NameCard/name-card.tsx +319 -0
- package/src/components/Notice/notice.tsx +196 -0
- package/src/components/NumberInput/number-input.tsx +203 -0
- package/src/components/OverflowIndicator/overflow-indicator.tsx +156 -0
- package/src/components/PeoplePicker/avatar-stack-overflow.ts +100 -0
- package/src/components/PeoplePicker/people-picker-helpers.ts +76 -0
- package/src/components/PeoplePicker/people-picker.tsx +455 -0
- package/src/components/PeoplePicker/person-display.tsx +358 -0
- package/src/components/Popover/popover.tsx +183 -0
- package/src/components/ProgressBar/progress-bar.tsx +157 -0
- package/src/components/README.md +58 -0
- package/src/components/RadioGroup/radio-group.tsx +261 -0
- package/src/components/Rating/rating.tsx +295 -0
- package/src/components/ScrollArea/scroll-area.tsx +110 -0
- package/src/components/SegmentedControl/segmented-control.tsx +304 -0
- package/src/components/Select/select.tsx +658 -0
- package/src/components/SelectMenu/select-menu.tsx +430 -0
- package/src/components/SelectionControl/selection-item.tsx +261 -0
- package/src/components/Separator/separator.tsx +48 -0
- package/src/components/Sheet/sheet.tsx +240 -0
- package/src/components/Sidebar/sidebar.tsx +1280 -0
- package/src/components/Skeleton/skeleton.tsx +35 -0
- package/src/components/Slider/slider.tsx +158 -0
- package/src/components/Steps/steps.tsx +850 -0
- package/src/components/Switch/switch.tsx +285 -0
- package/src/components/Tabs/tabs.tsx +515 -0
- package/src/components/Tag/tag.tsx +246 -0
- package/src/components/Textarea/textarea.tsx +280 -0
- package/src/components/TimePicker/time-columns.tsx +260 -0
- package/src/components/TimePicker/time-picker.tsx +419 -0
- package/src/components/Toast/toast.tsx +129 -0
- package/src/components/Tooltip/tooltip.tsx +68 -0
- package/src/components/TreeView/tree-view.tsx +1031 -0
- package/src/hooks/use-controllable.ts +40 -0
- package/src/hooks/use-is-narrow-viewport.ts +19 -0
- package/src/hooks/use-is-touch-device.ts +21 -0
- package/src/hooks/use-overflow-items.ts +256 -0
- package/src/index.ts +85 -0
- package/src/lib/README.md +82 -0
- package/src/lib/drag-visual.ts +272 -0
- package/src/lib/i18n/README.md +60 -0
- package/src/lib/i18n/i18n-context.tsx +129 -0
- package/src/lib/multi-select-ordering.ts +61 -0
- package/src/lib/utils.ts +93 -0
- package/src/patterns/README.md +67 -0
- package/src/patterns/element-anatomy/item-anatomy.tsx +744 -0
- package/src/patterns/header-canonical/chrome-header.tsx +175 -0
- package/src/patterns/header-canonical/header-canonical.css +27 -0
- package/src/patterns/horizontal-overflow/horizontal-overflow.tsx +217 -0
- package/src/patterns/overlay-surface/overlay-surface.tsx +191 -0
- package/src/patterns/resize-handle/resize-handle.tsx +188 -0
- package/src/stories-helpers/anatomy/anatomy-utils.tsx +64 -0
- package/src/styles/preset.css +31 -0
- package/src/styles/tokens.css +35 -0
- package/src/tokens/README.md +53 -0
- package/src/tokens/color/primitives.css +429 -0
- package/src/tokens/color/semantic.css +539 -0
- package/src/tokens/elevation/overlay-geometry.ts +13 -0
- package/src/tokens/layoutSpace/layoutSpace.css +36 -0
- package/src/tokens/motion/motion.css +30 -0
- package/src/tokens/motion/motion.ts +17 -0
- package/src/tokens/opacity/opacity.css +23 -0
- package/src/tokens/radius/radius.css +19 -0
- package/src/tokens/typography/typography.css +118 -0
- package/src/tokens/uiSize/icon-size.ts +52 -0
- package/src/tokens/uiSize/uiSize.css +125 -0
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* filter-operators.ts — Advanced Filter Operator Registry SSOT
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for all filter operator definitions.
|
|
5
|
+
* 51 ops × 9 columnTypes × 12 ValueShapes.
|
|
6
|
+
*
|
|
7
|
+
* **禁止**:component 內 hardcode op 字串。一律走 `OPERATOR_REGISTRY[columnType]`。
|
|
8
|
+
*
|
|
9
|
+
* 設計路線:ClickUp baseline + 合理擴充(string +2, url 獨立 +4, date +2)。
|
|
10
|
+
* 詳細 spec:`./advanced-filter-operators.draft.md`
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { ColumnType } from './column-types'
|
|
14
|
+
|
|
15
|
+
// ── ValueShape ───────────────────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Filter value 該用什麼 picker — panel 只認 ValueShape,不認 op。
|
|
19
|
+
* 新增 op 只需 map 到既有 shape。
|
|
20
|
+
*/
|
|
21
|
+
export type ValueShape =
|
|
22
|
+
| 'none' // is_set / is_not_set / is_true / is_false 純 predicate
|
|
23
|
+
| 'text' // <Input>
|
|
24
|
+
| 'number' // <NumberInput>
|
|
25
|
+
| 'date_single' // <DatePicker> single date
|
|
26
|
+
| 'date_range' // <DatePickerRange> Ant-style split-input
|
|
27
|
+
| 'date_relative' // <Select> 預設選項(today/yesterday/this_week/...)
|
|
28
|
+
| 'datetime_single' // <DatePicker showTime>(includeTime=true)
|
|
29
|
+
| 'datetime_range' // <DatePickerRange showTime>(includeTime=true)
|
|
30
|
+
| 'select_single' // <Select>(預留,目前無 op 採用)
|
|
31
|
+
| 'select_multi' // <SelectMenu multiple>(select.is 也走這)
|
|
32
|
+
| 'person_single' // <PeoplePicker>(預留)
|
|
33
|
+
| 'person_multi' // <PeoplePicker multiple>(person.is 也走這)
|
|
34
|
+
|
|
35
|
+
// ── OperatorSpec ────────────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
export interface OperatorSpec {
|
|
38
|
+
/** Internal key, snake_case, immutable for FilterTree serialization */
|
|
39
|
+
op: string
|
|
40
|
+
/** UI label in zh-TW(暫硬編,留 i18n hook) */
|
|
41
|
+
label: string
|
|
42
|
+
/** Reference label for English / future i18n key */
|
|
43
|
+
labelEn: string
|
|
44
|
+
/**
|
|
45
|
+
* Value picker shape.
|
|
46
|
+
* date_* shapes auto-promote to datetime_* when includeTime=true(via `getValueShape`).
|
|
47
|
+
*/
|
|
48
|
+
valueShape: ValueShape
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ── Date Relative Options(13 個,含分組)───────────────────────────────
|
|
52
|
+
// 群組順序 + 排列對齊 Linear / Notion / ClickUp 共識:Past → Current → Future
|
|
53
|
+
// 群內排列:由遠到近(Past:30 → 7 → 月 → 週 → 昨;Future:明 → 週 → 月 → 7 → 30)
|
|
54
|
+
// 中間語義 anchor today/this 排於 Current
|
|
55
|
+
|
|
56
|
+
export const DATE_RELATIVE_GROUPS = [
|
|
57
|
+
{ key: 'past', label: '過去' },
|
|
58
|
+
{ key: 'current', label: '目前' },
|
|
59
|
+
{ key: 'future', label: '未來' },
|
|
60
|
+
] as const
|
|
61
|
+
|
|
62
|
+
export const DATE_RELATIVE_OPTIONS = [
|
|
63
|
+
// Past
|
|
64
|
+
{ value: 'past_30_days', label: '過去 30 天', group: 'past' },
|
|
65
|
+
{ value: 'past_7_days', label: '過去 7 天', group: 'past' },
|
|
66
|
+
{ value: 'last_month', label: '上月', group: 'past' },
|
|
67
|
+
{ value: 'last_week', label: '上週', group: 'past' },
|
|
68
|
+
{ value: 'yesterday', label: '昨天', group: 'past' },
|
|
69
|
+
// Current
|
|
70
|
+
{ value: 'today', label: '今天', group: 'current' },
|
|
71
|
+
{ value: 'this_week', label: '本週', group: 'current' },
|
|
72
|
+
{ value: 'this_month', label: '本月', group: 'current' },
|
|
73
|
+
// Future
|
|
74
|
+
{ value: 'tomorrow', label: '明天', group: 'future' },
|
|
75
|
+
{ value: 'next_week', label: '下週', group: 'future' },
|
|
76
|
+
{ value: 'next_month', label: '下月', group: 'future' },
|
|
77
|
+
{ value: 'next_7_days', label: '未來 7 天', group: 'future' },
|
|
78
|
+
{ value: 'next_30_days', label: '未來 30 天', group: 'future' },
|
|
79
|
+
] as const
|
|
80
|
+
|
|
81
|
+
// ── Per-columnType op definitions ───────────────────────────────────────
|
|
82
|
+
|
|
83
|
+
const STRING_OPS: OperatorSpec[] = [
|
|
84
|
+
{ op: 'contains', label: '包含', labelEn: 'contains', valueShape: 'text' },
|
|
85
|
+
{ op: 'does_not_contain', label: '不包含', labelEn: 'does not contain', valueShape: 'text' },
|
|
86
|
+
{ op: 'is', label: '等於', labelEn: 'is', valueShape: 'text' },
|
|
87
|
+
{ op: 'is_not', label: '不等於', labelEn: 'is not', valueShape: 'text' },
|
|
88
|
+
{ op: 'is_set', label: '已設定', labelEn: 'is set', valueShape: 'none' },
|
|
89
|
+
{ op: 'is_not_set', label: '未設定', labelEn: 'is not set', valueShape: 'none' },
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
const URL_OPS: OperatorSpec[] = [
|
|
93
|
+
{ op: 'contains', label: '包含', labelEn: 'contains', valueShape: 'text' },
|
|
94
|
+
{ op: 'does_not_contain', label: '不包含', labelEn: 'does not contain', valueShape: 'text' },
|
|
95
|
+
{ op: 'is', label: '等於', labelEn: 'is', valueShape: 'text' },
|
|
96
|
+
{ op: 'is_not', label: '不等於', labelEn: 'is not', valueShape: 'text' },
|
|
97
|
+
{ op: 'starts_with', label: '開頭為', labelEn: 'starts with', valueShape: 'text' },
|
|
98
|
+
{ op: 'ends_with', label: '結尾為', labelEn: 'ends with', valueShape: 'text' },
|
|
99
|
+
{ op: 'is_set', label: '已設定', labelEn: 'is set', valueShape: 'none' },
|
|
100
|
+
{ op: 'is_not_set', label: '未設定', labelEn: 'is not set', valueShape: 'none' },
|
|
101
|
+
]
|
|
102
|
+
|
|
103
|
+
const NUMBER_OPS: OperatorSpec[] = [
|
|
104
|
+
{ op: 'equals', label: '等於', labelEn: '=', valueShape: 'number' },
|
|
105
|
+
{ op: 'not_equals', label: '不等於', labelEn: '≠', valueShape: 'number' },
|
|
106
|
+
{ op: 'gt', label: '大於', labelEn: '>', valueShape: 'number' },
|
|
107
|
+
{ op: 'gte', label: '大於等於', labelEn: '≥', valueShape: 'number' },
|
|
108
|
+
{ op: 'lt', label: '小於', labelEn: '<', valueShape: 'number' },
|
|
109
|
+
{ op: 'lte', label: '小於等於', labelEn: '≤', valueShape: 'number' },
|
|
110
|
+
{ op: 'is_set', label: '已設定', labelEn: 'is set', valueShape: 'none' },
|
|
111
|
+
{ op: 'is_not_set', label: '未設定', labelEn: 'is not set', valueShape: 'none' },
|
|
112
|
+
]
|
|
113
|
+
|
|
114
|
+
const DATE_OPS: OperatorSpec[] = [
|
|
115
|
+
{ op: 'is', label: '是', labelEn: 'is', valueShape: 'date_single' },
|
|
116
|
+
{ op: 'is_before', label: '早於', labelEn: 'before', valueShape: 'date_single' },
|
|
117
|
+
{ op: 'is_after', label: '晚於', labelEn: 'after', valueShape: 'date_single' },
|
|
118
|
+
{ op: 'is_on_or_before', label: '不晚於', labelEn: 'on or before', valueShape: 'date_single' },
|
|
119
|
+
{ op: 'is_on_or_after', label: '不早於', labelEn: 'on or after', valueShape: 'date_single' },
|
|
120
|
+
{ op: 'is_between', label: '介於', labelEn: 'between', valueShape: 'date_range' },
|
|
121
|
+
{ op: 'is_relative', label: '相對', labelEn: 'relative', valueShape: 'date_relative' },
|
|
122
|
+
{ op: 'is_set', label: '已設定', labelEn: 'is set', valueShape: 'none' },
|
|
123
|
+
{ op: 'is_not_set', label: '未設定', labelEn: 'is not set', valueShape: 'none' },
|
|
124
|
+
]
|
|
125
|
+
|
|
126
|
+
const SELECT_OPS: OperatorSpec[] = [
|
|
127
|
+
{ op: 'is', label: '是', labelEn: 'is', valueShape: 'select_multi' },
|
|
128
|
+
{ op: 'is_not', label: '不是', labelEn: 'is not', valueShape: 'select_multi' },
|
|
129
|
+
{ op: 'is_set', label: '已設定', labelEn: 'is set', valueShape: 'none' },
|
|
130
|
+
{ op: 'is_not_set', label: '未設定', labelEn: 'is not set', valueShape: 'none' },
|
|
131
|
+
]
|
|
132
|
+
|
|
133
|
+
const MULTI_SELECT_OPS: OperatorSpec[] = [
|
|
134
|
+
{ op: 'has_any_of', label: '含其中之一', labelEn: 'Any', valueShape: 'select_multi' },
|
|
135
|
+
{ op: 'has_all_of', label: '全部包含', labelEn: 'All', valueShape: 'select_multi' },
|
|
136
|
+
{ op: 'has_none_of', label: '不含其中任一', labelEn: 'None of', valueShape: 'select_multi' },
|
|
137
|
+
{ op: 'is_set', label: '已設定', labelEn: 'is set', valueShape: 'none' },
|
|
138
|
+
{ op: 'is_not_set', label: '未設定', labelEn: 'is not set', valueShape: 'none' },
|
|
139
|
+
]
|
|
140
|
+
|
|
141
|
+
const PERSON_OPS: OperatorSpec[] = [
|
|
142
|
+
{ op: 'is', label: '是', labelEn: 'is', valueShape: 'person_multi' },
|
|
143
|
+
{ op: 'is_not', label: '不是', labelEn: 'is not', valueShape: 'person_multi' },
|
|
144
|
+
{ op: 'is_set', label: '已指派', labelEn: 'is set', valueShape: 'none' },
|
|
145
|
+
{ op: 'is_not_set', label: '未指派', labelEn: 'is not set', valueShape: 'none' },
|
|
146
|
+
]
|
|
147
|
+
|
|
148
|
+
const MULTI_PERSON_OPS: OperatorSpec[] = [
|
|
149
|
+
{ op: 'has_any_of', label: '含其中之一', labelEn: 'Any', valueShape: 'person_multi' },
|
|
150
|
+
{ op: 'has_all_of', label: '全部包含', labelEn: 'All', valueShape: 'person_multi' },
|
|
151
|
+
{ op: 'has_none_of', label: '不含其中任一', labelEn: 'None of', valueShape: 'person_multi' },
|
|
152
|
+
{ op: 'is_set', label: '已指派', labelEn: 'is set', valueShape: 'none' },
|
|
153
|
+
{ op: 'is_not_set', label: '未指派', labelEn: 'is not set', valueShape: 'none' },
|
|
154
|
+
]
|
|
155
|
+
|
|
156
|
+
const BOOLEAN_OPS: OperatorSpec[] = [
|
|
157
|
+
{ op: 'is_true', label: '是', labelEn: 'is checked', valueShape: 'none' },
|
|
158
|
+
{ op: 'is_false', label: '否', labelEn: 'is not checked', valueShape: 'none' },
|
|
159
|
+
]
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* SSOT canonical — 全部 51 ops, 9 columnTypes 對照表.
|
|
163
|
+
*
|
|
164
|
+
* **禁止繞過**:component 內勿 hardcode op 字串(M17 Rule-of-3 + Phase 5 Q3=c TypeScript-enforce 派)。
|
|
165
|
+
* `currency` 共用 `number` op set,渲染依 `column.meta.precision/prefix` 格式化。
|
|
166
|
+
*/
|
|
167
|
+
export const OPERATOR_REGISTRY: Record<ColumnType, OperatorSpec[]> = {
|
|
168
|
+
string: STRING_OPS,
|
|
169
|
+
url: URL_OPS,
|
|
170
|
+
number: NUMBER_OPS,
|
|
171
|
+
currency: NUMBER_OPS,
|
|
172
|
+
date: DATE_OPS,
|
|
173
|
+
// Phase C(2026-05-05):time column 暫先 reuse DATE_OPS shape — advanced filter `time_*`
|
|
174
|
+
// ValueShape 整合留 Phase D+。consumer 走基本 cell render(無 advanced filter)不影響。
|
|
175
|
+
time: DATE_OPS,
|
|
176
|
+
select: SELECT_OPS,
|
|
177
|
+
multiSelect: MULTI_SELECT_OPS,
|
|
178
|
+
person: PERSON_OPS,
|
|
179
|
+
multiPerson: MULTI_PERSON_OPS,
|
|
180
|
+
boolean: BOOLEAN_OPS,
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Default operator per columnType — 開新 condition 時的 op preset */
|
|
184
|
+
export const DEFAULT_OPERATOR: Record<ColumnType, string> = {
|
|
185
|
+
string: 'contains',
|
|
186
|
+
url: 'contains',
|
|
187
|
+
number: 'equals',
|
|
188
|
+
currency: 'equals',
|
|
189
|
+
date: 'is',
|
|
190
|
+
time: 'is',
|
|
191
|
+
select: 'is',
|
|
192
|
+
multiSelect: 'has_any_of',
|
|
193
|
+
person: 'is',
|
|
194
|
+
multiPerson: 'has_any_of',
|
|
195
|
+
boolean: 'is_true',
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// ── Helpers ─────────────────────────────────────────────────────────────
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Resolve ValueShape for a given op + columnType + includeTime flag.
|
|
202
|
+
*
|
|
203
|
+
* `date_single` / `date_range` shapes auto-promote to `datetime_single` /
|
|
204
|
+
* `datetime_range` when columnType=`date` && includeTime=true. All other
|
|
205
|
+
* shapes pass-through unchanged.
|
|
206
|
+
*
|
|
207
|
+
* `date_relative` stays as-is(relative 本質 day-level,e.g. `today` =
|
|
208
|
+
* 從今天 00:00 到 23:59:59,跟 includeTime 無關)。
|
|
209
|
+
*/
|
|
210
|
+
export function getValueShape(
|
|
211
|
+
op: OperatorSpec,
|
|
212
|
+
columnType: ColumnType,
|
|
213
|
+
includeTime?: boolean,
|
|
214
|
+
): ValueShape {
|
|
215
|
+
if (columnType === 'date' && includeTime) {
|
|
216
|
+
if (op.valueShape === 'date_single') return 'datetime_single'
|
|
217
|
+
if (op.valueShape === 'date_range') return 'datetime_range'
|
|
218
|
+
}
|
|
219
|
+
return op.valueShape
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** Get OperatorSpec by columnType + op key. Returns null if not found. */
|
|
223
|
+
export function getOperatorSpec(columnType: ColumnType, op: string): OperatorSpec | null {
|
|
224
|
+
return OPERATOR_REGISTRY[columnType]?.find((o) => o.op === op) ?? null
|
|
225
|
+
}
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* filter-tree.ts — FilterTree types + helpers + evaluator
|
|
3
|
+
*
|
|
4
|
+
* 抽自 data-table-filter-panel.tsx 為了 file-size budget(panel 拆分)。
|
|
5
|
+
* 純資料結構 + pure functions,無 UI dep。
|
|
6
|
+
*
|
|
7
|
+
* 詳:./advanced-filter.draft.md
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
startOfDay, endOfDay,
|
|
12
|
+
startOfWeek, endOfWeek,
|
|
13
|
+
startOfMonth, endOfMonth,
|
|
14
|
+
addDays, addWeeks, addMonths, subDays,
|
|
15
|
+
} from 'date-fns'
|
|
16
|
+
import { OPERATOR_REGISTRY, type OperatorSpec } from './filter-operators'
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 把 relative key 轉成 [start, end] 時間區間(local time,inclusive)。
|
|
20
|
+
* 對齊 Notion / ClickUp idiom — 以「今天」為錨點計算。
|
|
21
|
+
*/
|
|
22
|
+
function relativeKeyToRange(key: string, now: Date = new Date()): [number, number] | null {
|
|
23
|
+
const today = startOfDay(now)
|
|
24
|
+
switch (key) {
|
|
25
|
+
case 'today': return [today.getTime(), endOfDay(now).getTime()]
|
|
26
|
+
case 'yesterday': return [startOfDay(subDays(now, 1)).getTime(), endOfDay(subDays(now, 1)).getTime()]
|
|
27
|
+
case 'tomorrow': return [startOfDay(addDays(now, 1)).getTime(), endOfDay(addDays(now, 1)).getTime()]
|
|
28
|
+
case 'this_week': return [startOfWeek(now, { weekStartsOn: 1 }).getTime(), endOfWeek(now, { weekStartsOn: 1 }).getTime()]
|
|
29
|
+
case 'last_week': return [startOfWeek(addWeeks(now, -1), { weekStartsOn: 1 }).getTime(), endOfWeek(addWeeks(now, -1), { weekStartsOn: 1 }).getTime()]
|
|
30
|
+
case 'next_week': return [startOfWeek(addWeeks(now, 1), { weekStartsOn: 1 }).getTime(), endOfWeek(addWeeks(now, 1), { weekStartsOn: 1 }).getTime()]
|
|
31
|
+
case 'this_month': return [startOfMonth(now).getTime(), endOfMonth(now).getTime()]
|
|
32
|
+
case 'last_month': return [startOfMonth(addMonths(now, -1)).getTime(), endOfMonth(addMonths(now, -1)).getTime()]
|
|
33
|
+
case 'next_month': return [startOfMonth(addMonths(now, 1)).getTime(), endOfMonth(addMonths(now, 1)).getTime()]
|
|
34
|
+
case 'past_7_days': return [startOfDay(subDays(now, 7)).getTime(), endOfDay(now).getTime()]
|
|
35
|
+
case 'past_30_days': return [startOfDay(subDays(now, 30)).getTime(), endOfDay(now).getTime()]
|
|
36
|
+
case 'next_7_days': return [startOfDay(now).getTime(), endOfDay(addDays(now, 7)).getTime()]
|
|
37
|
+
case 'next_30_days': return [startOfDay(now).getTime(), endOfDay(addDays(now, 30)).getTime()]
|
|
38
|
+
default: return null
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ── Types ────────────────────────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
export type Conjunction = 'and' | 'or'
|
|
45
|
+
|
|
46
|
+
export interface FilterCondition {
|
|
47
|
+
kind: 'cond'
|
|
48
|
+
/** 唯一 row id(stable across renders) */
|
|
49
|
+
id: string
|
|
50
|
+
/** 對應 column id;空字串 = 尚未選 field(operator/value picker disabled) */
|
|
51
|
+
field: string
|
|
52
|
+
/** OperatorSpec.op key — 對齊 OPERATOR_REGISTRY */
|
|
53
|
+
op: string
|
|
54
|
+
/** 依 ValueShape 解讀的值 */
|
|
55
|
+
value: unknown
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface FilterGroup {
|
|
59
|
+
kind: 'group'
|
|
60
|
+
id: string
|
|
61
|
+
/** group 內 children 共用的 join(全 AND 或全 OR;不允許混) */
|
|
62
|
+
conjunction: Conjunction
|
|
63
|
+
/** ⚠️ 型別鎖死:children 只能 condition,不可再 nested group(1-level cap) */
|
|
64
|
+
children: FilterCondition[]
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export type FilterTreeFlat = {
|
|
68
|
+
mode: 'flat'
|
|
69
|
+
conjunction: Conjunction
|
|
70
|
+
children: FilterCondition[]
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export type FilterTreeNested = {
|
|
74
|
+
mode: 'nested'
|
|
75
|
+
conjunction: Conjunction
|
|
76
|
+
children: FilterGroup[]
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export type FilterTree = FilterTreeFlat | FilterTreeNested
|
|
80
|
+
|
|
81
|
+
// ── Helpers — public API ────────────────────────────────────────────────
|
|
82
|
+
|
|
83
|
+
/** Empty FilterTree — consumer 用來 init useState */
|
|
84
|
+
export function createEmptyFilterTree(mode: 'flat'): FilterTreeFlat
|
|
85
|
+
export function createEmptyFilterTree(mode: 'nested'): FilterTreeNested
|
|
86
|
+
export function createEmptyFilterTree(mode: 'flat' | 'nested'): FilterTree {
|
|
87
|
+
if (mode === 'flat') return { mode: 'flat', conjunction: 'and', children: [] }
|
|
88
|
+
return { mode: 'nested', conjunction: 'and', children: [] }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** 是否有任何 active filter — DataTable trigger button checked state 用 */
|
|
92
|
+
export function isFilterTreeActive(tree: FilterTree): boolean {
|
|
93
|
+
if (tree.mode === 'flat') {
|
|
94
|
+
return tree.children.some((c) => isConditionComplete(c))
|
|
95
|
+
}
|
|
96
|
+
return tree.children.some((g) => g.children.some((c) => isConditionComplete(c)))
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Condition 是否「已填齊」可參與 filter 求值 */
|
|
100
|
+
function isConditionComplete(c: FilterCondition): boolean {
|
|
101
|
+
if (!c.field || !c.op) return false
|
|
102
|
+
const spec = getOperatorSpecLoose(c.op)
|
|
103
|
+
if (!spec) return false
|
|
104
|
+
if (spec.valueShape === 'none') return true
|
|
105
|
+
return c.value !== '' && c.value !== null && c.value !== undefined
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** loose lookup — 我們不知 columnType,試所有 type */
|
|
109
|
+
function getOperatorSpecLoose(op: string): OperatorSpec | null {
|
|
110
|
+
for (const list of Object.values(OPERATOR_REGISTRY)) {
|
|
111
|
+
const found = list.find((o) => o.op === op)
|
|
112
|
+
if (found) return found
|
|
113
|
+
}
|
|
114
|
+
return null
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ── Deep-equal compare(refresh icon 顯示判定用) ─────────────────────────
|
|
118
|
+
|
|
119
|
+
/** Compare two FilterTrees structurally — 忽略內部 row id(refresh-detect 不該被內部 id 干擾)*/
|
|
120
|
+
export function isFilterTreeEqual(a: FilterTree, b: FilterTree): boolean {
|
|
121
|
+
if (a.mode !== b.mode || a.conjunction !== b.conjunction) return false
|
|
122
|
+
if (a.children.length !== b.children.length) return false
|
|
123
|
+
if (a.mode === 'flat' && b.mode === 'flat') {
|
|
124
|
+
return a.children.every((c, i) => isConditionEqual(c, b.children[i]))
|
|
125
|
+
}
|
|
126
|
+
if (a.mode === 'nested' && b.mode === 'nested') {
|
|
127
|
+
return a.children.every((g, i) => isGroupEqual(g, b.children[i]))
|
|
128
|
+
}
|
|
129
|
+
return false
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function isGroupEqual(a: FilterGroup, b: FilterGroup): boolean {
|
|
133
|
+
if (a.conjunction !== b.conjunction) return false
|
|
134
|
+
if (a.children.length !== b.children.length) return false
|
|
135
|
+
return a.children.every((c, i) => isConditionEqual(c, b.children[i]))
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function isConditionEqual(a: FilterCondition, b: FilterCondition): boolean {
|
|
139
|
+
if (a.field !== b.field || a.op !== b.op) return false
|
|
140
|
+
return JSON.stringify(a.value) === JSON.stringify(b.value)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ── Tree Evaluation(globalFilter approach)──────────────────────────────
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* 整棵 FilterTree 對 row 求 boolean。
|
|
147
|
+
* 配合 useReactTable `globalFilterFn`:
|
|
148
|
+
*
|
|
149
|
+
* const tree = useState<FilterTree>(createEmptyFilterTree('flat'))
|
|
150
|
+
* useReactTable({
|
|
151
|
+
* state: { globalFilter: tree },
|
|
152
|
+
* onGlobalFilterChange: setTree,
|
|
153
|
+
* globalFilterFn: (row, _, t: FilterTree) => evaluateTree(t, row.original),
|
|
154
|
+
* getFilteredRowModel: getFilteredRowModel(),
|
|
155
|
+
* })
|
|
156
|
+
*/
|
|
157
|
+
// any-allow: row-generic — TanStack row.original 是 generic,filter eval 跨 type 必走 any
|
|
158
|
+
export function evaluateTree(tree: FilterTree, row: any): boolean {
|
|
159
|
+
if (tree.children.length === 0) return true
|
|
160
|
+
|
|
161
|
+
if (tree.mode === 'flat') {
|
|
162
|
+
const completed = tree.children.filter(isConditionComplete)
|
|
163
|
+
if (completed.length === 0) return true
|
|
164
|
+
const results = completed.map((c) => evaluateCondition(c, row))
|
|
165
|
+
return tree.conjunction === 'and' ? results.every(Boolean) : results.some(Boolean)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// nested
|
|
169
|
+
const groupResults = tree.children.map((g) => evaluateGroup(g, row))
|
|
170
|
+
// group 沒任何 complete condition 的視為 pass-through(true)
|
|
171
|
+
const meaningful = groupResults.filter((_, i) => tree.children[i].children.some(isConditionComplete))
|
|
172
|
+
if (meaningful.length === 0) return true
|
|
173
|
+
return tree.conjunction === 'and' ? meaningful.every(Boolean) : meaningful.some(Boolean)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// any-allow: row-generic
|
|
177
|
+
function evaluateGroup(group: FilterGroup, row: any): boolean {
|
|
178
|
+
const completed = group.children.filter(isConditionComplete)
|
|
179
|
+
if (completed.length === 0) return true
|
|
180
|
+
const results = completed.map((c) => evaluateCondition(c, row))
|
|
181
|
+
return group.conjunction === 'and' ? results.every(Boolean) : results.some(Boolean)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// any-allow: row-generic
|
|
185
|
+
function evaluateCondition(cond: FilterCondition, row: any): boolean {
|
|
186
|
+
if (!cond.field || !cond.op) return true
|
|
187
|
+
const cellValue = row?.[cond.field]
|
|
188
|
+
return matchOperator(cond.op, cellValue, cond.value)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// PersonValue identity helper(2026-05-07):
|
|
192
|
+
// person/multiPerson cell value 是 `{ name, avatarUrl?, description? }` 物件 — 比對時用 `name`
|
|
193
|
+
// 當 stable id(PeoplePicker SSOT 沒有 id field,name 是唯一身分標識)。對齊 Notion / Linear /
|
|
194
|
+
// Asana 的 person filter 比對 idiom(都用 name/email 當 id)。
|
|
195
|
+
function personId(v: unknown): string {
|
|
196
|
+
if (v && typeof v === 'object' && 'name' in v) return String((v as { name: unknown }).name ?? '')
|
|
197
|
+
return String(v ?? '')
|
|
198
|
+
}
|
|
199
|
+
function isPersonObject(v: unknown): v is { name: string } {
|
|
200
|
+
return !!v && typeof v === 'object' && 'name' in v
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// code-quality-allow: long-function — 13-operator switch dispatch table,table-driven 重構會把 op-specific guards 拆出反增 indirection
|
|
204
|
+
function matchOperator(op: string, cellValue: unknown, filterValue: unknown): boolean {
|
|
205
|
+
// 不需 value 的 op
|
|
206
|
+
switch (op) {
|
|
207
|
+
case 'is_set': return cellValue !== null && cellValue !== undefined && cellValue !== ''
|
|
208
|
+
case 'is_not_set': return cellValue === null || cellValue === undefined || cellValue === ''
|
|
209
|
+
case 'is_true': return cellValue === true
|
|
210
|
+
case 'is_false': return cellValue === false
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// 需 value 但 value 空 → 視為 incomplete,pass-through
|
|
214
|
+
if (filterValue === null || filterValue === undefined || filterValue === '') return true
|
|
215
|
+
if (Array.isArray(filterValue) && filterValue.length === 0) return true
|
|
216
|
+
|
|
217
|
+
// Person-aware specialization(person_single / person_multi)— 物件透過 name 比對。
|
|
218
|
+
// 偵測:cellValue 或 filterValue 任一是 Person object(或包 Person 的陣列)→ 走 personId path。
|
|
219
|
+
const cellIsPerson = isPersonObject(cellValue) || (Array.isArray(cellValue) && cellValue.some(isPersonObject))
|
|
220
|
+
const filterIsPerson = isPersonObject(filterValue) || (Array.isArray(filterValue) && filterValue.some(isPersonObject))
|
|
221
|
+
if (cellIsPerson || filterIsPerson) {
|
|
222
|
+
const cellIds = Array.isArray(cellValue) ? cellValue.map(personId) : [personId(cellValue)]
|
|
223
|
+
const filterIds = Array.isArray(filterValue) ? filterValue.map(personId) : [personId(filterValue)]
|
|
224
|
+
switch (op) {
|
|
225
|
+
case 'is': return cellIds.length === 1 && filterIds.length >= 1 && filterIds.includes(cellIds[0])
|
|
226
|
+
case 'is_not': return !(cellIds.length === 1 && filterIds.length >= 1 && filterIds.includes(cellIds[0]))
|
|
227
|
+
case 'has_any_of': return cellIds.some((c) => filterIds.includes(c))
|
|
228
|
+
case 'has_all_of': return filterIds.every((f) => cellIds.includes(f))
|
|
229
|
+
case 'has_none_of':return !cellIds.some((c) => filterIds.includes(c))
|
|
230
|
+
default: return true
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
switch (op) {
|
|
235
|
+
case 'contains': return String(cellValue ?? '').toLowerCase().includes(String(filterValue).toLowerCase())
|
|
236
|
+
case 'does_not_contain': return !String(cellValue ?? '').toLowerCase().includes(String(filterValue).toLowerCase())
|
|
237
|
+
// 2026-05-12 Round 6 fix(user 抓 Roadmap 進階篩選「全部」結果空)— impl-vs-spec drift。
|
|
238
|
+
// Per `advanced-filter-operators.draft.md` L116「is 直接接受多值,不另設 is_any_of」+ L103-116
|
|
239
|
+
// select(is/is_not + select_multi ValueShape OR 語意)+ L31-32(select.is / person.is 都走
|
|
240
|
+
// select_multi / person_multi ValueShape)。原 impl 只做 single-value 比對,filterValue 是
|
|
241
|
+
// array(全選 options)→ String(array)= "v1,v2" → 永遠 != single cellValue → 結果空。
|
|
242
|
+
// Fix:加 array handling,走 OR 語意:any → match;is_not → every-not-match。
|
|
243
|
+
case 'is':
|
|
244
|
+
if (Array.isArray(filterValue))
|
|
245
|
+
return filterValue.some((v) => String(cellValue ?? '').toLowerCase() === String(v).toLowerCase())
|
|
246
|
+
return String(cellValue ?? '').toLowerCase() === String(filterValue).toLowerCase()
|
|
247
|
+
case 'is_not':
|
|
248
|
+
if (Array.isArray(filterValue))
|
|
249
|
+
return filterValue.every((v) => String(cellValue ?? '').toLowerCase() !== String(v).toLowerCase())
|
|
250
|
+
return String(cellValue ?? '').toLowerCase() !== String(filterValue).toLowerCase()
|
|
251
|
+
case 'starts_with': return String(cellValue ?? '').toLowerCase().startsWith(String(filterValue).toLowerCase())
|
|
252
|
+
case 'ends_with': return String(cellValue ?? '').toLowerCase().endsWith(String(filterValue).toLowerCase())
|
|
253
|
+
|
|
254
|
+
case 'equals': return Number(cellValue) === Number(filterValue)
|
|
255
|
+
case 'not_equals': return Number(cellValue) !== Number(filterValue)
|
|
256
|
+
case 'gt': return Number(cellValue) > Number(filterValue)
|
|
257
|
+
case 'gte': return Number(cellValue) >= Number(filterValue)
|
|
258
|
+
case 'lt': return Number(cellValue) < Number(filterValue)
|
|
259
|
+
case 'lte': return Number(cellValue) <= Number(filterValue)
|
|
260
|
+
|
|
261
|
+
case 'is_before': return new Date(String(cellValue)).getTime() < new Date(String(filterValue)).getTime()
|
|
262
|
+
case 'is_after': return new Date(String(cellValue)).getTime() > new Date(String(filterValue)).getTime()
|
|
263
|
+
case 'is_on_or_before': return new Date(String(cellValue)).getTime() <= new Date(String(filterValue)).getTime()
|
|
264
|
+
case 'is_on_or_after': return new Date(String(cellValue)).getTime() >= new Date(String(filterValue)).getTime()
|
|
265
|
+
case 'is_between': {
|
|
266
|
+
if (!Array.isArray(filterValue) || filterValue.length !== 2) return true
|
|
267
|
+
const cv = new Date(String(cellValue)).getTime()
|
|
268
|
+
const start = filterValue[0] ? new Date(String(filterValue[0])).getTime() : -Infinity
|
|
269
|
+
const end = filterValue[1] ? new Date(String(filterValue[1])).getTime() : Infinity
|
|
270
|
+
return cv >= start && cv <= end
|
|
271
|
+
}
|
|
272
|
+
case 'is_relative': {
|
|
273
|
+
// Phase D 完整實作 — relative key → time range → in-range test
|
|
274
|
+
const range = relativeKeyToRange(String(filterValue))
|
|
275
|
+
if (!range) return true // unknown key,pass-through
|
|
276
|
+
const cv = new Date(String(cellValue)).getTime()
|
|
277
|
+
if (Number.isNaN(cv)) return false // invalid cellValue → 不命中
|
|
278
|
+
const [start, end] = range
|
|
279
|
+
return cv >= start && cv <= end
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
case 'has_any_of': {
|
|
283
|
+
if (!Array.isArray(filterValue)) return true
|
|
284
|
+
if (Array.isArray(cellValue)) return cellValue.some((c) => filterValue.includes(c))
|
|
285
|
+
return filterValue.includes(cellValue)
|
|
286
|
+
}
|
|
287
|
+
case 'has_all_of': {
|
|
288
|
+
if (!Array.isArray(filterValue)) return true
|
|
289
|
+
if (Array.isArray(cellValue)) return filterValue.every((v) => cellValue.includes(v))
|
|
290
|
+
return false
|
|
291
|
+
}
|
|
292
|
+
case 'has_none_of': {
|
|
293
|
+
if (!Array.isArray(filterValue)) return true
|
|
294
|
+
if (Array.isArray(cellValue)) return !cellValue.some((c) => filterValue.includes(c))
|
|
295
|
+
return !filterValue.includes(cellValue)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
default: return true
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* @deprecated v0.x — old per-column filterFn integration。
|
|
304
|
+
* 新版用 `evaluateTree` 配 TanStack `globalFilter`。
|
|
305
|
+
* 保留為了過渡期 backward-compat,新 consumer 不要用。
|
|
306
|
+
*/
|
|
307
|
+
export function dataTableFilterMatch(cellValue: unknown, filterValue: unknown): boolean {
|
|
308
|
+
if (typeof filterValue === 'object' && filterValue !== null && 'operator' in filterValue && 'value' in filterValue) {
|
|
309
|
+
const fv = filterValue as { operator: string; value: unknown }
|
|
310
|
+
return matchOperator(fv.operator, cellValue, fv.value)
|
|
311
|
+
}
|
|
312
|
+
return String(cellValue ?? '').toLowerCase().includes(String(filterValue ?? '').toLowerCase())
|
|
313
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* column-meta — TanStack ColumnDef typed accessor helpers.
|
|
3
|
+
*
|
|
4
|
+
* **Scope:Internal-only — DataTable 自用,非 public API**(本檔不 re-export 出
|
|
5
|
+
* `data-table-filter-panel.tsx` / `data-table.tsx`,consumer 透過 panel / sort-manager
|
|
6
|
+
* 間接使用)。M21 (a) classification:lib/ folder 慣例 = internal helper。
|
|
7
|
+
*
|
|
8
|
+
* 為什麼存在:`ColumnDef<T, V>` 是 discriminated union(AccessorKey / AccessorFn /
|
|
9
|
+
* Display / Group),每個 variant 有不同欄位,直接 `col.id` 在型別上不安全。
|
|
10
|
+
* 既有 consumer 用 `(col as any).id` cast,3+ 處 hard-code = M17 違反。
|
|
11
|
+
* 抽成 typed helper 統一型別 narrowing,消除 `any` cast。
|
|
12
|
+
*
|
|
13
|
+
* Why not annotate each `as any`:DRY + single SSOT for column field access。
|
|
14
|
+
* 任一 helper 行為變更(e.g. 加 fallback 邏輯)只需改一處。
|
|
15
|
+
*
|
|
16
|
+
* 對齊 mindset #2 「優先消費既有」+ M17「同 hard-code 在 3+ consumer 必抽」。
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import type { ColumnDef, ColumnMeta, RowData } from '@tanstack/react-table'
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 取 column 的 stable identifier。
|
|
23
|
+
*
|
|
24
|
+
* 解析順序對齊 TanStack 內部行為:
|
|
25
|
+
* 1. `col.id`(顯式設定優先)
|
|
26
|
+
* 2. `col.accessorKey`(string version,fallback for accessor columns)
|
|
27
|
+
*
|
|
28
|
+
* 都無 → return undefined(display column 無 accessorKey 且未設 id)。
|
|
29
|
+
*/
|
|
30
|
+
export function getColumnId<T extends RowData>(
|
|
31
|
+
col: ColumnDef<T, unknown>,
|
|
32
|
+
): string | undefined {
|
|
33
|
+
if ('id' in col && typeof col.id === 'string' && col.id) return col.id
|
|
34
|
+
if (
|
|
35
|
+
'accessorKey' in col &&
|
|
36
|
+
(typeof col.accessorKey === 'string' || typeof col.accessorKey === 'number')
|
|
37
|
+
) {
|
|
38
|
+
return String(col.accessorKey)
|
|
39
|
+
}
|
|
40
|
+
return undefined
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 取 column 的 header 定義(可為 string / React node / function — TanStack 規約)。
|
|
45
|
+
*
|
|
46
|
+
* 注意:本 helper 不執行 header function。consumer 若需 rendered string,
|
|
47
|
+
* 自行 narrow + call,或用 TanStack `flexRender` API。
|
|
48
|
+
*/
|
|
49
|
+
export function getColumnHeader<T extends RowData>(
|
|
50
|
+
col: ColumnDef<T, unknown>,
|
|
51
|
+
): ColumnDef<T, unknown>['header'] {
|
|
52
|
+
return 'header' in col ? col.header : undefined
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 取 column 的 meta(本 DS 的 ColumnMeta 已 extend 加上 type / align / wrap 等)。
|
|
57
|
+
*/
|
|
58
|
+
export function getColumnMeta<T extends RowData>(
|
|
59
|
+
col: ColumnDef<T, unknown>,
|
|
60
|
+
): ColumnMeta<T, unknown> | undefined {
|
|
61
|
+
return col.meta
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 從 header 定義抽 plain string label(filter / sort UI 顯示用)。
|
|
66
|
+
*
|
|
67
|
+
* 處理 3 種 header 形式:
|
|
68
|
+
* - string → 直接 return
|
|
69
|
+
* - function → 不執行(避過 React render context),fallback id
|
|
70
|
+
* - undefined / 其他 → fallback id
|
|
71
|
+
*/
|
|
72
|
+
export function getColumnLabel<T extends RowData>(
|
|
73
|
+
col: ColumnDef<T, unknown>,
|
|
74
|
+
fallbackId?: string,
|
|
75
|
+
): string {
|
|
76
|
+
const header = getColumnHeader(col)
|
|
77
|
+
if (typeof header === 'string') return header
|
|
78
|
+
return fallbackId ?? getColumnId(col) ?? ''
|
|
79
|
+
}
|