@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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tree-view.js","sources":["../../../src/components/TreeView/tree-view.tsx"],"sourcesContent":["// code-quality-allow: file-size — foundational composite(TreeView owns tree logic + TreeItem + drag-drop + keyboard;拆 sub-component 會把 register/unregister 跨檔傳 ref 複雜化超過可讀性 gain)\nimport * as React from 'react'\nimport * as CollapsiblePrimitive from '@radix-ui/react-collapsible'\nimport {\n DndContext,\n DragOverlay,\n useDraggable,\n useDroppable,\n PointerSensor,\n useSensor,\n useSensors,\n type DragStartEvent,\n type DragEndEvent,\n type DragOverEvent,\n} from '@dnd-kit/core'\nimport { ChevronRight } from 'lucide-react'\nimport { cva } from 'class-variance-authority'\nimport type { LucideIcon } from 'lucide-react'\nimport { dragSourceClass, dropIndicatorRow, dropIndicatorInside, DRAG_ACTIVATION_DISTANCE_PX } from '@/design-system/lib/drag-visual'\nimport { createDragAnnouncements, type DragOutcome } from '@/design-system/lib/drag-announcements'\nimport { cn } from '@/lib/utils'\nimport { useInputModality } from '@/design-system/hooks/use-input-modality'\nimport { Checkbox } from '@/design-system/components/Checkbox/checkbox'\n// Row primitive 共用常數——單一 source of truth\nimport {\n ICON_SIZE,\n RowSizeProvider,\n ItemIcon,\n ItemPrefix,\n ItemSuffix,\n ItemInlineAction,\n ROW_PADDING_BY_SIZE,\n type InlineActionConfig,\n} from '@/design-system/patterns/element-anatomy/item-anatomy'\n\n/**\n * TreeView — 階層結構的遞迴元件\n *\n * 一個 TreeItem 就是一個 node——有 children 就可展開,沒有就是 leaf。\n * 沒有第二個概念(沒有 TreeGroup)。\n *\n * TreeView 負責:\n * 1. 遞迴渲染 + indent\n * 2. 展開/收合狀態管理\n * 3. 鍵盤導覽 + ARIA tree\n *\n * 它不管 node 裡面長什麼樣——icon、badge、status indicator 等\n * 由 consumer 透過 props / slots 決定。\n *\n * 詳見 tree-view.spec.md。\n */\n\n// ═══════════════════════════════════════════════════════════════════════════\n// Types\n// ═══════════════════════════════════════════════════════════════════════════\n\ntype SizeKey = 'sm' | 'md' | 'lg'\ntype SelectionMode = 'single' | 'multiple' | 'none'\n/**\n * TreeView 的使用脈絡,決定 item 的水平 padding:\n * - `'sidebar'`:頁面側邊欄,用 `--layout-space-loose` token(md=16px / lg=24px,跟 density 連動)\n * - `'menu'`:浮層選單 / dropdown,取 `--item-px`(預設 12px),**對齊 MenuItem / DropdownMenu**\n */\ntype TreeContext = 'sidebar' | 'menu'\n\n// Base horizontal padding per context — 用 CSS variable 注入到 TreeView 容器,\n// TreeItem 用 calc(var(--tree-px) + indent) 算出最終 paddingLeft。\nconst CONTEXT_PX_VAR: Record<TreeContext, string> = {\n sidebar: 'var(--layout-space-loose)', // md=16px, lg=24px(density 連動)\n // 2026-09-17:原本是字面值 `'12px'` + 一句「對齊 MenuItem」的註解 —— 那是第二份,\n // 註解自己就是「它遲早會漂」的自白。改讀同一顆 token(預設 var(--field-px) = 12px,零視覺改動)。\n menu: 'var(--item-px, var(--field-px))', // 對齊 MenuItem / DropdownMenu(owner: item-anatomy.spec.md「Token: --item-px」)\n}\n\n/** Drag drop position — 拖放目標的三種位置 */\n// code-quality-allow: dead-export — public event/state type — consumer event handler parameter type\nexport type TreeDropPosition = 'before' | 'after' | 'inside'\n\n/** onDragEnd callback 的參數 */\n// code-quality-allow: dead-export — public event/state type — consumer event handler parameter type\nexport interface TreeDragEndEvent {\n /** 被拖曳的 node id */\n sourceId: string\n /** 目標 node id */\n targetId: string\n /** 放置位置:before(同層上方)/ after(同層下方)/ inside(成為子 node) */\n position: TreeDropPosition\n}\n\n/**\n * 鍵盤重排(`Cmd/Ctrl+Shift+方向鍵`,2026-07-14 v1)的 SR 播報文案 — zh-TW 預設,\n * per-key merge 覆寫(i18n 覆寫點)。詳 tree-view.spec.md「鍵盤重排」。\n * @public\n */\n// code-quality-allow: dead-export — public prop type — consumer 覆寫播報文案的參數型別\nexport interface TreeReorderAnnouncements {\n /** 同層上/下移完成。`index` / `count` = 移動後在同層的 1-based 序數 / 同層總數 */\n moved?: (args: { label: string; targetLabel: string; position: 'before' | 'after'; index: number; count: number }) => string\n /** 移入 folder 完成。folder 原本收合(children 尚未 mount)時序數不可知 → `index` / `count` 為 undefined */\n movedInside?: (args: { label: string; folderLabel: string; index?: number; count?: number }) => string\n /** 移出到上層完成。`level` = 移動後所在層(1-based,同 aria-level 語意) */\n movedOut?: (args: { label: string; parentLabel: string; level: number }) => string\n /** 邊界 no-op:top(已在最上方)/ bottom(已在最下方)/ not-folder(無可移入的 folder)/ root(已在最外層) */\n blocked?: (args: { reason: 'top' | 'bottom' | 'not-folder' | 'root'; label: string; targetLabel?: string }) => string\n /** sr-only 操作說明(tree 容器 `aria-describedby` 指向;僅 `draggable` 時渲染) */\n instructions?: string\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// Constants\n// ═══════════════════════════════════════════════════════════════════════════\n\n// Icon / chevron 尺寸——從 item-layout pattern module 引入(在檔頂 import),\n// 這裡本地不再宣告。所有 row primitives 共用同一個常數。\n\n// indentStep = chevronSize + gap-2(8px)。值 {24,24,28} 與 CSS token `--tree-indent-{sm,md,lg}`\n// (`tokens/uiSize/uiSize.css`)**完全一致、必須同步維護**(改一處要兩處一起改;非「token 取代 literal」)。\n// 為何兩源並存:本元件 render 用此 JS literal 算 `indentPx`(TreeItem inline calc 需 number)+ drop-indicator\n// pointer 數學(handleDragOver px 命中判斷,CSS var 在 JS 計算層拿不到);DataTable nested rows\n// 則走 CSS token 的 Tailwind class(跨元件視覺一致)。結構對齊:子 chevron 對齊父 icon,子 icon 對齊父 label。\nconst INDENT_STEP: Record<SizeKey, number> = { sm: 24, md: 24, lg: 28 }\n\n// ── 鍵盤重排(keyboard reorder,2026-07-14 v1)常數 ──\n// 鍵位 = Cmd/Ctrl+Shift+方向鍵,對齊 Notion 移動 block 鍵位(https://www.notion.com/help/keyboard-shortcuts,\n// WebFetch 驗證紀錄 .claude/logs/treeview-keyboard-dnd-design.md#L25);\n// modifier 層與 APG tree 導覽鍵(無 modifier 的 ↑↓→←)完全正交,零衝突。\nconst REORDER_ARROW_KEYS = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight']\ntype ReorderArrowKey = 'ArrowUp' | 'ArrowDown' | 'ArrowLeft' | 'ArrowRight'\n\n// 鍵盤重排 SR 播報預設文案(zh-TW;consumer 經 `reorderAnnouncements` prop per-key 覆寫)。\n// 格式 =「結果 + 序數位置」— 非視覺使用者靠「第 n 項,共 m 項」建立位置模型\n// (世界級 cite 詳 tree-view.spec.md「鍵盤重排」段,M22)。\n// i18n-allow-block: DS default SR-only 播報文案,prop 可覆寫(同 select-menu.tsx SelectMenuLiveStatus 先例)\nconst DEFAULT_REORDER_ANNOUNCEMENTS: Required<TreeReorderAnnouncements> = {\n moved: ({ label, targetLabel, position, index, count }) =>\n `已將『${label}』移到『${targetLabel}』${position === 'before' ? '之前' : '之後'},第 ${index} 項,共 ${count} 項`,\n movedInside: ({ label, folderLabel, index, count }) =>\n index !== undefined && count !== undefined\n ? `已將『${label}』移入『${folderLabel}』,第 ${index} 項,共 ${count} 項`\n : `已將『${label}』移入『${folderLabel}』`,\n movedOut: ({ label, parentLabel, level }) =>\n `已將『${label}』移出到『${parentLabel}』之後,第 ${level} 層`,\n blocked: ({ reason, targetLabel }) => {\n switch (reason) {\n case 'top': return '已在最上方'\n case 'bottom': return '已在最下方'\n case 'not-folder': return targetLabel ? `無法移入:『${targetLabel}』不是資料夾` : '無法移入'\n case 'root': return '已在最外層'\n }\n },\n instructions: '按 Cmd(Ctrl)+Shift+方向鍵可重新排列項目',\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// Context\n// ═══════════════════════════════════════════════════════════════════════════\n\ninterface TreeViewContextValue {\n size: SizeKey\n context: TreeContext\n selectionMode: SelectionMode\n expandOnSelect: boolean\n draggable: boolean\n isKeyboardRef: React.RefObject<boolean>\n /** 最近一次輸入是鍵盤(布林,隨模態變化觸發列 re-render;ref 身分不變不會) */\n keyboardModality: boolean\n /**\n * Per-tree instance 前綴(React.useId),用來組每個 treeitem 的 DOM `id`\n * (`${prefix}treeitem-${nodeId}`),讓容器的 `aria-activedescendant` 能指向目前 focused node。\n * 多棵 TreeView 同頁 / node id 跨樹重複時不會撞 DOM id。\n */\n activeDescendantPrefix: string\n expandedIds: Set<string>\n selectedIds: Set<string>\n focusedId: string | null\n /** 目前拖曳中的 node id(null = 沒在拖) */\n draggingId: string | null\n /** 目前 drop indicator 的位置 + depth(用於 line indent) */\n dropTarget: { id: string; position: TreeDropPosition; depth: number } | null\n toggleExpand: (id: string) => void\n select: (id: string) => void\n setFocusedId: (id: string | null) => void\n /** Pointer activation 後把 DOM focus 還給 virtual-focus tree root。 */\n focusTree: () => void\n registerNode: (id: string, parentId: string | null, hasChildren: boolean, label?: React.ReactNode, icon?: LucideIcon) => void\n getNodeInfo: (id: string) => NodeInfo | undefined\n unregisterNode: (id: string) => void\n}\n\nconst TreeViewContext = React.createContext<TreeViewContextValue | null>(null)\n\nfunction useTreeView(): TreeViewContextValue {\n const ctx = React.useContext(TreeViewContext)\n if (!ctx) throw new Error('TreeItem must be used within TreeView')\n return ctx\n}\n\n// TreeItem depth context(遞迴 depth tracking)\nconst DepthContext = React.createContext(0)\n\n// ═══════════════════════════════════════════════════════════════════════════\n// Node registry — 追蹤所有 node 的 parent/children 關係,用於鍵盤導覽\n// ═══════════════════════════════════════════════════════════════════════════\n\ninterface NodeInfo {\n id: string\n parentId: string | null\n hasChildren: boolean\n /** 用於 DragOverlay ghost 渲染 */\n label?: React.ReactNode\n icon?: LucideIcon\n}\n\nfunction useNodeRegistry() {\n const nodesRef = React.useRef(new Map<string, NodeInfo>())\n\n const registerNode = React.useCallback(\n (id: string, parentId: string | null, hasChildren: boolean, label?: React.ReactNode, icon?: LucideIcon) => {\n nodesRef.current.set(id, { id, parentId, hasChildren, label, icon })\n },\n []\n )\n\n const unregisterNode = React.useCallback((id: string) => {\n nodesRef.current.delete(id)\n }, [])\n\n const getNodeInfo = React.useCallback((id: string) => nodesRef.current.get(id), [])\n\n return { nodesRef, registerNode, unregisterNode, getNodeInfo }\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// TreeView\n// ═══════════════════════════════════════════════════════════════════════════\n\nexport interface TreeViewProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onDragEnd'> {\n /** 元件尺寸,影響 node 高度、icon 大小、indent 寬度 */\n size?: SizeKey\n /**\n * 使用脈絡,決定 item 的水平 padding:\n * - `'sidebar'`(預設):頁面側邊欄,`--layout-space-loose`(md=16px / lg=24px,隨 density 連動)\n * - `'menu'`:浮層選單 / dropdown,px-3(12px),對齊 MenuItem\n */\n context?: TreeContext\n /** 選取模式。預設 'single'(sidebar nav / stepper) */\n selectionMode?: SelectionMode\n /** 點擊 label 時是否同時展開 children。預設 false(chevron 是展開的唯一控件) */\n expandOnSelect?: boolean\n /** 受控:展開的 node id 集合 */\n expandedIds?: Set<string>\n /** 受控:展開狀態變更 callback */\n onExpandedChange?: (ids: Set<string>) => void\n /** 受控:選取的 node id 集合 */\n selectedIds?: Set<string>\n /** 受控:選取狀態變更 callback */\n onSelectedChange?: (ids: Set<string>) => void\n /** 非受控:預設展開的 node id 陣列 */\n defaultExpandedIds?: string[]\n /** 非受控:預設選取的 node id 陣列 */\n defaultSelectedIds?: string[]\n /**\n * 啟用拖曳排序。預設 false。\n * 啟用後整列可拖(Figma 風格,無 grip handle;靠 distance:5 區分 click vs drag),\n * 拖曳時顯示 drop indicator(before / after / inside 三種位置)。\n * Consumer 透過 `onDragEnd` callback 接收 reorder 事件,自行更新 data。\n */\n draggable?: boolean\n /** Drag 結束時觸發,提供 sourceId、targetId、position。Consumer 負責 reorder。 */\n onDragEnd?: (event: TreeDragEndEvent) => void\n /**\n * 鍵盤重排(`Cmd/Ctrl+Shift+方向鍵`)的 SR 播報文案覆寫(per-key merge;zh-TW 預設)。\n * 僅在 `draggable` 時生效;含 `instructions`(sr-only 操作說明)。\n * @public\n */\n reorderAnnouncements?: TreeReorderAnnouncements\n /** ARIA label */\n 'aria-label'?: string\n}\n\n// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding\nconst TreeView = React.forwardRef<HTMLDivElement, TreeViewProps>(\n (\n {\n size = 'md',\n context = 'sidebar',\n selectionMode = 'single',\n expandOnSelect = false,\n draggable = false,\n onDragEnd: onDragEndProp,\n reorderAnnouncements: reorderAnnouncementsProp,\n expandedIds: controlledExpanded,\n onExpandedChange,\n selectedIds: controlledSelected,\n onSelectedChange,\n defaultExpandedIds = [],\n defaultSelectedIds = [],\n className,\n children,\n ...props\n },\n ref\n ) => {\n // ── Accessible name dev-warn(2026-07-18:兌現 spec「Accessible name 必填契約」段承諾 —\n // 原 spec 宣稱有此 console.warn 但 code 缺,spec-code drift;補上使兩者一致)──\n // role=\"tree\" 名稱無法從子節點推導,缺 aria-label / aria-labelledby → SR 只讀「tree」。\n // 對齊 Button / Tag 的 dev-only 誤用警告 idiom;production 不觸發。\n const ariaLabel = props['aria-label']\n const ariaLabelledby = props['aria-labelledby']\n React.useEffect(() => {\n if (\n process.env.NODE_ENV !== 'production' &&\n ariaLabel == null &&\n ariaLabelledby == null\n ) {\n console.warn(\n '[DS] TreeView:role=\"tree\" 缺 accessible name — 請傳 aria-label(直接字串)或 aria-labelledby(指向可見標題 id)其一。' +\n '兩者皆缺時螢幕閱讀器只讀出「tree」無法辨識用途(WAI-ARIA APG 要求 role=\"tree\" 具 accessible name)。',\n )\n }\n }, [ariaLabel, ariaLabelledby])\n\n // ── Expand state(受控 / 非受控) ──\n const [internalExpanded, setInternalExpanded] = React.useState(\n () => new Set(defaultExpandedIds)\n )\n const expandedIds = controlledExpanded ?? internalExpanded\n const setExpandedIds = React.useCallback(\n (updater: (prev: Set<string>) => Set<string>) => {\n const update = (prev: Set<string>) => {\n const next = updater(prev)\n onExpandedChange?.(next)\n return next\n }\n if (controlledExpanded) {\n update(controlledExpanded)\n } else {\n setInternalExpanded(update)\n }\n },\n [controlledExpanded, onExpandedChange]\n )\n\n // ── Selection state(受控 / 非受控) ──\n const [internalSelected, setInternalSelected] = React.useState(\n () => new Set(defaultSelectedIds)\n )\n const selectedIds = controlledSelected ?? internalSelected\n const setSelectedIds = React.useCallback(\n (updater: (prev: Set<string>) => Set<string>) => {\n const update = (prev: Set<string>) => {\n const next = updater(prev)\n onSelectedChange?.(next)\n return next\n }\n if (controlledSelected) {\n update(controlledSelected)\n } else {\n setInternalSelected(update)\n }\n },\n [controlledSelected, onSelectedChange]\n )\n\n // ── Focus state ──\n const [focusedId, setFocusedId] = React.useState<string | null>(null)\n const treeRef = React.useRef<HTMLDivElement>(null)\n React.useImperativeHandle(ref, () => treeRef.current!)\n const focusTree = React.useCallback(() => {\n treeRef.current?.focus({ preventScroll: true })\n }, [])\n\n // ── Virtual focus id prefix ──\n // DOM focus 永遠停在 role=tree 容器(單一 tab stop);目前 node 透過 aria-activedescendant\n // 告知 AT(對齊 DS 既有 cmdk virtual-focus canonical:SelectMenu / Command listbox)。\n // useId 確保多棵 TreeView 同頁 / node id 跨樹重複時 DOM id 不撞。\n const activeDescendantPrefix = React.useId()\n\n // ── 鍵盤重排 SR 播報(2026-07-14 v1;詳 spec「鍵盤重排」)──\n // 單一 polite live region 覆寫式更新(textContent 替換非 append → 快速連按天然只播最新)。\n // 不可借 dnd-kit DndContext 內建 live region — 它只播 dnd-kit 管理的 drag session,\n // 自建鍵盤路徑觸不到。消費 select-menu.tsx SelectMenuLiveStatus role=\"status\" 先例。\n const [reorderAnnouncement, setReorderAnnouncement] = React.useState('')\n const announcements = React.useMemo(\n () => ({ ...DEFAULT_REORDER_ANNOUNCEMENTS, ...reorderAnnouncementsProp }),\n [reorderAnnouncementsProp]\n )\n // sr-only 操作說明節點 id(tree 容器 aria-describedby 指向;僅 draggable 渲染)\n const reorderInstructionsId = `${activeDescendantPrefix}tree-reorder-instructions`\n\n // ── Keyboard vs mouse detection ──\n // focus ring 只在鍵盤操作時顯示,滑鼠點擊用 bg-neutral-selected 表達選中,不顯示 ring。\n // 2026-09-08:判斷來源改為共用的 useInputModality(SelectMenu / DropdownMenu / AgentPanel 同款,\n // 原本四處各自實作 = 四份 SSOT)。ref 保留給 context 消費端讀,每次 render 由 hook 餵值;\n // 模態一變 hook 觸發 root re-render,子項在 render 期讀到的就是新值。\n const isKeyboardRef = React.useRef(false)\n const keyboardModality = useInputModality() === 'keyboard'\n isKeyboardRef.current = keyboardModality\n\n // ── Drag state ──\n const [draggingId, setDraggingId] = React.useState<string | null>(null)\n const [dropTarget, setDropTarget] = React.useState<{ id: string; position: TreeDropPosition; depth: number } | null>(null)\n const autoExpandTimerRef = React.useRef<ReturnType<typeof setTimeout> | null>(null)\n // 2026-05-16 audit codex Round 6:unmount cleanup(原 cleanup 只在 dragEnd/dragCancel,unmount-during-drag 漏 cancel)\n React.useEffect(() => () => { if (autoExpandTimerRef.current) clearTimeout(autoExpandTimerRef.current) }, [])\n // Ref for toggleExpand — handleDragOver 定義在 toggleExpand 之前(hook 順序限制),\n // 用 ref 打斷 temporal dead zone。\n const toggleExpandRef = React.useRef<(id: string) => void>(() => {})\n\n // ── Node registry ──(2026-07-14 上移至 drag handlers 之前:isInSubtree 消費 getNodeInfo,\n // 而 handleDragOver / handleDragEnd / 鍵盤 reorder 都消費 isInSubtree)\n const { registerNode, unregisterNode, getNodeInfo } = useNodeRegistry()\n\n // 子樹判定:candidate 是否位於 rootId 的子樹內(含相等)。沿 registry parentId 鏈上溯。\n // 雙路共用 SSOT:pointer 路徑 descendant drop guard(2026-07-14 R6 修:原 handleDragOver 只擋\n // `over.id === active.id` 未擋 descendants → 可發出 targetId ∈ source 子樹的非法事件,\n // consumer 按 remove→insert 實作會讓整個子樹靜默消失)+ 鍵盤 reorder commit 前防禦 assert。\n const isInSubtree = React.useCallback(\n (candidateId: string, rootId: string): boolean => {\n let cur: string | null = candidateId\n while (cur != null) {\n if (cur === rootId) return true\n cur = getNodeInfo(cur)?.parentId ?? null\n }\n return false\n },\n [getNodeInfo]\n )\n\n const sensors = useSensors(\n // 2026-09-07:5 → DRAG_ACTIVATION_DISTANCE_PX(8)。全 DS 原本三個值(8/5/8)加一處沒設,\n // 收斂到單一來源;8 是原本的多數,其中 AgentFab 那個 8 還是 user 實際用過調出來的。\n useSensor(PointerSensor, { activationConstraint: { distance: DRAG_ACTIVATION_DISTANCE_PX } })\n )\n\n const handleDragStart = React.useCallback((event: DragStartEvent) => {\n setDraggingId(String(event.active.id))\n }, [])\n\n // ── Figma-style drop detection(X + Y 雙軸)──\n //\n // Y 軸:決定在哪個 item 附近\n // - item 上 25% = before\n // - item 中 50% = inside(只有 folder)\n // - item 下 25% = after\n //\n // X 軸:決定 nesting 深度(Figma 核心邏輯)\n // - 滑鼠越左 = 越淺層(放在 parent 層級)\n // - 滑鼠越右 = 越深層(放進 folder)\n // - 用 pointer X 相對於 tree 左邊界計算 indent level\n //\n const handleDragOver = React.useCallback((event: DragOverEvent) => {\n const { over, active } = event\n // 2026-07-14 R6 descendant guard:over 是 active 自己「或其子樹內節點」→ 無效目標,\n // 不設 dropTarget(拖 folder 進自己子樹會發出非法 TreeDragEndEvent;guard SSOT = isInSubtree,\n // 鍵盤 reorder 路徑共用同一 helper)。\n if (!over || isInSubtree(String(over.id), String(active.id))) {\n if (autoExpandTimerRef.current) { clearTimeout(autoExpandTimerRef.current); autoExpandTimerRef.current = null }\n setDropTarget(null)\n return\n }\n\n const rowEl = document.querySelector(`[data-tree-row=\"${over.id}\"]`) as HTMLElement | null\n const targetEl = document.querySelector(`[data-tree-id=\"${over.id}\"]`) as HTMLElement | null\n if (!rowEl || !targetEl) { setDropTarget(null); return }\n\n // 實際指標位置\n const startX = (event.activatorEvent as PointerEvent)?.clientX ?? 0\n const startY = (event.activatorEvent as PointerEvent)?.clientY ?? 0\n const currentX = startX + (event.delta?.x ?? 0)\n const currentY = startY + (event.delta?.y ?? 0)\n\n const rect = rowEl.getBoundingClientRect()\n const offsetY = currentY - rect.top\n const height = rect.height || 32\n const ratio = Math.max(0, Math.min(1, offsetY / height))\n\n const hasChildren = targetEl.dataset.treeHasChildren === 'true'\n const targetDepth = Number(targetEl.getAttribute('aria-level') ?? 1) - 1\n\n // ── X 軸:計算指標在哪個 indent level ──\n const treeEl = treeRef.current\n const treeLeft = treeEl?.getBoundingClientRect().left ?? 0\n const indentStep = INDENT_STEP[size]\n const pointerIndentLevel = Math.max(0, Math.floor((currentX - treeLeft) / indentStep))\n\n let position: TreeDropPosition\n let finalDepth = targetDepth\n\n if (hasChildren) {\n // Folder node\n if (ratio < 0.25) {\n position = 'before'\n } else if (ratio > 0.75) {\n // after folder: 如果指標在 folder 層級或更淺 = after(同層)\n // 如果指標更深 = inside(放進 folder)\n position = pointerIndentLevel > targetDepth ? 'inside' : 'after'\n } else {\n position = 'inside'\n }\n } else {\n // Leaf node\n if (ratio < 0.5) {\n position = 'before'\n } else {\n position = 'after'\n // X 軸:如果指標在比 target 更淺的層級,提升 drop depth\n // 例:Contact(depth 1)的 after,如果滑鼠在 depth 0 → 變成「after Pages」\n if (pointerIndentLevel < targetDepth) {\n // 找 parent 來放\n const groupEl = targetEl.closest('[role=\"group\"]')\n const parentTreeItem = groupEl?.parentElement?.closest('[role=\"treeitem\"]')\n const parentId = parentTreeItem?.getAttribute('data-tree-id')\n if (parentId && parentId !== String(active.id)) {\n const parentDepth = Number(parentTreeItem?.getAttribute('aria-level') ?? 1) - 1\n finalDepth = parentDepth\n setDropTarget(prev => prev && prev.id === parentId && prev.position === 'after' && prev.depth === parentDepth ? prev : { id: parentId, position: 'after', depth: parentDepth })\n return\n }\n }\n }\n }\n\n // 2026-07-06 D3 perf:bail-out — drop target 未變(每 dragOver 跨 row 才變)時回傳 prev,\n // React 對相同 reference bail out,避免每次 over 事件重建 contextValue → 全樹 TreeItem re-render。\n setDropTarget(prev => {\n const id = String(over.id)\n return prev && prev.id === id && prev.position === position && prev.depth === finalDepth ? prev : { id, position, depth: finalDepth }\n })\n\n // Auto-expand collapsed folder after 500ms hover (Figma behavior)\n if (position === 'inside' && hasChildren && !expandedIds.has(String(over.id))) {\n if (autoExpandTimerRef.current) clearTimeout(autoExpandTimerRef.current)\n autoExpandTimerRef.current = setTimeout(() => {\n toggleExpandRef.current(String(over.id))\n }, 500)\n } else {\n if (autoExpandTimerRef.current) { clearTimeout(autoExpandTimerRef.current); autoExpandTimerRef.current = null }\n }\n }, [expandedIds, isInSubtree])\n\n const dropTargetRef = React.useRef(dropTarget)\n dropTargetRef.current = dropTarget\n\n // C2/B2 修(2026-09-07):TreeView 自有的繁中播報只有**鍵盤重排**那條路在寫,\n // 滑鼠拖曳走的是 dnd-kit 的英文預設 —— 實測整趟拖曳下來自有播報區維持空字串。\n // 這裡把指標路徑也接上,並誠實回報結果:守衛擋下(不合法 target / 子樹內)\n // 就播「未變更」而不是假的成功。共用 SSOT 見 `lib/drag-announcements.ts`。\n const dragOutcomeRef = React.useRef<DragOutcome | null>(null)\n const dndAnnouncements = React.useMemo(\n () => createDragAnnouncements({ getOutcome: () => dragOutcomeRef.current, kind: '項目' }),\n [],\n )\n\n const handleDragEnd = React.useCallback((event: DragEndEvent) => {\n dragOutcomeRef.current = null\n if (autoExpandTimerRef.current) { clearTimeout(autoExpandTimerRef.current); autoExpandTimerRef.current = null }\n const { active, over } = event\n const dt = dropTargetRef.current\n // descendant guard 同 handleDragOver(dt 已由 dragOver guard 保證為 null,此為 belt-and-braces)\n if (over && !isInSubtree(String(over.id), String(active.id)) && dt) {\n dragOutcomeRef.current = { kind: '項目', label: String(active.id) }\n onDragEndProp?.({\n sourceId: String(active.id),\n targetId: String(over.id),\n position: dt.position,\n })\n }\n setDraggingId(null)\n setDropTarget(null)\n }, [onDragEndProp, isInSubtree])\n\n const handleDragCancel = React.useCallback(() => {\n if (autoExpandTimerRef.current) { clearTimeout(autoExpandTimerRef.current); autoExpandTimerRef.current = null }\n setDraggingId(null)\n setDropTarget(null)\n }, [])\n\n // ── Actions ──\n const toggleExpand = React.useCallback(\n (id: string) => {\n setExpandedIds((prev) => {\n const next = new Set(prev)\n if (next.has(id)) next.delete(id)\n else next.add(id)\n return next\n })\n },\n [setExpandedIds]\n )\n toggleExpandRef.current = toggleExpand\n\n const select = React.useCallback(\n (id: string) => {\n if (selectionMode === 'none') return\n setSelectedIds((prev) => {\n if (selectionMode === 'single') {\n return new Set([id])\n }\n // multiple\n const next = new Set(prev)\n if (next.has(id)) next.delete(id)\n else next.add(id)\n return next\n })\n },\n [selectionMode, setSelectedIds]\n )\n\n // ── Keyboard reorder(Cmd/Ctrl+Shift+方向鍵,2026-07-14 v1)──\n // 設計 SSOT:tree-view.spec.md「鍵盤重排」。與 pointer 路徑共用 TreeDragEndEvent 契約 +\n // onDragEnd prop + registry(consumer API 零改動);每按一下立即 commit(無 grab-mode /\n // 無預覽,對齊 Notion Cmd/Ctrl+Shift+Arrow 移動 block:https://www.notion.com/help/keyboard-shortcuts,\n // WebFetch 驗證紀錄 .claude/logs/treeview-keyboard-dnd-design.md#L25)。\n // 不用 dnd-kit KeyboardSensor — 其 activator 需可接收 DOM focus\n // (https://dndkit.com/api-documentation/sensors/keyboard),與 row tabIndex={-1} +\n // aria-activedescendant 虛擬焦點模型結構性不相容(2026-07-05 D4 拍板不翻案)。\n\n // 播報用 label 文字:registry label 是 ReactNode — string 直接用,否則退 DOM row textContent。\n const getNodeLabelText = React.useCallback(\n (id: string): string => {\n const info = getNodeInfo(id)\n if (typeof info?.label === 'string') return info.label\n const rowEl = treeRef.current?.querySelector<HTMLElement>(`[data-tree-row=\"${id}\"]`)\n return rowEl?.textContent?.trim() || id\n },\n [getNodeInfo]\n )\n\n // code-quality-allow: long-function — 四鍵語意(同層上下 / 移入 / 移出)+ 邊界播報結構緊密,\n // 拆 sub-fn 會跨 fn 傳 siblings / announce state 反而複雜(同 handleKeyDown 先例)\n const handleKeyboardReorder = React.useCallback(\n (key: ReorderArrowKey) => {\n const tree = treeRef.current\n if (!tree || !focusedId) return\n const sourceId = focusedId\n const sourceEl = tree.querySelector<HTMLElement>(`[data-tree-id=\"${sourceId}\"]`)\n // disabled node 不可移(鍵盤導覽本就跳過 disabled,此為防禦 guard)\n if (!sourceEl || sourceEl.getAttribute('aria-disabled') === 'true') return\n\n const label = getNodeLabelText(sourceId)\n // 同層 siblings(DOM 序)。focused node 可見 ⇒ 其 parent 已展開 ⇒ 全 siblings 已 mount。\n const parentId = sourceEl.dataset.treeParentId || null\n const visibleItems = Array.from(\n tree.querySelectorAll<HTMLElement>('[role=\"treeitem\"]:not([hidden])')\n )\n const siblings = visibleItems.filter((el) => (el.dataset.treeParentId || null) === parentId)\n const sourceIndex = siblings.findIndex((el) => el.dataset.treeId === sourceId)\n if (sourceIndex < 0) return\n const isEnabled = (el: HTMLElement) => el.getAttribute('aria-disabled') !== 'true'\n\n // 發出與 pointer 路徑同型的 TreeDragEndEvent,並排定 re-render 後 scrollIntoView\n // (既有 effect 只在 isFocused 變化時跑;同 id 移動不 re-fire,故此處補)。\n const commit = (targetId: string, position: TreeDropPosition): boolean => {\n // 結構安全 assert:四鍵語意(sibling 間 / 進 prev sibling / 出 parent)結構上\n // 不可能移進自己子樹;防禦性仍擋(與 pointer descendant guard 共用 isInSubtree SSOT)。\n if (targetId === sourceId || isInSubtree(targetId, sourceId)) return false\n onDragEndProp?.({ sourceId, targetId, position })\n requestAnimationFrame(() => {\n treeRef.current\n ?.querySelector<HTMLElement>(`[data-tree-id=\"${sourceId}\"]`)\n ?.scrollIntoView({ block: 'nearest' })\n })\n return true\n }\n\n // 同層移動後的 1-based 新序數(播報用):移除 source 再插入的模擬;同層總數不變。\n const movedIndex = (targetId: string, position: 'before' | 'after'): number => {\n const order = siblings.map((el) => el.dataset.treeId).filter((id) => id !== sourceId)\n const tIdx = order.indexOf(targetId)\n return (position === 'before' ? tIdx : tIdx + 1) + 1\n }\n\n switch (key) {\n case 'ArrowUp':\n case 'ArrowDown': {\n // disabled sibling 不可當 target 錨點(pointer useDroppable 同樣 disable)——\n // 相鄰 sibling disabled 時錨到最近 enabled sibling 並翻轉 before/after 表達同一插槽\n // (可達位置與 pointer 一致);整個方向皆無 enabled sibling → 視同邊界。\n const dir = key === 'ArrowUp' ? -1 : 1\n const boundary = dir < 0 ? ('top' as const) : ('bottom' as const)\n const adjacent = siblings[sourceIndex + dir]\n if (!adjacent) {\n setReorderAnnouncement(announcements.blocked({ reason: boundary, label }))\n return\n }\n let target: HTMLElement | undefined\n let position: 'before' | 'after' = dir < 0 ? 'before' : 'after'\n if (isEnabled(adjacent)) {\n target = adjacent\n } else {\n for (let i = sourceIndex + dir; i >= 0 && i < siblings.length; i += dir) {\n if (isEnabled(siblings[i])) { target = siblings[i]; break }\n }\n position = dir < 0 ? 'after' : 'before' // 錨到最近 enabled sibling 的另一側 = 相鄰插槽\n }\n if (!target) {\n setReorderAnnouncement(announcements.blocked({ reason: boundary, label }))\n return\n }\n const targetId = target.dataset.treeId!\n if (commit(targetId, position)) {\n setReorderAnnouncement(\n announcements.moved({\n label,\n targetLabel: getNodeLabelText(targetId),\n position,\n index: movedIndex(targetId, position),\n count: siblings.length,\n })\n )\n }\n return\n }\n case 'ArrowRight': {\n // 移入:成為最近 enabled 上一 sibling 的子項(沿用 pointer「inside 限 folder」規則)\n let target: HTMLElement | undefined\n for (let i = sourceIndex - 1; i >= 0; i--) {\n if (isEnabled(siblings[i])) { target = siblings[i]; break }\n }\n if (!target) {\n setReorderAnnouncement(announcements.blocked({ reason: 'not-folder', label }))\n return\n }\n const targetId = target.dataset.treeId!\n if (target.dataset.treeHasChildren !== 'true') {\n setReorderAnnouncement(\n announcements.blocked({ reason: 'not-folder', label, targetLabel: getNodeLabelText(targetId) })\n )\n return\n }\n const wasExpanded = expandedIds.has(targetId)\n if (commit(targetId, 'inside')) {\n // 移入 collapsed folder:允許 + 自動展開(pointer 500ms hover auto-expand 的鍵盤對應物)\n if (!wasExpanded) toggleExpand(targetId)\n // 序數:consumer 對 inside 的慣例語意 = append 到 children 尾端;\n // folder 原收合 → children 未 mount 無從計數 → 播報省略序數(誠實不猜)。\n const childCount = wasExpanded\n ? visibleItems.filter((el) => el.dataset.treeParentId === targetId).length\n : undefined\n setReorderAnnouncement(\n announcements.movedInside({\n label,\n folderLabel: getNodeLabelText(targetId),\n index: childCount !== undefined ? childCount + 1 : undefined,\n count: childCount !== undefined ? childCount + 1 : undefined,\n })\n )\n }\n return\n }\n case 'ArrowLeft': {\n // 移出:成為 parent 的下一個 sibling(outdent)\n if (!parentId) {\n setReorderAnnouncement(announcements.blocked({ reason: 'root', label }))\n return\n }\n const parentEl = tree.querySelector<HTMLElement>(`[data-tree-id=\"${parentId}\"]`)\n const level = Number(parentEl?.getAttribute('aria-level') ?? '1')\n if (commit(parentId, 'after')) {\n setReorderAnnouncement(\n announcements.movedOut({ label, parentLabel: getNodeLabelText(parentId), level })\n )\n }\n return\n }\n }\n },\n [focusedId, expandedIds, toggleExpand, onDragEndProp, getNodeLabelText, isInSubtree, announcements]\n )\n\n // ── Context value ──\n const contextValue = React.useMemo<TreeViewContextValue>(\n () => ({\n size,\n context,\n selectionMode,\n expandOnSelect,\n draggable,\n isKeyboardRef,\n keyboardModality,\n activeDescendantPrefix,\n draggingId,\n dropTarget,\n expandedIds,\n selectedIds,\n focusedId,\n toggleExpand,\n select,\n setFocusedId,\n focusTree,\n registerNode,\n unregisterNode,\n getNodeInfo,\n }),\n [\n size,\n context,\n selectionMode,\n expandOnSelect,\n draggable,\n isKeyboardRef,\n keyboardModality,\n activeDescendantPrefix,\n draggingId,\n dropTarget,\n expandedIds,\n selectedIds,\n focusedId,\n toggleExpand,\n select,\n setFocusedId,\n focusTree,\n registerNode,\n unregisterNode,\n getNodeInfo,\n ]\n )\n\n // ── Keyboard handler ──\n // 2026-09-08:mousedown 不再手動改模態 —— 由 useInputModality 的 document 監聽統一處理\n const handleMouseDown = React.useCallback(() => {}, [])\n\n // code-quality-allow: long-function — helper fn 結構緊密,拆 sub-fn 會跨 fn 傳 state 反而複雜\n const handleKeyDown = React.useCallback(\n (e: React.KeyboardEvent) => {\n // 2026-09-08:模態由 useInputModality 判定(document capture 早於此 handler)\n if (!treeRef.current) return\n\n // ── 互動 descendant 自理鍵盤(先於導覽 / 重排分支)──\n // 公開 inlineActions / inlineActionsSlot 經 <ItemInlineAction> 渲染的原生 <button>\n // (及 consumer slot 內的 link / field / role=button)是各自獨立的 tab stop(spec\n // A11y「單一 tab stop」段)。其 Enter/Space/方向鍵應由該控件自理——若冒泡到容器\n // handleKeyDown,下方 Enter/Space 分支的 e.preventDefault()+select() 會吃掉 button\n // 原生啟動並誤選 tree node。虛擬焦點模型下 tree 導覽/重排只在事件來自容器本身時處理\n // (DOM focus 停在 tree 容器;treeitem row / chevron / checkbox 皆 tabIndex=-1)。\n const keyTarget = e.target as HTMLElement | null\n if (keyTarget && keyTarget !== e.currentTarget) {\n const interactive = keyTarget.closest<HTMLElement>(\n 'a[href], button, input, select, textarea, [contenteditable=\"true\"], [tabindex]:not([tabindex=\"-1\"])'\n )\n // interactive === e.currentTarget:唯一可聚焦祖先是 tree 容器(tabIndex=0 單一 tab\n // stop 進出點)→ 屬非互動 descendant,照常走 tree 導覽。\n if (interactive && interactive !== e.currentTarget) return\n }\n\n // ── 鍵盤重排:Cmd/Ctrl+Shift+方向鍵(2026-07-14 v1,詳 spec「鍵盤重排」)──\n // 必排在下方「無焦點時方向鍵先聚焦第一項」分支之前(該分支不分 modifier 攔 Arrow*);\n // 非 modifier 導覽路徑(下方 switch)完全不變。\n if ((e.metaKey || e.ctrlKey) && e.shiftKey && REORDER_ARROW_KEYS.includes(e.key)) {\n if (!draggable) return // 未啟用拖曳 → 整組 no-op(modifier 組合不落入導覽 switch)\n e.preventDefault() // 阻止瀏覽器原生行為(捲動 / 文字選取)\n if (draggingId !== null) return // pointer 拖曳進行中 → 互斥,忽略鍵盤重排\n handleKeyboardReorder(e.key as ReorderArrowKey)\n return\n }\n\n // 取得所有可見的 treeitem\n const items = Array.from(\n // a11y:排除 disabled 節點 → 鍵盤導覽/選取/展開全跳過(APG:disabled 不可鍵盤操作)\n treeRef.current.querySelectorAll<HTMLElement>('[role=\"treeitem\"]:not([hidden]):not([aria-disabled=\"true\"])')\n )\n const currentIndex = items.findIndex(\n (el) => el.dataset.treeId === focusedId\n )\n // a11y:沒焦點時任一方向鍵先聚焦第一個(含 ArrowLeft/Right,原漏 → AT 無 activedescendant 可讀)\n if (currentIndex < 0 && items.length > 0 && ['ArrowDown', 'ArrowUp', 'ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(e.key)) {\n // 沒有焦點時,任何方向鍵先聚焦第一個\n setFocusedId(items[0].dataset.treeId ?? null)\n e.preventDefault()\n return\n }\n\n const currentEl = items[currentIndex]\n\n switch (e.key) {\n case 'ArrowDown': {\n e.preventDefault()\n const next = items[currentIndex + 1]\n if (next) setFocusedId(next.dataset.treeId ?? null)\n break\n }\n case 'ArrowUp': {\n e.preventDefault()\n const prev = items[currentIndex - 1]\n if (prev) setFocusedId(prev.dataset.treeId ?? null)\n break\n }\n case 'ArrowRight': {\n e.preventDefault()\n const id = currentEl?.dataset.treeId\n if (!id) break\n const isExpanded = expandedIds.has(id)\n const hasChildren = currentEl?.dataset.treeHasChildren === 'true'\n if (hasChildren && !isExpanded) {\n toggleExpand(id)\n } else if (hasChildren && isExpanded) {\n // 已展開 → 移到第一個 child\n const next = items[currentIndex + 1]\n if (next) setFocusedId(next.dataset.treeId ?? null)\n }\n break\n }\n case 'ArrowLeft': {\n e.preventDefault()\n const id = currentEl?.dataset.treeId\n if (!id) break\n const isExpanded = expandedIds.has(id)\n const hasChildren = currentEl?.dataset.treeHasChildren === 'true'\n if (hasChildren && isExpanded) {\n toggleExpand(id)\n } else {\n // 收合狀態或 leaf → 移到 parent\n const parentId = currentEl?.dataset.treeParentId\n if (parentId) setFocusedId(parentId)\n }\n break\n }\n case 'Home': {\n e.preventDefault()\n if (items[0]) setFocusedId(items[0].dataset.treeId ?? null)\n break\n }\n case 'End': {\n e.preventDefault()\n const last = items[items.length - 1]\n if (last) setFocusedId(last.dataset.treeId ?? null)\n break\n }\n case 'Enter':\n case ' ': {\n e.preventDefault()\n const id = currentEl?.dataset.treeId\n if (id) select(id)\n break\n }\n }\n },\n [focusedId, expandedIds, toggleExpand, select, setFocusedId, draggable, draggingId, handleKeyboardReorder]\n )\n\n const treeEl = (\n <div\n // {...props} 在最前:內部 role/style(--tree-px)/onKeyDown/tabIndex 必須勝過 consumer\n // 誤傳(原 spread 在最後 → consumer style 會整組蓋掉 --tree-px、onKeyDown 蓋掉鍵盤導覽)\n {...props}\n ref={treeRef}\n role=\"tree\"\n aria-multiselectable={selectionMode === 'multiple' || undefined}\n // Virtual focus:DOM focus 停在容器(單一 tab stop),aria-activedescendant 指向目前 node\n // 的 DOM id,讓 AT 朗讀目前焦點 node(對齊 WAI-ARIA TreeView APG aria-activedescendant 模式)。\n aria-activedescendant={focusedId ? `${activeDescendantPrefix}treeitem-${focusedId}` : undefined}\n // 鍵盤重排操作說明(sr-only,僅 draggable;與 consumer 傳入的 aria-describedby 合併)\n aria-describedby={\n [props['aria-describedby'], draggable ? reorderInstructionsId : undefined]\n .filter(Boolean)\n .join(' ') || undefined\n }\n className={cn(\n // TreeView root 不加任何 py——呼吸空間由外層容器負責:\n // - 在 SidebarGroup 內: SidebarGroup py-2 提供\n // - 在 DropdownMenuContent 內: content py-2 提供\n // - 獨立使用(story demo): consumer 自己加 py-2\n // 這樣才能跟 DropdownMenu / MenuGroup 的結構一致(group 是容器,row 是內容)。\n 'flex flex-col',\n // 2026-09-07 H1f:DOM 焦點永遠停在這個 role=tree 容器(:369),視覺指示器畫在\n // aria-activedescendant 指到的那一列上。若不抑制,全域 base.css:44-47 會再給容器\n // 畫一圈 +2px 外框 → 同一次互動兩個焦點指示。\n // 條件寫成「有 aria-activedescendant 才抑制」:空樹(找不到任何 treeitem → 該屬性不渲染,\n // 見 :939)時全域框仍會畫,不會變成「聚焦了卻完全沒有指示」。\n // @focus-suppress A — A 虛擬游標;承擔者:指示器畫在 aria-activedescendant 指到的那一列(showRing → focus-ring-inset,:1381)\n '[&[aria-activedescendant]:focus-visible]:outline-none',\n className,\n )}\n style={{\n ['--tree-px' as string]: CONTEXT_PX_VAR[context],\n ...props.style,\n } as React.CSSProperties}\n onKeyDown={handleKeyDown}\n onMouseDown={handleMouseDown}\n // a11y APG:Tab 進入時初始化 virtual focus(優先 selected,其次第一個可用節點);\n // 原本無 onFocus init → focusedId=null → aria-activedescendant undefined,AT 讀不到目前節點\n onFocus={(e) => {\n ;(props as React.HTMLAttributes<HTMLDivElement>).onFocus?.(e)\n // 2026-09-07:Tab 進場時把鍵盤模式打開。\n // 原本 `isKeyboardRef` 只在**樹內** keydown 才變 true(:814),但 Tab 的 keydown\n // 發生在上一個元素上、根本不會傳到這裡 → 進場當下 `showRing`(:1174)恆為 false,\n // 於是 aria-activedescendant 已經指向某一列、那列卻沒有任何可見指示,\n // 違反 APG aria-activedescendant 模式(作者必須自己畫出目前節點)。\n // 判準用瀏覽器自己的 `:focus-visible` —— 它就是「這次聚焦該不該給可見指示」的權威答案:\n // 滑鼠按下進場時它不成立(且 mousedown 已先把 ref 設回 false),鍵盤進場才成立。\n // 2026-09-08:Tab 進場的鍵盤模態改由 useInputModality 判定(Tab 的 keydown 在 document\n // capture 就被記成鍵盤,不再依賴 :focus-visible 補位)。\n if (e.target === e.currentTarget && !focusedId && treeRef.current) {\n const first =\n treeRef.current.querySelector<HTMLElement>('[role=\"treeitem\"][aria-selected=\"true\"]:not([hidden]):not([aria-disabled=\"true\"])') ??\n treeRef.current.querySelector<HTMLElement>('[role=\"treeitem\"]:not([hidden]):not([aria-disabled=\"true\"])')\n if (first) setFocusedId(first.dataset.treeId ?? null)\n }\n }}\n tabIndex={0}\n >\n {children}\n </div>\n )\n\n return (\n <TreeViewContext.Provider value={contextValue}>\n {/* RowSizeProvider:讓 TreeView 子樹內任何 <ItemIcon> / <ItemAvatar> /\n <ItemInlineAction> 自動讀到對的 size,跟 SidebarProvider 同一條規則。\n inlineActions API 也吃這個 context。 */}\n <RowSizeProvider value={size}>\n {/* 永遠包 DndContext(hooks 不能 conditional call)。不 draggable 時無 sensors = 不可拖 */}\n <DndContext\n sensors={draggable ? sensors : undefined}\n onDragStart={handleDragStart}\n onDragOver={handleDragOver}\n onDragEnd={handleDragEnd}\n onDragCancel={handleDragCancel}\n accessibility={{ announcements: dndAnnouncements }}\n >\n {treeEl}\n {draggable && (\n <>\n {/* 鍵盤重排 SR 播報 — TreeView 自有 sr-only polite live region(單一節點覆寫式更新;\n 消費 select-menu.tsx SelectMenuLiveStatus 先例)。渲染在 role=\"tree\" 之外\n (tree 的合法 children 只有 treeitem / group);dnd-kit DndContext 內建 live region\n 只播 dnd-kit drag session,自建鍵盤路徑觸不到,故必須自有。 */}\n <div role=\"status\" aria-live=\"polite\" className=\"sr-only\">\n {reorderAnnouncement}\n </div>\n {/* 鍵盤重排操作說明 — tree 容器 aria-describedby 指向(對齊 dnd-kit\n screenReaderInstructions 慣例:https://dndkit.com/guides/accessibility) */}\n <div id={reorderInstructionsId} className=\"sr-only\">\n {announcements.instructions}\n </div>\n </>\n )}\n {draggable && (\n <DragOverlay dropAnimation={null}>\n {draggingId ? (() => {\n const info = getNodeInfo(draggingId)\n const IconComp = info?.icon\n return (\n <div className={cn(\n 'flex items-center gap-2 rounded-lg bg-surface border border-border pointer-events-none',\n 'shadow-[var(--elevation-200)]',\n size === 'lg' ? 'text-body-lg leading-compact px-4 py-2' : 'text-body leading-compact px-3 py-1.5',\n )}>\n {IconComp && <IconComp size={ICON_SIZE[size]} className=\"shrink-0\" aria-hidden />}\n <span className=\"text-foreground truncate max-w-[200px]\">{info?.label ?? draggingId}</span>\n </div>\n )\n })() : null}\n </DragOverlay>\n )}\n </DndContext>\n </RowSizeProvider>\n </TreeViewContext.Provider>\n )\n }\n)\nTreeView.displayName = 'TreeView'\n\n// ═══════════════════════════════════════════════════════════════════════════\n// TreeItem variants\n// ═══════════════════════════════════════════════════════════════════════════\n\nconst treeItemVariants = cva(\n [\n // items-start:多行 label 時 prefix 留在第一行(item-layout 規則)\n 'flex items-start gap-2 w-full',\n 'cursor-pointer select-none',\n // hover 底色瞬間切換,不做過渡(user 2026-09-10 拍板「第三題改成全部瞬間」;SSOT = tokens/motion/motion.spec.md「hover 回饋不做過渡」)\n // 2026-09-07 刪 `outline-none`:虛擬游標(showRing → focus-ring-inset)也寫 outline,\n // 兩者特異性同階,留著等於讓「誰贏」取決於 Tailwind 的排序。這一列本來就不可聚焦\n // (tabIndex 在 li 上且為 -1),不需要防禦性抑制。\n // Label 字重 500(跟 SidebarMenuButton 一致)\n 'font-medium',\n ],\n {\n variants: {\n // 消費 ROW_PADDING_BY_SIZE SSOT(item-anatomy.tsx)— drift risk 消除\n size: ROW_PADDING_BY_SIZE,\n },\n defaultVariants: {\n size: 'md',\n },\n }\n)\n\n// ═══════════════════════════════════════════════════════════════════════════\n// TreeItem\n// ═══════════════════════════════════════════════════════════════════════════\n\nexport interface TreeItemProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'id'> {\n /** 唯一 id。必填,用於 expand / select / keyboard 追蹤 */\n id: string\n /** 主要文字 */\n label: React.ReactNode\n /** 左側 icon(chevron 之後)。LucideIcon 型別,尺寸由 TreeView size 決定 */\n icon?: LucideIcon\n /**\n * Checkbox(多選模式,label 前方)。傳入 ReactNode(Checkbox 元件)。\n * 位置:在 chevron 之後、indicator/icon 之前。\n * 此 slot 僅是 treeitem selection 的視覺鏡像;有效 React element 會由 TreeItem\n * 強制正規化為 `aria-hidden` + `tabIndex=-1`,不形成第二個 node tab stop。\n * 單選模式通常不需要(用 bg-neutral-selected 表達選中)。\n */\n checkbox?: React.ReactNode\n /**\n * 右側 inline actions(suffix slot,宣告式 API)。對齊 `patterns/element-anatomy/inline-action.spec.md`\n * 與 `SidebarMenuButton.inlineActions` 的同一條規格——TreeItem / SidebarMenuButton /\n * 未來的 row primitive 全部用同一個 declarative API。\n *\n * Consumer 只宣告 intent,TreeItem 用 `<ItemInlineAction>` 自動渲染:\n * - Icon 尺寸 = `ICON_SIZE[treeViewSize]`(自動)\n * - Hover bg、tooltip、aria-label、cursor-pointer 自動處理\n * - **不可以**手刻 button JSX(canonical 實作在 `patterns/element-anatomy/item-anatomy.tsx` `ItemInlineAction`)\n *\n * ```tsx\n * <TreeItem\n * id=\"inbox\"\n * icon={Inbox}\n * label=\"Inbox\"\n * inlineActions={[\n * { icon: MoreVertical, label: '更多', onClick: handleMore },\n * { icon: Plus, label: '新增', onClick: handleAdd },\n * ]}\n * actionsReveal=\"hover\"\n * />\n * ```\n *\n * 若需要永遠可見的 suffix(如 badge 計數),放在 `label` 內:\n * ```tsx\n * <TreeItem label={<>Inbox <Badge count={3} /></>} />\n * ```\n */\n inlineActions?: InlineActionConfig[]\n /**\n * 右側 actions slot(ReactNode)— escape hatch 供 consumer 放自訂元素\n * (如 DropdownMenu trigger / 自訂 popover / 多 tier 動作)。\n *\n * 跟 `inlineActions` 互斥(同時傳 `inlineActionsSlot` 會優先,`inlineActions` 被忽略)。\n *\n * 規則對齊 Input.endSlot canonical:90% case 用 `inlineActions` 宣告式 API,\n * 10% config 表達不出時走 slot。視覺一致性由 consumer 負責(可使用 host 內部 helper\n * — 但禁止 app-code 直接 import L3 primitive,見 `check_canonical_propagation.sh` E.2,原 `check_l3_primitive_import` 已 folded)。\n */\n inlineActionsSlot?: React.ReactNode\n /**\n * Inline actions 的顯示模式:\n * - `\"hover\"`(預設):row hover 或鍵盤 focus(focus-visible)時才淡入\n * - `false`:常駐顯示\n *\n * 對齊 `SidebarMenuButton.actionsReveal`,同一套規則。\n */\n actionsReveal?: false | \"hover\"\n /**\n * 取代 icon 的位置。用於 stepper 的 status indicator(●/○/✓)。\n * 設定後 icon 不渲染、改渲染 indicator;chevron 永遠保留(expandable=旋轉箭頭 / leaf=placeholder)。\n */\n indicator?: React.ReactNode\n /** 是否停用 */\n disabled?: boolean\n /** 子 TreeItem(有 children = expandable,沒有 = leaf) */\n children?: React.ReactNode\n}\n\n// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding\nconst TreeItem = React.forwardRef<HTMLDivElement, TreeItemProps>(\n ({ id, label, icon: Icon, checkbox, inlineActions, inlineActionsSlot, actionsReveal = 'hover', indicator, disabled, children, className, style, onClick, ...props }, ref) => {\n const ctx = useTreeView()\n const depth = React.useContext(DepthContext)\n const {\n size,\n selectionMode,\n expandOnSelect,\n draggable,\n expandedIds,\n selectedIds,\n focusedId,\n draggingId,\n dropTarget,\n toggleExpand,\n select,\n setFocusedId,\n focusTree,\n registerNode,\n unregisterNode,\n keyboardModality,\n activeDescendantPrefix,\n } = ctx\n\n const hasChildren = React.Children.count(children) > 0\n const isExpanded = expandedIds.has(id)\n const isSelected = selectedIds.has(id)\n const isFocused = focusedId === id\n const showRing = isFocused && keyboardModality\n const isDragging = draggingId === id\n const isDropTarget = dropTarget?.id === id\n const visualCheckbox = checkbox && React.isValidElement<Record<string, unknown>>(checkbox)\n ? React.cloneElement(checkbox, { 'aria-hidden': true, tabIndex: -1 })\n : checkbox\n\n const iconPx = ICON_SIZE[size]\n const indentPx = depth * INDENT_STEP[size]\n\n // ── Drag hooks ──\n // Figma 風格:整列可拖(不用 grip handle),靠 distance:5 區分 click vs drag\n const { attributes: dragAttrs, listeners: dragListeners, setNodeRef: setDragRef } = useDraggable({\n id, disabled: !draggable || disabled,\n })\n const { setNodeRef: setDropRef } = useDroppable({\n id, disabled: !draggable || disabled,\n })\n\n // ── 找 parent id(from depth context chain)──\n const parentId = React.useContext(ParentIdContext)\n\n // ── Register / unregister ──\n React.useEffect(() => {\n registerNode(id, parentId, hasChildren, label, Icon)\n return () => unregisterNode(id)\n }, [id, parentId, hasChildren, label, Icon, registerNode, unregisterNode])\n\n // ── Focus scroll into view ──\n const itemRef = React.useRef<HTMLDivElement>(null)\n React.useImperativeHandle(ref, () => itemRef.current!)\n\n React.useEffect(() => {\n if (isFocused && itemRef.current) {\n itemRef.current.scrollIntoView({ block: 'nearest' })\n }\n }, [isFocused])\n\n // ── Handlers ──\n const handleRowClick = React.useCallback(\n (e: React.MouseEvent<HTMLDivElement>) => {\n onClick?.(e)\n if (e.defaultPrevented || disabled) return\n e.stopPropagation()\n // Virtual-focus contract:DOM focus 必留在 tree root。先 focus 再設定 clicked id,\n // 避免 root onFocus 的「初次進入選第一項」初始化覆寫 pointer target。\n focusTree()\n setFocusedId(id)\n select(id)\n if (expandOnSelect && hasChildren) {\n toggleExpand(id)\n }\n },\n [id, disabled, onClick, focusTree, select, setFocusedId, expandOnSelect, hasChildren, toggleExpand]\n )\n\n const handleChevronClick = React.useCallback(\n (e: React.MouseEvent) => {\n e.stopPropagation()\n if (disabled) return\n focusTree()\n setFocusedId(id)\n toggleExpand(id)\n },\n [id, disabled, focusTree, setFocusedId, toggleExpand]\n )\n\n // dnd-kit PointerSensor listener 與 consumer DOM callback 必安全 compose:consumer 先收到\n // event,可用 preventDefault 取消 drag;未取消才執行 internal listener。`...props` 會先\n // spread,這份 composed map 隨後覆蓋同名 pointer listener,避免任一方被靜默吃掉。\n const composedDragListeners = dragListeners\n ? Object.fromEntries(\n Object.entries(dragListeners).map(([eventName, internalHandler]) => [\n eventName,\n (event: React.SyntheticEvent<HTMLElement>) => {\n const consumerHandler = (props as Record<string, unknown>)[eventName]\n if (typeof consumerHandler === 'function') {\n ;(consumerHandler as (event: React.SyntheticEvent<HTMLElement>) => void)(event)\n }\n if (!event.defaultPrevented) {\n ;(internalHandler as (event: React.SyntheticEvent<HTMLElement>) => void)(event)\n }\n },\n ])\n ) as typeof dragListeners\n : undefined\n\n // ── Chevron(永遠存在:expandable = 旋轉箭頭;leaf = placeholder 佔位) ──\n // 消費 `<ItemPrefix>` SSOT — 永遠 h-[1lh] 對齊 label 第一行中線(item-anatomy 對應)。\n // forced width 透過 style 鎖 chevron 槽寬,讓 sibling label 起點水平對齊(無 chevron leaf 佔位同寬)。\n const chevronSlot = (\n <ItemPrefix style={{ width: iconPx }}>\n {hasChildren ? (\n <button\n type=\"button\"\n tabIndex={-1}\n onClick={handleChevronClick}\n className={cn(\n 'flex items-center justify-center rounded-md',\n 'text-fg-muted hover:text-fg-secondary hover:bg-neutral-hover',\n // hover 底色瞬間切換,不做過渡(user 2026-09-10 拍板「第三題改成全部瞬間」;SSOT = tokens/motion/motion.spec.md「hover 回饋不做過渡」);只留展開箭頭的旋轉\n 'transition-transform duration-150 motion-reduce:duration-0',\n isExpanded && 'rotate-90',\n disabled && 'text-fg-disabled pointer-events-none',\n )}\n style={{ width: iconPx, height: iconPx }}\n aria-hidden\n >\n <ChevronRight size={iconPx} />\n </button>\n ) : (\n // Leaf placeholder\n <span style={{ width: iconPx }} aria-hidden />\n )}\n </ItemPrefix>\n )\n\n return (\n <ParentIdContext.Provider value={id}>\n <div\n ref={(node) => {\n (itemRef as React.MutableRefObject<HTMLDivElement | null>).current = node\n if (typeof ref === 'function') ref(node)\n else if (ref) (ref as React.MutableRefObject<HTMLDivElement | null>).current = node\n }}\n // DOM id 供容器 aria-activedescendant 指向(virtual focus);與 data-tree-id 並存\n // (data-tree-id 給內部 querySelector / drag,id 給 AT)。\n id={`${activeDescendantPrefix}treeitem-${id}`}\n role=\"treeitem\"\n aria-expanded={hasChildren ? isExpanded : undefined}\n aria-selected={selectionMode !== 'none' ? isSelected : undefined}\n aria-level={depth + 1}\n aria-disabled={disabled || undefined}\n // 2026-07-05 D4 修:aria-roledescription 移到 treeitem(有 role 的元素才合法)—\n // 供 AT 提示此 node 可拖曳;值消費 dnd-kit attributes SSOT(預設 'draggable')。\n aria-roledescription={draggable && !disabled ? dragAttrs['aria-roledescription'] : undefined}\n data-tree-id={id}\n data-tree-parent-id={parentId ?? ''}\n data-tree-has-children={hasChildren}\n tabIndex={-1}\n className={cn('w-full min-w-0 relative', isDragging && dragSourceClass)}\n >\n {/* Drop indicator — before:水平 2px primary line(指 SSOT drag-visual.ts);\n indent 跟隨 depth(left 由 inline style override class 的 left-0)*/}\n {isDropTarget && dropTarget?.position === 'before' && (\n <div\n className={dropIndicatorRow.before}\n style={{ left: `calc(var(--tree-px) + ${indentPx}px)` }}\n />\n )}\n\n {/* Row: draggable + droppable 都在這一行(合併 ref),確保碰撞偵測只看行高 */}\n <div\n {...props}\n ref={(node) => {\n // 合併 drag + drop ref 到同一個 element\n if (draggable) setDragRef(node)\n setDropRef(node)\n }}\n data-tree-row={id}\n className={cn(\n 'group/tree-item',\n treeItemVariants({ size }),\n // 2026-05-26 SSOT lock(user explicit「multi 已有 checkbox 強信號,text 不該再變色」):\n // ── Single mode ──\n // - default text 預設 fg-secondary muted(hierarchy navigation 慣例,跟 Sidebar 一致)\n // - selected → text-foreground emphasis + bg-neutral-selected(無 checkbox,需 text+bg 雙信號)\n // ── Multi mode ──\n // - default text 維持 fg-secondary muted(跟 single 對齊 hierarchy)\n // - selected → 視覺信號只在 checkbox(auto-render below),text 不變、bg 不變\n // - 對齊 SelectMenu multi pattern(menu-item.tsx:194-195 selected → bg only;multi → checkbox only)\n // multi-selected 也維持 fg-secondary(上方註解「text 不變」;原 !isSelected 條件讓 multi-selected 掉到繼承色)\n !disabled && (!isSelected || selectionMode === 'multiple') && 'text-fg-secondary',\n !disabled && isSelected && selectionMode === 'single' && 'text-foreground',\n !disabled && 'hover:bg-neutral-hover hover:text-foreground',\n // 2026-08-11 修偏移(SSOT = item-anatomy「選中 × 互動疊加」):先前 hover:bg-neutral-hover\n //(0,2,0)蓋掉無修飾的 bg-neutral-selected(0,1,0)→ 選中列 hover 反而變淺 = bug。\n // 釘住 hover 不變(twMerge 同組後者勝)+ 鍵盤焦點深一階 -focus。\n // 2026-09-06:移除 `focus-visible:bg-neutral-selected-focus`。TreeView 是**虛擬焦點**元件\n //(tree 根 tabIndex=0 :959 / treeitem tabIndex=-1 :1292 / 本 row div 無 tabIndex,\n // 全 DS 131 處 <TreeItem> 用法 0 處傳 tabIndex),該列永遠不是 DOM 焦點,\n // `:focus-visible` 恆不 match —— 那行自 2026-08-11 加入起從未生效。\n // 而且本元件的鍵盤游標**已經由下一行的 ring 表達**(showRing,:1154),再深一階是多餘的。\n // 判準見 item-anatomy.spec.md「虛擬焦點以 not-hover: 分流、真焦點用 focus-visible:」。\n !disabled && isSelected && selectionMode === 'single' && 'bg-neutral-selected hover:bg-neutral-selected',\n // 落點底色排在所有其他 `bg-*` 之後 —— twMerge 同組後者勝。\n // 2026-09-06 修:原本排在 `hover:bg-neutral-hover` 與 `bg-neutral-selected` **之前**,\n // 造成兩個都會發生的 bug —— (1) 已選中的列 twMerge 直接把 `bg-drop-target` 刪掉,\n // 實跑 tailwind-merge 3.5.0 輸出只剩 `hover:text-foreground bg-neutral-selected\n // hover:bg-neutral-selected`;(2) 未選中的列兩個 class 都留著,但 hover 特異性較高、\n // 游標又必然停在該列上,落點底色一樣看不到。`dropIndicatorInside` 自帶 `hover:` 同色治 (2),\n // 這行的位置治 (1)。\n isDropTarget && dropTarget?.position === 'inside' && dropIndicatorInside,\n // 2026-09-07 遷到 outline 通道(全 DS 兩種幾何的 SSOT,base.css @utility)。\n // **不加 `focus-visible:`** —— 這一列永遠不是 DOM 焦點(焦點停在 role=tree 容器,\n // 見 :369),加了變體會變成永不生效的死用法(TimePicker 就是這樣死的)。\n // 順帶修掉 box-shadow 通道的既有缺陷:高對比模式下 box-shadow 被強制 none,\n // 原本的 ring 在那個模式完全看不見;outline 會照畫。\n showRing && 'focus-ring-inset',\n disabled && 'pointer-events-none text-fg-disabled cursor-default',\n className,\n )}\n style={{\n ...style,\n // Structural indent 由 TreeView SSOT 最終擁有,consumer style 不得抹掉。\n paddingLeft: indentPx > 0\n ? `calc(var(--tree-px) + ${indentPx}px)`\n : 'var(--tree-px)',\n paddingRight: 'var(--tree-px)',\n }}\n // 2026-07-05 D4 修:只 spread listeners,不 spread dnd-kit attributes —\n // useDraggable 預設 attributes 注入 role=\"button\" + tabIndex=0 + aria-pressed +\n // aria-describedby(鍵盤拖曳指示):role=button 污染 treeitem 語意、tabIndex=0\n // 讓每 row 變 DOM tab stop 破壞單一 tab stop 虛擬焦點模型(DOM focus 在 row 上按\n // Enter 會 bubble 到容器 handleKeyDown 但 handler 操作 state focusedId → 錯位);\n // sensors 僅 PointerSensor(無 KeyboardSensor),這些 attrs 換不到鍵盤拖曳能力;\n // 鍵盤重排(2026-07-14 v1)走容器 handleKeyDown 的 Cmd/Ctrl+Shift+Arrow 分支,非 dnd-kit sensor。\n // aria-roledescription 保留在上方 treeitem 元素(有 role 才合法)。\n {...(draggable ? composedDragListeners : {})}\n onClick={handleRowClick}\n >\n {chevronSlot}\n\n {/* Checkbox 在 icon 前——消費 `<ItemPrefix>` 對齊第一行\n * 2026-05-26 SSOT lock(user explicit「多選的方式應該也是要跟 menu 一樣是出現 checkbox」):\n * - selectionMode='multiple' + 無 consumer checkbox prop → auto-render `<Checkbox>` reflect selectedIds\n * (對齊 SelectMenu multi pattern;consumer 不用手寫 checkbox)\n * - selectionMode='multiple' + consumer 傳 checkbox → 用 consumer 的(parent-child cascade 等 advanced)\n * - selectionMode='single' / 'none' → 不 render checkbox(text-foreground + bg 雙信號表 selected)\n * 對齊 cite:menu-item.tsx:194-195(MenuItem selected bg)+ select-menu.tsx:352-354(SelectMenu multi=checkbox) */}\n {(checkbox || selectionMode === 'multiple') && (\n <ItemPrefix className=\"pointer-events-none\">\n {/* 2026-07-05 D4 修:auto-render Checkbox 補 tabIndex={-1} — Radix Checkbox root\n * 是原生 button(預設可聚焦),aria-hidden + 可聚焦 = axe aria-hidden-focus,且\n * 破壞 tree 單一 tab stop 虛擬焦點模型(tree-view.spec.md「Focus」段);\n * ItemPrefix pointer-events-none 只擋滑鼠不擋鍵盤。選取語意由 treeitem\n * aria-selected 承載,checkbox 純視覺反映。 */}\n {visualCheckbox || <Checkbox checked={isSelected} disabled={disabled} aria-hidden=\"true\" tabIndex={-1} />}\n </ItemPrefix>\n )}\n\n {/* indicator 取代 icon 的位置;h-[1lh] 對齊第一行\n indicator 是 escape hatch(stepper status dot 等客製內容),消費 `<ItemPrefix>` 鎖 chevron 槽寬;\n Icon 走 canonical `<ItemIcon>` helper——自動標 data-prefix-type=\"icon\",\n 讓 SidebarProvider 的全域 :has() prefix-mix 偵測能命中。 */}\n {indicator ? (\n <ItemPrefix style={{ width: iconPx }}>\n {indicator}\n </ItemPrefix>\n ) : Icon ? (\n <ItemIcon icon={Icon} className={disabled ? 'text-fg-disabled' : undefined} />\n ) : null}\n\n <span className={cn('flex-1 min-w-0 truncate', disabled && 'text-fg-disabled')}>\n {label}\n </span>\n\n {/* Suffix inline actions——宣告式 API,用 `<ItemInlineAction>` 渲染。\n 消費 `<ItemSuffix hoverReveal hoverGroup=\"tree-item\">` SSOT(2026-05-05 v8 group selector 參數化後)。\n actionsReveal=\"hover\"(預設):row hover 或 keyboard focus-visible 才顯示;\n actionsReveal=false:常駐顯示。跟 SidebarMenuButton 共用同一條規則,行為一致。\n inlineActionsSlot escape hatch 優先(consumer 自控 JSX,reveal 一樣套外層 group)。 */}\n {/* 2026-06-12 R2(同 sidebar.tsx 修):宿主 disabled 時 render 層擋 inline actions —\n inline-action.spec.md「宿主 disabled | 不渲染」;row pointer-events 蓋不住\n actionsReveal=false 常駐顯示的視覺暗示,必須 render 層 guard。 */}\n {disabled ? null : inlineActionsSlot ? (\n <ItemSuffix hoverReveal={actionsReveal === 'hover'} hoverGroup=\"tree-item\">\n {inlineActionsSlot}\n </ItemSuffix>\n ) : inlineActions && inlineActions.length > 0 ? (\n <ItemSuffix hoverReveal={actionsReveal === 'hover'} hoverGroup=\"tree-item\">\n {inlineActions.map((action, i) => (\n <ItemInlineAction key={action.label + i} action={action} />\n ))}\n </ItemSuffix>\n ) : null}\n </div>\n\n {/* Drop indicator — after:同 before mirror 到 bottom edge(SSOT drag-visual.ts)*/}\n {isDropTarget && dropTarget?.position === 'after' && (\n <div\n className={dropIndicatorRow.after}\n style={{ left: `calc(var(--tree-px) + ${indentPx}px)` }}\n />\n )}\n\n {/* Children: Collapsible 展開/收合 */}\n {hasChildren && (\n <CollapsiblePrimitive.Root open={isExpanded}>\n <CollapsiblePrimitive.Content\n className=\"overflow-hidden data-[state=closed]:animate-collapsible-up data-[state=open]:animate-collapsible-down motion-reduce:animate-none\"\n >\n <DepthContext.Provider value={depth + 1}>\n <div role=\"group\" className=\"flex flex-col w-full\">\n {children}\n </div>\n </DepthContext.Provider>\n </CollapsiblePrimitive.Content>\n </CollapsiblePrimitive.Root>\n )}\n </div>\n </ParentIdContext.Provider>\n )\n }\n)\nTreeItem.displayName = 'TreeItem'\n\n// Parent ID context for keyboard navigation (← to parent)\nconst ParentIdContext = React.createContext<string | null>(null)\n\n// ═══════════════════════════════════════════════════════════════════════════\n// Exports\n// ═══════════════════════════════════════════════════════════════════════════\n\n// Story auto-compile metadata — Phase 1 mechanical migration(2026-04-24)\n// Phase 2 fill needed: purpose descriptions + when rationale + world-class refs\nexport const treeViewMeta = {\n component: 'TreeView',\n family: 1, // Family 1(Menu item layout)消費者 — 對齊 tree-view.spec.md frontmatter family: 1\n variants: {\n\n },\n sizes: {\n // 對齊 tree-view.spec.md frontmatter(compile-stories --check 驗 key 一致)\n sm: { typography: 'body', indent: 24 },\n md: { typography: 'body', indent: 24 }, // default\n lg: { typography: 'body-lg', indent: 28 },\n },\n // 'selected' = single-selection 持續選中(bg-neutral-selected + aria-selected);'active' 移除 —\n // 全檔無 Tailwind 按壓 utility,無按壓專屬視覺態(2026-07-07 詞彙統一對抗稽核補修)。\n states: ['default', 'hover', 'selected', 'focus-visible', 'disabled'],\n tokens: {\n bg: ['bg-neutral-hover', 'bg-neutral-selected', 'bg-surface'],\n fg: ['text-fg-disabled', 'text-fg-muted', 'text-fg-secondary', 'text-foreground'],\n ring: ['ring-ring'],\n },\n defaultSize: 'md',\n} as const\n\nexport { TreeView, TreeItem, treeItemVariants }\n"],"names":["treeEl"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"tree-view.js","sources":["../../../src/components/TreeView/tree-view.tsx"],"sourcesContent":["// code-quality-allow: file-size — foundational composite(TreeView owns tree logic + TreeItem + drag-drop + keyboard;拆 sub-component 會把 register/unregister 跨檔傳 ref 複雜化超過可讀性 gain)\nimport * as React from 'react'\nimport * as CollapsiblePrimitive from '@radix-ui/react-collapsible'\nimport {\n DndContext,\n DragOverlay,\n useDraggable,\n useDroppable,\n PointerSensor,\n useSensor,\n useSensors,\n type DragStartEvent,\n type DragEndEvent,\n type DragOverEvent,\n} from '@dnd-kit/core'\nimport { ChevronRight } from 'lucide-react'\nimport { cva } from 'class-variance-authority'\nimport type { LucideIcon } from 'lucide-react'\nimport { dragSourceClass, dropIndicatorRow, dropIndicatorInside, DRAG_ACTIVATION_DISTANCE_PX } from '@/design-system/lib/drag-visual'\nimport { type DragOutcome, useDragAccessibility } from '@/design-system/lib/drag-announcements'\nimport { cn } from '@/lib/utils'\nimport { Checkbox } from '@/design-system/components/Checkbox/checkbox'\n// 「列上有小按鈕的一串」鍵盤路線的唯一判定與執行(與 Sidebar / FileUpload / Command 共用;判定表 scripts/test-roving-list-keyboard.mjs\n// + 樹專屬格 scripts/test-tree-keyboard-route.mjs;SSOT tree-view.spec.md「鍵盤導覽」,總帳 B9 + 〇節「按鍵規則合併」)\nimport {\n ROVING_CONTROL_SELECTOR,\n applyRovingAction,\n isTextEntryElement,\n listRovingControls,\n pickRovingTabStop,\n removeRovingControlsFromTabOrder,\n resolveRovingKey,\n type RovingReorderKey,\n} from '@/design-system/lib/roving-list-keyboard'\n// Row primitive 共用常數——單一 source of truth\nimport {\n ICON_SIZE,\n RowSizeProvider,\n ItemIcon,\n ItemPrefix,\n ItemSuffix,\n ItemInlineAction,\n ItemInlineActionButton,\n ROW_PADDING_BY_SIZE,\n type InlineActionConfig,\n} from '@/design-system/patterns/element-anatomy/item-anatomy'\n\n/**\n * TreeView — 階層結構的遞迴元件\n *\n * 一個 TreeItem 就是一個 node——有 children 就可展開,沒有就是 leaf。\n * 沒有第二個概念(沒有 TreeGroup)。\n *\n * TreeView 負責:\n * 1. 遞迴渲染 + indent\n * 2. 展開/收合狀態管理\n * 3. 鍵盤導覽 + ARIA 樹狀表格(treegrid;2026-09-25 由 tree 改,總帳 B9)\n *\n * 它不管 node 裡面長什麼樣——icon、badge、status indicator 等\n * 由 consumer 透過 props / slots 決定。\n *\n * 詳見 tree-view.spec.md。\n */\n\n// ═══════════════════════════════════════════════════════════════════════════\n// Types\n// ═══════════════════════════════════════════════════════════════════════════\n\ntype SizeKey = 'sm' | 'md' | 'lg'\ntype SelectionMode = 'single' | 'multiple' | 'none'\n/**\n * TreeView 的使用脈絡,決定 item 的水平 padding:\n * - `'sidebar'`:頁面側邊欄,用 `--layout-space-loose` token(md=16px / lg=24px,跟 density 連動)\n * - `'menu'`:浮層選單 / dropdown,取 `--item-px`(預設 12px),**對齊 MenuItem / DropdownMenu**\n */\ntype TreeContext = 'sidebar' | 'menu'\n\n// Base horizontal padding per context — 用 CSS variable 注入到 TreeView 容器,\n// TreeItem 用 calc(var(--tree-px) + indent) 算出最終 paddingLeft。\nconst CONTEXT_PX_VAR: Record<TreeContext, string> = {\n sidebar: 'var(--layout-space-loose)', // md=16px, lg=24px(density 連動)\n // 2026-09-17:原本是字面值 `'12px'` + 一句「對齊 MenuItem」的註解 —— 那是第二份,\n // 註解自己就是「它遲早會漂」的自白。改讀同一顆 token(預設 var(--field-px) = 12px,零視覺改動)。\n menu: 'var(--item-px, var(--field-px))', // 對齊 MenuItem / DropdownMenu(owner: item-anatomy.spec.md「Token: --item-px」)\n}\n\n/** Drag drop position — 拖放目標的三種位置 */\n// code-quality-allow: dead-export — public event/state type — consumer event handler parameter type\nexport type TreeDropPosition = 'before' | 'after' | 'inside'\n\n/** onDragEnd callback 的參數 */\n// code-quality-allow: dead-export — public event/state type — consumer event handler parameter type\nexport interface TreeDragEndEvent {\n /** 被拖曳的 node id */\n sourceId: string\n /** 目標 node id */\n targetId: string\n /** 放置位置:before(同層上方)/ after(同層下方)/ inside(成為子 node) */\n position: TreeDropPosition\n}\n\n/**\n * 鍵盤重排(`Cmd/Ctrl+Shift+方向鍵`,2026-07-14 v1)的 SR 播報文案 — zh-TW 預設,\n * per-key merge 覆寫(i18n 覆寫點)。詳 tree-view.spec.md「鍵盤重排」。\n * @public\n */\n// code-quality-allow: dead-export — public prop type — consumer 覆寫播報文案的參數型別\nexport interface TreeReorderAnnouncements {\n /** 同層上/下移完成。`index` / `count` = 移動後在同層的 1-based 序數 / 同層總數 */\n moved?: (args: { label: string; targetLabel: string; position: 'before' | 'after'; index: number; count: number }) => string\n /** 移入 folder 完成。folder 原本收合(children 尚未 mount)時序數不可知 → `index` / `count` 為 undefined */\n movedInside?: (args: { label: string; folderLabel: string; index?: number; count?: number }) => string\n /** 移出到上層完成。`level` = 移動後所在層(1-based,同 aria-level 語意) */\n movedOut?: (args: { label: string; parentLabel: string; level: number }) => string\n /** 邊界 no-op:top(已在最上方)/ bottom(已在最下方)/ not-folder(無可移入的 folder)/ root(已在最外層) */\n blocked?: (args: { reason: 'top' | 'bottom' | 'not-folder' | 'root'; label: string; targetLabel?: string }) => string\n /** sr-only 操作說明(tree 容器 `aria-describedby` 指向;僅 `draggable` 時渲染) */\n instructions?: string\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// Constants\n// ═══════════════════════════════════════════════════════════════════════════\n\n// Icon / chevron 尺寸——從 item-layout pattern module 引入(在檔頂 import),\n// 這裡本地不再宣告。所有 row primitives 共用同一個常數。\n\n// indentStep = chevronSize + gap-2(8px)。值 {24,24,28} 與 CSS token `--tree-indent-{sm,md,lg}`\n// (`tokens/uiSize/uiSize.css`)**完全一致、必須同步維護**(改一處要兩處一起改;非「token 取代 literal」)。\n// 為何兩源並存:本元件 render 用此 JS literal 算 `indentPx`(TreeItem inline calc 需 number)+ drop-indicator\n// pointer 數學(handleDragOver px 命中判斷,CSS var 在 JS 計算層拿不到);DataTable nested rows\n// 則走 CSS token 的 Tailwind class(跨元件視覺一致)。結構對齊:子 chevron 對齊父 icon,子 icon 對齊父 label。\nconst INDENT_STEP: Record<SizeKey, number> = { sm: 24, md: 24, lg: 28 }\n\n// 鍵盤重排 SR 播報預設文案(zh-TW;consumer 經 `reorderAnnouncements` prop per-key 覆寫)。\n// 格式 =「結果 + 序數位置」— 非視覺使用者靠「第 n 項,共 m 項」建立位置模型\n// (世界級 cite 詳 tree-view.spec.md「鍵盤重排」段,M22)。\n// i18n-allow-block: DS default SR-only 播報文案,prop 可覆寫(同 select-menu.tsx SelectMenuLiveStatus 先例)\nconst DEFAULT_REORDER_ANNOUNCEMENTS: Required<TreeReorderAnnouncements> = {\n moved: ({ label, targetLabel, position, index, count }) =>\n `已將『${label}』移到『${targetLabel}』${position === 'before' ? '之前' : '之後'},第 ${index} 項,共 ${count} 項`,\n movedInside: ({ label, folderLabel, index, count }) =>\n index !== undefined && count !== undefined\n ? `已將『${label}』移入『${folderLabel}』,第 ${index} 項,共 ${count} 項`\n : `已將『${label}』移入『${folderLabel}』`,\n movedOut: ({ label, parentLabel, level }) =>\n `已將『${label}』移出到『${parentLabel}』之後,第 ${level} 層`,\n blocked: ({ reason, targetLabel }) => {\n switch (reason) {\n case 'top': return '已在最上方'\n case 'bottom': return '已在最下方'\n case 'not-folder': return targetLabel ? `無法移入:『${targetLabel}』不是資料夾` : '無法移入'\n case 'root': return '已在最外層'\n }\n },\n instructions: '按 Cmd(Ctrl)+Shift+方向鍵可重新排列項目',\n}\n\n// ── 樹狀表格的 DOM 查詢(2026-09-25 總帳 B9:由虛擬焦點改為列上的 roving tabindex)──\n// 每個 node = 外層 wrapper(`data-tree-id`,無角色)+ 直屬子節點 `role=\"row\"`(`data-tree-row`)\n// + 子項容器(`data-tree-children`,Radix Collapsible Content)。列不得包住子列(treegrid 的 row 只能擁有格),\n// 所以 aria-level / aria-expanded / aria-selected 都住在 row 上,wrapper 只是結構。\nconst ROW_SELECTOR = '[data-tree-row]'\n/** 打字跳位:兩次按鍵相隔在這之內算同一串(AI 推導;見 TreeView 內 typeahead) */\nconst TYPEAHEAD_WINDOW_MS = 1000\nconst cssEscape = (value: string) =>\n typeof CSS !== 'undefined' && typeof CSS.escape === 'function' ? CSS.escape(value) : value.replace(/[\"\\\\]/g, '\\\\$&')\nconst rowSelector = (id: string) => `[data-tree-row=\"${cssEscape(id)}\"]`\n/** node wrapper 的那一列(直屬子節點) */\nconst rowOf = (wrapper: Element | null | undefined) => wrapper?.querySelector<HTMLElement>(':scope > [data-tree-row]') ?? null\n/**\n * 看得到 = 不在收合中的子樹裡。Radix Collapsible 關閉動畫期間 Content 仍掛著、`data-state=\"closed\"`,\n * 那段時間裡的列不能再被方向鍵走到(舊查詢 `:not([hidden])` 對列本身永遠成立,擋不到)。\n */\nconst isInOpenBranch = (el: Element) => !el.closest('[data-tree-children][data-state=\"closed\"], [hidden]')\nconst isRowEnabled = (row: Element) => row.getAttribute('aria-disabled') !== 'true'\n/**\n * 動作格裡可以走到的東西 —— 全部 tabIndex=-1,只用 → / ← 走到(總帳 B9;tree-view.spec.md「鍵盤導覽」)。\n * 候選與過濾 = lib/roving-list-keyboard.ts(四個宿主同一份);動作格裡的輸入框 / 可編輯區保留自己的方向鍵,\n * 那一格由共用判定的 `controlIsTextEntry` 處理(2026-09-26 前這裡另有一份 isButtonLike)。\n */\nconst getRowActions = (row: Element): HTMLElement[] => {\n const cell = row.querySelector<HTMLElement>(':scope > [data-tree-actions]')\n return cell ? listRovingControls(cell) : []\n}\nconst hasAccessibleName = (el: Element) =>\n Boolean(\n el.getAttribute('aria-label')?.trim() ||\n el.getAttribute('aria-labelledby')?.trim() ||\n el.getAttribute('title')?.trim() ||\n el.textContent?.trim(),\n )\nconst warnedUnnamedAction = new WeakSet<Element>()\n\n// ═══════════════════════════════════════════════════════════════════════════\n// Context\n// ═══════════════════════════════════════════════════════════════════════════\n\ninterface TreeViewContextValue {\n size: SizeKey\n context: TreeContext\n selectionMode: SelectionMode\n expandOnSelect: boolean\n draggable: boolean\n expandedIds: Set<string>\n selectedIds: Set<string>\n /** 焦點最後停在哪一列(真 DOM 焦點,由容器 onFocus 同步) */\n focusedId: string | null\n /** 整棵樹在 Tab 路上唯一的那一站(roving tabindex;總帳 B9) */\n tabStopId: string | null\n /** 目前拖曳中的 node id(null = 沒在拖) */\n draggingId: string | null\n /** 目前 drop indicator 的位置 + depth(用於 line indent) */\n dropTarget: { id: string; position: TreeDropPosition; depth: number } | null\n toggleExpand: (id: string) => void\n select: (id: string) => void\n setFocusedId: (id: string | null) => void\n /** 把 DOM 焦點放到某一列(不捲動;捲動由該列的 isFocused effect 以 block:nearest 處理) */\n focusRow: (id: string) => void\n /** 列卸載時通知(它若是 Tab 停靠點或焦點所在,要重新選一站,否則整棵樹 Tab 不到) */\n onRowUnmount: (id: string) => void\n registerNode: (id: string, parentId: string | null, hasChildren: boolean, label?: React.ReactNode, icon?: LucideIcon) => void\n getNodeInfo: (id: string) => NodeInfo | undefined\n unregisterNode: (id: string) => void\n}\n\nconst TreeViewContext = React.createContext<TreeViewContextValue | null>(null)\n\nfunction useTreeView(): TreeViewContextValue {\n const ctx = React.useContext(TreeViewContext)\n if (!ctx) throw new Error('TreeItem must be used within TreeView')\n return ctx\n}\n\n// TreeItem depth context(遞迴 depth tracking)\nconst DepthContext = React.createContext(0)\n\n// ═══════════════════════════════════════════════════════════════════════════\n// Node registry — 追蹤所有 node 的 parent/children 關係,用於鍵盤導覽\n// ═══════════════════════════════════════════════════════════════════════════\n\ninterface NodeInfo {\n id: string\n parentId: string | null\n hasChildren: boolean\n /** 用於 DragOverlay ghost 渲染 */\n label?: React.ReactNode\n icon?: LucideIcon\n}\n\nfunction useNodeRegistry() {\n const nodesRef = React.useRef(new Map<string, NodeInfo>())\n\n const registerNode = React.useCallback(\n (id: string, parentId: string | null, hasChildren: boolean, label?: React.ReactNode, icon?: LucideIcon) => {\n nodesRef.current.set(id, { id, parentId, hasChildren, label, icon })\n },\n []\n )\n\n const unregisterNode = React.useCallback((id: string) => {\n nodesRef.current.delete(id)\n }, [])\n\n const getNodeInfo = React.useCallback((id: string) => nodesRef.current.get(id), [])\n\n return { nodesRef, registerNode, unregisterNode, getNodeInfo }\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// TreeView\n// ═══════════════════════════════════════════════════════════════════════════\n\nexport interface TreeViewProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onDragEnd'> {\n /** 元件尺寸,影響 node 高度、icon 大小、indent 寬度 */\n size?: SizeKey\n /**\n * 使用脈絡,決定 item 的水平 padding:\n * - `'sidebar'`(預設):頁面側邊欄,`--layout-space-loose`(md=16px / lg=24px,隨 density 連動)\n * - `'menu'`:浮層選單 / dropdown,px-3(12px),對齊 MenuItem\n */\n context?: TreeContext\n /** 選取模式。預設 'single'(sidebar nav / stepper) */\n selectionMode?: SelectionMode\n /** 點擊 label 時是否同時展開 children。預設 false(chevron 是展開的唯一控件) */\n expandOnSelect?: boolean\n /** 受控:展開的 node id 集合 */\n expandedIds?: Set<string>\n /** 受控:展開狀態變更 callback */\n onExpandedChange?: (ids: Set<string>) => void\n /** 受控:選取的 node id 集合 */\n selectedIds?: Set<string>\n /** 受控:選取狀態變更 callback */\n onSelectedChange?: (ids: Set<string>) => void\n /** 非受控:預設展開的 node id 陣列 */\n defaultExpandedIds?: string[]\n /** 非受控:預設選取的 node id 陣列 */\n defaultSelectedIds?: string[]\n /**\n * 啟用拖曳排序。預設 false。\n * 啟用後整列可拖(Figma 風格,無 grip handle;靠 distance:5 區分 click vs drag),\n * 拖曳時顯示 drop indicator(before / after / inside 三種位置)。\n * Consumer 透過 `onDragEnd` callback 接收 reorder 事件,自行更新 data。\n */\n draggable?: boolean\n /** Drag 結束時觸發,提供 sourceId、targetId、position。Consumer 負責 reorder。 */\n onDragEnd?: (event: TreeDragEndEvent) => void\n /**\n * 鍵盤重排(`Cmd/Ctrl+Shift+方向鍵`)的 SR 播報文案覆寫(per-key merge;zh-TW 預設)。\n * 僅在 `draggable` 時生效;含 `instructions`(sr-only 操作說明)。\n * @public\n */\n reorderAnnouncements?: TreeReorderAnnouncements\n /** ARIA label */\n 'aria-label'?: string\n}\n\n// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding\nconst TreeView = React.forwardRef<HTMLDivElement, TreeViewProps>(\n (\n {\n size = 'md',\n context = 'sidebar',\n selectionMode = 'single',\n expandOnSelect = false,\n draggable = false,\n onDragEnd: onDragEndProp,\n reorderAnnouncements: reorderAnnouncementsProp,\n expandedIds: controlledExpanded,\n onExpandedChange,\n selectedIds: controlledSelected,\n onSelectedChange,\n defaultExpandedIds = [],\n defaultSelectedIds = [],\n className,\n children,\n ...props\n },\n ref\n ) => {\n // ── Accessible name dev-warn(2026-07-18:兌現 spec「Accessible name 必填契約」段承諾 —\n // 原 spec 宣稱有此 console.warn 但 code 缺,spec-code drift;補上使兩者一致)──\n // role=\"treegrid\" 名稱無法從子節點推導,缺 aria-label / aria-labelledby → SR 只讀出角色名。\n // 對齊 Button / Tag 的 dev-only 誤用警告 idiom;production 不觸發。\n const ariaLabel = props['aria-label']\n const ariaLabelledby = props['aria-labelledby']\n React.useEffect(() => {\n if (\n process.env.NODE_ENV !== 'production' &&\n ariaLabel == null &&\n ariaLabelledby == null\n ) {\n console.warn(\n '[DS] TreeView:role=\"treegrid\" 缺 accessible name — 請傳 aria-label(直接字串)或 aria-labelledby(指向可見標題 id)其一。' +\n '兩者皆缺時螢幕閱讀器只讀出角色名、無法辨識用途(WAI-ARIA 要求 treegrid 具 accessible name)。',\n )\n }\n }, [ariaLabel, ariaLabelledby])\n\n // ── Expand state(受控 / 非受控) ──\n const [internalExpanded, setInternalExpanded] = React.useState(\n () => new Set(defaultExpandedIds)\n )\n const expandedIds = controlledExpanded ?? internalExpanded\n const setExpandedIds = React.useCallback(\n (updater: (prev: Set<string>) => Set<string>) => {\n const update = (prev: Set<string>) => {\n const next = updater(prev)\n onExpandedChange?.(next)\n return next\n }\n if (controlledExpanded) {\n update(controlledExpanded)\n } else {\n setInternalExpanded(update)\n }\n },\n [controlledExpanded, onExpandedChange]\n )\n\n // ── Selection state(受控 / 非受控) ──\n const [internalSelected, setInternalSelected] = React.useState(\n () => new Set(defaultSelectedIds)\n )\n const selectedIds = controlledSelected ?? internalSelected\n const setSelectedIds = React.useCallback(\n (updater: (prev: Set<string>) => Set<string>) => {\n const update = (prev: Set<string>) => {\n const next = updater(prev)\n onSelectedChange?.(next)\n return next\n }\n if (controlledSelected) {\n update(controlledSelected)\n } else {\n setInternalSelected(update)\n }\n },\n [controlledSelected, onSelectedChange]\n )\n\n // ── Focus state(2026-09-25 總帳 B9:虛擬焦點 → 列上的 roving tabindex)──\n // 真 DOM 焦點落在列(role=\"row\")或列裡的按鈕上;focusedId 由容器 onFocus 同步。\n // 為何改:樹狀表格要讓 → 把焦點**真的**交給列裡的按鈕、← 再交回列 —— 這一步本來就是真焦點,\n // 列若維持虛擬焦點,同一棵樹就有兩種焦點模型。W3C 樹狀表格範例與 Adobe 的樹都是列上真焦點\n // (來源見 tree-view.spec.md「A11y 預設」)。\n const [focusedId, setFocusedId] = React.useState<string | null>(null)\n const treeRef = React.useRef<HTMLDivElement>(null)\n // 打字跳位的累積字串(見下方 typeahead)\n const typeaheadRef = React.useRef({ buffer: '', at: 0 })\n React.useImperativeHandle(ref, () => treeRef.current!)\n const focusRow = React.useCallback((id: string) => {\n treeRef.current?.querySelector<HTMLElement>(rowSelector(id))?.focus({ preventScroll: true })\n }, [])\n\n // ── 唯一的 Tab 停靠點(roving tabindex)──\n // 順序:焦點最後停的那一列(仍看得到)→ 選中的第一列 → 第一個可用列\n // (W3C keyboard-interface「進入組合元件時落在選中項,沒有就第一項」;來源見 spec)。\n // 在 layout 階段從真 DOM 重算,相同就不 setState,不會迴圈:答案只看列的存在 / 開合 / aria-selected / aria-disabled 與 focusedId,\n // 不看 tabStopId 本身(它只影響列的 tabIndex),所以 set 之後那一次重算必然相等。\n const [tabStopId, setTabStopId] = React.useState<string | null>(null)\n // 值本身不讀,只當下方 layout effect 的相依:列卸載時 TreeView 自己不一定 re-render,靠它讓 effect 重算停靠點\n const [rowRepairTick, setRowRepairTick] = React.useState(0)\n const tabStopRef = React.useRef<string | null>(null)\n tabStopRef.current = tabStopId\n const focusedIdRef = React.useRef<string | null>(null)\n focusedIdRef.current = focusedId\n React.useLayoutEffect(() => {\n const tree = treeRef.current\n if (!tree) return\n const rows = Array.from(tree.querySelectorAll<HTMLElement>(ROW_SELECTOR)).filter(isInOpenBranch)\n // 判定與 Sidebar / FileUpload 同一份(X1;lib/roving-list-keyboard.ts `pickRovingTabStop`)。\n // 焦點停過的列即使停用也還拿得住焦點(← 回上一層可能落在停用列),所以 canHoldFocus 恆真;「選中」「第一列」只算可用列。\n const stop = pickRovingTabStop(rows, {\n remembered: focusedId != null ? rows.find((row) => row.dataset.treeRow === focusedId) ?? null : null,\n canHoldFocus: () => true,\n isNavigable: isRowEnabled,\n isCurrent: (row) => row.getAttribute('aria-selected') === 'true',\n })\n const next = stop?.dataset.treeRow ?? null\n if (next !== tabStopId) setTabStopId(next)\n // 相依 = 會改變「哪些列存在 / 在開著的分支裡 / aria-selected / aria-disabled」的 render 輸入,加上 remembered(focusedId)與比對用的 tabStopId:\n // children(節點增刪 / disabled)、expandedIds(子樹掛載與開合)、selectedIds + selectionMode(aria-selected)、rowRepairTick(列卸載通知)。\n // 2026-09-29 前沒有相依清單(每次 render 都跑);列 DOM 只會因這幾個輸入而變,其餘 render(拖曳落點 / 播報文字 / 無關 prop)重算結果必相同,\n // 跳過不改行為。日後新增會影響列 DOM 的輸入時要一併列進來。\n }, [children, expandedIds, selectedIds, selectionMode, focusedId, tabStopId, rowRepairTick])\n // 列被卸載(例:consumer 收合了焦點所在列的上一層,關閉動畫結束後子樹才卸載 —— 那一刻 TreeView 本身不會 re-render)\n // → 若它是停靠點或焦點所在,觸發一次重算,否則整棵樹會從 Tab 路上消失。\n const onRowUnmount = React.useCallback((id: string) => {\n if (id === tabStopRef.current || id === focusedIdRef.current) setRowRepairTick((tick) => tick + 1)\n }, [])\n\n // ── DOM id prefix(重排操作說明節點的 id)──\n // useId 確保多棵 TreeView 同頁時 DOM id 不撞。\n const idPrefix = React.useId()\n\n // ── 鍵盤重排 SR 播報(2026-07-14 v1;詳 spec「鍵盤重排」)──\n // 單一 polite live region 覆寫式更新(textContent 替換非 append → 快速連按天然只播最新)。\n // 不可借 dnd-kit DndContext 內建 live region — 它只播 dnd-kit 管理的 drag session,\n // 自建鍵盤路徑觸不到。消費 select-menu.tsx SelectMenuLiveStatus role=\"status\" 先例。\n const [reorderAnnouncement, setReorderAnnouncement] = React.useState('')\n const announcements = React.useMemo(\n () => ({ ...DEFAULT_REORDER_ANNOUNCEMENTS, ...reorderAnnouncementsProp }),\n [reorderAnnouncementsProp]\n )\n // sr-only 操作說明節點 id(tree 容器 aria-describedby 指向;僅 draggable 渲染)\n const reorderInstructionsId = `${idPrefix}tree-reorder-instructions`\n\n // ── Drag state ──\n const [draggingId, setDraggingId] = React.useState<string | null>(null)\n const [dropTarget, setDropTarget] = React.useState<{ id: string; position: TreeDropPosition; depth: number } | null>(null)\n const autoExpandTimerRef = React.useRef<ReturnType<typeof setTimeout> | null>(null)\n // 2026-05-16 audit codex Round 6:unmount cleanup(原 cleanup 只在 dragEnd/dragCancel,unmount-during-drag 漏 cancel)\n React.useEffect(() => () => { if (autoExpandTimerRef.current) clearTimeout(autoExpandTimerRef.current) }, [])\n // Ref for toggleExpand — handleDragOver 定義在 toggleExpand 之前(hook 順序限制),\n // 用 ref 打斷 temporal dead zone。\n const toggleExpandRef = React.useRef<(id: string) => void>(() => {})\n\n // ── Node registry ──(2026-07-14 上移至 drag handlers 之前:isInSubtree 消費 getNodeInfo,\n // 而 handleDragOver / handleDragEnd / 鍵盤 reorder 都消費 isInSubtree)\n const { registerNode, unregisterNode, getNodeInfo } = useNodeRegistry()\n\n // 子樹判定:candidate 是否位於 rootId 的子樹內(含相等)。沿 registry parentId 鏈上溯。\n // 雙路共用 SSOT:pointer 路徑 descendant drop guard(2026-07-14 R6 修:原 handleDragOver 只擋\n // `over.id === active.id` 未擋 descendants → 可發出 targetId ∈ source 子樹的非法事件,\n // consumer 按 remove→insert 實作會讓整個子樹靜默消失)+ 鍵盤 reorder commit 前防禦 assert。\n const isInSubtree = React.useCallback(\n (candidateId: string, rootId: string): boolean => {\n let cur: string | null = candidateId\n while (cur != null) {\n if (cur === rootId) return true\n cur = getNodeInfo(cur)?.parentId ?? null\n }\n return false\n },\n [getNodeInfo]\n )\n\n const sensors = useSensors(\n // 2026-09-07:5 → DRAG_ACTIVATION_DISTANCE_PX(8)。全 DS 原本三個值(8/5/8)加一處沒設,\n // 收斂到單一來源;8 是原本的多數,其中 AgentFab 那個 8 還是 user 實際用過調出來的。\n useSensor(PointerSensor, { activationConstraint: { distance: DRAG_ACTIVATION_DISTANCE_PX } })\n )\n\n const handleDragStart = React.useCallback((event: DragStartEvent) => {\n setDraggingId(String(event.active.id))\n }, [])\n\n // ── Figma-style drop detection(X + Y 雙軸)──\n //\n // Y 軸:決定在哪個 item 附近\n // - item 上 25% = before\n // - item 中 50% = inside(只有 folder)\n // - item 下 25% = after\n //\n // X 軸:決定 nesting 深度(Figma 核心邏輯)\n // - 滑鼠越左 = 越淺層(放在 parent 層級)\n // - 滑鼠越右 = 越深層(放進 folder)\n // - 用 pointer X 相對於 tree 左邊界計算 indent level\n //\n const handleDragOver = React.useCallback((event: DragOverEvent) => {\n const { over, active } = event\n // 2026-07-14 R6 descendant guard:over 是 active 自己「或其子樹內節點」→ 無效目標,\n // 不設 dropTarget(拖 folder 進自己子樹會發出非法 TreeDragEndEvent;guard SSOT = isInSubtree,\n // 鍵盤 reorder 路徑共用同一 helper)。\n if (!over || isInSubtree(String(over.id), String(active.id))) {\n if (autoExpandTimerRef.current) { clearTimeout(autoExpandTimerRef.current); autoExpandTimerRef.current = null }\n setDropTarget(null)\n return\n }\n\n const rowEl = document.querySelector(`[data-tree-row=\"${over.id}\"]`) as HTMLElement | null\n const targetEl = document.querySelector(`[data-tree-id=\"${over.id}\"]`) as HTMLElement | null\n if (!rowEl || !targetEl) { setDropTarget(null); return }\n\n // 實際指標位置\n const startX = (event.activatorEvent as PointerEvent)?.clientX ?? 0\n const startY = (event.activatorEvent as PointerEvent)?.clientY ?? 0\n const currentX = startX + (event.delta?.x ?? 0)\n const currentY = startY + (event.delta?.y ?? 0)\n\n const rect = rowEl.getBoundingClientRect()\n const offsetY = currentY - rect.top\n const height = rect.height || 32\n const ratio = Math.max(0, Math.min(1, offsetY / height))\n\n const hasChildren = targetEl.dataset.treeHasChildren === 'true'\n // 2026-09-25:aria-level 從 wrapper 搬到列(role=\"row\")上(樹狀表格的層級屬性住在列)\n const targetDepth = Number(rowEl.getAttribute('aria-level') ?? 1) - 1\n\n // ── X 軸:計算指標在哪個 indent level ──\n const treeEl = treeRef.current\n const treeLeft = treeEl?.getBoundingClientRect().left ?? 0\n const indentStep = INDENT_STEP[size]\n const pointerIndentLevel = Math.max(0, Math.floor((currentX - treeLeft) / indentStep))\n\n let position: TreeDropPosition\n let finalDepth = targetDepth\n\n if (hasChildren) {\n // Folder node\n if (ratio < 0.25) {\n position = 'before'\n } else if (ratio > 0.75) {\n // after folder: 如果指標在 folder 層級或更淺 = after(同層)\n // 如果指標更深 = inside(放進 folder)\n position = pointerIndentLevel > targetDepth ? 'inside' : 'after'\n } else {\n position = 'inside'\n }\n } else {\n // Leaf node\n if (ratio < 0.5) {\n position = 'before'\n } else {\n position = 'after'\n // X 軸:如果指標在比 target 更淺的層級,提升 drop depth\n // 例:Contact(depth 1)的 after,如果滑鼠在 depth 0 → 變成「after Pages」\n if (pointerIndentLevel < targetDepth) {\n // 找 parent 來放(2026-09-25:原本沿 role=\"group\" / role=\"treeitem\" 上溯,treegrid 沒有這兩個角色 → 改讀 wrapper 記的 parent id)\n const parentId = targetEl.dataset.treeParentId || null\n const parentRow = parentId ? treeEl?.querySelector<HTMLElement>(rowSelector(parentId)) ?? null : null\n if (parentId && parentId !== String(active.id)) {\n const parentDepth = Number(parentRow?.getAttribute('aria-level') ?? 1) - 1\n finalDepth = parentDepth\n setDropTarget(prev => prev && prev.id === parentId && prev.position === 'after' && prev.depth === parentDepth ? prev : { id: parentId, position: 'after', depth: parentDepth })\n return\n }\n }\n }\n }\n\n // 2026-07-06 D3 perf:bail-out — drop target 未變(每 dragOver 跨 row 才變)時回傳 prev,\n // React 對相同 reference bail out,避免每次 over 事件重建 contextValue → 全樹 TreeItem re-render。\n setDropTarget(prev => {\n const id = String(over.id)\n return prev && prev.id === id && prev.position === position && prev.depth === finalDepth ? prev : { id, position, depth: finalDepth }\n })\n\n // Auto-expand collapsed folder after 500ms hover (Figma behavior)\n if (position === 'inside' && hasChildren && !expandedIds.has(String(over.id))) {\n if (autoExpandTimerRef.current) clearTimeout(autoExpandTimerRef.current)\n autoExpandTimerRef.current = setTimeout(() => {\n toggleExpandRef.current(String(over.id))\n }, 500)\n } else {\n if (autoExpandTimerRef.current) { clearTimeout(autoExpandTimerRef.current); autoExpandTimerRef.current = null }\n }\n }, [expandedIds, isInSubtree, size])\n\n const dropTargetRef = React.useRef(dropTarget)\n dropTargetRef.current = dropTarget\n\n // C2/B2 修(2026-09-07):TreeView 自有的繁中播報只有**鍵盤重排**那條路在寫,\n // 滑鼠拖曳走的是 dnd-kit 的英文預設 —— 實測整趟拖曳下來自有播報區維持空字串。\n // 這裡把指標路徑也接上,並誠實回報結果:守衛擋下(不合法 target / 子樹內)\n // 就播「未變更」而不是假的成功。共用 SSOT 見 `lib/drag-announcements.ts`。\n const dragOutcomeRef = React.useRef<DragOutcome | null>(null)\n // 指標拖曳的播報走 polite 區域 + 繁中操作說明(2026-09-27 OE10;先前交給 dnd-kit 寫死的 assertive 區域)\n const drag = useDragAccessibility({ getOutcome: () => dragOutcomeRef.current, kind: '項目' })\n\n const handleDragEnd = React.useCallback((event: DragEndEvent) => {\n dragOutcomeRef.current = null\n if (autoExpandTimerRef.current) { clearTimeout(autoExpandTimerRef.current); autoExpandTimerRef.current = null }\n const { active, over } = event\n const dt = dropTargetRef.current\n // descendant guard 同 handleDragOver(dt 已由 dragOver guard 保證為 null,此為 belt-and-braces)\n if (over && !isInSubtree(String(over.id), String(active.id)) && dt) {\n dragOutcomeRef.current = { kind: '項目', label: String(active.id) }\n onDragEndProp?.({\n sourceId: String(active.id),\n targetId: String(over.id),\n position: dt.position,\n })\n }\n setDraggingId(null)\n setDropTarget(null)\n }, [onDragEndProp, isInSubtree])\n\n const handleDragCancel = React.useCallback(() => {\n if (autoExpandTimerRef.current) { clearTimeout(autoExpandTimerRef.current); autoExpandTimerRef.current = null }\n setDraggingId(null)\n setDropTarget(null)\n }, [])\n\n // ── Actions ──\n const toggleExpand = React.useCallback(\n (id: string) => {\n setExpandedIds((prev) => {\n const next = new Set(prev)\n if (next.has(id)) next.delete(id)\n else next.add(id)\n return next\n })\n },\n [setExpandedIds]\n )\n toggleExpandRef.current = toggleExpand\n\n const select = React.useCallback(\n (id: string) => {\n if (selectionMode === 'none') return\n setSelectedIds((prev) => {\n if (selectionMode === 'single') {\n return new Set([id])\n }\n // multiple\n const next = new Set(prev)\n if (next.has(id)) next.delete(id)\n else next.add(id)\n return next\n })\n },\n [selectionMode, setSelectedIds]\n )\n\n // ── Keyboard reorder(Cmd/Ctrl+Shift+方向鍵,2026-07-14 v1)──\n // 設計 SSOT:tree-view.spec.md「鍵盤重排」。與 pointer 路徑共用 TreeDragEndEvent 契約 +\n // onDragEnd prop + registry(consumer API 零改動);每按一下立即 commit(無 grab-mode /\n // 無預覽,對齊 Notion Cmd/Ctrl+Shift+Arrow 移動 block:https://www.notion.com/help/keyboard-shortcuts,\n // WebFetch 驗證紀錄 .claude/logs/treeview-keyboard-dnd-design.md#L25)。\n // 不用 dnd-kit KeyboardSensor:它是「按住 → 方向鍵搬 → 放開」的抓取模式(https://dndkit.com/api-documentation/sensors/keyboard),\n // 啟動鍵是 Enter / 空白鍵 —— 本元件那兩個鍵是「選取目前列」;每按即 commit 的語意也與抓取模式不同。\n // (2026-09-25 更正:舊理由「列不可聚焦、與虛擬焦點結構性不相容」在列改為真焦點後已不成立。)\n\n // 播報用 label 文字:registry label 是 ReactNode — string 直接用,否則退 DOM row textContent。\n const getNodeLabelText = React.useCallback(\n (id: string): string => {\n const info = getNodeInfo(id)\n if (typeof info?.label === 'string') return info.label\n const rowEl = treeRef.current?.querySelector<HTMLElement>(rowSelector(id))\n return rowEl?.textContent?.trim() || id\n },\n [getNodeInfo]\n )\n\n // code-quality-allow: long-function — 四鍵語意(同層上下 / 移入 / 移出)+ 邊界播報結構緊密,\n // 拆 sub-fn 會跨 fn 傳 siblings / announce state 反而複雜(同 handleKeyDownCapture 先例)\n const handleKeyboardReorder = React.useCallback(\n (key: RovingReorderKey, sourceId: string) => {\n const tree = treeRef.current\n if (!tree) return\n const sourceEl = tree.querySelector<HTMLElement>(`[data-tree-id=\"${cssEscape(sourceId)}\"]`)\n // disabled node 不可移(鍵盤導覽本就跳過 disabled,此為防禦 guard)\n if (!sourceEl || !isRowEnabled(rowOf(sourceEl) ?? sourceEl)) return\n\n const label = getNodeLabelText(sourceId)\n // 同層 siblings(DOM 序)。focused node 可見 ⇒ 其 parent 已展開 ⇒ 全 siblings 已 mount。\n const parentId = sourceEl.dataset.treeParentId || null\n const visibleItems = Array.from(\n tree.querySelectorAll<HTMLElement>('[data-tree-id]')\n ).filter(isInOpenBranch)\n const siblings = visibleItems.filter((el) => (el.dataset.treeParentId || null) === parentId)\n const sourceIndex = siblings.findIndex((el) => el.dataset.treeId === sourceId)\n if (sourceIndex < 0) return\n const isEnabled = (el: HTMLElement) => isRowEnabled(rowOf(el) ?? el)\n\n // 發出與 pointer 路徑同型的 TreeDragEndEvent,並排定 re-render 後 scrollIntoView\n // (既有 effect 只在 isFocused 變化時跑;同 id 移動不 re-fire,故此處補)。\n const commit = (targetId: string, position: TreeDropPosition): boolean => {\n // 結構安全 assert:四鍵語意(sibling 間 / 進 prev sibling / 出 parent)結構上\n // 不可能移進自己子樹;防禦性仍擋(與 pointer descendant guard 共用 isInSubtree SSOT)。\n if (targetId === sourceId || isInSubtree(targetId, sourceId)) return false\n onDragEndProp?.({ sourceId, targetId, position })\n requestAnimationFrame(() => {\n const movedRow = treeRef.current?.querySelector<HTMLElement>(rowSelector(sourceId))\n movedRow?.scrollIntoView({ block: 'nearest' })\n // 2026-09-25(總帳 B9 改真焦點後):React 搬動 / 重掛那一列時,瀏覽器會把焦點丟到 body ——\n // 鍵盤使用者按完一下就失去位置。焦點還給被移動的那一列(虛擬焦點時代靠 aria-activedescendant 自動跟上)。\n if (movedRow && document.activeElement !== movedRow) movedRow.focus({ preventScroll: true })\n })\n return true\n }\n\n // 同層移動後的 1-based 新序數(播報用):移除 source 再插入的模擬;同層總數不變。\n const movedIndex = (targetId: string, position: 'before' | 'after'): number => {\n const order = siblings.map((el) => el.dataset.treeId).filter((id) => id !== sourceId)\n const tIdx = order.indexOf(targetId)\n return (position === 'before' ? tIdx : tIdx + 1) + 1\n }\n\n switch (key) {\n case 'ArrowUp':\n case 'ArrowDown': {\n // disabled sibling 不可當 target 錨點(pointer useDroppable 同樣 disable)——\n // 相鄰 sibling disabled 時錨到最近 enabled sibling 並翻轉 before/after 表達同一插槽\n // (可達位置與 pointer 一致);整個方向皆無 enabled sibling → 視同邊界。\n const dir = key === 'ArrowUp' ? -1 : 1\n const boundary = dir < 0 ? ('top' as const) : ('bottom' as const)\n const adjacent = siblings[sourceIndex + dir]\n if (!adjacent) {\n setReorderAnnouncement(announcements.blocked({ reason: boundary, label }))\n return\n }\n let target: HTMLElement | undefined\n let position: 'before' | 'after' = dir < 0 ? 'before' : 'after'\n if (isEnabled(adjacent)) {\n target = adjacent\n } else {\n for (let i = sourceIndex + dir; i >= 0 && i < siblings.length; i += dir) {\n if (isEnabled(siblings[i])) { target = siblings[i]; break }\n }\n position = dir < 0 ? 'after' : 'before' // 錨到最近 enabled sibling 的另一側 = 相鄰插槽\n }\n if (!target) {\n setReorderAnnouncement(announcements.blocked({ reason: boundary, label }))\n return\n }\n const targetId = target.dataset.treeId!\n if (commit(targetId, position)) {\n setReorderAnnouncement(\n announcements.moved({\n label,\n targetLabel: getNodeLabelText(targetId),\n position,\n index: movedIndex(targetId, position),\n count: siblings.length,\n })\n )\n }\n return\n }\n case 'ArrowRight': {\n // 移入:成為最近 enabled 上一 sibling 的子項(沿用 pointer「inside 限 folder」規則)\n let target: HTMLElement | undefined\n for (let i = sourceIndex - 1; i >= 0; i--) {\n if (isEnabled(siblings[i])) { target = siblings[i]; break }\n }\n if (!target) {\n setReorderAnnouncement(announcements.blocked({ reason: 'not-folder', label }))\n return\n }\n const targetId = target.dataset.treeId!\n if (target.dataset.treeHasChildren !== 'true') {\n setReorderAnnouncement(\n announcements.blocked({ reason: 'not-folder', label, targetLabel: getNodeLabelText(targetId) })\n )\n return\n }\n const wasExpanded = expandedIds.has(targetId)\n if (commit(targetId, 'inside')) {\n // 移入 collapsed folder:允許 + 自動展開(pointer 500ms hover auto-expand 的鍵盤對應物)\n if (!wasExpanded) toggleExpand(targetId)\n // 序數:consumer 對 inside 的慣例語意 = append 到 children 尾端;\n // folder 原收合 → children 未 mount 無從計數 → 播報省略序數(誠實不猜)。\n const childCount = wasExpanded\n ? visibleItems.filter((el) => el.dataset.treeParentId === targetId).length\n : undefined\n setReorderAnnouncement(\n announcements.movedInside({\n label,\n folderLabel: getNodeLabelText(targetId),\n index: childCount !== undefined ? childCount + 1 : undefined,\n count: childCount !== undefined ? childCount + 1 : undefined,\n })\n )\n }\n return\n }\n case 'ArrowLeft': {\n // 移出:成為 parent 的下一個 sibling(outdent)\n if (!parentId) {\n setReorderAnnouncement(announcements.blocked({ reason: 'root', label }))\n return\n }\n const parentRow = tree.querySelector<HTMLElement>(rowSelector(parentId))\n const level = Number(parentRow?.getAttribute('aria-level') ?? '1')\n if (commit(parentId, 'after')) {\n setReorderAnnouncement(\n announcements.movedOut({ label, parentLabel: getNodeLabelText(parentId), level })\n )\n }\n return\n }\n }\n },\n [expandedIds, toggleExpand, onDragEndProp, getNodeLabelText, isInSubtree, announcements]\n )\n\n // ── Context value ──\n const contextValue = React.useMemo<TreeViewContextValue>(\n () => ({\n size,\n context,\n selectionMode,\n expandOnSelect,\n draggable,\n draggingId,\n dropTarget,\n expandedIds,\n selectedIds,\n focusedId,\n tabStopId,\n toggleExpand,\n select,\n setFocusedId,\n focusRow,\n onRowUnmount,\n registerNode,\n unregisterNode,\n getNodeInfo,\n }),\n [\n size,\n context,\n selectionMode,\n expandOnSelect,\n draggable,\n draggingId,\n dropTarget,\n expandedIds,\n selectedIds,\n focusedId,\n tabStopId,\n toggleExpand,\n select,\n setFocusedId,\n focusRow,\n onRowUnmount,\n registerNode,\n unregisterNode,\n getNodeInfo,\n ]\n )\n\n // ── Keyboard handler(2026-09-25 總帳 B9:樹狀表格路線;2026-09-26 判定與執行併入共用零件)──\n // 判定 = lib/roving-list-keyboard.ts `resolveRovingKey`(與 Sidebar / FileUpload / Command 同一份按鍵表),\n // 執行 = 同檔 `applyRovingAction`;這裡只讀樹自己的 DOM 狀態(哪一列、展開與否、上一層),以及重排。\n // 用 capture(同 Sidebar):方向鍵整串歸樹,搶在列上按鈕自己的處理之前 —— 列上的選單鈕按 ↓ 換下一列、\n // 不開選單(X6「統一成側欄做法」;開選單用 Enter / 空白鍵)。2026-09-26 之前在冒泡階段,選單鈕的 ↓ 讓給 Radix 開選單。\n // 不包 useCallback:它只掛在一個 DOM 節點上,每次 render 讀最新的 props.onKeyDownCapture / expandedIds(同下方 onFocus)\n const handleKeyDownCapture = (e: React.KeyboardEvent<HTMLDivElement>) => {\n ;(props as React.HTMLAttributes<HTMLDivElement>).onKeyDownCapture?.(e)\n const tree = treeRef.current\n if (!tree) return\n const target = e.target as HTMLElement\n // React 合成事件會穿過 portal 傳遞(例:列上選單鈕打開的選單),DOM 上不在這棵樹裡的一律不管\n if (!tree.contains(target)) return\n const row = target.closest<HTMLElement>(ROW_SELECTOR)\n const id = row?.dataset.treeRow\n if (!row || !id) return\n const onRow = target === row\n const actions = getRowActions(row)\n // 列裡不在動作格的東西(展開箭頭、勾選框:都是 tabIndex=-1 的視覺件):交給它自己\n if (!onRow && !actions.includes(target)) return\n\n const wrapper = row.closest<HTMLElement>('[data-tree-id]')\n const parentId = wrapper?.dataset.treeParentId || null\n const action = resolveRovingKey({\n key: e.key,\n focus: onRow ? 'item' : 'control',\n controlCount: actions.length,\n controlIndex: onRow ? -1 : actions.indexOf(target),\n controlIsTextEntry: !onRow && isTextEntryElement(target),\n tree: { hasChildren: wrapper?.dataset.treeHasChildren === 'true', expanded: expandedIds.has(id), hasParent: parentId != null },\n // 打字跳位只在列上(按鈕上打字沒有意義;動作格裡的輸入框由 controlIsTextEntry 保住自己的打字)\n typeahead: onRow,\n // 未啟用拖曳 → 重排組合整組 no-op(modifier 組合不落入導覽)\n reorderable: draggable,\n defaultPrevented: e.defaultPrevented,\n metaKey: e.metaKey,\n ctrlKey: e.ctrlKey,\n altKey: e.altKey,\n shiftKey: e.shiftKey,\n })\n if (action.type === 'reorder') {\n e.preventDefault() // 阻止瀏覽器原生行為(捲動 / 文字選取)\n if (draggingId !== null) return // pointer 拖曳進行中 → 互斥,忽略鍵盤重排\n handleKeyboardReorder(action.key, id)\n return\n }\n // 看得到的列(DOM 序)。停用列不是方向鍵的落點(既有規則),但焦點可能經 ← 停在停用的上一層,\n // 所以相鄰列以 DOM 序從目前這一列往前 / 往後找第一個可用列(pickRovingTarget)。\n applyRovingAction(action, {\n event: e,\n item: row,\n controls: actions,\n items: Array.from(tree.querySelectorAll<HTMLElement>(ROW_SELECTOR)).filter(isInOpenBranch),\n isNavigable: isRowEnabled,\n focusOptions: { preventScroll: true },\n onExpand: () => toggleExpand(id),\n onCollapse: () => toggleExpand(id),\n onParent: () => { if (parentId) focusRow(parentId) },\n onActivateItem: () => select(id),\n onTypeahead: (char) => typeahead(char, row),\n })\n }\n\n // ── 打字跳位(typeahead;2026-09-29 待辦總帳 N46)──\n // keyboard-model-canonical.md 五條判準第 2 條把「打字前導」列為樹必備的鍵盤模型;規格 tree-view.spec.md「鍵盤導覽」。\n // W3C APG Tree View 逐字:\"Type a character: focus moves to the next node with a name that starts with the typed character.\" /\n // \"Type multiple characters in rapid succession: focus moves to the next node with a name that starts with the string of\n // characters typed.\"(https://www.w3.org/WAI/ARIA/apg/patterns/treeview/)。\n // 名字 = 列的 accessible name(aria-labelledby 指到的 label 文字),比對折疊大小寫;候選 = 看得到、沒停用的列,\n // 從目前這一列的**下一列**往後找、到底再從頭(繞回一圈,最後才是自己 → 只有自己符合就不動)。\n // 連打同一個字母(「bb」)而沒有名字以「bb」開頭時退回單字循環(連按同一鍵 = 在同字母的列之間輪流)。\n // 字串累積窗 1000ms(AI 推導,無外部出處;超過就當重新開始)。判定在 lib/roving-list-keyboard.ts(`typeahead` 格),\n // 這裡只做「找哪一列、搬焦點」。\n const typeahead = (char: string, row: HTMLElement) => {\n const tree = treeRef.current\n if (!tree) return\n const now = Date.now()\n const state = typeaheadRef.current\n const buffer = now - state.at <= TYPEAHEAD_WINDOW_MS ? state.buffer + char : char\n typeaheadRef.current = { buffer, at: now }\n const rows = Array.from(tree.querySelectorAll<HTMLElement>(ROW_SELECTOR)).filter((r) => isInOpenBranch(r) && isRowEnabled(r))\n const nameOf = (r: HTMLElement) => {\n const labelledBy = r.getAttribute('aria-labelledby')\n const label = labelledBy ? document.getElementById(labelledBy) : null\n return (label?.textContent ?? '').trim().toLocaleLowerCase()\n }\n const start = rows.indexOf(row)\n const ordered = [...rows.slice(start + 1), ...rows.slice(0, start + 1)]\n const find = (prefix: string) => ordered.find((r) => nameOf(r).startsWith(prefix))\n const lower = buffer.toLocaleLowerCase()\n const sameChar = lower.length > 1 && [...lower].every((c) => c === lower[0])\n const target = find(lower) ?? (sameChar ? find(lower[0]) : undefined)\n target?.focus({ preventScroll: true })\n }\n\n const treeEl = (\n <div\n // {...props} 在最前:內部 role/style(--tree-px)/onKeyDownCapture 必須勝過 consumer\n // 誤傳(原 spread 在最後 → consumer style 會整組蓋掉 --tree-px、鍵盤 handler 蓋掉鍵盤導覽);\n // consumer 的 onKeyDownCapture 由 handleKeyDownCapture 先呼叫再接手(同 SidebarMenu)\n {...props}\n ref={treeRef}\n // 2026-09-25 總帳 B9:tree → treegrid。W3C 只在樹狀表格定義了「列上的按鈕」怎麼走\n // (來源見 tree-view.spec.md「A11y 預設」);容器本身不再可聚焦,唯一的 Tab 停靠點是 tabStopId 那一列。\n role=\"treegrid\"\n aria-multiselectable={selectionMode === 'multiple' || undefined}\n // 鍵盤重排操作說明(sr-only,僅 draggable;與 consumer 傳入的 aria-describedby 合併)\n aria-describedby={\n [props['aria-describedby'], draggable ? reorderInstructionsId : undefined]\n .filter(Boolean)\n .join(' ') || undefined\n }\n className={cn(\n // TreeView root 不加任何 py——呼吸空間由外層容器負責:\n // - 在 SidebarGroup 內: SidebarGroup py-2 提供\n // - 在 DropdownMenuContent 內: content py-2 提供\n // - 獨立使用(story demo): consumer 自己加 py-2\n // 這樣才能跟 DropdownMenu / MenuGroup 的結構一致(group 是容器,row 是內容)。\n // 2026-09-25:原本這裡的「有 aria-activedescendant 才抑制容器外框」(@focus-suppress A)隨虛擬焦點一起拿掉 ——\n // 容器已不可聚焦,框由拿到真焦點的那一列自己畫(TreeItem 列的 focus-visible:focus-ring-inset)。\n 'flex flex-col',\n className,\n )}\n style={{\n ['--tree-px' as string]: CONTEXT_PX_VAR[context],\n ...props.style,\n } as React.CSSProperties}\n onKeyDownCapture={handleKeyDownCapture}\n // 焦點落在任何一列或列裡的按鈕 → 記下是哪一列(它就是下一次 Tab 進來的落點;總帳 B9)\n onFocus={(e) => {\n ;(props as React.HTMLAttributes<HTMLDivElement>).onFocus?.(e)\n const tree = treeRef.current\n const target = e.target as HTMLElement\n // React 合成的 focus 事件也會穿過 portal 冒泡;只認 DOM 上在這棵樹裡的\n if (!tree || !tree.contains(target)) return\n const id = target.closest<HTMLElement>(ROW_SELECTOR)?.dataset.treeRow\n if (id && id !== focusedIdRef.current) setFocusedId(id)\n }}\n >\n {children}\n </div>\n )\n\n return (\n <TreeViewContext.Provider value={contextValue}>\n {/* RowSizeProvider:讓 TreeView 子樹內任何 <ItemIcon> / <ItemAvatar> /\n <ItemInlineAction> 自動讀到對的 size,跟 SidebarProvider 同一條規則。\n inlineActions API 也吃這個 context。 */}\n <RowSizeProvider value={size}>\n {/* 永遠包 DndContext(hooks 不能 conditional call)。不 draggable 時無 sensors = 不可拖 */}\n <DndContext\n sensors={draggable ? sensors : undefined}\n onDragStart={handleDragStart}\n onDragOver={handleDragOver}\n onDragEnd={handleDragEnd}\n onDragCancel={handleDragCancel}\n accessibility={drag.accessibility}\n >\n {treeEl}\n {/* 指標拖曳播報的 polite 區域(sr-only;lib/drag-announcements.ts useDragAccessibility);鍵盤重排另有下方自有區域 */}\n {drag.liveRegion}\n {draggable && (\n <>\n {/* 鍵盤重排 SR 播報 — TreeView 自有 sr-only polite live region(單一節點覆寫式更新;\n 消費 select-menu.tsx SelectMenuLiveStatus 先例)。渲染在 role=\"treegrid\" 之外\n (treegrid 的合法 children 只有 row / rowgroup);dnd-kit DndContext 內建 live region\n 只播 dnd-kit drag session,自建鍵盤路徑觸不到,故必須自有。 */}\n <div role=\"status\" aria-live=\"polite\" className=\"sr-only\">\n {reorderAnnouncement}\n </div>\n {/* 鍵盤重排操作說明 — tree 容器 aria-describedby 指向(對齊 dnd-kit\n screenReaderInstructions 慣例:https://dndkit.com/guides/accessibility) */}\n <div id={reorderInstructionsId} className=\"sr-only\">\n {announcements.instructions}\n </div>\n </>\n )}\n {draggable && (\n <DragOverlay dropAnimation={null}>\n {draggingId ? (() => {\n const info = getNodeInfo(draggingId)\n const IconComp = info?.icon\n return (\n <div className={cn(\n 'flex items-center gap-2 rounded-lg bg-surface border border-border pointer-events-none',\n 'shadow-[var(--elevation-200)]',\n size === 'lg' ? 'text-body-lg leading-compact px-4 py-2' : 'text-body leading-compact px-3 py-1.5',\n )}>\n {IconComp && <IconComp size={ICON_SIZE[size]} className=\"shrink-0\" aria-hidden />}\n <span className=\"text-foreground truncate max-w-[200px]\">{info?.label ?? draggingId}</span>\n </div>\n )\n })() : null}\n </DragOverlay>\n )}\n </DndContext>\n </RowSizeProvider>\n </TreeViewContext.Provider>\n )\n }\n)\nTreeView.displayName = 'TreeView'\n\n// ═══════════════════════════════════════════════════════════════════════════\n// TreeItem variants\n// ═══════════════════════════════════════════════════════════════════════════\n\nconst treeItemVariants = cva(\n [\n // items-start:多行 label 時 prefix 留在第一行(item-layout 規則)\n 'flex items-start gap-2 w-full',\n 'cursor-pointer select-none',\n // hover 底色瞬間切換,不做過渡(user 2026-09-10 拍板「第三題改成全部瞬間」;SSOT = tokens/motion/motion.spec.md「hover 回饋不做過渡」)\n // 2026-09-07 刪 `outline-none`。2026-09-25 起這一列就是真焦點(roving tabindex,總帳 B9),\n // 焦點框由下方 TreeItem 的 `focus-visible:focus-ring-inset` 畫,更不能加 outline-none。\n // Label 字重 500(跟 SidebarMenuButton 一致)\n 'font-medium',\n ],\n {\n variants: {\n // 消費 ROW_PADDING_BY_SIZE SSOT(item-anatomy.tsx)— drift risk 消除\n size: ROW_PADDING_BY_SIZE,\n },\n defaultVariants: {\n size: 'md',\n },\n }\n)\n\n// ═══════════════════════════════════════════════════════════════════════════\n// TreeItem\n// ═══════════════════════════════════════════════════════════════════════════\n\nexport interface TreeItemProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'id'> {\n /** 唯一 id。必填,用於 expand / select / keyboard 追蹤 */\n id: string\n /** 主要文字 */\n label: React.ReactNode\n /** 左側 icon(chevron 之後)。LucideIcon 型別,尺寸由 TreeView size 決定 */\n icon?: LucideIcon\n /**\n * Checkbox(多選模式,label 前方)。傳入 ReactNode(Checkbox 元件)。\n * 位置:在 chevron 之後、indicator/icon 之前。\n * 此 slot 僅是列 selection 的視覺鏡像;有效 React element 會由 TreeItem\n * 強制正規化為 `aria-hidden` + `tabIndex=-1`,不形成第二個 Tab 停靠點。\n * 單選模式通常不需要(用 bg-neutral-selected 表達選中)。\n */\n checkbox?: React.ReactNode\n /**\n * 右側 inline actions(suffix slot,宣告式 API)。對齊 `patterns/element-anatomy/inline-action.spec.md`\n * 與 `SidebarMenuButton.inlineActions` 的同一條規格——TreeItem / SidebarMenuButton /\n * 未來的 row primitive 全部用同一個 declarative API。\n *\n * Consumer 只宣告 intent,TreeItem 用 `<ItemInlineAction>` 自動渲染:\n * - Icon 尺寸 = `ICON_SIZE[treeViewSize]`(自動)\n * - Hover bg、tooltip、aria-label、cursor-pointer 自動處理\n * - **不可以**手刻 button JSX(canonical 實作在 `patterns/element-anatomy/item-anatomy.tsx` `ItemInlineAction`)\n * - 鍵盤:按鈕不在 Tab 路上;焦點在列上按 → 進第一顆、→ / ← 在按鈕間走、第一顆再 ← 回列(tree-view.spec.md「鍵盤導覽」)\n *\n * ```tsx\n * <TreeItem\n * id=\"inbox\"\n * icon={Inbox}\n * label=\"Inbox\"\n * inlineActions={[\n * { icon: MoreVertical, label: '更多', onClick: handleMore },\n * { icon: Plus, label: '新增', onClick: handleAdd },\n * ]}\n * actionsReveal=\"hover\"\n * />\n * ```\n *\n * 若需要永遠可見的 suffix(如 badge 計數),放在 `label` 內:\n * ```tsx\n * <TreeItem label={<>Inbox <Badge count={3} /></>} />\n * ```\n */\n inlineActions?: InlineActionConfig[]\n /**\n * 右側 actions slot(ReactNode)— escape hatch 供 consumer 放自訂元素\n * (如 DropdownMenu trigger / 自訂 popover / 多 tier 動作)。\n *\n * 跟 `inlineActions` 互斥(同時傳 `inlineActionsSlot` 會優先,`inlineActions` 被忽略)。\n * slot 裡每一個可聚焦的元素都會被設成 `tabIndex=-1`、改由 → / ← 走到,而且**每一個都必須有可讀名稱**\n * (aria-label / 可見文字;缺的話 dev 模式 console.warn)。\n *\n * 規則對齊 Input.endSlot canonical:90% case 用 `inlineActions` 宣告式 API,\n * 10% config 表達不出時走 slot。視覺一致性由 consumer 負責(可使用 host 內部 helper\n * — 但禁止 app-code 直接 import L3 primitive,見 `check_canonical_propagation.sh` E.2,原 `check_l3_primitive_import` 已 folded)。\n */\n inlineActionsSlot?: React.ReactNode\n /**\n * Inline actions 的顯示模式:\n * - `\"hover\"`(預設):列被滑過、或鍵盤焦點在這一列(列本身或列裡的按鈕)時才出現,瞬間出現、不淡入\n * (待辦總帳 L9;規則住 ItemSuffix `hoverReveal`,見 tree-view.spec.md「Hover-only Inline Actions」)\n * - `false`:常駐顯示\n *\n * 對齊 `SidebarMenuButton.actionsReveal`,同一套規則。\n */\n actionsReveal?: false | \"hover\"\n /**\n * 取代 icon 的位置。用於 stepper 的 status indicator(●/○/✓)。\n * 設定後 icon 不渲染、改渲染 indicator;chevron 永遠保留(expandable=旋轉箭頭 / leaf=placeholder)。\n */\n indicator?: React.ReactNode\n /** 是否停用 */\n disabled?: boolean\n /** 子 TreeItem(有 children = expandable,沒有 = leaf) */\n children?: React.ReactNode\n}\n\n// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding\nconst TreeItem = React.forwardRef<HTMLDivElement, TreeItemProps>(\n ({ id, label, icon: Icon, checkbox, inlineActions, inlineActionsSlot, actionsReveal = 'hover', indicator, disabled, children, className, style, onClick, ...props }, ref) => {\n const ctx = useTreeView()\n const depth = React.useContext(DepthContext)\n const {\n size,\n selectionMode,\n expandOnSelect,\n draggable,\n expandedIds,\n selectedIds,\n focusedId,\n tabStopId,\n draggingId,\n dropTarget,\n toggleExpand,\n select,\n setFocusedId,\n focusRow,\n onRowUnmount,\n registerNode,\n unregisterNode,\n } = ctx\n\n const hasChildren = React.Children.count(children) > 0\n const isExpanded = expandedIds.has(id)\n const isSelected = selectedIds.has(id)\n const isFocused = focusedId === id\n // 整棵樹唯一的 Tab 停靠點(roving tabindex;總帳 B9)\n const isTabStop = tabStopId === id\n const isDragging = draggingId === id\n const isDropTarget = dropTarget?.id === id\n // 列名只取 label(aria-labelledby 指過來)。用 useId 而非 node id 組:node id 可能含空白,會把 IDREF 清單拆開\n const labelId = React.useId()\n // 沿用舊的真值判斷(slot 傳 false / '' 視同沒傳,改走 inlineActions)\n const hasSlot = Boolean(inlineActionsSlot)\n const hasActionCell = !disabled && (hasSlot || (inlineActions?.length ?? 0) > 0)\n const visualCheckbox = checkbox && React.isValidElement<Record<string, unknown>>(checkbox)\n ? React.cloneElement(checkbox, { 'aria-hidden': true, tabIndex: -1 })\n : checkbox\n\n const iconPx = ICON_SIZE[size]\n const indentPx = depth * INDENT_STEP[size]\n\n // ── Drag hooks ──\n // Figma 風格:整列可拖(不用 grip handle),靠 distance:5 區分 click vs drag\n const { attributes: dragAttrs, listeners: dragListeners, setNodeRef: setDragRef } = useDraggable({\n id, disabled: !draggable || disabled,\n })\n const { setNodeRef: setDropRef } = useDroppable({\n id, disabled: !draggable || disabled,\n })\n\n // ── 找 parent id(from depth context chain)──\n const parentId = React.useContext(ParentIdContext)\n\n // ── Register / unregister ──\n React.useEffect(() => {\n registerNode(id, parentId, hasChildren, label, Icon)\n return () => unregisterNode(id)\n }, [id, parentId, hasChildren, label, Icon, registerNode, unregisterNode])\n\n // 卸載時通知 TreeView 重選 Tab 停靠點(總帳 B9;見 TreeView onRowUnmount)\n React.useEffect(() => () => onRowUnmount(id), [id, onRowUnmount])\n\n // ── Refs ──\n const itemRef = React.useRef<HTMLDivElement>(null)\n React.useImperativeHandle(ref, () => itemRef.current!)\n const rowRef = React.useRef<HTMLDivElement | null>(null)\n const actionsRef = React.useRef<HTMLSpanElement | null>(null)\n\n // ── Focus scroll into view ──(捲的是列本身;原本捲 wrapper,展開的資料夾 wrapper 含整個子樹)\n React.useEffect(() => {\n if (isFocused && rowRef.current) {\n rowRef.current.scrollIntoView({ block: 'nearest' })\n }\n }, [isFocused])\n\n // ── 列被刪掉時焦點去下一列(2026-09-29 待辦總帳 N46「刪除 / 消失後焦點去下一項」)──\n // consumer 從資料裡拿掉這個節點時,焦點若在這一列或它的按鈕上,搬到**下一列**;沒有下一列 → **上一列**(上一列可能就是父節點)。\n // 落點順序同 FileUpload focusAfterFileRemoval(下一個 → 上一個 → 擁有者)。用 layout effect 的 cleanup:它在 React 把這棵子樹從 DOM\n // 拆掉之前跑,鄰列都還在、焦點還在列上;passive effect 的 cleanup 跑的時候節點已不在 DOM、焦點早掉到 body。真正搬焦點放到 microtask\n //(commit 同步結束之後):那時列真的不在 DOM 了才搬 —— StrictMode 開發模式會多跑一次 cleanup 再 re-mount,那一次列還在,不搬。\n // 收合中的分支(Collapsible 關閉動畫結束後才卸載、期間 data-state=\"closed\")不算候選:整段子樹都在拆,落點是上一列(= 收合的父節點)。\n // 整棵樹一起卸載時候選全空 / 目標也已離線,focus() 是 no-op。\n React.useLayoutEffect(() => () => {\n const row = rowRef.current\n const active = document.activeElement\n if (!row || !active || !row.contains(active)) return\n const tree = row.closest<HTMLElement>('[role=\"treegrid\"]')\n const wrapper = row.closest<HTMLElement>('[data-tree-id]')\n if (!tree || !wrapper) return\n const rows = Array.from(tree.querySelectorAll<HTMLElement>(ROW_SELECTOR))\n .filter((r) => !wrapper.contains(r) && isInOpenBranch(r) && isRowEnabled(r))\n const after = rows.find((r) => (wrapper.compareDocumentPosition(r) & Node.DOCUMENT_POSITION_FOLLOWING) !== 0)\n const before = [...rows].reverse().find((r) => (wrapper.compareDocumentPosition(r) & Node.DOCUMENT_POSITION_PRECEDING) !== 0)\n const target = after ?? before\n if (!target) return\n queueMicrotask(() => {\n if (row.isConnected) return\n const now = document.activeElement\n if (now && now !== document.body && now.isConnected) return // 有人(consumer 的對話框等)已經接走焦點\n target.focus({ preventScroll: true })\n })\n }, [])\n\n // ── 列上的按鈕不在 Tab 路上(總帳 B9;tree-view.spec.md「鍵盤導覽」)──\n // 動作格裡每一個可聚焦的東西都設 tabIndex=-1,改由 → / ← 走到。用 DOM 設而不是逐一傳 prop:\n // inlineActionsSlot 是 consumer 的任意 ReactNode(選單觸發鈕、連結…),拿不到它們的 props;\n // MutationObserver 接住 slot 內容之後才掛上 / 自己改回 tabindex 的情況。\n React.useLayoutEffect(() => {\n const cell = actionsRef.current\n if (!cell) return\n const enforce = () => {\n const controls = cell.querySelectorAll<HTMLElement>(ROVING_CONTROL_SELECTOR)\n removeRovingControlsFromTabOrder(controls)\n // 每顆列上按鈕都要有可讀名稱(總帳 B9 驗收;inlineActions 由 ItemInlineAction 的 aria-label 保證,這裡管 slot)\n if (process.env.NODE_ENV === 'production') return\n controls.forEach((el) => {\n if (!hasAccessibleName(el) && !warnedUnnamedAction.has(el)) {\n warnedUnnamedAction.add(el)\n console.warn(`[DS] TreeItem「${id}」:inlineActionsSlot 裡有一個可聚焦元素沒有可讀名稱 —— 請加 aria-label 或可見文字。`, el)\n }\n })\n }\n enforce()\n const observer = new MutationObserver(enforce)\n observer.observe(cell, { subtree: true, childList: true, attributes: true, attributeFilter: ['tabindex'] })\n return () => observer.disconnect()\n }, [hasActionCell, id])\n\n // ── Handlers ──\n const handleRowClick = React.useCallback(\n (e: React.MouseEvent<HTMLDivElement>) => {\n onClick?.(e)\n if (e.defaultPrevented || disabled) return\n e.stopPropagation()\n // 焦點:點在列上 → 列拿到焦點(roving tabindex 的停靠點跟著搬);\n // 點在列上的按鈕(或對它按 Enter 合成的 click)→ 焦點留在那顆按鈕上,不搶(總帳 B9)。\n // 選取行為沿用既有(點到按鈕也會選取該列,本次不改;見 spec「展開/收合」)。\n const fromActionCell = (e.target as HTMLElement).closest('[data-tree-actions]')\n if (!fromActionCell) focusRow(id)\n setFocusedId(id)\n select(id)\n if (expandOnSelect && hasChildren) {\n toggleExpand(id)\n }\n },\n [id, disabled, onClick, focusRow, select, setFocusedId, expandOnSelect, hasChildren, toggleExpand]\n )\n\n const handleChevronClick = React.useCallback(\n (e: React.MouseEvent) => {\n e.stopPropagation()\n if (disabled) return\n // 滑鼠點展開箭頭時瀏覽器會把焦點給這顆(tabIndex=-1)按鈕 —— 還給列,鍵盤接著從這一列走\n focusRow(id)\n setFocusedId(id)\n toggleExpand(id)\n },\n [id, disabled, focusRow, setFocusedId, toggleExpand]\n )\n\n // dnd-kit PointerSensor listener 與 consumer DOM callback 必安全 compose:consumer 先收到\n // event,可用 preventDefault 取消 drag;未取消才執行 internal listener。`...props` 會先\n // spread,這份 composed map 隨後覆蓋同名 pointer listener,避免任一方被靜默吃掉。\n const composedDragListeners = dragListeners\n ? Object.fromEntries(\n Object.entries(dragListeners).map(([eventName, internalHandler]) => [\n eventName,\n (event: React.SyntheticEvent<HTMLElement>) => {\n const consumerHandler = (props as Record<string, unknown>)[eventName]\n if (typeof consumerHandler === 'function') {\n ;(consumerHandler as (event: React.SyntheticEvent<HTMLElement>) => void)(event)\n }\n if (!event.defaultPrevented) {\n ;(internalHandler as (event: React.SyntheticEvent<HTMLElement>) => void)(event)\n }\n },\n ])\n ) as typeof dragListeners\n : undefined\n\n // ── Chevron(永遠存在:expandable = 旋轉箭頭;leaf = placeholder 佔位) ──\n // 消費 `<ItemPrefix>` SSOT — 永遠 h-[1lh] 對齊 label 第一行中線(item-anatomy 對應)。\n // forced width 透過 style 鎖 chevron 槽寬,讓 sibling label 起點水平對齊(無 chevron leaf 佔位同寬;縮排不變)。\n // 箭頭本身 = 共用行內小按鈕 `ItemInlineActionButton`(2026-09-26,待辦總帳 L5,user:「是改成inline action對吧?」;\n // 依據 inline-action.spec.md「尺寸對照」TreeItem 列 + hit-area-canonical.md:滑過底色 18(lg 22)= 點得到的範圍,\n // 盒與排版佔位仍是圖示尺寸 16 / 20,多出的 1px 靠溢出;按下多一階 neutral-active)。與 DataTable 巢狀列的展開箭頭同一顆\n //(data-table.tsx nestedPrefix)。2026-09-26 之前這裡手刻一顆 16×16 的 <button>,滑過底色與點擊範圍都只有 16。\n // 鍵盤用 → / ← 展開收合(列上的 aria-expanded),箭頭本身不在 Tab 路上(tabIndex -1),**但保留名字與角色、不 aria-hidden**\n //(2026-09-29 待辦總帳 N46):語音控制使用者喊「展開」才按得到、讀屏在列裡也叫得到它;與 DataTable 巢狀列的展開箭頭同一種寫法\n //(data-table.tsx nestedPrefix:aria-label 展開 / 收合 + aria-expanded)。2026-09-29 前寫 aria-hidden,一顆能聚焦的按鈕對讀屏隱藏\n // 也是 axe aria-hidden-focus 那一族。\n const chevronSlot = (\n <ItemPrefix style={{ width: iconPx }}>\n {hasChildren ? (\n <ItemInlineActionButton\n icon={ChevronRight}\n tabIndex={-1}\n aria-label={isExpanded ? '收合' : '展開'}\n aria-expanded={isExpanded}\n onClick={handleChevronClick}\n // 展開 / 收合的旋轉是狀態切換的動畫,不是滑過回饋 → 保留(motion.spec.md「hover 回饋不做過渡」只管滑過;\n // 滑過底色與圖示色由 ItemInlineActionButton 瞬間切換)\n iconClassName={cn('transition-transform duration-150 motion-reduce:duration-0', isExpanded && 'rotate-90')}\n className={disabled ? 'text-fg-disabled pointer-events-none' : undefined}\n />\n ) : (\n // Leaf placeholder\n <span style={{ width: iconPx }} aria-hidden />\n )}\n </ItemPrefix>\n )\n\n return (\n <ParentIdContext.Provider value={id}>\n <div\n ref={(node) => {\n (itemRef as React.MutableRefObject<HTMLDivElement | null>).current = node\n if (typeof ref === 'function') ref(node)\n else if (ref) (ref as React.MutableRefObject<HTMLDivElement | null>).current = node\n }}\n // node wrapper(2026-09-25 總帳 B9):無角色 —— 樹狀表格的列不得包住子列,角色與 aria-* 都搬到下面的 row。\n // data-tree-* 留在這裡給內部查詢 / 拖曳 / 閘使用。\n data-tree-id={id}\n data-tree-parent-id={parentId ?? ''}\n data-tree-has-children={hasChildren}\n className={cn('w-full min-w-0 relative', isDragging && dragSourceClass)}\n >\n {/* Drop indicator — before:水平 2px primary line(指 SSOT drag-visual.ts);\n indent 跟隨 depth(left 由 inline style override class 的 left-0)*/}\n {isDropTarget && dropTarget?.position === 'before' && (\n <div\n className={dropIndicatorRow.before}\n style={{ left: `calc(var(--tree-px) + ${indentPx}px)` }}\n />\n )}\n\n {/* Row: draggable + droppable 都在這一行(合併 ref),確保碰撞偵測只看行高 */}\n <div\n {...props}\n ref={(node) => {\n // 合併 drag + drop + 本地 ref 到同一個 element\n rowRef.current = node\n if (draggable) setDragRef(node)\n setDropRef(node)\n }}\n // ── 樹狀表格的列(2026-09-25 總帳 B9)──\n // role / aria-* / tabIndex 放在 {...props} 之後:內部語意必須勝過 consumer 誤傳。\n role=\"row\"\n aria-level={depth + 1}\n aria-expanded={hasChildren ? isExpanded : undefined}\n aria-selected={selectionMode !== 'none' ? isSelected : undefined}\n aria-disabled={disabled || undefined}\n // 列的名稱只取 label —— 不讓動作按鈕的 aria-label 併進列名(否則念成「Alice 重新命名 刪除」)\n aria-labelledby={labelId}\n // 2026-07-05 D4 修:aria-roledescription 放在有角色的元素上(原 treeitem,現 row)—\n // 供 AT 提示此 node 可拖曳;值消費 dnd-kit attributes SSOT(預設 'draggable')。\n aria-roledescription={draggable && !disabled ? dragAttrs['aria-roledescription'] : undefined}\n // roving tabindex:整棵樹只有停靠點那一列是 0,其餘列與所有列上按鈕都是 -1(總帳 B9)\n tabIndex={isTabStop ? 0 : -1}\n data-tree-row={id}\n className={cn(\n 'group/tree-item',\n treeItemVariants({ size }),\n // 2026-05-26 SSOT lock(user explicit「multi 已有 checkbox 強信號,text 不該再變色」):\n // ── Single mode ──\n // - default text 預設 fg-secondary muted(hierarchy navigation 慣例,跟 Sidebar 一致)\n // - selected → text-foreground emphasis + bg-neutral-selected(無 checkbox,需 text+bg 雙信號)\n // ── Multi mode ──\n // - default text 維持 fg-secondary muted(跟 single 對齊 hierarchy)\n // - selected → 視覺信號只在 checkbox(auto-render below),text 不變、bg 不變\n // - 對齊 SelectMenu multi pattern(menu-item.tsx:194-195 selected → bg only;multi → checkbox only)\n // multi-selected 也維持 fg-secondary(上方註解「text 不變」;原 !isSelected 條件讓 multi-selected 掉到繼承色)\n !disabled && (!isSelected || selectionMode === 'multiple') && 'text-fg-secondary',\n !disabled && isSelected && selectionMode === 'single' && 'text-foreground',\n !disabled && 'hover:bg-neutral-hover hover:text-foreground',\n // 2026-08-11 修偏移(SSOT = item-anatomy「選中 × 互動疊加」):先前 hover:bg-neutral-hover\n //(0,2,0)蓋掉無修飾的 bg-neutral-selected(0,1,0)→ 選中列 hover 反而變淺 = bug。\n // 釘住 hover 不變(twMerge 同組後者勝)。鍵盤焦點不改底色,只畫框(focus-canonical 規則二)。\n !disabled && isSelected && selectionMode === 'single' && 'bg-neutral-selected hover:bg-neutral-selected',\n // 落點底色排在所有其他 `bg-*` 之後 —— twMerge 同組後者勝。\n // 2026-09-06 修:原本排在 `hover:bg-neutral-hover` 與 `bg-neutral-selected` **之前**,\n // 造成兩個都會發生的 bug —— (1) 已選中的列 twMerge 直接把 `bg-drop-target` 刪掉,\n // 實跑 tailwind-merge 3.5.0 輸出只剩 `hover:text-foreground bg-neutral-selected\n // hover:bg-neutral-selected`;(2) 未選中的列兩個 class 都留著,但 hover 特異性較高、\n // 游標又必然停在該列上,落點底色一樣看不到。`dropIndicatorInside` 自帶 `hover:` 同色治 (2),\n // 這行的位置治 (1)。\n isDropTarget && dropTarget?.position === 'inside' && dropIndicatorInside,\n // 焦點框(2026-09-25 總帳 B9:虛擬游標 showRing → 真焦點 focus-visible):\n // 這一列現在是真 DOM 焦點,「這次要不要畫」交給瀏覽器的 :focus-visible(滑鼠點 → 不畫、鍵盤 → 畫),\n // 與 Sidebar 選單鈕同一種寫法(focus-canonical「真焦點用 focus-visible:」)。\n // 往內畫(outline 通道,base.css @utility):列撐滿容器寬,往外會被裁。\n // 只在列**本身**是焦點時畫;焦點在列上的按鈕時由那顆按鈕自己畫框,列不再多畫一個。\n 'focus-visible:focus-ring-inset',\n disabled && 'pointer-events-none text-fg-disabled cursor-default',\n className,\n )}\n style={{\n ...style,\n // Structural indent 由 TreeView SSOT 最終擁有,consumer style 不得抹掉。\n paddingLeft: indentPx > 0\n ? `calc(var(--tree-px) + ${indentPx}px)`\n : 'var(--tree-px)',\n paddingRight: 'var(--tree-px)',\n }}\n // 2026-07-05 D4 修:只 spread listeners,不 spread dnd-kit attributes —\n // useDraggable 預設 attributes 注入 role=\"button\" + tabIndex=0 + aria-pressed +\n // aria-describedby(鍵盤拖曳指示):role=button 會蓋掉列的語意、tabIndex=0 會讓每一列都變成\n // Tab 停靠點(破壞整棵樹只佔一站);sensors 僅 PointerSensor(無 KeyboardSensor),這些 attrs 換不到鍵盤拖曳能力;\n // 鍵盤重排(2026-07-14 v1)走容器 handleKeyDownCapture 的 Cmd/Ctrl+Shift+Arrow 分支,非 dnd-kit sensor。\n // aria-roledescription 保留在上方列元素(有 role 才合法)。\n {...(draggable ? composedDragListeners : {})}\n onClick={handleRowClick}\n >\n {/* 第一格:chevron / checkbox / indicator 或 icon / label(樹狀表格 row 只能擁有格,2026-09-25 總帳 B9) */}\n <div role=\"gridcell\" className=\"flex flex-1 min-w-0 items-start gap-2\">\n {chevronSlot}\n\n {/* Checkbox 在 icon 前——消費 `<ItemPrefix>` 對齊第一行\n * 2026-05-26 SSOT lock(user explicit「多選的方式應該也是要跟 menu 一樣是出現 checkbox」):\n * - selectionMode='multiple' + 無 consumer checkbox prop → auto-render `<Checkbox>` reflect selectedIds\n * (對齊 SelectMenu multi pattern;consumer 不用手寫 checkbox)\n * - selectionMode='multiple' + consumer 傳 checkbox → 用 consumer 的(parent-child cascade 等 advanced)\n * - selectionMode='single' / 'none' → 不 render checkbox(text-foreground + bg 雙信號表 selected)\n * 對齊 cite:menu-item.tsx:194-195(MenuItem selected bg)+ select-menu.tsx:352-354(SelectMenu multi=checkbox) */}\n {(checkbox || selectionMode === 'multiple') && (\n <ItemPrefix className=\"pointer-events-none\">\n {/* 2026-07-05 D4 修:auto-render Checkbox 補 tabIndex={-1} — 勾選框的 root 是原生 button\n * (預設可聚焦),aria-hidden + 可聚焦 = axe aria-hidden-focus,且會多出 Tab 停靠點\n * (tree-view.spec.md「A11y 預設」Focus 段);ItemPrefix pointer-events-none 只擋滑鼠不擋鍵盤。\n * 選取語意由列的 aria-selected 承載,checkbox 純視覺反映。 */}\n {visualCheckbox || <Checkbox checked={isSelected} disabled={disabled} aria-hidden=\"true\" tabIndex={-1} />}\n </ItemPrefix>\n )}\n\n {/* indicator 取代 icon 的位置;h-[1lh] 對齊第一行\n indicator 是 escape hatch(stepper status dot 等客製內容),消費 `<ItemPrefix>` 鎖 chevron 槽寬;\n Icon 走 canonical `<ItemIcon>` helper——自動標 data-prefix-type=\"icon\",\n 讓 SidebarProvider 的全域 :has() prefix-mix 偵測能命中。 */}\n {indicator ? (\n <ItemPrefix style={{ width: iconPx }}>\n {indicator}\n </ItemPrefix>\n ) : Icon ? (\n <ItemIcon icon={Icon} className={disabled ? 'text-fg-disabled' : undefined} />\n ) : null}\n\n <span id={labelId} className={cn('flex-1 min-w-0 truncate', disabled && 'text-fg-disabled')}>\n {label}\n </span>\n </div>\n\n {/* 第二格:Suffix inline actions——宣告式 API,用 `<ItemInlineAction>` 渲染。\n 消費 `<ItemSuffix hoverReveal hoverGroup=\"tree-item\">` SSOT(2026-05-05 v8 group selector 參數化後)。\n actionsReveal=\"hover\"(預設):列被滑過、或鍵盤焦點在這一列(列本身 / 列裡的按鈕)才顯示;\n actionsReveal=false:常駐顯示。跟 SidebarMenuButton 共用同一條規則,行為一致。\n inlineActionsSlot escape hatch 優先(consumer 自控 JSX,reveal 一樣套外層 group)。\n 2026-06-12 R2(同 sidebar.tsx 修):宿主 disabled 時 render 層擋 inline actions —\n inline-action.spec.md「宿主 disabled | 不渲染」;row pointer-events 蓋不住\n actionsReveal=false 常駐顯示的視覺暗示,必須 render 層 guard。 */}\n {/* 動作格的 hover-reveal 另加 `group-focus-visible/tree-item`:ItemSuffix 內建的\n group-has-[:focus-visible] 只看**子孫**,列自己拿到鍵盤焦點時抓不到(總帳 B9:\n 滑過才出現的按鈕,焦點在這一列裡時必須看得到)。 */}\n {hasActionCell && (\n <ItemSuffix\n ref={actionsRef}\n role=\"gridcell\"\n data-tree-actions=\"\"\n hoverReveal={actionsReveal === 'hover'}\n hoverGroup=\"tree-item\"\n className={actionsReveal === 'hover' ? 'group-focus-visible/tree-item:opacity-100' : undefined}\n >\n {hasSlot\n ? inlineActionsSlot\n : inlineActions!.map((action, i) => (\n <ItemInlineAction key={action.label + i} action={action} />\n ))}\n </ItemSuffix>\n )}\n </div>\n\n {/* Drop indicator — after:同 before mirror 到 bottom edge(SSOT drag-visual.ts)*/}\n {isDropTarget && dropTarget?.position === 'after' && (\n <div\n className={dropIndicatorRow.after}\n style={{ left: `calc(var(--tree-px) + ${indentPx}px)` }}\n />\n )}\n\n {/* Children: Collapsible 展開/收合。\n 2026-09-25:子項容器不再是 role=\"group\"(樹狀表格裡 row 之間不允許 group);\n data-tree-children 讓鍵盤查詢認得「關閉動畫中(data-state=closed)的子樹」並跳過。 */}\n {hasChildren && (\n <CollapsiblePrimitive.Root open={isExpanded}>\n <CollapsiblePrimitive.Content\n data-tree-children=\"\"\n className=\"overflow-hidden data-[state=closed]:animate-collapsible-up data-[state=open]:animate-collapsible-down motion-reduce:animate-none\"\n >\n <DepthContext.Provider value={depth + 1}>\n <div className=\"flex flex-col w-full\">\n {children}\n </div>\n </DepthContext.Provider>\n </CollapsiblePrimitive.Content>\n </CollapsiblePrimitive.Root>\n )}\n </div>\n </ParentIdContext.Provider>\n )\n }\n)\nTreeItem.displayName = 'TreeItem'\n\n// Parent ID context for keyboard navigation (← to parent)\nconst ParentIdContext = React.createContext<string | null>(null)\n\n// ═══════════════════════════════════════════════════════════════════════════\n// Exports\n// ═══════════════════════════════════════════════════════════════════════════\n\n// Story auto-compile metadata — Phase 1 mechanical migration(2026-04-24)\n// Phase 2 fill needed: purpose descriptions + when rationale + world-class refs\nexport const treeViewMeta = {\n component: 'TreeView',\n family: 1, // Family 1(Menu item layout)消費者 — 對齊 tree-view.spec.md frontmatter family: 1\n variants: {\n\n },\n sizes: {\n // 對齊 tree-view.spec.md frontmatter(compile-stories --check 驗 key 一致)\n sm: { typography: 'body', indent: 24 },\n md: { typography: 'body', indent: 24 }, // default\n lg: { typography: 'body-lg', indent: 28 },\n },\n // 'selected' = single-selection 持續選中(bg-neutral-selected + aria-selected);'active' 移除 —\n // 全檔無 Tailwind 按壓 utility,無按壓專屬視覺態(2026-07-07 詞彙統一對抗稽核補修)。\n states: ['default', 'hover', 'selected', 'focus-visible', 'disabled'],\n tokens: {\n bg: ['bg-neutral-hover', 'bg-neutral-selected', 'bg-surface'],\n fg: ['text-fg-disabled', 'text-fg-muted', 'text-fg-secondary', 'text-foreground'],\n ring: ['focus-ring-inset'],\n },\n defaultSize: 'md',\n} as const\n\nexport { TreeView, TreeItem, treeItemVariants }\n"],"names":["treeEl"],"mappings":"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"}
|
|
@@ -1,22 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* **最後一次搬動「反白」的是誰**(SSOT:ds-canonical/references/focus-canonical.md 規則一「兩類元件」+ 規則二)。
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* not keyboard.」(https://github.com/WICG/focus-visible/blob/main/explainer.md)
|
|
11
|
-
* Shift 不在排除清單內(Shift+Tab 是鍵盤導覽)。**滑鼠移動不算輸入**(polyfill 只監聽 mousedown / pointerdown /
|
|
12
|
-
* touchstart;mousemove 只在載入時用來判初始模態,第一次移動後就移除 listener —— src/focus-visible.js
|
|
13
|
-
* `onInitialPointerMove`),否則常駐清單的鍵盤框會被滑鼠一晃就抹掉,跟瀏覽器對真焦點的行為不一致。
|
|
4
|
+
* 2026-09-26 起本檔只剩這一個訊號。原本的第一個訊號 `useInputModality()`(最近一次輸入是鍵盤還是指標,WICG `:focus-visible`
|
|
5
|
+
* 啟發式,給常駐清單的虛擬游標)唯一的消費者是 TreeView 的 `showRing`;2026-09-25 TreeView 改成列上的真焦點(待辦總帳 B9、
|
|
6
|
+
* 批次細節 X8「樹改真焦點」),框改由瀏覽器的 `:focus-visible` 決定,那個訊號就沒有任何消費者了 —— 留著 = 一份沒人讀的第二套判準,
|
|
7
|
+
* 下一個人會以為常駐清單還有兩種畫框依據。刪除依據:待辦總帳〇節「09-26 同意清單回覆」(同意的清單含「按鍵規則合併」與 X8),
|
|
8
|
+
* user 逐字:「確保符合我們一致的設計語言且不違背世界級的設計且都有確保整個ds 是SSOT,避免漂移就照你建議做」。
|
|
9
|
+
* 常駐清單(TreeView / Sidebar / FileUpload 檔案清單)現在全部是真焦點 + `focus-visible:`,不需要這裡。
|
|
14
10
|
*
|
|
15
|
-
*
|
|
11
|
+
* `useCursorMover()` + `markPointerGrab()` —— 給會搶反白的浮層選單(cmdk / Radix Menu)。
|
|
16
12
|
* 這類選單裡反白只有一個主人:滑鼠移過項目就把反白搶走(cmdk `onPointerMove → select()` /
|
|
17
13
|
* Radix `onPointerMove → item.focus()`),鍵盤方向鍵再搶回來;兩種畫法(滑鼠 → 底色、鍵盤 → 框)永遠不同時出現。
|
|
18
14
|
* 所以這裡**滑鼠移過項目要算**(那就是搶),但**滑鼠停著不算**(只有移動才會觸發 pointermove)——
|
|
19
|
-
*
|
|
15
|
+
* 這一點與瀏覽器的 `:focus-visible` 啟發式相反(那個只看按下,不看移動)。項目在 `onPointerMoveCapture` 呼叫 `markPointerGrab`,
|
|
16
|
+
* keydown 統一在 document 記成鍵盤 ——
|
|
20
17
|
* **但在文字輸入框裡打字不算搬游標**(2026-09-10 user:「滑鼠點擊輸入框然後輸入 a,再點 backspace,選單上會出現鍵盤焦點的藍色邊框」):
|
|
21
18
|
* 字元 / Backspace / Delete / 空白 是在編輯文字,反白跳到第一個符合項是函式庫的自動落點(cmdk `search` 一變就
|
|
22
19
|
* `schedule(1, selectFirstItem)`),沒有人「搬」它;只有方向鍵 / Home / End / PageUp / PageDown / Tab / Esc 才算鍵盤搬游標。
|
|
@@ -30,7 +27,7 @@
|
|
|
30
27
|
* 由來(2026-09-08 user 抓到):滑鼠點開 Select,已選項立刻出現鍵盤焦點框。
|
|
31
28
|
* 根因是 cmdk 開啟時把游標放在已選項上,而「已選 + 游標」的畫框規則沒有模態條件。
|
|
32
29
|
* 同款在 DropdownMenu / AgentPanel 歷史清單各一份,TreeView 自帶一份 `isKeyboardRef` ——
|
|
33
|
-
* 四份各自實作 = 四份 SSOT(M17)
|
|
30
|
+
* 四份各自實作 = 四份 SSOT(M17),收攏到這裡(TreeView 那一份之後已隨改真焦點退役,見上)。
|
|
34
31
|
*
|
|
35
32
|
* 監聽掛在 document 的 capture 階段,**模組載入即安裝**(有 `document` 才裝,SSR 安全)。
|
|
36
33
|
* 第一版是「第一個消費者掛載時才安裝」——實測會漏:鍵盤開啟選單時,Tab / ArrowDown 都發生在
|
|
@@ -39,11 +36,9 @@
|
|
|
39
36
|
*
|
|
40
37
|
* 另外訂閱當下若還沒觀察到任何輸入(模組載入晚於使用者互動時會這樣),
|
|
41
38
|
* 用瀏覽器自己的 `document.activeElement.matches(':focus-visible')` 當種子 ——
|
|
42
|
-
*
|
|
39
|
+
* 「它就是『這次聚焦該不該給可見指示』的權威答案」。
|
|
43
40
|
*/
|
|
44
41
|
export type InputModality = "keyboard" | "pointer";
|
|
45
|
-
/** 最近一次輸入是鍵盤還是指標(WICG :focus-visible 啟發式;給常駐清單的虛擬游標)。 */
|
|
46
|
-
export declare function useInputModality(): InputModality;
|
|
47
42
|
/** 最後一次搬動反白的是鍵盤還是指標(給會搶反白的浮層選單:cmdk / Radix Menu 的項目)。 */
|
|
48
43
|
export declare function useCursorMover(): InputModality;
|
|
49
44
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-input-modality.d.ts","sourceRoot":"","sources":["../../src/hooks/use-input-modality.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"use-input-modality.d.ts","sourceRoot":"","sources":["../../src/hooks/use-input-modality.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,SAAS,CAAA;AA4ElD,0DAA0D;AAC1D,wBAAgB,cAAc,IAAI,aAAa,CAE9C;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,EAAE,CAAC,EAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,QAI5D"}
|