@qijenchen/design-system 0.1.0-beta.85 → 0.1.0-beta.86
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/AGENTS.md +210 -0
- package/CLAUDE.md +17 -191
- package/cli-init.mjs +30 -2
- package/dist/components/Accordion/accordion.d.ts +2 -2
- package/dist/components/Accordion/accordion.d.ts.map +1 -1
- package/dist/components/Accordion/accordion.js.map +1 -1
- package/dist/components/Avatar/avatar.js +1 -1
- package/dist/components/Avatar/avatar.js.map +1 -1
- package/dist/components/BulkActionBar/bulk-action-bar.d.ts +1 -1
- package/dist/components/BulkActionBar/bulk-action-bar.d.ts.map +1 -1
- package/dist/components/BulkActionBar/bulk-action-bar.js +3 -3
- package/dist/components/BulkActionBar/bulk-action-bar.js.map +1 -1
- package/dist/components/Button/button-group.d.ts +2 -2
- package/dist/components/Button/button-group.d.ts.map +1 -1
- package/dist/components/Button/button-group.js +2 -2
- package/dist/components/Button/button-group.js.map +1 -1
- package/dist/components/Button/button.d.ts +2 -1
- package/dist/components/Button/button.d.ts.map +1 -1
- package/dist/components/Button/button.js +1 -1
- package/dist/components/Button/button.js.map +1 -1
- package/dist/components/Carousel/carousel.d.ts +14 -8
- package/dist/components/Carousel/carousel.d.ts.map +1 -1
- package/dist/components/Carousel/carousel.js +64 -37
- package/dist/components/Carousel/carousel.js.map +1 -1
- package/dist/components/Checkbox/checkbox-group.d.ts.map +1 -1
- package/dist/components/Checkbox/checkbox-group.js +21 -14
- package/dist/components/Checkbox/checkbox-group.js.map +1 -1
- package/dist/components/Checkbox/checkbox.d.ts +2 -2
- package/dist/components/Checkbox/checkbox.d.ts.map +1 -1
- package/dist/components/Checkbox/checkbox.js +49 -10
- package/dist/components/Checkbox/checkbox.js.map +1 -1
- package/dist/components/Combobox/combobox.d.ts +15 -6
- package/dist/components/Combobox/combobox.d.ts.map +1 -1
- package/dist/components/Combobox/combobox.js +13 -7
- package/dist/components/Combobox/combobox.js.map +1 -1
- package/dist/components/Command/command.d.ts.map +1 -1
- package/dist/components/Command/command.js +11 -2
- package/dist/components/Command/command.js.map +1 -1
- package/dist/components/DataTable/cell-registry.d.ts +5 -5
- package/dist/components/DataTable/cell-registry.d.ts.map +1 -1
- package/dist/components/DataTable/cell-registry.js +41 -38
- package/dist/components/DataTable/cell-registry.js.map +1 -1
- package/dist/components/DataTable/column-types.d.ts +1 -17
- 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.map +1 -1
- package/dist/components/DataTable/data-table-column-visibility-panel.js +71 -64
- package/dist/components/DataTable/data-table-column-visibility-panel.js.map +1 -1
- package/dist/components/DataTable/data-table-filter-panel.d.ts +0 -2
- package/dist/components/DataTable/data-table-filter-panel.d.ts.map +1 -1
- package/dist/components/DataTable/data-table-filter-panel.js +3 -4
- package/dist/components/DataTable/data-table-filter-panel.js.map +1 -1
- package/dist/components/DataTable/data-table-interaction-layer.d.ts +2 -2
- package/dist/components/DataTable/data-table-interaction-layer.js.map +1 -1
- package/dist/components/DataTable/data-table-sort-manager.js +2 -2
- package/dist/components/DataTable/data-table-sort-manager.js.map +1 -1
- package/dist/components/DataTable/data-table.d.ts +1 -1
- package/dist/components/DataTable/data-table.d.ts.map +1 -1
- package/dist/components/DataTable/data-table.js +44 -32
- package/dist/components/DataTable/data-table.js.map +1 -1
- package/dist/components/DatePicker/date-picker.d.ts +3 -3
- package/dist/components/DatePicker/date-picker.js +4 -4
- package/dist/components/DatePicker/date-picker.js.map +1 -1
- package/dist/components/DescriptionList/description-list.d.ts +3 -3
- package/dist/components/DescriptionList/description-list.d.ts.map +1 -1
- package/dist/components/DescriptionList/description-list.js +6 -6
- package/dist/components/DescriptionList/description-list.js.map +1 -1
- package/dist/components/Dialog/dialog.d.ts +2 -2
- package/dist/components/Dialog/dialog.d.ts.map +1 -1
- package/dist/components/Dialog/dialog.js.map +1 -1
- package/dist/components/DropdownMenu/dropdown-menu.d.ts +8 -3
- package/dist/components/DropdownMenu/dropdown-menu.d.ts.map +1 -1
- package/dist/components/DropdownMenu/dropdown-menu.js +7 -7
- package/dist/components/DropdownMenu/dropdown-menu.js.map +1 -1
- package/dist/components/Field/field-context.d.ts +16 -10
- package/dist/components/Field/field-context.d.ts.map +1 -1
- package/dist/components/Field/field-context.js.map +1 -1
- package/dist/components/Field/field-types.d.ts +2 -2
- 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 +20 -4
- package/dist/components/Field/field-wrapper.d.ts.map +1 -1
- package/dist/components/Field/field-wrapper.js +30 -31
- package/dist/components/Field/field-wrapper.js.map +1 -1
- package/dist/components/Field/field.d.ts +1 -1
- package/dist/components/Field/field.d.ts.map +1 -1
- package/dist/components/Field/field.js +25 -3
- package/dist/components/Field/field.js.map +1 -1
- package/dist/components/Field/use-form-validation.d.ts +1 -1
- package/dist/components/Field/use-form-validation.js.map +1 -1
- package/dist/components/FileItem/file-item.d.ts.map +1 -1
- package/dist/components/FileItem/file-item.js +2 -5
- package/dist/components/FileItem/file-item.js.map +1 -1
- package/dist/components/FileUpload/file-upload.d.ts.map +1 -1
- package/dist/components/FileUpload/file-upload.js +5 -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 +2 -1
- package/dist/components/FileViewer/file-viewer.js.map +1 -1
- package/dist/components/HoverCard/hover-card.d.ts +2 -2
- package/dist/components/HoverCard/hover-card.d.ts.map +1 -1
- package/dist/components/HoverCard/hover-card.js +4 -2
- package/dist/components/HoverCard/hover-card.js.map +1 -1
- package/dist/components/InlineEdit/inline-edit.d.ts +23 -16
- package/dist/components/InlineEdit/inline-edit.d.ts.map +1 -1
- package/dist/components/InlineEdit/inline-edit.js +104 -62
- package/dist/components/InlineEdit/inline-edit.js.map +1 -1
- package/dist/components/Input/input.d.ts +1 -1
- package/dist/components/Input/input.d.ts.map +1 -1
- package/dist/components/Input/input.js +6 -6
- package/dist/components/Input/input.js.map +1 -1
- package/dist/components/LinkInput/link-input.d.ts +5 -5
- package/dist/components/LinkInput/link-input.d.ts.map +1 -1
- package/dist/components/LinkInput/link-input.js +3 -3
- package/dist/components/LinkInput/link-input.js.map +1 -1
- package/dist/components/Notice/notice.d.ts +17 -1
- package/dist/components/Notice/notice.d.ts.map +1 -1
- package/dist/components/Notice/notice.js +7 -1
- package/dist/components/Notice/notice.js.map +1 -1
- package/dist/components/NumberInput/number-input.d.ts +1 -1
- package/dist/components/NumberInput/number-input.d.ts.map +1 -1
- package/dist/components/NumberInput/number-input.js +12 -1
- package/dist/components/NumberInput/number-input.js.map +1 -1
- package/dist/components/PeoplePicker/avatar-stack-overflow.d.ts +1 -1
- package/dist/components/PeoplePicker/avatar-stack-overflow.js.map +1 -1
- package/dist/components/PeoplePicker/people-picker.d.ts +5 -5
- package/dist/components/PeoplePicker/people-picker.d.ts.map +1 -1
- package/dist/components/PeoplePicker/people-picker.js +7 -5
- package/dist/components/PeoplePicker/people-picker.js.map +1 -1
- package/dist/components/PeoplePicker/person-display.d.ts +1 -1
- package/dist/components/PeoplePicker/person-display.js.map +1 -1
- package/dist/components/ProfileCard/profile-card.d.ts +1 -1
- package/dist/components/ProfileCard/profile-card.js.map +1 -1
- package/dist/components/RadioGroup/radio-group.d.ts +3 -3
- package/dist/components/RadioGroup/radio-group.d.ts.map +1 -1
- package/dist/components/RadioGroup/radio-group.js +1 -1
- package/dist/components/RadioGroup/radio-group.js.map +1 -1
- package/dist/components/Rating/rating.d.ts.map +1 -1
- package/dist/components/Rating/rating.js +4 -0
- package/dist/components/Rating/rating.js.map +1 -1
- package/dist/components/ScrollArea/scroll-area.d.ts +3 -2
- package/dist/components/ScrollArea/scroll-area.d.ts.map +1 -1
- package/dist/components/ScrollArea/scroll-area.js.map +1 -1
- package/dist/components/SegmentedControl/segmented-control.d.ts +4 -3
- package/dist/components/SegmentedControl/segmented-control.d.ts.map +1 -1
- package/dist/components/SegmentedControl/segmented-control.js.map +1 -1
- package/dist/components/Select/select.d.ts +16 -6
- package/dist/components/Select/select.d.ts.map +1 -1
- package/dist/components/Select/select.js +17 -5
- package/dist/components/Select/select.js.map +1 -1
- package/dist/components/SelectMenu/select-menu.d.ts +17 -1
- package/dist/components/SelectMenu/select-menu.d.ts.map +1 -1
- package/dist/components/SelectMenu/select-menu.js +17 -3
- package/dist/components/SelectMenu/select-menu.js.map +1 -1
- package/dist/components/SelectionControl/boolean-value.d.ts +1 -1
- package/dist/components/SelectionControl/boolean-value.js.map +1 -1
- package/dist/components/Sheet/sheet.d.ts +2 -2
- package/dist/components/Sheet/sheet.d.ts.map +1 -1
- package/dist/components/Sheet/sheet.js.map +1 -1
- package/dist/components/Sidebar/sidebar.d.ts +1 -1
- package/dist/components/Sidebar/sidebar.js +2 -2
- 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 +6 -3
- package/dist/components/Slider/slider.js.map +1 -1
- package/dist/components/Steps/steps.d.ts +6 -4
- package/dist/components/Steps/steps.d.ts.map +1 -1
- package/dist/components/Steps/steps.js +19 -5
- package/dist/components/Steps/steps.js.map +1 -1
- package/dist/components/Switch/switch.d.ts +4 -4
- package/dist/components/Switch/switch.d.ts.map +1 -1
- package/dist/components/Switch/switch.js +3 -1
- package/dist/components/Switch/switch.js.map +1 -1
- package/dist/components/Tabs/tabs.d.ts +1 -1
- package/dist/components/Tabs/tabs.d.ts.map +1 -1
- package/dist/components/Tabs/tabs.js +73 -23
- package/dist/components/Tabs/tabs.js.map +1 -1
- package/dist/components/Tag/tag.d.ts +3 -1
- package/dist/components/Tag/tag.d.ts.map +1 -1
- package/dist/components/Tag/tag.js +2 -2
- package/dist/components/Tag/tag.js.map +1 -1
- package/dist/components/Textarea/textarea.d.ts +4 -4
- package/dist/components/Textarea/textarea.d.ts.map +1 -1
- package/dist/components/Textarea/textarea.js +15 -15
- package/dist/components/Textarea/textarea.js.map +1 -1
- package/dist/components/TimePicker/time-picker.d.ts +4 -4
- package/dist/components/TimePicker/time-picker.d.ts.map +1 -1
- package/dist/components/TimePicker/time-picker.js +8 -5
- 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 +27 -23
- package/dist/components/Tooltip/tooltip.js.map +1 -1
- package/dist/components/TreeView/tree-view.d.ts.map +1 -1
- package/dist/components/TreeView/tree-view.js +16 -0
- package/dist/components/TreeView/tree-view.js.map +1 -1
- package/dist/index.d.ts +7 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -13
- package/dist/index.js.map +1 -1
- package/dist/lib/drag-visual.d.ts +2 -2
- package/dist/lib/drag-visual.js.map +1 -1
- package/dist/lib/utils.d.ts +1 -0
- package/dist/lib/utils.d.ts.map +1 -1
- package/dist/lib/utils.js +21 -1
- package/dist/lib/utils.js.map +1 -1
- package/dist/patterns/element-anatomy/item-anatomy.d.ts +2 -0
- package/dist/patterns/element-anatomy/item-anatomy.d.ts.map +1 -1
- package/dist/patterns/element-anatomy/item-anatomy.js +2 -2
- package/dist/patterns/element-anatomy/item-anatomy.js.map +1 -1
- package/dist/tokens/categorical-color.d.ts +3 -3
- package/dist/tokens/categorical-color.d.ts.map +1 -1
- package/dist/tokens/categorical-color.js +2 -2
- package/dist/tokens/categorical-color.js.map +1 -1
- package/ds-canonical/fork/AGENTS.md +213 -0
- package/ds-canonical/fork/codex/agents/skills/independent-review/SKILL.md +33 -0
- package/ds-canonical/fork/codex/hooks.json +16 -0
- package/ds-canonical/fork/governance.lock +22 -7
- package/ds-canonical/fork/hooks/check_ds_anchor_preflight.sh +22 -1
- package/ds-canonical/fork/hooks/inject_deploy_url_after_push.sh +14 -2
- package/ds-canonical/fork/manifest.json +10 -1
- package/ds-canonical/fork/preamble.md +3 -2
- package/ds-canonical/fork/skills/product-ui-audit/SKILL.md +7 -7
- package/ds-canonical/fork/skills/ux-audit/SKILL.md +1 -1
- package/ds-canonical/fork/skills/visual-audit/SKILL.md +0 -1
- package/ds-canonical/hooks/check_codex_brief_invariants.sh +9 -3
- package/ds-canonical/hooks/check_ds_anchor_preflight.sh +22 -1
- package/ds-canonical/hooks/check_overlay_open_focus_escape_probe.sh +11 -2
- package/ds-canonical/hooks/check_plugin_fork_health.sh +17 -1
- package/ds-canonical/hooks/check_post_main_ssot_propagate.sh +21 -4
- package/ds-canonical/hooks/inject_deploy_url_after_push.sh +14 -2
- package/ds-canonical/hooks/session_start_governance_check.sh +10 -2
- package/ds-canonical/references/certified-surfaces.md +20 -0
- package/ds-canonical/references/preflight-gate-baseline.json +5 -0
- package/ds-canonical/references/principle-dim-map.json +20 -2
- package/ds-canonical/references/props-naming.md +2 -1
- package/ds-canonical/references/ssot-index.md +2 -2
- package/ds-canonical/references/story-baseline-registry.json +2 -2
- package/ds-canonical/rules/story-rules.md +1 -1
- package/ds-canonical/rules/ui-development.md +2 -1
- package/ds-canonical/skills/codex-collab/SKILL.md +7 -7
- package/ds-canonical/skills/codex-collab/references/brief-template.md +5 -1
- package/ds-canonical/skills/component-quality-gate/SKILL.md +1 -1
- package/ds-canonical/skills/deep-audit-cross-codex/SKILL.md +5 -5
- package/ds-canonical/skills/design-system-audit/SKILL.md +5 -5
- package/ds-canonical/skills/product-ui-audit/SKILL.md +7 -7
- package/ds-canonical/skills/ux-audit/SKILL.md +1 -1
- package/ds-canonical/skills/visual-audit/SKILL.md +0 -1
- package/ds-story-manifest.json +21 -21
- package/llms-full.txt +3 -3
- package/llms.txt +1 -1
- package/package.json +2 -1
- package/src/components/Accordion/accordion.anatomy.stories.tsx +11 -11
- package/src/components/Accordion/accordion.principles.stories.tsx +21 -6
- package/src/components/Accordion/accordion.spec.md +10 -1
- package/src/components/Accordion/accordion.stories.tsx +10 -5
- package/src/components/Accordion/accordion.tsx +7 -2
- package/src/components/Alert/alert.principles.stories.tsx +2 -8
- package/src/components/AppShell/app-shell.anatomy.stories.tsx +19 -13
- package/src/components/AppShell/app-shell.principles.stories.tsx +21 -21
- package/src/components/AppShell/app-shell.spec.md +4 -0
- package/src/components/AppShell/app-shell.stories.tsx +4 -5
- package/src/components/Avatar/avatar.tsx +1 -1
- package/src/components/Badge/badge.spec.md +2 -2
- package/src/components/Breadcrumb/breadcrumb.anatomy.stories.tsx +7 -3
- package/src/components/Breadcrumb/breadcrumb.principles.stories.tsx +4 -1
- package/src/components/Breadcrumb/breadcrumb.stories.tsx +1 -1
- package/src/components/BulkActionBar/bulk-action-bar.anatomy.stories.tsx +6 -6
- package/src/components/BulkActionBar/bulk-action-bar.principles.stories.tsx +18 -6
- package/src/components/BulkActionBar/bulk-action-bar.spec.md +15 -5
- package/src/components/BulkActionBar/bulk-action-bar.stories.tsx +8 -24
- package/src/components/BulkActionBar/bulk-action-bar.tsx +4 -4
- package/src/components/Button/button-group.tsx +4 -4
- package/src/components/Button/button.anatomy.stories.tsx +26 -7
- package/src/components/Button/button.principles.stories.tsx +1 -1
- package/src/components/Button/button.spec.md +2 -2
- package/src/components/Button/button.stories.tsx +22 -28
- package/src/components/Button/button.tsx +2 -1
- package/src/components/Calendar/calendar.anatomy.stories.tsx +1 -1
- package/src/components/Calendar/calendar.spec.md +1 -1
- package/src/components/Carousel/carousel.spec.md +17 -1
- package/src/components/Carousel/carousel.tsx +93 -42
- package/src/components/Checkbox/checkbox-group.tsx +26 -15
- package/src/components/Checkbox/checkbox.anatomy.stories.tsx +3 -3
- package/src/components/Checkbox/checkbox.spec.md +12 -3
- package/src/components/Checkbox/checkbox.stories.tsx +4 -4
- package/src/components/Checkbox/checkbox.tsx +79 -19
- package/src/components/Coachmark/coachmark.anatomy.stories.tsx +5 -5
- package/src/components/Coachmark/coachmark.principles.stories.tsx +1 -1
- package/src/components/Combobox/combobox.anatomy.stories.tsx +11 -4
- package/src/components/Combobox/combobox.principles.stories.tsx +10 -11
- package/src/components/Combobox/combobox.spec.md +6 -2
- package/src/components/Combobox/combobox.stories.tsx +9 -36
- package/src/components/Combobox/combobox.tsx +42 -27
- package/src/components/Command/command.spec.md +3 -3
- package/src/components/Command/command.tsx +9 -2
- package/src/components/DataTable/cell-registry.tsx +75 -76
- package/src/components/DataTable/column-types.ts +4 -17
- package/src/components/DataTable/data-table-column-visibility-panel.tsx +20 -7
- package/src/components/DataTable/data-table-filter-panel.tsx +6 -6
- package/src/components/DataTable/data-table-interaction-layer.tsx +3 -3
- package/src/components/DataTable/data-table-sort-manager.tsx +2 -2
- package/src/components/DataTable/data-table.anatomy.stories.tsx +25 -24
- package/src/components/DataTable/data-table.css +2 -3
- package/src/components/DataTable/data-table.principles.stories.tsx +17 -11
- package/src/components/DataTable/data-table.spec.md +7 -7
- package/src/components/DataTable/data-table.stories.tsx +39 -29
- package/src/components/DataTable/data-table.tsx +90 -67
- package/src/components/DataTable/filter-operators.spec.md +2 -0
- package/src/components/DateGrid/date-grid.spec.md +1 -1
- package/src/components/DatePicker/date-picker.anatomy.stories.tsx +9 -9
- package/src/components/DatePicker/date-picker.principles.stories.tsx +4 -4
- package/src/components/DatePicker/date-picker.spec.md +9 -9
- package/src/components/DatePicker/date-picker.stories.tsx +9 -9
- package/src/components/DatePicker/date-picker.tsx +13 -13
- package/src/components/DescriptionList/description-list.anatomy.stories.tsx +5 -5
- package/src/components/DescriptionList/description-list.principles.stories.tsx +2 -2
- package/src/components/DescriptionList/description-list.spec.md +12 -12
- package/src/components/DescriptionList/description-list.stories.tsx +3 -3
- package/src/components/DescriptionList/description-list.tsx +10 -10
- package/src/components/Dialog/dialog.tsx +4 -1
- package/src/components/DropdownMenu/dropdown-menu.tsx +27 -14
- package/src/components/Field/field-context.ts +17 -11
- package/src/components/Field/field-controls.spec.md +44 -37
- package/src/components/Field/field-types.ts +14 -12
- package/src/components/Field/field-wrapper.tsx +59 -43
- package/src/components/Field/field.anatomy.stories.tsx +6 -6
- package/src/components/Field/field.principles.stories.tsx +5 -28
- package/src/components/Field/field.spec.md +4 -4
- package/src/components/Field/field.stories.tsx +103 -72
- package/src/components/Field/field.tsx +36 -4
- package/src/components/Field/form-validation.spec.md +2 -0
- package/src/components/Field/use-form-validation.ts +1 -1
- package/src/components/FieldControlGroup/field-control-group.principles.stories.tsx +3 -3
- package/src/components/FieldControlGroup/field-control-group.spec.md +3 -3
- package/src/components/FileItem/file-item.tsx +10 -8
- package/src/components/FileUpload/file-upload.principles.stories.tsx +7 -7
- package/src/components/FileUpload/file-upload.spec.md +3 -2
- package/src/components/FileUpload/file-upload.tsx +5 -1
- package/src/components/FileViewer/file-viewer.anatomy.stories.tsx +1 -1
- package/src/components/FileViewer/file-viewer.principles.stories.tsx +1 -1
- package/src/components/FileViewer/file-viewer.spec.md +1 -1
- package/src/components/FileViewer/file-viewer.stories.tsx +1 -1
- package/src/components/FileViewer/file-viewer.tsx +3 -2
- package/src/components/HoverCard/hover-card.anatomy.stories.tsx +6 -4
- package/src/components/HoverCard/hover-card.principles.stories.tsx +6 -1
- package/src/components/HoverCard/hover-card.spec.md +2 -2
- package/src/components/HoverCard/hover-card.tsx +4 -2
- package/src/components/InlineEdit/inline-edit.anatomy.stories.tsx +184 -0
- package/src/components/InlineEdit/inline-edit.principles.stories.tsx +142 -0
- package/src/components/InlineEdit/inline-edit.spec.md +71 -46
- package/src/components/InlineEdit/inline-edit.stories.tsx +20 -19
- package/src/components/InlineEdit/inline-edit.tsx +211 -125
- package/src/components/Input/input.anatomy.stories.tsx +15 -15
- package/src/components/Input/input.principles.stories.tsx +3 -3
- package/src/components/Input/input.spec.md +4 -4
- package/src/components/Input/input.stories.tsx +3 -3
- package/src/components/Input/input.tsx +13 -13
- package/src/components/LinkInput/link-input.anatomy.stories.tsx +2 -2
- package/src/components/LinkInput/link-input.principles.stories.tsx +1 -1
- package/src/components/LinkInput/link-input.spec.md +6 -2
- package/src/components/LinkInput/link-input.stories.tsx +8 -8
- package/src/components/LinkInput/link-input.tsx +21 -19
- package/src/components/Notice/notice.principles.stories.tsx +1 -1
- package/src/components/Notice/notice.tsx +8 -2
- package/src/components/NumberInput/number-input.anatomy.stories.tsx +6 -6
- package/src/components/NumberInput/number-input.principles.stories.tsx +4 -4
- package/src/components/NumberInput/number-input.spec.md +6 -6
- package/src/components/NumberInput/number-input.stories.tsx +4 -4
- package/src/components/NumberInput/number-input.tsx +17 -5
- package/src/components/OverflowIndicator/overflow-indicator.anatomy.stories.tsx +8 -4
- package/src/components/OverflowIndicator/overflow-indicator.principles.stories.tsx +6 -7
- package/src/components/OverflowIndicator/overflow-indicator.spec.md +1 -1
- package/src/components/OverflowIndicator/overflow-indicator.stories.tsx +6 -30
- package/src/components/PeoplePicker/avatar-stack-overflow.ts +1 -1
- package/src/components/PeoplePicker/people-picker.anatomy.stories.tsx +13 -10
- package/src/components/PeoplePicker/people-picker.principles.stories.tsx +5 -1
- package/src/components/PeoplePicker/people-picker.stories.tsx +5 -5
- package/src/components/PeoplePicker/people-picker.tsx +39 -20
- package/src/components/PeoplePicker/person-display.tsx +3 -3
- package/src/components/Popover/popover.anatomy.stories.tsx +10 -7
- package/src/components/Popover/popover.principles.stories.tsx +49 -28
- package/src/components/Popover/popover.spec.md +4 -2
- package/src/components/ProfileCard/profile-card.anatomy.stories.tsx +1 -1
- package/src/components/ProfileCard/profile-card.tsx +1 -1
- package/src/components/ProgressBar/progress-bar.anatomy.stories.tsx +14 -13
- package/src/components/ProgressBar/progress-bar.principles.stories.tsx +8 -23
- package/src/components/ProgressBar/progress-bar.stories.tsx +8 -2
- package/src/components/README.md +1 -0
- package/src/components/RadioGroup/radio-group.anatomy.stories.tsx +8 -3
- package/src/components/RadioGroup/radio-group.stories.tsx +3 -3
- package/src/components/RadioGroup/radio-group.tsx +19 -14
- package/src/components/Rating/rating.anatomy.stories.tsx +1 -1
- package/src/components/Rating/rating.principles.stories.tsx +3 -3
- package/src/components/Rating/rating.spec.md +2 -2
- package/src/components/Rating/rating.stories.tsx +4 -18
- package/src/components/Rating/rating.tsx +5 -1
- package/src/components/ScrollArea/scroll-area.principles.stories.tsx +1 -1
- package/src/components/ScrollArea/scroll-area.spec.md +1 -1
- package/src/components/ScrollArea/scroll-area.tsx +5 -2
- package/src/components/SegmentedControl/segmented-control.principles.stories.tsx +4 -1
- package/src/components/SegmentedControl/segmented-control.spec.md +5 -10
- package/src/components/SegmentedControl/segmented-control.tsx +6 -3
- package/src/components/Select/select.anatomy.stories.tsx +13 -7
- package/src/components/Select/select.principles.stories.tsx +3 -3
- package/src/components/Select/select.spec.md +9 -1
- package/src/components/Select/select.stories.tsx +41 -27
- package/src/components/Select/select.tsx +44 -26
- package/src/components/SelectMenu/select-menu.anatomy.stories.tsx +44 -26
- package/src/components/SelectMenu/select-menu.principles.stories.tsx +0 -1
- package/src/components/SelectMenu/select-menu.stories.tsx +7 -19
- package/src/components/SelectMenu/select-menu.tsx +38 -5
- package/src/components/SelectionControl/boolean-value.tsx +2 -2
- package/src/components/SelectionControl/selection-item.anatomy.stories.tsx +1 -1
- package/src/components/SelectionControl/selection-item.principles.stories.tsx +4 -4
- package/src/components/Separator/separator.principles.stories.tsx +1 -1
- package/src/components/Sheet/sheet.principles.stories.tsx +5 -3
- package/src/components/Sheet/sheet.tsx +4 -1
- package/src/components/Sidebar/sidebar.tsx +1 -1
- package/src/components/Slider/slider.anatomy.stories.tsx +5 -4
- package/src/components/Slider/slider.principles.stories.tsx +6 -6
- package/src/components/Slider/slider.spec.md +13 -1
- package/src/components/Slider/slider.stories.tsx +44 -32
- package/src/components/Slider/slider.tsx +13 -4
- package/src/components/Steps/steps.anatomy.stories.tsx +7 -7
- package/src/components/Steps/steps.principles.stories.tsx +1 -1
- package/src/components/Steps/steps.spec.md +4 -2
- package/src/components/Steps/steps.stories.tsx +4 -56
- package/src/components/Steps/steps.tsx +31 -7
- package/src/components/Switch/switch.anatomy.stories.tsx +3 -3
- package/src/components/Switch/switch.principles.stories.tsx +1 -1
- package/src/components/Switch/switch.spec.md +4 -4
- package/src/components/Switch/switch.stories.tsx +3 -3
- package/src/components/Switch/switch.tsx +16 -10
- package/src/components/Tabs/tabs.anatomy.stories.tsx +6 -0
- package/src/components/Tabs/tabs.principles.stories.tsx +10 -2
- package/src/components/Tabs/tabs.spec.md +5 -2
- package/src/components/Tabs/tabs.stories.tsx +36 -44
- package/src/components/Tabs/tabs.tsx +101 -36
- package/src/components/Tag/tag.anatomy.stories.tsx +2 -2
- package/src/components/Tag/tag.principles.stories.tsx +4 -4
- package/src/components/Tag/tag.spec.md +3 -1
- package/src/components/Tag/tag.tsx +5 -3
- package/src/components/Textarea/textarea.anatomy.stories.tsx +6 -6
- package/src/components/Textarea/textarea.spec.md +5 -5
- package/src/components/Textarea/textarea.tsx +24 -24
- package/src/components/TimePicker/time-picker.anatomy.stories.tsx +12 -11
- package/src/components/TimePicker/time-picker.principles.stories.tsx +37 -44
- package/src/components/TimePicker/time-picker.spec.md +6 -5
- package/src/components/TimePicker/time-picker.stories.tsx +3 -3
- package/src/components/TimePicker/time-picker.tsx +24 -14
- package/src/components/Toast/toast.principles.stories.tsx +1 -1
- package/src/components/Toast/toast.stories.tsx +1 -1
- package/src/components/Tooltip/tooltip.anatomy.stories.tsx +15 -6
- package/src/components/Tooltip/tooltip.principles.stories.tsx +4 -4
- package/src/components/Tooltip/tooltip.spec.md +9 -3
- package/src/components/Tooltip/tooltip.stories.tsx +1 -1
- package/src/components/Tooltip/tooltip.tsx +12 -2
- package/src/components/TreeView/tree-view.anatomy.stories.tsx +16 -14
- package/src/components/TreeView/tree-view.principles.stories.tsx +44 -11
- package/src/components/TreeView/tree-view.spec.md +8 -6
- package/src/components/TreeView/tree-view.stories.tsx +9 -43
- package/src/components/TreeView/tree-view.tsx +36 -0
- package/src/index.ts +13 -10
- package/src/lib/drag-visual.ts +2 -2
- package/src/lib/utils.ts +19 -0
- package/src/patterns/element-anatomy/inline-action.spec.md +1 -1
- package/src/patterns/element-anatomy/item-anatomy.tsx +4 -2
- package/src/patterns/header-canonical/header-canonical.spec.md +9 -0
- package/src/patterns/header-canonical/header-canonical.stories.tsx +31 -5
- package/src/tokens/categorical-color.ts +5 -5
- package/src/tokens/color/color.spec.md +4 -4
- package/src/tokens/color/primitives.css +9 -2
- package/src/tokens/opacity/opacity.spec.md +1 -1
- package/src/tokens/orphan-tokens.spec.md +1 -1
- package/src/tokens/typography/typography.spec.md +2 -0
- package/src/tokens/uiSize/uiSize.css +5 -0
- package/src/tokens/uiSize/uiSize.spec.md +1 -1
|
@@ -3,49 +3,41 @@ import * as React from "react";
|
|
|
3
3
|
import { cn } from "../../lib/utils.js";
|
|
4
4
|
import { Input } from "../Input/input.js";
|
|
5
5
|
import { Textarea } from "../Textarea/textarea.js";
|
|
6
|
-
import {
|
|
7
|
-
import { fieldDisplayTextClass } from "../Field/field-wrapper.js";
|
|
6
|
+
import { useResolvedFieldSize, useFieldContext, FieldSurfaceProvider } from "../Field/field-context.js";
|
|
7
|
+
import { fieldViewGeometry, fieldDisplayTextClass } from "../Field/field-wrapper.js";
|
|
8
8
|
import { makeEditSettleKeyHandler } from "../Field/field-edit-keys.js";
|
|
9
|
-
const inlineEditReadStyles = cva(
|
|
10
|
-
// relative(疊隱形 Pressable)/ border-transparent(預留 1px 邊框盒 → 態切換零位移;focus 才染色)/
|
|
11
|
-
// px-[--field-px](文字↔底色水平內距 = Field 內距 SSOT,read=edit x 位置零跳)/ hover 底色 tint /
|
|
12
|
-
// [&:has(button:focus-visible)]:border-primary(鍵盤 focus = Field edit 藍框語言,非 Button ring)。
|
|
13
|
-
"relative flex rounded-md border border-transparent px-[var(--field-px)] transition-colors duration-150 hover:bg-neutral-hover [&:has(button:focus-visible)]:border-primary",
|
|
14
|
-
{
|
|
15
|
-
variants: {
|
|
16
|
-
size: {
|
|
17
|
-
sm: "min-h-[var(--field-height-sm)]",
|
|
18
|
-
md: "min-h-[var(--field-height-md)]",
|
|
19
|
-
lg: "min-h-[var(--field-height-lg)]"
|
|
20
|
-
},
|
|
21
|
-
multiline: {
|
|
22
|
-
false: "items-center",
|
|
23
|
-
true: "items-start py-1.5"
|
|
24
|
-
}
|
|
25
|
-
},
|
|
26
|
-
// 預設 sm + 單行(2026-07-09 user 拍板):read 態無邊框、視覺 = 純文字,尺寸過大會讓版面鬆散。
|
|
27
|
-
defaultVariants: { size: "sm", multiline: false }
|
|
28
|
-
}
|
|
29
|
-
);
|
|
30
9
|
function InlineEditImpl({
|
|
31
10
|
value,
|
|
32
11
|
onCommit,
|
|
33
12
|
renderEdit,
|
|
34
13
|
renderRead,
|
|
35
14
|
multiline = false,
|
|
15
|
+
editable = true,
|
|
36
16
|
label,
|
|
37
17
|
placeholder,
|
|
38
|
-
size
|
|
18
|
+
size: sizeProp,
|
|
39
19
|
as = "span",
|
|
40
20
|
readClassName,
|
|
41
21
|
className
|
|
42
22
|
}, ref) {
|
|
23
|
+
const size = useResolvedFieldSize(sizeProp, "sm");
|
|
24
|
+
const fieldCtx = useFieldContext();
|
|
25
|
+
const orientation = (fieldCtx == null ? void 0 : fieldCtx.orientation) ?? "vertical";
|
|
43
26
|
const [editing, setEditing] = React.useState(false);
|
|
44
27
|
const [draft, setDraft] = React.useState(value);
|
|
45
28
|
const draftRef = React.useRef(draft);
|
|
46
29
|
const finalizedRef = React.useRef(false);
|
|
47
30
|
const returnFocusRef = React.useRef(false);
|
|
48
31
|
const readButtonRef = React.useRef(null);
|
|
32
|
+
const editContainerRef = React.useRef(null);
|
|
33
|
+
const setEditContainerRef = React.useCallback(
|
|
34
|
+
(node) => {
|
|
35
|
+
editContainerRef.current = node;
|
|
36
|
+
if (typeof ref === "function") ref(node);
|
|
37
|
+
else if (ref) ref.current = node;
|
|
38
|
+
},
|
|
39
|
+
[ref]
|
|
40
|
+
);
|
|
49
41
|
const setDraftValue = React.useCallback((next) => {
|
|
50
42
|
draftRef.current = next;
|
|
51
43
|
setDraft(next);
|
|
@@ -55,19 +47,32 @@ function InlineEditImpl({
|
|
|
55
47
|
setDraftValue(value);
|
|
56
48
|
setEditing(true);
|
|
57
49
|
}, [value, setDraftValue]);
|
|
58
|
-
const commit = React.useCallback(
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
50
|
+
const commit = React.useCallback(
|
|
51
|
+
(returnFocus) => {
|
|
52
|
+
if (finalizedRef.current) return;
|
|
53
|
+
finalizedRef.current = true;
|
|
54
|
+
returnFocusRef.current = returnFocus;
|
|
55
|
+
setEditing(false);
|
|
56
|
+
if (draftRef.current !== value) onCommit(draftRef.current);
|
|
57
|
+
},
|
|
58
|
+
[value, onCommit]
|
|
59
|
+
);
|
|
65
60
|
const cancel = React.useCallback(() => {
|
|
66
61
|
if (finalizedRef.current) return;
|
|
67
62
|
finalizedRef.current = true;
|
|
68
63
|
returnFocusRef.current = true;
|
|
69
64
|
setEditing(false);
|
|
70
65
|
}, []);
|
|
66
|
+
const handleEditBlur = React.useCallback((e) => {
|
|
67
|
+
const wrapper = e.currentTarget;
|
|
68
|
+
requestAnimationFrame(() => {
|
|
69
|
+
if (finalizedRef.current) return;
|
|
70
|
+
const active = document.activeElement;
|
|
71
|
+
if (active && wrapper.contains(active)) return;
|
|
72
|
+
if (active && active.closest('[data-radix-popper-content-wrapper],[data-radix-select-viewport],[role="listbox"],[role="dialog"]')) return;
|
|
73
|
+
commit(false);
|
|
74
|
+
});
|
|
75
|
+
}, [commit]);
|
|
71
76
|
React.useEffect(() => {
|
|
72
77
|
var _a;
|
|
73
78
|
if (!editing && returnFocusRef.current) {
|
|
@@ -75,14 +80,28 @@ function InlineEditImpl({
|
|
|
75
80
|
(_a = readButtonRef.current) == null ? void 0 : _a.focus();
|
|
76
81
|
}
|
|
77
82
|
}, [editing]);
|
|
83
|
+
React.useEffect(() => {
|
|
84
|
+
if (!editing) return;
|
|
85
|
+
const onPointerDown = (e) => {
|
|
86
|
+
if (finalizedRef.current) return;
|
|
87
|
+
const target = e.target;
|
|
88
|
+
if (!target) return;
|
|
89
|
+
const box = editContainerRef.current;
|
|
90
|
+
if (box && box.contains(target)) return;
|
|
91
|
+
if (target.closest('[data-radix-popper-content-wrapper],[data-radix-select-viewport],[role="listbox"],[role="dialog"]')) return;
|
|
92
|
+
commit(false);
|
|
93
|
+
};
|
|
94
|
+
document.addEventListener("pointerdown", onPointerDown, true);
|
|
95
|
+
return () => document.removeEventListener("pointerdown", onPointerDown, true);
|
|
96
|
+
}, [editing, commit]);
|
|
78
97
|
const isEmpty = value == null || value === "" || Array.isArray(value) && value.length === 0;
|
|
79
98
|
const Tag = as;
|
|
80
|
-
const
|
|
99
|
+
const alignBleed = orientation === "vertical" ? "-mx-[var(--field-px)] w-[calc(100%_+_2_*_var(--field-px))]" : "w-full min-w-0";
|
|
81
100
|
if (editing) {
|
|
82
101
|
const editProps = {
|
|
83
102
|
value: draft,
|
|
84
103
|
onChange: setDraftValue,
|
|
85
|
-
commit,
|
|
104
|
+
commit: () => commit(true),
|
|
86
105
|
cancel,
|
|
87
106
|
autoFocus: true,
|
|
88
107
|
size,
|
|
@@ -92,57 +111,79 @@ function InlineEditImpl({
|
|
|
92
111
|
Textarea,
|
|
93
112
|
{
|
|
94
113
|
autoFocus: true,
|
|
114
|
+
mode: "edit",
|
|
95
115
|
value: draft,
|
|
96
116
|
size,
|
|
97
117
|
"aria-label": label,
|
|
98
118
|
onChange: (e) => setDraftValue(e.target.value),
|
|
99
|
-
onBlur: commit,
|
|
100
|
-
onKeyDown: makeEditSettleKeyHandler({ onCommit: commit, onCancel: cancel, commitOnEnter: false })
|
|
119
|
+
onBlur: () => commit(false),
|
|
120
|
+
onKeyDown: makeEditSettleKeyHandler({ onCommit: () => commit(true), onCancel: cancel, commitOnEnter: false })
|
|
101
121
|
}
|
|
102
122
|
) : /* @__PURE__ */ jsx(
|
|
103
123
|
Input,
|
|
104
124
|
{
|
|
105
125
|
autoFocus: true,
|
|
126
|
+
mode: "edit",
|
|
106
127
|
value: draft,
|
|
107
128
|
size,
|
|
108
129
|
"aria-label": label,
|
|
109
130
|
onChange: (e) => setDraftValue(e.target.value),
|
|
110
|
-
onBlur: commit,
|
|
111
|
-
onKeyDown: makeEditSettleKeyHandler({ onCommit: commit, onCancel: cancel })
|
|
131
|
+
onBlur: () => commit(false),
|
|
132
|
+
onKeyDown: makeEditSettleKeyHandler({ onCommit: () => commit(true), onCancel: cancel })
|
|
112
133
|
}
|
|
113
134
|
);
|
|
114
135
|
const editNode = renderEdit ? renderEdit(editProps) : defaultEditNode;
|
|
115
|
-
return
|
|
136
|
+
return (
|
|
137
|
+
// exit SSOT 雙保險:onBlur(focusout,tab-out / 非 overlay 控件)+ document pointerdown-outside(overlay
|
|
138
|
+
// 點外面 Radix 送焦回 trigger 時 focusout 不觸發之補位)。任何 edit 控件離開 → 回 view。
|
|
139
|
+
// edit 態**不套** surface='inline-edit'(完全繼承原 field,含 tagPadding — Jira 式,user 2026-07-17 拍板)。
|
|
140
|
+
/* @__PURE__ */ jsx("div", { ref: setEditContainerRef, "data-editing": true, className: cn(alignBleed, className), onBlur: handleEditBlur, children: editNode })
|
|
141
|
+
);
|
|
116
142
|
}
|
|
143
|
+
const viewNode = renderRead ? (
|
|
144
|
+
// 格式化 view(consumer 傳 <Control mode="view"> 等)。**對齊 SSOT(2026-07-17)**:委派控件的
|
|
145
|
+
// bare view 一律 0px 左內距(audit 證實 Select/Combobox/DatePicker/TimePicker/PeoplePicker/LinkInput
|
|
146
|
+
// 預設 view 皆裸 span/Tag),故 InlineEdit 用**與純值路徑同一個 `fieldViewGeometry(field-px + min-h +
|
|
147
|
+
// py)`** 包住 renderRead → 委派控件 view 左 px 統一 field-px、高度統一 field-height → 固定
|
|
148
|
+
// `-mx-field-px` 精準抵消 → tag/avatar/值左緣落 label x=0。不碰任何控件 tagPadding(bare view 本就不用)。
|
|
149
|
+
// surface='inline-edit' 保留為 per-control 逃生訊號(若某控件 bare view 有內部 inset 破壞對齊,該控件可
|
|
150
|
+
// 讀 surface 自抑;目前全控件 bare view=0px 不需)。
|
|
151
|
+
/* @__PURE__ */ jsx(FieldSurfaceProvider, { surface: "inline-edit", children: /* @__PURE__ */ jsx("div", { className: fieldViewGeometry(size, multiline), children: renderRead(value) }) })
|
|
152
|
+
) : /* @__PURE__ */ jsx(
|
|
153
|
+
Tag,
|
|
154
|
+
{
|
|
155
|
+
"data-empty": isEmpty,
|
|
156
|
+
className: cn(
|
|
157
|
+
// 純值/標題共用 view 幾何 SSOT(px-field-px + min-h-field + 單行 items-center / 多行 items-start py-2)
|
|
158
|
+
fieldViewGeometry(size, multiline),
|
|
159
|
+
// view 態 typography 預設消費 Field view SSOT(sm/md→text-body,lg→text-body-lg)——
|
|
160
|
+
// plain 用法 view 字級 = edit <Input>(text-body)= <Input mode="view"> 字級,三者一致零跳字。
|
|
161
|
+
fieldDisplayTextClass(size),
|
|
162
|
+
// 多行:保留換行(pre-wrap)+ 長字斷行 —— view 忠實呈現 Textarea 內容(Jira description 類)。
|
|
163
|
+
multiline && "whitespace-pre-wrap break-words",
|
|
164
|
+
readClassName,
|
|
165
|
+
isEmpty && "text-fg-muted"
|
|
166
|
+
),
|
|
167
|
+
children: isEmpty ? placeholder : String(value)
|
|
168
|
+
}
|
|
169
|
+
);
|
|
117
170
|
return /* @__PURE__ */ jsxs(
|
|
118
171
|
"div",
|
|
119
172
|
{
|
|
120
173
|
ref,
|
|
121
|
-
"data-editing":
|
|
122
|
-
|
|
174
|
+
"data-editing": false,
|
|
175
|
+
"data-editable": editable || void 0,
|
|
176
|
+
className: cn(
|
|
177
|
+
"relative flex min-w-0 rounded-md border border-transparent transition-colors duration-150",
|
|
178
|
+
alignBleed,
|
|
179
|
+
// editable 才有 hover 底色 + 鍵盤 focus 藍框(Field focus 語言,非 Button ring);
|
|
180
|
+
// editable=false = 純 view 鎖定,無入口、無藍框、不灰化。
|
|
181
|
+
editable && "hover:bg-neutral-hover [&:has(button:focus-visible)]:border-primary",
|
|
182
|
+
className
|
|
183
|
+
),
|
|
123
184
|
children: [
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
/* @__PURE__ */ jsx("div", { className: "w-full min-w-0", children: renderRead(value) })
|
|
127
|
-
) : /* @__PURE__ */ jsx(
|
|
128
|
-
Tag,
|
|
129
|
-
{
|
|
130
|
-
"data-empty": isEmpty,
|
|
131
|
-
className: cn(
|
|
132
|
-
"w-full min-w-0",
|
|
133
|
-
// read 態 typography 預設消費 Field display SSOT(sm/md→text-body,lg→text-body-lg)——
|
|
134
|
-
// plain 用法 read 字級 = edit <Input>(text-body)= <Input mode="display"> 字級,三者一致零跳字。
|
|
135
|
-
fieldDisplayTextClass(size),
|
|
136
|
-
// 多行:保留換行(pre-wrap)+ 長字斷行 —— read 忠實呈現 Textarea 內容(Jira description 類),
|
|
137
|
-
// 對齊 Textarea mode="display" 的 `whitespace-pre-wrap break-words`。
|
|
138
|
-
multiline && "whitespace-pre-wrap break-words",
|
|
139
|
-
readClassName,
|
|
140
|
-
isEmpty && "text-fg-muted"
|
|
141
|
-
),
|
|
142
|
-
children: isEmpty ? placeholder : String(value)
|
|
143
|
-
}
|
|
144
|
-
),
|
|
145
|
-
/* @__PURE__ */ jsx(
|
|
185
|
+
viewNode,
|
|
186
|
+
editable && /* @__PURE__ */ jsx(
|
|
146
187
|
"button",
|
|
147
188
|
{
|
|
148
189
|
ref: readButtonRef,
|
|
@@ -158,6 +199,7 @@ function InlineEditImpl({
|
|
|
158
199
|
}
|
|
159
200
|
const InlineEdit = React.forwardRef(InlineEditImpl);
|
|
160
201
|
InlineEdit.displayName = "InlineEdit";
|
|
202
|
+
InlineEdit.fieldPreferredSize = "sm";
|
|
161
203
|
export {
|
|
162
204
|
InlineEdit
|
|
163
205
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inline-edit.js","sources":["../../../src/components/InlineEdit/inline-edit.tsx"],"sourcesContent":["// ── 消費的 SSOT ──\n// - components/Field/field-controls.spec.md(「Field 框架地圖」段:InlineEdit = read↔edit 疊在 Field 之上;edit-in-place)\n// - components/Field/field-edit-keys.ts(makeEditSettleKeyHandler = Enter/Esc + 中文 IME guard 結算 SSOT,與 DataTable cell 同源)\n// - components/Input/input.tsx / Textarea/textarea.tsx(預設 renderEdit;不重刻 input chrome)\n// - components/Field/field-context.ts(FieldSize 型別)\n// - tokens/uiSize/uiSize.css(--field-px 水平內距 SSOT / --field-height-{sm,md,lg} read 態高度)\n// - tokens/color/color.spec.md(bg-neutral-hover = hover 回饋)\n// - components/Field/field-wrapper.tsx(read focus = border-primary,消費 Field edit focus 藍框語言;非 Button ring)\n// - 世界級對照(泛型 readView/editView 就地編輯 + read 態底色 tint + 隱形 Pressable):\n// Atlassian inline-edit(readView/editView 兩 render prop + 泛型 <FieldValue>)\n// https://github.com/pioug/atlassian-frontend-mirror/blob/main/design-system/inline-edit/src/types.tsx\n// read-view hover = 背景 tint 非邊框 + 隱形 Pressable 供鍵盤 focus+Enter:\n// https://github.com/pioug/atlassian-frontend-mirror/blob/main/design-system/inline-edit/src/internal/read-view.tsx\n//\n// ── 型別化 read / 多行(2026-07-09 L2,edit-in-place SSOT 研究落地)──\n// InlineEdit 現為泛型 <T>(非只 string)。**格式化 read view 的 SSOT 鐵律**:renderRead 要顯示\n// Tag / 日期 / label 等格式化值時,**渲染對應 Field 控件的 `mode=\"display\"`**(如 `<Select mode=\"display\"\n// display=\"tag\">`),**不可自刻 Tag / 格式化**——「select 值 → Tag」的 SSOT 住在 Select 的 display mode\n// (= DataTable cell 消費的同一份)。這保證 read 與 edit 用同一個控件、格式零分歧。多行(multiline)\n// 供 Jira description 類欄位。\nimport * as React from 'react'\nimport { cn } from '@/lib/utils'\nimport { Input } from '@/design-system/components/Input/input'\nimport { Textarea } from '@/design-system/components/Textarea/textarea'\nimport type { FieldSize } from '@/design-system/components/Field/field-context'\nimport { cva } from 'class-variance-authority'\nimport { fieldDisplayTextClass } from '@/design-system/components/Field/field-wrapper'\nimport { makeEditSettleKeyHandler } from '@/design-system/components/Field/field-edit-keys'\n\n// ── Types ───────────────────────────────────────────────────────────────────\n\n/** renderEdit 回呼收到的受控介面 —— consumer 換任意 Field 控件(Textarea / Select / DatePicker …)接線用 */\nexport interface InlineEditRenderProps<T = string> {\n /** 目前編輯中的草稿值(內部管理,非已 commit 的 value) */\n value: T\n /** 草稿變更 */\n onChange: (next: T) => void\n /** 提交草稿 → onCommit(next) + 回 read 態 */\n commit: () => void\n /** 放棄草稿 → 回 read 態不 commit */\n cancel: () => void\n /** 進 edit 態時自動 focus 控件 */\n autoFocus: boolean\n /** 尺寸(cascade 給控件,與 read 態高度對齊) */\n size: FieldSize\n /** 控件無障礙標籤(= label prop) */\n 'aria-label'?: string\n}\n\ntype ReadTag = 'span' | 'p' | 'div' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'\n\nexport interface InlineEditProps<T = string> {\n /** 已提交的欄位值(read 態顯示;edit 進入時作草稿初值)。泛型:string / number / string[] / PersonValue 等 */\n value: T\n /** 提交回呼 —— blur / Enter 且草稿有變更時觸發 */\n onCommit: (next: T) => void\n /**\n * 自訂 edit 態控件(換 Textarea / Select / DatePicker 等 Field 控件)。\n * 不傳則預設:multiline → `<Textarea>`,否則 `<Input>`(僅 T=string 時預設有效;非 string 值必傳此 prop)。\n */\n renderEdit?: (props: InlineEditRenderProps<T>) => React.ReactNode\n /**\n * 自訂 read 態格式化顯示。不傳則顯示 `String(value)`(空值顯 placeholder)。\n * **SSOT 鐵律**:要顯示 Tag / 日期格式 / option label 等,**渲染對應控件的 `mode=\"display\"`**\n * (如 `renderRead={(v) => <Select mode=\"display\" display=\"tag\" value={v} options={opts} />}`),\n * 不可自刻 —— 格式化 SSOT 住在該控件的 display mode(見檔頭)。\n */\n renderRead?: (value: T) => React.ReactNode\n /** 多行欄位(Jira description 類):read 態換行 + 預設 edit 態用 `<Textarea>`,Enter=換行、Cmd/Ctrl+Enter 或 blur 提交 */\n multiline?: boolean\n /** 欄位人類可讀名稱 —— 組 read 按鈕 aria-label(「編輯 {label}」)+ 控件 aria-label */\n label?: string\n /** 空值時 read 態顯示的提示文字(fg-muted) */\n placeholder?: string\n /** 尺寸 —— read 態最小高度 + 預設 Input 尺寸(與 Field 家族一致) */\n size?: FieldSize\n /** read 態語意標籤(標題場景傳 'h1'/'h2' 維持文件大綱;預設 'span') */\n as?: ReadTag\n /** read 態文字樣式(如標題 `text-h4 font-bold`) */\n readClassName?: string\n /** 外層容器 className */\n className?: string\n}\n\n// ── Read-view geometry cva ────────────────────────────────────────────────────\n// base = 排版結構;size variant = min-height(與 edit 態 Field 高度一致,降低態切換位移)。\n// multiline variant:單行 → `items-center`(文字垂直置中於 field-height);多行 → `items-start py-1.5`\n// (頂對齊 + 上下內距,對齊 Textarea `py-2` 多行閱讀語意,內容換行往下長)。\n// read 態幾何典範對照(M22 cite,2026-07-09 逐行核對原始碼,見檔頭 read-view URL):\n// hover = 底色 tint 非邊框;focus = 實線 solid border;wrapper 恆帶透明 border 預留邊框盒。\nconst inlineEditReadStyles = cva(\n // relative(疊隱形 Pressable)/ border-transparent(預留 1px 邊框盒 → 態切換零位移;focus 才染色)/\n // px-[--field-px](文字↔底色水平內距 = Field 內距 SSOT,read=edit x 位置零跳)/ hover 底色 tint /\n // [&:has(button:focus-visible)]:border-primary(鍵盤 focus = Field edit 藍框語言,非 Button ring)。\n 'relative flex rounded-md border border-transparent px-[var(--field-px)] transition-colors duration-150 hover:bg-neutral-hover [&:has(button:focus-visible)]:border-primary',\n {\n variants: {\n size: {\n sm: 'min-h-[var(--field-height-sm)]',\n md: 'min-h-[var(--field-height-md)]',\n lg: 'min-h-[var(--field-height-lg)]',\n },\n multiline: {\n false: 'items-center',\n true: 'items-start py-1.5',\n },\n },\n // 預設 sm + 單行(2026-07-09 user 拍板):read 態無邊框、視覺 = 純文字,尺寸過大會讓版面鬆散。\n defaultVariants: { size: 'sm', multiline: false },\n },\n)\n\n/**\n * InlineEdit — 就地編輯 primitive(read ↔ edit 二態,泛型 <T>)\n *\n * 互動模型 world-class 對照見檔頭「消費的 SSOT」+ inline-edit.spec.md benchmark 表:\n * - **read 態** = 純文字 / 格式化 renderRead、寬度 fill 容器、透明邊框(預留)→ 視覺 = 純內容\n * - **hover(read)** = 灰色底色 `bg-neutral-hover` + `rounded-md`(**非邊框**)+ cursor\n * - **鍵盤 focus(read)** = 外框染 `border-primary` 藍框(Field focus 語言,非 Button ring)\n * - **click / Enter/Space** = 切 edit 態 = 真正的 Field 控件(border + focus + input);多行走 Textarea\n * - **退出**:blur / Enter → commit;Esc → cancel 還原不 commit;focus 返回 read 按鈕\n * - **鍵盤結算**:消費 `makeEditSettleKeyHandler`(含中文 IME guard,與 DataTable cell 同源)\n *\n * read 態用隱形 Pressable(絕對定位 `<button>` 疊於內容上)—— 讓 `as=\"h1\"` 等標題標籤保留文件大綱\n * a11y,同時提供鍵盤 focus + Enter 進 edit(HTML 內容模型不允許 heading 巢狀於 button,故分離)。\n */\nfunction InlineEditImpl<T = string>(\n {\n value,\n onCommit,\n renderEdit,\n renderRead,\n multiline = false,\n label,\n placeholder,\n size = 'sm',\n as = 'span',\n readClassName,\n className,\n }: InlineEditProps<T>,\n ref: React.Ref<HTMLDivElement>,\n) {\n const [editing, setEditing] = React.useState(false)\n const [draft, setDraft] = React.useState<T>(value)\n // draft 最新值 ref —— commit 從事件 handler 呼叫時避免 stale closure\n const draftRef = React.useRef(draft)\n // 單次結算 guard —— Enter/Esc 已結算後,unmount 觸發的 onBlur 不再重複 commit\n const finalizedRef = React.useRef(false)\n // 鍵盤結算後把 focus 送回 read 按鈕(a11y:焦點不遺失)\n const returnFocusRef = React.useRef(false)\n const readButtonRef = React.useRef<HTMLButtonElement>(null)\n\n const setDraftValue = React.useCallback((next: T) => {\n draftRef.current = next\n setDraft(next)\n }, [])\n\n const enterEdit = React.useCallback(() => {\n finalizedRef.current = false\n setDraftValue(value)\n setEditing(true)\n }, [value, setDraftValue])\n\n const commit = React.useCallback(() => {\n if (finalizedRef.current) return\n finalizedRef.current = true\n returnFocusRef.current = true\n setEditing(false)\n if (draftRef.current !== value) onCommit(draftRef.current)\n }, [value, onCommit])\n\n const cancel = React.useCallback(() => {\n if (finalizedRef.current) return\n finalizedRef.current = true\n returnFocusRef.current = true\n setEditing(false)\n }, [])\n\n // 結算後把 focus 送回 read 按鈕(所有結算路徑含 blur / Enter / Esc 皆 return —— commit/cancel\n // 一律 set returnFocusRef=true;對齊 spec「任一路徑結算後 focus 送回 read 按鈕」L39/L141「焦點不遺失」)\n React.useEffect(() => {\n if (!editing && returnFocusRef.current) {\n returnFocusRef.current = false\n readButtonRef.current?.focus()\n }\n }, [editing])\n\n const isEmpty =\n value == null || (value as unknown) === '' || (Array.isArray(value) && value.length === 0)\n const Tag = as as React.ElementType\n // 外框(read/edit 兩態共用同一幾何 → 保證「底色範圍 = 輸入框範圍」逐 pixel 一致):\n // -mx-[--field-px] 左右各外擴 field-px;w-[calc(100% + 2*--field-px)] 對稱補回 → 兩側等寬填滿。\n const rootClass = cn('-mx-[var(--field-px)] w-[calc(100%_+_2_*_var(--field-px))]', className)\n\n if (editing) {\n const editProps: InlineEditRenderProps<T> = {\n value: draft,\n onChange: setDraftValue,\n commit,\n cancel,\n autoFocus: true,\n size,\n 'aria-label': label,\n }\n // 預設 edit 控件:multiline → Textarea(Enter=換行、Cmd/Ctrl+Enter/blur 提交);否則 Input。\n // 兩者 Enter/Esc 結算都走 makeEditSettleKeyHandler(含 IME guard)。string-only 預設路徑;\n // 非 string 值必傳 renderEdit。\n const defaultEditNode = multiline ? (\n <Textarea\n autoFocus\n value={draft as unknown as string}\n size={size}\n aria-label={label}\n onChange={(e) => setDraftValue(e.target.value as unknown as T)}\n onBlur={commit}\n // 多行:plain Enter=換行、Esc=cancel、Cmd/Ctrl+Enter=commit —— 全走\n // makeEditSettleKeyHandler SSOT(含 IME guard + Cmd/Ctrl+Enter),不再手刻 capture。\n onKeyDown={makeEditSettleKeyHandler({ onCommit: commit, onCancel: cancel, commitOnEnter: false })}\n />\n ) : (\n <Input\n autoFocus\n value={draft as unknown as string}\n size={size}\n aria-label={label}\n onChange={(e) => setDraftValue(e.target.value as unknown as T)}\n onBlur={commit}\n onKeyDown={makeEditSettleKeyHandler({ onCommit: commit, onCancel: cancel })}\n />\n )\n const editNode = renderEdit ? renderEdit(editProps) : defaultEditNode\n return (\n <div ref={ref} data-editing={editing} className={rootClass}>\n {editNode}\n </div>\n )\n }\n\n return (\n <div\n ref={ref}\n data-editing={editing}\n // 外框(rootClass 幾何)+ read chrome(inlineEditReadStyles:border 預留 / px / min-h / hover 底色 / focus 藍框)\n className={cn(rootClass, inlineEditReadStyles({ size, multiline }))}\n >\n {renderRead ? (\n // 格式化 read view(consumer 傳 <Select mode=\"display\"> 等)。invisible Pressable 疊其上供點擊進 edit。\n <div className=\"w-full min-w-0\">{renderRead(value)}</div>\n ) : (\n <Tag\n data-empty={isEmpty}\n className={cn(\n 'w-full min-w-0',\n // read 態 typography 預設消費 Field display SSOT(sm/md→text-body,lg→text-body-lg)——\n // plain 用法 read 字級 = edit <Input>(text-body)= <Input mode=\"display\"> 字級,三者一致零跳字。\n fieldDisplayTextClass(size),\n // 多行:保留換行(pre-wrap)+ 長字斷行 —— read 忠實呈現 Textarea 內容(Jira description 類),\n // 對齊 Textarea mode=\"display\" 的 `whitespace-pre-wrap break-words`。\n multiline && 'whitespace-pre-wrap break-words',\n readClassName,\n isEmpty && 'text-fg-muted',\n )}\n >\n {isEmpty ? placeholder : String(value)}\n </Tag>\n )}\n {/* 隱形 Pressable:提供 click + 鍵盤 Tab focus + Enter/Space 進 edit;透明疊於內容上。\n hover 底色 + focus 藍框(border-primary)由外層 div 承載(Field focus 語言,非 ring),\n 故本 button 只需 outline-none 消除瀏覽器預設外框。 */}\n <button\n ref={readButtonRef}\n type=\"button\"\n aria-label={label ? `編輯 ${label}` : '編輯'}\n onClick={enterEdit}\n className=\"absolute inset-0 cursor-text rounded-md focus-visible:outline-none\"\n />\n </div>\n )\n}\n\n// 泛型 forwardRef:forwardRef 不原生支援泛型,以 cast 還原公開型別(標準 workaround)。\nconst InlineEdit = React.forwardRef(InlineEditImpl) as (<T = string>(\n props: InlineEditProps<T> & { ref?: React.Ref<HTMLDivElement> },\n) => React.ReactElement) & { displayName?: string }\nInlineEdit.displayName = 'InlineEdit'\n\nexport { InlineEdit }\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"inline-edit.js","sources":["../../../src/components/InlineEdit/inline-edit.tsx"],"sourcesContent":["// ── 消費的 SSOT ──\n// - components/Field/field-controls.spec.md(「軸二 就地編輯 host」段:InlineEdit = view↔edit 二態疊在 Field 之上;edit-in-place + editable 閘)\n// - components/Field/field-edit-keys.ts(makeEditSettleKeyHandler = Enter/Esc + 中文 IME guard 結算 SSOT,與 DataTable cell 同源)\n// - components/Field/field-wrapper.tsx(fieldViewGeometry = view 幾何 class SSOT〔純值/標題路徑〕;fieldDisplayTextClass = view 字級)\n// - components/Input/input.tsx / Textarea/textarea.tsx(預設 renderEdit;不重刻 input chrome)\n// - components/Field/field-context.ts(useFieldContext / useResolvedFieldSize;接 size/orientation/labelId cascade)\n// - tokens/uiSize/uiSize.css(--field-px 水平內距 SSOT / --field-height-{sm,md,lg} view 態高度)\n// - tokens/color/color.spec.md(bg-neutral-hover = hover 回饋)\n// - 世界級對照(泛型 readView/editView 就地編輯 + read 態底色 tint + 隱形 Pressable):\n// Atlassian inline-edit(readView/editView 兩 render prop + read wrapper 靠負邊距對齊 edit)\n// https://github.com/pioug/atlassian-frontend-mirror/blob/main/design-system/inline-edit/src/internal/read-view.tsx\n// 就地編輯無 disabled 態(鎖定 = 純 view 無入口、不灰化):MUI X isCellEditable / cellMode\n// https://mui.com/x/react-data-grid/editing/ + AG Grid editable https://www.ag-grid.com/react-data-grid/cell-editing/\n//\n// ── Model A(2026-07-16 round16,user GO)──────────────────────────────────────\n// InlineEdit **不自帶 geometry cva**(消 M17 重複):\n// - 值-格式化(Select→Tag / Date / avatar)→ renderRead 委派 `<Control mode=\"view\">`,幾何 + 內部間距\n// 皆由控件 view×default 提供(read=edit 同一顆 → 天生一致、多 tag/avatar 間距零偏移)。\n// - 純值 / 標題 `<Tag as=\"h1\">` → 套 `fieldViewGeometry(size, multiline)` helper(= view×default 幾何 SSOT)。\n// InlineEdit 本體只給:`-mx`(orientation-aware,拉到欄左緣,= 上方 read-view 負邊距對照)+ hover bg + focus 藍框 + 隱形 Pressable。\n// read↔edit 零跳 = view 與 edit 同一顆控件、只差 chrome。\n// **格式化 read 鐵律**:renderRead 要顯示 Tag/日期/label,**渲染對應控件的 `mode=\"view\"`**\n// (如 `<Select mode=\"view\" display=\"tag\">`),不可自刻 —— 格式化 SSOT 住在該控件的 view mode。\nimport * as React from 'react'\nimport { cn } from '@/lib/utils'\nimport { Input } from '@/design-system/components/Input/input'\nimport { Textarea } from '@/design-system/components/Textarea/textarea'\nimport type { FieldSize } from '@/design-system/components/Field/field-context'\nimport { useFieldContext, useResolvedFieldSize, FieldSurfaceProvider } from '@/design-system/components/Field/field-context'\nimport { fieldDisplayTextClass, fieldViewGeometry } from '@/design-system/components/Field/field-wrapper'\nimport { makeEditSettleKeyHandler } from '@/design-system/components/Field/field-edit-keys'\n\n// ── Types ───────────────────────────────────────────────────────────────────\n\n/** renderEdit 回呼收到的受控介面 —— consumer 換任意 Field 控件(Textarea / Select / DatePicker …)接線用 */\nexport interface InlineEditRenderProps<T = string> {\n /** 目前編輯中的草稿值(內部管理,非已 commit 的 value) */\n value: T\n /** 草稿變更 */\n onChange: (next: T) => void\n /** 提交草稿 → onCommit(next) + 回 view 態 */\n commit: () => void\n /** 放棄草稿 → 回 view 態不 commit */\n cancel: () => void\n /** 進 edit 態時自動 focus 控件 */\n autoFocus: boolean\n /** 尺寸(cascade 給控件,與 view 態高度對齊) */\n size: FieldSize\n /** 控件無障礙標籤(= label prop) */\n 'aria-label'?: string\n}\n\ntype ReadTag = 'span' | 'p' | 'div' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'\n\nexport interface InlineEditProps<T = string> {\n /** 已提交的欄位值(view 態顯示;edit 進入時作草稿初值)。泛型:string / number / string[] / PersonValue 等 */\n value: T\n /** 提交回呼 —— blur / Enter 且草稿有變更時觸發 */\n onCommit: (next: T) => void\n /**\n * 自訂 edit 態控件(換 Textarea / Select / DatePicker 等 Field 控件,必用 `mode=\"edit\"`)。\n * 不傳則預設:multiline → `<Textarea mode=\"edit\">`,否則 `<Input mode=\"edit\">`(僅 T=string 時預設有效;非 string 值必傳此 prop)。\n */\n renderEdit?: (props: InlineEditRenderProps<T>) => React.ReactNode\n /**\n * 自訂 view 態格式化顯示。不傳則顯示 `String(value)`(空值顯 placeholder)。\n * **SSOT 鐵律**:要顯示 Tag / 日期格式 / option label 等,**渲染對應控件的 `mode=\"view\"`**\n * (如 `renderRead={(v) => <Select mode=\"view\" display=\"tag\" value={v} options={opts} />}`),\n * 不可自刻 —— 格式化 + 內部間距 SSOT 住在該控件的 view mode(見檔頭 Model A)。\n */\n renderRead?: (value: T) => React.ReactNode\n /** 多行欄位(Jira description 類):view 態換行 + 預設 edit 態用 `<Textarea>`,Enter=換行、Cmd/Ctrl+Enter 或 blur 提交 */\n multiline?: boolean\n /**\n * 是否可編輯(預設 true)。false → view 態無 hover 入口、無藍框、不可點進 edit(對齊世界級就地編輯\n * **無 disabled 態**,鎖定 = 純 view 無入口、**不灰化**;cite 見檔頭 MUI X / AG Grid URL)。\n * 永久唯讀資料 → 用 `<Control mode=\"view\">` 或此 prop false;不用 disabled。\n */\n editable?: boolean\n /** 欄位人類可讀名稱 —— 組 view 按鈕 aria-label(「編輯 {label}」)+ 控件 aria-label */\n label?: string\n /** 空值時 view 態顯示的提示文字(fg-muted) */\n placeholder?: string\n /** 尺寸 —— view 態最小高度 + 預設 Input 尺寸(不傳則經 useResolvedFieldSize 接 Field size cascade,standalone fallback sm) */\n size?: FieldSize\n /** view 態語意標籤(標題場景傳 'h1'/'h2' 維持文件大綱;預設 'span') */\n as?: ReadTag\n /** view 態文字樣式(如標題 `text-h4 font-bold`) */\n readClassName?: string\n /** 外層容器 className */\n className?: string\n}\n\n/**\n * InlineEdit — 就地編輯 primitive(view ↔ edit 二態,泛型 <T>)\n *\n * 互動模型 world-class 對照見檔頭「消費的 SSOT」+ inline-edit.spec.md benchmark 表:\n * - **view 態** = 純文字 / 格式化 renderRead、寬度 fill 容器、透明邊框(預留)→ 視覺 = 純內容\n * - **hover(view,editable)** = 灰色底色 `bg-neutral-hover` + `rounded-md`(**非邊框**)+ cursor\n * - **鍵盤 focus(view,editable)** = 外框染 `border-primary` 藍框(Field focus 語言,非 Button ring)\n * - **click / Enter/Space** = 切 edit 態 = 真正的 Field 控件(mode=\"edit\":border + focus + input);多行走 Textarea\n * - **退出**:blur(滑鼠)→ commit + 純 view 不搶焦;Enter(鍵盤)→ commit + 焦點回 view 按鈕 + 藍框;Esc → cancel 還原\n * - **鍵盤結算**:消費 `makeEditSettleKeyHandler`(含中文 IME guard,與 DataTable cell 同源)\n * - **editable=false**:view 無 hover / 無 Pressable / 不可進 edit、**不灰化**(detail-pane 鎖定語意)\n *\n * view 態用隱形 Pressable(絕對定位 `<button>` 疊於內容上)—— 讓 `as=\"h1\"` 等標題標籤保留文件大綱\n * a11y,同時提供鍵盤 focus + Enter 進 edit(HTML 內容模型不允許 heading 巢狀於 button,故分離)。\n */\nfunction InlineEditImpl<T = string>(\n {\n value,\n onCommit,\n renderEdit,\n renderRead,\n multiline = false,\n editable = true,\n label,\n placeholder,\n size: sizeProp,\n as = 'span',\n readClassName,\n className,\n }: InlineEditProps<T>,\n ref: React.Ref<HTMLDivElement>,\n) {\n // size:接 fieldCtx.size cascade,standalone fallback sm(InlineEdit 靜態 fieldPreferredSize='sm'\n // 讓外層 `<Field>` 自動收 sm — 見檔尾 static)。\n const size = useResolvedFieldSize(sizeProp, 'sm')\n // orientation:vertical(值貼 label 左緣 → 用 -mx)/ horizontal(值落內容欄左緣+field-px = 對齊 sibling 控件 → 不用 -mx)。\n const fieldCtx = useFieldContext()\n const orientation = fieldCtx?.orientation ?? 'vertical'\n\n const [editing, setEditing] = React.useState(false)\n const [draft, setDraft] = React.useState<T>(value)\n // draft 最新值 ref —— commit 從事件 handler 呼叫時避免 stale closure\n const draftRef = React.useRef(draft)\n // 單次結算 guard —— Enter/Esc 已結算後,unmount 觸發的 onBlur 不再重複 commit\n const finalizedRef = React.useRef(false)\n // 鍵盤結算後把 focus 送回 view 按鈕(a11y:焦點不遺失);滑鼠 blur 不送回(不搶焦)\n const returnFocusRef = React.useRef(false)\n const readButtonRef = React.useRef<HTMLButtonElement>(null)\n // edit 態容器 ref(pointerdown-outside 用);與 forwarded ref merge。\n // 顯式 `| null` → mutable overload(手動賦值 .current;`useRef<T>(null)` 會匹配 readonly RefObject overload)。\n const editContainerRef = React.useRef<HTMLDivElement | null>(null)\n const setEditContainerRef = React.useCallback(\n (node: HTMLDivElement | null) => {\n editContainerRef.current = node\n if (typeof ref === 'function') ref(node)\n else if (ref) (ref as React.MutableRefObject<HTMLDivElement | null>).current = node\n },\n [ref],\n )\n\n const setDraftValue = React.useCallback((next: T) => {\n draftRef.current = next\n setDraft(next)\n }, [])\n\n const enterEdit = React.useCallback(() => {\n finalizedRef.current = false\n setDraftValue(value)\n setEditing(true)\n }, [value, setDraftValue])\n\n // commit(returnFocus):鍵盤結算傳 true(焦點回 view 按鈕);滑鼠 blur 傳 false(不搶焦、純 view)\n const commit = React.useCallback(\n (returnFocus: boolean) => {\n if (finalizedRef.current) return\n finalizedRef.current = true\n returnFocusRef.current = returnFocus\n setEditing(false)\n if (draftRef.current !== value) onCommit(draftRef.current)\n },\n [value, onCommit],\n )\n\n const cancel = React.useCallback(() => {\n if (finalizedRef.current) return\n finalizedRef.current = true\n returnFocusRef.current = true // Esc = 鍵盤路徑,焦點回 view 按鈕\n setEditing(false)\n }, [])\n\n // ── Exit-on-blur SSOT(2026-07-17 root cause 修:blur 後 chrome 殘留)──────────────\n // InlineEdit **保證**任何 edit 控件(預設 Input + 自訂 renderEdit 的 Select/DatePicker/Combobox…)\n // blur 都回 view,**不靠** consumer 在 renderEdit 自接 onBlur(舊設計缺口:story Select 只接\n // onChange→commit,不選直接 blur → commit 從沒呼叫 → 卡 editing=true,edit chrome 殘留)。\n // portal-aware:overlay 控件(Select/DatePicker)開的 Radix popper/listbox 是 portaled 出子樹,\n // 焦點在其中時**不誤結算**(dropdown 操作中);真正離開(都不在 edit 子樹 + 不在本 edit 的 overlay)才 commit。\n // rAF 延一拍讓焦點落定(Radix 關 popover 時焦點先回 trigger,避免 blur 瞬間誤判)。\n const handleEditBlur = React.useCallback((e: React.FocusEvent<HTMLDivElement>) => {\n const wrapper = e.currentTarget\n requestAnimationFrame(() => {\n if (finalizedRef.current) return\n const active = document.activeElement\n if (active && wrapper.contains(active)) return // 仍在 edit 子樹(如 trigger)\n // 焦點在本 edit 開的 Radix overlay 內 → dropdown 操作中,不結算\n if (active && active.closest('[data-radix-popper-content-wrapper],[data-radix-select-viewport],[role=\"listbox\"],[role=\"dialog\"]')) return\n commit(false) // blur 離開 → 回 view(滑鼠路徑不搶焦)\n })\n }, [commit])\n\n // 鍵盤結算後把 focus 送回 view 按鈕(commit(true)/cancel);滑鼠 blur(commit(false))刻意不送回——\n // 對齊 inline-edit.spec.md「退出 edit 態(focus 分流)」:鍵盤結算焦點回 view 按鈕 + 藍框、\n // 滑鼠 blur 回純 view 不搶焦。focus-visible 保證只有鍵盤路徑顯藍框。\n React.useEffect(() => {\n if (!editing && returnFocusRef.current) {\n returnFocusRef.current = false\n readButtonRef.current?.focus()\n }\n }, [editing])\n\n // ── Pointerdown-outside exit(2026-07-17,blur 殘留 root cause 真修)──────────────\n // focusout 不夠:overlay 控件(Select)點外面關 popover 時 Radix 把焦點送回 trigger(在 edit 子樹內)\n // → onBlur 不觸發 → 卡 editing。改用 document-level pointerdown-outside 偵測(對齊 Radix\n // DismissableLayer onPointerDownOutside https://www.radix-ui.com/primitives/docs/utilities/dismissable-layer\n // + Atlassian inline-edit click-outside 退出\n // https://github.com/pioug/atlassian-frontend-mirror/blob/main/design-system/inline-edit/src/inline-edit.tsx):\n // editing 時點在 edit 子樹外且不在本 edit popover 內的 pointerdown → commit 回 view。\n // 保證任何控件(含 overlay)點外面都回 display,不靠 consumer 在 renderEdit 自接。\n React.useEffect(() => {\n if (!editing) return\n const onPointerDown = (e: PointerEvent) => {\n if (finalizedRef.current) return\n const target = e.target as Element | null\n if (!target) return\n const box = editContainerRef.current\n if (box && box.contains(target)) return // 點在 edit 子樹內(trigger 等)\n // 點在本 edit 開的 Radix overlay 內(popper / listbox / dialog)→ dropdown 操作中,不結算\n if (target.closest('[data-radix-popper-content-wrapper],[data-radix-select-viewport],[role=\"listbox\"],[role=\"dialog\"]')) return\n commit(false)\n }\n document.addEventListener('pointerdown', onPointerDown, true)\n return () => document.removeEventListener('pointerdown', onPointerDown, true)\n }, [editing, commit])\n\n const isEmpty =\n value == null || (value as unknown) === '' || (Array.isArray(value) && value.length === 0)\n const Tag = as as React.ElementType\n\n // 對齊盒(orientation-aware,Model A;2026-07-17 移除 fieldCtx gate — root cause 修):\n // vertical(預設,含 standalone)→ `-mx-field-px` + `w-calc` 把整塊拉到欄左緣(值貼 label 左緣;\n // view 委派控件在 `surface='inline-edit'` 下 px 統一 field-px → 被 -mx 精準抵消 → 落 x=0 對齊 label)。\n // horizontal → 不用 -mx(純 `w-full`,值內縮 px = 對齊 sibling 控件)。\n // ⚠️ **移除舊 `fieldCtx &&` gate(root cause)**:standalone 預設 orientation='vertical'(:131),舊 gate 讓\n // standalone 掉到 `w-full` 無 -mx → 內容內縮 field-px 不對齊 label(圖一/圖二 bug)。spec Model A\n // (field-controls.spec.md:71「orientation-aware -mx / vertical 貼 label」)只依 orientation、不依 fieldCtx。\n // -mx bleed field-px 到容器 padding = 刻意(hover/edit 才顯真實邊界)→ consumer 放 ≥field-px padding 容器\n // (detail panel / card;stories 容器 layout-space-loose 16px > field-px 12px 天然吸收)。\n const alignBleed =\n orientation === 'vertical'\n ? '-mx-[var(--field-px)] w-[calc(100%_+_2_*_var(--field-px))]'\n : 'w-full min-w-0'\n\n if (editing) {\n const editProps: InlineEditRenderProps<T> = {\n value: draft,\n onChange: setDraftValue,\n commit: () => commit(true),\n cancel,\n autoFocus: true,\n size,\n 'aria-label': label,\n }\n // 預設 edit 控件:multiline → Textarea(Enter=換行、Cmd/Ctrl+Enter/blur 提交);否則 Input。\n // 兩者皆 mode=\"edit\"(edit 態 = 真正的 edit-mode Field 控件),Enter/Esc 結算走 makeEditSettleKeyHandler(含 IME guard)。\n // string-only 預設路徑;非 string 值必傳 renderEdit。\n const defaultEditNode = multiline ? (\n <Textarea\n autoFocus\n mode=\"edit\"\n value={draft as unknown as string}\n size={size}\n aria-label={label}\n onChange={(e) => setDraftValue(e.target.value as unknown as T)}\n onBlur={() => commit(false)}\n // 多行:plain Enter=換行、Esc=cancel、Cmd/Ctrl+Enter=commit —— 全走 makeEditSettleKeyHandler SSOT。\n onKeyDown={makeEditSettleKeyHandler({ onCommit: () => commit(true), onCancel: cancel, commitOnEnter: false })}\n />\n ) : (\n <Input\n autoFocus\n mode=\"edit\"\n value={draft as unknown as string}\n size={size}\n aria-label={label}\n onChange={(e) => setDraftValue(e.target.value as unknown as T)}\n onBlur={() => commit(false)}\n onKeyDown={makeEditSettleKeyHandler({ onCommit: () => commit(true), onCancel: cancel })}\n />\n )\n const editNode = renderEdit ? renderEdit(editProps) : defaultEditNode\n return (\n // exit SSOT 雙保險:onBlur(focusout,tab-out / 非 overlay 控件)+ document pointerdown-outside(overlay\n // 點外面 Radix 送焦回 trigger 時 focusout 不觸發之補位)。任何 edit 控件離開 → 回 view。\n // edit 態**不套** surface='inline-edit'(完全繼承原 field,含 tagPadding — Jira 式,user 2026-07-17 拍板)。\n <div ref={setEditContainerRef} data-editing className={cn(alignBleed, className)} onBlur={handleEditBlur}>\n {editNode}\n </div>\n )\n }\n\n // view 態:外框只承載對齊盒 + (editable 時) hover bg + 鍵盤 focus 藍框;幾何由內容(委派控件 view / fieldViewGeometry)提供。\n const viewNode = renderRead ? (\n // 格式化 view(consumer 傳 <Control mode=\"view\"> 等)。**對齊 SSOT(2026-07-17)**:委派控件的\n // bare view 一律 0px 左內距(audit 證實 Select/Combobox/DatePicker/TimePicker/PeoplePicker/LinkInput\n // 預設 view 皆裸 span/Tag),故 InlineEdit 用**與純值路徑同一個 `fieldViewGeometry(field-px + min-h +\n // py)`** 包住 renderRead → 委派控件 view 左 px 統一 field-px、高度統一 field-height → 固定\n // `-mx-field-px` 精準抵消 → tag/avatar/值左緣落 label x=0。不碰任何控件 tagPadding(bare view 本就不用)。\n // surface='inline-edit' 保留為 per-control 逃生訊號(若某控件 bare view 有內部 inset 破壞對齊,該控件可\n // 讀 surface 自抑;目前全控件 bare view=0px 不需)。\n <FieldSurfaceProvider surface=\"inline-edit\">\n <div className={fieldViewGeometry(size, multiline)}>{renderRead(value)}</div>\n </FieldSurfaceProvider>\n ) : (\n <Tag\n data-empty={isEmpty}\n className={cn(\n // 純值/標題共用 view 幾何 SSOT(px-field-px + min-h-field + 單行 items-center / 多行 items-start py-2)\n fieldViewGeometry(size, multiline),\n // view 態 typography 預設消費 Field view SSOT(sm/md→text-body,lg→text-body-lg)——\n // plain 用法 view 字級 = edit <Input>(text-body)= <Input mode=\"view\"> 字級,三者一致零跳字。\n fieldDisplayTextClass(size),\n // 多行:保留換行(pre-wrap)+ 長字斷行 —— view 忠實呈現 Textarea 內容(Jira description 類)。\n multiline && 'whitespace-pre-wrap break-words',\n readClassName,\n isEmpty && 'text-fg-muted',\n )}\n >\n {isEmpty ? placeholder : String(value)}\n </Tag>\n )\n\n return (\n <div\n ref={ref}\n data-editing={false}\n data-editable={editable || undefined}\n className={cn(\n 'relative flex min-w-0 rounded-md border border-transparent transition-colors duration-150',\n alignBleed,\n // editable 才有 hover 底色 + 鍵盤 focus 藍框(Field focus 語言,非 Button ring);\n // editable=false = 純 view 鎖定,無入口、無藍框、不灰化。\n editable && 'hover:bg-neutral-hover [&:has(button:focus-visible)]:border-primary',\n className,\n )}\n >\n {viewNode}\n {/* 隱形 Pressable(僅 editable):提供 click + 鍵盤 Tab focus + Enter/Space 進 edit;透明疊於內容上。\n hover 底色 + focus 藍框由外層 div 承載(Field focus 語言),故本 button 只需 outline-none 消瀏覽器預設外框。 */}\n {editable && (\n <button\n ref={readButtonRef}\n type=\"button\"\n aria-label={label ? `編輯 ${label}` : '編輯'}\n onClick={enterEdit}\n className=\"absolute inset-0 cursor-text rounded-md focus-visible:outline-none\"\n />\n )}\n </div>\n )\n}\n\n// 泛型 forwardRef:forwardRef 不原生支援泛型,以 cast 還原公開型別(標準 workaround)。\nconst InlineEdit = React.forwardRef(InlineEditImpl) as (<T = string>(\n props: InlineEditProps<T> & { ref?: React.Ref<HTMLDivElement> },\n) => React.ReactElement) & { displayName?: string; fieldPreferredSize?: FieldSize }\nInlineEdit.displayName = 'InlineEdit'\n// 靜態偏好尺寸:外層 `<Field>` render 時讀 `child.type.fieldPreferredSize` → Field.size 未顯式指定時收 sm\n// (對齊 detectControlLayout 讀 fieldLayout 的機制;InlineEdit view 態無邊框,md 過大會讓版面鬆散)。\nInlineEdit.fieldPreferredSize = 'sm'\n\nexport { InlineEdit }\n"],"names":[],"mappings":";;;;;;;;AA4GA,SAAS,eACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ,WAAW;AAAA,EACX;AAAA,EACA;AAAA,EACA,MAAM;AAAA,EACN,KAAK;AAAA,EACL;AAAA,EACA;AACF,GACA,KACA;AAGA,QAAM,OAAO,qBAAqB,UAAU,IAAI;AAEhD,QAAM,WAAW,gBAAA;AACjB,QAAM,eAAc,qCAAU,gBAAe;AAE7C,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAS,KAAK;AAClD,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAY,KAAK;AAEjD,QAAM,WAAW,MAAM,OAAO,KAAK;AAEnC,QAAM,eAAe,MAAM,OAAO,KAAK;AAEvC,QAAM,iBAAiB,MAAM,OAAO,KAAK;AACzC,QAAM,gBAAgB,MAAM,OAA0B,IAAI;AAG1D,QAAM,mBAAmB,MAAM,OAA8B,IAAI;AACjE,QAAM,sBAAsB,MAAM;AAAA,IAChC,CAAC,SAAgC;AAC/B,uBAAiB,UAAU;AAC3B,UAAI,OAAO,QAAQ,WAAY,KAAI,IAAI;AAAA,eAC9B,IAAM,KAAsD,UAAU;AAAA,IACjF;AAAA,IACA,CAAC,GAAG;AAAA,EAAA;AAGN,QAAM,gBAAgB,MAAM,YAAY,CAAC,SAAY;AACnD,aAAS,UAAU;AACnB,aAAS,IAAI;AAAA,EACf,GAAG,CAAA,CAAE;AAEL,QAAM,YAAY,MAAM,YAAY,MAAM;AACxC,iBAAa,UAAU;AACvB,kBAAc,KAAK;AACnB,eAAW,IAAI;AAAA,EACjB,GAAG,CAAC,OAAO,aAAa,CAAC;AAGzB,QAAM,SAAS,MAAM;AAAA,IACnB,CAAC,gBAAyB;AACxB,UAAI,aAAa,QAAS;AAC1B,mBAAa,UAAU;AACvB,qBAAe,UAAU;AACzB,iBAAW,KAAK;AAChB,UAAI,SAAS,YAAY,MAAO,UAAS,SAAS,OAAO;AAAA,IAC3D;AAAA,IACA,CAAC,OAAO,QAAQ;AAAA,EAAA;AAGlB,QAAM,SAAS,MAAM,YAAY,MAAM;AACrC,QAAI,aAAa,QAAS;AAC1B,iBAAa,UAAU;AACvB,mBAAe,UAAU;AACzB,eAAW,KAAK;AAAA,EAClB,GAAG,CAAA,CAAE;AASL,QAAM,iBAAiB,MAAM,YAAY,CAAC,MAAwC;AAChF,UAAM,UAAU,EAAE;AAClB,0BAAsB,MAAM;AAC1B,UAAI,aAAa,QAAS;AAC1B,YAAM,SAAS,SAAS;AACxB,UAAI,UAAU,QAAQ,SAAS,MAAM,EAAG;AAExC,UAAI,UAAU,OAAO,QAAQ,mGAAmG,EAAG;AACnI,aAAO,KAAK;AAAA,IACd,CAAC;AAAA,EACH,GAAG,CAAC,MAAM,CAAC;AAKX,QAAM,UAAU,MAAM;;AACpB,QAAI,CAAC,WAAW,eAAe,SAAS;AACtC,qBAAe,UAAU;AACzB,0BAAc,YAAd,mBAAuB;AAAA,IACzB;AAAA,EACF,GAAG,CAAC,OAAO,CAAC;AAUZ,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,QAAS;AACd,UAAM,gBAAgB,CAAC,MAAoB;AACzC,UAAI,aAAa,QAAS;AAC1B,YAAM,SAAS,EAAE;AACjB,UAAI,CAAC,OAAQ;AACb,YAAM,MAAM,iBAAiB;AAC7B,UAAI,OAAO,IAAI,SAAS,MAAM,EAAG;AAEjC,UAAI,OAAO,QAAQ,mGAAmG,EAAG;AACzH,aAAO,KAAK;AAAA,IACd;AACA,aAAS,iBAAiB,eAAe,eAAe,IAAI;AAC5D,WAAO,MAAM,SAAS,oBAAoB,eAAe,eAAe,IAAI;AAAA,EAC9E,GAAG,CAAC,SAAS,MAAM,CAAC;AAEpB,QAAM,UACJ,SAAS,QAAS,UAAsB,MAAO,MAAM,QAAQ,KAAK,KAAK,MAAM,WAAW;AAC1F,QAAM,MAAM;AAWZ,QAAM,aACJ,gBAAgB,aACZ,+DACA;AAEN,MAAI,SAAS;AACX,UAAM,YAAsC;AAAA,MAC1C,OAAO;AAAA,MACP,UAAU;AAAA,MACV,QAAQ,MAAM,OAAO,IAAI;AAAA,MACzB;AAAA,MACA,WAAW;AAAA,MACX;AAAA,MACA,cAAc;AAAA,IAAA;AAKhB,UAAM,kBAAkB,YACtB;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,WAAS;AAAA,QACT,MAAK;AAAA,QACL,OAAO;AAAA,QACP;AAAA,QACA,cAAY;AAAA,QACZ,UAAU,CAAC,MAAM,cAAc,EAAE,OAAO,KAAqB;AAAA,QAC7D,QAAQ,MAAM,OAAO,KAAK;AAAA,QAE1B,WAAW,yBAAyB,EAAE,UAAU,MAAM,OAAO,IAAI,GAAG,UAAU,QAAQ,eAAe,MAAA,CAAO;AAAA,MAAA;AAAA,IAAA,IAG9G;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,WAAS;AAAA,QACT,MAAK;AAAA,QACL,OAAO;AAAA,QACP;AAAA,QACA,cAAY;AAAA,QACZ,UAAU,CAAC,MAAM,cAAc,EAAE,OAAO,KAAqB;AAAA,QAC7D,QAAQ,MAAM,OAAO,KAAK;AAAA,QAC1B,WAAW,yBAAyB,EAAE,UAAU,MAAM,OAAO,IAAI,GAAG,UAAU,OAAA,CAAQ;AAAA,MAAA;AAAA,IAAA;AAG1F,UAAM,WAAW,aAAa,WAAW,SAAS,IAAI;AACtD;AAAA;AAAA;AAAA;AAAA,MAIE,oBAAC,OAAA,EAAI,KAAK,qBAAqB,gBAAY,MAAC,WAAW,GAAG,YAAY,SAAS,GAAG,QAAQ,gBACvF,UAAA,SAAA,CACH;AAAA;AAAA,EAEJ;AAGA,QAAM,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQf,oBAAC,sBAAA,EAAqB,SAAQ,eAC5B,8BAAC,OAAA,EAAI,WAAW,kBAAkB,MAAM,SAAS,GAAI,UAAA,WAAW,KAAK,GAAE,EAAA,CACzE;AAAA,MAEA;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,cAAY;AAAA,MACZ,WAAW;AAAA;AAAA,QAET,kBAAkB,MAAM,SAAS;AAAA;AAAA;AAAA,QAGjC,sBAAsB,IAAI;AAAA;AAAA,QAE1B,aAAa;AAAA,QACb;AAAA,QACA,WAAW;AAAA,MAAA;AAAA,MAGZ,UAAA,UAAU,cAAc,OAAO,KAAK;AAAA,IAAA;AAAA,EAAA;AAIzC,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC;AAAA,MACA,gBAAc;AAAA,MACd,iBAAe,YAAY;AAAA,MAC3B,WAAW;AAAA,QACT;AAAA,QACA;AAAA;AAAA;AAAA,QAGA,YAAY;AAAA,QACZ;AAAA,MAAA;AAAA,MAGD,UAAA;AAAA,QAAA;AAAA,QAGA,YACC;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,KAAK;AAAA,YACL,MAAK;AAAA,YACL,cAAY,QAAQ,MAAM,KAAK,KAAK;AAAA,YACpC,SAAS;AAAA,YACT,WAAU;AAAA,UAAA;AAAA,QAAA;AAAA,MACZ;AAAA,IAAA;AAAA,EAAA;AAIR;AAGA,MAAM,aAAa,MAAM,WAAW,cAAc;AAGlD,WAAW,cAAc;AAGzB,WAAW,qBAAqB;"}
|
|
@@ -5,7 +5,7 @@ import type { FieldMode, FieldVariant } from '../../components/Field/field-types
|
|
|
5
5
|
import { fieldWrapperStyles } from '../../components/Field/field-wrapper';
|
|
6
6
|
import { type InlineActionConfig } from '../../patterns/element-anatomy/item-anatomy';
|
|
7
7
|
export interface InputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'>, Omit<VariantProps<typeof fieldWrapperStyles>, 'mode' | 'variant' | 'width'> {
|
|
8
|
-
/** Field
|
|
8
|
+
/** Field mode */
|
|
9
9
|
mode?: FieldMode;
|
|
10
10
|
/**
|
|
11
11
|
* Visual variant(正交於 mode),對齊 FieldContext.variant 透傳。
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"input.d.ts","sourceRoot":"","sources":["../../../src/components/Input/input.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAC5D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAE9C,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAwB,MAAM,8CAA8C,CAAA;AACjH,OAAO,EAAE,kBAAkB,EAAmB,MAAM,gDAAgD,CAAA;AAGpG,OAAO,EAAgC,KAAK,kBAAkB,EAAE,MAAM,uDAAuD,CAAA;AAM7H,MAAM,WAAW,UACf,SAAQ,IAAI,CAAC,KAAK,CAAC,mBAAmB,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC,EAC/D,IAAI,CAAC,YAAY,CAAC,OAAO,kBAAkB,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC;IAC7E,
|
|
1
|
+
{"version":3,"file":"input.d.ts","sourceRoot":"","sources":["../../../src/components/Input/input.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAC5D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAE9C,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAwB,MAAM,8CAA8C,CAAA;AACjH,OAAO,EAAE,kBAAkB,EAAmB,MAAM,gDAAgD,CAAA;AAGpG,OAAO,EAAgC,KAAK,kBAAkB,EAAE,MAAM,uDAAuD,CAAA;AAM7H,MAAM,WAAW,UACf,SAAQ,IAAI,CAAC,KAAK,CAAC,mBAAmB,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC,EAC/D,IAAI,CAAC,YAAY,CAAC,OAAO,kBAAkB,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC;IAC7E,iBAAiB;IACjB,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB;;;;;;;;OAQG;IACH,OAAO,CAAC,EAAE,YAAY,CAAA;IACtB,sDAAsD;IACtD,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,oDAAoD;IACpD,SAAS,CAAC,EAAE,UAAU,CAAA;IACtB,qDAAqD;IACrD,SAAS,CAAC,EAAE,kBAAkB,CAAA;IAC9B;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACzB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;;;;;;;;;;;;;OAcG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AAKD,QAAA,MAAM,KAAK,qFAoLV,CAAA;AAUD,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmBZ,CAAA;AAEV,OAAO,EAAE,KAAK,EAAE,CAAA"}
|
|
@@ -34,7 +34,7 @@ const Input = React.forwardRef(
|
|
|
34
34
|
const resolvedMode = useResolvedFieldMode({ mode: modeProp, disabled, readOnly });
|
|
35
35
|
const emptyDisplay = useFieldEmptyDisplay();
|
|
36
36
|
const isEditable = resolvedMode === "edit";
|
|
37
|
-
const
|
|
37
|
+
const isView = resolvedMode === "view";
|
|
38
38
|
const resolvedError = useResolvedFieldInvalid(error);
|
|
39
39
|
const iconSize = ICON_SIZE[size];
|
|
40
40
|
const iconColor = resolvedMode === "disabled" ? "text-fg-disabled" : "text-fg-muted";
|
|
@@ -44,7 +44,7 @@ const Input = React.forwardRef(
|
|
|
44
44
|
defaultValue: defaultValue ?? ""
|
|
45
45
|
});
|
|
46
46
|
const displayValue = resolved != null && resolved !== "" ? String(resolved) : null;
|
|
47
|
-
const showDisplaySpan =
|
|
47
|
+
const showDisplaySpan = isView || resolvedMode === "readonly" && displayValue == null;
|
|
48
48
|
const innerRef = React.useRef(null);
|
|
49
49
|
React.useEffect(() => {
|
|
50
50
|
var _a;
|
|
@@ -61,7 +61,7 @@ const Input = React.forwardRef(
|
|
|
61
61
|
else if (ref) ref.current = el;
|
|
62
62
|
}, [ref]);
|
|
63
63
|
if (showDisplaySpan) {
|
|
64
|
-
const spanMode =
|
|
64
|
+
const spanMode = isView ? "view" : "readonly";
|
|
65
65
|
return /* @__PURE__ */ jsxs(
|
|
66
66
|
"div",
|
|
67
67
|
{
|
|
@@ -85,9 +85,9 @@ const Input = React.forwardRef(
|
|
|
85
85
|
{
|
|
86
86
|
className: cn(
|
|
87
87
|
bareInputStyles,
|
|
88
|
-
// B1 fix(2026-05-05):
|
|
89
|
-
// cell display canonical:single-line value 過長 → ellipsis。Textarea
|
|
90
|
-
// 不在此處;Input
|
|
88
|
+
// B1 fix(2026-05-05):view mode 單行 ellipsis 截斷(對齊 Notion / Airtable / Linear
|
|
89
|
+
// cell display canonical:single-line value 過長 → ellipsis。Textarea view 走 wrap path,
|
|
90
|
+
// 不在此處;Input view 永遠 single-line。)
|
|
91
91
|
"truncate",
|
|
92
92
|
displayValue == null && fieldEmptyColorClass(resolvedMode)
|
|
93
93
|
),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"input.js","sources":["../../../src/components/Input/input.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 { type VariantProps } from 'class-variance-authority'\nimport type { LucideIcon } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport type { FieldMode, FieldVariant, FieldVariantInternal } from '@/design-system/components/Field/field-types'\nimport { fieldWrapperStyles, bareInputStyles } from '@/design-system/components/Field/field-wrapper'\nimport { useFieldContext, useResolvedFieldSize, useResolvedFieldDisabled, useResolvedFieldMode, useResolvedFieldVariant, useResolvedFieldInvalid, useFieldEmptyDisplay, fieldEmptyColorClass } from '@/design-system/components/Field/field-context'\nimport { useControllable } from '@/design-system/hooks/use-controllable'\nimport { ItemInlineAction, ItemPrefix, type InlineActionConfig } from '@/design-system/patterns/element-anatomy/item-anatomy'\nimport { CircularProgress } from '@/design-system/components/CircularProgress/circular-progress'\nimport { ICON_SIZE } from '@/design-system/tokens/uiSize/icon-size'\n\n// ── Types ───────────────────────────────────────────────────────────────────\n\nexport interface InputProps\n extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'>,\n Omit<VariantProps<typeof fieldWrapperStyles>, 'mode' | 'variant' | 'width'> {\n /** Field display mode */\n mode?: FieldMode\n /**\n * Visual variant(正交於 mode),對齊 FieldContext.variant 透傳。\n * 公開 variant 一個(2026-07-09 `bare` 退役,詳 field-types.ts FieldVariant note;naked 2026-07-14 型別收窄至 FieldVariantInternal):\n * - `'default'`(預設)— Field wrapper 完整 variant:bg-surface + 明顯 border + hover/focus 回饋。適用表單、Field 內嵌。\n *\n * @internal `'naked'` — 完全無 chrome / 無 border / 無 focus ring。單獨使用無視覺邊界,**不可直接 standalone 用**;僅供 DS 內部 cell-as-input 組合(edit×naked 的 border/hover/focus/error 由 field-wrapper 自渲 state machine 提供,非 host cell 自管;詳 field-wrapper.tsx)。consumer 請用 `default`(2026-07-14 起型別層已擋:公開 FieldVariant 不含 naked,僅 DataTable cell-registry 經 WithFieldVariantInternal 通道傳入)。\n *\n * 透傳:在 `<Field variant=\"default\">` 內自動繼承 context.variant;per-prop override context。\n */\n variant?: FieldVariant\n /** Error 狀態(正交於 mode)。border-error + aria-invalid。 */\n error?: boolean\n /** 左側靜態 icon — 輔助理解 input 用途(如 Search)。fg-muted。 */\n startIcon?: LucideIcon\n /** 右側 inline action — 宣告式 API,Field 根據 size 自動渲染。 */\n endAction?: InlineActionConfig\n /**\n * 右側 slot(ReactNode)— escape hatch 供 consumer 放自訂元素(如 DropdownMenuTrigger asChild + ItemInlineActionButton)。\n * 跟 `endAction` 互斥(同時傳 endSlot 會優先,endAction 被忽略)。\n *\n * **使用情境**:ZoomInput 需要 chevron 作 DropdownMenuTrigger anchor,config-only API 無法做到。\n * **禁止情境**:表單欄位 / 一般 inline action → 用 `endAction` 宣告式 API。\n */\n endSlot?: React.ReactNode\n /**\n * Loading 狀態(async 驗證 / debounce fetch 中)。\n * - **input 保持可編輯**(user 可以邊改邊讀,debounce 場景 UX 最好)\n * - 世界級對照:Ant Input.Search 派(input editable during loading);非 Material readonly 派\n * - 自動在 endAction slot 塞 `<CircularProgress size={iconSize}/>`(與 endAction prop 互斥)\n * - 宣告 `aria-busy=\"true\"` 讓 screen reader 感知處理中\n */\n loading?: boolean\n /**\n * Auto-width:Input 寬度 = 內容寬(value / placeholder 文字寬)+ startIcon + endAction + padding。\n * 使用 CSS `field-sizing: content`(Chrome 123+ / Safari 17.4+;Firefox 還在實驗)。\n *\n * **使用情境**:\n * - Inline edit(VS Code setting row / Figma property toolbar number input)\n * - ZoomInput(FileViewer 縮放比例:輸入「100%」自動縮到三位數寬)\n * - Tag / Chip 內 inline rename\n *\n * **不要用在**:表單 Field(Field 需要欄寬對齊,不該隨值跳動)\n *\n * **fallback**:不支援 `field-sizing` 的瀏覽器會退化為 `w-auto`(wrapper 縮到 content 尺寸,\n * input 為 min-w-0,依 content/placeholder 撐寬)。UX 上稍不一致但不致斷;若必須精準對齊所有瀏覽器,\n * consumer 可自行傳 `style={{ width: ... }}` 顯式寬度,不走 auto。\n */\n autoWidth?: boolean\n}\n\n// ── Component ───────────────────────────────────────────────────────────────\n\n// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding\nconst Input = React.forwardRef<HTMLInputElement, InputProps>(\n (\n {\n mode: modeProp,\n variant: variantProp,\n error = false,\n size: sizeProp,\n startIcon: StartIcon,\n endAction,\n endSlot,\n loading = false,\n autoWidth = false,\n className,\n disabled: disabledProp,\n readOnly,\n value,\n defaultValue,\n id: idProp,\n 'aria-describedby': ariaDescribedByProp,\n 'aria-errormessage': ariaErrorMessageProp,\n ...props\n },\n ref\n ) => {\n // ── FieldContext 自動讀取(在 <Field> 內時,invalid / disabled / mode / chrome 由 context 接管) ──\n const fieldCtx = useFieldContext()\n // 2026-05-31 #11/#12:size + disabled 從 Field context cascade(對齊 NumberInput number-input.tsx:100-101\n // + MUI FormControl)。原 Input 不讀 fieldCtx.size/disabled → <Field size=\"lg\"> / <Field disabled> 對 Input 無效。\n const size = useResolvedFieldSize(sizeProp)\n const disabled = useResolvedFieldDisabled(disabledProp)\n // chrome 透傳:per-prop override context;context 沒值則 'default'\n const variant: FieldVariantInternal = useResolvedFieldVariant(variantProp)\n // 2026-06-08 SSOT:mode 經 useResolvedFieldMode 統一解析(prop > 有效 disabled > fieldCtx.mode > readOnly > 'edit')。\n // loading 期間 input 保持可編輯(Ant Input.Search 派),只用 aria-busy + endAction Spinner,不動 mode。\n const resolvedMode: FieldMode = useResolvedFieldMode({ mode: modeProp, disabled, readOnly })\n const emptyDisplay = useFieldEmptyDisplay()\n const isEditable = resolvedMode === 'edit'\n const isDisplay = resolvedMode === 'display'\n // error 合併:自身 error prop OR Field context invalid\n const resolvedError = useResolvedFieldInvalid(error)\n // 2026-05-18 改 import ICON_SIZE SSOT(per user『做完』approval,消除 M17 違反 7+ 重複 ternary)\n const iconSize = ICON_SIZE[size as 'sm' | 'md' | 'lg']\n const iconColor = resolvedMode === 'disabled' ? 'text-fg-disabled' : 'text-fg-muted'\n\n // ── display mode(+ readonly 空值)純展示,渲染 <span> 取代 <input> ──\n // 對齊 Carbon read-only / PatternFly inline-edit hidden-input / Cloudscape display-mode\n // 2026-07-08 user 拍板:readonly 空值也走 display-span 顯 '-'(readonly **有值** 仍走下方\n // native <input readOnly> 保留選取/複製語意 — field-controls.spec.md「null / undefined 值」)。\n // 2026-07-14 audit Dim 26(dual-mode coherence):uncontrolled `defaultValue` 也要被\n // display / readonly-空值判定認得 — 原本只讀 `value`,uncontrolled Input 切 display 會誤顯 '-'。\n // 2026-07-14 R2(dual-model consensus,取代同日稍早 onChange-mirror 版):resolved value 改走\n // useControllable 內部 SSOT(Radix idiom,同 Select / Pagination 既有消費)。uncontrolled 時\n // native input 由 value={resolved} 內部驅動(defaultValue 只作初始值,不落 DOM attribute),\n // 修 mirror 版兩個真缺陷:\n // (1) remount-stale — 切 display / readonly(native input unmount)再回 edit,native input 從\n // defaultValue attribute 重掛 → 顯示 stale 初始值,與 mirror 判定分歧;\n // (2) form.reset() stale — HTML 標準 reset 不發 input event,onChange mirror 停在 reset 前的值\n // (下方 reset bridge 修)。\n // controlled(傳 value)時 useControllable 純 passthrough,行為與原本完全一致。\n // consumer onChange 不接進 hook(保留 native event signature),於 <input> onChange 另行轉發。\n const isControlled = value !== undefined\n const [resolved, setResolved] = useControllable<string | number | readonly string[]>({\n value: value as string | number | readonly string[] | undefined,\n defaultValue: defaultValue ?? '',\n })\n const displayValue = resolved != null && resolved !== '' ? String(resolved) : null\n const showDisplaySpan = isDisplay || (resolvedMode === 'readonly' && displayValue == null)\n\n // form.reset() bridge(uncontrolled only):reset 恢復 defaultValue 但不發 input event →\n // 手動把 resolved 歸位 defaultValue。keyed on showDisplaySpan:display 分支不掛 native input,\n // 回 edit 重掛時 effect 重跑補掛 listener。\n const innerRef = React.useRef<HTMLInputElement | null>(null)\n React.useEffect(() => {\n if (isControlled || showDisplaySpan) return\n const form = innerRef.current?.form\n if (!form) return\n const handleReset = () => setResolved(defaultValue ?? '')\n form.addEventListener('reset', handleReset)\n return () => form.removeEventListener('reset', handleReset)\n }, [isControlled, showDisplaySpan, defaultValue, setResolved])\n // Merge refs(LinkInput setRef idiom,link-input.tsx:138)\n const setRef = React.useCallback((el: HTMLInputElement | null) => {\n innerRef.current = el\n if (typeof ref === 'function') ref(el)\n else if (ref) (ref as React.MutableRefObject<HTMLInputElement | null>).current = el\n }, [ref])\n\n if (showDisplaySpan) {\n const spanMode = isDisplay ? 'display' : 'readonly'\n return (\n <div\n className={cn(\n fieldWrapperStyles({ mode: spanMode, variant: variant, size }),\n autoWidth && 'inline-flex w-auto',\n className,\n )}\n data-field-mode={spanMode}\n >\n {StartIcon && (\n <ItemPrefix>\n <StartIcon\n size={iconSize}\n className={cn('pointer-events-none', iconColor)}\n aria-hidden\n />\n </ItemPrefix>\n )}\n <span\n className={cn(\n bareInputStyles,\n // B1 fix(2026-05-05):display mode 單行 ellipsis 截斷(對齊 Notion / Airtable / Linear\n // cell display canonical:single-line value 過長 → ellipsis。Textarea display 走 wrap path,\n // 不在此處;Input display 永遠 single-line。)\n 'truncate',\n displayValue == null && fieldEmptyColorClass(resolvedMode),\n )}\n >\n {displayValue ?? emptyDisplay}\n </span>\n </div>\n )\n }\n\n return (\n <div\n className={cn(\n fieldWrapperStyles({ mode: resolvedMode, variant: variant, size, error: resolvedError }),\n // autoWidth:wrapper 縮到 inline-flex + w-auto,讓寬度由 startIcon + input(field-sizing: content)+ endAction 自然累加\n autoWidth && 'inline-flex w-auto',\n className,\n )}\n data-field-mode={resolvedMode}\n data-error={isEditable && resolvedError ? '' : undefined}\n aria-busy={loading || undefined}\n >\n {StartIcon && (\n <ItemPrefix>\n <StartIcon\n size={iconSize}\n className={cn('pointer-events-none', iconColor)}\n aria-hidden\n />\n </ItemPrefix>\n )}\n <input\n ref={setRef}\n type=\"text\"\n id={idProp ?? fieldCtx?.id}\n value={resolved}\n readOnly={resolvedMode === 'readonly'}\n disabled={resolvedMode === 'disabled'}\n aria-invalid={resolvedError || undefined}\n aria-required={fieldCtx?.required || undefined}\n aria-describedby={ariaDescribedByProp ?? fieldCtx?.descriptionId}\n aria-errormessage={ariaErrorMessageProp ?? (resolvedError ? fieldCtx?.errorId : undefined)}\n className={cn(\n bareInputStyles,\n resolvedMode === 'disabled' && 'text-fg-disabled placeholder:text-fg-disabled cursor-not-allowed',\n // autoWidth:input 本身 field-sizing:content(Chrome 123+ / Safari 17.4+),寬度跟 value 文字寬。\n // w-auto 關掉預設 w-full;min-w-0 讓 flex shrink 不卡住。\n autoWidth && '[field-sizing:content] w-auto min-w-0',\n )}\n {...props}\n // 置於 {...props} 後:寫回 resolved SSOT(controlled 時 useControllable 內部為 no-op)再轉發 consumer onChange。\n onChange={(e) => {\n setResolved(e.target.value)\n props.onChange?.(e)\n }}\n />\n {loading ? (\n <CircularProgress size={iconSize} className=\"shrink-0\" />\n ) : endSlot && isEditable ? (\n // endSlot escape hatch:consumer 自控右側 slot(如 DropdownMenuTrigger asChild wrap)\n endSlot\n ) : endAction && isEditable ? (\n <ItemInlineAction action={endAction} size={size ?? 'md'} />\n ) : null}\n </div>\n )\n }\n)\nInput.displayName = 'Input'\n\n// Phase B1(2026-05-05):InputDisplay 退場。改用 `<Input mode=\"display\" value={...} />`\n// 對齊 Carbon read-only / PatternFly inline-edit hidden-input / Cloudscape display-mode 統一 mode 模型。\n\n// Story auto-compile metadata — Phase 1 mechanical migration(2026-04-24)\n// 2026-07-05 Phase 2 fill(deep-audit A.1b):variants 按 input.tsx variant jsdoc、sizes 按\n// fieldWrapperStyles cva 真值(h-field-sm/md/lg = 28/32/36,uiSize.css)+ ICON_SIZE SSOT\n// (sm/md 16、lg 20)填。`naked` 為 @internal cell-as-input substrate → 不列入 public variants。\nexport const inputMeta = {\n component: 'Input',\n family: 4,\n variants: {\n default: { purpose: 'Field wrapper 完整 chrome(bg-surface + border + hover/focus 回饋)— 表單 / Field 內嵌' },\n // (2026-07-09 `bare` variant 退役 — 見 field-types.ts FieldVariant note)\n },\n sizes: {\n sm: { fieldHeight: 28, iconSize: 16, typography: 'body' },\n md: { fieldHeight: 32, iconSize: 16, typography: 'body' },\n lg: { fieldHeight: 36, iconSize: 20, typography: 'body-lg' },\n },\n // 'active' 移除 — Field chrome 無按壓態(對齊 Textarea / LinkInput meta 既有寫法)(2026-07-07 詞彙統一 DS-wide 按壓訊號盤點:檔內 0 active: utility / 0 *-active token)。\n states: ['default', 'hover', 'focus-visible', 'disabled'],\n tokens: {\n bg: ['bg-surface'],\n fg: ['text-fg-disabled', 'text-fg-muted'],\n ring: [],\n },\n} as const\n\nexport { Input }\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"input.js","sources":["../../../src/components/Input/input.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 { type VariantProps } from 'class-variance-authority'\nimport type { LucideIcon } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport type { FieldMode, FieldVariant, FieldVariantInternal } from '@/design-system/components/Field/field-types'\nimport { fieldWrapperStyles, bareInputStyles } from '@/design-system/components/Field/field-wrapper'\nimport { useFieldContext, useResolvedFieldSize, useResolvedFieldDisabled, useResolvedFieldMode, useResolvedFieldVariant, useResolvedFieldInvalid, useFieldEmptyDisplay, fieldEmptyColorClass } from '@/design-system/components/Field/field-context'\nimport { useControllable } from '@/design-system/hooks/use-controllable'\nimport { ItemInlineAction, ItemPrefix, type InlineActionConfig } from '@/design-system/patterns/element-anatomy/item-anatomy'\nimport { CircularProgress } from '@/design-system/components/CircularProgress/circular-progress'\nimport { ICON_SIZE } from '@/design-system/tokens/uiSize/icon-size'\n\n// ── Types ───────────────────────────────────────────────────────────────────\n\nexport interface InputProps\n extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'>,\n Omit<VariantProps<typeof fieldWrapperStyles>, 'mode' | 'variant' | 'width'> {\n /** Field mode */\n mode?: FieldMode\n /**\n * Visual variant(正交於 mode),對齊 FieldContext.variant 透傳。\n * 公開 variant 一個(2026-07-09 `bare` 退役,詳 field-types.ts FieldVariant note;naked 2026-07-14 型別收窄至 FieldVariantInternal):\n * - `'default'`(預設)— Field wrapper 完整 variant:bg-surface + 明顯 border + hover/focus 回饋。適用表單、Field 內嵌。\n *\n * @internal `'naked'` — 完全無 chrome / 無 border / 無 focus ring。單獨使用無視覺邊界,**不可直接 standalone 用**;僅供 DS 內部 cell-as-input 組合(edit×naked 的 border/hover/focus/error 由 field-wrapper 自渲 state machine 提供,非 host cell 自管;詳 field-wrapper.tsx)。consumer 請用 `default`(2026-07-14 起型別層已擋:公開 FieldVariant 不含 naked,僅 DataTable cell-registry 經 WithFieldVariantInternal 通道傳入)。\n *\n * 透傳:在 `<Field variant=\"default\">` 內自動繼承 context.variant;per-prop override context。\n */\n variant?: FieldVariant\n /** Error 狀態(正交於 mode)。border-error + aria-invalid。 */\n error?: boolean\n /** 左側靜態 icon — 輔助理解 input 用途(如 Search)。fg-muted。 */\n startIcon?: LucideIcon\n /** 右側 inline action — 宣告式 API,Field 根據 size 自動渲染。 */\n endAction?: InlineActionConfig\n /**\n * 右側 slot(ReactNode)— escape hatch 供 consumer 放自訂元素(如 DropdownMenuTrigger asChild + ItemInlineActionButton)。\n * 跟 `endAction` 互斥(同時傳 endSlot 會優先,endAction 被忽略)。\n *\n * **使用情境**:ZoomInput 需要 chevron 作 DropdownMenuTrigger anchor,config-only API 無法做到。\n * **禁止情境**:表單欄位 / 一般 inline action → 用 `endAction` 宣告式 API。\n */\n endSlot?: React.ReactNode\n /**\n * Loading 狀態(async 驗證 / debounce fetch 中)。\n * - **input 保持可編輯**(user 可以邊改邊讀,debounce 場景 UX 最好)\n * - 世界級對照:Ant Input.Search 派(input editable during loading);非 Material readonly 派\n * - 自動在 endAction slot 塞 `<CircularProgress size={iconSize}/>`(與 endAction prop 互斥)\n * - 宣告 `aria-busy=\"true\"` 讓 screen reader 感知處理中\n */\n loading?: boolean\n /**\n * Auto-width:Input 寬度 = 內容寬(value / placeholder 文字寬)+ startIcon + endAction + padding。\n * 使用 CSS `field-sizing: content`(Chrome 123+ / Safari 17.4+;Firefox 還在實驗)。\n *\n * **使用情境**:\n * - Inline edit(VS Code setting row / Figma property toolbar number input)\n * - ZoomInput(FileViewer 縮放比例:輸入「100%」自動縮到三位數寬)\n * - Tag / Chip 內 inline rename\n *\n * **不要用在**:表單 Field(Field 需要欄寬對齊,不該隨值跳動)\n *\n * **fallback**:不支援 `field-sizing` 的瀏覽器會退化為 `w-auto`(wrapper 縮到 content 尺寸,\n * input 為 min-w-0,依 content/placeholder 撐寬)。UX 上稍不一致但不致斷;若必須精準對齊所有瀏覽器,\n * consumer 可自行傳 `style={{ width: ... }}` 顯式寬度,不走 auto。\n */\n autoWidth?: boolean\n}\n\n// ── Component ───────────────────────────────────────────────────────────────\n\n// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding\nconst Input = React.forwardRef<HTMLInputElement, InputProps>(\n (\n {\n mode: modeProp,\n variant: variantProp,\n error = false,\n size: sizeProp,\n startIcon: StartIcon,\n endAction,\n endSlot,\n loading = false,\n autoWidth = false,\n className,\n disabled: disabledProp,\n readOnly,\n value,\n defaultValue,\n id: idProp,\n 'aria-describedby': ariaDescribedByProp,\n 'aria-errormessage': ariaErrorMessageProp,\n ...props\n },\n ref\n ) => {\n // ── FieldContext 自動讀取(在 <Field> 內時,invalid / disabled / mode / chrome 由 context 接管) ──\n const fieldCtx = useFieldContext()\n // 2026-05-31 #11/#12:size + disabled 從 Field context cascade(對齊 NumberInput number-input.tsx:100-101\n // + MUI FormControl)。原 Input 不讀 fieldCtx.size/disabled → <Field size=\"lg\"> / <Field disabled> 對 Input 無效。\n const size = useResolvedFieldSize(sizeProp)\n const disabled = useResolvedFieldDisabled(disabledProp)\n // chrome 透傳:per-prop override context;context 沒值則 'default'\n const variant: FieldVariantInternal = useResolvedFieldVariant(variantProp)\n // 2026-06-08 SSOT:mode 經 useResolvedFieldMode 統一解析(prop > 有效 disabled > fieldCtx.mode > readOnly > 'edit')。\n // loading 期間 input 保持可編輯(Ant Input.Search 派),只用 aria-busy + endAction Spinner,不動 mode。\n const resolvedMode: FieldMode = useResolvedFieldMode({ mode: modeProp, disabled, readOnly })\n const emptyDisplay = useFieldEmptyDisplay()\n const isEditable = resolvedMode === 'edit'\n const isView = resolvedMode === 'view'\n // error 合併:自身 error prop OR Field context invalid\n const resolvedError = useResolvedFieldInvalid(error)\n // 2026-05-18 改 import ICON_SIZE SSOT(per user『做完』approval,消除 M17 違反 7+ 重複 ternary)\n const iconSize = ICON_SIZE[size as 'sm' | 'md' | 'lg']\n const iconColor = resolvedMode === 'disabled' ? 'text-fg-disabled' : 'text-fg-muted'\n\n // ── view mode(+ readonly 空值)純展示,渲染 <span> 取代 <input> ──\n // 對齊 Carbon read-only / PatternFly inline-edit hidden-input / Cloudscape display-mode\n // 2026-07-08 user 拍板:readonly 空值也走 view-span 顯 '-'(readonly **有值** 仍走下方\n // native <input readOnly> 保留選取/複製語意 — field-controls.spec.md「null / undefined 值」)。\n // 2026-07-14 audit Dim 26(dual-mode coherence):uncontrolled `defaultValue` 也要被\n // view / readonly-空值判定認得 — 原本只讀 `value`,uncontrolled Input 切 view 會誤顯 '-'。\n // 2026-07-14 R2(dual-model consensus,取代同日稍早 onChange-mirror 版):resolved value 改走\n // useControllable 內部 SSOT(Radix idiom,同 Select / Pagination 既有消費)。uncontrolled 時\n // native input 由 value={resolved} 內部驅動(defaultValue 只作初始值,不落 DOM attribute),\n // 修 mirror 版兩個真缺陷:\n // (1) remount-stale — 切 view / readonly(native input unmount)再回 edit,native input 從\n // defaultValue attribute 重掛 → 顯示 stale 初始值,與 mirror 判定分歧;\n // (2) form.reset() stale — HTML 標準 reset 不發 input event,onChange mirror 停在 reset 前的值\n // (下方 reset bridge 修)。\n // controlled(傳 value)時 useControllable 純 passthrough,行為與原本完全一致。\n // consumer onChange 不接進 hook(保留 native event signature),於 <input> onChange 另行轉發。\n const isControlled = value !== undefined\n const [resolved, setResolved] = useControllable<string | number | readonly string[]>({\n value: value as string | number | readonly string[] | undefined,\n defaultValue: defaultValue ?? '',\n })\n const displayValue = resolved != null && resolved !== '' ? String(resolved) : null\n const showDisplaySpan = isView || (resolvedMode === 'readonly' && displayValue == null)\n\n // form.reset() bridge(uncontrolled only):reset 恢復 defaultValue 但不發 input event →\n // 手動把 resolved 歸位 defaultValue。keyed on showDisplaySpan:view 分支不掛 native input,\n // 回 edit 重掛時 effect 重跑補掛 listener。\n const innerRef = React.useRef<HTMLInputElement | null>(null)\n React.useEffect(() => {\n if (isControlled || showDisplaySpan) return\n const form = innerRef.current?.form\n if (!form) return\n const handleReset = () => setResolved(defaultValue ?? '')\n form.addEventListener('reset', handleReset)\n return () => form.removeEventListener('reset', handleReset)\n }, [isControlled, showDisplaySpan, defaultValue, setResolved])\n // Merge refs(LinkInput setRef idiom,link-input.tsx:138)\n const setRef = React.useCallback((el: HTMLInputElement | null) => {\n innerRef.current = el\n if (typeof ref === 'function') ref(el)\n else if (ref) (ref as React.MutableRefObject<HTMLInputElement | null>).current = el\n }, [ref])\n\n if (showDisplaySpan) {\n const spanMode = isView ? 'view' : 'readonly'\n return (\n <div\n className={cn(\n fieldWrapperStyles({ mode: spanMode, variant: variant, size }),\n autoWidth && 'inline-flex w-auto',\n className,\n )}\n data-field-mode={spanMode}\n >\n {StartIcon && (\n <ItemPrefix>\n <StartIcon\n size={iconSize}\n className={cn('pointer-events-none', iconColor)}\n aria-hidden\n />\n </ItemPrefix>\n )}\n <span\n className={cn(\n bareInputStyles,\n // B1 fix(2026-05-05):view mode 單行 ellipsis 截斷(對齊 Notion / Airtable / Linear\n // cell display canonical:single-line value 過長 → ellipsis。Textarea view 走 wrap path,\n // 不在此處;Input view 永遠 single-line。)\n 'truncate',\n displayValue == null && fieldEmptyColorClass(resolvedMode),\n )}\n >\n {displayValue ?? emptyDisplay}\n </span>\n </div>\n )\n }\n\n return (\n <div\n className={cn(\n fieldWrapperStyles({ mode: resolvedMode, variant: variant, size, error: resolvedError }),\n // autoWidth:wrapper 縮到 inline-flex + w-auto,讓寬度由 startIcon + input(field-sizing: content)+ endAction 自然累加\n autoWidth && 'inline-flex w-auto',\n className,\n )}\n data-field-mode={resolvedMode}\n data-error={isEditable && resolvedError ? '' : undefined}\n aria-busy={loading || undefined}\n >\n {StartIcon && (\n <ItemPrefix>\n <StartIcon\n size={iconSize}\n className={cn('pointer-events-none', iconColor)}\n aria-hidden\n />\n </ItemPrefix>\n )}\n <input\n ref={setRef}\n type=\"text\"\n id={idProp ?? fieldCtx?.id}\n value={resolved}\n readOnly={resolvedMode === 'readonly'}\n disabled={resolvedMode === 'disabled'}\n aria-invalid={resolvedError || undefined}\n aria-required={fieldCtx?.required || undefined}\n aria-describedby={ariaDescribedByProp ?? fieldCtx?.descriptionId}\n aria-errormessage={ariaErrorMessageProp ?? (resolvedError ? fieldCtx?.errorId : undefined)}\n className={cn(\n bareInputStyles,\n resolvedMode === 'disabled' && 'text-fg-disabled placeholder:text-fg-disabled cursor-not-allowed',\n // autoWidth:input 本身 field-sizing:content(Chrome 123+ / Safari 17.4+),寬度跟 value 文字寬。\n // w-auto 關掉預設 w-full;min-w-0 讓 flex shrink 不卡住。\n autoWidth && '[field-sizing:content] w-auto min-w-0',\n )}\n {...props}\n // 置於 {...props} 後:寫回 resolved SSOT(controlled 時 useControllable 內部為 no-op)再轉發 consumer onChange。\n onChange={(e) => {\n setResolved(e.target.value)\n props.onChange?.(e)\n }}\n />\n {loading ? (\n <CircularProgress size={iconSize} className=\"shrink-0\" />\n ) : endSlot && isEditable ? (\n // endSlot escape hatch:consumer 自控右側 slot(如 DropdownMenuTrigger asChild wrap)\n endSlot\n ) : endAction && isEditable ? (\n <ItemInlineAction action={endAction} size={size ?? 'md'} />\n ) : null}\n </div>\n )\n }\n)\nInput.displayName = 'Input'\n\n// Phase B1(2026-05-05):InputDisplay 退場。改用 `<Input mode=\"view\" value={...} />`\n// 對齊 Carbon read-only / PatternFly inline-edit hidden-input / Cloudscape display-mode 統一 mode 模型。\n\n// Story auto-compile metadata — Phase 1 mechanical migration(2026-04-24)\n// 2026-07-05 Phase 2 fill(deep-audit A.1b):variants 按 input.tsx variant jsdoc、sizes 按\n// fieldWrapperStyles cva 真值(h-field-sm/md/lg = 28/32/36,uiSize.css)+ ICON_SIZE SSOT\n// (sm/md 16、lg 20)填。`naked` 為 @internal cell-as-input substrate → 不列入 public variants。\nexport const inputMeta = {\n component: 'Input',\n family: 4,\n variants: {\n default: { purpose: 'Field wrapper 完整 chrome(bg-surface + border + hover/focus 回饋)— 表單 / Field 內嵌' },\n // (2026-07-09 `bare` variant 退役 — 見 field-types.ts FieldVariant note)\n },\n sizes: {\n sm: { fieldHeight: 28, iconSize: 16, typography: 'body' },\n md: { fieldHeight: 32, iconSize: 16, typography: 'body' },\n lg: { fieldHeight: 36, iconSize: 20, typography: 'body-lg' },\n },\n // 'active' 移除 — Field chrome 無按壓態(對齊 Textarea / LinkInput meta 既有寫法)(2026-07-07 詞彙統一 DS-wide 按壓訊號盤點:檔內 0 active: utility / 0 *-active token)。\n states: ['default', 'hover', 'focus-visible', 'disabled'],\n tokens: {\n bg: ['bg-surface'],\n fg: ['text-fg-disabled', 'text-fg-muted'],\n ring: [],\n },\n} as const\n\nexport { Input }\n"],"names":[],"mappings":"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"}
|
|
@@ -9,7 +9,7 @@ export interface LinkInputProps extends Omit<React.InputHTMLAttributes<HTMLInput
|
|
|
9
9
|
* - `'default'`(預設)— Field wrapper 完整 chrome(form / Field 內嵌)。
|
|
10
10
|
* (2026-07-09 `bare` variant 退役;naked = cell-as-input substrate,@internal)
|
|
11
11
|
*
|
|
12
|
-
* mode='
|
|
12
|
+
* mode='view' 時 chrome 無視覺意義(view 完全無 wrapper);chrome 僅作用於 edit / readonly / disabled。
|
|
13
13
|
*/
|
|
14
14
|
variant?: FieldVariant;
|
|
15
15
|
error?: boolean;
|
|
@@ -21,11 +21,11 @@ export interface LinkInputProps extends Omit<React.InputHTMLAttributes<HTMLInput
|
|
|
21
21
|
/** 自訂顯示文字(非編輯時) */
|
|
22
22
|
label?: string;
|
|
23
23
|
/**
|
|
24
|
-
*
|
|
25
|
-
* — DataTable cell
|
|
26
|
-
* 設 true 時
|
|
24
|
+
* View 態是否包 Field naked wrapper(D-path opt-in,2026-05-08)
|
|
25
|
+
* — DataTable cell view↔edit 像素級對齊用。預設 false(裸 anchor,backward compat)。
|
|
26
|
+
* 設 true 時 view 走 fieldWrapperStyles(naked variant)包覆 anchor,
|
|
27
27
|
* 與 cell edit (`<Input naked>`) 同 DOM 結構,消除 Layer-B padding mismatch。
|
|
28
|
-
* **本元件 edit 無 endIcon(UrlCell 用 plain Input edit)→
|
|
28
|
+
* **本元件 edit 無 endIcon(UrlCell 用 plain Input edit)→ view 也無 ItemSuffix**(僅 wrapper)。
|
|
29
29
|
*/
|
|
30
30
|
showDisplayEndIcon?: boolean;
|
|
31
31
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"link-input.d.ts","sourceRoot":"","sources":["../../../src/components/LinkInput/link-input.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAE5D,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAwB,MAAM,8CAA8C,CAAA;AACjH,OAAO,EAAE,kBAAkB,EAA0C,MAAM,gDAAgD,CAAA;AA4C3H,MAAM,WAAW,cAIf,SAAQ,IAAI,CAAC,KAAK,CAAC,mBAAmB,CAAC,gBAAgB,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,cAAc,GAAG,UAAU,CAAC,EACvG,IAAI,CAAC,YAAY,CAAC,OAAO,kBAAkB,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC;IAC7E,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,YAAY,CAAA;IACtB,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IAClC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,mBAAmB;IACnB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;;;;;;OAMG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAA;CAC7B;AAGD,QAAA,MAAM,SAAS,
|
|
1
|
+
{"version":3,"file":"link-input.d.ts","sourceRoot":"","sources":["../../../src/components/LinkInput/link-input.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAE5D,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAwB,MAAM,8CAA8C,CAAA;AACjH,OAAO,EAAE,kBAAkB,EAA0C,MAAM,gDAAgD,CAAA;AA4C3H,MAAM,WAAW,cAIf,SAAQ,IAAI,CAAC,KAAK,CAAC,mBAAmB,CAAC,gBAAgB,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,cAAc,GAAG,UAAU,CAAC,EACvG,IAAI,CAAC,YAAY,CAAC,OAAO,kBAAkB,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC;IAC7E,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,YAAY,CAAA;IACtB,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IAClC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,mBAAmB;IACnB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;;;;;;OAMG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAA;CAC7B;AAGD,QAAA,MAAM,SAAS,yFAsNd,CAAA;AAKD,eAAO,MAAM,aAAa;;;;;;;;;;;CAkBhB,CAAA;AAEV,OAAO,EAAE,SAAS,EAAE,CAAA"}
|
|
@@ -77,7 +77,7 @@ const LinkInput = React.forwardRef(
|
|
|
77
77
|
React.useEffect(() => () => {
|
|
78
78
|
if (focusRafIdRef.current) cancelAnimationFrame(focusRafIdRef.current);
|
|
79
79
|
}, []);
|
|
80
|
-
if (resolvedMode === "
|
|
80
|
+
if (resolvedMode === "view") {
|
|
81
81
|
if (!showDisplayEndIcon) {
|
|
82
82
|
if (!value) return /* @__PURE__ */ jsx("span", { className: cn(fieldDisplayTextClass(size), fieldEmptyColorClass(resolvedMode), "block truncate"), children: emptyDisplay });
|
|
83
83
|
return /* @__PURE__ */ jsx("span", { className: cn(fieldDisplayTextClass(size), "block truncate"), children: renderLinkAnchor(value, label) });
|
|
@@ -85,8 +85,8 @@ const LinkInput = React.forwardRef(
|
|
|
85
85
|
return /* @__PURE__ */ jsx(
|
|
86
86
|
"div",
|
|
87
87
|
{
|
|
88
|
-
className: cn(fieldWrapperStyles({ mode: "
|
|
89
|
-
"data-field-mode": "
|
|
88
|
+
className: cn(fieldWrapperStyles({ mode: "view", variant: resolvedVariant, size }), className),
|
|
89
|
+
"data-field-mode": "view",
|
|
90
90
|
children: /* @__PURE__ */ jsx("span", { className: "flex-1 min-w-0 truncate", children: value ? renderLinkAnchor(value, label) : /* @__PURE__ */ jsx("span", { className: fieldEmptyColorClass(resolvedMode), children: emptyDisplay }) })
|
|
91
91
|
}
|
|
92
92
|
);
|