@qijenchen/design-system 0.1.0-beta.145 → 0.1.0-beta.147
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/Accordion/accordion.d.ts +1 -1
- package/dist/components/Accordion/accordion.d.ts.map +1 -1
- package/dist/components/Accordion/accordion.js +8 -3
- package/dist/components/Accordion/accordion.js.map +1 -1
- package/dist/components/AccountMenu/account-menu.d.ts.map +1 -1
- package/dist/components/AccountMenu/account-menu.js.map +1 -1
- package/dist/components/AgentPanel/agent-panel-fab.d.ts +7 -1
- package/dist/components/AgentPanel/agent-panel-fab.d.ts.map +1 -1
- package/dist/components/AgentPanel/agent-panel-fab.js +36 -14
- package/dist/components/AgentPanel/agent-panel-fab.js.map +1 -1
- package/dist/components/AgentPanel/agent-panel-logo.d.ts +3 -3
- package/dist/components/AgentPanel/agent-panel-logo.js.map +1 -1
- package/dist/components/AgentPanel/agent-panel.d.ts +1 -1
- package/dist/components/AgentPanel/agent-panel.d.ts.map +1 -1
- package/dist/components/AgentPanel/agent-panel.js +28 -22
- package/dist/components/AgentPanel/agent-panel.js.map +1 -1
- package/dist/components/AppShell/app-shell.d.ts +1 -1
- package/dist/components/AppShell/app-shell.js +1 -1
- package/dist/components/AppShell/app-shell.js.map +1 -1
- package/dist/components/Avatar/avatar.d.ts +95 -1
- package/dist/components/Avatar/avatar.d.ts.map +1 -1
- package/dist/components/Avatar/avatar.js +106 -15
- package/dist/components/Avatar/avatar.js.map +1 -1
- package/dist/components/Avatar/index.js +14 -2
- package/dist/components/Breadcrumb/breadcrumb.d.ts +4 -2
- package/dist/components/Breadcrumb/breadcrumb.d.ts.map +1 -1
- package/dist/components/Breadcrumb/breadcrumb.js +2 -2
- package/dist/components/Breadcrumb/breadcrumb.js.map +1 -1
- package/dist/components/BulkActionBar/bulk-action-bar.d.ts.map +1 -1
- package/dist/components/BulkActionBar/bulk-action-bar.js +27 -2
- package/dist/components/BulkActionBar/bulk-action-bar.js.map +1 -1
- package/dist/components/Button/button.d.ts +2 -2
- package/dist/components/Button/button.d.ts.map +1 -1
- package/dist/components/Button/button.js +118 -22
- package/dist/components/Button/button.js.map +1 -1
- package/dist/components/Calendar/calendar.d.ts +34 -10
- package/dist/components/Calendar/calendar.d.ts.map +1 -1
- package/dist/components/Calendar/calendar.js +210 -36
- package/dist/components/Calendar/calendar.js.map +1 -1
- package/dist/components/Carousel/carousel.d.ts +1 -1
- package/dist/components/Carousel/carousel.d.ts.map +1 -1
- package/dist/components/Carousel/carousel.js +3 -2
- package/dist/components/Carousel/carousel.js.map +1 -1
- package/dist/components/Chart/chart.d.ts +1 -0
- package/dist/components/Chart/chart.d.ts.map +1 -1
- package/dist/components/Chart/chart.js +8 -0
- package/dist/components/Chart/chart.js.map +1 -1
- package/dist/components/Checkbox/checkbox.d.ts +1 -1
- package/dist/components/Checkbox/checkbox.d.ts.map +1 -1
- package/dist/components/Checkbox/checkbox.js +10 -2
- package/dist/components/Checkbox/checkbox.js.map +1 -1
- package/dist/components/Chip/chip.d.ts +1 -1
- package/dist/components/Chip/chip.d.ts.map +1 -1
- package/dist/components/Chip/chip.js +4 -2
- package/dist/components/Chip/chip.js.map +1 -1
- package/dist/components/Combobox/combobox.d.ts +1 -1
- package/dist/components/Combobox/combobox.d.ts.map +1 -1
- package/dist/components/Combobox/combobox.js +37 -11
- package/dist/components/Combobox/combobox.js.map +1 -1
- package/dist/components/Command/command.d.ts +1 -1
- package/dist/components/Command/command.d.ts.map +1 -1
- package/dist/components/Command/command.js +106 -8
- package/dist/components/Command/command.js.map +1 -1
- package/dist/components/DataTable/cell-registry.d.ts.map +1 -1
- package/dist/components/DataTable/cell-registry.js +3 -2
- package/dist/components/DataTable/cell-registry.js.map +1 -1
- package/dist/components/DataTable/data-table-column-visibility-panel.d.ts.map +1 -1
- package/dist/components/DataTable/data-table-column-visibility-panel.js +24 -24
- package/dist/components/DataTable/data-table-column-visibility-panel.js.map +1 -1
- package/dist/components/DataTable/data-table-interaction-layer.d.ts +2 -1
- package/dist/components/DataTable/data-table-interaction-layer.d.ts.map +1 -1
- package/dist/components/DataTable/data-table-interaction-layer.js +3 -4
- package/dist/components/DataTable/data-table-interaction-layer.js.map +1 -1
- package/dist/components/DataTable/data-table-sort-manager.d.ts.map +1 -1
- package/dist/components/DataTable/data-table-sort-manager.js +20 -20
- package/dist/components/DataTable/data-table-sort-manager.js.map +1 -1
- package/dist/components/DataTable/data-table.d.ts +8 -5
- package/dist/components/DataTable/data-table.d.ts.map +1 -1
- package/dist/components/DataTable/data-table.js +197 -71
- package/dist/components/DataTable/data-table.js.map +1 -1
- package/dist/components/DateGrid/date-grid.d.ts +1 -1
- package/dist/components/DateGrid/date-grid.d.ts.map +1 -1
- package/dist/components/DateGrid/date-grid.js +358 -225
- package/dist/components/DateGrid/date-grid.js.map +1 -1
- package/dist/components/DatePicker/date-picker.d.ts.map +1 -1
- package/dist/components/DatePicker/date-picker.js +16 -1
- package/dist/components/DatePicker/date-picker.js.map +1 -1
- package/dist/components/Dialog/dialog.d.ts.map +1 -1
- package/dist/components/Dialog/dialog.js +55 -43
- package/dist/components/Dialog/dialog.js.map +1 -1
- package/dist/components/DropdownMenu/dropdown-menu-keyboard.d.ts +86 -0
- package/dist/components/DropdownMenu/dropdown-menu-keyboard.d.ts.map +1 -0
- package/dist/components/DropdownMenu/dropdown-menu-keyboard.js +172 -0
- package/dist/components/DropdownMenu/dropdown-menu-keyboard.js.map +1 -0
- package/dist/components/DropdownMenu/dropdown-menu.d.ts +6 -1
- package/dist/components/DropdownMenu/dropdown-menu.d.ts.map +1 -1
- package/dist/components/DropdownMenu/dropdown-menu.js +50 -39
- package/dist/components/DropdownMenu/dropdown-menu.js.map +1 -1
- package/dist/components/Field/field-context.d.ts +4 -0
- package/dist/components/Field/field-context.d.ts.map +1 -1
- package/dist/components/Field/field-context.js +1 -2
- package/dist/components/Field/field-context.js.map +1 -1
- package/dist/components/Field/field-edit-keys.d.ts +8 -0
- package/dist/components/Field/field-edit-keys.d.ts.map +1 -1
- package/dist/components/Field/field-edit-keys.js +3 -0
- package/dist/components/Field/field-edit-keys.js.map +1 -1
- package/dist/components/Field/field-wrapper.d.ts +18 -1
- package/dist/components/Field/field-wrapper.d.ts.map +1 -1
- package/dist/components/Field/field-wrapper.js +39 -10
- package/dist/components/Field/field-wrapper.js.map +1 -1
- package/dist/components/Field/field.d.ts +3 -2
- package/dist/components/Field/field.d.ts.map +1 -1
- package/dist/components/Field/field.js +2 -1
- package/dist/components/Field/field.js.map +1 -1
- package/dist/components/FileItem/file-item.d.ts +26 -2
- package/dist/components/FileItem/file-item.d.ts.map +1 -1
- package/dist/components/FileItem/file-item.js +82 -25
- package/dist/components/FileItem/file-item.js.map +1 -1
- package/dist/components/FileItem/index.js +4 -2
- package/dist/components/FileUpload/file-upload.d.ts +1 -1
- package/dist/components/FileUpload/file-upload.d.ts.map +1 -1
- package/dist/components/FileUpload/file-upload.js +132 -55
- package/dist/components/FileUpload/file-upload.js.map +1 -1
- package/dist/components/FileViewer/file-viewer.d.ts +1 -1
- package/dist/components/FileViewer/file-viewer.d.ts.map +1 -1
- package/dist/components/FileViewer/file-viewer.js +9 -4
- package/dist/components/FileViewer/file-viewer.js.map +1 -1
- package/dist/components/InlineEdit/inline-edit.d.ts.map +1 -1
- package/dist/components/InlineEdit/inline-edit.js +1 -1
- package/dist/components/InlineEdit/inline-edit.js.map +1 -1
- package/dist/components/Input/input.d.ts.map +1 -1
- package/dist/components/Input/input.js +6 -1
- package/dist/components/Input/input.js.map +1 -1
- package/dist/components/LinkInput/link-input.d.ts.map +1 -1
- package/dist/components/LinkInput/link-input.js +20 -4
- package/dist/components/LinkInput/link-input.js.map +1 -1
- package/dist/components/Notice/notice.js +1 -1
- package/dist/components/Notice/notice.js.map +1 -1
- package/dist/components/NumberInput/number-input.d.ts.map +1 -1
- package/dist/components/NumberInput/number-input.js +6 -1
- package/dist/components/NumberInput/number-input.js.map +1 -1
- package/dist/components/OverflowIndicator/index.js +2 -1
- package/dist/components/OverflowIndicator/overflow-indicator.d.ts +19 -1
- package/dist/components/OverflowIndicator/overflow-indicator.d.ts.map +1 -1
- package/dist/components/OverflowIndicator/overflow-indicator.js +47 -27
- package/dist/components/OverflowIndicator/overflow-indicator.js.map +1 -1
- package/dist/components/Pagination/pagination.d.ts +1 -1
- package/dist/components/Pagination/pagination.js +1 -1
- package/dist/components/Pagination/pagination.js.map +1 -1
- package/dist/components/PeoplePicker/avatar-stack-overflow.d.ts +7 -6
- package/dist/components/PeoplePicker/avatar-stack-overflow.d.ts.map +1 -1
- package/dist/components/PeoplePicker/avatar-stack-overflow.js +6 -7
- package/dist/components/PeoplePicker/avatar-stack-overflow.js.map +1 -1
- package/dist/components/PeoplePicker/people-picker-helpers.d.ts.map +1 -1
- package/dist/components/PeoplePicker/people-picker-helpers.js +1 -1
- package/dist/components/PeoplePicker/people-picker-helpers.js.map +1 -1
- package/dist/components/PeoplePicker/people-picker.d.ts.map +1 -1
- package/dist/components/PeoplePicker/people-picker.js +2 -2
- package/dist/components/PeoplePicker/people-picker.js.map +1 -1
- package/dist/components/PeoplePicker/person-display.d.ts.map +1 -1
- package/dist/components/PeoplePicker/person-display.js +26 -28
- package/dist/components/PeoplePicker/person-display.js.map +1 -1
- package/dist/components/ProfileCard/profile-card.d.ts.map +1 -1
- package/dist/components/ProfileCard/profile-card.js.map +1 -1
- package/dist/components/RadioGroup/radio-group.d.ts +1 -1
- package/dist/components/RadioGroup/radio-group.d.ts.map +1 -1
- package/dist/components/RadioGroup/radio-group.js +10 -4
- package/dist/components/RadioGroup/radio-group.js.map +1 -1
- package/dist/components/Rating/rating.d.ts +28 -6
- package/dist/components/Rating/rating.d.ts.map +1 -1
- package/dist/components/Rating/rating.js +97 -80
- package/dist/components/Rating/rating.js.map +1 -1
- package/dist/components/ScrollArea/scroll-area.d.ts +1 -1
- package/dist/components/ScrollArea/scroll-area.js +1 -1
- package/dist/components/ScrollArea/scroll-area.js.map +1 -1
- package/dist/components/SegmentedControl/segmented-control.d.ts +1 -1
- package/dist/components/SegmentedControl/segmented-control.d.ts.map +1 -1
- package/dist/components/SegmentedControl/segmented-control.js +3 -2
- package/dist/components/SegmentedControl/segmented-control.js.map +1 -1
- package/dist/components/Select/select.d.ts.map +1 -1
- package/dist/components/Select/select.js +15 -8
- package/dist/components/Select/select.js.map +1 -1
- package/dist/components/SelectMenu/index.js +2 -1
- package/dist/components/SelectMenu/index.js.map +1 -1
- package/dist/components/SelectMenu/select-menu-keyboard.d.ts +59 -0
- package/dist/components/SelectMenu/select-menu-keyboard.d.ts.map +1 -0
- package/dist/components/SelectMenu/select-menu-keyboard.js +107 -0
- package/dist/components/SelectMenu/select-menu-keyboard.js.map +1 -0
- package/dist/components/SelectMenu/select-menu.d.ts +1 -22
- package/dist/components/SelectMenu/select-menu.d.ts.map +1 -1
- package/dist/components/SelectMenu/select-menu.js +18 -34
- package/dist/components/SelectMenu/select-menu.js.map +1 -1
- package/dist/components/SelectionControl/selection-item.d.ts.map +1 -1
- package/dist/components/SelectionControl/selection-item.js +1 -0
- package/dist/components/SelectionControl/selection-item.js.map +1 -1
- package/dist/components/Sidebar/sidebar.d.ts +7 -9
- package/dist/components/Sidebar/sidebar.d.ts.map +1 -1
- package/dist/components/Sidebar/sidebar.js +171 -50
- package/dist/components/Sidebar/sidebar.js.map +1 -1
- package/dist/components/Slider/slider.d.ts +1 -1
- package/dist/components/Slider/slider.d.ts.map +1 -1
- package/dist/components/Slider/slider.js +9 -4
- package/dist/components/Slider/slider.js.map +1 -1
- package/dist/components/Steps/steps.d.ts.map +1 -1
- package/dist/components/Steps/steps.js +54 -26
- package/dist/components/Steps/steps.js.map +1 -1
- package/dist/components/Switch/switch.d.ts +1 -1
- package/dist/components/Switch/switch.d.ts.map +1 -1
- package/dist/components/Switch/switch.js +7 -2
- package/dist/components/Switch/switch.js.map +1 -1
- package/dist/components/Tabs/tabs.d.ts +1 -1
- package/dist/components/Tabs/tabs.d.ts.map +1 -1
- package/dist/components/Tabs/tabs.js +39 -4
- package/dist/components/Tabs/tabs.js.map +1 -1
- package/dist/components/Tag/tag.d.ts +1 -1
- package/dist/components/Tag/tag.d.ts.map +1 -1
- package/dist/components/Tag/tag.js +3 -2
- package/dist/components/Tag/tag.js.map +1 -1
- package/dist/components/Textarea/textarea.d.ts.map +1 -1
- package/dist/components/Textarea/textarea.js +8 -2
- package/dist/components/Textarea/textarea.js.map +1 -1
- package/dist/components/TimePicker/time-columns.d.ts.map +1 -1
- package/dist/components/TimePicker/time-columns.js +8 -4
- package/dist/components/TimePicker/time-columns.js.map +1 -1
- package/dist/components/TimePicker/time-picker.d.ts +1 -1
- package/dist/components/TimePicker/time-picker.js +1 -1
- package/dist/components/TimePicker/time-picker.js.map +1 -1
- package/dist/components/TreeView/tree-view.d.ts +9 -5
- package/dist/components/TreeView/tree-view.d.ts.map +1 -1
- package/dist/components/TreeView/tree-view.js +266 -214
- package/dist/components/TreeView/tree-view.js.map +1 -1
- package/dist/hooks/use-input-modality.d.ts +12 -17
- package/dist/hooks/use-input-modality.d.ts.map +1 -1
- package/dist/hooks/use-input-modality.js +4 -34
- package/dist/hooks/use-input-modality.js.map +1 -1
- package/dist/hooks/use-overflow-items.d.ts.map +1 -1
- package/dist/hooks/use-overflow-items.js +2 -0
- package/dist/hooks/use-overflow-items.js.map +1 -1
- package/dist/hooks/use-truncated.d.ts.map +1 -1
- package/dist/hooks/use-truncated.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +23 -3
- package/dist/index.js.map +1 -1
- package/dist/lib/drag-announcements.d.ts +32 -0
- package/dist/lib/drag-announcements.d.ts.map +1 -1
- package/dist/lib/drag-announcements.js +39 -1
- package/dist/lib/drag-announcements.js.map +1 -1
- package/dist/lib/focus-after-trigger.d.ts +45 -0
- package/dist/lib/focus-after-trigger.d.ts.map +1 -0
- package/dist/lib/focus-after-trigger.js +66 -0
- package/dist/lib/focus-after-trigger.js.map +1 -0
- package/dist/lib/overlay-coexistence.js +1 -1
- package/dist/lib/roving-list-keyboard.d.ts +208 -0
- package/dist/lib/roving-list-keyboard.d.ts.map +1 -0
- package/dist/lib/roving-list-keyboard.js +174 -0
- package/dist/lib/roving-list-keyboard.js.map +1 -0
- package/dist/lib/utils.d.ts.map +1 -1
- package/dist/lib/utils.js +6 -1
- package/dist/lib/utils.js.map +1 -1
- package/dist/patterns/element-anatomy/item-anatomy.d.ts +4 -4
- package/dist/patterns/element-anatomy/item-anatomy.d.ts.map +1 -1
- package/dist/patterns/element-anatomy/item-anatomy.js +14 -4
- package/dist/patterns/element-anatomy/item-anatomy.js.map +1 -1
- package/dist/patterns/horizontal-overflow/horizontal-overflow.d.ts.map +1 -1
- package/dist/patterns/horizontal-overflow/horizontal-overflow.js +25 -0
- package/dist/patterns/horizontal-overflow/horizontal-overflow.js.map +1 -1
- package/dist/patterns/overlay-surface/overlay-surface.d.ts.map +1 -1
- package/dist/patterns/overlay-surface/overlay-surface.js.map +1 -1
- package/dist/tokens/categorical-color.d.ts +6 -1
- package/dist/tokens/categorical-color.d.ts.map +1 -1
- package/dist/tokens/categorical-color.js.map +1 -1
- package/dist/tokens/uiSize/icon-size.d.ts +1 -1
- package/dist/tokens/uiSize/stack-gap.d.ts +18 -0
- package/dist/tokens/uiSize/stack-gap.d.ts.map +1 -0
- package/dist/tokens/uiSize/stack-gap.js +5 -0
- package/dist/tokens/uiSize/stack-gap.js.map +1 -0
- package/ds-canonical/fork/codex/agents/skills/performance-audit/SKILL.md +1 -1
- package/ds-canonical/fork/codex/hooks.json +1 -1
- package/ds-canonical/fork/consumer/lock.json +17 -14
- package/ds-canonical/fork/consumer/managed-files/423729840acdd43c812f17c245b871716c6b60f76e4d3e94864d0e34a815bf8d.blob +9 -0
- package/ds-canonical/fork/consumer/managed-files/46f10bc55cdfdf834d0749eebff42ea0bb8576f361b36d2b6ba1851354c50150.blob +47 -9
- package/ds-canonical/fork/consumer/managed-files/5a0d134a2645114c8caac20458070b00a7f0fd55b3e8805b7325cb4cf52fa15e.blob +110 -0
- package/ds-canonical/fork/consumer/managed-files/5af0a46760af338d51fd0f7950675f8e97f7d2f09386b8818fcd7a58e6654e75.blob +790 -0
- package/ds-canonical/fork/consumer/managed-files/68bfcddff9c4a16388c248cd34fb66163e9e64b3d809a48ef3984f6ae4ff6d12.blob +211 -0
- package/ds-canonical/fork/consumer/managed-files/7eb6cc60abfdabaf6617c336058fad1454ccde08889295767bfd9b7fd153cc6e.blob +45 -6
- package/ds-canonical/fork/consumer/managed-files/92e4828cdcb96ff0d01daa16f6885f24045a8e72820665650f2a1890dc3a446c.blob +42 -2
- package/ds-canonical/fork/consumer/managed-files/ab072e7f2eca51f0e18a5e18c4b333260ee5d219a12cc6df5bb2d683f0b2734d.blob +27 -1
- package/ds-canonical/fork/consumer/managed-files/e71c65f45c6af7ce12b6b3c0d8028f185da5b719e2050c9b2a6b243d41ae4878.blob +62 -14
- package/ds-canonical/fork/governance.lock +41 -17
- package/ds-canonical/fork/hooks/check_consumer_app_invariants.sh +16 -4
- package/ds-canonical/fork/hooks/check_consumer_code_quality.sh +3 -1
- package/ds-canonical/fork/hooks/check_pattern_invariants.sh +13 -9
- package/ds-canonical/fork/launchers/settings-hooks.json +1 -1
- package/ds-canonical/fork/manifest.json +17 -11
- package/ds-canonical/fork/providers/claude/skills/performance-audit/SKILL.md +1 -1
- package/ds-canonical/fork/providers/codex/hook-config.json +1 -1
- package/ds-canonical/fork/providers/codex/skills/performance-audit/SKILL.md +1 -1
- package/ds-canonical/fork/skills/performance-audit/SKILL.md +1 -1
- package/ds-canonical/hooks/check_consumer_app_invariants.sh +16 -4
- package/ds-canonical/hooks/check_consumer_code_quality.sh +3 -1
- package/ds-canonical/hooks/check_datatable_invariants.sh +4 -1
- package/ds-canonical/hooks/check_pattern_invariants.sh +13 -9
- package/ds-canonical/hooks/check_plugin_fork_health.sh +8 -1
- package/ds-canonical/hooks/check_propose_discipline.sh +8 -1
- package/ds-canonical/hooks/check_storybook_addon_packaging.sh +8 -1
- package/ds-canonical/hooks/check_substantive_edit_approval_preflight.sh +64 -2
- package/ds-canonical/hooks/lib/_overlay_handcraft.sh +5 -2
- package/ds-canonical/hooks/registrations.json +12 -7
- package/ds-canonical/hooks/session_start_governance_check.sh +16 -0
- package/ds-canonical/hooks/tests/test_check_consumer_app_invariants.sh +25 -0
- package/ds-canonical/hooks/tests/test_check_consumer_code_quality.sh +4 -0
- package/ds-canonical/hooks/tests/test_check_consumer_ds_primitive_misuse.sh +14 -0
- package/ds-canonical/hooks/tests/test_check_datatable_invariants.sh +24 -0
- package/ds-canonical/hooks/tests/test_check_overlay_handcraft.sh +32 -0
- package/ds-canonical/hooks/tests/test_check_pattern_invariants.sh +59 -0
- package/ds-canonical/hooks/tests/test_check_plugin_fork_health.sh +32 -1
- package/ds-canonical/hooks/tests/test_check_propose_discipline.sh +21 -0
- package/ds-canonical/hooks/tests/test_check_storybook_addon_packaging.sh +21 -0
- package/ds-canonical/hooks/tests/test_check_substantive_edit_approval_preflight.sh +97 -0
- package/ds-canonical/hooks/tests/test_session_start_governance_check.sh +49 -0
- package/ds-canonical/references/README.md +12 -6
- package/ds-canonical/references/drag-canonical.md +3 -1
- package/ds-canonical/references/focus-canonical.md +26 -13
- package/ds-canonical/references/governance-audit-coverage.md +1 -1
- package/ds-canonical/references/hit-area-canonical.md +361 -0
- package/ds-canonical/references/item-anatomy-recipe.md +4 -2
- package/ds-canonical/references/keyboard-model-canonical.md +559 -0
- package/ds-canonical/rules/meta-patterns.md +4 -4
- package/ds-canonical/rules/self-verify.md +4 -4
- package/ds-canonical/skills/design-system-audit/references/audit-prompts.md +1 -1
- package/ds-canonical/skills/design-system-audit/references/historical-bugs.md +96 -0
- package/ds-canonical/skills/knowledge-prune/SKILL.md +4 -1
- package/ds-canonical/skills/performance-audit/SKILL.md +1 -1
- package/ds-story-manifest.json +19 -7
- package/llms-full.txt +2 -2
- package/llms.txt +2 -2
- package/package.json +3 -1
- package/src/components/Accordion/accordion.anatomy.stories.tsx +3 -3
- package/src/components/Accordion/accordion.spec.md +1 -1
- package/src/components/Accordion/accordion.tsx +8 -3
- package/src/components/AccountMenu/account-menu.spec.md +2 -3
- package/src/components/AccountMenu/account-menu.tsx +0 -1
- package/src/components/AgentPanel/agent-decision-card.spec.md +88 -0
- package/src/components/AgentPanel/agent-panel-fab.spec.md +151 -0
- package/src/components/AgentPanel/agent-panel-fab.tsx +67 -24
- package/src/components/AgentPanel/agent-panel-logo.spec.md +87 -0
- package/src/components/AgentPanel/agent-panel-logo.tsx +3 -3
- package/src/components/AgentPanel/agent-panel.anatomy.stories.tsx +1 -1
- package/src/components/AgentPanel/agent-panel.principles.stories.tsx +1 -1
- package/src/components/AgentPanel/agent-panel.spec.md +30 -275
- package/src/components/AgentPanel/agent-panel.stories.tsx +113 -8
- package/src/components/AgentPanel/agent-panel.tsx +59 -22
- package/src/components/Alert/alert.anatomy.stories.tsx +1 -1
- package/src/components/AppShell/app-shell.tsx +1 -1
- package/src/components/AspectRatio/aspect-ratio.anatomy.stories.tsx +10 -15
- package/src/components/Avatar/avatar.anatomy.stories.tsx +23 -26
- package/src/components/Avatar/avatar.principles.stories.tsx +10 -10
- package/src/components/Avatar/avatar.spec.md +45 -6
- package/src/components/Avatar/avatar.stories.tsx +8 -8
- package/src/components/Avatar/avatar.tsx +227 -21
- package/src/components/Badge/badge.anatomy.stories.tsx +16 -21
- package/src/components/Breadcrumb/breadcrumb.anatomy.stories.tsx +3 -3
- package/src/components/Breadcrumb/breadcrumb.spec.md +3 -3
- package/src/components/Breadcrumb/breadcrumb.tsx +5 -3
- package/src/components/BulkActionBar/bulk-action-bar.anatomy.stories.tsx +1 -1
- package/src/components/BulkActionBar/bulk-action-bar.principles.stories.tsx +1 -1
- package/src/components/BulkActionBar/bulk-action-bar.spec.md +1 -0
- package/src/components/BulkActionBar/bulk-action-bar.stories.tsx +57 -2
- package/src/components/BulkActionBar/bulk-action-bar.tsx +32 -2
- package/src/components/Button/button.anatomy.stories.tsx +174 -33
- package/src/components/Button/button.spec.md +23 -8
- package/src/components/Button/button.tsx +116 -21
- package/src/components/Calendar/calendar.anatomy.stories.tsx +34 -11
- package/src/components/Calendar/calendar.principles.stories.tsx +44 -1
- package/src/components/Calendar/calendar.spec.md +97 -20
- package/src/components/Calendar/calendar.stories.tsx +101 -4
- package/src/components/Calendar/calendar.tsx +379 -74
- package/src/components/Carousel/carousel.anatomy.stories.tsx +16 -17
- package/src/components/Carousel/carousel.spec.md +48 -2
- package/src/components/Carousel/carousel.tsx +9 -3
- package/src/components/Chart/chart.anatomy.stories.tsx +7 -5
- package/src/components/Chart/chart.spec.md +32 -7
- package/src/components/Chart/chart.tsx +9 -0
- package/src/components/Checkbox/checkbox.anatomy.stories.tsx +21 -28
- package/src/components/Checkbox/checkbox.spec.md +4 -2
- package/src/components/Checkbox/checkbox.tsx +10 -2
- package/src/components/Chip/chip.anatomy.stories.tsx +1 -1
- package/src/components/Chip/chip.spec.md +2 -1
- package/src/components/Chip/chip.tsx +4 -2
- package/src/components/Combobox/combobox.anatomy.stories.tsx +30 -37
- package/src/components/Combobox/combobox.spec.md +11 -3
- package/src/components/Combobox/combobox.tsx +43 -12
- package/src/components/Command/command.spec.md +11 -0
- package/src/components/Command/command.tsx +143 -10
- package/src/components/DataTable/cell-registry.tsx +5 -1
- package/src/components/DataTable/data-table-column-visibility-panel.tsx +5 -6
- package/src/components/DataTable/data-table-interaction-layer.tsx +27 -14
- package/src/components/DataTable/data-table-known-defects.spec.md +72 -0
- package/src/components/DataTable/data-table-scroll-performance.spec.md +295 -0
- package/src/components/DataTable/data-table-sort-manager.tsx +5 -6
- package/src/components/DataTable/data-table.anatomy.stories.tsx +1 -1
- package/src/components/DataTable/data-table.css +64 -0
- package/src/components/DataTable/data-table.spec.md +70 -346
- package/src/components/DataTable/data-table.stories.tsx +56 -9
- package/src/components/DataTable/data-table.tsx +392 -103
- package/src/components/DateGrid/date-grid.anatomy.stories.tsx +2 -2
- package/src/components/DateGrid/date-grid.spec.md +163 -1
- package/src/components/DateGrid/date-grid.tsx +270 -7
- package/src/components/DatePicker/date-picker.anatomy.stories.tsx +18 -27
- package/src/components/DatePicker/date-picker.spec.md +6 -2
- package/src/components/DatePicker/date-picker.tsx +20 -1
- package/src/components/Dialog/dialog.spec.md +6 -2
- package/src/components/Dialog/dialog.stories.tsx +18 -9
- package/src/components/Dialog/dialog.tsx +83 -48
- package/src/components/DropdownMenu/dropdown-menu-keyboard.ts +255 -0
- package/src/components/DropdownMenu/dropdown-menu.anatomy.stories.tsx +32 -33
- package/src/components/DropdownMenu/dropdown-menu.spec.md +18 -5
- package/src/components/DropdownMenu/dropdown-menu.stories.tsx +58 -1
- package/src/components/DropdownMenu/dropdown-menu.tsx +70 -33
- package/src/components/Empty/empty.spec.md +1 -2
- package/src/components/Field/field-context.ts +9 -4
- package/src/components/Field/field-controls.spec.md +5 -2
- package/src/components/Field/field-edit-keys.ts +12 -0
- package/src/components/Field/field-wrapper.tsx +76 -10
- package/src/components/Field/field.anatomy.stories.tsx +1 -1
- package/src/components/Field/field.spec.md +11 -6
- package/src/components/Field/field.stories.tsx +24 -1
- package/src/components/Field/field.tsx +10 -4
- package/src/components/FileItem/file-item.anatomy.stories.tsx +20 -10
- package/src/components/FileItem/file-item.principles.stories.tsx +14 -7
- package/src/components/FileItem/file-item.spec.md +50 -48
- package/src/components/FileItem/file-item.stories.tsx +41 -28
- package/src/components/FileItem/file-item.tsx +184 -50
- package/src/components/FileUpload/file-upload.anatomy.stories.tsx +11 -15
- package/src/components/FileUpload/file-upload.spec.md +27 -1
- package/src/components/FileUpload/file-upload.stories.tsx +33 -0
- package/src/components/FileUpload/file-upload.tsx +173 -43
- package/src/components/FileViewer/file-viewer.tsx +9 -4
- package/src/components/HoverCard/hover-card.stories.tsx +2 -2
- package/src/components/InlineEdit/inline-edit.spec.md +2 -0
- package/src/components/InlineEdit/inline-edit.tsx +8 -3
- package/src/components/Input/input.anatomy.stories.tsx +23 -31
- package/src/components/Input/input.spec.md +11 -0
- package/src/components/Input/input.tsx +6 -1
- package/src/components/LinkInput/link-input.anatomy.stories.tsx +18 -21
- package/src/components/LinkInput/link-input.principles.stories.tsx +4 -4
- package/src/components/LinkInput/link-input.spec.md +12 -1
- package/src/components/LinkInput/link-input.tsx +37 -4
- package/src/components/Menu/menu-item.anatomy.stories.tsx +50 -56
- package/src/components/Menu/menu-item.spec.md +1 -1
- package/src/components/Menu/menu-item.stories.tsx +48 -42
- package/src/components/Notice/notice.anatomy.stories.tsx +8 -16
- package/src/components/Notice/notice.spec.md +1 -1
- package/src/components/Notice/notice.tsx +1 -1
- package/src/components/NumberInput/number-input.anatomy.stories.tsx +21 -28
- package/src/components/NumberInput/number-input.spec.md +2 -0
- package/src/components/NumberInput/number-input.tsx +8 -1
- package/src/components/OverflowIndicator/overflow-indicator.anatomy.stories.tsx +28 -48
- package/src/components/OverflowIndicator/overflow-indicator.spec.md +13 -1
- package/src/components/OverflowIndicator/overflow-indicator.stories.tsx +12 -9
- package/src/components/OverflowIndicator/overflow-indicator.tsx +32 -12
- package/src/components/Pagination/pagination.spec.md +1 -0
- package/src/components/Pagination/pagination.tsx +1 -1
- package/src/components/PeoplePicker/avatar-stack-overflow.ts +14 -13
- package/src/components/PeoplePicker/people-picker-helpers.ts +5 -2
- package/src/components/PeoplePicker/people-picker.anatomy.stories.tsx +1 -1
- package/src/components/PeoplePicker/people-picker.spec.md +4 -2
- package/src/components/PeoplePicker/people-picker.tsx +1 -4
- package/src/components/PeoplePicker/person-display.tsx +45 -28
- package/src/components/Popover/popover.anatomy.stories.tsx +1 -1
- package/src/components/Popover/popover.principles.stories.tsx +3 -12
- package/src/components/Popover/popover.spec.md +7 -3
- package/src/components/ProfileCard/profile-card.anatomy.stories.tsx +1 -1
- package/src/components/ProfileCard/profile-card.spec.md +1 -1
- package/src/components/ProfileCard/profile-card.tsx +0 -3
- package/src/components/ProgressBar/progress-bar.anatomy.stories.tsx +11 -21
- package/src/components/ProgressBar/progress-bar.spec.md +1 -1
- package/src/components/RadioGroup/radio-group.anatomy.stories.tsx +17 -24
- package/src/components/RadioGroup/radio-group.spec.md +1 -1
- package/src/components/RadioGroup/radio-group.tsx +22 -4
- package/src/components/Rating/rating.anatomy.stories.tsx +131 -124
- package/src/components/Rating/rating.principles.stories.tsx +69 -43
- package/src/components/Rating/rating.spec.md +142 -63
- package/src/components/Rating/rating.stories.tsx +11 -15
- package/src/components/Rating/rating.tsx +131 -117
- package/src/components/ScrollArea/scroll-area.anatomy.stories.tsx +1 -1
- package/src/components/ScrollArea/scroll-area.principles.stories.tsx +6 -3
- package/src/components/ScrollArea/scroll-area.spec.md +3 -1
- package/src/components/ScrollArea/scroll-area.stories.tsx +5 -2
- package/src/components/ScrollArea/scroll-area.tsx +1 -1
- package/src/components/SegmentedControl/segmented-control.anatomy.stories.tsx +1 -1
- package/src/components/SegmentedControl/segmented-control.spec.md +2 -2
- package/src/components/SegmentedControl/segmented-control.tsx +3 -2
- package/src/components/Select/select.anatomy.stories.tsx +26 -35
- package/src/components/Select/select.spec.md +4 -3
- package/src/components/Select/select.tsx +11 -4
- package/src/components/SelectMenu/select-menu-keyboard.ts +218 -0
- package/src/components/SelectMenu/select-menu.anatomy.stories.tsx +6 -17
- package/src/components/SelectMenu/select-menu.spec.md +12 -4
- package/src/components/SelectMenu/select-menu.tsx +21 -56
- package/src/components/SelectionControl/selection-item.anatomy.stories.tsx +9 -33
- package/src/components/SelectionControl/selection-item.spec.md +2 -2
- package/src/components/SelectionControl/selection-item.tsx +3 -1
- package/src/components/Separator/separator.stories.tsx +4 -4
- package/src/components/Sheet/sheet.principles.stories.tsx +31 -13
- package/src/components/Sidebar/sidebar.anatomy.stories.tsx +1 -1
- package/src/components/Sidebar/sidebar.spec.md +73 -6
- package/src/components/Sidebar/sidebar.stories.tsx +82 -4
- package/src/components/Sidebar/sidebar.tsx +281 -64
- package/src/components/Skeleton/skeleton.spec.md +1 -1
- package/src/components/Slider/slider.anatomy.stories.tsx +1 -1
- package/src/components/Slider/slider.spec.md +6 -5
- package/src/components/Slider/slider.tsx +13 -8
- package/src/components/Steps/steps-state-visuals.spec.md +157 -0
- package/src/components/Steps/steps.anatomy.stories.tsx +1 -0
- package/src/components/Steps/steps.spec.md +59 -142
- package/src/components/Steps/steps.stories.tsx +1 -1
- package/src/components/Steps/steps.tsx +125 -33
- package/src/components/Switch/switch.spec.md +3 -3
- package/src/components/Switch/switch.tsx +6 -1
- package/src/components/Tabs/tabs.spec.md +5 -1
- package/src/components/Tabs/tabs.tsx +42 -3
- package/src/components/Tag/tag.anatomy.stories.tsx +24 -28
- package/src/components/Tag/tag.spec.md +10 -11
- package/src/components/Tag/tag.tsx +3 -3
- package/src/components/Textarea/textarea.tsx +7 -1
- package/src/components/TimePicker/time-columns.tsx +9 -4
- package/src/components/TimePicker/time-picker.spec.md +4 -1
- package/src/components/TimePicker/time-picker.tsx +1 -1
- package/src/components/Tooltip/tooltip.anatomy.stories.tsx +6 -15
- package/src/components/Tooltip/tooltip.principles.stories.tsx +4 -16
- package/src/components/Tooltip/tooltip.spec.md +2 -1
- package/src/components/TreeView/tree-view.anatomy.stories.tsx +35 -13
- package/src/components/TreeView/tree-view.spec.md +67 -38
- package/src/components/TreeView/tree-view.stories.tsx +221 -12
- package/src/components/TreeView/tree-view.tsx +463 -356
- package/src/hooks/use-input-modality.ts +17 -46
- package/src/hooks/use-overflow-items.ts +4 -0
- package/src/hooks/use-truncated.ts +3 -2
- package/src/index.ts +17 -0
- package/src/lib/README.md +2 -0
- package/src/lib/drag-announcements.ts +78 -0
- package/src/lib/focus-after-trigger.ts +102 -0
- package/src/lib/roving-list-keyboard.ts +400 -0
- package/src/lib/utils.ts +5 -0
- package/src/patterns/action-bar/action-bar.spec.md +2 -0
- package/src/patterns/element-anatomy/inline-action.spec.md +16 -12
- package/src/patterns/element-anatomy/item-anatomy.spec.md +98 -6
- package/src/patterns/element-anatomy/item-anatomy.stories.tsx +75 -70
- package/src/patterns/element-anatomy/item-anatomy.tsx +17 -6
- package/src/patterns/element-anatomy/truncated-text.spec.md +1 -0
- package/src/patterns/header-canonical/header-canonical.spec.md +3 -2
- package/src/patterns/horizontal-overflow/horizontal-overflow.spec.md +2 -0
- package/src/patterns/horizontal-overflow/horizontal-overflow.stories.tsx +36 -0
- package/src/patterns/horizontal-overflow/horizontal-overflow.tsx +28 -0
- package/src/patterns/overlay-surface/overlay-chrome-sizing.spec.md +180 -0
- package/src/patterns/overlay-surface/overlay-surface.spec.md +8 -162
- package/src/patterns/overlay-surface/overlay-surface.tsx +5 -3
- package/src/patterns/resize-handle/resize-handle.spec.md +3 -3
- package/src/styles/base.css +1 -1
- package/src/tokens/categorical-color.ts +6 -1
- package/src/tokens/color/color.spec.md +91 -24
- package/src/tokens/color/color.stories.tsx +50 -2
- package/src/tokens/color/primitives.css +41 -38
- package/src/tokens/color/semantic.css +105 -36
- package/src/tokens/layoutSpace/layoutSpace.spec.md +2 -0
- package/src/tokens/motion/motion.spec.md +31 -8
- package/src/tokens/orphan-tokens.spec.md +1 -1
- package/src/tokens/uiSize/icon-size.ts +1 -1
- package/src/tokens/uiSize/stack-gap.ts +17 -0
- package/src/tokens/uiSize/uiSize.css +12 -0
- package/src/tokens/uiSize/uiSize.spec.md +17 -16
- package/src/tokens/uiSize/uiSize.stories.tsx +1 -1
- package/dist/node_modules/aria-hidden/dist/es2015/index.js +0 -147
- package/dist/node_modules/aria-hidden/dist/es2015/index.js.map +0 -1
|
@@ -50,7 +50,7 @@ const PARTS: Record<PartKey, PartSpec> = {
|
|
|
50
50
|
dayHover: { label: 'Hover', bg: 'transparent', text: '--foreground', border: '--primary', extra: 'hover 藍圈 1.5px(無填底)' },
|
|
51
51
|
dayOutside: { label: 'Outside 月份', bg: 'transparent', text: '--fg-muted', border: 'transparent' },
|
|
52
52
|
dayDisabled: { label: 'Disabled', bg: '--bg-disabled', text: '--fg-disabled', border: 'transparent', extra: 'bg-disabled · cursor-not-allowed · native disabled' },
|
|
53
|
-
dayFocus: { label: 'Focus-visible',bg: 'transparent', text: '--foreground', border: '--ring', extra: 'ring-
|
|
53
|
+
dayFocus: { label: 'Focus-visible',bg: 'transparent', text: '--foreground', border: '--ring', extra: 'focus-visible:focus-ring-inset · 內描邊往內 2px;填色格 focus-ring-inset-emphasis(1px 白線退 3px)' },
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
const PART_ORDER: PartKey[] = [
|
|
@@ -280,7 +280,7 @@ export const Inspector = {
|
|
|
280
280
|
<span className="text-[10px] font-semibold text-fg-muted uppercase tracking-wider">Style</span>
|
|
281
281
|
</div>
|
|
282
282
|
<PropRow label="日格 Radius">rounded-full · 圓形</PropRow>
|
|
283
|
-
<PropRow label="Focus">ring-
|
|
283
|
+
<PropRow label="Focus">focus-visible:focus-ring-inset · 內描邊往內 2px;填色格 focus-ring-inset-emphasis(1px 白線退 3px)</PropRow>
|
|
284
284
|
<PropRow label="Transition">transition-colors</PropRow>
|
|
285
285
|
</div>
|
|
286
286
|
</div>
|
|
@@ -108,7 +108,7 @@ DateGrid 是 internal primitive(見「定位」),一般 consumer 經 `DatePicker
|
|
|
108
108
|
| **range 端點 cell bg** | 灰底半圓 track,**高度 = button**,向 middle 外擴 2px bridge gap | `neutral-selected`;class 細節見「Range track canonical」+ tsx | 圓弧半徑 = button 半徑無錯位;舊版 cell-level bg 圓弧半徑 16px 比 button 14px 大 = 視覺 misalign |
|
|
109
109
|
| **range track(中間)** | 灰底矩形,**高度 = button**(28×28 @ md),左右各外擴 2px 接合相鄰 cell | `neutral-selected`;button 透明顯露 track(class 細節見 tsx)| track 高度跟 selected 圓一致,不留 2px「fat」邊;相鄰 pseudo 接合連貫橫向 track |
|
|
110
110
|
| **hover(未選中)** | 藍圈 outline(無 fill) | button hover ring 色 `primary-hover`(2026-07-07 user 拍板統一:瞬時 hover 進 primary 家族 = hover 階,FileUpload / Slider thumb hover 同族;base 專屬持續選中與 focus),無 bg(ring 寬度等 class 細節見 tsx)| outline 保留 cell 底色,與 selected fill 明確區分 |
|
|
111
|
-
| **range 預覽框(停留 / 焦點)** | 同色同粗的藍色細框把「點下去會變成」的區間框起來,兩端半圓、與 track 同高;停留日就是框的那一端 | td `::after`(track 用 `::before`),1.5px `primary-hover`;class 住在 tsx `RANGE_PREVIEW_CLASSNAMES` | 只有 `DatePicker.Range` 會算出這組 modifier(它才知道正在選哪一端);DateGrid 只擁有畫法。規則見下方「區間預覽框」。**跨格不閃**:停留日掛在 button 的 mouseenter / mouseleave,格間 4px 縫隙屬於 table
|
|
111
|
+
| **range 預覽框(停留 / 焦點)** | 同色同粗的藍色細框把「點下去會變成」的區間框起來,兩端半圓、與 track 同高;停留日就是框的那一端 | td `::after`(track 用 `::before`),1.5px `primary-hover`;class 住在 tsx `RANGE_PREVIEW_CLASSNAMES` | 只有 `DatePicker.Range` 會算出這組 modifier(它才知道正在選哪一端);DateGrid 只擁有畫法。規則見下方「區間預覽框」。**跨格不閃**:停留日掛在 button 的 mouseenter / mouseleave,格間 4px 縫隙屬於 table、圓外的四個角屬於 td,指標經過這些地方會先 leave 再 enter、整條框卸掉一幀(user 2026-09-23 抓到「水平移動到隔日框會閃一下」)—— 現行解法是 DateGrid **只在指標真的離開整張格陣時才轉發 leave**,停在縫裡或可點日子圓外的角裡都算「還在上一天」(不論移得多快多慢、走不走中線;慢慢從縫裡走出去的那條路 2026-09-26 補上 = D1,圓外的角 2026-09-26 補上 = D2),見下方「日期格的命中區 = 可視形狀」的「現行機制」;框亮著時在縫或角裡點下去 = 點停留日,見同節「縫與角裡的點擊」;2026-09-23 曾用 day button `::before` 外擴 2px 吃掉那道縫,已於 2026-09-24 撤除(命中區大於可視形狀) |
|
|
112
112
|
| **focus-visible(鍵盤焦點)** | 非填色格:往內 2px 藍線;填色格(selected / range 端點):1px 白線退 3px,外圈留藍 | day button `focus-visible:focus-ring-inset`;填色 modifier 另掛 `EMPHASIS_FOCUS_RING_CLASSNAME`(= `focus-ring-inset-emphasis`,幾何 owner `styles/base.css` + `focus-canonical.md`「填色元素上的內描邊」)| 格與格只隔 4px,track / 預覽框就跑在縫裡,往外畫會壓到框線;藍底上藍線看不見、白線貼邊只是削小藍圓(2026-09-23 user 拍板 D,原話在 focus-canonical 來源總帳) |
|
|
113
113
|
|
|
114
114
|
### 鄰月日子:一條原則(2026-09-24 user 拍板)
|
|
@@ -160,6 +160,168 @@ DateGrid 是 internal primitive(見「定位」),一般 consumer 經 `DatePicker
|
|
|
160
160
|
|
|
161
161
|
**世界級對照**(讀原始碼,2026-09-23):五家有區間選擇的元件庫都在停留時預覽區間,差別只在畫法 —— Ant Design v4 用虛線上下邊 + 兩端側邊([panel.less](https://github.com/ant-design/ant-design/blob/4.x-stable/components/date-picker/style/panel.less) `-range-hover*`,`border-top/bottom: dashed @picker-date-hover-range-border-color`)、MUI X 用 1.2px 虛線([DateRangePickerDay.tsx](https://github.com/mui/mui-x/blob/master/packages/x-date-pickers-pro/src/DateRangePickerDay/DateRangePickerDay.tsx) `previewStyles`)、Ant v5 現行與 Polaris 用與已選區間同色的淺色填([rc-picker PanelBody.tsx](https://github.com/react-component/picker/blob/master/src/PickerPanel/PanelBody.tsx) 把 `hoverRangeValue` 算成 `-in-range`;[Polaris Day.tsx](https://github.com/Shopify/polaris/blob/main/polaris-react/src/components/DatePicker/components/Day/Day.tsx) `(inRange || inHoveringRange) && styles['Day-inRange']`)、Carbon(flatpickr)用填色([index.ts](https://github.com/flatpickr/flatpickr/blob/master/src/index.ts) `onMouseOver` → `startRange / inRange / endRange`)。**本 DS 選實線**:沿用單日 hover 圈的顏色與粗細,不新增第二種「暫定」表達(M23 DS 既有語言優先;user 2026-09-23 Q2 拍板)。我們包的 react-day-picker 本身沒有預覽([range-mode 文件](https://daypicker.dev/selections/range-mode)只有 `range_start / range_middle / range_end`),所以由 DatePicker.Range 自算。
|
|
162
162
|
|
|
163
|
+
## 日期格的命中區 = 可視形狀(2026-09-24 逐案裁定)
|
|
164
|
+
|
|
165
|
+
**日期格的可視形狀是那個 28×28(lg 32)的圓**(`day_button` 的 `rounded-full`,懸停時 1.5px 藍圈就畫在它身上),
|
|
166
|
+
**命中區就是同一個圓,不外擴**。跨元件契約(`../../ds-canonical/references/hit-area-canonical.md`
|
|
167
|
+
「懸停回饋的形狀 ≡ 命中區」)的唯一例外是「可視形狀先天當不了目標的線與點」;28px 的圓不屬於那一類,吃不到例外。
|
|
168
|
+
(2026-09-26 補註:區間預覽框亮著時,縫與圓外的角裡那一下點擊交給停留日 —— 那**不是**把命中區外擴:滑過仍只從這顆圓開始,
|
|
169
|
+
沒有框時縫與角點了照樣沒反應。規則與理由見下方「縫與角裡的點擊」。)
|
|
170
|
+
|
|
171
|
+
### 先前這裡有一條隱形帶,而且比看起來大得多
|
|
172
|
+
|
|
173
|
+
2026-09-23 這裡曾有 `before:content-[''] before:absolute before:-inset-[2px]`,
|
|
174
|
+
名義上是「四邊各 2px」,**實測是最遠外推 9.3px** —— 因為 `::before` 是**方的**、沒有圓角,
|
|
175
|
+
它不只補了格間 4px 的縫,還把圓**四個角外面**的區域一起收進命中區(方角到圓心 √2×16 ≈ 22.6,圓半徑 14)。
|
|
176
|
+
|
|
177
|
+
| 量的東西(`elementFromPoint` 逐 0.5px 掃,storybook 展示--單選) | 移除前 | 移除後 |
|
|
178
|
+
|---|---|---|
|
|
179
|
+
| 圓內取樣點命中 / 漏掉 | 2188 / **0** | 2188 / **0** |
|
|
180
|
+
| 圓外取樣點「仍然命中」 | **1763** | **40** |
|
|
181
|
+
| 圓外最遠仍命中的距離 | **9.33px** | **1.10px**(瀏覽器對圓角命中測試的抗鋸齒,同 `agent-fab-hit-area-invariant.mjs` 的 `EDGE_TOLERANCE = 1.5`) |
|
|
182
|
+
| 格間縫隙中點的擁有者 | 相鄰日期的 button | `<table>`(沒有人宣稱它) |
|
|
183
|
+
|
|
184
|
+
### 那條帶原本在解什麼 —— 以及為什麼那是表層解
|
|
185
|
+
|
|
186
|
+
它解的是 user 2026-09-23 抓到的「從某日水平移動到其隔日,藍色的區間框線都會閃動一下」:
|
|
187
|
+
停留日掛在 button 的 `mouseenter` / `mouseleave`(react-day-picker 只給這一層),
|
|
188
|
+
而格與格之間的 4px(`border-spacing-1`)屬於 `<table>`、不屬於任何一天 ——
|
|
189
|
+
指標經過縫時先 leave(整條框卸掉)再 enter(補回)。
|
|
190
|
+
|
|
191
|
+
**閃動的根因是「停留日在縫裡被清掉」,不是「縫裡沒有人收 enter」。** 幾何外擴改的是後者,是表層。
|
|
192
|
+
根因層的解法就在當時那條註解自己引到的 MUI:**只在指標真的離開整張格陣時才清停留日**
|
|
193
|
+
([DateRangeCalendar.tsx:479-486](https://github.com/mui/mui-x/blob/master/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.tsx) ——
|
|
194
|
+
`rangePreviewDay` 的清除逐字掛在 `CalendarTransitionProps` 的 `onMouseLeave`,也就是**整個日曆容器**,不是每一天)。
|
|
195
|
+
|
|
196
|
+
### 現行機制(`date-grid.tsx`)
|
|
197
|
+
|
|
198
|
+
1. 月曆格陣掛 `data-day-grid` 錨點(`AnchoredMonthGrid`,覆寫 RDP 的 `MonthGrid`)。
|
|
199
|
+
2. `handleDayMouseLeave`:leave 事件的 `relatedTarget` 若落在同一張格陣的**縫或角**裡(`isGapOrCorner`),就**不轉發**給消費端;
|
|
200
|
+
其餘一律照常轉發。
|
|
201
|
+
- **縫**:在格陣裡、但既不在 `<td>` 也不在 `<th>` 上 —— border-spacing 的 4px 空白、表格最外圈,都屬於 `<table>` 自己。
|
|
202
|
+
- **角**(2026-09-26 補,D2):在某個**可點日子**的 `<td>` 裡、但不在它那顆圓(button)上。日期是 28px 的圓、放在 28×28 的方格裡,
|
|
203
|
+
圓外的四個角屬於 td、不屬於圓。先前只算縫、把角當成「已經離開」,指標只要沒走在格子正中線上就先清框再補回 ——
|
|
204
|
+
2026-09-26 實測(每步 1px,淺深色相同):偏上 1/4 格 6 步、貼上緣 2px 14 步、貼左緣 2px 14 步、斜穿四格交會處 12 步,
|
|
205
|
+
整條框(17 格)消失;走中線 0 步。09-24 的實測與閘都只走中線,所以沒抓到。
|
|
206
|
+
- 不可點的日子(含它的角)、空白補位格、星期列、格陣之外:都不算,照常清掉。
|
|
207
|
+
- 若整張 DateGrid 被放進別的表格的格子裡,`closest('td,th')` 會找到外面那一格;判斷一律以「那一格在不在本格陣裡」為準,不會把縫誤判成離開。
|
|
208
|
+
3. `handleGridMouseOver`:補掉「穿過縫或角之後停在**不可點**的日子、空白補位格或星期列」那條路徑 ——
|
|
209
|
+
disabled 的 button 收不到滑鼠事件、不會再有任何 day enter/leave,沒有這一段的話上一天的預覽框會留著
|
|
210
|
+
(`datepicker-range-preview.mjs`「順序不合不預覽」那兩條斷言就是這樣紅的,2026-09-24 實測)。停在縫或角裡不動作(與第 2 條同一支判斷)。
|
|
211
|
+
4. `handleGridMouseLeave`(2026-09-26 補,D1,待辦總帳 N54):補掉「**從縫或角直接離開格陣**」那條路徑 ——
|
|
212
|
+
指標離開最外圈那一天時落在縫或角裡,那次 leave 依第 2 條被吞(欠著);接著指標走出 `<table>`,已經沒有任何一天會再收到 leave、
|
|
213
|
+
也沒有 `td` / `th` 會收到 mouseover,預覽框就卡在上一天。實測(2026-09-26 前):從 6/13 每步 1px 往右,經過縫、月曆內距、浮層邊,
|
|
214
|
+
走到浮層外 20px,停 2 秒仍是 5/4→6/13;每步 1–4、8px 都卡住,6、12、20px 或一次跳出去才會清 —— 快慢決定了指標有沒有「停」在縫裡。
|
|
215
|
+
修法:格陣 `<table>` 掛 `onMouseLeave`,**只在有欠著的 leave 時**補送一次(一次跳出去那條路 day button 的 leave 已照常轉發,不重送)。
|
|
216
|
+
這是上面那句判準「只在指標真的離開整張格陣時才清」本來就要的,缺的只是實作:MUI X 同時掛了「容器 `onMouseLeave` 清除」
|
|
217
|
+
([DateRangeCalendar.tsx#L481-L486](https://github.com/mui/mui-x/blob/v9.14.0/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.tsx#L481-L486)
|
|
218
|
+
`onMouseLeave: () => setRangePreviewDay(null)`),2026-09-24 照 MUI 做「縫裡不清」時沒一起做這一半。
|
|
219
|
+
兩張月曆並排時每張各是一張格陣:兩張之間的空白算離開(`../DatePicker/date-picker.spec.md`「滑鼠離開**日期格區**」)。
|
|
220
|
+
縫與角裡照亮(維持上一天)不變;「偏離中線橫越時仍會閃」(D2)由第 2 條的「角」解決。
|
|
221
|
+
閘:`scripts/datepicker-range-preview.mjs`「慢慢移出」段(`exit` 家族,含對照組;(c) 路線貼上緣 2px,先穿過角再出去)。
|
|
222
|
+
5. `handleGridClick` / `handleGridPointerDown` / `handleGridMouseMove` / `handleDayFocus`(2026-09-26):框亮著時在縫或角裡點下去 = 點停留日,
|
|
223
|
+
游標同為手形;`handleDayFocus` 只記「滑鼠停留之後,鍵盤又移了看得見的焦點」(最後一個輸入是鍵盤 → 縫與角不接點擊),不改任何焦點行為 ——
|
|
224
|
+
見下一節「縫與角裡的點擊」。
|
|
225
|
+
|
|
226
|
+
錨點刻意用顯式屬性而不是 `closest('table')`:標籤名是「剛好成立的觀察量」,不是要保證的性質(M37);
|
|
227
|
+
而且顯式屬性讓閘的對照組可以只用一行 `removeAttribute` 精準弄壞這個機制。
|
|
228
|
+
|
|
229
|
+
⚠️ `AnchoredMonthGrid` **必須定義在 module 層**。第一版寫成 render 內的 inline 箭頭函式,
|
|
230
|
+
每次 render 都是新的 component type → React 把整個格陣 unmount 再 mount → RDP 內部 effect 重設 state →
|
|
231
|
+
`Maximum update depth exceeded`(React #185),storybook 整個 DatePicker range 故事白畫面。
|
|
232
|
+
|
|
233
|
+
### 縫與角裡的點擊(2026-09-26)
|
|
234
|
+
|
|
235
|
+
**規則**:區間預覽框亮著、指標停在縫或角裡(框仍停在上一天)時,**點下去 = 點那一天** —— 結果與直接點那一天完全相同
|
|
236
|
+
(DatePicker.Range 正在選結束日時:結束日變成那一天、浮層關)。游標在那裡與日期一樣是手形。
|
|
237
|
+
沒有框的時候,縫與角點下去沒反應、游標不是手形。
|
|
238
|
+
跨元件的原則住在 `../../ds-canonical/references/hit-area-canonical.md`「滑過原則」一-6(評分星星同一條);本節只寫日期的細則。
|
|
239
|
+
|
|
240
|
+
| 指標在哪、當時的狀態 | 縫與角裡的預覽框 | 點下去 | 游標 |
|
|
241
|
+
|---|---|---|---|
|
|
242
|
+
| 滑鼠從某一天移進縫或角,框亮著、停留端就是那一天 | 維持上一天 | = 點那一天 | 手形 |
|
|
243
|
+
| 沒有框(兩端都空,或對面那端還空、只有單格 hover 圈) | —— | 沒反應 | 一般箭頭 |
|
|
244
|
+
| 鍵盤把框移到別天之後,滑鼠還停在縫或角裡 | 框在鍵盤那一天 | 沒反應(縫與角屬於滑鼠上一天,那一天不是框的停留端) | 一般箭頭 |
|
|
245
|
+
| 觸控 / 觸控筆點在縫或角裡 | —— | 沒反應(維持原樣) | —— |
|
|
246
|
+
| 不可點的日子、空白補位格、星期列 | 清掉(見「現行機制」第 3 條) | 沒反應 | —— |
|
|
247
|
+
|
|
248
|
+
**來源**:
|
|
249
|
+
- 決定:待辦總帳(`governance/planning/2026-09-25-interaction-and-hover-remediation.md`)N54 建議 ①
|
|
250
|
+
「預覽亮著的任何位置、包括縫,點下去就得到正在預覽的值」(`:229`,AI 措辭);2026-09-26 同意批次 `:105` ——
|
|
251
|
+
「日期縫與角」列在 user 沒點名、依「其餘都照建議做嗎」的問法理解為照建議做的四項裡(總帳原文標為 AI 判讀、回報時明講);
|
|
252
|
+
實作的兩句「在縫或角裡點下去 = 確認正在預覽的那一天」「日期圓圈外的角滑過去不再閃」是 **AI 的措辭**,不是 user 原話。
|
|
253
|
+
- 原則:user 2026-09-26(總帳 `:225`,逐字)「我滑到某個區塊但不是滑到文字上但文字卻變色了,此時表示產生了樣式變化,儘管沒滑到文字上,此時點擊也應該要有反應,我的意思是這樣」;
|
|
254
|
+
同意改寫原則見 `:65`(「1. 我同意」)。框亮著時,縫與角正是「滑過有變化」的位置。
|
|
255
|
+
|
|
256
|
+
**點到哪一天 = 框停在哪一天**:同一個角點下去得到哪一天,取決於指標是從哪一天移進來的(例:從 5/20 移進 5/21 的左上角 → 得到 5/20;
|
|
257
|
+
React Aria 實測相同,R20 已指出 —— 它在「月曆裡、但不在按鈕上」放開一律確認正在預覽的區間,[`useRangeCalendar.ts#L80-L86`](https://github.com/adobe/react-spectrum/blob/4dd44e0f400636a87a9ad4390903e78c5ae6113c/packages/react-aria/src/calendar/useRangeCalendar.ts#L80-L86))。滑鼠甩得很快、中間幾天沒被取樣到時也一樣 —— 框停在哪一天,點下去就是哪一天,永遠是畫面上框著的那一個。
|
|
258
|
+
|
|
259
|
+
**這不是把命中區外擴**(與上方「命中區就是同一個圓」相容):滑過(enter)仍只從那顆圓開始;沒有框時縫與角點了照樣沒反應。
|
|
260
|
+
縫與角只在「框已經亮著、而且停留端就是那一天」時接那一下點擊 —— 回饋在前、點擊在後,不會出現「沒亮卻點得到」。
|
|
261
|
+
判斷「框的停留端就是那一天」直接看那一格有沒有掛上框的端點 class(`RANGE_PREVIEW_CLASSNAMES` 的起點 / 終點 / 單格,只住在 `date-grid.tsx`)——
|
|
262
|
+
框正是那組 class 畫出來的,所以這一問就是性質本身,不是旁證(M37)。
|
|
263
|
+
|
|
264
|
+
**實作**:真的去點那顆 button(`button.click()`),不另寫一份選取邏輯 —— 選取、焦點、關浮層都走 react-day-picker 的同一條路,結果與直接點那一天一模一樣。
|
|
265
|
+
游標:日期是 button,手形來自 `../../styles/base.css`「`button:not(:disabled), [role="button"]:not(:disabled) { cursor: pointer; }`」;
|
|
266
|
+
縫與角裡點得到時,格陣掛 `data-gap-confirms`,`month_grid` 的 `data-[gap-confirms]:cursor-pointer` 讓縫(`<table>`)與角(`<td>` 繼承)同為手形。
|
|
267
|
+
游標與點擊用同一支判斷(`gapConfirmTarget`),指標每移一步重算一次(框是 React 狀態、慢一幀才畫上;鍵盤也可能把框移走)。
|
|
268
|
+
|
|
269
|
+
**只算滑鼠**(AI 工程判斷;user 的討論與 R21 實測都只涵蓋滑鼠):觸控沒有「滑過」,框停在上一次點的那一天,不是手指現在碰到的位置 ——
|
|
270
|
+
手指點在 5/17 的角卻選到 5/10 是錯的。所以觸控 / 觸控筆點在縫或角裡維持原樣(沒反應)。
|
|
271
|
+
|
|
272
|
+
**鍵盤不變**:本節只動滑鼠。方向鍵、Enter / Space、焦點框、預覽跟著焦點走,全部照舊。
|
|
273
|
+
|
|
274
|
+
**世界級**(2026-09-26 R21 實測 + 釘版原始碼):
|
|
275
|
+
|
|
276
|
+
| 系統 | 縫與角裡的預覽 | 在那裡點下去 | 一手出處 |
|
|
277
|
+
|---|---|---|---|
|
|
278
|
+
| MUI X DateRangeCalendar v9.14.0 | 維持上一天(只在離開月份容器時清) | 沒反應 | [`DateRangeCalendar.tsx#L481-L486`](https://github.com/mui/mui-x/blob/c83b3dd6996f6913947b1be3e5d47655153ced60/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.tsx#L481-L486) `onMouseLeave: () => setRangePreviewDay(null),` |
|
|
279
|
+
| Chakra UI DatePicker(Zag 1.43.3) | 維持上一天(只在離開表格時清) | 沒反應 | [`date-picker.connect.ts#L546-L548`](https://github.com/chakra-ui/zag/blob/46f88c089c1dbb0fc681b31172dc5eb8a07eef0d/packages/machines/date-picker/src/date-picker.connect.ts#L546-L548) `onPointerLeave() { send({ type: "TABLE.POINTER_LEAVE" })`;[`date-picker.machine.ts#L516-L519`](https://github.com/chakra-ui/zag/blob/46f88c089c1dbb0fc681b31172dc5eb8a07eef0d/packages/machines/date-picker/src/date-picker.machine.ts#L516-L519) `actions: ["clearHoveredDate"],` |
|
|
280
|
+
| React Aria RangeCalendar | 維持上一天(只有 enter) | **確認正在預覽的區間** | [`useRangeCalendar.ts#L23-L32`](https://github.com/adobe/react-spectrum/blob/4dd44e0f400636a87a9ad4390903e78c5ae6113c/packages/react-aria/src/calendar/useRangeCalendar.ts#L23-L32) 預設 `select`:"select the currently hovered range of dates."(該設定的範圍:"Controls the behavior when a pointer is released outside the calendar or a blur occurs mid selection");讓「月曆裡、但不在按鈕上」也算進去的是 [`#L80-L86`](https://github.com/adobe/react-spectrum/blob/4dd44e0f400636a87a9ad4390903e78c5ae6113c/packages/react-aria/src/calendar/useRangeCalendar.ts#L80-L86) 的 `!target.closest('button, [role="button"]')` |
|
|
281
|
+
| Ant Design RangePicker 6.6.5 | 沒有縫:整格就是那一天 | 選那一天 | [`panel.ts#L346-L349`](https://github.com/ant-design/ant-design/blob/4a39f54842eade4e565ab336ef6097cd7e723cdd/components/date-picker/style/panel.ts#L346-L349) `borderCollapse: 'collapse',`、[`#L364-L367`](https://github.com/ant-design/ant-design/blob/4a39f54842eade4e565ab336ef6097cd7e723cdd/components/date-picker/style/panel.ts#L364-L367) 整格 `cursor: 'pointer',`;[rc-picker `PanelBody.tsx#L145-L164`](https://github.com/react-component/picker/blob/a975a11f9a959a811b1ecd901e8ff8ae8f0e515b/src/PickerPanel/PanelBody.tsx#L145-L164) 同一個 `<td>` 掛 `onClick` / `onMouseEnter` / `onMouseLeave` |
|
|
282
|
+
| **本 DS** | 維持上一天(縫 + 可點日子圓外的角) | 框亮著時 = 點那一天 | 本節 |
|
|
283
|
+
|
|
284
|
+
沒有一家兩件事都做:「縫與角維持上一天」取 MUI X、Chakra,「縫裡點下去確認」取 React Aria。
|
|
285
|
+
Ant 靠「整格都是那一天」達成同樣的「亮了就點得到」,但它能點的方格(36)大於畫出來的方塊(24),正是本 DS 09-24 撤掉的那條路(上方「先前這裡有一條隱形帶」)。
|
|
286
|
+
Ant 整格是手形(上表 `#L364-L367`),與本節「點得到的縫與角是手形」同一個道理。
|
|
287
|
+
|
|
288
|
+
閘:`scripts/datepicker-range-preview.mjs` 的 `corner` 家族(四條角落路線,每步 1px)與 `gapclick` 家族(角、縫各點一次 + 沒有框時的對照面),
|
|
289
|
+
兩家各有 `--selftest` 對照組(把「離開圓進到角」改寫成離開格陣 / 攔下縫與角裡的點擊並把游標蓋回 auto → 必須紅)。
|
|
290
|
+
|
|
291
|
+
### 實測(2026-09-24)
|
|
292
|
+
|
|
293
|
+
| 斷言 | 結果 |
|
|
294
|
+
|---|---|
|
|
295
|
+
| 命中 ≡ 可視(圓) | 圓內 2188 點全命中、0 漏;圓外最遠 1.10px(抗鋸齒容差內) |
|
|
296
|
+
| 水平跨格 5/20→5/21(30 小步) | 框最少仍有 **17** 格(0 = 縫裡整條框消失) |
|
|
297
|
+
| 垂直跨列 5/20→5/27(30 小步) | 框最少仍有 **17** 格 |
|
|
298
|
+
| **對照組**:拔掉 `data-day-grid` 錨點 | 兩條跨法都掉到 **0** —— 證明這組數字量得到機制在不在 |
|
|
299
|
+
| `scripts/datepicker-range-preview.mjs` | 109 條全過(exit 0);`--selftest` 五個家族全部會紅,含 `crossing` |
|
|
300
|
+
|
|
301
|
+
**不越出宿主**:第一格的命中左緣 42.5 落在 `month` 的 padding box(左緣 33)之內 9.5px,連 `<table>` 盒都沒出去。
|
|
302
|
+
**不蓋鄰居**:相鄰兩格的命中盒水平間隙實測 **0.00px**(相切零重疊)。
|
|
303
|
+
|
|
304
|
+
**依據不是觸控尺寸建議** —— 本 DS 明確不以「手指要多大才點得到」當命中區的依據
|
|
305
|
+
(hit-area-canonical「本 DS 不採納觸控尺寸建議」)。
|
|
306
|
+
|
|
307
|
+
### 實測(2026-09-26,角 + 縫與角的點擊)
|
|
308
|
+
|
|
309
|
+
Chromium 單一行程、1280×900;「現行建置」= repo 的 `storybook-static`(已含 D1);「D1 前建置」= 09-25 的建置;「修後」= 本次改動建出的 storybook。
|
|
310
|
+
|
|
311
|
+
| 斷言 | D1 前建置 | 現行建置 | 修後 |
|
|
312
|
+
|---|---|---|---|
|
|
313
|
+
| 角落路線(每步 1px,停 5/20)偏上 1/4 格 / 貼上緣 2px / 貼左緣 2px / 斜穿交會處:框縮掉的步數 | 6 / 14 / 14 / 12 | 6 / 14 / 14 / 12 | **0 / 0 / 0 / 0**(淺、深色另以 R21 量具重量,六條路線全 0) |
|
|
314
|
+
| 同路線上「停在格子裡、圓外的角」的步數(證明路線真的經過角) | 2 / 10 / 10 / 8 | 2 / 10 / 10 / 8 | 2 / 10 / 10 / 8 |
|
|
315
|
+
| 框亮著,在 5/20 右上角點下去 | 沒反應(框早已清掉) | 沒反應(框早已清掉) | 結束日 5/12 → **5/20**、浮層關;游標 `pointer` |
|
|
316
|
+
| 框亮著,在 5/20 右邊縫的中點點下去 | 沒反應;游標 `auto` | 沒反應;游標 `auto` | 結束日 → **5/20**、浮層關;游標 `pointer` |
|
|
317
|
+
| 沒有框(兩端都空),在某天的角裡點下去 | 沒反應;`auto` | 沒反應;`auto` | 沒反應;`auto`(不變) |
|
|
318
|
+
| 慢慢移出 (c):6/13 貼上緣 2px 往右走出浮層 | 一進角框就沒了(格陣最外圈量到 0 格) | 同左 | 最外圈仍有框(48 格)、出去後 0 格 |
|
|
319
|
+
| 鍵盤把框移到 5/5 後,滑鼠仍停在 5/20 的角裡點 | —— | —— | 沒反應;游標 `auto`(對照:不按鍵盤 → 選到 5/20) |
|
|
320
|
+
| 同上,但滑鼠停的是 5/4(剛好是鍵盤那個框的起點) | —— | —— | 沒反應;游標 `auto` |
|
|
321
|
+
| 框亮著,改用手指點同一個角 | —— | —— | 沒反應(對照:滑鼠點 → 選到框著的那一天) |
|
|
322
|
+
| `scripts/datepicker-range-preview.mjs` | 12 條紅(新增 10 條 + D1 的 2 條) | 10 條紅(全是新增的) | 136 條全過;`--selftest` 八個家族全部會紅 |
|
|
323
|
+
| **對照組**:拔掉 `data-day-grid` 錨點(中線橫向) | —— | —— | 縮掉 4 步(量具看得到閃) |
|
|
324
|
+
|
|
163
325
|
## Spacing canonical(2026-05-03 v8)
|
|
164
326
|
|
|
165
327
|
- **內距住在每一張月曆身上,不在根**(2026-09-18 user 裁示,原話:「我覺得真的要做的話,邏輯就是這樣,
|
|
@@ -110,6 +110,14 @@ export const RANGE_PREVIEW_CLASSNAMES = Object.freeze({
|
|
|
110
110
|
end: cn(PREVIEW_FRAME, PREVIEW_STROKE, 'after:-left-[2px] after:right-0 after:rounded-r-full', CLIP_INNER_LEFT),
|
|
111
111
|
single: cn(PREVIEW_FRAME, PREVIEW_STROKE, 'after:inset-x-0 after:rounded-full'),
|
|
112
112
|
})
|
|
113
|
+
// 某一格是不是正畫著框的**端點**(起點 / 終點 / 單格)。判準就是上面這三串 class 有沒有整串落在那個 td 上 ——
|
|
114
|
+
// 框正是由它們畫出來的,而它們只住在本檔(DatePicker.Range 原樣交給 modifiersClassNames),所以這一問就是「框的端點畫在這格上嗎」
|
|
115
|
+
// 本身,不是旁證(M37)。中段只有上下線、不算端點。用在下方「縫或角裡點下去 = 點停留日」:停留日一定是框的端點,
|
|
116
|
+
// 不是端點(框是鍵盤那一天設的、或根本沒有框)就不接那一下點擊。
|
|
117
|
+
const PREVIEW_END_TOKENS = [RANGE_PREVIEW_CLASSNAMES.start, RANGE_PREVIEW_CLASSNAMES.end, RANGE_PREVIEW_CLASSNAMES.single]
|
|
118
|
+
.map((classes) => classes.split(/\s+/).filter(Boolean))
|
|
119
|
+
const drawsPreviewEnd = (cell: Element | null) =>
|
|
120
|
+
!!cell && PREVIEW_END_TOKENS.some((tokens) => tokens.every((token) => cell.classList.contains(token)))
|
|
113
121
|
// 預覽「上膛」的格(停留會有框的那些天)**靜態**壓掉 button 的單格 hover 圈。
|
|
114
122
|
// 單格圈是 CSS :hover,指標一到就畫;框是 React 狀態,慢一幀。只靠上面 PREVIEW_FRAME 裡的壓制(掛在停留後才出現的
|
|
115
123
|
// preview modifier 上),每次停留都會先閃一圈整圓、再變成半圓(user 2026-09-23:「hover 到日期都會先看到一圈圓形藍色外框,
|
|
@@ -128,6 +136,70 @@ const ZH_FULL_DATE = new Intl.DateTimeFormat('zh-TW', { dateStyle: 'full' })
|
|
|
128
136
|
const ZH_MONTH_YEAR = new Intl.DateTimeFormat('zh-TW', { year: 'numeric', month: 'long' })
|
|
129
137
|
const ZH_WEEKDAY = new Intl.DateTimeFormat('zh-TW', { weekday: 'long' })
|
|
130
138
|
|
|
139
|
+
// 可點的日子 = 沒有 disabled、也沒有 aria-disabled 的那顆 button(RDP 對鍵盤焦點落在不可選日時改掛 aria-disabled)
|
|
140
|
+
const CLICKABLE_DAY = 'button:not(:disabled):not([aria-disabled="true"])'
|
|
141
|
+
|
|
142
|
+
// ── 「縫或角」:格陣裡不屬於任何一天、但仍算「指標還在上一天身上」的位置(2026-09-26 補上「角」)──
|
|
143
|
+
// (a) 縫:既不在 `<td>` 也不在 `<th>` 上 —— border-spacing 的 4px 空白、表格最外圈,都屬於 `<table>` 自己;
|
|
144
|
+
// (b) 角:在某個**可點日子**的 `<td>` 裡、但不在它那顆 button(28px 的圓)上 —— 圓放在 28×28 的方格裡,圓外的四個角屬於 td。
|
|
145
|
+
// 停在不可點的日子(含它的角)、空白補位格、星期列、或格陣之外 → 都不算,照常清掉。
|
|
146
|
+
// 先前只有 (a):角被當成「已經離開」,指標只要沒走在格子正中線上(偏 1/4 格、貼邊、斜著走)就先清框再補回 ——
|
|
147
|
+
// 2026-09-26 實測整條框(17 格)消失 6–14 步(每步 1px),淺深色相同(世界級對照與一手出處:date-grid.spec.md「縫與角裡的點擊」)。
|
|
148
|
+
// 格陣自己的判斷(`grid.contains(cell)`):整張 DateGrid 若被放進別的表格的格子裡,`closest('td,th')` 會找到外面那一格,
|
|
149
|
+
// 那仍是本格陣的縫,不是「離開」。
|
|
150
|
+
function isGapOrCorner(node: EventTarget | null, grid: Element | null): boolean {
|
|
151
|
+
if (!(node instanceof Element) || !grid || !grid.contains(node)) return false
|
|
152
|
+
const cell = node.closest('td,th')
|
|
153
|
+
if (!cell || !grid.contains(cell)) return true // (a) 縫
|
|
154
|
+
if (cell.tagName !== 'TD') return false // 星期列
|
|
155
|
+
const button = node.closest('button')
|
|
156
|
+
if (button && cell.contains(button)) return false // 就在那顆圓上(可點與否都不是縫也不是角)
|
|
157
|
+
return cell.querySelector(`:scope > ${CLICKABLE_DAY}`) !== null // (b) 可點日子的角
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// 縫或角裡「點下去會確認」時掛在格陣上的屬性,只用來把游標變成跟日期一樣的手形
|
|
161
|
+
//(日期是 button,手形來自 styles/base.css「button:not(:disabled), [role="button"]:not(:disabled) { cursor: pointer }」)。
|
|
162
|
+
// 下方 month_grid 的 `data-[gap-confirms]:cursor-pointer` 讀的就是它 —— 兩處字串必須一致。
|
|
163
|
+
// 用屬性而不是 React state:它跟著指標每一步變,不該讓整張格陣重畫(同 tabs.tsx 的 data-action-hover、data-table.tsx 的 data-hovered)。
|
|
164
|
+
const GAP_CONFIRMS_ATTR = 'data-gap-confirms'
|
|
165
|
+
|
|
166
|
+
// 月曆格陣:在 react-day-picker 預設的 `<table {...props} />`
|
|
167
|
+
//(node_modules/react-day-picker/dist/esm/components/MonthGrid.js)上多掛這些東西 ——
|
|
168
|
+
// (1) `data-day-grid` 錨點,給下方 handleDayMouseLeave 判斷「指標現在停在格陣的哪一種地方」;
|
|
169
|
+
// (2) `onMouseOver`(由 context 遞進來),補掉「指標穿過縫或角之後停在不可點的日子」這條路徑;
|
|
170
|
+
// (3) `onMouseLeave`(由 context 遞進來),補掉「指標從縫或角直接離開格陣」這條路徑(D1,見 handleGridMouseLeave);
|
|
171
|
+
// (4) `onMouseMove` / `onPointerDown` / `onClick`(由 context 遞進來):縫或角裡的手形游標,與「在縫或角裡點下去 = 點停留日」。
|
|
172
|
+
// 只多屬性與事件,不改結構。
|
|
173
|
+
//
|
|
174
|
+
// ⚠️ **必須定義在 module 層,不能寫成 render 內的 inline 箭頭函式。** 寫成 inline 時每次 render 都是一個
|
|
175
|
+
// 新的 component type,React 會把整個格陣 unmount 再 mount,RDP 內部的焦點/動畫 effect 因此重新設 state,
|
|
176
|
+
// 立刻撞上「Maximum update depth exceeded」(React #185)—— 2026-09-24 第一版就是這樣寫的,storybook
|
|
177
|
+
// 整個 DatePicker range 故事白畫面。同檔的 PreviousMonthButton / NextMonthButton 是葉節點按鈕,沒有這個問題。
|
|
178
|
+
// handler 走 context 而不是 props,正是因為 RDP 只給 MonthGrid 固定的那組 props,塞不進額外的。
|
|
179
|
+
interface GridPointerHandlers {
|
|
180
|
+
onMouseOver?: (event: React.MouseEvent<HTMLTableElement>) => void
|
|
181
|
+
onMouseLeave?: (event: React.MouseEvent<HTMLTableElement>) => void
|
|
182
|
+
onMouseMove?: (event: React.MouseEvent<HTMLTableElement>) => void
|
|
183
|
+
onPointerDown?: (event: React.PointerEvent<HTMLTableElement>) => void
|
|
184
|
+
onClick?: (event: React.MouseEvent<HTMLTableElement>) => void
|
|
185
|
+
}
|
|
186
|
+
const GridPointerContext = React.createContext<GridPointerHandlers>({})
|
|
187
|
+
|
|
188
|
+
function AnchoredMonthGrid(props: React.TableHTMLAttributes<HTMLTableElement>) {
|
|
189
|
+
const { onMouseOver, onMouseLeave, onMouseMove, onPointerDown, onClick } = React.useContext(GridPointerContext)
|
|
190
|
+
return (
|
|
191
|
+
<table
|
|
192
|
+
{...props}
|
|
193
|
+
data-day-grid=""
|
|
194
|
+
onMouseOver={onMouseOver}
|
|
195
|
+
onMouseLeave={onMouseLeave}
|
|
196
|
+
onMouseMove={onMouseMove}
|
|
197
|
+
onPointerDown={onPointerDown}
|
|
198
|
+
onClick={onClick}
|
|
199
|
+
/>
|
|
200
|
+
)
|
|
201
|
+
}
|
|
202
|
+
|
|
131
203
|
// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding
|
|
132
204
|
const DateGrid = React.forwardRef<HTMLDivElement, DateGridProps>(function DateGrid(
|
|
133
205
|
{
|
|
@@ -136,13 +208,191 @@ const DateGrid = React.forwardRef<HTMLDivElement, DateGridProps>(function DateGr
|
|
|
136
208
|
labels,
|
|
137
209
|
showOutsideDays = true,
|
|
138
210
|
numberOfMonths,
|
|
211
|
+
onDayMouseEnter,
|
|
212
|
+
onDayMouseLeave,
|
|
213
|
+
onDayFocus,
|
|
139
214
|
...props
|
|
140
215
|
},
|
|
141
216
|
_ref,
|
|
142
217
|
) {
|
|
218
|
+
// ── 停留日只在指標真的離開整張格陣時才清(2026-09-24;2026-09-26 把「角」也算進來)──────────────
|
|
219
|
+
//
|
|
220
|
+
// react-day-picker 只把 mouseenter / mouseleave 掛在 day button 上,而格與格之間的 4px
|
|
221
|
+
// (`border-spacing-1`)屬於 `<table>`、不屬於任何一天;圓外的四個角屬於 `<td>`、也不屬於那顆圓。
|
|
222
|
+
// 指標經過這些地方時瀏覽器必然先送一次 leave、再送 enter,消費端(DatePicker 的 previewAnchor)因此把整條區間
|
|
223
|
+
// 預覽框卸掉再補回 —— user 2026-09-23 的原話是「從某日水平移動到其隔日,藍色的區間框線都會閃動一下」。
|
|
224
|
+
//
|
|
225
|
+
// 先前的解法是給 button 一條 `before:-inset-[2px]` 的隱形帶,讓縫裡也有人收 enter。
|
|
226
|
+
// 那是**表層**解:它改的是命中幾何,而閃動的根因是「停留日被清掉」。而且它讓命中區(32)大於
|
|
227
|
+
// 懸停回饋(28 圓 + 1.5px ring),違反 hit-area-canonical「懸停回饋的形狀 ≡ 命中區」,
|
|
228
|
+
// 而日期格既不是線也不是點,吃不到那條唯一例外。
|
|
229
|
+
//
|
|
230
|
+
// 根因層的解法:**只在離開整個日曆時才清停留日** —— MUI X 把清除只掛在月份容器上
|
|
231
|
+
//(v9.14.0 `DateRangeCalendar.tsx#L481-L486` `onMouseLeave: () => setRangePreviewDay(null)`,
|
|
232
|
+
// https://github.com/mui/mui-x/blob/c83b3dd6996f6913947b1be3e5d47655153ced60/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.tsx#L481-L486 ),
|
|
233
|
+
// Zag(Chakra DatePicker 的底層)只掛在整張表格上(`date-picker.connect.ts#L546-L548` `onPointerLeave` →
|
|
234
|
+
// `send({ type: "TABLE.POINTER_LEAVE" })`,
|
|
235
|
+
// https://github.com/chakra-ui/zag/blob/46f88c089c1dbb0fc681b31172dc5eb8a07eef0d/packages/machines/date-picker/src/date-picker.connect.ts#L546-L548 )。
|
|
236
|
+
// 本檔用同樣的判準:leave 事件的 `relatedTarget` 還落在同一張 `[data-day-grid]` 的**縫或角**裡(isGapOrCorner)就
|
|
237
|
+
// **不轉發**給消費端。指標真的離開格陣時 relatedTarget 落在格陣外(或為 null = 離開視窗),照常轉發。
|
|
238
|
+
//
|
|
239
|
+
// 為什麼錨在 `data-day-grid` 而不是 `closest('table')`:標籤名是「剛好成立的觀察量」,不是要保證的性質
|
|
240
|
+
//(M37);而且顯式屬性讓閘的對照組可以只用一行 `removeAttribute` 精準弄壞這個機制。
|
|
241
|
+
// 閘:`scripts/datepicker-range-preview.mjs`「跨格不閃」(中線)+「角落路線」(偏離中線、貼邊、斜穿交會處)+ 同檔 `--selftest` 對照組。
|
|
242
|
+
|
|
243
|
+
// 記住最後一次停留的日子與它的 button:下方補送 leave、縫或角裡的點擊都用得到
|
|
244
|
+
//(消費端 DatePicker 的 onDayMouseLeave 兩個參數都沒用到,但契約要求帶,不能亂編)。
|
|
245
|
+
const lastEnterRef = React.useRef<{
|
|
246
|
+
day: Date
|
|
247
|
+
modifiers: Parameters<NonNullable<typeof onDayMouseEnter>>[1]
|
|
248
|
+
button: HTMLButtonElement | null
|
|
249
|
+
} | null>(null)
|
|
250
|
+
// 縫或角裡被吞掉的那一次 leave 還欠著沒送 = 停留日(預覽框)還掛在上一天身上。只有這種狀態才需要在離開格陣時補送,
|
|
251
|
+
// 也只有這種狀態下,縫或角裡的點擊才會交給那一天。
|
|
252
|
+
const owesLeaveRef = React.useRef(false)
|
|
253
|
+
// 這一下點擊是哪一種指標按的(pointerdown 記、click 讀完就清)。只有滑鼠才有「滑過」:觸控的預覽框是上一次點的那一天,
|
|
254
|
+
// 不是手指現在碰到的位置,所以觸控 / 觸控筆點在縫或角裡維持原樣(沒反應)。
|
|
255
|
+
const pressPointerTypeRef = React.useRef<string | null>(null)
|
|
256
|
+
// 滑鼠停留之後,鍵盤又把**看得見的**焦點移到某一天 = 最後一個輸入是鍵盤(date-picker.spec.md「滑鼠與鍵盤,最後一個輸入贏」),
|
|
257
|
+
// 框改由焦點那一天決定,滑鼠所在的縫或角就不再代表框的停留端 —— 連「滑鼠上一天剛好是框另一端」的巧合也不接點擊。
|
|
258
|
+
// 只讀焦點事件、不改任何鍵盤行為;滑鼠再進任何一天就歸零。
|
|
259
|
+
const keyboardAfterPointerRef = React.useRef(false)
|
|
260
|
+
|
|
261
|
+
// 「縫或角裡點下去會確認」的對象:指標正停在縫或角裡(leave 欠著)、上一天那顆 button 還在這張格陣裡而且可點、
|
|
262
|
+
// 它那一格正畫著框的端點 —— 也就是「框亮著,而且框的停留端就是這一天」。沒有框(兩端都空、只有單格圈)就是 null:
|
|
263
|
+
// 那時縫與角裡什麼都沒亮,點下去不該有反應(hit-area-canonical「懸停回饋的形狀 ≡ 命中區」)。
|
|
264
|
+
// 手形游標與點擊用同一支判斷,游標不會說謊。
|
|
265
|
+
const gapConfirmTarget = (grid: Element | null): HTMLButtonElement | null => {
|
|
266
|
+
if (!owesLeaveRef.current || keyboardAfterPointerRef.current || !grid) return null
|
|
267
|
+
const button = lastEnterRef.current?.button
|
|
268
|
+
if (!button || !button.isConnected || !grid.contains(button) || !button.matches(CLICKABLE_DAY)) return null
|
|
269
|
+
return drawsPreviewEnd(button.closest('td')) ? button : null
|
|
270
|
+
}
|
|
271
|
+
const syncGapCursor = (grid: Element | null) => {
|
|
272
|
+
grid?.toggleAttribute(GAP_CONFIRMS_ATTR, gapConfirmTarget(grid) !== null)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const handleDayMouseEnter = (
|
|
276
|
+
day: Date,
|
|
277
|
+
modifiers: Parameters<NonNullable<typeof onDayMouseEnter>>[1],
|
|
278
|
+
event: React.MouseEvent,
|
|
279
|
+
) => {
|
|
280
|
+
const button = event.currentTarget instanceof HTMLButtonElement ? event.currentTarget : null
|
|
281
|
+
lastEnterRef.current = { day, modifiers, button }
|
|
282
|
+
owesLeaveRef.current = false
|
|
283
|
+
keyboardAfterPointerRef.current = false
|
|
284
|
+
syncGapCursor(button?.closest('[data-day-grid]') ?? null)
|
|
285
|
+
onDayMouseEnter?.(day, modifiers, event)
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const handleDayFocus = (
|
|
289
|
+
day: Date,
|
|
290
|
+
modifiers: Parameters<NonNullable<typeof onDayFocus>>[1],
|
|
291
|
+
event: React.FocusEvent,
|
|
292
|
+
) => {
|
|
293
|
+
if (event.currentTarget instanceof Element && event.currentTarget.matches(':focus-visible')) {
|
|
294
|
+
keyboardAfterPointerRef.current = true
|
|
295
|
+
syncGapCursor(event.currentTarget.closest('[data-day-grid]'))
|
|
296
|
+
}
|
|
297
|
+
onDayFocus?.(day, modifiers, event)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const handleDayMouseLeave =
|
|
301
|
+
onDayMouseLeave &&
|
|
302
|
+
((day: Date, modifiers: Parameters<NonNullable<typeof onDayMouseLeave>>[1], event: React.MouseEvent) => {
|
|
303
|
+
const from = (event.currentTarget ?? event.target) as Element | null
|
|
304
|
+
const grid = from?.closest?.('[data-day-grid]') ?? null
|
|
305
|
+
if (isGapOrCorner(event.relatedTarget, grid)) {
|
|
306
|
+
owesLeaveRef.current = true
|
|
307
|
+
syncGapCursor(grid)
|
|
308
|
+
return
|
|
309
|
+
}
|
|
310
|
+
owesLeaveRef.current = false
|
|
311
|
+
syncGapCursor(grid)
|
|
312
|
+
onDayMouseLeave(day, modifiers, event)
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
// 補洞:指標**穿過縫或角之後停在不可點的日子**(順序不合而 disabled 的那些)、空白補位格或星期列。
|
|
316
|
+
// 那條路徑上 button 的 leave 早在縫或角裡就發生過(被上面吞掉),而 disabled 的 button 收不到滑鼠事件、
|
|
317
|
+
// 不會再有任何 day enter/leave —— 沒有這一段的話,上一天的預覽框會一直留著。
|
|
318
|
+
// 判準跟上面同一條:停在縫或角裡不動作;停在其他 td / th 上,就補送一次 leave。
|
|
319
|
+
const handleGridMouseOver =
|
|
320
|
+
onDayMouseLeave &&
|
|
321
|
+
((event: React.MouseEvent<HTMLTableElement>) => {
|
|
322
|
+
const grid = event.currentTarget
|
|
323
|
+
const last = lastEnterRef.current
|
|
324
|
+
if (!last) return
|
|
325
|
+
const target = event.target
|
|
326
|
+
if (!(target instanceof Element)) return
|
|
327
|
+
if (target.closest(CLICKABLE_DAY)) return // 停在可點的日子 → 交給 RDP 的 enter
|
|
328
|
+
if (isGapOrCorner(target, grid)) { syncGapCursor(grid); return } // 停在縫或角裡 → 視為還在上一天
|
|
329
|
+
owesLeaveRef.current = false
|
|
330
|
+
syncGapCursor(grid)
|
|
331
|
+
onDayMouseLeave(last.day, last.modifiers, event)
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
// D1 補洞(2026-09-26,待辦總帳 N54「日期 D1」;date-grid.spec.md「現行機制」第 4 條):指標**從縫或角直接離開格陣**。
|
|
335
|
+
// 上面兩支都不管這條路:離開最外圈那一天時 relatedTarget 落在縫或角裡 → leave 被吞(欠著);接著指標走出 <table>,
|
|
336
|
+
// 已經沒有任何一天會再收到 leave、也沒有 td / th 會收到 mouseover —— 預覽框就一直卡在上一天(實測:從 6/13 每步 1px
|
|
337
|
+
// 往右移到浮層外 20px,停 2 秒仍是 5/4→6/13;一次跳出去則會清,因為 day button 的 leave 直接落在格陣外、照常轉發)。
|
|
338
|
+
// 本檔「只在指標真的離開整張格陣時才清」的判準本來就涵蓋這條,缺的是實作:MUI X 在月份容器掛
|
|
339
|
+
// `onMouseLeave: () => setRangePreviewDay(null)`(上方同一條連結),09-24 照 MUI 做「縫裡不清」時沒一起做這一半。
|
|
340
|
+
// 只在「欠著」時補送:一次跳出去的那條路,day button 的 leave 已經照常轉發過,不重送。
|
|
341
|
+
// 兩張月曆並排時每張各是一張格陣,兩張之間的空白 = 離開格陣(date-picker.spec.md「滑鼠離開日期格區」)。
|
|
342
|
+
const handleGridMouseLeave =
|
|
343
|
+
onDayMouseLeave &&
|
|
344
|
+
((event: React.MouseEvent<HTMLTableElement>) => {
|
|
345
|
+
const owed = owesLeaveRef.current
|
|
346
|
+
owesLeaveRef.current = false
|
|
347
|
+
syncGapCursor(event.currentTarget)
|
|
348
|
+
if (!owed) return
|
|
349
|
+
const last = lastEnterRef.current
|
|
350
|
+
if (!last) return
|
|
351
|
+
onDayMouseLeave(last.day, last.modifiers, event)
|
|
352
|
+
})
|
|
353
|
+
|
|
354
|
+
// 手形游標跟著每一步更新:框是 React 狀態、慢一幀才畫上,指標若在框畫好之前就滑進縫或角,上面 leave 那一刻還判不到端點;
|
|
355
|
+
// 鍵盤把框移走時也一樣。每一步重算一次(只讀幾個 class,不觸發 render)。
|
|
356
|
+
const handleGridMouseMove =
|
|
357
|
+
onDayMouseLeave &&
|
|
358
|
+
((event: React.MouseEvent<HTMLTableElement>) => syncGapCursor(event.currentTarget))
|
|
359
|
+
|
|
360
|
+
const handleGridPointerDown =
|
|
361
|
+
onDayMouseLeave &&
|
|
362
|
+
((event: React.PointerEvent<HTMLTableElement>) => { pressPointerTypeRef.current = event.pointerType })
|
|
363
|
+
|
|
364
|
+
// ── 縫或角裡點下去 = 點停留日(2026-09-26,待辦總帳 N54 ①;date-grid.spec.md「縫與角裡的點擊」)──
|
|
365
|
+
// 預覽框亮著時,指標停在縫或角裡,框仍停在上一天(上面吞掉的 leave);此時點下去 = 點那一天的 button —— 真的去點它
|
|
366
|
+
//(`button.click()`),所以結果、焦點、關浮層都跟直接點那一天一模一樣,不另寫一份選取邏輯。
|
|
367
|
+
// 點在日子本身上由 RDP 的 handleDayClick 處理(它 stopPropagation,不會走到這裡);點在不可點的格、星期列上 → isGapOrCorner 為 false,沒反應。
|
|
368
|
+
// 世界級:React Aria 預設就是「在月曆裡、但不在按鈕上放開 = 確認正在預覽的區間」(`useRangeCalendar.ts#L23-L32` 預設 `select`:
|
|
369
|
+
// "select the currently hovered range of dates."、`#L80-L86` 的條件 `!target.closest('button, [role="button"]')`,
|
|
370
|
+
// https://github.com/adobe/react-spectrum/blob/4dd44e0f400636a87a9ad4390903e78c5ae6113c/packages/react-aria/src/calendar/useRangeCalendar.ts#L80-L86 );
|
|
371
|
+
// MUI X、Chakra 在縫裡點沒反應(R21 實測)—— 本 DS 取前者,理由是 user 的原則「滑過有變化的地方,點下去要有反應」(待辦總帳 N50)。
|
|
372
|
+
const handleGridClick =
|
|
373
|
+
onDayMouseLeave &&
|
|
374
|
+
((event: React.MouseEvent<HTMLTableElement>) => {
|
|
375
|
+
const pointerType = pressPointerTypeRef.current
|
|
376
|
+
pressPointerTypeRef.current = null
|
|
377
|
+
if (pointerType !== 'mouse') return
|
|
378
|
+
const grid = event.currentTarget
|
|
379
|
+
if (!isGapOrCorner(event.target, grid)) return
|
|
380
|
+
gapConfirmTarget(grid)?.click()
|
|
381
|
+
})
|
|
143
382
|
// Note: react-day-picker v9 DayPicker 未對外 forward ref 到單一 DOM 節點(內部有多 div),
|
|
144
383
|
// 故 ref 簽名保留但不附著(符合 DS 統一 forwardRef 慣例;真要取 DOM 用 wrapper 包)。
|
|
145
384
|
return (
|
|
385
|
+
<GridPointerContext.Provider
|
|
386
|
+
value={{
|
|
387
|
+
onMouseOver: handleGridMouseOver,
|
|
388
|
+
onMouseLeave: handleGridMouseLeave,
|
|
389
|
+
onMouseMove: handleGridMouseMove,
|
|
390
|
+
onPointerDown: handleGridPointerDown,
|
|
391
|
+
onClick: handleGridClick,
|
|
392
|
+
}}
|
|
393
|
+
>
|
|
394
|
+
{/* 下方 <DayPicker> 刻意不隨這層 Provider 往內縮排:整塊兩百多行只為了多包一層 context 而全部位移,
|
|
395
|
+
會讓 diff 看起來像整檔重寫。Provider 本身沒有任何視覺或結構作用,只把格陣的指標事件(見 GridPointerHandlers)遞給 AnchoredMonthGrid。 */}
|
|
146
396
|
<DayPicker
|
|
147
397
|
// 兩月以上**不渲染鄰月日子**(2026-09-23 user 拍板):同一天會在相鄰兩張月曆各出現一次,區間 track / 端點藍圓 /
|
|
148
398
|
// 預覽框就被畫兩次(user 圖一:4/26 在四月與五月面板各一顆藍圓)。MUI X(calendars > 1 時 filler 格 opacity 0,
|
|
@@ -204,6 +454,9 @@ const DateGrid = React.forwardRef<HTMLDivElement, DateGridProps>(function DateGr
|
|
|
204
454
|
// 一處宣告、三種 range 狀態一起管,也少兩個要同步的地方。
|
|
205
455
|
month_grid: cn(
|
|
206
456
|
'border-separate border-spacing-1 -m-1',
|
|
457
|
+
// 縫或角裡點下去會確認停留日時(GAP_CONFIRMS_ATTR,由上方 syncGapCursor 掛),游標跟日期一樣是手形。
|
|
458
|
+
// td 沒有自己的 cursor,角會繼承這一條;button 自己的 pointer / disabled 的 not-allowed 不受影響。
|
|
459
|
+
'data-[gap-confirms]:cursor-pointer',
|
|
207
460
|
'[&_tr>td:first-child]:before:!left-0',
|
|
208
461
|
'[&_tr>td:last-child]:before:!right-0',
|
|
209
462
|
// 預覽框(td ::after)在列首 / 列尾同樣不得溢出面板留白;換列處框是開口的,與 track 同款
|
|
@@ -240,12 +493,14 @@ const DateGrid = React.forwardRef<HTMLDivElement, DateGridProps>(function DateGr
|
|
|
240
493
|
// 就跑在那 4px 縫裡 —— 往外畫的 2px 框正好壓在框線上(user 2026-09-23:「date 的鍵盤焦點感覺要改成
|
|
241
494
|
// 往內畫的那種,否則會跟區間藍框有視覺衝突」)。藍底格另走 EMPHASIS_FOCUS_RING_CLASSNAME(白線退 3px)。
|
|
242
495
|
'focus-visible:focus-ring-inset',
|
|
243
|
-
//
|
|
244
|
-
//
|
|
245
|
-
//
|
|
246
|
-
//
|
|
247
|
-
//
|
|
248
|
-
|
|
496
|
+
// 命中區 = 懸停回饋形狀(上面那圈 1.5px 藍圈就畫在這顆圓上)= 可視形狀 = 這顆 28×28(lg 32)的圓,不外擴
|
|
497
|
+
//(hit-area-canonical「一-4 細則:控件的懸停回饋形狀 ≡ 命中區」;理由與實測數字寫在 date-grid.spec.md「日期格的命中區 = 可視形狀」)。
|
|
498
|
+
// 2026-09-23 這裡曾有一條 `before:-inset-[2px]`,用四邊各 2px 的**不畫任何東西**的帶去補格間 4px 縫,
|
|
499
|
+
// 解的是「跨格時區間框閃一下」。帶是對的解嗎?不是 —— 閃動的根因是**停留日在縫裡被清掉**,
|
|
500
|
+
// 幾何外擴只是讓縫裡也有人收 enter。根因層的解法寫在上方 handleDayMouseLeave(只在指標真的離開整張格陣時才清),
|
|
501
|
+
// 那正是同一條註解當時就引到的 MUI 作法。
|
|
502
|
+
// 2026-09-26「縫或角裡點下去 = 點停留日」**不是**把這個盒外擴:滑過(enter)仍只從這顆圓開始,沒有框時縫與角點了照樣沒反應;
|
|
503
|
+
// 只有框亮著、停留端就是那一天時,縫與角那一下點擊交給那一天(上方 handleGridClick)。
|
|
249
504
|
),
|
|
250
505
|
// today:藍色 underline bar 貼近數字
|
|
251
506
|
today: cn(
|
|
@@ -344,6 +599,8 @@ const DateGrid = React.forwardRef<HTMLDivElement, DateGridProps>(function DateGr
|
|
|
344
599
|
...labels,
|
|
345
600
|
}}
|
|
346
601
|
components={{
|
|
602
|
+
// 月曆格陣的錨點(module 層 AnchoredMonthGrid,不可寫成 inline —— 見該處註解)
|
|
603
|
+
MonthGrid: AnchoredMonthGrid,
|
|
347
604
|
// ── Prev/Next nav(canonical 2026-05-03 v9,DS 一致設計語言)──
|
|
348
605
|
// User 2026-05-03 audit:「icon-only Button icon 都用 neutral-9,只有 dismiss 用 45%」
|
|
349
606
|
// → chevron 不是 dismiss,**走 Button 預設 text-foreground**(neutral-9 85%),
|
|
@@ -363,7 +620,13 @@ const DateGrid = React.forwardRef<HTMLDivElement, DateGridProps>(function DateGr
|
|
|
363
620
|
),
|
|
364
621
|
}}
|
|
365
622
|
{...props}
|
|
623
|
+
// 放在 {...props} 之後:本元件對 enter/leave/focus 的記錄與過濾是格陣幾何的不變條件,不可被 consumer 的同名 prop 蓋掉
|
|
624
|
+
//(consumer 的 handler 本來就由這三支包住後轉發,語意沒有被吃掉;focus 只多記一個「鍵盤在滑鼠之後」,不改焦點行為)。
|
|
625
|
+
onDayMouseEnter={handleDayMouseEnter}
|
|
626
|
+
onDayMouseLeave={handleDayMouseLeave}
|
|
627
|
+
onDayFocus={handleDayFocus}
|
|
366
628
|
/>
|
|
629
|
+
</GridPointerContext.Provider>
|
|
367
630
|
)
|
|
368
631
|
})
|
|
369
632
|
DateGrid.displayName = 'DateGrid'
|
|
@@ -385,7 +648,7 @@ export const dateGridMeta = {
|
|
|
385
648
|
tokens: {
|
|
386
649
|
bg: ['bg-disabled', 'bg-neutral-selected', 'bg-on-emphasis', 'bg-primary', 'bg-primary-hover', 'bg-transparent'],
|
|
387
650
|
fg: ['text-fg-disabled', 'text-fg-muted', 'text-foreground', 'text-on-emphasis'],
|
|
388
|
-
ring: ['ring-
|
|
651
|
+
ring: ['focus-ring-inset', 'focus-ring-inset-emphasis'],
|
|
389
652
|
},
|
|
390
653
|
} as const
|
|
391
654
|
|