@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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dialog.js","sources":["../../../src/components/Dialog/dialog.tsx"],"sourcesContent":["// @benchmark-unverified-blanket: file-level retraction per M22 (d) — claims herein not individually URL-cited; treat as unverified visual/usage rumor unless retrofit per-claim. Hook escape preserved.\nimport * as React from \"react\"\nimport * as DialogPrimitive from \"@radix-ui/react-dialog\"\nimport { X as XIcon } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/design-system/components/Button/button\"\nimport { SurfaceHeader, SurfaceFooter, type SurfaceHeaderProps } from \"@/design-system/patterns/overlay-surface/overlay-surface\"\nimport { ScrollArea } from \"@/design-system/components/ScrollArea/scroll-area\"\n\n/**\n * Dialog (Modal) — Radix Dialog + 設計系統 token\n *\n * ── Layout ──\n * px = layout-space-loose, header/footer py = layout-space-tight。\n * Body pt = layout-space-tight, pb = layout-space-bottom。\n * Density:繼承 page `data-density`(v5 校準,跟 Sheet 對齊;header 自動對齊\n * `--chrome-header-height` 48/56)。詳 dialog.spec.md「Density」節。\n *\n * ── Viewport Inset ──\n * Modal 與 viewport 四邊保持 layout-space-bottom (48px) 最小間距。\n *\n * ── 高度行為 ──\n * 預設:height 填滿 viewport(扣除 inset),body 捲動。防止動態內容跳動。\n * height=\"auto\":高度隨內容,超過 viewport 時 max-height 安全帽。\n */\n\nconst Dialog = DialogPrimitive.Root\nconst DialogTrigger = DialogPrimitive.Trigger\nconst DialogPortal = DialogPrimitive.Portal\nconst DialogClose = DialogPrimitive.Close\n\n// Modal 與 viewport 四邊的最小間距 = layout-space-bottom (48px)\nconst DIALOG_INSET_VAR = 'var(--layout-space-bottom)'\n\nconst DialogOverlay = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Overlay>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Overlay\n ref={ref}\n className={cn(\n \"fixed inset-0 z-50 bg-overlay\",\n \"data-[state=open]:animate-in data-[state=closed]:animate-out motion-reduce:animate-none\",\n \"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0\",\n className,\n )}\n {...props}\n />\n))\nDialogOverlay.displayName = DialogPrimitive.Overlay.displayName\n\ninterface DialogContentProps extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> {\n /** 最大寬度。預設 512px。傳 number 視為 px。 */\n maxWidth?: string | number\n /**\n * 高度模式。\n * - 不傳(預設):填滿 viewport(height = 100vh - inset*2),body 捲動。防止內容跳動。\n * - true:高度隨內容,超過 viewport 時捲動(max-height 安全帽)。\n */\n autoHeight?: boolean\n}\n\nconst DialogContent = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Content>,\n DialogContentProps\n>(({ className, maxWidth = '512px', autoHeight, children, style, ...props }, ref) => {\n const insetCalc = `${DIALOG_INSET_VAR} * 2`\n const viewportH = `calc(100vh - ${insetCalc})`\n const maxWidthCss = typeof maxWidth === 'number' ? `${maxWidth}px` : maxWidth\n\n const heightStyle: React.CSSProperties = autoHeight\n ? { maxHeight: viewportH }\n : { height: viewportH }\n\n // AutoFocus canonical(對齊 Material / Polaris / Atlassian)—\n // 開啟時 focus 落在 body 第一個有意義互動元素(input / button),不是 chrome close X。\n // 預設 Radix 會 focus first tabbable = close X → Button iconOnly 的 focus-triggered\n // tooltip 會立即顯示「關閉」,user-hostile。此 callback 攔截:先找 body 第一個\n // input/textarea/select/button(排除 data-dismiss)focus;找不到就 focus container(不 focus X)。\n const handleOpenAutoFocus = (e: Event) => {\n e.preventDefault()\n const content = e.currentTarget as HTMLElement\n const firstBodyTarget = content.querySelector<HTMLElement>(\n '[data-dialog-body] input:not([disabled]),[data-dialog-body] textarea:not([disabled]),[data-dialog-body] select:not([disabled]),[data-dialog-body] button:not([disabled]):not([data-dismiss])'\n )\n const firstFooterButton = content.querySelector<HTMLElement>(\n '[data-dialog-footer] button:not([disabled]):not([data-dismiss])'\n )\n ;(firstBodyTarget ?? firstFooterButton ?? content).focus({ preventScroll: true })\n }\n\n return (\n <DialogPortal>\n <DialogOverlay />\n <DialogPrimitive.Content\n ref={ref}\n // Density:**全繼承 page**(layout-space + ui-size 都不自鎖)。2026-06-16 定論(撤回本 session 一度加的\n // data-layout-space=\"lg\"):density.spec 第 10 行親自定義 layout-space 管「dialog body padding」——\n // Dialog 鎖死它 = override 自家 dial 對它點名要管的對象失效 = 自相矛盾。有同類 padding-density dial 的\n // 世界級(SAP Fiori syncStyleClass / AWS Cloudscape「all view types」)都讓 modal 跟 page dial 走、不鎖固定 tier。\n // 效果:md page → body px-loose 16 / header py-tight 12(header 48);lg page → 24 / 16(header 56),隨 page。\n // 「modal 要寬鬆」需求在 lg 階自然滿足(Polaris modal 16 = 世界級下限,證明 md 16 合格);「button 不撐高\n // header」由 ui-size 繼承 page 解決(button=page sm),與 layout-space 鎖不鎖無關 → 故不需鎖。\n onOpenAutoFocus={handleOpenAutoFocus}\n className={cn(\n \"fixed left-1/2 top-1/2 z-50 w-full -translate-x-1/2 -translate-y-1/2\",\n \"flex flex-col bg-surface-raised rounded-lg border border-border\",\n \"data-[state=open]:animate-in data-[state=closed]:animate-out motion-reduce:animate-none\",\n \"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0\",\n \"data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95\",\n \"data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%]\",\n \"data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%]\",\n className,\n )}\n style={{\n boxShadow: 'var(--elevation-200)',\n maxWidth: `min(${maxWidthCss}, calc(100vw - ${insetCalc}))`,\n ...heightStyle,\n ...style,\n }}\n {...props}\n >\n {children}\n </DialogPrimitive.Content>\n </DialogPortal>\n )\n})\nDialogContent.displayName = DialogPrimitive.Content.displayName\n\n// DialogHeader: SurfaceHeader + Close 按鈕(Dialog 特有)\n// justify-between 讓 children 與 Close 分左右;Close 用 Radix DialogPrimitive.Close 包裝。\n// 2026-05-18 audit gap fix:type 用 SurfaceHeaderProps 對齊 — DialogHeader 是 SurfaceHeader\n// 薄包裝,withTabs / lockDensity 等 props 透過 spread 已 forward,但 TS type 沒 expose\n// 導致 consumer 不能用 `<DialogHeader withTabs>` 而只能寫 `as any` 繞。Type lift 修\n// per header-canonical.spec.md W1 跨 6 consumer 同契約。\nconst DialogHeader = React.forwardRef<\n HTMLDivElement,\n SurfaceHeaderProps\n>(({ className, children, ...props }, ref) => (\n // 2026-05-18:className 不再硬加 justify-between(冗餘:row 1 是 flex items-center gap-2,\n // 第一 child flex-1 grow 自然 push close X 靠右,跟 justify-between 同視覺)。\n // 並且 column mode(tabsSlot 提供)justify-between 會把 row 1 / row 2 上下推開 = 破裂。\n // tabsSlot via `...props` spread 自動 forward(type 來自 SurfaceHeaderProps)。\n <SurfaceHeader\n ref={ref}\n className={className}\n {...props}\n >\n <div className=\"flex-1 min-w-0\">{children}</div>\n {/* Dismiss X(chrome-slot canonical,v5):Button 本身 native sm(28 md / 32 lg,touch target 亦同),\n 但 `dismiss` prop 自動標 `data-unbounded`,SurfaceHeader CSS rule 對其套負 my 讓\n layout 佔位 = 24(`data-dismiss` 僅作 openAutoFocus 排除 marker,與縮位無關),\n header = 24 + 2×tight = 48 / 56 chrome-header-height ✓。\n 詳 overlay-surface.spec.md「Chrome dismiss size canonical」*/}\n <DialogPrimitive.Close asChild>\n <Button data-dismiss iconOnly dismiss size=\"sm\" startIcon={XIcon} aria-label=\"關閉\" />\n </DialogPrimitive.Close>\n </SurfaceHeader>\n))\nDialogHeader.displayName = \"DialogHeader\"\n\n// DialogBody: flex-1 ScrollArea + chrome padding(對齊 overlay-surface SSOT + ScrollArea canonical)\n// 捲軸必用 ScrollArea(跨 OS 一致、不吃寬度)— 不自寫 overflow-y-auto。\n// padding 搬進 viewport inner div:px-loose / pt-tight / pb-bottom(Dialog 「大容器」底部多一拍呼吸)。\n// data-dialog-body:讓 DialogContent onOpenAutoFocus 找得到 body 第一個有意義互動元素(避免 focus 到 close X)\n//\n// ── List-as-region 場景(menu group / Cmd+K)──\n// 不再提供 `flush` variant(2026-05-01 移除,先前曾叫 `variant=\"list\"`)。\n// **canonical pattern** = consumer 自管 list outer wrapper + 用 `className` override 撤掉 chrome padding:\n// ```tsx\n// <DialogBody className=\"!px-0 !pt-0 !pb-0\">\n// <div className=\"py-2\"> {/* list outer wrapper 自帶 py-2(menu group canonical)*/}\n// {items.map(item => <MenuItem className=\"px-[var(--layout-space-loose)] rounded-md\" />)}\n// </div>\n// </DialogBody>\n// ```\n// **rationale**:flush 只為 list-only body 省一行 className,但 (a) 多一個 row(search / banner)\n// 就破功 → 保留 chrome padding 反而更穩,(b) 加新 variant 不解決底層脆弱(consumer 仍要管 list py\n// 且 item px-loose),反而把 1 個 surface decision 拆兩 API。世界級主流(Material/Atlassian/Mantine/\n// shadcn)無 universal LayoutBody flush variant,Polaris flush API 只用於極窄 scope。\n// 詳 `tokens/layoutSpace/layoutSpace.spec.md`「List-as-region in overlay body」節\n// `className` forward 到 **inner content div**(非外層 ScrollArea wrapper)——\n// consumer `<DialogBody className=\"flex flex-col gap-X\">` 期望作用於 children 排列;\n// 套在 ScrollArea 上會 0 效果(children 住 inner div),曾造成 modal form field 完全貼邊。\nconst DialogBody = React.forwardRef<\n HTMLDivElement,\n React.ComponentPropsWithoutRef<typeof ScrollArea>\n>(({ className, children, ...props }, ref) => (\n <ScrollArea ref={ref} data-dialog-body className=\"flex-1 min-h-0\" {...props}>\n <div\n className={cn(\n \"px-[var(--layout-space-loose)] pt-[var(--layout-space-tight)] pb-[var(--layout-space-bottom)]\",\n className,\n )}\n >\n {children}\n </div>\n </ScrollArea>\n))\nDialogBody.displayName = \"DialogBody\"\n\n// DialogFooter: SurfaceFooter wrap 加 data-dialog-footer(autoFocus fallback target)\nconst DialogFooter = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ ...props }, ref) => <SurfaceFooter ref={ref} data-dialog-footer {...props} />)\nDialogFooter.displayName = \"DialogFooter\"\n\nconst DialogTitle = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Title>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Title\n ref={ref}\n className={cn(\"text-body-lg font-medium truncate\", className)}\n {...props}\n />\n))\nDialogTitle.displayName = DialogPrimitive.Title.displayName\n\nconst DialogDescription = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Description>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Description\n ref={ref}\n // title → description 間距 canonical:DialogTitle 是 body-lg(16)+ desc body(14)→ reading-lg token\n // (label tier 決定 token 選擇;item-anatomy Family 2 reading-family token 對照表)\n className={cn(\"mt-[var(--item-gap-label-desc-reading-lg)] text-body text-fg-secondary\", className)}\n {...props}\n />\n))\nDialogDescription.displayName = DialogPrimitive.Description.displayName\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 dialogMeta = {\n component: 'Dialog',\n family: null, // non-family composite / overlay / layout\n variants: {\n\n },\n sizes: {\n\n },\n // 2026-07-04 audit 對齊:容器無 hover/active/disabled 態(spec「狀態處理的職責邊界」明文;對齊 popover meta 已修 pattern)\n states: ['default'],\n tokens: {\n bg: ['bg-surface-raised'],\n fg: ['text-fg-secondary'],\n ring: [],\n },\n} as const\n\nexport {\n Dialog,\n DialogPortal,\n DialogOverlay,\n DialogClose,\n DialogTrigger,\n DialogContent,\n DialogHeader,\n DialogBody,\n DialogFooter,\n DialogTitle,\n DialogDescription,\n}\n"],"names":["XIcon"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"dialog.js","sources":["../../../src/components/Dialog/dialog.tsx"],"sourcesContent":["// @benchmark-unverified-blanket: file-level retraction per M22 (d) — claims herein not individually URL-cited; treat as unverified visual/usage rumor unless retrofit per-claim. Hook escape preserved.\nimport * as React from \"react\"\nimport * as DialogPrimitive from \"@radix-ui/react-dialog\"\nimport { X as XIcon } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/design-system/components/Button/button\"\nimport { ButtonDivider } from \"@/design-system/components/Button/button-group\"\nimport { SurfaceHeader, SurfaceFooter, type SurfaceHeaderProps } from \"@/design-system/patterns/overlay-surface/overlay-surface\"\nimport { ScrollArea } from \"@/design-system/components/ScrollArea/scroll-area\"\nimport { surfaceMotion } from \"@/design-system/tokens/motion/overlay-motion\"\n\n/**\n * Dialog (Modal) — Radix Dialog + 設計系統 token\n *\n * ── Layout ──\n * px = layout-space-loose, header/footer py = layout-space-tight。\n * Body pt = layout-space-tight, pb = layout-space-bottom。\n * Density:繼承 page `data-density`(v5 校準,跟 Sheet 對齊;header 自動對齊\n * `--chrome-header-height` 48/56)。詳 dialog.spec.md「Density」節。\n *\n * ── Viewport Inset ──\n * Modal 與 viewport 四邊保持 layout-space-bottom (48px) 最小間距。\n *\n * ── 高度行為 ──\n * 預設:height 填滿 viewport(扣除 inset),body 捲動。防止動態內容跳動。\n * autoHeight(boolean):高度隨內容,超過 viewport 時 max-height 安全帽。\n */\n\nconst Dialog = DialogPrimitive.Root\nconst DialogTrigger = DialogPrimitive.Trigger\nconst DialogPortal = DialogPrimitive.Portal\nconst DialogClose = DialogPrimitive.Close\n\n// Modal 與 viewport 四邊的最小間距 = layout-space-bottom (48px)\nconst DIALOG_INSET_VAR = 'var(--layout-space-bottom)'\n\nconst DialogOverlay = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Overlay>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Overlay\n ref={ref}\n className={cn(\n \"fixed inset-0 z-50 bg-overlay\",\n \"data-[state=open]:animate-in data-[state=closed]:animate-out motion-reduce:animate-none\",\n \"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0\",\n className,\n )}\n {...props}\n />\n))\nDialogOverlay.displayName = DialogPrimitive.Overlay.displayName\n\ninterface DialogContentProps extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> {\n /** 最大寬度。預設 512px。傳 number 視為 px。 */\n maxWidth?: string | number\n /**\n * 高度模式。\n * - 不傳(預設):填滿 viewport(height = 100vh - inset*2),body 捲動。防止內容跳動。\n * - true:高度隨內容,超過 viewport 時捲動(max-height 安全帽)。\n */\n autoHeight?: boolean\n}\n\nconst DialogContent = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Content>,\n DialogContentProps\n>(({ className, maxWidth = '512px', autoHeight, children, style, ...props }, ref) => {\n const insetCalc = `${DIALOG_INSET_VAR} * 2`\n const viewportH = `calc(100vh - ${insetCalc})`\n const maxWidthCss = typeof maxWidth === 'number' ? `${maxWidth}px` : maxWidth\n\n const heightStyle: React.CSSProperties = autoHeight\n ? { maxHeight: viewportH }\n : { height: viewportH }\n\n // AutoFocus canonical(對齊 Material / Polaris / Atlassian)—\n // 開啟時 focus 落在 body 第一個有意義互動元素(input / button),不是 chrome close X。\n // 預設 Radix 會 focus first tabbable = close X → Button iconOnly 的 focus-triggered\n // tooltip 會立即顯示「關閉」,user-hostile。此 callback 攔截:先找 body 第一個\n // input/textarea/select/button(排除 data-dismiss)focus;找不到就 focus container(不 focus X)。\n const handleOpenAutoFocus = (e: Event) => {\n e.preventDefault()\n const content = e.currentTarget as HTMLElement\n const firstBodyTarget = content.querySelector<HTMLElement>(\n '[data-dialog-body] input:not([disabled]),[data-dialog-body] textarea:not([disabled]),[data-dialog-body] select:not([disabled]),[data-dialog-body] button:not([disabled]):not([data-dismiss])'\n )\n const firstFooterButton = content.querySelector<HTMLElement>(\n '[data-dialog-footer] button:not([disabled]):not([data-dismiss])'\n )\n ;(firstBodyTarget ?? firstFooterButton ?? content).focus({ preventScroll: true })\n }\n\n return (\n <DialogPortal>\n <DialogOverlay />\n <DialogPrimitive.Content\n ref={ref}\n // Density:**全繼承 page**(layout-space + ui-size 都不自鎖)。2026-06-16 定論(撤回本 session 一度加的\n // data-layout-space=\"lg\"):density.spec 第 10 行親自定義 layout-space 管「dialog body padding」——\n // Dialog 鎖死它 = override 自家 dial 對它點名要管的對象失效 = 自相矛盾。有同類 padding-density dial 的\n // 世界級(SAP Fiori syncStyleClass / AWS Cloudscape「all view types」)都讓 modal 跟 page dial 走、不鎖固定 tier。\n // 效果:md page → body px-loose 16 / header py-tight 12(header 48);lg page → 24 / 16(header 56),隨 page。\n // 「modal 要寬鬆」需求在 lg 階自然滿足(Polaris modal 16 = 世界級下限,證明 md 16 合格);「button 不撐高\n // header」由 ui-size 繼承 page 解決(button=page sm),與 layout-space 鎖不鎖無關 → 故不需鎖。\n onOpenAutoFocus={handleOpenAutoFocus}\n className={cn(\n \"fixed left-1/2 top-1/2 z-50 w-full -translate-x-1/2 -translate-y-1/2\",\n \"flex flex-col bg-surface-raised rounded-lg border border-border\",\n surfaceMotion,\n \"data-[state=open]:animate-in data-[state=closed]:animate-out\",\n \"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0\",\n \"data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95\",\n \"data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%]\",\n \"data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%]\",\n className,\n )}\n style={{\n boxShadow: 'var(--elevation-200)',\n maxWidth: `min(${maxWidthCss}, calc(100vw - ${insetCalc}))`,\n ...heightStyle,\n ...style,\n }}\n {...props}\n >\n {children}\n </DialogPrimitive.Content>\n </DialogPortal>\n )\n})\nDialogContent.displayName = DialogPrimitive.Content.displayName\n\n// DialogHeader: SurfaceHeader + Close 按鈕(Dialog 特有)\n// Close 靠右由「第一 child flex-1 grow」達成(justify-between 已移除,見 return 註解);Close 用 Radix DialogPrimitive.Close 包裝。\n// 2026-05-18 audit gap fix:type 用 SurfaceHeaderProps 對齊 — DialogHeader 是 SurfaceHeader\n// 薄包裝,withTabs / tabsSlot props 透過 spread 已 forward,但 TS type 沒 expose\n// 導致 consumer 不能用 `<DialogHeader withTabs>` 而只能寫 `as any` 繞。Type lift 修\n// per header-canonical.spec.md W1 跨 6 consumer 同契約。\nexport interface DialogHeaderProps extends SurfaceHeaderProps {\n /**\n * Header 級操作 cluster(2026-07-08 WM 戰役 codify — 關閉鈕分隔線 SSOT 化)。\n *\n * 提供時 render 在 Close X 左側,DS 自動在 actions 與 Close 之間放 `<ButtonDivider>`\n * (action-bar.spec.md:281「最右側為關閉/解除按鈕 → 分隔線必須(誤觸保護)」;\n * 幾何 = ButtonDivider mx-1 於 gap-2 cluster → 12px 對稱,同 spec「分隔線幾何」段)。\n *\n * 用例:任務詳情 prev/next 導覽、header 級溢出選單(⋮)。\n * Consumer 傳 `<Button variant=\"text\" iconOnly …>`(text variant 自動 data-unbounded,\n * 不撐高 chrome header)。**禁**在 children 內自刻 cluster + divider(分隔線落點歸 DS)。\n */\n actions?: React.ReactNode\n}\n\nconst DialogHeader = React.forwardRef<\n HTMLDivElement,\n DialogHeaderProps\n>(({ className, children, actions, ...props }, ref) => {\n // Dismiss X(chrome-slot canonical,v5):Button 本身 native sm(28 md / 32 lg,touch target 亦同),\n // 但 `dismiss` prop 自動標 `data-unbounded`,SurfaceHeader CSS rule 對其套負 my 讓\n // layout 佔位 = 24(`data-dismiss` 僅作 openAutoFocus 排除 marker,與縮位無關),\n // header = 24 + 2×tight = 48 / 56 chrome-header-height ✓。\n // 詳 overlay-surface.spec.md「Chrome dismiss size canonical」\n const closeButton = (\n <DialogPrimitive.Close asChild>\n <Button data-dismiss iconOnly dismiss size=\"sm\" startIcon={XIcon} aria-label=\"關閉\" />\n </DialogPrimitive.Close>\n )\n return (\n // 2026-05-18:className 不再硬加 justify-between(冗餘:row 1 是 flex items-center gap-2,\n // 第一 child flex-1 grow 自然 push close X 靠右,跟 justify-between 同視覺)。\n // 並且 column mode(tabsSlot 提供)justify-between 會把 row 1 / row 2 上下推開 = 破裂。\n // tabsSlot via `...props` spread 自動 forward(type 來自 SurfaceHeaderProps)。\n <SurfaceHeader\n ref={ref}\n className={className}\n {...props}\n >\n <div className=\"flex-1 min-w-0\">{children}</div>\n {actions != null ? (\n // actions 提供 → 右側一體 cluster:{actions} + ButtonDivider + Close。\n // 分隔線 = 關閉保護 canonical(action-bar.spec.md:281),由 DS 放,consumer 零自刻。\n // gap-2 + ButtonDivider mx-1 = 兩側 12px 對稱(action-bar.spec.md「分隔線幾何」)。\n <div className=\"flex items-center gap-2 shrink-0\">\n {actions}\n <ButtonDivider />\n {closeButton}\n </div>\n ) : (\n // 無 actions → 原結構(Close 直接是 SurfaceHeader child),行為零回歸\n closeButton\n )}\n </SurfaceHeader>\n )\n})\nDialogHeader.displayName = \"DialogHeader\"\n\n// DialogBody: flex-1 ScrollArea + chrome padding(對齊 overlay-surface SSOT + ScrollArea canonical)\n// 捲軸必用 ScrollArea(跨 OS 一致、不吃寬度)— 不自寫 overflow-y-auto。\n// padding 搬進 viewport inner div:px-loose / pt-tight / pb-bottom(Dialog 「大容器」底部多一拍呼吸)。\n// data-dialog-body:讓 DialogContent onOpenAutoFocus 找得到 body 第一個有意義互動元素(避免 focus 到 close X)\n//\n// ── List-as-region 場景(menu group / Cmd+K)──\n// 不再提供 `flush` variant(2026-05-01 移除,先前曾叫 `variant=\"list\"`)。\n// **canonical pattern** = consumer 自管 list outer wrapper + 用 `className` override 撤掉 chrome padding:\n// ```tsx\n// <DialogBody className=\"!px-0 !pt-0 !pb-0\"> ← @tabs-content-gap-ok: JSDoc 文件範例(list-as-region canonical,非 tabs !pt-0 hack)\n// <div className=\"py-2\"> {/* list outer wrapper 自帶 py-2(menu group canonical)*/}\n// {items.map(item => <MenuItem className=\"px-[var(--layout-space-loose)] rounded-md\" />)}\n// </div>\n// </DialogBody>\n// ```\n// **rationale**:flush 只為 list-only body 省一行 className,但 (a) 多一個 row(search / banner)\n// 就破功 → 保留 chrome padding 反而更穩,(b) 加新 variant 不解決底層脆弱(consumer 仍要管 list py\n// 且 item px-loose),反而把 1 個 surface decision 拆兩 API。世界級主流(Material/Atlassian/Mantine/\n// shadcn)無 universal LayoutBody flush variant,Polaris flush API 只用於極窄 scope。\n// 詳 `tokens/layoutSpace/layoutSpace.spec.md`「List-as-region in overlay body」節\n// `className` forward 到 **inner content div**(非外層 ScrollArea wrapper)——\n// consumer `<DialogBody className=\"flex flex-col gap-X\">` 期望作用於 children 排列;\n// 套在 ScrollArea 上會 0 效果(children 住 inner div),曾造成 modal form field 完全貼邊。\nconst DialogBody = React.forwardRef<\n HTMLDivElement,\n React.ComponentPropsWithoutRef<typeof ScrollArea>\n>(({ className, children, ...props }, ref) => (\n <ScrollArea fillX ref={ref} data-dialog-body className=\"flex-1 min-h-0\" {...props}>\n <div\n className={cn(\n \"px-[var(--layout-space-loose)] pt-[var(--layout-space-tight)] pb-[var(--layout-space-bottom)]\",\n className,\n )}\n >\n {children}\n </div>\n </ScrollArea>\n))\nDialogBody.displayName = \"DialogBody\"\n\n// DialogFooter: SurfaceFooter wrap 加 data-dialog-footer(autoFocus fallback target)\nconst DialogFooter = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ ...props }, ref) => <SurfaceFooter ref={ref} data-dialog-footer {...props} />)\nDialogFooter.displayName = \"DialogFooter\"\n\nconst DialogTitle = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Title>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Title\n ref={ref}\n className={cn(\"text-body-lg font-medium truncate\", className)}\n {...props}\n />\n))\nDialogTitle.displayName = DialogPrimitive.Title.displayName\n\nconst DialogDescription = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Description>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Description\n ref={ref}\n // title → description 間距 canonical:DialogTitle 是 body-lg(16)+ desc body(14)→ reading-lg token\n // (label tier 決定 token 選擇;item-anatomy Family 2 reading-family token 對照表)\n className={cn(\"mt-[var(--item-gap-label-desc-reading-lg)] text-body text-fg-secondary\", className)}\n {...props}\n />\n))\nDialogDescription.displayName = DialogPrimitive.Description.displayName\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 dialogMeta = {\n component: 'Dialog',\n family: null, // non-family composite / overlay / layout\n variants: {\n\n },\n sizes: {\n\n },\n // 2026-07-04 audit 對齊:容器無 hover/active/disabled 態(spec「狀態處理的職責邊界」明文;對齊 popover meta 已修 pattern)\n states: ['default'],\n tokens: {\n bg: ['bg-surface-raised'],\n fg: ['text-fg-secondary'],\n ring: [],\n },\n} as const\n\nexport {\n Dialog,\n DialogPortal,\n DialogOverlay,\n DialogClose,\n DialogTrigger,\n DialogContent,\n DialogHeader,\n DialogBody,\n DialogFooter,\n DialogTitle,\n DialogDescription,\n}\n"],"names":["XIcon"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dropdown-menu.d.ts","sourceRoot":"","sources":["../../../src/components/DropdownMenu/dropdown-menu.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,KAAK,qBAAqB,MAAM,+BAA+B,CAAA;AACtE,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,cAAc,CAAA;AAG5D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0CAA0C,CAAA;AAI1E,OAAO,EAIL,KAAK,OAAO,EACb,MAAM,uDAAuD,CAAA;
|
|
1
|
+
{"version":3,"file":"dropdown-menu.d.ts","sourceRoot":"","sources":["../../../src/components/DropdownMenu/dropdown-menu.tsx"],"names":[],"mappings":"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"}
|
|
@@ -7,9 +7,11 @@ import { MenuItem } from "../Menu/menu-item.js";
|
|
|
7
7
|
import { ScrollArea } from "../ScrollArea/scroll-area.js";
|
|
8
8
|
import { OVERLAY_COLLISION_PADDING, OVERLAY_SIDE_OFFSET } from "../../tokens/elevation/overlay-geometry.js";
|
|
9
9
|
import { useRowSize, RowSizeProvider, ICON_SIZE as ICON_SIZE$1 } from "../../patterns/element-anatomy/item-anatomy.js";
|
|
10
|
+
import { overlayMotion } from "../../tokens/motion/overlay-motion.js";
|
|
10
11
|
const floatingLayerClass = [
|
|
11
12
|
"z-50 overflow-hidden rounded-lg border border-border bg-surface-raised",
|
|
12
|
-
|
|
13
|
+
overlayMotion,
|
|
14
|
+
"data-[state=open]:animate-in data-[state=closed]:animate-out",
|
|
13
15
|
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
|
14
16
|
"data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
|
|
15
17
|
"data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dropdown-menu.js","sources":["../../../src/components/DropdownMenu/dropdown-menu.tsx"],"sourcesContent":["import * as React from \"react\"\nimport * as DropdownMenuPrimitive from \"@radix-ui/react-dropdown-menu\"\nimport { ChevronRight, type LucideIcon } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport type { AvatarData } from \"@/design-system/components/Avatar/avatar\"\nimport { MenuItem } from \"@/design-system/components/Menu/menu-item\"\nimport { ScrollArea } from \"@/design-system/components/ScrollArea/scroll-area\"\nimport { OVERLAY_SIDE_OFFSET, OVERLAY_COLLISION_PADDING } from \"@/design-system/tokens/elevation/overlay-geometry\"\nimport {\n RowSizeProvider,\n useRowSize,\n ICON_SIZE as ROW_ICON_SIZE,\n type RowSize,\n} from \"@/design-system/patterns/element-anatomy/item-anatomy\"\n\n/**\n * DropdownMenu — Radix DropdownMenu + MenuItem visual layer\n *\n * 架構分工:\n * - Radix primitives:behavior(keyboard nav, focus management, aria roles)\n * - MenuItem:visual(layout, padding, icon alignment, typography)\n *\n * Radix primitive 是外層容器,控制 `data-[highlighted]:bg-neutral-hover`。\n * MenuItem 內層只負責佈局,不加互動樣式。\n *\n * ── Hover / highlight canonical(2026-04-22 修正)──\n * 用 Radix 官方的 `data-[highlighted]` attribute,**不用 `:focus-visible` / `:hover` /\n * `:focus`**:\n * - Radix 在 **mouse hover、keyboard arrow nav、focus move in** 時自動 set `data-highlighted`\n * - mouse leave / focus move out / menu close 時自動清掉\n * - 不會在 click 後留殘影(Radix 內部已處理)\n * - 跨瀏覽器一致(不依賴 `:focus-visible` 的 heuristic)\n *\n * 曾經用過 `focus-visible:bg-neutral-hover` 的理由:避免 click 後殘影。但實測:mouse hover\n * 觸發 Radix 程式化 `.focus()`,Chromium / Safari / Firefox 對 programmatic focus 是否 fire\n * `:focus-visible` 行為不一致,導致 mouse hover 有時無 bg。改用 `data-[highlighted]:` 後行為\n * 一致 —— 世界級 canonical(shadcn / Radix docs / Ariakit 皆此)。\n */\n\n// ── Floating layer 共用樣式 ──\nconst floatingLayerClass = [\n 'z-50 overflow-hidden rounded-lg border border-border bg-surface-raised',\n 'data-[state=open]:animate-in data-[state=closed]:animate-out motion-reduce:animate-none',\n 'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',\n 'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',\n 'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2',\n 'data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',\n 'origin-[var(--radix-dropdown-menu-content-transform-origin)]',\n].join(' ')\n\n// ── Size:統一用 RowSizeContext(item-layout module),消除本地 SizeContext 漂移 ──\ntype SizeKey = RowSize\n// Re-export for backward compat(內部命名)\nconst ICON_SIZE = ROW_ICON_SIZE\n\n// ── Shared item classes on Radix primitive ──\n// Highlight(hover + keyboard nav): 用 Radix `data-[highlighted]` canonical(見 docblock)\nconst radixItemClass = [\n 'relative cursor-pointer select-none outline-none',\n 'transition-colors duration-150',\n 'data-[highlighted]:bg-neutral-hover',\n 'data-[disabled]:pointer-events-none data-[disabled]:text-fg-disabled data-[disabled]:cursor-default',\n].join(' ')\n\n// ── Root ──\nconst DropdownMenu = DropdownMenuPrimitive.Root\nconst DropdownMenuTrigger = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.Trigger>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Trigger>\n>(({ className, ...props }, ref) => (\n <DropdownMenuPrimitive.Trigger\n ref={ref}\n // 2026-07-05:非 asChild 裸用需可見 focus 指示(WCAG 2.4.7)——原 `outline-none` 讓裸 Trigger\n // 完全無 focus ring。消費 Button focus canonical(button.tsx buttonVariants base)。\n // asChild+Button 場景同名 class 重複,無影響。\n className={cn(\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1',\n className,\n )}\n {...props}\n />\n))\nDropdownMenuTrigger.displayName = DropdownMenuPrimitive.Trigger.displayName\n// DropdownMenuGroup — 對齊 MenuGroup 的 group separation 設計語言\n//\n// 設計語言(跨 Menu-like 元件統一,SSOT 見 item-anatomy.spec.md\n// 「Group auto-separation」):\n// 每個 group 上下各 8px padding,相鄰 group 之間用 border-divider 分隔\n// 兩個 group 之間視覺 gap = 8px(上一個 bottom)+ 8px(下一個 top)= 16px + border\n//\n// MenuGroup(menu-item.tsx)實作:`py-2 [&+&]:border-t [&+&]:border-divider`\n// (在 Command.List 下提供 Content 邊界 8px + group 間 16px gap)\n//\n// DropdownMenuGroup(本元件)實作:`[&+&]:mt-2 [&+&]:pt-2 [&+&]:border-t\n// [&+&]:border-divider`(因為 DropdownMenuContent 已有 py-2 提供 Content 邊界\n// 的 8px,只需在第二個起的 group 加 8+8 = 16px gap + border)\n//\n// **視覺結果等同**:兩種實作的 visual output 一致,只是「padding 住在哪層」\n// 不同。不強制統一 CSS 表達式——DropdownMenuContent 的 py-2 是既有 Radix\n// 期望的行為,移除會影響 trigger 鍵盤導覽的 focus offset。\nconst DropdownMenuGroup = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.Group>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Group>\n>(({ className, ...props }, ref) => (\n <DropdownMenuPrimitive.Group\n ref={ref}\n className={cn('[&+&]:border-t [&+&]:border-divider [&+&]:mt-2 [&+&]:pt-2', className)}\n {...props}\n />\n))\nDropdownMenuGroup.displayName = 'DropdownMenuGroup'\n\nconst DropdownMenuPortal = DropdownMenuPrimitive.Portal\nconst DropdownMenuSub = DropdownMenuPrimitive.Sub\nconst DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup\n\n// ── Content ──\ninterface DropdownMenuContentProps\n extends React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content> {\n size?: SizeKey\n /** 最小寬度(px),預設 `max(180px, 觸發元件寬度)`——窄 trigger 時吃 180px 地板 */\n minWidth?: number\n /** 最大高度(px),超過時捲動 */\n maxHeight?: number\n}\n\nconst DropdownMenuContent = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.Content>,\n DropdownMenuContentProps\n>(({ className, size = 'md', sideOffset = OVERLAY_SIDE_OFFSET, collisionPadding = OVERLAY_COLLISION_PADDING, align = 'start', minWidth, maxHeight, children, ...props }, ref) => (\n <DropdownMenuPrimitive.Portal>\n <DropdownMenuPrimitive.Content\n ref={ref}\n sideOffset={sideOffset}\n collisionPadding={collisionPadding}\n align={align}\n // Density:繼承 page density(2026-06-15 canonical)。menu item 高度 = field-height-{size},而\n // field-height 隨 density 變(md 28/32/36 → lg 32/36/40)→ 鎖 data-density=\"md\" 會把選單釘在 md-scale,\n // lg page 上對不上 lg 觸發點。原 data-density 是 409b91da a11y 批次順手加(對齊 Popover),非設計\n // 決策 → 移除,item 隨 page density 與觸發點一致(tier 仍由 size prop 決定)。\n // Focus return on close:不 override `onCloseAutoFocus` — 用 Radix 內建 default\n // (close 時 focus 還 trigger;outside-interaction 例外由 Radix `hasInteractedOutsideRef` 自管)。\n // W3C APG menubar「Escape: …return focus to the element…from which the menu was opened」\n // (w3.org/WAI/ARIA/apg/patterns/menubar/);shadcn dropdown-menu 同樣不 override。\n // 2026-06-11 移除 2026-04-08 的 `(e) => e.preventDefault()` hardcode:它跑在 Radix\n // composeEventHandlers 內建 handler 之前,defaultPrevented → trigger focus 被 skip →\n // Esc 關閉後 focus 掉到 body(APG violation)。原動機「mouse close 後 trigger 殘留\n // focus ring」不構成 override 理由:實測(2026-06-12 playwright)pointer item-click\n // close 後 programmatic refocus 在 Chromium 仍可 match `:focus-visible`(UA heuristic),\n // 但與 shadcn 官方 live demo 同流程 DOM 比對 IDENTICAL — Radix 生態一致接受此\n // tradeoff:APG keyboard focus-return 優先於 cosmetic ring。\n className={cn(floatingLayerClass, !maxHeight && 'py-2', className)}\n style={{\n boxShadow: 'var(--elevation-200)',\n minWidth: minWidth ?? 'max(180px, var(--radix-dropdown-menu-trigger-width))',\n maxHeight,\n }}\n {...props}\n >\n <RowSizeProvider value={size}>\n {maxHeight ? (\n // 長選單用 ScrollArea 跨 OS 一致捲動(不吃寬度,macOS/Windows 視覺一致)\n // py-2 移到內層,ScrollArea Viewport 才能 scroll 整個 padded 區\n <ScrollArea className=\"max-h-[inherit]\">\n <div className=\"py-2\">{children}</div>\n </ScrollArea>\n ) : (\n children\n )}\n </RowSizeProvider>\n </DropdownMenuPrimitive.Content>\n </DropdownMenuPrimitive.Portal>\n))\nDropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName\n\n// ── SubContent ──\nconst DropdownMenuSubContent = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>\n>(({ className, children, ...props }, ref) => {\n const size = useRowSize()\n return (\n <DropdownMenuPrimitive.SubContent\n ref={ref}\n sideOffset={OVERLAY_SIDE_OFFSET}\n className={cn(floatingLayerClass, 'py-2', className)}\n style={{ boxShadow: 'var(--elevation-200)', minWidth: 180 }}\n {...props}\n >\n <RowSizeProvider value={size}>\n {children}\n </RowSizeProvider>\n </DropdownMenuPrimitive.SubContent>\n )\n})\nDropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName\n\n// ── Helper: build endContent from badge + endIcon + shortcut ──\nfunction buildEndContent(\n size: SizeKey,\n badge?: React.ReactNode,\n endIcon?: LucideIcon,\n shortcut?: string,\n): React.ReactNode | undefined {\n const EndIcon = endIcon\n if (!badge && !EndIcon && !shortcut) return undefined\n const iconPx = ICON_SIZE[size]\n return (\n <>\n {badge}\n {EndIcon && <EndIcon size={iconPx} className=\"text-fg-muted\" aria-hidden />}\n {shortcut && <span className=\"text-caption text-fg-muted tracking-shortcut\">{shortcut}</span>}\n </>\n )\n}\n\n// ── Item ──\ninterface DropdownMenuItemProps\n extends Omit<React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item>, 'children'> {\n children: React.ReactNode\n /** 左側 icon */\n startIcon?: LucideIcon\n /** 左側頭像資料(AvatarData),與 startIcon 互斥 */\n avatar?: AvatarData\n /** 次要說明文字 */\n description?: React.ReactNode\n /** 後綴 Tag(ReactNode) */\n tag?: React.ReactNode\n /** 後綴 Badge(ReactNode) */\n badge?: React.ReactNode\n /** 後綴指示型 icon(LucideIcon),fg-muted */\n endIcon?: LucideIcon\n /** 鍵盤快捷鍵提示(canonical)——渲染進 MenuItem `endContent` 正規後綴 slot。\n * 替代方案是 `<DropdownMenuShortcut>` child(composition escape-hatch);**同 item 勿混用**。 */\n shortcut?: string\n /** 單選選中(bg-neutral-selected,持續選中狀態)*/\n selected?: boolean\n}\n\nconst DropdownMenuItem = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.Item>,\n DropdownMenuItemProps\n>(({ className, children, startIcon, avatar, description, tag, badge, endIcon, shortcut, selected, disabled, ...props }, ref) => {\n const size = useRowSize()\n const endContent = buildEndContent(size, badge, endIcon, shortcut)\n\n return (\n <DropdownMenuPrimitive.Item\n ref={ref}\n disabled={disabled}\n // 2026-07-05:typeahead 只比對 label——children 為字串時 auto-forward textValue,避免 Radix\n // fallback 拿整段 textContent(label+description+badge+shortcut 串接)比對。consumer 顯式\n // textValue 在 {...props} 較後 spread,仍優先。\n textValue={typeof children === 'string' ? children : undefined}\n className={cn(\n radixItemClass,\n // 2026-07-04 Q2 拍板:selected bg 勝 hover/highlighted(bg 是唯一選中指示器,被 hover 洗掉\n // = 選中資訊消失)。對齊 MenuItem(menu-item.tsx selected 時關 hover bg)+ Ant Menu\n // `:not(-item-selected)` + VS Code list `:hover:not(.selected)`;Radix highlighted 同時是\n // keyboard cursor,selected 項停留時維持 selected bg(DS bg 通道單一職責)。\n // 2026-07-05 D4 補完 Q2 附帶條件:selected×highlighted(鍵盤 cursor 停留)用加深一階\n // bg-neutral-selected-active(neutral-3;M23 nearest canonical = Button toggle active 同\n // family)— 深化而非洗掉 selection,cursor 可感知(WCAG 2.4.7),對齊 MUI selectedOpacity+hover idiom。\n selected && 'bg-neutral-selected data-[highlighted]:bg-neutral-selected-active',\n className,\n )}\n {...props}\n >\n <MenuItem\n size={size}\n startIcon={startIcon}\n avatar={avatar}\n description={description}\n tag={tag}\n endContent={endContent}\n disabled={disabled}\n // Pure visual — Radix parent handles role/aria/interaction\n role=\"presentation\"\n className=\"!bg-transparent hover:!bg-transparent pointer-events-none\"\n >\n {children}\n </MenuItem>\n </DropdownMenuPrimitive.Item>\n )\n})\nDropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName\n\n// ── SubTrigger(子選單觸發器,自動附加 ChevronRight)──\ninterface DropdownMenuSubTriggerProps\n extends Omit<React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger>, 'children'> {\n children: React.ReactNode\n /** 左側 icon */\n startIcon?: LucideIcon\n /** 子選單目前狀態值文字(如 \"深色\") */\n value?: string\n /** 子選單狀態 badge */\n badge?: React.ReactNode\n}\n\nconst DropdownMenuSubTrigger = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,\n DropdownMenuSubTriggerProps\n>(({ className, children, startIcon, value, badge, ...props }, ref) => {\n const size = useRowSize()\n const iconPx = ICON_SIZE[size]\n\n // SubTrigger suffix: [value?] [badge?] [ChevronRight] with gap-1\n const endContent = (\n <div className=\"flex items-center gap-1\">\n {value && <span className=\"text-fg-muted\">{value}</span>}\n {badge}\n <ChevronRight size={iconPx} className=\"text-fg-muted\" />\n </div>\n )\n\n return (\n <DropdownMenuPrimitive.SubTrigger\n ref={ref}\n // 2026-07-05:同 DropdownMenuItem——typeahead 只比對 label(endContent 的 value/badge 不進比對)。\n textValue={typeof children === 'string' ? children : undefined}\n className={cn(\n radixItemClass,\n 'data-[state=open]:bg-neutral-hover',\n className,\n )}\n {...props}\n >\n <MenuItem\n size={size}\n startIcon={startIcon}\n endContent={endContent}\n role=\"presentation\"\n className=\"!bg-transparent hover:!bg-transparent pointer-events-none\"\n >\n {children}\n </MenuItem>\n </DropdownMenuPrimitive.SubTrigger>\n )\n})\nDropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName\n\n// ── CheckboxItem ──\ninterface DropdownMenuCheckboxItemProps\n extends Omit<React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>, 'children'> {\n children: React.ReactNode\n /** 左側 icon */\n startIcon?: LucideIcon\n /** 次要說明文字 */\n description?: React.ReactNode\n}\n\nconst DropdownMenuCheckboxItem = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,\n DropdownMenuCheckboxItemProps\n>(({ className, children, startIcon, description, checked, disabled, ...props }, ref) => {\n const size = useRowSize()\n\n return (\n <DropdownMenuPrimitive.CheckboxItem\n ref={ref}\n checked={checked}\n disabled={disabled}\n // 2026-07-05:同 DropdownMenuItem——typeahead 只比對 label(M10 同 pattern:帶 description)。\n textValue={typeof children === 'string' ? children : undefined}\n onSelect={(e) => e.preventDefault()}\n className={cn(radixItemClass, className)}\n {...props}\n >\n <MenuItem\n size={size}\n checkbox\n checked={!!checked}\n startIcon={startIcon}\n description={description}\n disabled={disabled}\n role=\"presentation\"\n className=\"!bg-transparent hover:!bg-transparent pointer-events-none\"\n >\n {children}\n </MenuItem>\n </DropdownMenuPrimitive.CheckboxItem>\n )\n})\nDropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName\n\n// ── Label(群組標題)──\nconst DropdownMenuLabel = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.Label>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label>\n>(({ className, children, ...props }, ref) => {\n const size = useRowSize()\n return (\n <DropdownMenuPrimitive.Label\n ref={ref}\n className={cn('outline-none', className)}\n {...props}\n >\n <MenuItem\n size={size}\n header\n role=\"presentation\"\n className=\"pointer-events-none\"\n >\n {children}\n </MenuItem>\n </DropdownMenuPrimitive.Label>\n )\n})\nDropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName\n\n// ── Separator ──\nconst DropdownMenuSeparator = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.Separator>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>\n>(({ className, ...props }, ref) => (\n <DropdownMenuPrimitive.Separator\n ref={ref}\n className={cn(\"my-2 h-px bg-divider\", className)}\n {...props}\n />\n))\nDropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName\n\n// ── RadioItem(單選,排序方式等)──\n// Radix handles checked state; visual用 MenuItem 的 selected highlight。\ninterface DropdownMenuRadioItemProps\n extends React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem> {\n /** Prefix icon(LucideIcon) */\n startIcon?: LucideIcon\n /** 次要說明文字 */\n description?: React.ReactNode\n}\n\nconst DropdownMenuRadioItem = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,\n DropdownMenuRadioItemProps\n>(({ className, children, startIcon, description, disabled, ...props }, ref) => {\n const size = useRowSize()\n\n return (\n <DropdownMenuPrimitive.RadioItem\n ref={ref}\n disabled={disabled}\n // 2026-07-05:同 DropdownMenuItem——typeahead 只比對 label(M10 同 pattern:帶 description)。\n textValue={typeof children === 'string' ? children : undefined}\n onSelect={(e) => e.preventDefault()}\n // 2026-05-31 #10:selected 底色套在外層 Radix RadioItem 本身(非 `[&>*]` 子 MenuItem),\n // 因內層 MenuItem 自帶 `!bg-transparent` 會蓋掉子層 bg → 選中底色從不顯示。\n // 改 parent-bg pattern(對齊 DropdownMenuItem selected):RadioItem 上底色,MenuItem 透明讓它透出。\n // 2026-07-04 Q2:checked 亦勝 highlighted(同 DropdownMenuItem selected 規則)\n className={cn(radixItemClass, 'data-[state=checked]:bg-neutral-selected data-[state=checked]:data-[highlighted]:bg-neutral-selected-active', className)}\n {...props}\n >\n <MenuItem\n size={size}\n startIcon={startIcon}\n description={description}\n disabled={disabled}\n role=\"presentation\"\n className=\"!bg-transparent hover:!bg-transparent pointer-events-none\"\n >\n {children}\n </MenuItem>\n </DropdownMenuPrimitive.RadioItem>\n )\n})\nDropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName\n\n// ── Shortcut(鍵盤快捷鍵提示,ml-auto 靠右)──\n// **Canonical 是 `<DropdownMenuItem shortcut=\"⌘C\">`** prop —— 走 MenuItem `endContent` 正規後綴 slot\n// (跟 badge / endIcon 同槽、gap/對齊一致)。本 child 是 **composition escape-hatch**(對齊 shadcn\n// DropdownMenuShortcut idiom),供需手動 compose children 的少數場景;它用 `ml-auto` 塞在 children\n// 內(繞過 endContent slot)。**同一 item 只用一種,勿混用**(見 spec.md 禁止事項)。\n// 視覺統一:text-caption + tracking-shortcut + fg-muted(對齊 prop 後綴 + CommandShortcut)。\nconst DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => (\n <span\n className={cn('ml-auto text-caption text-fg-muted tracking-shortcut', className)}\n {...props}\n />\n)\nDropdownMenuShortcut.displayName = 'DropdownMenuShortcut'\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 dropdownMenuMeta = {\n component: 'DropdownMenu',\n family: 1, // 對齊 dropdown-menu.spec.md frontmatter L3(Family 1 消費者;DS 慣例 = meta 對齊 spec frontmatter,原 null 是 Phase 1 boilerplate 殘留)\n variants: {\n\n },\n sizes: {\n\n },\n // 'selected' = 單選/checked item 持續選中(bg-neutral-selected);'active' 保留 — highlight-on-selected\n // 走 bg-neutral-selected-active(active 階 token,M23 nearest canonical;2026-07-07 詞彙統一補修)。\n states: ['default', 'hover', 'active', 'selected', 'focus-visible', 'disabled'],\n tokens: {\n bg: ['bg-neutral-hover', 'bg-neutral-selected', 'bg-neutral-selected-active', 'bg-surface-raised', 'bg-transparent'],\n fg: ['text-fg-disabled', 'text-fg-muted'],\n ring: ['ring-ring'], // 2026-07-05:Trigger focus canonical ring(對齊 Checkbox/Tabs 等 meta 慣例)\n },\n} as const\n\nexport {\n DropdownMenu,\n DropdownMenuTrigger,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuCheckboxItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuGroup,\n DropdownMenuPortal,\n DropdownMenuSub,\n DropdownMenuSubContent,\n DropdownMenuSubTrigger,\n DropdownMenuRadioGroup,\n DropdownMenuRadioItem,\n DropdownMenuShortcut,\n floatingLayerClass,\n}\nexport type { SizeKey, DropdownMenuItemProps }\n"],"names":["ROW_ICON_SIZE"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"dropdown-menu.js","sources":["../../../src/components/DropdownMenu/dropdown-menu.tsx"],"sourcesContent":["import * as React from \"react\"\nimport * as DropdownMenuPrimitive from \"@radix-ui/react-dropdown-menu\"\nimport { ChevronRight, type LucideIcon } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport type { AvatarData } from \"@/design-system/components/Avatar/avatar\"\nimport { MenuItem } from \"@/design-system/components/Menu/menu-item\"\nimport { ScrollArea } from \"@/design-system/components/ScrollArea/scroll-area\"\nimport { OVERLAY_SIDE_OFFSET, OVERLAY_COLLISION_PADDING } from \"@/design-system/tokens/elevation/overlay-geometry\"\nimport {\n RowSizeProvider,\n useRowSize,\n ICON_SIZE as ROW_ICON_SIZE,\n type RowSize,\n} from \"@/design-system/patterns/element-anatomy/item-anatomy\"\nimport { overlayMotion } from \"@/design-system/tokens/motion/overlay-motion\"\n\n/**\n * DropdownMenu — Radix DropdownMenu + MenuItem visual layer\n *\n * 架構分工:\n * - Radix primitives:behavior(keyboard nav, focus management, aria roles)\n * - MenuItem:visual(layout, padding, icon alignment, typography)\n *\n * Radix primitive 是外層容器,控制 `data-[highlighted]:bg-neutral-hover`。\n * MenuItem 內層只負責佈局,不加互動樣式。\n *\n * ── Hover / highlight canonical(2026-04-22 修正)──\n * 用 Radix 官方的 `data-[highlighted]` attribute,**不用 `:focus-visible` / `:hover` /\n * `:focus`**:\n * - Radix 在 **mouse hover、keyboard arrow nav、focus move in** 時自動 set `data-highlighted`\n * - mouse leave / focus move out / menu close 時自動清掉\n * - 不會在 click 後留殘影(Radix 內部已處理)\n * - 跨瀏覽器一致(不依賴 `:focus-visible` 的 heuristic)\n *\n * 曾經用過 `focus-visible:bg-neutral-hover` 的理由:避免 click 後殘影。但實測:mouse hover\n * 觸發 Radix 程式化 `.focus()`,Chromium / Safari / Firefox 對 programmatic focus 是否 fire\n * `:focus-visible` 行為不一致,導致 mouse hover 有時無 bg。改用 `data-[highlighted]:` 後行為\n * 一致 —— 世界級 canonical(shadcn / Radix docs / Ariakit 皆此)。\n */\n\n// ── Floating layer 共用樣式 ──\nconst floatingLayerClass = [\n 'z-50 overflow-hidden rounded-lg border border-border bg-surface-raised',\n overlayMotion,\n 'data-[state=open]:animate-in data-[state=closed]:animate-out',\n 'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',\n 'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',\n 'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2',\n 'data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',\n 'origin-[var(--radix-dropdown-menu-content-transform-origin)]',\n].join(' ')\n\n// ── Size:統一用 RowSizeContext(item-layout module),消除本地 SizeContext 漂移 ──\ntype SizeKey = RowSize\n// Re-export for backward compat(內部命名)\nconst ICON_SIZE = ROW_ICON_SIZE\n\n// ── Shared item classes on Radix primitive ──\n// Highlight(hover + keyboard nav): 用 Radix `data-[highlighted]` canonical(見 docblock)\nconst radixItemClass = [\n 'relative cursor-pointer select-none outline-none',\n 'transition-colors duration-150',\n 'data-[highlighted]:bg-neutral-hover',\n 'data-[disabled]:pointer-events-none data-[disabled]:text-fg-disabled data-[disabled]:cursor-default',\n].join(' ')\n\n// ── Root ──\nconst DropdownMenu = DropdownMenuPrimitive.Root\nconst DropdownMenuTrigger = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.Trigger>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Trigger>\n>(({ className, ...props }, ref) => (\n <DropdownMenuPrimitive.Trigger\n ref={ref}\n // 2026-07-05:非 asChild 裸用需可見 focus 指示(WCAG 2.4.7)——原 `outline-none` 讓裸 Trigger\n // 完全無 focus ring。消費 Button focus canonical(button.tsx buttonVariants base)。\n // asChild+Button 場景同名 class 重複,無影響。\n className={cn(\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1',\n className,\n )}\n {...props}\n />\n))\nDropdownMenuTrigger.displayName = DropdownMenuPrimitive.Trigger.displayName\n// DropdownMenuGroup — 對齊 MenuGroup 的 group separation 設計語言\n//\n// 設計語言(跨 Menu-like 元件統一,SSOT 見 item-anatomy.spec.md\n// 「Group auto-separation」):\n// 每個 group 上下各 8px padding,相鄰 group 之間用 border-divider 分隔\n// 兩個 group 之間視覺 gap = 8px(上一個 bottom)+ 8px(下一個 top)= 16px + border\n//\n// MenuGroup(menu-item.tsx)實作:`py-2 [&+&]:border-t [&+&]:border-divider`\n// (在 Command.List 下提供 Content 邊界 8px + group 間 16px gap)\n//\n// DropdownMenuGroup(本元件)實作:`[&+&]:mt-2 [&+&]:pt-2 [&+&]:border-t\n// [&+&]:border-divider`(因為 DropdownMenuContent 已有 py-2 提供 Content 邊界\n// 的 8px,只需在第二個起的 group 加 8+8 = 16px gap + border)\n//\n// **視覺結果等同**:兩種實作的 visual output 一致,只是「padding 住在哪層」\n// 不同。不強制統一 CSS 表達式——DropdownMenuContent 的 py-2 是純視覺 Content\n// 邊界 padding(Radix roving focus 依 collection refs + element.focus(),不讀\n// CSS padding;對齊 spec.md「為什麼不直接套 py-2」段)。Group 若再套 py-2 會\n// double padding,故 Group 用 mt-2/pt-2 只加在第二個起。\nconst DropdownMenuGroup = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.Group>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Group>\n>(({ className, ...props }, ref) => (\n <DropdownMenuPrimitive.Group\n ref={ref}\n className={cn('[&+&]:border-t [&+&]:border-divider [&+&]:mt-2 [&+&]:pt-2', className)}\n {...props}\n />\n))\nDropdownMenuGroup.displayName = 'DropdownMenuGroup'\n\nconst DropdownMenuPortal = DropdownMenuPrimitive.Portal\nconst DropdownMenuSub = DropdownMenuPrimitive.Sub\nconst DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup\n\n// ── Content ──\ninterface DropdownMenuContentProps\n extends React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content> {\n size?: SizeKey\n /** 最小寬度(px),預設 `max(180px, 觸發元件寬度)`——窄 trigger 時吃 180px 地板 */\n minWidth?: number\n /** 最大高度(px),超過時捲動 */\n maxHeight?: number\n}\n\nconst DropdownMenuContent = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.Content>,\n DropdownMenuContentProps\n>(({ className, size = 'md', sideOffset = OVERLAY_SIDE_OFFSET, collisionPadding = OVERLAY_COLLISION_PADDING, align = 'start', minWidth, maxHeight, children, ...props }, ref) => (\n <DropdownMenuPrimitive.Portal>\n <DropdownMenuPrimitive.Content\n ref={ref}\n sideOffset={sideOffset}\n collisionPadding={collisionPadding}\n align={align}\n // Density:繼承 page density(2026-06-15 canonical)。menu item 高度 = field-height-{size},而\n // field-height 隨 density 變(md 28/32/36 → lg 32/36/40)→ 鎖 data-density=\"md\" 會把選單釘在 md-scale,\n // lg page 上對不上 lg 觸發點。原 data-density 是 409b91da a11y 批次順手加(對齊 Popover),非設計\n // 決策 → 移除,item 隨 page density 與觸發點一致(tier 仍由 size prop 決定)。\n // Focus return on close:不 override `onCloseAutoFocus` — 用 Radix 內建 default\n // (close 時 focus 還 trigger;outside-interaction 例外由 Radix `hasInteractedOutsideRef` 自管)。\n // W3C APG menubar「Escape: …return focus to the element…from which the menu was opened」\n // (w3.org/WAI/ARIA/apg/patterns/menubar/);shadcn dropdown-menu 同樣不 override。\n // 2026-06-11 移除 2026-04-08 的 `(e) => e.preventDefault()` hardcode:它跑在 Radix\n // composeEventHandlers 內建 handler 之前,defaultPrevented → trigger focus 被 skip →\n // Esc 關閉後 focus 掉到 body(APG violation)。原動機「mouse close 後 trigger 殘留\n // focus ring」不構成 override 理由:實測(2026-06-12 playwright)pointer item-click\n // close 後 programmatic refocus 在 Chromium 仍可 match `:focus-visible`(UA heuristic),\n // 但與 shadcn 官方 live demo 同流程 DOM 比對 IDENTICAL — Radix 生態一致接受此\n // tradeoff:APG keyboard focus-return 優先於 cosmetic ring。\n className={cn(floatingLayerClass, !maxHeight && 'py-2', className)}\n style={{\n boxShadow: 'var(--elevation-200)',\n minWidth: minWidth ?? 'max(180px, var(--radix-dropdown-menu-trigger-width))',\n maxHeight,\n }}\n {...props}\n >\n <RowSizeProvider value={size}>\n {maxHeight ? (\n // 長選單用 ScrollArea 跨 OS 一致捲動(不吃寬度,macOS/Windows 視覺一致)\n // py-2 移到內層,ScrollArea Viewport 才能 scroll 整個 padded 區\n <ScrollArea className=\"max-h-[inherit]\">\n <div className=\"py-2\">{children}</div>\n </ScrollArea>\n ) : (\n children\n )}\n </RowSizeProvider>\n </DropdownMenuPrimitive.Content>\n </DropdownMenuPrimitive.Portal>\n))\nDropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName\n\n// ── SubContent ──\nconst DropdownMenuSubContent = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>\n>(({ className, children, ...props }, ref) => {\n const size = useRowSize()\n return (\n <DropdownMenuPrimitive.SubContent\n ref={ref}\n sideOffset={OVERLAY_SIDE_OFFSET}\n className={cn(floatingLayerClass, 'py-2', className)}\n style={{ boxShadow: 'var(--elevation-200)', minWidth: 180 }}\n {...props}\n >\n <RowSizeProvider value={size}>\n {children}\n </RowSizeProvider>\n </DropdownMenuPrimitive.SubContent>\n )\n})\nDropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName\n\n// ── Helper: build endContent from badge + endIcon + shortcut ──\nfunction buildEndContent(\n size: SizeKey,\n badge?: React.ReactNode,\n endIcon?: LucideIcon,\n shortcut?: string,\n): React.ReactNode | undefined {\n const EndIcon = endIcon\n if (!badge && !EndIcon && !shortcut) return undefined\n const iconPx = ICON_SIZE[size]\n return (\n <>\n {badge}\n {EndIcon && <EndIcon size={iconPx} className=\"text-fg-muted\" aria-hidden />}\n {shortcut && <span className=\"text-caption text-fg-muted tracking-shortcut\">{shortcut}</span>}\n </>\n )\n}\n\n// ── Item ──\ninterface DropdownMenuItemProps\n extends Omit<React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item>, 'children'> {\n children: React.ReactNode\n /** 左側 icon */\n startIcon?: LucideIcon\n /** 左側頭像資料(AvatarData),與 startIcon 互斥 */\n avatar?: AvatarData\n /** 次要說明文字 */\n description?: React.ReactNode\n /** 後綴 Tag(ReactNode) */\n tag?: React.ReactNode\n /** 後綴 Badge(ReactNode) */\n badge?: React.ReactNode\n /** 後綴指示型 icon(LucideIcon),fg-muted */\n endIcon?: LucideIcon\n /** 鍵盤快捷鍵提示(canonical)——渲染進 MenuItem `endContent` 正規後綴 slot。\n * 替代方案是 `<DropdownMenuShortcut>` child(composition escape-hatch);**同 item 勿混用**。 */\n shortcut?: string\n /** 單選選中(bg-neutral-selected,持續選中狀態)*/\n selected?: boolean\n}\n\nconst DropdownMenuItem = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.Item>,\n DropdownMenuItemProps\n>(({ className, children, startIcon, avatar, description, tag, badge, endIcon, shortcut, selected, disabled, ...props }, ref) => {\n const size = useRowSize()\n const endContent = buildEndContent(size, badge, endIcon, shortcut)\n\n return (\n <DropdownMenuPrimitive.Item\n ref={ref}\n disabled={disabled}\n // 2026-07-05:typeahead 只比對 label——children 為字串時 auto-forward textValue,避免 Radix\n // fallback 拿整段 textContent(label+description+badge+shortcut 串接)比對。consumer 顯式\n // textValue 在 {...props} 較後 spread,仍優先。\n textValue={typeof children === 'string' ? children : undefined}\n className={cn(\n radixItemClass,\n // 2026-07-04 Q2 拍板:selected bg 勝 hover/highlighted(bg 是唯一選中指示器,被 hover 洗掉\n // = 選中資訊消失)。對齊 MenuItem(menu-item.tsx selected 時關 hover bg)+ Ant Menu\n // `:not(-item-selected)` + VS Code list `:hover:not(.selected)`;Radix highlighted 同時是\n // keyboard cursor,selected 項停留時維持 selected bg(DS bg 通道單一職責)。\n // 2026-07-05 D4 補完 Q2 附帶條件:selected×highlighted(鍵盤 cursor 停留)用加深一階\n // bg-neutral-selected-active(neutral-3;M23 nearest canonical = Button toggle active 同\n // family)— 深化而非洗掉 selection,cursor 可感知(WCAG 2.4.7),對齊 MUI selectedOpacity+hover idiom。\n selected && 'bg-neutral-selected data-[highlighted]:bg-neutral-selected-active',\n className,\n )}\n {...props}\n >\n <MenuItem\n size={size}\n startIcon={startIcon}\n avatar={avatar}\n description={description}\n tag={tag}\n endContent={endContent}\n disabled={disabled}\n // Pure visual — Radix parent handles role/aria/interaction\n role=\"presentation\"\n className=\"!bg-transparent hover:!bg-transparent pointer-events-none\"\n >\n {children}\n </MenuItem>\n </DropdownMenuPrimitive.Item>\n )\n})\nDropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName\n\n// ── SubTrigger(子選單觸發器,自動附加 ChevronRight)──\ninterface DropdownMenuSubTriggerProps\n extends Omit<React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger>, 'children'> {\n children: React.ReactNode\n /** 左側 icon */\n startIcon?: LucideIcon\n /** 子選單目前狀態值文字(如 \"深色\") */\n value?: string\n /** 子選單狀態 badge */\n badge?: React.ReactNode\n}\n\nconst DropdownMenuSubTrigger = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,\n DropdownMenuSubTriggerProps\n>(({ className, children, startIcon, value, badge, ...props }, ref) => {\n const size = useRowSize()\n const iconPx = ICON_SIZE[size]\n\n // SubTrigger suffix: [value?] [badge?] [ChevronRight] with gap-1\n const endContent = (\n <div className=\"flex items-center gap-1\">\n {value && <span className=\"text-fg-muted\">{value}</span>}\n {badge}\n <ChevronRight size={iconPx} className=\"text-fg-muted\" />\n </div>\n )\n\n return (\n <DropdownMenuPrimitive.SubTrigger\n ref={ref}\n // 2026-07-05:同 DropdownMenuItem——typeahead 只比對 label(endContent 的 value/badge 不進比對)。\n textValue={typeof children === 'string' ? children : undefined}\n className={cn(\n radixItemClass,\n 'data-[state=open]:bg-neutral-hover',\n className,\n )}\n {...props}\n >\n <MenuItem\n size={size}\n startIcon={startIcon}\n endContent={endContent}\n role=\"presentation\"\n className=\"!bg-transparent hover:!bg-transparent pointer-events-none\"\n >\n {children}\n </MenuItem>\n </DropdownMenuPrimitive.SubTrigger>\n )\n})\nDropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName\n\n// ── CheckboxItem ──\ninterface DropdownMenuCheckboxItemProps\n extends Omit<React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>, 'children'> {\n children: React.ReactNode\n /** 左側 icon */\n startIcon?: LucideIcon\n /** 次要說明文字 */\n description?: React.ReactNode\n}\n\nconst DropdownMenuCheckboxItem = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,\n DropdownMenuCheckboxItemProps\n>(({ className, children, startIcon, description, checked, disabled, ...props }, ref) => {\n const size = useRowSize()\n\n return (\n <DropdownMenuPrimitive.CheckboxItem\n ref={ref}\n checked={checked}\n disabled={disabled}\n // 2026-07-05:同 DropdownMenuItem——typeahead 只比對 label(M10 同 pattern:帶 description)。\n textValue={typeof children === 'string' ? children : undefined}\n onSelect={(e) => e.preventDefault()}\n className={cn(radixItemClass, className)}\n {...props}\n >\n <MenuItem\n size={size}\n checkbox\n checked={!!checked}\n startIcon={startIcon}\n description={description}\n disabled={disabled}\n role=\"presentation\"\n className=\"!bg-transparent hover:!bg-transparent pointer-events-none\"\n >\n {children}\n </MenuItem>\n </DropdownMenuPrimitive.CheckboxItem>\n )\n})\nDropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName\n\n// ── Label(群組標題)──\nconst DropdownMenuLabel = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.Label>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label>\n>(({ className, children, ...props }, ref) => {\n const size = useRowSize()\n return (\n <DropdownMenuPrimitive.Label\n ref={ref}\n className={cn('outline-none', className)}\n {...props}\n >\n <MenuItem\n size={size}\n header\n role=\"presentation\"\n className=\"pointer-events-none\"\n >\n {children}\n </MenuItem>\n </DropdownMenuPrimitive.Label>\n )\n})\nDropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName\n\n// ── Separator ──\nconst DropdownMenuSeparator = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.Separator>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>\n>(({ className, ...props }, ref) => (\n <DropdownMenuPrimitive.Separator\n ref={ref}\n className={cn(\"my-2 h-px bg-divider\", className)}\n {...props}\n />\n))\nDropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName\n\n// ── RadioItem(單選,排序方式等)──\n// Radix handles checked state;checked 底色套在外層 Radix RadioItem 本身(parent-bg\n// pattern,詳下方 2026-05-31 #10 註解),內層 MenuItem 恆 !bg-transparent 讓它透出。\ninterface DropdownMenuRadioItemProps\n extends React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem> {\n /** Prefix icon(LucideIcon) */\n startIcon?: LucideIcon\n /** 次要說明文字 */\n description?: React.ReactNode\n}\n\nconst DropdownMenuRadioItem = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,\n DropdownMenuRadioItemProps\n>(({ className, children, startIcon, description, disabled, ...props }, ref) => {\n const size = useRowSize()\n\n return (\n <DropdownMenuPrimitive.RadioItem\n ref={ref}\n disabled={disabled}\n // 2026-07-05:同 DropdownMenuItem——typeahead 只比對 label(M10 同 pattern:帶 description)。\n textValue={typeof children === 'string' ? children : undefined}\n onSelect={(e) => e.preventDefault()}\n // 2026-05-31 #10:selected 底色套在外層 Radix RadioItem 本身(非 `[&>*]` 子 MenuItem),\n // 因內層 MenuItem 自帶 `!bg-transparent` 會蓋掉子層 bg → 選中底色從不顯示。\n // 改 parent-bg pattern(對齊 DropdownMenuItem selected):RadioItem 上底色,MenuItem 透明讓它透出。\n // 2026-07-04 Q2:checked 亦勝 highlighted(同 DropdownMenuItem selected 規則)\n className={cn(radixItemClass, 'data-[state=checked]:bg-neutral-selected data-[state=checked]:data-[highlighted]:bg-neutral-selected-active', className)}\n {...props}\n >\n <MenuItem\n size={size}\n startIcon={startIcon}\n description={description}\n disabled={disabled}\n role=\"presentation\"\n className=\"!bg-transparent hover:!bg-transparent pointer-events-none\"\n >\n {children}\n </MenuItem>\n </DropdownMenuPrimitive.RadioItem>\n )\n})\nDropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName\n\n// ── Shortcut(鍵盤快捷鍵提示 child;escape-hatch,不靠右)──\n// **Canonical 是 `<DropdownMenuItem shortcut=\"⌘C\">`** prop —— 走 MenuItem `endContent` 正規後綴 slot\n// (跟 badge / endIcon 同槽、gap/對齊一致,真正靠右對齊列尾)。本 child 是 **composition\n// escape-hatch**(相容 shadcn DropdownMenuShortcut idiom = `ml-auto` span,source:\n// https://ui.shadcn.com/docs/components/dropdown-menu;npm published API 不砍)。⚠️ 在\n// DropdownMenuItem 內 children 會進 MenuItem ItemContent 的 label `<span>`(inline context,\n// 非 flex row),故 `ml-auto` **不生效、不會靠右**——只會 inline 附加在 label 文字尾;`ml-auto`\n// 僅在 consumer 自組 flex row 時生效。要靠右一律用 canonical `shortcut` prop。**同一 item\n// 只用一種,勿混用**(見 spec.md「快捷鍵提示 API」+ 禁止事項)。\n// 字體樣式統一:text-caption + tracking-shortcut + fg-muted(對齊 prop 後綴 + CommandShortcut);位置不等價。\nconst DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => (\n <span\n className={cn('ml-auto text-caption text-fg-muted tracking-shortcut', className)}\n {...props}\n />\n)\nDropdownMenuShortcut.displayName = 'DropdownMenuShortcut'\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 dropdownMenuMeta = {\n component: 'DropdownMenu',\n family: 1, // 對齊 dropdown-menu.spec.md frontmatter L3(Family 1 消費者;DS 慣例 = meta 對齊 spec frontmatter,原 null 是 Phase 1 boilerplate 殘留)\n variants: {\n\n },\n sizes: {\n\n },\n // 'selected' = 單選/checked item 持續選中(bg-neutral-selected);'active' 保留 — highlight-on-selected\n // 走 bg-neutral-selected-active(active 階 token,M23 nearest canonical;2026-07-07 詞彙統一補修)。\n states: ['default', 'hover', 'active', 'selected', 'focus-visible', 'disabled'],\n tokens: {\n bg: ['bg-neutral-hover', 'bg-neutral-selected', 'bg-neutral-selected-active', 'bg-surface-raised', 'bg-transparent'],\n fg: ['text-fg-disabled', 'text-fg-muted'],\n ring: ['ring-ring'], // 2026-07-05:Trigger focus canonical ring(對齊 Checkbox/Tabs 等 meta 慣例)\n },\n} as const\n\nexport {\n DropdownMenu,\n DropdownMenuTrigger,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuCheckboxItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuGroup,\n DropdownMenuPortal,\n DropdownMenuSub,\n DropdownMenuSubContent,\n DropdownMenuSubTrigger,\n DropdownMenuRadioGroup,\n DropdownMenuRadioItem,\n DropdownMenuShortcut,\n floatingLayerClass,\n}\nexport type { SizeKey, DropdownMenuItemProps }\n"],"names":["ROW_ICON_SIZE"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import type { FieldMode, FieldVariant } from './field-types';
|
|
3
|
-
export type { FieldMode, FieldVariant };
|
|
2
|
+
import type { FieldMode, FieldVariant, FieldVariantInternal, FieldWidth } from './field-types';
|
|
3
|
+
export type { FieldMode, FieldVariant, FieldVariantInternal, FieldWidth };
|
|
4
4
|
export type FieldOrientation = 'vertical' | 'horizontal';
|
|
5
5
|
export type FieldSize = 'sm' | 'md' | 'lg';
|
|
6
6
|
export type FieldControlLayout = 'inline' | 'block';
|
|
@@ -24,7 +24,8 @@ export interface FieldContextValue {
|
|
|
24
24
|
descriptionId: string;
|
|
25
25
|
errorId: string;
|
|
26
26
|
mode: FieldMode;
|
|
27
|
-
/** 視覺外殼透傳(2026-05-05)。default = 含 border+bg;
|
|
27
|
+
/** 視覺外殼透傳(2026-05-05)。default = 含 border+bg;naked = cell-as-input(edit×naked 自畫 border-based state machine;display/readonly/disabled×naked 用 transparent border 由 host cell 供邊框);`bare` 2026-07-09 退役;naked 2026-07-14 型別收窄至
|
|
28
|
+
* FieldVariantInternal(@internal)— 公開 `<Field variant>` 只收 default,故本欄位維持 FieldVariant。
|
|
28
29
|
* child Field control 自動繼承,per-control prop override 可覆寫。詳 field-types.ts。 */
|
|
29
30
|
variant: FieldVariant;
|
|
30
31
|
disabled: boolean;
|
|
@@ -106,10 +107,12 @@ export declare function useResolvedFieldMode({ mode, disabled, readOnly, }: {
|
|
|
106
107
|
readOnly?: boolean;
|
|
107
108
|
}): FieldMode;
|
|
108
109
|
/**
|
|
109
|
-
* Resolve Field control 的 **variant**(default / bare
|
|
110
|
+
* Resolve Field control 的 **variant**(default / naked 視覺外殼;`bare` 2026-07-09 退役)— 2026-06-08 SSOT。
|
|
110
111
|
* 優先序:顯式 prop > FieldContext.variant > 'default'。與既有各控件公式一字不差(Δ=0),統一以利 gate 強制。
|
|
112
|
+
* 2026-07-14 API 策展 E:入參 / 回傳 widen 為 FieldVariantInternal — naked 僅 DataTable cell-registry
|
|
113
|
+
* 經 @internal 通道傳入(公開 prop 型別 = FieldVariant,不含 naked)。
|
|
111
114
|
*/
|
|
112
|
-
export declare function useResolvedFieldVariant(variantProp?:
|
|
115
|
+
export declare function useResolvedFieldVariant(variantProp?: FieldVariantInternal | null): FieldVariantInternal;
|
|
113
116
|
/**
|
|
114
117
|
* Resolve Field control 的 **error/invalid** — 取代散落的 `errorProp || (fieldCtx?.invalid ?? false)`(2026-06-08 SSOT)。
|
|
115
118
|
* 自身 error prop **或** FieldContext.invalid(`<Field invalid>`)任一為真即 error。與既有公式一致(Δ=0)。
|
|
@@ -123,4 +126,41 @@ export declare function FieldSurfaceSizeProvider({ size, children, }: {
|
|
|
123
126
|
size: FieldSize;
|
|
124
127
|
children: React.ReactNode;
|
|
125
128
|
}): React.ReactElement;
|
|
129
|
+
export declare function FieldSurfaceEditableProvider({ isEditable, children, }: {
|
|
130
|
+
isEditable: boolean;
|
|
131
|
+
children: React.ReactNode;
|
|
132
|
+
}): React.ReactElement;
|
|
133
|
+
/**
|
|
134
|
+
* 空值顯示分流(2026-07-08 user 拍板,verbatim「...都用"-"...我從頭到尾哪裡有說要用全形的」):
|
|
135
|
+
*
|
|
136
|
+
* | 情境 | 空值顯示 |
|
|
137
|
+
* |------------------------------------------------|----------|
|
|
138
|
+
* | table-cell **可編輯** 的 display 靜止態 | **空白 `''`**(不佔位,affordance = hover outline)|
|
|
139
|
+
* | table-cell **不可編輯**(readonly cell) | **半形 `-`** |
|
|
140
|
+
* | standalone display / readonly / form / toolbar | **半形 `-`** |
|
|
141
|
+
*
|
|
142
|
+
* 收斂式:`surface==='table-cell' && isEditable ? '' : EMPTY_DISPLAY`。
|
|
143
|
+
* 可編輯 form / edit 輸入框走 native placeholder(不經此 hook)。boolean → unchecked / disabled →
|
|
144
|
+
* 同上文字 + text-fg-disabled(M24),各控件自理。
|
|
145
|
+
* 世界級對照:table-cell blank = MUI X / AG Grid / Ant core / Notion / Airtable grid 域共識;
|
|
146
|
+
* 非 table `-` = Ant ProTable `columnEmptyText`(見 field-wrapper.tsx EMPTY_DISPLAY 註)。
|
|
147
|
+
* SSOT 條文 → field-controls.spec.md「null / undefined 值」;全 Field family display/readonly/disabled
|
|
148
|
+
* 空值渲染必經此 hook,禁直接引 EMPTY_DISPLAY 常數(genre 分流會漏)。
|
|
149
|
+
*/
|
|
150
|
+
export declare function useFieldEmptyDisplay(): string;
|
|
151
|
+
/**
|
|
152
|
+
* 空值符號「-」顏色分流(2026-07-09 user 拍板 verbatim「「-」代表的是不可編輯只拿來供檢視的值
|
|
153
|
+
* 所以應該跟readonly 的value同樣顏色吧」):
|
|
154
|
+
*
|
|
155
|
+
* 「-」是「不可編輯、供檢視的值」→ 同 readonly value 色 = `text-foreground`
|
|
156
|
+
* (field-wrapper.tsx base `text-foreground`),**非** `text-fg-muted`(muted 是「可編輯 placeholder
|
|
157
|
+
* 提示」的裝飾語意;空值符號不是提示、是被檢視的值狀態)。
|
|
158
|
+
* disabled 態維持 `text-fg-disabled`(M24 disabled 顯著性 > foreground/muted,不可被蓋)。
|
|
159
|
+
*
|
|
160
|
+
* SSOT:全 Field family display/readonly/disabled 空值 span 消費此 helper,傳入已 resolve 的
|
|
161
|
+
* resolvedMode。純函式(非 hook)—— resolvedMode 已由 useResolvedFieldMode 解析(含控件自身
|
|
162
|
+
* disabled prop,context hook 讀不到);故以 resolvedMode 為入參,可條件呼叫、不受 Rules of Hooks 限制。
|
|
163
|
+
* 世界級對照:Ant read-only / Carbon read-only value = 正常前景色;placeholder 才 muted。
|
|
164
|
+
*/
|
|
165
|
+
export declare function fieldEmptyColorClass(resolvedMode: FieldMode): string;
|
|
126
166
|
//# sourceMappingURL=field-context.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"field-context.d.ts","sourceRoot":"","sources":["../../../src/components/Field/field-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"field-context.d.ts","sourceRoot":"","sources":["../../../src/components/Field/field-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,oBAAoB,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAI9F,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,oBAAoB,EAAE,UAAU,EAAE,CAAA;AACzE,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG,YAAY,CAAA;AACxD,MAAM,MAAM,SAAS,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;AAC1C,MAAM,MAAM,kBAAkB,GAAG,QAAQ,GAAG,OAAO,CAAA;AAEnD;;;;;;;;;GASG;AACH,KAAK,YAAY,GAAG,MAAM,GAAG,SAAS,GAAG,YAAY,CAAA;AAGrD,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAA;IACV;;wEAEoE;IACpE,OAAO,EAAE,MAAM,CAAA;IACf,aAAa,EAAE,MAAM,CAAA;IACrB,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,SAAS,CAAA;IACf;;mFAE+E;IAC/E,OAAO,EAAE,YAAY,CAAA;IACrB,QAAQ,EAAE,OAAO,CAAA;IACjB,QAAQ,EAAE,OAAO,CAAA;IACjB,OAAO,EAAE,OAAO,CAAA;IAChB,IAAI,EAAE,SAAS,CAAA;IACf,WAAW,EAAE,gBAAgB,CAAA;IAC7B,aAAa,EAAE,kBAAkB,CAAA;IACjC,eAAe,EAAE,IAAI,CAAA;IACrB;;qDAEiD;IACjD,OAAO,CAAC,EAAE,YAAY,CAAA;CACvB;AAED,eAAO,MAAM,YAAY,yCAAsD,CAAA;AAE/E,uEAAuE;AACvE,wBAAgB,eAAe,IAAI,iBAAiB,GAAG,IAAI,CAE1D;AAgBD;;;;;;;;;GASG;AACH,wBAAgB,eAAe,IAAI,YAAY,CAK9C;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,OAAO,EACP,QAAQ,GACT,EAAE;IACD,OAAO,EAAE,YAAY,CAAA;IACrB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAC1B,GAAG,KAAK,CAAC,YAAY,CAErB;AAkBD,wBAAgB,mBAAmB,IAAI,OAAO,CAE7C;AAED,wBAAgB,mBAAmB,CAAC,EAClC,WAAW,EACX,QAAQ,GACT,EAAE;IACD,WAAW,EAAE,OAAO,CAAA;IACpB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAC1B,GAAG,KAAK,CAAC,YAAY,CAErB;AAaD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,SAAS,MAAM,GAAG,SAAS,EAAE,QAAQ,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,QAAQ,CAAC,EAAE,CAAC,GAAG,CAAC,CAOvG;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,YAAY,CAAC,EAAE,OAAO,GAAG,IAAI,GAAG,OAAO,CAG/E;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,IAAI,EACJ,QAAQ,EACR,QAAQ,GACT,EAAE;IACD,IAAI,CAAC,EAAE,SAAS,GAAG,IAAI,CAAA;IACvB,QAAQ,CAAC,EAAE,OAAO,GAAG,IAAI,CAAA;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,GAAG,SAAS,CAOZ;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,WAAW,CAAC,EAAE,oBAAoB,GAAG,IAAI,GAAG,oBAAoB,CAGvG;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,SAAS,CAAC,EAAE,OAAO,GAAG,OAAO,CAGpE;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,EACvC,IAAI,EACJ,QAAQ,GACT,EAAE;IACD,IAAI,EAAE,SAAS,CAAA;IACf,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAC1B,GAAG,KAAK,CAAC,YAAY,CAErB;AAWD,wBAAgB,4BAA4B,CAAC,EAC3C,UAAU,EACV,QAAQ,GACT,EAAE;IACD,UAAU,EAAE,OAAO,CAAA;IACnB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAC1B,GAAG,KAAK,CAAC,YAAY,CAErB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAI7C;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,SAAS,GAAG,MAAM,CAEpE"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
+
import { EMPTY_DISPLAY } from "./field-wrapper.js";
|
|
2
3
|
const FieldContext = React.createContext(null);
|
|
3
4
|
function useFieldContext() {
|
|
4
5
|
return React.useContext(FieldContext);
|
|
@@ -62,12 +63,30 @@ function FieldSurfaceSizeProvider({
|
|
|
62
63
|
}) {
|
|
63
64
|
return React.createElement(FieldSurfaceSizeContext.Provider, { value: size }, children);
|
|
64
65
|
}
|
|
66
|
+
const FieldSurfaceEditableContext = React.createContext(false);
|
|
67
|
+
function FieldSurfaceEditableProvider({
|
|
68
|
+
isEditable,
|
|
69
|
+
children
|
|
70
|
+
}) {
|
|
71
|
+
return React.createElement(FieldSurfaceEditableContext.Provider, { value: isEditable }, children);
|
|
72
|
+
}
|
|
73
|
+
function useFieldEmptyDisplay() {
|
|
74
|
+
const surface = useFieldSurface();
|
|
75
|
+
const isEditable = React.useContext(FieldSurfaceEditableContext);
|
|
76
|
+
return surface === "table-cell" && isEditable ? "" : EMPTY_DISPLAY;
|
|
77
|
+
}
|
|
78
|
+
function fieldEmptyColorClass(resolvedMode) {
|
|
79
|
+
return resolvedMode === "disabled" ? "text-fg-disabled" : "text-foreground";
|
|
80
|
+
}
|
|
65
81
|
export {
|
|
66
82
|
FieldContext,
|
|
83
|
+
FieldSurfaceEditableProvider,
|
|
67
84
|
FieldSurfaceProvider,
|
|
68
85
|
FieldSurfaceSizeProvider,
|
|
69
86
|
TableScrollProvider,
|
|
87
|
+
fieldEmptyColorClass,
|
|
70
88
|
useFieldContext,
|
|
89
|
+
useFieldEmptyDisplay,
|
|
71
90
|
useFieldSurface,
|
|
72
91
|
useResolvedFieldDisabled,
|
|
73
92
|
useResolvedFieldInvalid,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"field-context.js","sources":["../../../src/components/Field/field-context.ts"],"sourcesContent":["import * as React from 'react'\nimport type { FieldMode, FieldVariant } from './field-types'\n\n// ── Types ──\nexport type { FieldMode, FieldVariant }\nexport type FieldOrientation = 'vertical' | 'horizontal'\nexport type FieldSize = 'sm' | 'md' | 'lg'\nexport type FieldControlLayout = 'inline' | 'block'\n\n/**\n * Field surface(2026-05-12 codex Q2 verdict — Stream C 4-issues Cluster B fix):\n * 替代散落的 `variant === 'naked'` 判 cell heuristic,讓 Field family components\n * 顯式 query「我在哪 surface」derive cell-specific padding / alignment / overflow。\n *\n * - `'form'`(default)— Standalone Field control(對齊 Polaris / Material Form idiom)\n * - `'toolbar'`— Toolbar / Action bar inline Field(future,目前無 consumer)\n * - `'table-cell'`— DataTable cell-as-input substrate(取代 variant='naked' cell-detection\n * heuristic;canonical metrics 由 surface 推導,不再 hardcode in Field consumer)\n */\ntype FieldSurface = 'form' | 'toolbar' | 'table-cell'\n\n// ── Context ──\nexport interface FieldContextValue {\n id: string\n /** a11y(2026-04-25):FieldLabel 渲染時若元素為 div-based role(combobox/slider\n * 等非 native form control),`<label for>` 無效,需 aria-labelledby 指向 labelId。\n * FieldLabel 自動設 id={labelId},下游 control 可讀此值寫入 aria-labelledby。 */\n labelId: string\n descriptionId: string\n errorId: string\n mode: FieldMode\n /** 視覺外殼透傳(2026-05-05)。default = 含 border+bg;bare = 透明 variant,hover/focus reveal。\n * child Field control 自動繼承,per-control prop override 可覆寫。詳 field-types.ts。 */\n variant: FieldVariant\n disabled: boolean\n required: boolean\n invalid: boolean\n size: FieldSize\n orientation: FieldOrientation\n controlLayout: FieldControlLayout\n hasFieldWrapper: true\n /** 2026-05-12 Stream C Cluster B fix:surface 上下文(form / toolbar / table-cell)\n * 從 host(Field wrapper / DataTable cell registry)propagate 給 child controls。\n * Optional — 未設視同 `'form'`(backward compat)。 */\n surface?: FieldSurface\n}\n\nexport const FieldContext = React.createContext<FieldContextValue | null>(null)\n\n/** 讓 primitive(Checkbox/Switch/Radio/Button/Input 等)讀 Field context */\nexport function useFieldContext(): FieldContextValue | null {\n return React.useContext(FieldContext)\n}\n\n/**\n * Surface context — **獨立於 FieldContext** 的 surface signal(2026-05-12 v2 fix)。\n *\n * 原 v1 設計(`FieldSurfaceProvider` 透過 FieldContext.Provider 注入)誤把 stub\n * `hasFieldWrapper: true` 傳給沒有真正 Field wrapper 的場景(例 DataTable cell 內 standalone\n * DatePicker),導致 cells 誤以為有 Field 父 → 視覺 regression(2026-05-12 visual-audit 抓 3\n * stories 超過 baseline budget:DatePicker range-picker / FileViewer open-snapshot /\n * DataTable pinned-columns)。\n *\n * v2 修:**完全分離** FieldSurfaceContext;FieldContext 不被污染;non-FieldWrapper hosts\n * 只能影響 surface signal,不能假冒 Field wrapper 身份。\n */\nconst FieldSurfaceContext = React.createContext<FieldSurface | null>(null)\n\n/**\n * Helper: 取當前 surface,fall back 'form'。\n *\n * Stream C Cluster B canonical entry point — Field family controls 用此 helper 替代\n * 散落的 `variant === 'naked'` cell heuristic。對齊 codex Q2 verdict「surface context」\n * canonical(替代「inCellContext per-prop」反 pattern)。\n *\n * 優先順序:FieldSurfaceContext(host-provided) → FieldContext.surface(Field-wrapper-provided)\n * → fallback 'form'。\n */\nexport function useFieldSurface(): FieldSurface {\n const surfaceCtx = React.useContext(FieldSurfaceContext)\n if (surfaceCtx) return surfaceCtx\n const fieldCtx = React.useContext(FieldContext)\n return fieldCtx?.surface ?? 'form'\n}\n\n/**\n * Standalone FieldSurface provider — 給 host(non-Field-wrapper, 如 DataTable cell)\n * **僅**設 surface signal,**不**污染 FieldContext。\n *\n * Usage(per cell-registry.tsx,2026-05-12):\n * <FieldSurfaceProvider surface=\"table-cell\"><Field control /></FieldSurfaceProvider>\n *\n * 與 FieldContext.Provider 差別:\n * - FieldContext.Provider — 完整 Field wrapper 用,provides mode/variant/disabled/...\n * - FieldSurfaceProvider — host **僅**注 surface signal,Field controls 透過\n * `useFieldSurface()` 讀;`useFieldContext()` 仍 returns null(沒被假冒)。\n */\nexport function FieldSurfaceProvider({\n surface,\n children,\n}: {\n surface: FieldSurface\n children: React.ReactNode\n}): React.ReactElement {\n return React.createElement(FieldSurfaceContext.Provider, { value: surface }, children)\n}\n\n/**\n * 2026-05-13 (c) scroll-defer perf SSOT(per codex DataTable perf brief V3 + user 拍板\n * 「Roadmap still >50ms 進 (c)」):\n * Host(DataTable)在 virtualizer.isScrolling=true 期間 wrap children with `TableScrollProvider`,\n * 重 cell 組件(Avatar HoverCard / PeoplePicker / etc.)讀此 signal 跳過昂貴 subtree(如 HoverCard\n * Portal + useDocumentTheme observer),scroll 結束 unset → 重渲染為完整 affordance。\n *\n * 對齊 codex Profile Plan step 6(Scripting vs Layout 判分):scrolling 期間 cell 渲染壓縮 →\n * 240ms/frame catastrophe → close to 50ms target。世界級對齊:AG Grid `deferRender` for slow\n * React cell components / MUI X DataGrid scroll-defer hover affordance。\n *\n * 不放在 FieldSurfaceContext 內(避免 value object identity drift on scroll → 全 cell 重渲)\n * — 用獨立 Context,value = boolean primitive(stable when unchanged)。\n */\nconst TableScrollContext = React.createContext<boolean>(false)\n\nexport function useTableIsScrolling(): boolean {\n return React.useContext(TableScrollContext)\n}\n\nexport function TableScrollProvider({\n isScrolling,\n children,\n}: {\n isScrolling: boolean\n children: React.ReactNode\n}): React.ReactElement {\n return React.createElement(TableScrollContext.Provider, { value: isScrolling }, children)\n}\n\n/**\n * Surface size signal — 獨立於 FieldContext + FieldSurfaceContext 的純視覺 size context(2026-06-08)。\n * 讓 host(DataTable cell substrate)把「這片 surface 的 density size」propagate 給 child Field controls,\n * 未來新 cell 漏傳 size prop 也自動繼承 → 字級一致(根治 StringCell/NumberCell 漏傳 size class)。\n *\n * 不放進 FieldSurfaceContext value(避免 string→object identity drift on scroll → 全 cell 重渲,\n * 同 TableScrollContext L119-121 canonical)— 獨立 Context,value = FieldSize primitive(stable when unchanged)。\n * 絕不污染 FieldContext:不碰 hasFieldWrapper/mode/invalid/disabled,useFieldContext() 在 cell 內仍 null。\n */\nconst FieldSurfaceSizeContext = React.createContext<FieldSize | null>(null)\n\n/**\n * Resolve Field control size — 控件用此 helper 取代散落的 `sizeProp ?? fieldCtx?.size ?? 'md'`。\n * 優先序:prop(caller 顯式)> FieldContext.size(真 <Field> wrapper)> surface-size(host 如 cell)> 'md'。\n * 真 <Field> 永遠勝 host surface(安全序);cell 無 Field wrapper(fieldCtx=null)→ 自動接 surface-size,\n * 漏傳 size 的新 cell 也字級一致。\n */\nexport function useResolvedFieldSize<T extends string = FieldSize>(sizeProp?: T | null, fallback?: T): T {\n const fieldCtx = React.useContext(FieldContext)\n const surfaceSize = React.useContext(FieldSurfaceSizeContext)\n // generic T(預設 FieldSize)讓非-input 控件(SegmentedControl/Rating 的 'xs'|'sm'|'md'|'lg' 超集、各自 fallback)\n // 也走同一 SSOT resolution。fieldCtx.size/surfaceSize 為 FieldSize(T 的子集)→ widen cast 安全。\n // fallback 未傳預設 'md'(input-class 控件向後相容);Rating 傳 'xs'、SegmentedControl 傳 'md'。\n return (sizeProp ?? (fieldCtx?.size as T | undefined) ?? (surfaceSize as T | undefined) ?? fallback ?? ('md' as T))\n}\n\n/**\n * Resolve Field control 的 **disabled** — 取代散落的 `disabledProp ?? fieldCtx?.disabled`(2026-06-08 SSOT)。\n * 優先序:顯式 prop > FieldContext.disabled(`<Field disabled>` / `<Field mode=\"disabled\">`)> false。\n * **caller 必傳「未預設」的原始 prop**(沒傳 = undefined),否則 `disabled=false` 預設會吃掉 `??` fallback。\n * DataTable cell 無 FieldContext(fieldCtx=null)→ 回 prop 或 false,行為與現況完全一致(inert)。\n */\nexport function useResolvedFieldDisabled(disabledProp?: boolean | null): boolean {\n const fieldCtx = React.useContext(FieldContext)\n return disabledProp ?? fieldCtx?.disabled ?? false\n}\n\n/**\n * Resolve Field control 的 **mode**(display / readonly / disabled / edit)— 2026-06-08 SSOT,統一兩派散落:\n * 舊 Input 派 `modeProp ?? fieldCtx?.mode ?? (...)` → `<Field disabled>` 時 ctx.mode 仍 'edit',漏 disabled chrome。\n * 舊 picker 派 `disabled ? 'disabled' : mode`(mode 預設 'edit')→ 完全不讀 fieldCtx.mode,`<Field mode=\"display\">` 失效。\n * 統一優先序(world-class:MUI FormControl disabled 完整 cascade + 顯式 prop 永遠最優先):\n * 1. 顯式 mode prop(caller / DataTable cell 的 displayOrDisabled)→ **永遠最優先**,故表格等顯式場景 inert\n * 2. 有效 disabled(prop 或 `<Field disabled>`)→ 'disabled'(完整 disabled chrome)\n * 3. FieldContext.mode(`<Field mode=\"display\"/\"readonly\">`)→ 讓 mode cascade 真正生效\n * 4. 本地 readOnly → 'readonly'\n * 5. 'edit'\n * cell:mode prop 必有 → step 1 命中、fieldCtx=null → 完全 inert(Δ=0)。`disabled` 傳已 resolve 的 boolean 或未預設 prop。\n */\nexport function useResolvedFieldMode({\n mode,\n disabled,\n readOnly,\n}: {\n mode?: FieldMode | null\n disabled?: boolean | null\n readOnly?: boolean\n}): FieldMode {\n const fieldCtx = React.useContext(FieldContext)\n if (mode) return mode\n if ((disabled ?? fieldCtx?.disabled) === true) return 'disabled'\n if (fieldCtx?.mode) return fieldCtx.mode\n if (readOnly) return 'readonly'\n return 'edit'\n}\n\n/**\n * Resolve Field control 的 **variant**(default / bare / naked 視覺外殼)— 2026-06-08 SSOT。\n * 優先序:顯式 prop > FieldContext.variant > 'default'。與既有各控件公式一字不差(Δ=0),統一以利 gate 強制。\n */\nexport function useResolvedFieldVariant(variantProp?: FieldVariant | null): FieldVariant {\n const fieldCtx = React.useContext(FieldContext)\n return variantProp ?? fieldCtx?.variant ?? 'default'\n}\n\n/**\n * Resolve Field control 的 **error/invalid** — 取代散落的 `errorProp || (fieldCtx?.invalid ?? false)`(2026-06-08 SSOT)。\n * 自身 error prop **或** FieldContext.invalid(`<Field invalid>`)任一為真即 error。與既有公式一致(Δ=0)。\n */\nexport function useResolvedFieldInvalid(errorProp?: boolean): boolean {\n const fieldCtx = React.useContext(FieldContext)\n return Boolean(errorProp) || (fieldCtx?.invalid ?? false)\n}\n\n/**\n * Host(non-Field-wrapper,如 DataTable cell)注 surface density size 給 child Field controls。\n * 僅注 size 視覺訊號,不污染 FieldContext。Usage:cell-registry buildCellWithSurface。\n */\nexport function FieldSurfaceSizeProvider({\n size,\n children,\n}: {\n size: FieldSize\n children: React.ReactNode\n}): React.ReactElement {\n return React.createElement(FieldSurfaceSizeContext.Provider, { value: size }, children)\n}\n"],"names":[],"mappings":";AA+CO,MAAM,eAAe,MAAM,cAAwC,IAAI;AAGvE,SAAS,kBAA4C;AAC1D,SAAO,MAAM,WAAW,YAAY;AACtC;AAcA,MAAM,sBAAsB,MAAM,cAAmC,IAAI;AAYlE,SAAS,kBAAgC;AAC9C,QAAM,aAAa,MAAM,WAAW,mBAAmB;AACvD,MAAI,WAAY,QAAO;AACvB,QAAM,WAAW,MAAM,WAAW,YAAY;AAC9C,UAAO,qCAAU,YAAW;AAC9B;AAcO,SAAS,qBAAqB;AAAA,EACnC;AAAA,EACA;AACF,GAGuB;AACrB,SAAO,MAAM,cAAc,oBAAoB,UAAU,EAAE,OAAO,QAAA,GAAW,QAAQ;AACvF;AAgBA,MAAM,qBAAqB,MAAM,cAAuB,KAAK;AAEtD,SAAS,sBAA+B;AAC7C,SAAO,MAAM,WAAW,kBAAkB;AAC5C;AAEO,SAAS,oBAAoB;AAAA,EAClC;AAAA,EACA;AACF,GAGuB;AACrB,SAAO,MAAM,cAAc,mBAAmB,UAAU,EAAE,OAAO,YAAA,GAAe,QAAQ;AAC1F;AAWA,MAAM,0BAA0B,MAAM,cAAgC,IAAI;AAQnE,SAAS,qBAAmD,UAAqB,UAAiB;AACvG,QAAM,WAAW,MAAM,WAAW,YAAY;AAC9C,QAAM,cAAc,MAAM,WAAW,uBAAuB;AAI5D,SAAQ,aAAa,qCAAU,SAA2B,eAAiC,YAAa;AAC1G;AAQO,SAAS,yBAAyB,cAAwC;AAC/E,QAAM,WAAW,MAAM,WAAW,YAAY;AAC9C,SAAO,iBAAgB,qCAAU,aAAY;AAC/C;AAcO,SAAS,qBAAqB;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AACF,GAIc;AACZ,QAAM,WAAW,MAAM,WAAW,YAAY;AAC9C,MAAI,KAAM,QAAO;AACjB,OAAK,aAAY,qCAAU,eAAc,KAAM,QAAO;AACtD,MAAI,qCAAU,KAAM,QAAO,SAAS;AACpC,MAAI,SAAU,QAAO;AACrB,SAAO;AACT;AAMO,SAAS,wBAAwB,aAAiD;AACvF,QAAM,WAAW,MAAM,WAAW,YAAY;AAC9C,SAAO,gBAAe,qCAAU,YAAW;AAC7C;AAMO,SAAS,wBAAwB,WAA8B;AACpE,QAAM,WAAW,MAAM,WAAW,YAAY;AAC9C,SAAO,QAAQ,SAAS,OAAM,qCAAU,YAAW;AACrD;AAMO,SAAS,yBAAyB;AAAA,EACvC;AAAA,EACA;AACF,GAGuB;AACrB,SAAO,MAAM,cAAc,wBAAwB,UAAU,EAAE,OAAO,KAAA,GAAQ,QAAQ;AACxF;"}
|
|
1
|
+
{"version":3,"file":"field-context.js","sources":["../../../src/components/Field/field-context.ts"],"sourcesContent":["import * as React from 'react'\nimport type { FieldMode, FieldVariant, FieldVariantInternal, FieldWidth } from './field-types'\nimport { EMPTY_DISPLAY } from './field-wrapper'\n\n// ── Types ──\nexport type { FieldMode, FieldVariant, FieldVariantInternal, FieldWidth }\nexport type FieldOrientation = 'vertical' | 'horizontal'\nexport type FieldSize = 'sm' | 'md' | 'lg'\nexport type FieldControlLayout = 'inline' | 'block'\n\n/**\n * Field surface(2026-05-12 codex Q2 verdict — Stream C 4-issues Cluster B fix):\n * 替代散落的 `variant === 'naked'` 判 cell heuristic,讓 Field family components\n * 顯式 query「我在哪 surface」derive cell-specific padding / alignment / overflow。\n *\n * - `'form'`(default)— Standalone Field control(對齊 Polaris / Material Form idiom)\n * - `'toolbar'`— Toolbar / Action bar inline Field(future,目前無 consumer)\n * - `'table-cell'`— DataTable cell-as-input substrate(取代 variant='naked' cell-detection\n * heuristic;canonical metrics 由 surface 推導,不再 hardcode in Field consumer)\n */\ntype FieldSurface = 'form' | 'toolbar' | 'table-cell'\n\n// ── Context ──\nexport interface FieldContextValue {\n id: string\n /** a11y(2026-04-25):FieldLabel 渲染時若元素為 div-based role(combobox/slider\n * 等非 native form control),`<label for>` 無效,需 aria-labelledby 指向 labelId。\n * FieldLabel 自動設 id={labelId},下游 control 可讀此值寫入 aria-labelledby。 */\n labelId: string\n descriptionId: string\n errorId: string\n mode: FieldMode\n /** 視覺外殼透傳(2026-05-05)。default = 含 border+bg;naked = cell-as-input(edit×naked 自畫 border-based state machine;display/readonly/disabled×naked 用 transparent border 由 host cell 供邊框);`bare` 2026-07-09 退役;naked 2026-07-14 型別收窄至\n * FieldVariantInternal(@internal)— 公開 `<Field variant>` 只收 default,故本欄位維持 FieldVariant。\n * child Field control 自動繼承,per-control prop override 可覆寫。詳 field-types.ts。 */\n variant: FieldVariant\n disabled: boolean\n required: boolean\n invalid: boolean\n size: FieldSize\n orientation: FieldOrientation\n controlLayout: FieldControlLayout\n hasFieldWrapper: true\n /** 2026-05-12 Stream C Cluster B fix:surface 上下文(form / toolbar / table-cell)\n * 從 host(Field wrapper / DataTable cell registry)propagate 給 child controls。\n * Optional — 未設視同 `'form'`(backward compat)。 */\n surface?: FieldSurface\n}\n\nexport const FieldContext = React.createContext<FieldContextValue | null>(null)\n\n/** 讓 primitive(Checkbox/Switch/Radio/Button/Input 等)讀 Field context */\nexport function useFieldContext(): FieldContextValue | null {\n return React.useContext(FieldContext)\n}\n\n/**\n * Surface context — **獨立於 FieldContext** 的 surface signal(2026-05-12 v2 fix)。\n *\n * 原 v1 設計(`FieldSurfaceProvider` 透過 FieldContext.Provider 注入)誤把 stub\n * `hasFieldWrapper: true` 傳給沒有真正 Field wrapper 的場景(例 DataTable cell 內 standalone\n * DatePicker),導致 cells 誤以為有 Field 父 → 視覺 regression(2026-05-12 visual-audit 抓 3\n * stories 超過 baseline budget:DatePicker range-picker / FileViewer open-snapshot /\n * DataTable pinned-columns)。\n *\n * v2 修:**完全分離** FieldSurfaceContext;FieldContext 不被污染;non-FieldWrapper hosts\n * 只能影響 surface signal,不能假冒 Field wrapper 身份。\n */\nconst FieldSurfaceContext = React.createContext<FieldSurface | null>(null)\n\n/**\n * Helper: 取當前 surface,fall back 'form'。\n *\n * Stream C Cluster B canonical entry point — Field family controls 用此 helper 替代\n * 散落的 `variant === 'naked'` cell heuristic。對齊 codex Q2 verdict「surface context」\n * canonical(替代「inCellContext per-prop」反 pattern)。\n *\n * 優先順序:FieldSurfaceContext(host-provided) → FieldContext.surface(Field-wrapper-provided)\n * → fallback 'form'。\n */\nexport function useFieldSurface(): FieldSurface {\n const surfaceCtx = React.useContext(FieldSurfaceContext)\n if (surfaceCtx) return surfaceCtx\n const fieldCtx = React.useContext(FieldContext)\n return fieldCtx?.surface ?? 'form'\n}\n\n/**\n * Standalone FieldSurface provider — 給 host(non-Field-wrapper, 如 DataTable cell)\n * **僅**設 surface signal,**不**污染 FieldContext。\n *\n * Usage(per cell-registry.tsx,2026-05-12):\n * <FieldSurfaceProvider surface=\"table-cell\"><Field control /></FieldSurfaceProvider>\n *\n * 與 FieldContext.Provider 差別:\n * - FieldContext.Provider — 完整 Field wrapper 用,provides mode/variant/disabled/...\n * - FieldSurfaceProvider — host **僅**注 surface signal,Field controls 透過\n * `useFieldSurface()` 讀;`useFieldContext()` 仍 returns null(沒被假冒)。\n */\nexport function FieldSurfaceProvider({\n surface,\n children,\n}: {\n surface: FieldSurface\n children: React.ReactNode\n}): React.ReactElement {\n return React.createElement(FieldSurfaceContext.Provider, { value: surface }, children)\n}\n\n/**\n * 2026-05-13 (c) scroll-defer perf SSOT(per codex DataTable perf brief V3 + user 拍板\n * 「Roadmap still >50ms 進 (c)」):\n * Host(DataTable)在 virtualizer.isScrolling=true 期間 wrap children with `TableScrollProvider`,\n * 重 cell 組件(Avatar HoverCard / PeoplePicker / etc.)讀此 signal 跳過昂貴 subtree(如 HoverCard\n * Portal + useDocumentTheme observer),scroll 結束 unset → 重渲染為完整 affordance。\n *\n * 對齊 codex Profile Plan step 6(Scripting vs Layout 判分):scrolling 期間 cell 渲染壓縮 →\n * 240ms/frame catastrophe → close to 50ms target。世界級對齊:AG Grid `deferRender` for slow\n * React cell components / MUI X DataGrid scroll-defer hover affordance。\n *\n * 不放在 FieldSurfaceContext 內(避免 value object identity drift on scroll → 全 cell 重渲)\n * — 用獨立 Context,value = boolean primitive(stable when unchanged)。\n */\nconst TableScrollContext = React.createContext<boolean>(false)\n\nexport function useTableIsScrolling(): boolean {\n return React.useContext(TableScrollContext)\n}\n\nexport function TableScrollProvider({\n isScrolling,\n children,\n}: {\n isScrolling: boolean\n children: React.ReactNode\n}): React.ReactElement {\n return React.createElement(TableScrollContext.Provider, { value: isScrolling }, children)\n}\n\n/**\n * Surface size signal — 獨立於 FieldContext + FieldSurfaceContext 的純視覺 size context(2026-06-08)。\n * 讓 host(DataTable cell substrate)把「這片 surface 的 density size」propagate 給 child Field controls,\n * 未來新 cell 漏傳 size prop 也自動繼承 → 字級一致(根治 StringCell/NumberCell 漏傳 size class)。\n *\n * 不放進 FieldSurfaceContext value(避免 string→object identity drift on scroll → 全 cell 重渲,\n * 同 TableScrollContext L119-121 canonical)— 獨立 Context,value = FieldSize primitive(stable when unchanged)。\n * 絕不污染 FieldContext:不碰 hasFieldWrapper/mode/invalid/disabled,useFieldContext() 在 cell 內仍 null。\n */\nconst FieldSurfaceSizeContext = React.createContext<FieldSize | null>(null)\n\n/**\n * Resolve Field control size — 控件用此 helper 取代散落的 `sizeProp ?? fieldCtx?.size ?? 'md'`。\n * 優先序:prop(caller 顯式)> FieldContext.size(真 <Field> wrapper)> surface-size(host 如 cell)> 'md'。\n * 真 <Field> 永遠勝 host surface(安全序);cell 無 Field wrapper(fieldCtx=null)→ 自動接 surface-size,\n * 漏傳 size 的新 cell 也字級一致。\n */\nexport function useResolvedFieldSize<T extends string = FieldSize>(sizeProp?: T | null, fallback?: T): T {\n const fieldCtx = React.useContext(FieldContext)\n const surfaceSize = React.useContext(FieldSurfaceSizeContext)\n // generic T(預設 FieldSize)讓非-input 控件(SegmentedControl/Rating 的 'xs'|'sm'|'md'|'lg' 超集、各自 fallback)\n // 也走同一 SSOT resolution。fieldCtx.size/surfaceSize 為 FieldSize(T 的子集)→ widen cast 安全。\n // fallback 未傳預設 'md'(input-class 控件向後相容);Rating 傳 'xs'、SegmentedControl 傳 'md'。\n return (sizeProp ?? (fieldCtx?.size as T | undefined) ?? (surfaceSize as T | undefined) ?? fallback ?? ('md' as T))\n}\n\n/**\n * Resolve Field control 的 **disabled** — 取代散落的 `disabledProp ?? fieldCtx?.disabled`(2026-06-08 SSOT)。\n * 優先序:顯式 prop > FieldContext.disabled(`<Field disabled>` / `<Field mode=\"disabled\">`)> false。\n * **caller 必傳「未預設」的原始 prop**(沒傳 = undefined),否則 `disabled=false` 預設會吃掉 `??` fallback。\n * DataTable cell 無 FieldContext(fieldCtx=null)→ 回 prop 或 false,行為與現況完全一致(inert)。\n */\nexport function useResolvedFieldDisabled(disabledProp?: boolean | null): boolean {\n const fieldCtx = React.useContext(FieldContext)\n return disabledProp ?? fieldCtx?.disabled ?? false\n}\n\n/**\n * Resolve Field control 的 **mode**(display / readonly / disabled / edit)— 2026-06-08 SSOT,統一兩派散落:\n * 舊 Input 派 `modeProp ?? fieldCtx?.mode ?? (...)` → `<Field disabled>` 時 ctx.mode 仍 'edit',漏 disabled chrome。\n * 舊 picker 派 `disabled ? 'disabled' : mode`(mode 預設 'edit')→ 完全不讀 fieldCtx.mode,`<Field mode=\"display\">` 失效。\n * 統一優先序(world-class:MUI FormControl disabled 完整 cascade + 顯式 prop 永遠最優先):\n * 1. 顯式 mode prop(caller / DataTable cell 的 displayOrDisabled)→ **永遠最優先**,故表格等顯式場景 inert\n * 2. 有效 disabled(prop 或 `<Field disabled>`)→ 'disabled'(完整 disabled chrome)\n * 3. FieldContext.mode(`<Field mode=\"display\"/\"readonly\">`)→ 讓 mode cascade 真正生效\n * 4. 本地 readOnly → 'readonly'\n * 5. 'edit'\n * cell:mode prop 必有 → step 1 命中、fieldCtx=null → 完全 inert(Δ=0)。`disabled` 傳已 resolve 的 boolean 或未預設 prop。\n */\nexport function useResolvedFieldMode({\n mode,\n disabled,\n readOnly,\n}: {\n mode?: FieldMode | null\n disabled?: boolean | null\n readOnly?: boolean\n}): FieldMode {\n const fieldCtx = React.useContext(FieldContext)\n if (mode) return mode\n if ((disabled ?? fieldCtx?.disabled) === true) return 'disabled'\n if (fieldCtx?.mode) return fieldCtx.mode\n if (readOnly) return 'readonly'\n return 'edit'\n}\n\n/**\n * Resolve Field control 的 **variant**(default / naked 視覺外殼;`bare` 2026-07-09 退役)— 2026-06-08 SSOT。\n * 優先序:顯式 prop > FieldContext.variant > 'default'。與既有各控件公式一字不差(Δ=0),統一以利 gate 強制。\n * 2026-07-14 API 策展 E:入參 / 回傳 widen 為 FieldVariantInternal — naked 僅 DataTable cell-registry\n * 經 @internal 通道傳入(公開 prop 型別 = FieldVariant,不含 naked)。\n */\nexport function useResolvedFieldVariant(variantProp?: FieldVariantInternal | null): FieldVariantInternal {\n const fieldCtx = React.useContext(FieldContext)\n return variantProp ?? fieldCtx?.variant ?? 'default'\n}\n\n/**\n * Resolve Field control 的 **error/invalid** — 取代散落的 `errorProp || (fieldCtx?.invalid ?? false)`(2026-06-08 SSOT)。\n * 自身 error prop **或** FieldContext.invalid(`<Field invalid>`)任一為真即 error。與既有公式一致(Δ=0)。\n */\nexport function useResolvedFieldInvalid(errorProp?: boolean): boolean {\n const fieldCtx = React.useContext(FieldContext)\n return Boolean(errorProp) || (fieldCtx?.invalid ?? false)\n}\n\n/**\n * Host(non-Field-wrapper,如 DataTable cell)注 surface density size 給 child Field controls。\n * 僅注 size 視覺訊號,不污染 FieldContext。Usage:cell-registry buildCellWithSurface。\n */\nexport function FieldSurfaceSizeProvider({\n size,\n children,\n}: {\n size: FieldSize\n children: React.ReactNode\n}): React.ReactElement {\n return React.createElement(FieldSurfaceSizeContext.Provider, { value: size }, children)\n}\n\n/**\n * Table-cell 可編輯訊號(2026-07-08 user 拍板)— 獨立於 FieldSurfaceContext / FieldSurfaceSizeContext\n * 的純 boolean context,由 host(DataTable cell registry)注入該 cell 是否可編輯,讓 useFieldEmptyDisplay\n * 分流「可編輯 cell 空 display → 空白」vs「不可編輯 cell 空 display → '-'」。\n * value = boolean primitive(stable when unchanged),不破壞 cell memo identity(同 TableScrollContext /\n * FieldSurfaceSizeContext L119-121 canonical);絕不污染 FieldContext(useFieldContext() 在 cell 內仍 null)。\n */\nconst FieldSurfaceEditableContext = React.createContext<boolean>(false)\n\nexport function FieldSurfaceEditableProvider({\n isEditable,\n children,\n}: {\n isEditable: boolean\n children: React.ReactNode\n}): React.ReactElement {\n return React.createElement(FieldSurfaceEditableContext.Provider, { value: isEditable }, children)\n}\n\n/**\n * 空值顯示分流(2026-07-08 user 拍板,verbatim「...都用\"-\"...我從頭到尾哪裡有說要用全形的」):\n *\n * | 情境 | 空值顯示 |\n * |------------------------------------------------|----------|\n * | table-cell **可編輯** 的 display 靜止態 | **空白 `''`**(不佔位,affordance = hover outline)|\n * | table-cell **不可編輯**(readonly cell) | **半形 `-`** |\n * | standalone display / readonly / form / toolbar | **半形 `-`** |\n *\n * 收斂式:`surface==='table-cell' && isEditable ? '' : EMPTY_DISPLAY`。\n * 可編輯 form / edit 輸入框走 native placeholder(不經此 hook)。boolean → unchecked / disabled →\n * 同上文字 + text-fg-disabled(M24),各控件自理。\n * 世界級對照:table-cell blank = MUI X / AG Grid / Ant core / Notion / Airtable grid 域共識;\n * 非 table `-` = Ant ProTable `columnEmptyText`(見 field-wrapper.tsx EMPTY_DISPLAY 註)。\n * SSOT 條文 → field-controls.spec.md「null / undefined 值」;全 Field family display/readonly/disabled\n * 空值渲染必經此 hook,禁直接引 EMPTY_DISPLAY 常數(genre 分流會漏)。\n */\nexport function useFieldEmptyDisplay(): string {\n const surface = useFieldSurface()\n const isEditable = React.useContext(FieldSurfaceEditableContext)\n return surface === 'table-cell' && isEditable ? '' : EMPTY_DISPLAY\n}\n\n/**\n * 空值符號「-」顏色分流(2026-07-09 user 拍板 verbatim「「-」代表的是不可編輯只拿來供檢視的值\n * 所以應該跟readonly 的value同樣顏色吧」):\n *\n * 「-」是「不可編輯、供檢視的值」→ 同 readonly value 色 = `text-foreground`\n * (field-wrapper.tsx base `text-foreground`),**非** `text-fg-muted`(muted 是「可編輯 placeholder\n * 提示」的裝飾語意;空值符號不是提示、是被檢視的值狀態)。\n * disabled 態維持 `text-fg-disabled`(M24 disabled 顯著性 > foreground/muted,不可被蓋)。\n *\n * SSOT:全 Field family display/readonly/disabled 空值 span 消費此 helper,傳入已 resolve 的\n * resolvedMode。純函式(非 hook)—— resolvedMode 已由 useResolvedFieldMode 解析(含控件自身\n * disabled prop,context hook 讀不到);故以 resolvedMode 為入參,可條件呼叫、不受 Rules of Hooks 限制。\n * 世界級對照:Ant read-only / Carbon read-only value = 正常前景色;placeholder 才 muted。\n */\nexport function fieldEmptyColorClass(resolvedMode: FieldMode): string {\n return resolvedMode === 'disabled' ? 'text-fg-disabled' : 'text-foreground'\n}\n"],"names":[],"mappings":";;AAiDO,MAAM,eAAe,MAAM,cAAwC,IAAI;AAGvE,SAAS,kBAA4C;AAC1D,SAAO,MAAM,WAAW,YAAY;AACtC;AAcA,MAAM,sBAAsB,MAAM,cAAmC,IAAI;AAYlE,SAAS,kBAAgC;AAC9C,QAAM,aAAa,MAAM,WAAW,mBAAmB;AACvD,MAAI,WAAY,QAAO;AACvB,QAAM,WAAW,MAAM,WAAW,YAAY;AAC9C,UAAO,qCAAU,YAAW;AAC9B;AAcO,SAAS,qBAAqB;AAAA,EACnC;AAAA,EACA;AACF,GAGuB;AACrB,SAAO,MAAM,cAAc,oBAAoB,UAAU,EAAE,OAAO,QAAA,GAAW,QAAQ;AACvF;AAgBA,MAAM,qBAAqB,MAAM,cAAuB,KAAK;AAEtD,SAAS,sBAA+B;AAC7C,SAAO,MAAM,WAAW,kBAAkB;AAC5C;AAEO,SAAS,oBAAoB;AAAA,EAClC;AAAA,EACA;AACF,GAGuB;AACrB,SAAO,MAAM,cAAc,mBAAmB,UAAU,EAAE,OAAO,YAAA,GAAe,QAAQ;AAC1F;AAWA,MAAM,0BAA0B,MAAM,cAAgC,IAAI;AAQnE,SAAS,qBAAmD,UAAqB,UAAiB;AACvG,QAAM,WAAW,MAAM,WAAW,YAAY;AAC9C,QAAM,cAAc,MAAM,WAAW,uBAAuB;AAI5D,SAAQ,aAAa,qCAAU,SAA2B,eAAiC,YAAa;AAC1G;AAQO,SAAS,yBAAyB,cAAwC;AAC/E,QAAM,WAAW,MAAM,WAAW,YAAY;AAC9C,SAAO,iBAAgB,qCAAU,aAAY;AAC/C;AAcO,SAAS,qBAAqB;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AACF,GAIc;AACZ,QAAM,WAAW,MAAM,WAAW,YAAY;AAC9C,MAAI,KAAM,QAAO;AACjB,OAAK,aAAY,qCAAU,eAAc,KAAM,QAAO;AACtD,MAAI,qCAAU,KAAM,QAAO,SAAS;AACpC,MAAI,SAAU,QAAO;AACrB,SAAO;AACT;AAQO,SAAS,wBAAwB,aAAiE;AACvG,QAAM,WAAW,MAAM,WAAW,YAAY;AAC9C,SAAO,gBAAe,qCAAU,YAAW;AAC7C;AAMO,SAAS,wBAAwB,WAA8B;AACpE,QAAM,WAAW,MAAM,WAAW,YAAY;AAC9C,SAAO,QAAQ,SAAS,OAAM,qCAAU,YAAW;AACrD;AAMO,SAAS,yBAAyB;AAAA,EACvC;AAAA,EACA;AACF,GAGuB;AACrB,SAAO,MAAM,cAAc,wBAAwB,UAAU,EAAE,OAAO,KAAA,GAAQ,QAAQ;AACxF;AASA,MAAM,8BAA8B,MAAM,cAAuB,KAAK;AAE/D,SAAS,6BAA6B;AAAA,EAC3C;AAAA,EACA;AACF,GAGuB;AACrB,SAAO,MAAM,cAAc,4BAA4B,UAAU,EAAE,OAAO,WAAA,GAAc,QAAQ;AAClG;AAmBO,SAAS,uBAA+B;AAC7C,QAAM,UAAU,gBAAA;AAChB,QAAM,aAAa,MAAM,WAAW,2BAA2B;AAC/D,SAAO,YAAY,gBAAgB,aAAa,KAAK;AACvD;AAgBO,SAAS,qBAAqB,cAAiC;AACpE,SAAO,iBAAiB,aAAa,qBAAqB;AAC5D;"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type * as React from 'react';
|
|
2
|
+
export interface EditSettleKeyOptions {
|
|
3
|
+
/** Enter(非組字中)→ 呼叫。已 preventDefault。 */
|
|
4
|
+
onCommit: (e: React.KeyboardEvent) => void;
|
|
5
|
+
/** Escape(非組字中)→ 呼叫。已 preventDefault。 */
|
|
6
|
+
onCancel: (e: React.KeyboardEvent) => void;
|
|
7
|
+
/**
|
|
8
|
+
* Enter 是否 = commit。預設 true。
|
|
9
|
+
* 多行(Textarea)場景設 false:plain Enter = 換行(不攔,交還 Textarea 預設),
|
|
10
|
+
* Cmd/Ctrl+Enter = commit(**本 handler 內建**,consumer 不需另接 capture handler),
|
|
11
|
+
* 其餘 commit 走 blur。
|
|
12
|
+
*/
|
|
13
|
+
commitOnEnter?: boolean;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* edit-in-place 鍵盤結算 handler(SSOT)。IME 組字 guard 為第一道:中文/日文選字的 Enter 是
|
|
17
|
+
* 組字確認、Esc 是取消組字,非 commit/cancel 意圖 —— 無 guard 會誤送半截組字。`isComposing` 為主,
|
|
18
|
+
* `keyCode === 229` 補 Safari / 舊 Chrome(該分支不 emit isComposing)。
|
|
19
|
+
*/
|
|
20
|
+
export declare function makeEditSettleKeyHandler(opts: EditSettleKeyOptions): (e: React.KeyboardEvent) => void;
|
|
21
|
+
//# sourceMappingURL=field-edit-keys.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"field-edit-keys.d.ts","sourceRoot":"","sources":["../../../src/components/Field/field-edit-keys.ts"],"names":[],"mappings":"AAyBA,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAA;AAEnC,MAAM,WAAW,oBAAoB;IACnC,wCAAwC;IACxC,QAAQ,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,aAAa,KAAK,IAAI,CAAA;IAC1C,yCAAyC;IACzC,QAAQ,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,aAAa,KAAK,IAAI,CAAA;IAC1C;;;;;OAKG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;CACxB;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,oBAAoB,IAEzD,GAAG,KAAK,CAAC,aAAa,UAiB/B"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
function makeEditSettleKeyHandler(opts) {
|
|
2
|
+
const commitOnEnter = opts.commitOnEnter ?? true;
|
|
3
|
+
return (e) => {
|
|
4
|
+
if (e.nativeEvent.isComposing || e.nativeEvent.keyCode === 229) return;
|
|
5
|
+
if (e.key === "Escape") {
|
|
6
|
+
e.preventDefault();
|
|
7
|
+
opts.onCancel(e);
|
|
8
|
+
} else if (e.key === "Enter") {
|
|
9
|
+
if (commitOnEnter || e.metaKey || e.ctrlKey) {
|
|
10
|
+
e.preventDefault();
|
|
11
|
+
opts.onCommit(e);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export {
|
|
17
|
+
makeEditSettleKeyHandler
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=field-edit-keys.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"field-edit-keys.js","sources":["../../../src/components/Field/field-edit-keys.ts"],"sourcesContent":["// @internal — DS-internal 單元(edit-in-place 鍵盤結算 SSOT);consumer 用 InlineEdit / DataTable,不直用。\n// ── 消費的 SSOT ──\n// (本檔即是被消費的 SSOT;不 import 其他 canonical)\n//\n// ── 為什麼存在 ──\n// 「就地編輯(edit-in-place)」的鍵盤結算契約 —— Enter=commit / Esc=cancel + **中文 IME 組字 guard** ——\n// 原本在兩處各寫一份、且**已分歧**:\n// - DataTable `cell-registry.tsx` makeKeyHandler:有 IME guard(2026-07-05 D4 fix)\n// - InlineEdit `inline-edit.tsx`:**無 IME guard** → 中文使用者按 Enter 確認選字時,會把半截組字\n// 提交並退出 edit(Esc 則丟棄整個 draft)= latent bug。\n// 2026-07-09 edit-in-place SSOT 研究(6-agent + 世界級)結論:**不抽大的共用狀態機**(兩 host 的 draft\n// 擁有權 / state locus / focus model 根本不同,強抽違 M21 + 破壞表格虛擬捲動效能),**但抽這一片**——\n// 唯一在兩處逐字重複、且現在分歧、且不碰 draft/state/focus 的純鍵盤結算邏輯。抽出後 InlineEdit 順手\n// 得到 IME guard(修 bug)。對齊世界級「controls + 型別 registry 共用,orchestration 各自」split。\n//\n// **不含**:blur=commit(各 host 的 blur 語意不同)、finalizedRef 單次結算 guard(InlineEdit 專屬,\n// 防 blur-after-Enter 雙 commit)、draft 管理(host-specific)。這些刻意留在各 host。\n//\n// ── 新增 edit-in-place host 的規矩(2026-07-10)──\n// 之後任何新的「就地編輯」host(第 3 個以上),**Enter/Esc/Cmd·Ctrl+Enter/IME 組字結算一律消費本\n// helper**,禁再手刻 `isComposing || keyCode===229` guard 或 Enter/Esc dispatch —— 現有 3 host\n// (cell string / cell number / InlineEdit multiline)已全數收斂於此,手刻 = drift 回頭路(正是 2026-07\n// 之前 InlineEdit 漏 IME guard 的病根)。單行傳 `commitOnEnter` 預設;多行傳 `commitOnEnter:false`\n// (本 helper 內建 Cmd/Ctrl+Enter=commit)。此為文件層 SSOT 規矩,不另設 hook(host 少 + 避治理膨脹)。\n\nimport type * as React from 'react'\n\nexport interface EditSettleKeyOptions {\n /** Enter(非組字中)→ 呼叫。已 preventDefault。 */\n onCommit: (e: React.KeyboardEvent) => void\n /** Escape(非組字中)→ 呼叫。已 preventDefault。 */\n onCancel: (e: React.KeyboardEvent) => void\n /**\n * Enter 是否 = commit。預設 true。\n * 多行(Textarea)場景設 false:plain Enter = 換行(不攔,交還 Textarea 預設),\n * Cmd/Ctrl+Enter = commit(**本 handler 內建**,consumer 不需另接 capture handler),\n * 其餘 commit 走 blur。\n */\n commitOnEnter?: boolean\n}\n\n/**\n * edit-in-place 鍵盤結算 handler(SSOT)。IME 組字 guard 為第一道:中文/日文選字的 Enter 是\n * 組字確認、Esc 是取消組字,非 commit/cancel 意圖 —— 無 guard 會誤送半截組字。`isComposing` 為主,\n * `keyCode === 229` 補 Safari / 舊 Chrome(該分支不 emit isComposing)。\n */\nexport function makeEditSettleKeyHandler(opts: EditSettleKeyOptions) {\n const commitOnEnter = opts.commitOnEnter ?? true\n return (e: React.KeyboardEvent) => {\n // IME 組字 guard(見檔頭)—— 對齊 cell-registry 既有寫法 + data-table nav handler。\n if (e.nativeEvent.isComposing || (e.nativeEvent as { keyCode?: number }).keyCode === 229) return\n if (e.key === 'Escape') {\n e.preventDefault()\n opts.onCancel(e)\n } else if (e.key === 'Enter') {\n // 單行(commitOnEnter):Enter = commit。\n // 多行(commitOnEnter:false):plain Enter = 換行(不攔,交還 Textarea 預設),\n // Cmd/Ctrl+Enter = commit —— 此契約由本 SSOT 擁有,3 個 consumer\n // (cell string/number + InlineEdit multiline)不再各自手刻 capture handler。\n if (commitOnEnter || e.metaKey || e.ctrlKey) {\n e.preventDefault()\n opts.onCommit(e)\n }\n }\n }\n}\n"],"names":[],"mappings":"AA8CO,SAAS,yBAAyB,MAA4B;AACnE,QAAM,gBAAgB,KAAK,iBAAiB;AAC5C,SAAO,CAAC,MAA2B;AAEjC,QAAI,EAAE,YAAY,eAAgB,EAAE,YAAqC,YAAY,IAAK;AAC1F,QAAI,EAAE,QAAQ,UAAU;AACtB,QAAE,eAAA;AACF,WAAK,SAAS,CAAC;AAAA,IACjB,WAAW,EAAE,QAAQ,SAAS;AAK5B,UAAI,iBAAiB,EAAE,WAAW,EAAE,SAAS;AAC3C,UAAE,eAAA;AACF,aAAK,SAAS,CAAC;AAAA,MACjB;AAAA,IACF;AAAA,EACF;AACF;"}
|
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
export type FieldMode = 'edit' | 'display' | 'readonly' | 'disabled';
|
|
2
|
-
export type FieldVariant = 'default'
|
|
2
|
+
export type FieldVariant = 'default';
|
|
3
|
+
/**
|
|
4
|
+
* @internal Substrate union — 公開 FieldVariant + `naked`(cell-as-input)。
|
|
5
|
+
* 2026-07-14 API 策展 E(user 拍板「全部收窄」):naked 從公開 union 拆出,型別層防 consumer
|
|
6
|
+
* `<Input variant="naked">` 繞過 canonical chrome / focus。內部實作(field-wrapper cva、各控件
|
|
7
|
+
* useResolvedFieldVariant 分支)吃本型別;唯一合法 naked 傳入點 = DataTable cell-registry
|
|
8
|
+
* (field-controls.spec.md「軸二 variant」明文)。
|
|
9
|
+
*/
|
|
10
|
+
export type FieldVariantInternal = FieldVariant | 'naked';
|
|
11
|
+
/**
|
|
12
|
+
* @internal 型別層 naked 通道 — 把 Field control 的公開 `variant?: FieldVariant` widen 為
|
|
13
|
+
* FieldVariantInternal(純型別、零 runtime;元件 runtime 本就支援 naked)。消費者:DataTable
|
|
14
|
+
* cell-registry(naked 起點)+ PeoplePicker(wrapper forward 給 Select / Combobox,M30 全 surface
|
|
15
|
+
* forward)。保留原 call signature 與 statics(`Pick<C, keyof C>`),widen 方向與函式參數
|
|
16
|
+
* contravariance 相容,故單次 assertion 可過(非 `as unknown as` 雙跳,型別仍受檢)。
|
|
17
|
+
*/
|
|
18
|
+
export type WithFieldVariantInternal<C> = C extends (props: infer P) => infer R ? ((props: Omit<P, 'variant'> & {
|
|
19
|
+
variant?: FieldVariantInternal;
|
|
20
|
+
}) => R) & Pick<C, keyof C> : never;
|
|
21
|
+
export type FieldWidth = 'fill' | 'hug';
|
|
3
22
|
/** CommandList 最小高度 = field-height × rows + 16px(CommandGroup py-2 上下 padding) */
|
|
4
23
|
export declare function getMenuListMinHeight(size: string, rows?: number): string;
|
|
5
24
|
//# sourceMappingURL=field-types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"field-types.d.ts","sourceRoot":"","sources":["../../../src/components/Field/field-types.ts"],"names":[],"mappings":"AAkBA,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,SAAS,GAAG,UAAU,GAAG,UAAU,CAAA;
|
|
1
|
+
{"version":3,"file":"field-types.d.ts","sourceRoot":"","sources":["../../../src/components/Field/field-types.ts"],"names":[],"mappings":"AAkBA,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,SAAS,GAAG,UAAU,GAAG,UAAU,CAAA;AA6BpE,MAAM,MAAM,YAAY,GAAG,SAAS,CAAA;AAEpC;;;;;;GAMG;AACH,MAAM,MAAM,oBAAoB,GAAG,YAAY,GAAG,OAAO,CAAA;AAEzD;;;;;;GAMG;AACH,MAAM,MAAM,wBAAwB,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,MAAM,CAAC,KAAK,MAAM,CAAC,GAC3E,CAAC,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,EAAE,SAAS,CAAC,GAAG;IAAE,OAAO,CAAC,EAAE,oBAAoB,CAAA;CAAE,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,GAC1F,KAAK,CAAA;AAgBT,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,KAAK,CAAA;AAYvC,kFAAkF;AAClF,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,MAAU,GAAG,MAAM,CAG3E"}
|