@qijenchen/design-system 0.1.0-beta.83 → 0.1.0-beta.85
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/CLAUDE.md +8 -8
- package/README.md +3 -2
- package/dist/components/Alert/alert.js.map +1 -1
- package/dist/components/AppShell/_demo-helpers.d.ts.map +1 -1
- package/dist/components/AppShell/app-shell.d.ts +1 -1
- package/dist/components/AppShell/app-shell.d.ts.map +1 -1
- package/dist/components/AppShell/app-shell.js +22 -2
- package/dist/components/AppShell/app-shell.js.map +1 -1
- package/dist/components/Avatar/avatar.d.ts.map +1 -1
- package/dist/components/Avatar/avatar.js +4 -4
- package/dist/components/Avatar/avatar.js.map +1 -1
- package/dist/components/Breadcrumb/breadcrumb.d.ts.map +1 -1
- package/dist/components/Breadcrumb/breadcrumb.js +1 -1
- package/dist/components/Breadcrumb/breadcrumb.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 +12 -3
- package/dist/components/Button/button.js.map +1 -1
- package/dist/components/Calendar/calendar.d.ts +13 -5
- package/dist/components/Calendar/calendar.d.ts.map +1 -1
- package/dist/components/Calendar/calendar.js +7 -1
- package/dist/components/Calendar/calendar.js.map +1 -1
- package/dist/components/Carousel/carousel.d.ts.map +1 -1
- package/dist/components/Carousel/carousel.js +8 -3
- package/dist/components/Carousel/carousel.js.map +1 -1
- package/dist/components/Chart/chart.d.ts.map +1 -1
- package/dist/components/Chart/chart.js +1 -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 +8 -4
- 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.map +1 -1
- package/dist/components/Coachmark/coachmark.d.ts +10 -3
- package/dist/components/Coachmark/coachmark.d.ts.map +1 -1
- package/dist/components/Coachmark/coachmark.js.map +1 -1
- package/dist/components/Combobox/combobox.d.ts +5 -1
- package/dist/components/Combobox/combobox.d.ts.map +1 -1
- package/dist/components/Combobox/combobox.js +30 -13
- package/dist/components/Combobox/combobox.js.map +1 -1
- package/dist/components/DataTable/cell-registry.d.ts.map +1 -1
- package/dist/components/DataTable/cell-registry.js +53 -62
- package/dist/components/DataTable/cell-registry.js.map +1 -1
- package/dist/components/DataTable/column-types.d.ts +10 -4
- package/dist/components/DataTable/column-types.d.ts.map +1 -1
- package/dist/components/DataTable/column-types.js.map +1 -1
- package/dist/components/DataTable/data-table-column-visibility-panel.d.ts +1 -1
- package/dist/components/DataTable/data-table-column-visibility-panel.js +183 -0
- package/dist/components/DataTable/data-table-column-visibility-panel.js.map +1 -0
- package/dist/components/DataTable/data-table-filter-group.d.ts +47 -0
- package/dist/components/DataTable/data-table-filter-group.d.ts.map +1 -0
- package/dist/components/DataTable/data-table-filter-group.js +154 -0
- package/dist/components/DataTable/data-table-filter-group.js.map +1 -0
- package/dist/components/DataTable/data-table-filter-panel.d.ts.map +1 -1
- package/dist/components/DataTable/data-table-filter-panel.js +285 -0
- package/dist/components/DataTable/data-table-filter-panel.js.map +1 -0
- package/dist/components/DataTable/data-table-filter-value-picker.d.ts +29 -0
- package/dist/components/DataTable/data-table-filter-value-picker.d.ts.map +1 -0
- package/dist/components/DataTable/data-table-filter-value-picker.js +187 -0
- package/dist/components/DataTable/data-table-filter-value-picker.js.map +1 -0
- package/dist/components/DataTable/data-table-interaction-layer.d.ts +11 -15
- package/dist/components/DataTable/data-table-interaction-layer.d.ts.map +1 -1
- package/dist/components/DataTable/data-table-interaction-layer.js +1 -3
- package/dist/components/DataTable/data-table-interaction-layer.js.map +1 -1
- package/dist/components/DataTable/data-table-sort-manager.js +140 -0
- package/dist/components/DataTable/data-table-sort-manager.js.map +1 -0
- package/dist/components/DataTable/data-table.d.ts +5 -6
- package/dist/components/DataTable/data-table.d.ts.map +1 -1
- package/dist/components/DataTable/data-table.js +36 -15
- package/dist/components/DataTable/data-table.js.map +1 -1
- package/dist/components/DataTable/filter-operators.js +140 -0
- package/dist/components/DataTable/filter-operators.js.map +1 -0
- package/dist/components/DataTable/filter-tree.js +312 -0
- package/dist/components/DataTable/filter-tree.js.map +1 -0
- package/dist/components/DataTable/index.d.ts +3 -0
- package/dist/components/DataTable/index.d.ts.map +1 -1
- package/dist/components/DataTable/index.js +12 -1
- package/dist/components/DataTable/index.js.map +1 -1
- package/dist/components/DataTable/lib/column-meta.js +25 -0
- package/dist/components/DataTable/lib/column-meta.js.map +1 -0
- package/dist/components/DateGrid/date-grid.d.ts.map +1 -1
- package/dist/components/DateGrid/date-grid.js +3 -1
- package/dist/components/DateGrid/date-grid.js.map +1 -1
- package/dist/components/DatePicker/date-picker.d.ts +7 -1
- package/dist/components/DatePicker/date-picker.d.ts.map +1 -1
- package/dist/components/DatePicker/date-picker.js +105 -74
- package/dist/components/DatePicker/date-picker.js.map +1 -1
- package/dist/components/Dialog/dialog.d.ts +20 -3
- package/dist/components/Dialog/dialog.d.ts.map +1 -1
- package/dist/components/Dialog/dialog.js +38 -20
- package/dist/components/Dialog/dialog.js.map +1 -1
- package/dist/components/DropdownMenu/dropdown-menu.d.ts.map +1 -1
- package/dist/components/DropdownMenu/dropdown-menu.js +3 -1
- package/dist/components/DropdownMenu/dropdown-menu.js.map +1 -1
- package/dist/components/Field/field-context.d.ts +45 -5
- package/dist/components/Field/field-context.d.ts.map +1 -1
- package/dist/components/Field/field-context.js +19 -0
- package/dist/components/Field/field-context.js.map +1 -1
- package/dist/components/Field/field-edit-keys.d.ts +21 -0
- package/dist/components/Field/field-edit-keys.d.ts.map +1 -0
- package/dist/components/Field/field-edit-keys.js +19 -0
- package/dist/components/Field/field-edit-keys.js.map +1 -0
- package/dist/components/Field/field-types.d.ts +20 -1
- package/dist/components/Field/field-types.d.ts.map +1 -1
- package/dist/components/Field/field-types.js.map +1 -1
- package/dist/components/Field/field-wrapper.d.ts +3 -2
- package/dist/components/Field/field-wrapper.d.ts.map +1 -1
- package/dist/components/Field/field-wrapper.js +38 -39
- package/dist/components/Field/field-wrapper.js.map +1 -1
- package/dist/components/Field/field.d.ts +2 -1
- package/dist/components/Field/field.d.ts.map +1 -1
- package/dist/components/Field/field.js.map +1 -1
- package/dist/components/Field/index.d.ts +1 -0
- package/dist/components/Field/index.d.ts.map +1 -1
- package/dist/components/Field/index.js +2 -0
- package/dist/components/Field/index.js.map +1 -1
- package/dist/components/FieldControlGroup/field-control-group.d.ts +1 -1
- package/dist/components/FieldControlGroup/field-control-group.js.map +1 -1
- package/dist/components/FileItem/file-item.d.ts.map +1 -1
- package/dist/components/FileItem/file-item.js +1 -1
- package/dist/components/FileItem/file-item.js.map +1 -1
- package/dist/components/FileUpload/file-upload.d.ts +1 -1
- package/dist/components/FileUpload/file-upload.js.map +1 -1
- package/dist/components/FileViewer/file-viewer.d.ts.map +1 -1
- package/dist/components/FileViewer/file-viewer.js +7 -2
- package/dist/components/FileViewer/file-viewer.js.map +1 -1
- package/dist/components/FileViewer/image-renderer.d.ts.map +1 -1
- package/dist/components/FileViewer/image-renderer.js +22 -1
- package/dist/components/FileViewer/image-renderer.js.map +1 -1
- package/dist/components/HoverCard/hover-card.d.ts.map +1 -1
- package/dist/components/HoverCard/hover-card.js +5 -3
- package/dist/components/HoverCard/hover-card.js.map +1 -1
- package/dist/components/InlineEdit/index.d.ts +2 -0
- package/dist/components/InlineEdit/index.d.ts.map +1 -0
- package/dist/components/InlineEdit/index.js +5 -0
- package/dist/components/InlineEdit/index.js.map +1 -0
- package/dist/components/InlineEdit/inline-edit.d.ts +59 -0
- package/dist/components/InlineEdit/inline-edit.d.ts.map +1 -0
- package/dist/components/InlineEdit/inline-edit.js +164 -0
- package/dist/components/InlineEdit/inline-edit.js.map +1 -0
- package/dist/components/Input/input.d.ts +6 -11
- package/dist/components/Input/input.d.ts.map +1 -1
- package/dist/components/Input/input.js +43 -13
- package/dist/components/Input/input.js.map +1 -1
- package/dist/components/LinkInput/link-input.d.ts +2 -2
- package/dist/components/LinkInput/link-input.d.ts.map +1 -1
- package/dist/components/LinkInput/link-input.js +6 -5
- package/dist/components/LinkInput/link-input.js.map +1 -1
- package/dist/components/Menu/menu-item.d.ts +14 -2
- package/dist/components/Menu/menu-item.d.ts.map +1 -1
- package/dist/components/Menu/menu-item.js +25 -3
- package/dist/components/Menu/menu-item.js.map +1 -1
- package/dist/components/NumberInput/number-input.d.ts +3 -3
- package/dist/components/NumberInput/number-input.d.ts.map +1 -1
- package/dist/components/NumberInput/number-input.js +6 -5
- package/dist/components/NumberInput/number-input.js.map +1 -1
- package/dist/components/OverflowIndicator/overflow-indicator.d.ts.map +1 -1
- package/dist/components/OverflowIndicator/overflow-indicator.js +2 -6
- package/dist/components/OverflowIndicator/overflow-indicator.js.map +1 -1
- package/dist/components/Pagination/pagination.js +1 -1
- package/dist/components/Pagination/pagination.js.map +1 -1
- package/dist/components/PeoplePicker/index.js +7 -1
- package/dist/components/PeoplePicker/index.js.map +1 -1
- package/dist/components/PeoplePicker/people-picker.d.ts +9 -2
- package/dist/components/PeoplePicker/people-picker.d.ts.map +1 -1
- package/dist/components/PeoplePicker/people-picker.js +23 -10
- package/dist/components/PeoplePicker/people-picker.js.map +1 -1
- package/dist/components/PeoplePicker/person-display.d.ts +6 -3
- package/dist/components/PeoplePicker/person-display.d.ts.map +1 -1
- package/dist/components/PeoplePicker/person-display.js +19 -14
- package/dist/components/PeoplePicker/person-display.js.map +1 -1
- package/dist/components/Popover/popover.d.ts.map +1 -1
- package/dist/components/Popover/popover.js +3 -1
- package/dist/components/Popover/popover.js.map +1 -1
- package/dist/components/ProfileCard/profile-card.d.ts +2 -2
- package/dist/components/ProfileCard/profile-card.js +3 -3
- package/dist/components/ProfileCard/profile-card.js.map +1 -1
- package/dist/components/RadioGroup/radio-group.d.ts.map +1 -1
- package/dist/components/RadioGroup/radio-group.js +15 -9
- package/dist/components/RadioGroup/radio-group.js.map +1 -1
- package/dist/components/ScrollArea/scroll-area.d.ts +11 -1
- package/dist/components/ScrollArea/scroll-area.d.ts.map +1 -1
- package/dist/components/ScrollArea/scroll-area.js +6 -3
- package/dist/components/ScrollArea/scroll-area.js.map +1 -1
- package/dist/components/Select/select.d.ts +46 -5
- package/dist/components/Select/select.d.ts.map +1 -1
- package/dist/components/Select/select.js +109 -28
- package/dist/components/Select/select.js.map +1 -1
- package/dist/components/SelectMenu/select-menu.d.ts +3 -1
- package/dist/components/SelectMenu/select-menu.d.ts.map +1 -1
- package/dist/components/SelectMenu/select-menu.js +8 -1
- package/dist/components/SelectMenu/select-menu.js.map +1 -1
- package/dist/components/SelectionControl/boolean-value.d.ts +11 -0
- package/dist/components/SelectionControl/boolean-value.d.ts.map +1 -0
- package/dist/components/SelectionControl/boolean-value.js +23 -0
- package/dist/components/SelectionControl/boolean-value.js.map +1 -0
- package/dist/components/SelectionControl/index.d.ts +1 -0
- package/dist/components/SelectionControl/index.d.ts.map +1 -1
- package/dist/components/SelectionControl/index.js +2 -0
- package/dist/components/SelectionControl/index.js.map +1 -1
- package/dist/components/Sheet/index.js +2 -1
- package/dist/components/Sheet/sheet.d.ts +7 -1
- package/dist/components/Sheet/sheet.d.ts.map +1 -1
- package/dist/components/Sheet/sheet.js +5 -2
- package/dist/components/Sheet/sheet.js.map +1 -1
- package/dist/components/Sidebar/sidebar.d.ts +9 -4
- package/dist/components/Sidebar/sidebar.d.ts.map +1 -1
- package/dist/components/Sidebar/sidebar.js +8 -10
- package/dist/components/Sidebar/sidebar.js.map +1 -1
- package/dist/components/Slider/slider.d.ts.map +1 -1
- package/dist/components/Slider/slider.js.map +1 -1
- package/dist/components/Switch/switch.d.ts +6 -6
- package/dist/components/Switch/switch.d.ts.map +1 -1
- package/dist/components/Switch/switch.js +3 -2
- package/dist/components/Switch/switch.js.map +1 -1
- package/dist/components/Tabs/tabs.d.ts.map +1 -1
- package/dist/components/Tabs/tabs.js +15 -1
- package/dist/components/Tabs/tabs.js.map +1 -1
- package/dist/components/Textarea/textarea.d.ts +2 -2
- package/dist/components/Textarea/textarea.d.ts.map +1 -1
- package/dist/components/Textarea/textarea.js +50 -42
- 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 +42 -38
- package/dist/components/TimePicker/time-columns.js.map +1 -1
- package/dist/components/TimePicker/time-picker.d.ts.map +1 -1
- package/dist/components/TimePicker/time-picker.js +8 -6
- package/dist/components/TimePicker/time-picker.js.map +1 -1
- package/dist/components/Tooltip/tooltip.d.ts.map +1 -1
- package/dist/components/Tooltip/tooltip.js +7 -5
- package/dist/components/Tooltip/tooltip.js.map +1 -1
- package/dist/components/TreeView/tree-view.d.ts +56 -1
- package/dist/components/TreeView/tree-view.d.ts.map +1 -1
- package/dist/components/TreeView/tree-view.js +211 -12
- package/dist/components/TreeView/tree-view.js.map +1 -1
- package/dist/index.d.ts +115 -58
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +95 -108
- package/dist/index.js.map +1 -1
- package/dist/patterns/element-anatomy/item-anatomy.d.ts +7 -6
- package/dist/patterns/element-anatomy/item-anatomy.d.ts.map +1 -1
- package/dist/patterns/element-anatomy/item-anatomy.js.map +1 -1
- package/dist/tokens/categorical-color.js +15 -0
- package/dist/tokens/categorical-color.js.map +1 -1
- package/dist/tokens/motion/motion.d.ts +7 -6
- package/dist/tokens/motion/motion.d.ts.map +1 -1
- package/dist/tokens/motion/motion.js +6 -6
- package/dist/tokens/motion/motion.js.map +1 -1
- package/dist/tokens/motion/overlay-motion.d.ts +19 -0
- package/dist/tokens/motion/overlay-motion.d.ts.map +1 -0
- package/dist/tokens/motion/overlay-motion.js +9 -0
- package/dist/tokens/motion/overlay-motion.js.map +1 -0
- package/dist/tokens/uiSize/icon-size.js +5 -0
- package/dist/tokens/uiSize/icon-size.js.map +1 -0
- package/ds-canonical/fork/governance.lock +31 -19
- package/ds-canonical/fork/hooks/_log-fire.sh +46 -0
- package/ds-canonical/fork/hooks/block_prototype_imports.py +15 -4
- package/ds-canonical/fork/hooks/check_consumer_app_invariants.sh +145 -13
- package/ds-canonical/fork/hooks/check_ds_anchor_preflight.sh +2 -1
- package/ds-canonical/fork/hooks/check_dynamic_tailwind_class.sh +47 -0
- package/ds-canonical/fork/hooks/check_escape_marker_abuse.sh +27 -0
- package/ds-canonical/fork/hooks/check_field_family_invariants.sh +61 -1
- package/ds-canonical/fork/hooks/check_layout_space_magic_numbers.sh +28 -4
- package/ds-canonical/fork/hooks/check_opacity_token_usage.sh +28 -0
- package/ds-canonical/fork/hooks/check_story_determinism.sh +2 -0
- package/ds-canonical/fork/hooks/check_story_invariants.sh +119 -0
- package/ds-canonical/fork/hooks/check_tabs_content_chrome_body_double_gap.sh +29 -0
- package/ds-canonical/fork/manifest.json +15 -0
- package/ds-canonical/fork/preamble.md +20 -18
- package/ds-canonical/fork/skills/bug-fix-rhythm/SKILL.md +3 -3
- package/ds-canonical/fork/skills/code-quality-audit/SKILL.md +3 -3
- package/ds-canonical/fork/skills/delivery-handoff/SKILL.md +1 -1
- package/ds-canonical/fork/skills/product-ui-audit/SKILL.md +3 -3
- package/ds-canonical/fork/skills/propose-options/SKILL.md +3 -3
- package/ds-canonical/fork/skills/prototype/SKILL.md +1 -1
- package/ds-canonical/fork/skills/scan-similar-bugs/SKILL.md +2 -2
- package/ds-canonical/fork/skills/visual-audit/SKILL.md +2 -2
- package/ds-canonical/hooks/README.md +1 -1
- package/ds-canonical/hooks/block_prototype_imports.py +15 -4
- package/ds-canonical/hooks/check_audit_post_report_validator.sh +65 -7
- package/ds-canonical/hooks/check_codex_brief_invariants.sh +28 -1
- package/ds-canonical/hooks/check_consumer_app_invariants.sh +145 -13
- package/ds-canonical/hooks/check_datatable_invariants.sh +6 -3
- package/ds-canonical/hooks/check_ds_anchor_preflight.sh +2 -1
- package/ds-canonical/hooks/check_dynamic_tailwind_class.sh +47 -0
- package/ds-canonical/hooks/check_escape_marker_abuse.sh +27 -0
- package/ds-canonical/hooks/check_field_family_invariants.sh +61 -1
- package/ds-canonical/hooks/check_layout_space_magic_numbers.sh +28 -4
- package/ds-canonical/hooks/check_naming_and_abstraction.sh +1 -1
- package/ds-canonical/hooks/check_opacity_token_usage.sh +28 -0
- package/ds-canonical/hooks/check_pixel_quantified_audit.sh +3 -2
- package/ds-canonical/hooks/check_post_main_ssot_propagate.sh +6 -1
- package/ds-canonical/hooks/check_ssot_header_declaration.sh +3 -1
- package/ds-canonical/hooks/check_story_determinism.sh +2 -0
- package/ds-canonical/hooks/check_substantive_edit_approval_preflight.sh +8 -7
- package/ds-canonical/hooks/check_tabs_content_chrome_body_double_gap.sh +29 -0
- package/ds-canonical/hooks/lib/_story_compile_drift.sh +1 -1
- package/ds-canonical/hooks/lib/_token_hygiene.sh +10 -1
- package/ds-canonical/hooks/stop_self_audit.sh +31 -0
- package/ds-canonical/hooks/tests/test_block_prototype_imports.sh +24 -0
- package/ds-canonical/hooks/tests/test_check_audit_post_report_validator.sh +44 -0
- package/ds-canonical/hooks/tests/test_check_codex_brief_invariants.sh +43 -2
- package/ds-canonical/hooks/tests/test_check_consumer_ds_primitive_misuse.sh +112 -0
- package/ds-canonical/hooks/tests/test_check_consumer_story_baseline.sh +9 -2
- package/ds-canonical/hooks/tests/test_check_dynamic_tailwind_class.sh +76 -0
- package/ds-canonical/hooks/tests/test_check_escape_marker_abuse.sh +24 -0
- package/ds-canonical/hooks/tests/test_check_field_family_invariants.sh +27 -2
- package/ds-canonical/hooks/tests/test_check_opacity_token_usage.sh +39 -0
- package/ds-canonical/hooks/tests/test_check_tabs_content_chrome_body_double_gap.sh +76 -0
- package/ds-canonical/references/audit-coverage-vs-24-checklist.md +1 -1
- package/ds-canonical/references/build-ui-canonicals.md +2 -1
- package/ds-canonical/references/failure-class-registry.json +145 -0
- package/ds-canonical/references/governance-audit-coverage.md +33 -0
- package/ds-canonical/references/item-anatomy-recipe.md +2 -2
- package/ds-canonical/references/scenario-definition.md +6 -9
- package/ds-canonical/references/ssot-consultation.md +2 -2
- package/ds-canonical/references/ssot-index.md +5 -4
- package/ds-canonical/references/story-baseline-registry.json +8 -2
- package/ds-canonical/references/structural-token-retention.md +6 -9
- package/ds-canonical/rules/meta-patterns.md +8 -8
- package/ds-canonical/rules/self-verify.md +4 -3
- package/ds-canonical/rules/story-rules.md +2 -2
- package/ds-canonical/rules/ui-development.md +2 -2
- package/ds-canonical/skills/bug-fix-rhythm/SKILL.md +3 -3
- package/ds-canonical/skills/code-quality-audit/SKILL.md +3 -3
- package/ds-canonical/skills/codex-collab/SKILL.md +9 -7
- package/ds-canonical/skills/codex-collab/references/brief-template.md +1 -1
- package/ds-canonical/skills/codify-principle/SKILL.md +2 -2
- package/ds-canonical/skills/component-quality-gate/SKILL.md +1 -1
- package/ds-canonical/skills/component-quality-gate/references/checklist.md +2 -2
- package/ds-canonical/skills/deep-audit-cross-codex/SKILL.md +51 -58
- package/ds-canonical/skills/deep-audit-cross-codex/references/phase-a-workflow.md +29 -0
- package/ds-canonical/skills/deep-audit-cross-codex/references/phase-b-codex-brief.md +29 -10
- package/ds-canonical/skills/deep-audit-cross-codex/references/triage-rubric.md +21 -0
- package/ds-canonical/skills/delivery-handoff/SKILL.md +1 -1
- package/ds-canonical/skills/design-system-audit/SKILL.md +26 -25
- package/ds-canonical/skills/design-system-audit/references/audit-prompts.md +13 -1
- package/ds-canonical/skills/design-system-audit/references/rule-placement.md +1 -1
- package/ds-canonical/skills/governance-health/SKILL.md +1 -1
- package/ds-canonical/skills/knowledge-prune/SKILL.md +3 -0
- package/ds-canonical/skills/knowledge-prune/references/prune-dimensions-d5-d10.md +1 -0
- package/ds-canonical/skills/new-component/SKILL.md +5 -5
- package/ds-canonical/skills/new-component/references/new-component-checklist.md +1 -1
- package/ds-canonical/skills/product-ui-audit/SKILL.md +3 -3
- package/ds-canonical/skills/propose-options/SKILL.md +3 -3
- package/ds-canonical/skills/prototype/SKILL.md +1 -1
- package/ds-canonical/skills/scan-similar-bugs/SKILL.md +2 -2
- package/ds-canonical/skills/story-auto-compile-migrate/SKILL.md +3 -3
- package/ds-canonical/skills/story-writing/SKILL.md +3 -3
- package/ds-canonical/skills/visual-audit/SKILL.md +2 -2
- package/ds-story-manifest.json +29 -20
- package/llms-full.txt +22 -1
- package/llms.txt +5 -4
- package/package.json +7 -3
- package/src/components/Accordion/accordion.principles.stories.tsx +1 -1
- package/src/components/Accordion/accordion.stories.tsx +16 -1
- package/src/components/Alert/alert.principles.stories.tsx +1 -1
- package/src/components/Alert/alert.spec.md +8 -10
- package/src/components/Alert/alert.stories.tsx +28 -28
- package/src/components/Alert/alert.tsx +1 -1
- package/src/components/AppShell/_demo-helpers.tsx +6 -3
- package/src/components/AppShell/app-shell.spec.md +4 -4
- package/src/components/AppShell/app-shell.tsx +28 -2
- package/src/components/AspectRatio/aspect-ratio.spec.md +1 -1
- package/src/components/AspectRatio/aspect-ratio.stories.tsx +7 -89
- package/src/components/Avatar/avatar.anatomy.stories.tsx +7 -3
- package/src/components/Avatar/avatar.principles.stories.tsx +19 -15
- package/src/components/Avatar/avatar.spec.md +2 -2
- package/src/components/Avatar/avatar.stories.tsx +8 -6
- package/src/components/Avatar/avatar.tsx +13 -5
- package/src/components/Badge/badge.principles.stories.tsx +1 -1
- package/src/components/Badge/badge.spec.md +2 -0
- package/src/components/Badge/badge.stories.tsx +4 -2
- package/src/components/Breadcrumb/breadcrumb.principles.stories.tsx +6 -4
- package/src/components/Breadcrumb/breadcrumb.stories.tsx +6 -5
- package/src/components/Breadcrumb/breadcrumb.tsx +3 -2
- package/src/components/BulkActionBar/bulk-action-bar.spec.md +4 -2
- package/src/components/Button/button.principles.stories.tsx +1 -1
- package/src/components/Button/button.spec.md +2 -2
- package/src/components/Button/button.tsx +18 -3
- package/src/components/Calendar/calendar.anatomy.stories.tsx +12 -10
- package/src/components/Calendar/calendar.principles.stories.tsx +14 -6
- package/src/components/Calendar/calendar.spec.md +5 -4
- package/src/components/Calendar/calendar.stories.tsx +7 -4
- package/src/components/Calendar/calendar.tsx +26 -7
- package/src/components/Carousel/carousel.anatomy.stories.tsx +5 -4
- package/src/components/Carousel/carousel.spec.md +2 -2
- package/src/components/Carousel/carousel.tsx +11 -3
- package/src/components/Chart/chart.anatomy.stories.tsx +7 -4
- package/src/components/Chart/chart.principles.stories.tsx +1 -1
- package/src/components/Chart/chart.stories.tsx +2 -8
- package/src/components/Chart/chart.tsx +3 -0
- package/src/components/Checkbox/checkbox.anatomy.stories.tsx +28 -49
- package/src/components/Checkbox/checkbox.principles.stories.tsx +1 -1
- package/src/components/Checkbox/checkbox.spec.md +13 -26
- package/src/components/Checkbox/checkbox.stories.tsx +11 -8
- package/src/components/Checkbox/checkbox.tsx +18 -13
- package/src/components/Chip/chip.anatomy.stories.tsx +44 -24
- package/src/components/Chip/chip.principles.stories.tsx +1 -1
- package/src/components/Chip/chip.spec.md +5 -4
- package/src/components/Chip/chip.tsx +5 -1
- package/src/components/CircularProgress/circular-progress.principles.stories.tsx +7 -4
- package/src/components/CircularProgress/circular-progress.spec.md +3 -3
- package/src/components/CircularProgress/circular-progress.stories.tsx +2 -2
- package/src/components/Coachmark/coachmark.anatomy.stories.tsx +2 -2
- package/src/components/Coachmark/coachmark.principles.stories.tsx +2 -1
- package/src/components/Coachmark/coachmark.spec.md +5 -5
- package/src/components/Coachmark/coachmark.stories.tsx +3 -1
- package/src/components/Coachmark/coachmark.tsx +10 -3
- package/src/components/Combobox/combobox.anatomy.stories.tsx +2 -2
- package/src/components/Combobox/combobox.tsx +53 -19
- package/src/components/Command/command.anatomy.stories.tsx +2 -2
- package/src/components/Command/command.principles.stories.tsx +1 -1
- package/src/components/Command/command.stories.tsx +42 -10
- package/src/components/DataTable/cell-registry.tsx +86 -63
- package/src/components/DataTable/column-types.ts +10 -1
- package/src/components/DataTable/data-table-column-visibility-panel.tsx +1 -1
- package/src/components/DataTable/data-table-filter-group.tsx +231 -0
- package/src/components/DataTable/data-table-filter-panel.tsx +16 -430
- package/src/components/DataTable/data-table-filter-value-picker.tsx +237 -0
- package/src/components/DataTable/data-table-interaction-layer.tsx +15 -19
- package/src/components/DataTable/data-table.anatomy.stories.tsx +15 -13
- package/src/components/DataTable/data-table.principles.stories.tsx +9 -9
- package/src/components/DataTable/data-table.spec.md +33 -13
- package/src/components/DataTable/data-table.stories.tsx +16 -17
- package/src/components/DataTable/data-table.tsx +107 -61
- package/src/components/DataTable/filter-operators.spec.md +3 -3
- package/src/components/DataTable/index.ts +3 -0
- package/src/components/DateGrid/date-grid.anatomy.stories.tsx +4 -4
- package/src/components/DateGrid/date-grid.principles.stories.tsx +1 -1
- package/src/components/DateGrid/date-grid.spec.md +1 -1
- package/src/components/DateGrid/date-grid.tsx +3 -1
- package/src/components/DatePicker/date-picker.anatomy.stories.tsx +8 -8
- package/src/components/DatePicker/date-picker.principles.stories.tsx +3 -3
- package/src/components/DatePicker/date-picker.spec.md +3 -3
- package/src/components/DatePicker/date-picker.stories.tsx +4 -5
- package/src/components/DatePicker/date-picker.tsx +99 -45
- package/src/components/DescriptionList/description-list.anatomy.stories.tsx +12 -10
- package/src/components/DescriptionList/description-list.principles.stories.tsx +2 -2
- package/src/components/DescriptionList/description-list.spec.md +3 -3
- package/src/components/DescriptionList/description-list.stories.tsx +7 -6
- package/src/components/Dialog/dialog.anatomy.stories.tsx +1 -0
- package/src/components/Dialog/dialog.spec.md +9 -0
- package/src/components/Dialog/dialog.stories.tsx +66 -5
- package/src/components/Dialog/dialog.tsx +60 -25
- package/src/components/DropdownMenu/dropdown-menu.anatomy.stories.tsx +1 -1
- package/src/components/DropdownMenu/dropdown-menu.principles.stories.tsx +8 -6
- package/src/components/DropdownMenu/dropdown-menu.spec.md +5 -5
- package/src/components/DropdownMenu/dropdown-menu.tsx +18 -9
- package/src/components/Empty/empty.anatomy.stories.tsx +1 -1
- package/src/components/Empty/empty.spec.md +2 -2
- package/src/components/Field/field-context.ts +69 -5
- package/src/components/Field/field-controls.spec.md +62 -8
- package/src/components/Field/field-edit-keys.ts +66 -0
- package/src/components/Field/field-types.ts +58 -9
- package/src/components/Field/field-wrapper.tsx +50 -42
- package/src/components/Field/field.anatomy.stories.tsx +9 -6
- package/src/components/Field/field.spec.md +1 -1
- package/src/components/Field/field.stories.tsx +1 -1
- package/src/components/Field/field.tsx +2 -1
- package/src/components/Field/form-validation.spec.md +3 -3
- package/src/components/Field/index.ts +1 -0
- package/src/components/FieldControlGroup/field-control-group.principles.stories.tsx +6 -6
- package/src/components/FieldControlGroup/field-control-group.spec.md +9 -4
- package/src/components/FieldControlGroup/field-control-group.tsx +1 -1
- package/src/components/FileItem/file-item.anatomy.stories.tsx +1 -1
- package/src/components/FileItem/file-item.spec.md +5 -8
- package/src/components/FileItem/file-item.stories.tsx +36 -25
- package/src/components/FileItem/file-item.tsx +8 -6
- package/src/components/FileUpload/file-upload.anatomy.stories.tsx +6 -6
- package/src/components/FileUpload/file-upload.principles.stories.tsx +5 -5
- package/src/components/FileUpload/file-upload.spec.md +1 -1
- package/src/components/FileUpload/file-upload.tsx +1 -1
- package/src/components/FileViewer/file-viewer.anatomy.stories.tsx +4 -3
- package/src/components/FileViewer/file-viewer.principles.stories.tsx +3 -3
- package/src/components/FileViewer/file-viewer.spec.md +30 -12
- package/src/components/FileViewer/file-viewer.stories.tsx +1 -2
- package/src/components/FileViewer/file-viewer.tsx +12 -4
- package/src/components/FileViewer/image-renderer.tsx +35 -7
- package/src/components/HoverCard/hover-card.anatomy.stories.tsx +6 -2
- package/src/components/HoverCard/hover-card.principles.stories.tsx +6 -3
- package/src/components/HoverCard/hover-card.spec.md +5 -5
- package/src/components/HoverCard/hover-card.stories.tsx +16 -16
- package/src/components/HoverCard/hover-card.tsx +5 -3
- package/src/components/InlineEdit/index.ts +5 -0
- package/src/components/InlineEdit/inline-edit.spec.md +149 -0
- package/src/components/InlineEdit/inline-edit.stories.tsx +246 -0
- package/src/components/InlineEdit/inline-edit.tsx +287 -0
- package/src/components/Input/input.anatomy.stories.tsx +8 -7
- package/src/components/Input/input.principles.stories.tsx +2 -12
- package/src/components/Input/input.spec.md +13 -18
- package/src/components/Input/input.tsx +69 -22
- package/src/components/LinkInput/link-input.anatomy.stories.tsx +1 -1
- package/src/components/LinkInput/link-input.principles.stories.tsx +2 -2
- package/src/components/LinkInput/link-input.tsx +11 -10
- package/src/components/Menu/menu-item.anatomy.stories.tsx +3 -3
- package/src/components/Menu/menu-item.principles.stories.tsx +1 -1
- package/src/components/Menu/menu-item.spec.md +22 -15
- package/src/components/Menu/menu-item.stories.tsx +39 -11
- package/src/components/Menu/menu-item.tsx +38 -4
- package/src/components/Notice/notice.principles.stories.tsx +1 -1
- package/src/components/Notice/notice.spec.md +16 -3
- package/src/components/NumberInput/number-input.anatomy.stories.tsx +3 -3
- package/src/components/NumberInput/number-input.principles.stories.tsx +1 -1
- package/src/components/NumberInput/number-input.spec.md +4 -4
- package/src/components/NumberInput/number-input.tsx +19 -12
- package/src/components/OverflowIndicator/overflow-indicator.anatomy.stories.tsx +10 -8
- package/src/components/OverflowIndicator/overflow-indicator.principles.stories.tsx +3 -3
- package/src/components/OverflowIndicator/overflow-indicator.spec.md +4 -2
- package/src/components/OverflowIndicator/overflow-indicator.stories.tsx +71 -63
- package/src/components/OverflowIndicator/overflow-indicator.tsx +9 -8
- package/src/components/Pagination/pagination.spec.md +1 -1
- package/src/components/Pagination/pagination.stories.tsx +2 -6
- package/src/components/Pagination/pagination.tsx +1 -1
- package/src/components/PeoplePicker/people-picker.anatomy.stories.tsx +4 -4
- package/src/components/PeoplePicker/people-picker.principles.stories.tsx +2 -2
- package/src/components/PeoplePicker/people-picker.spec.md +6 -6
- package/src/components/PeoplePicker/people-picker.tsx +56 -16
- package/src/components/PeoplePicker/person-display.tsx +32 -16
- package/src/components/Popover/popover.anatomy.stories.tsx +2 -2
- package/src/components/Popover/popover.tsx +3 -1
- package/src/components/ProfileCard/profile-card.anatomy.stories.tsx +12 -23
- package/src/components/ProfileCard/profile-card.spec.md +3 -3
- package/src/components/ProfileCard/profile-card.tsx +6 -6
- package/src/components/ProgressBar/progress-bar.anatomy.stories.tsx +3 -1
- package/src/components/ProgressBar/progress-bar.principles.stories.tsx +1 -1
- package/src/components/ProgressBar/progress-bar.stories.tsx +50 -42
- package/src/components/RadioGroup/radio-group.anatomy.stories.tsx +10 -28
- package/src/components/RadioGroup/radio-group.stories.tsx +29 -32
- package/src/components/RadioGroup/radio-group.tsx +41 -15
- package/src/components/Rating/rating.spec.md +11 -3
- package/src/components/ScrollArea/scroll-area.anatomy.stories.tsx +1 -1
- package/src/components/ScrollArea/scroll-area.principles.stories.tsx +1 -1
- package/src/components/ScrollArea/scroll-area.spec.md +7 -3
- package/src/components/ScrollArea/scroll-area.stories.tsx +1 -1
- package/src/components/ScrollArea/scroll-area.tsx +17 -4
- package/src/components/Select/select.anatomy.stories.tsx +3 -3
- package/src/components/Select/select.spec.md +29 -17
- package/src/components/Select/select.stories.tsx +33 -0
- package/src/components/Select/select.tsx +190 -36
- package/src/components/SelectMenu/select-menu.anatomy.stories.tsx +4 -4
- package/src/components/SelectMenu/select-menu.spec.md +15 -7
- package/src/components/SelectMenu/select-menu.tsx +16 -3
- package/src/components/SelectionControl/boolean-value.tsx +39 -0
- package/src/components/SelectionControl/index.ts +1 -0
- package/src/components/SelectionControl/selection-item.anatomy.stories.tsx +2 -2
- package/src/components/SelectionControl/selection-item.principles.stories.tsx +2 -2
- package/src/components/SelectionControl/selection-item.spec.md +3 -3
- package/src/components/SelectionControl/selection-item.stories.tsx +3 -3
- package/src/components/Separator/separator.spec.md +2 -2
- package/src/components/Separator/separator.stories.tsx +6 -27
- package/src/components/Sheet/sheet.anatomy.stories.tsx +1 -1
- package/src/components/Sheet/sheet.principles.stories.tsx +1 -1
- package/src/components/Sheet/sheet.spec.md +2 -2
- package/src/components/Sheet/sheet.tsx +9 -5
- package/src/components/Sidebar/sidebar.anatomy.stories.tsx +16 -12
- package/src/components/Sidebar/sidebar.principles.stories.tsx +10 -8
- package/src/components/Sidebar/sidebar.spec.md +13 -15
- package/src/components/Sidebar/sidebar.stories.tsx +8 -7
- package/src/components/Sidebar/sidebar.tsx +37 -25
- package/src/components/Skeleton/skeleton.stories.tsx +4 -11
- package/src/components/Slider/slider.anatomy.stories.tsx +3 -3
- package/src/components/Slider/slider.principles.stories.tsx +2 -2
- package/src/components/Slider/slider.spec.md +5 -5
- package/src/components/Slider/slider.tsx +3 -1
- package/src/components/Steps/steps.anatomy.stories.tsx +4 -4
- package/src/components/Steps/steps.principles.stories.tsx +1 -1
- package/src/components/Steps/steps.spec.md +1 -1
- package/src/components/Switch/switch.anatomy.stories.tsx +7 -7
- package/src/components/Switch/switch.principles.stories.tsx +4 -4
- package/src/components/Switch/switch.spec.md +8 -8
- package/src/components/Switch/switch.stories.tsx +9 -24
- package/src/components/Switch/switch.tsx +17 -13
- package/src/components/Tabs/tabs.anatomy.stories.tsx +10 -7
- package/src/components/Tabs/tabs.spec.md +1 -1
- package/src/components/Tabs/tabs.tsx +21 -1
- package/src/components/Textarea/textarea.anatomy.stories.tsx +1 -1
- package/src/components/Textarea/textarea.principles.stories.tsx +1 -1
- package/src/components/Textarea/textarea.spec.md +4 -4
- package/src/components/Textarea/textarea.tsx +72 -46
- package/src/components/TimePicker/time-columns.tsx +6 -2
- package/src/components/TimePicker/time-picker.principles.stories.tsx +1 -1
- package/src/components/TimePicker/time-picker.spec.md +3 -3
- package/src/components/TimePicker/time-picker.tsx +16 -10
- package/src/components/Toast/toast.stories.tsx +7 -4
- package/src/components/Tooltip/tooltip.anatomy.stories.tsx +4 -4
- package/src/components/Tooltip/tooltip.principles.stories.tsx +3 -3
- package/src/components/Tooltip/tooltip.spec.md +1 -1
- package/src/components/Tooltip/tooltip.stories.tsx +4 -39
- package/src/components/Tooltip/tooltip.tsx +8 -6
- package/src/components/TreeView/tree-view.anatomy.stories.tsx +6 -3
- package/src/components/TreeView/tree-view.spec.md +42 -11
- package/src/components/TreeView/tree-view.stories.tsx +1 -0
- package/src/components/TreeView/tree-view.tsx +322 -17
- package/src/index.ts +588 -60
- package/src/patterns/action-bar/action-bar.spec.md +9 -0
- package/src/patterns/action-bar/action-bar.stories.tsx +6 -3
- package/src/patterns/element-anatomy/inline-action.spec.md +2 -0
- package/src/patterns/element-anatomy/item-anatomy.spec.md +14 -5
- package/src/patterns/element-anatomy/item-anatomy.stories.tsx +6 -4
- package/src/patterns/element-anatomy/item-anatomy.tsx +7 -6
- package/src/patterns/header-canonical/header-canonical.spec.md +5 -4
- package/src/patterns/overlay-surface/overlay-surface.spec.md +22 -1
- package/src/styles/tokens.css +3 -0
- package/src/tokens/README.md +1 -1
- package/src/tokens/color/color.spec.md +4 -4
- package/src/tokens/color/color.stories.tsx +3 -3
- package/src/tokens/density/density.stories.tsx +16 -3
- package/src/tokens/layoutSpace/layoutSpace.spec.md +3 -1
- package/src/tokens/motion/motion.css +33 -21
- package/src/tokens/motion/motion.spec.md +32 -15
- package/src/tokens/motion/motion.stories.tsx +58 -5
- package/src/tokens/motion/motion.ts +7 -6
- package/src/tokens/motion/overlay-motion.ts +26 -0
- package/src/tokens/orphan-tokens.spec.md +4 -4
- package/src/tokens/token-system.spec.md +1 -0
- package/src/tokens/uiSize/uiSize.css +1 -1
- package/src/tokens/uiSize/uiSize.spec.md +1 -1
- package/src/tokens/uiSize/uiSize.stories.tsx +4 -2
- package/src/tokens/utility-registry.json +160 -23
|
@@ -83,6 +83,25 @@ export interface TreeDragEndEvent {
|
|
|
83
83
|
position: TreeDropPosition
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
/**
|
|
87
|
+
* 鍵盤重排(`Cmd/Ctrl+Shift+方向鍵`,2026-07-14 v1)的 SR 播報文案 — zh-TW 預設,
|
|
88
|
+
* per-key merge 覆寫(i18n 覆寫點)。詳 tree-view.spec.md「鍵盤重排」。
|
|
89
|
+
* @public
|
|
90
|
+
*/
|
|
91
|
+
// code-quality-allow: dead-export — public prop type — consumer 覆寫播報文案的參數型別
|
|
92
|
+
export interface TreeReorderAnnouncements {
|
|
93
|
+
/** 同層上/下移完成。`index` / `count` = 移動後在同層的 1-based 序數 / 同層總數 */
|
|
94
|
+
moved?: (args: { label: string; targetLabel: string; position: 'before' | 'after'; index: number; count: number }) => string
|
|
95
|
+
/** 移入 folder 完成。folder 原本收合(children 尚未 mount)時序數不可知 → `index` / `count` 為 undefined */
|
|
96
|
+
movedInside?: (args: { label: string; folderLabel: string; index?: number; count?: number }) => string
|
|
97
|
+
/** 移出到上層完成。`level` = 移動後所在層(1-based,同 aria-level 語意) */
|
|
98
|
+
movedOut?: (args: { label: string; parentLabel: string; level: number }) => string
|
|
99
|
+
/** 邊界 no-op:top(已在最上方)/ bottom(已在最下方)/ not-folder(無可移入的 folder)/ root(已在最外層) */
|
|
100
|
+
blocked?: (args: { reason: 'top' | 'bottom' | 'not-folder' | 'root'; label: string; targetLabel?: string }) => string
|
|
101
|
+
/** sr-only 操作說明(tree 容器 `aria-describedby` 指向;僅 `draggable` 時渲染) */
|
|
102
|
+
instructions?: string
|
|
103
|
+
}
|
|
104
|
+
|
|
86
105
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
87
106
|
// Constants
|
|
88
107
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -92,11 +111,42 @@ export interface TreeDragEndEvent {
|
|
|
92
111
|
|
|
93
112
|
// indentStep = chevronSize + gap-2(8px)。值 {24,24,28} 與 CSS token `--tree-indent-{sm,md,lg}`
|
|
94
113
|
// (`tokens/uiSize/uiSize.css`)**完全一致、必須同步維護**(改一處要兩處一起改;非「token 取代 literal」)。
|
|
95
|
-
// 為何兩源並存:本元件 render 用此 JS literal 算 `indentPx`(
|
|
96
|
-
// pointer 數學(
|
|
114
|
+
// 為何兩源並存:本元件 render 用此 JS literal 算 `indentPx`(TreeItem inline calc 需 number)+ drop-indicator
|
|
115
|
+
// pointer 數學(handleDragOver px 命中判斷,CSS var 在 JS 計算層拿不到);DataTable nested rows
|
|
97
116
|
// 則走 CSS token 的 Tailwind class(跨元件視覺一致)。結構對齊:子 chevron 對齊父 icon,子 icon 對齊父 label。
|
|
98
117
|
const INDENT_STEP: Record<SizeKey, number> = { sm: 24, md: 24, lg: 28 }
|
|
99
118
|
|
|
119
|
+
// ── 鍵盤重排(keyboard reorder,2026-07-14 v1)常數 ──
|
|
120
|
+
// 鍵位 = Cmd/Ctrl+Shift+方向鍵,對齊 Notion 移動 block 鍵位(https://www.notion.com/help/keyboard-shortcuts,
|
|
121
|
+
// WebFetch 驗證紀錄 .claude/logs/treeview-keyboard-dnd-design.md#L25);
|
|
122
|
+
// modifier 層與 APG tree 導覽鍵(無 modifier 的 ↑↓→←)完全正交,零衝突。
|
|
123
|
+
const REORDER_ARROW_KEYS = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight']
|
|
124
|
+
type ReorderArrowKey = 'ArrowUp' | 'ArrowDown' | 'ArrowLeft' | 'ArrowRight'
|
|
125
|
+
|
|
126
|
+
// 鍵盤重排 SR 播報預設文案(zh-TW;consumer 經 `reorderAnnouncements` prop per-key 覆寫)。
|
|
127
|
+
// 格式 =「結果 + 序數位置」— 非視覺使用者靠「第 n 項,共 m 項」建立位置模型
|
|
128
|
+
// (世界級 cite 詳 tree-view.spec.md「鍵盤重排」段,M22)。
|
|
129
|
+
// i18n-allow-block: DS default SR-only 播報文案,prop 可覆寫(同 select-menu.tsx SelectMenuLiveStatus 先例)
|
|
130
|
+
const DEFAULT_REORDER_ANNOUNCEMENTS: Required<TreeReorderAnnouncements> = {
|
|
131
|
+
moved: ({ label, targetLabel, position, index, count }) =>
|
|
132
|
+
`已將『${label}』移到『${targetLabel}』${position === 'before' ? '之前' : '之後'},第 ${index} 項,共 ${count} 項`,
|
|
133
|
+
movedInside: ({ label, folderLabel, index, count }) =>
|
|
134
|
+
index !== undefined && count !== undefined
|
|
135
|
+
? `已將『${label}』移入『${folderLabel}』,第 ${index} 項,共 ${count} 項`
|
|
136
|
+
: `已將『${label}』移入『${folderLabel}』`,
|
|
137
|
+
movedOut: ({ label, parentLabel, level }) =>
|
|
138
|
+
`已將『${label}』移出到『${parentLabel}』之後,第 ${level} 層`,
|
|
139
|
+
blocked: ({ reason, targetLabel }) => {
|
|
140
|
+
switch (reason) {
|
|
141
|
+
case 'top': return '已在最上方'
|
|
142
|
+
case 'bottom': return '已在最下方'
|
|
143
|
+
case 'not-folder': return targetLabel ? `無法移入:『${targetLabel}』不是資料夾` : '無法移入'
|
|
144
|
+
case 'root': return '已在最外層'
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
instructions: '按 Cmd(Ctrl)+Shift+方向鍵可重新排列項目',
|
|
148
|
+
}
|
|
149
|
+
|
|
100
150
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
101
151
|
// Context
|
|
102
152
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -210,6 +260,12 @@ export interface TreeViewProps extends Omit<React.HTMLAttributes<HTMLDivElement>
|
|
|
210
260
|
draggable?: boolean
|
|
211
261
|
/** Drag 結束時觸發,提供 sourceId、targetId、position。Consumer 負責 reorder。 */
|
|
212
262
|
onDragEnd?: (event: TreeDragEndEvent) => void
|
|
263
|
+
/**
|
|
264
|
+
* 鍵盤重排(`Cmd/Ctrl+Shift+方向鍵`)的 SR 播報文案覆寫(per-key merge;zh-TW 預設)。
|
|
265
|
+
* 僅在 `draggable` 時生效;含 `instructions`(sr-only 操作說明)。
|
|
266
|
+
* @public
|
|
267
|
+
*/
|
|
268
|
+
reorderAnnouncements?: TreeReorderAnnouncements
|
|
213
269
|
/** ARIA label */
|
|
214
270
|
'aria-label'?: string
|
|
215
271
|
}
|
|
@@ -224,6 +280,7 @@ const TreeView = React.forwardRef<HTMLDivElement, TreeViewProps>(
|
|
|
224
280
|
expandOnSelect = false,
|
|
225
281
|
draggable = false,
|
|
226
282
|
onDragEnd: onDragEndProp,
|
|
283
|
+
reorderAnnouncements: reorderAnnouncementsProp,
|
|
227
284
|
expandedIds: controlledExpanded,
|
|
228
285
|
onExpandedChange,
|
|
229
286
|
selectedIds: controlledSelected,
|
|
@@ -287,6 +344,18 @@ const TreeView = React.forwardRef<HTMLDivElement, TreeViewProps>(
|
|
|
287
344
|
// useId 確保多棵 TreeView 同頁 / node id 跨樹重複時 DOM id 不撞。
|
|
288
345
|
const activeDescendantPrefix = React.useId()
|
|
289
346
|
|
|
347
|
+
// ── 鍵盤重排 SR 播報(2026-07-14 v1;詳 spec「鍵盤重排」)──
|
|
348
|
+
// 單一 polite live region 覆寫式更新(textContent 替換非 append → 快速連按天然只播最新)。
|
|
349
|
+
// 不可借 dnd-kit DndContext 內建 live region — 它只播 dnd-kit 管理的 drag session,
|
|
350
|
+
// 自建鍵盤路徑觸不到。消費 select-menu.tsx SelectMenuLiveStatus role="status" 先例。
|
|
351
|
+
const [reorderAnnouncement, setReorderAnnouncement] = React.useState('')
|
|
352
|
+
const announcements = React.useMemo(
|
|
353
|
+
() => ({ ...DEFAULT_REORDER_ANNOUNCEMENTS, ...reorderAnnouncementsProp }),
|
|
354
|
+
[reorderAnnouncementsProp]
|
|
355
|
+
)
|
|
356
|
+
// sr-only 操作說明節點 id(tree 容器 aria-describedby 指向;僅 draggable 渲染)
|
|
357
|
+
const reorderInstructionsId = `${activeDescendantPrefix}tree-reorder-instructions`
|
|
358
|
+
|
|
290
359
|
// ── Keyboard vs mouse detection ──
|
|
291
360
|
// focus ring 只在鍵盤操作時顯示,滑鼠點擊用 bg-neutral-selected 表達選中,不顯示 ring
|
|
292
361
|
const isKeyboardRef = React.useRef(false)
|
|
@@ -301,6 +370,26 @@ const TreeView = React.forwardRef<HTMLDivElement, TreeViewProps>(
|
|
|
301
370
|
// 用 ref 打斷 temporal dead zone。
|
|
302
371
|
const toggleExpandRef = React.useRef<(id: string) => void>(() => {})
|
|
303
372
|
|
|
373
|
+
// ── Node registry ──(2026-07-14 上移至 drag handlers 之前:isInSubtree 消費 getNodeInfo,
|
|
374
|
+
// 而 handleDragOver / handleDragEnd / 鍵盤 reorder 都消費 isInSubtree)
|
|
375
|
+
const { registerNode, unregisterNode, getNodeInfo } = useNodeRegistry()
|
|
376
|
+
|
|
377
|
+
// 子樹判定:candidate 是否位於 rootId 的子樹內(含相等)。沿 registry parentId 鏈上溯。
|
|
378
|
+
// 雙路共用 SSOT:pointer 路徑 descendant drop guard(2026-07-14 R6 修:原 handleDragOver 只擋
|
|
379
|
+
// `over.id === active.id` 未擋 descendants → 可發出 targetId ∈ source 子樹的非法事件,
|
|
380
|
+
// consumer 按 remove→insert 實作會讓整個子樹靜默消失)+ 鍵盤 reorder commit 前防禦 assert。
|
|
381
|
+
const isInSubtree = React.useCallback(
|
|
382
|
+
(candidateId: string, rootId: string): boolean => {
|
|
383
|
+
let cur: string | null = candidateId
|
|
384
|
+
while (cur != null) {
|
|
385
|
+
if (cur === rootId) return true
|
|
386
|
+
cur = getNodeInfo(cur)?.parentId ?? null
|
|
387
|
+
}
|
|
388
|
+
return false
|
|
389
|
+
},
|
|
390
|
+
[getNodeInfo]
|
|
391
|
+
)
|
|
392
|
+
|
|
304
393
|
const sensors = useSensors(
|
|
305
394
|
useSensor(PointerSensor, { activationConstraint: { distance: 5 } })
|
|
306
395
|
)
|
|
@@ -323,7 +412,10 @@ const TreeView = React.forwardRef<HTMLDivElement, TreeViewProps>(
|
|
|
323
412
|
//
|
|
324
413
|
const handleDragOver = React.useCallback((event: DragOverEvent) => {
|
|
325
414
|
const { over, active } = event
|
|
326
|
-
|
|
415
|
+
// 2026-07-14 R6 descendant guard:over 是 active 自己「或其子樹內節點」→ 無效目標,
|
|
416
|
+
// 不設 dropTarget(拖 folder 進自己子樹會發出非法 TreeDragEndEvent;guard SSOT = isInSubtree,
|
|
417
|
+
// 鍵盤 reorder 路徑共用同一 helper)。
|
|
418
|
+
if (!over || isInSubtree(String(over.id), String(active.id))) {
|
|
327
419
|
if (autoExpandTimerRef.current) { clearTimeout(autoExpandTimerRef.current); autoExpandTimerRef.current = null }
|
|
328
420
|
setDropTarget(null)
|
|
329
421
|
return
|
|
@@ -406,7 +498,7 @@ const TreeView = React.forwardRef<HTMLDivElement, TreeViewProps>(
|
|
|
406
498
|
} else {
|
|
407
499
|
if (autoExpandTimerRef.current) { clearTimeout(autoExpandTimerRef.current); autoExpandTimerRef.current = null }
|
|
408
500
|
}
|
|
409
|
-
}, [expandedIds])
|
|
501
|
+
}, [expandedIds, isInSubtree])
|
|
410
502
|
|
|
411
503
|
const dropTargetRef = React.useRef(dropTarget)
|
|
412
504
|
dropTargetRef.current = dropTarget
|
|
@@ -415,7 +507,8 @@ const TreeView = React.forwardRef<HTMLDivElement, TreeViewProps>(
|
|
|
415
507
|
if (autoExpandTimerRef.current) { clearTimeout(autoExpandTimerRef.current); autoExpandTimerRef.current = null }
|
|
416
508
|
const { active, over } = event
|
|
417
509
|
const dt = dropTargetRef.current
|
|
418
|
-
|
|
510
|
+
// descendant guard 同 handleDragOver(dt 已由 dragOver guard 保證為 null,此為 belt-and-braces)
|
|
511
|
+
if (over && !isInSubtree(String(over.id), String(active.id)) && dt) {
|
|
419
512
|
onDragEndProp?.({
|
|
420
513
|
sourceId: String(active.id),
|
|
421
514
|
targetId: String(over.id),
|
|
@@ -424,7 +517,7 @@ const TreeView = React.forwardRef<HTMLDivElement, TreeViewProps>(
|
|
|
424
517
|
}
|
|
425
518
|
setDraggingId(null)
|
|
426
519
|
setDropTarget(null)
|
|
427
|
-
}, [onDragEndProp])
|
|
520
|
+
}, [onDragEndProp, isInSubtree])
|
|
428
521
|
|
|
429
522
|
const handleDragCancel = React.useCallback(() => {
|
|
430
523
|
if (autoExpandTimerRef.current) { clearTimeout(autoExpandTimerRef.current); autoExpandTimerRef.current = null }
|
|
@@ -432,9 +525,6 @@ const TreeView = React.forwardRef<HTMLDivElement, TreeViewProps>(
|
|
|
432
525
|
setDropTarget(null)
|
|
433
526
|
}, [])
|
|
434
527
|
|
|
435
|
-
// ── Node registry ──
|
|
436
|
-
const { registerNode, unregisterNode, getNodeInfo } = useNodeRegistry()
|
|
437
|
-
|
|
438
528
|
// ── Actions ──
|
|
439
529
|
const toggleExpand = React.useCallback(
|
|
440
530
|
(id: string) => {
|
|
@@ -466,6 +556,168 @@ const TreeView = React.forwardRef<HTMLDivElement, TreeViewProps>(
|
|
|
466
556
|
[selectionMode, setSelectedIds]
|
|
467
557
|
)
|
|
468
558
|
|
|
559
|
+
// ── Keyboard reorder(Cmd/Ctrl+Shift+方向鍵,2026-07-14 v1)──
|
|
560
|
+
// 設計 SSOT:tree-view.spec.md「鍵盤重排」。與 pointer 路徑共用 TreeDragEndEvent 契約 +
|
|
561
|
+
// onDragEnd prop + registry(consumer API 零改動);每按一下立即 commit(無 grab-mode /
|
|
562
|
+
// 無預覽,對齊 Notion Cmd/Ctrl+Shift+Arrow 移動 block:https://www.notion.com/help/keyboard-shortcuts,
|
|
563
|
+
// WebFetch 驗證紀錄 .claude/logs/treeview-keyboard-dnd-design.md#L25)。
|
|
564
|
+
// 不用 dnd-kit KeyboardSensor — 其 activator 需可接收 DOM focus
|
|
565
|
+
// (https://dndkit.com/api-documentation/sensors/keyboard),與 row tabIndex={-1} +
|
|
566
|
+
// aria-activedescendant 虛擬焦點模型結構性不相容(2026-07-05 D4 拍板不翻案)。
|
|
567
|
+
|
|
568
|
+
// 播報用 label 文字:registry label 是 ReactNode — string 直接用,否則退 DOM row textContent。
|
|
569
|
+
const getNodeLabelText = React.useCallback(
|
|
570
|
+
(id: string): string => {
|
|
571
|
+
const info = getNodeInfo(id)
|
|
572
|
+
if (typeof info?.label === 'string') return info.label
|
|
573
|
+
const rowEl = treeRef.current?.querySelector<HTMLElement>(`[data-tree-row="${id}"]`)
|
|
574
|
+
return rowEl?.textContent?.trim() || id
|
|
575
|
+
},
|
|
576
|
+
[getNodeInfo]
|
|
577
|
+
)
|
|
578
|
+
|
|
579
|
+
// code-quality-allow: long-function — 四鍵語意(同層上下 / 移入 / 移出)+ 邊界播報結構緊密,
|
|
580
|
+
// 拆 sub-fn 會跨 fn 傳 siblings / announce state 反而複雜(同 handleKeyDown 先例)
|
|
581
|
+
const handleKeyboardReorder = React.useCallback(
|
|
582
|
+
(key: ReorderArrowKey) => {
|
|
583
|
+
const tree = treeRef.current
|
|
584
|
+
if (!tree || !focusedId) return
|
|
585
|
+
const sourceId = focusedId
|
|
586
|
+
const sourceEl = tree.querySelector<HTMLElement>(`[data-tree-id="${sourceId}"]`)
|
|
587
|
+
// disabled node 不可移(鍵盤導覽本就跳過 disabled,此為防禦 guard)
|
|
588
|
+
if (!sourceEl || sourceEl.getAttribute('aria-disabled') === 'true') return
|
|
589
|
+
|
|
590
|
+
const label = getNodeLabelText(sourceId)
|
|
591
|
+
// 同層 siblings(DOM 序)。focused node 可見 ⇒ 其 parent 已展開 ⇒ 全 siblings 已 mount。
|
|
592
|
+
const parentId = sourceEl.dataset.treeParentId || null
|
|
593
|
+
const visibleItems = Array.from(
|
|
594
|
+
tree.querySelectorAll<HTMLElement>('[role="treeitem"]:not([hidden])')
|
|
595
|
+
)
|
|
596
|
+
const siblings = visibleItems.filter((el) => (el.dataset.treeParentId || null) === parentId)
|
|
597
|
+
const sourceIndex = siblings.findIndex((el) => el.dataset.treeId === sourceId)
|
|
598
|
+
if (sourceIndex < 0) return
|
|
599
|
+
const isEnabled = (el: HTMLElement) => el.getAttribute('aria-disabled') !== 'true'
|
|
600
|
+
|
|
601
|
+
// 發出與 pointer 路徑同型的 TreeDragEndEvent,並排定 re-render 後 scrollIntoView
|
|
602
|
+
// (既有 effect 只在 isFocused 變化時跑;同 id 移動不 re-fire,故此處補)。
|
|
603
|
+
const commit = (targetId: string, position: TreeDropPosition): boolean => {
|
|
604
|
+
// 結構安全 assert:四鍵語意(sibling 間 / 進 prev sibling / 出 parent)結構上
|
|
605
|
+
// 不可能移進自己子樹;防禦性仍擋(與 pointer descendant guard 共用 isInSubtree SSOT)。
|
|
606
|
+
if (targetId === sourceId || isInSubtree(targetId, sourceId)) return false
|
|
607
|
+
onDragEndProp?.({ sourceId, targetId, position })
|
|
608
|
+
requestAnimationFrame(() => {
|
|
609
|
+
treeRef.current
|
|
610
|
+
?.querySelector<HTMLElement>(`[data-tree-id="${sourceId}"]`)
|
|
611
|
+
?.scrollIntoView({ block: 'nearest' })
|
|
612
|
+
})
|
|
613
|
+
return true
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// 同層移動後的 1-based 新序數(播報用):移除 source 再插入的模擬;同層總數不變。
|
|
617
|
+
const movedIndex = (targetId: string, position: 'before' | 'after'): number => {
|
|
618
|
+
const order = siblings.map((el) => el.dataset.treeId).filter((id) => id !== sourceId)
|
|
619
|
+
const tIdx = order.indexOf(targetId)
|
|
620
|
+
return (position === 'before' ? tIdx : tIdx + 1) + 1
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
switch (key) {
|
|
624
|
+
case 'ArrowUp':
|
|
625
|
+
case 'ArrowDown': {
|
|
626
|
+
// disabled sibling 不可當 target 錨點(pointer useDroppable 同樣 disable)——
|
|
627
|
+
// 相鄰 sibling disabled 時錨到最近 enabled sibling 並翻轉 before/after 表達同一插槽
|
|
628
|
+
// (可達位置與 pointer 一致);整個方向皆無 enabled sibling → 視同邊界。
|
|
629
|
+
const dir = key === 'ArrowUp' ? -1 : 1
|
|
630
|
+
const boundary = dir < 0 ? ('top' as const) : ('bottom' as const)
|
|
631
|
+
const adjacent = siblings[sourceIndex + dir]
|
|
632
|
+
if (!adjacent) {
|
|
633
|
+
setReorderAnnouncement(announcements.blocked({ reason: boundary, label }))
|
|
634
|
+
return
|
|
635
|
+
}
|
|
636
|
+
let target: HTMLElement | undefined
|
|
637
|
+
let position: 'before' | 'after' = dir < 0 ? 'before' : 'after'
|
|
638
|
+
if (isEnabled(adjacent)) {
|
|
639
|
+
target = adjacent
|
|
640
|
+
} else {
|
|
641
|
+
for (let i = sourceIndex + dir; i >= 0 && i < siblings.length; i += dir) {
|
|
642
|
+
if (isEnabled(siblings[i])) { target = siblings[i]; break }
|
|
643
|
+
}
|
|
644
|
+
position = dir < 0 ? 'after' : 'before' // 錨到最近 enabled sibling 的另一側 = 相鄰插槽
|
|
645
|
+
}
|
|
646
|
+
if (!target) {
|
|
647
|
+
setReorderAnnouncement(announcements.blocked({ reason: boundary, label }))
|
|
648
|
+
return
|
|
649
|
+
}
|
|
650
|
+
const targetId = target.dataset.treeId!
|
|
651
|
+
if (commit(targetId, position)) {
|
|
652
|
+
setReorderAnnouncement(
|
|
653
|
+
announcements.moved({
|
|
654
|
+
label,
|
|
655
|
+
targetLabel: getNodeLabelText(targetId),
|
|
656
|
+
position,
|
|
657
|
+
index: movedIndex(targetId, position),
|
|
658
|
+
count: siblings.length,
|
|
659
|
+
})
|
|
660
|
+
)
|
|
661
|
+
}
|
|
662
|
+
return
|
|
663
|
+
}
|
|
664
|
+
case 'ArrowRight': {
|
|
665
|
+
// 移入:成為最近 enabled 上一 sibling 的子項(沿用 pointer「inside 限 folder」規則)
|
|
666
|
+
let target: HTMLElement | undefined
|
|
667
|
+
for (let i = sourceIndex - 1; i >= 0; i--) {
|
|
668
|
+
if (isEnabled(siblings[i])) { target = siblings[i]; break }
|
|
669
|
+
}
|
|
670
|
+
if (!target) {
|
|
671
|
+
setReorderAnnouncement(announcements.blocked({ reason: 'not-folder', label }))
|
|
672
|
+
return
|
|
673
|
+
}
|
|
674
|
+
const targetId = target.dataset.treeId!
|
|
675
|
+
if (target.dataset.treeHasChildren !== 'true') {
|
|
676
|
+
setReorderAnnouncement(
|
|
677
|
+
announcements.blocked({ reason: 'not-folder', label, targetLabel: getNodeLabelText(targetId) })
|
|
678
|
+
)
|
|
679
|
+
return
|
|
680
|
+
}
|
|
681
|
+
const wasExpanded = expandedIds.has(targetId)
|
|
682
|
+
if (commit(targetId, 'inside')) {
|
|
683
|
+
// 移入 collapsed folder:允許 + 自動展開(pointer 500ms hover auto-expand 的鍵盤對應物)
|
|
684
|
+
if (!wasExpanded) toggleExpand(targetId)
|
|
685
|
+
// 序數:consumer 對 inside 的慣例語意 = append 到 children 尾端;
|
|
686
|
+
// folder 原收合 → children 未 mount 無從計數 → 播報省略序數(誠實不猜)。
|
|
687
|
+
const childCount = wasExpanded
|
|
688
|
+
? visibleItems.filter((el) => el.dataset.treeParentId === targetId).length
|
|
689
|
+
: undefined
|
|
690
|
+
setReorderAnnouncement(
|
|
691
|
+
announcements.movedInside({
|
|
692
|
+
label,
|
|
693
|
+
folderLabel: getNodeLabelText(targetId),
|
|
694
|
+
index: childCount !== undefined ? childCount + 1 : undefined,
|
|
695
|
+
count: childCount !== undefined ? childCount + 1 : undefined,
|
|
696
|
+
})
|
|
697
|
+
)
|
|
698
|
+
}
|
|
699
|
+
return
|
|
700
|
+
}
|
|
701
|
+
case 'ArrowLeft': {
|
|
702
|
+
// 移出:成為 parent 的下一個 sibling(outdent)
|
|
703
|
+
if (!parentId) {
|
|
704
|
+
setReorderAnnouncement(announcements.blocked({ reason: 'root', label }))
|
|
705
|
+
return
|
|
706
|
+
}
|
|
707
|
+
const parentEl = tree.querySelector<HTMLElement>(`[data-tree-id="${parentId}"]`)
|
|
708
|
+
const level = Number(parentEl?.getAttribute('aria-level') ?? '1')
|
|
709
|
+
if (commit(parentId, 'after')) {
|
|
710
|
+
setReorderAnnouncement(
|
|
711
|
+
announcements.movedOut({ label, parentLabel: getNodeLabelText(parentId), level })
|
|
712
|
+
)
|
|
713
|
+
}
|
|
714
|
+
return
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
},
|
|
718
|
+
[focusedId, expandedIds, toggleExpand, onDragEndProp, getNodeLabelText, isInSubtree, announcements]
|
|
719
|
+
)
|
|
720
|
+
|
|
469
721
|
// ── Context value ──
|
|
470
722
|
const contextValue = React.useMemo<TreeViewContextValue>(
|
|
471
723
|
() => ({
|
|
@@ -524,14 +776,27 @@ const TreeView = React.forwardRef<HTMLDivElement, TreeViewProps>(
|
|
|
524
776
|
isKeyboardRef.current = true
|
|
525
777
|
if (!treeRef.current) return
|
|
526
778
|
|
|
779
|
+
// ── 鍵盤重排:Cmd/Ctrl+Shift+方向鍵(2026-07-14 v1,詳 spec「鍵盤重排」)──
|
|
780
|
+
// 必排在下方「無焦點時方向鍵先聚焦第一項」分支之前(該分支不分 modifier 攔 Arrow*);
|
|
781
|
+
// 非 modifier 導覽路徑(下方 switch)完全不變。
|
|
782
|
+
if ((e.metaKey || e.ctrlKey) && e.shiftKey && REORDER_ARROW_KEYS.includes(e.key)) {
|
|
783
|
+
if (!draggable) return // 未啟用拖曳 → 整組 no-op(modifier 組合不落入導覽 switch)
|
|
784
|
+
e.preventDefault() // 阻止瀏覽器原生行為(捲動 / 文字選取)
|
|
785
|
+
if (draggingId !== null) return // pointer 拖曳進行中 → 互斥,忽略鍵盤重排
|
|
786
|
+
handleKeyboardReorder(e.key as ReorderArrowKey)
|
|
787
|
+
return
|
|
788
|
+
}
|
|
789
|
+
|
|
527
790
|
// 取得所有可見的 treeitem
|
|
528
791
|
const items = Array.from(
|
|
529
|
-
|
|
792
|
+
// a11y:排除 disabled 節點 → 鍵盤導覽/選取/展開全跳過(APG:disabled 不可鍵盤操作)
|
|
793
|
+
treeRef.current.querySelectorAll<HTMLElement>('[role="treeitem"]:not([hidden]):not([aria-disabled="true"])')
|
|
530
794
|
)
|
|
531
795
|
const currentIndex = items.findIndex(
|
|
532
796
|
(el) => el.dataset.treeId === focusedId
|
|
533
797
|
)
|
|
534
|
-
|
|
798
|
+
// a11y:沒焦點時任一方向鍵先聚焦第一個(含 ArrowLeft/Right,原漏 → AT 無 activedescendant 可讀)
|
|
799
|
+
if (currentIndex < 0 && items.length > 0 && ['ArrowDown', 'ArrowUp', 'ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(e.key)) {
|
|
535
800
|
// 沒有焦點時,任何方向鍵先聚焦第一個
|
|
536
801
|
setFocusedId(items[0].dataset.treeId ?? null)
|
|
537
802
|
e.preventDefault()
|
|
@@ -603,17 +868,26 @@ const TreeView = React.forwardRef<HTMLDivElement, TreeViewProps>(
|
|
|
603
868
|
}
|
|
604
869
|
}
|
|
605
870
|
},
|
|
606
|
-
[focusedId, expandedIds, toggleExpand, select, setFocusedId]
|
|
871
|
+
[focusedId, expandedIds, toggleExpand, select, setFocusedId, draggable, draggingId, handleKeyboardReorder]
|
|
607
872
|
)
|
|
608
873
|
|
|
609
874
|
const treeEl = (
|
|
610
875
|
<div
|
|
876
|
+
// {...props} 在最前:內部 role/style(--tree-px)/onKeyDown/tabIndex 必須勝過 consumer
|
|
877
|
+
// 誤傳(原 spread 在最後 → consumer style 會整組蓋掉 --tree-px、onKeyDown 蓋掉鍵盤導覽)
|
|
878
|
+
{...props}
|
|
611
879
|
ref={treeRef}
|
|
612
880
|
role="tree"
|
|
613
881
|
aria-multiselectable={selectionMode === 'multiple' || undefined}
|
|
614
882
|
// Virtual focus:DOM focus 停在容器(單一 tab stop),aria-activedescendant 指向目前 node
|
|
615
883
|
// 的 DOM id,讓 AT 朗讀目前焦點 node(對齊 WAI-ARIA TreeView APG aria-activedescendant 模式)。
|
|
616
884
|
aria-activedescendant={focusedId ? `${activeDescendantPrefix}treeitem-${focusedId}` : undefined}
|
|
885
|
+
// 鍵盤重排操作說明(sr-only,僅 draggable;與 consumer 傳入的 aria-describedby 合併)
|
|
886
|
+
aria-describedby={
|
|
887
|
+
[props['aria-describedby'], draggable ? reorderInstructionsId : undefined]
|
|
888
|
+
.filter(Boolean)
|
|
889
|
+
.join(' ') || undefined
|
|
890
|
+
}
|
|
617
891
|
className={cn(
|
|
618
892
|
// TreeView root 不加任何 py——呼吸空間由外層容器負責:
|
|
619
893
|
// - 在 SidebarGroup 內: SidebarGroup py-2 提供
|
|
@@ -629,8 +903,18 @@ const TreeView = React.forwardRef<HTMLDivElement, TreeViewProps>(
|
|
|
629
903
|
} as React.CSSProperties}
|
|
630
904
|
onKeyDown={handleKeyDown}
|
|
631
905
|
onMouseDown={handleMouseDown}
|
|
906
|
+
// a11y APG:Tab 進入時初始化 virtual focus(優先 selected,其次第一個可用節點);
|
|
907
|
+
// 原本無 onFocus init → focusedId=null → aria-activedescendant undefined,AT 讀不到目前節點
|
|
908
|
+
onFocus={(e) => {
|
|
909
|
+
;(props as React.HTMLAttributes<HTMLDivElement>).onFocus?.(e)
|
|
910
|
+
if (e.target === e.currentTarget && !focusedId && treeRef.current) {
|
|
911
|
+
const first =
|
|
912
|
+
treeRef.current.querySelector<HTMLElement>('[role="treeitem"][aria-selected="true"]:not([hidden]):not([aria-disabled="true"])') ??
|
|
913
|
+
treeRef.current.querySelector<HTMLElement>('[role="treeitem"]:not([hidden]):not([aria-disabled="true"])')
|
|
914
|
+
if (first) setFocusedId(first.dataset.treeId ?? null)
|
|
915
|
+
}
|
|
916
|
+
}}
|
|
632
917
|
tabIndex={0}
|
|
633
|
-
{...props}
|
|
634
918
|
>
|
|
635
919
|
{children}
|
|
636
920
|
</div>
|
|
@@ -640,7 +924,7 @@ const TreeView = React.forwardRef<HTMLDivElement, TreeViewProps>(
|
|
|
640
924
|
<TreeViewContext.Provider value={contextValue}>
|
|
641
925
|
{/* RowSizeProvider:讓 TreeView 子樹內任何 <ItemIcon> / <ItemAvatar> /
|
|
642
926
|
<ItemInlineAction> 自動讀到對的 size,跟 SidebarProvider 同一條規則。
|
|
643
|
-
|
|
927
|
+
inlineActions API 也吃這個 context。 */}
|
|
644
928
|
<RowSizeProvider value={size}>
|
|
645
929
|
{/* 永遠包 DndContext(hooks 不能 conditional call)。不 draggable 時無 sensors = 不可拖 */}
|
|
646
930
|
<DndContext
|
|
@@ -651,6 +935,22 @@ const TreeView = React.forwardRef<HTMLDivElement, TreeViewProps>(
|
|
|
651
935
|
onDragCancel={handleDragCancel}
|
|
652
936
|
>
|
|
653
937
|
{treeEl}
|
|
938
|
+
{draggable && (
|
|
939
|
+
<>
|
|
940
|
+
{/* 鍵盤重排 SR 播報 — TreeView 自有 sr-only polite live region(單一節點覆寫式更新;
|
|
941
|
+
消費 select-menu.tsx SelectMenuLiveStatus 先例)。渲染在 role="tree" 之外
|
|
942
|
+
(tree 的合法 children 只有 treeitem / group);dnd-kit DndContext 內建 live region
|
|
943
|
+
只播 dnd-kit drag session,自建鍵盤路徑觸不到,故必須自有。 */}
|
|
944
|
+
<div role="status" aria-live="polite" className="sr-only">
|
|
945
|
+
{reorderAnnouncement}
|
|
946
|
+
</div>
|
|
947
|
+
{/* 鍵盤重排操作說明 — tree 容器 aria-describedby 指向(對齊 dnd-kit
|
|
948
|
+
screenReaderInstructions 慣例:https://dndkit.com/guides/accessibility) */}
|
|
949
|
+
<div id={reorderInstructionsId} className="sr-only">
|
|
950
|
+
{announcements.instructions}
|
|
951
|
+
</div>
|
|
952
|
+
</>
|
|
953
|
+
)}
|
|
654
954
|
{draggable && (
|
|
655
955
|
<DragOverlay dropAnimation={null}>
|
|
656
956
|
{draggingId ? (() => {
|
|
@@ -946,7 +1246,8 @@ const TreeItem = React.forwardRef<HTMLDivElement, TreeItemProps>(
|
|
|
946
1246
|
// - default text 維持 fg-secondary muted(跟 single 對齊 hierarchy)
|
|
947
1247
|
// - selected → 視覺信號只在 checkbox(auto-render below),text 不變、bg 不變
|
|
948
1248
|
// - 對齊 SelectMenu multi pattern(menu-item.tsx:194-195 selected → bg only;multi → checkbox only)
|
|
949
|
-
|
|
1249
|
+
// multi-selected 也維持 fg-secondary(上方註解「text 不變」;原 !isSelected 條件讓 multi-selected 掉到繼承色)
|
|
1250
|
+
!disabled && (!isSelected || selectionMode === 'multiple') && 'text-fg-secondary',
|
|
950
1251
|
!disabled && isSelected && selectionMode === 'single' && 'text-foreground',
|
|
951
1252
|
isDropTarget && dropTarget?.position === 'inside' && dropIndicatorInside,
|
|
952
1253
|
!disabled && 'hover:bg-neutral-hover hover:text-foreground',
|
|
@@ -967,7 +1268,8 @@ const TreeItem = React.forwardRef<HTMLDivElement, TreeItemProps>(
|
|
|
967
1268
|
// aria-describedby(鍵盤拖曳指示):role=button 污染 treeitem 語意、tabIndex=0
|
|
968
1269
|
// 讓每 row 變 DOM tab stop 破壞單一 tab stop 虛擬焦點模型(DOM focus 在 row 上按
|
|
969
1270
|
// Enter 會 bubble 到容器 handleKeyDown 但 handler 操作 state focusedId → 錯位);
|
|
970
|
-
// sensors 僅 PointerSensor(無 KeyboardSensor),這些 attrs
|
|
1271
|
+
// sensors 僅 PointerSensor(無 KeyboardSensor),這些 attrs 換不到鍵盤拖曳能力;
|
|
1272
|
+
// 鍵盤重排(2026-07-14 v1)走容器 handleKeyDown 的 Cmd/Ctrl+Shift+Arrow 分支,非 dnd-kit sensor。
|
|
971
1273
|
// aria-roledescription 保留在上方 treeitem 元素(有 role 才合法)。
|
|
972
1274
|
{...(draggable ? dragListeners : {})}
|
|
973
1275
|
{...props}
|
|
@@ -1074,7 +1376,10 @@ export const treeViewMeta = {
|
|
|
1074
1376
|
|
|
1075
1377
|
},
|
|
1076
1378
|
sizes: {
|
|
1077
|
-
|
|
1379
|
+
// 對齊 tree-view.spec.md frontmatter(compile-stories --check 驗 key 一致)
|
|
1380
|
+
sm: { typography: 'body', indent: 24 },
|
|
1381
|
+
md: { typography: 'body', indent: 24 }, // default
|
|
1382
|
+
lg: { typography: 'body-lg', indent: 28 },
|
|
1078
1383
|
},
|
|
1079
1384
|
// 'selected' = single-selection 持續選中(bg-neutral-selected + aria-selected);'active' 移除 —
|
|
1080
1385
|
// 全檔無 Tailwind 按壓 utility,無按壓專屬視覺態(2026-07-07 詞彙統一對抗稽核補修)。
|