@zerodep-dev/vue-tsx-pro-ui 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/base/Badge.js +3 -3
- package/dist/base/Badge.js.map +1 -1
- package/dist/base/Can.d.ts +12 -0
- package/dist/base/Can.js +20 -0
- package/dist/base/Can.js.map +1 -0
- package/dist/base/ClientOnly.d.ts +10 -0
- package/dist/base/ClientOnly.js +18 -0
- package/dist/base/ClientOnly.js.map +1 -0
- package/dist/base/CloseButton.js +3 -3
- package/dist/base/CloseButton.js.map +1 -1
- package/dist/base/CodeBlock.js +5 -5
- package/dist/base/CodeBlock.js.map +1 -1
- package/dist/base/CopyButton.js +3 -3
- package/dist/base/CopyButton.js.map +1 -1
- package/dist/base/Icon.js +3 -3
- package/dist/base/Icon.js.map +1 -1
- package/dist/base/Indicator.js +3 -3
- package/dist/base/Indicator.js.map +1 -1
- package/dist/base/StatusDot.js +3 -3
- package/dist/base/StatusDot.js.map +1 -1
- package/dist/base/Tag.js +5 -5
- package/dist/base/Tag.js.map +1 -1
- package/dist/base/button-core.js +4 -4
- package/dist/base/button-core.js.map +1 -1
- package/dist/charts/AreaChart.js +3 -3
- package/dist/charts/AreaChart.js.map +1 -1
- package/dist/charts/BarChart.js +3 -3
- package/dist/charts/BarChart.js.map +1 -1
- package/dist/charts/ChordChart.js +3 -3
- package/dist/charts/ChordChart.js.map +1 -1
- package/dist/charts/ComboChart.js +3 -3
- package/dist/charts/ComboChart.js.map +1 -1
- package/dist/charts/DonutChart.js +3 -3
- package/dist/charts/DonutChart.js.map +1 -1
- package/dist/charts/FunnelChart.js +5 -5
- package/dist/charts/FunnelChart.js.map +1 -1
- package/dist/charts/GraphChart.js +3 -3
- package/dist/charts/GraphChart.js.map +1 -1
- package/dist/charts/LineChart.js +3 -3
- package/dist/charts/LineChart.js.map +1 -1
- package/dist/charts/MapChart.js +7 -7
- package/dist/charts/MapChart.js.map +1 -1
- package/dist/charts/RadarChart.js +16 -15
- package/dist/charts/RadarChart.js.map +1 -1
- package/dist/charts/SankeyChart.js +3 -3
- package/dist/charts/SankeyChart.js.map +1 -1
- package/dist/charts/ScatterChart.js +3 -3
- package/dist/charts/ScatterChart.js.map +1 -1
- package/dist/charts/TimelineChart.js +6 -5
- package/dist/charts/TimelineChart.js.map +1 -1
- package/dist/charts/TreemapChart.js +3 -3
- package/dist/charts/TreemapChart.js.map +1 -1
- package/dist/charts/WaterfallChart.js +3 -3
- package/dist/charts/WaterfallChart.js.map +1 -1
- package/dist/charts/XYChart.js +9 -7
- package/dist/charts/XYChart.js.map +1 -1
- package/dist/charts/frame.js +10 -8
- package/dist/charts/frame.js.map +1 -1
- package/dist/charts/single.js +4 -3
- package/dist/charts/single.js.map +1 -1
- package/dist/config/Provider.d.ts +3 -1
- package/dist/config/Provider.js +9 -4
- package/dist/config/Provider.js.map +1 -1
- package/dist/config/context.d.ts +18 -0
- package/dist/config/context.js +16 -1
- package/dist/config/context.js.map +1 -1
- package/dist/config/createUi.d.ts +20 -3
- package/dist/config/createUi.js +80 -49
- package/dist/config/createUi.js.map +1 -1
- package/dist/config/layout/preference.d.ts +43 -0
- package/dist/config/layout/preference.js +108 -0
- package/dist/config/layout/preference.js.map +1 -0
- package/dist/date/Calendar.js +3 -3
- package/dist/date/Calendar.js.map +1 -1
- package/dist/date/CalendarBoard.js +9 -9
- package/dist/date/CalendarBoard.js.map +1 -1
- package/dist/date/CalendarView.js +7 -7
- package/dist/date/CalendarView.js.map +1 -1
- package/dist/date/DateField.js +3 -3
- package/dist/date/DateField.js.map +1 -1
- package/dist/date/DatePicker.js +5 -5
- package/dist/date/DatePicker.js.map +1 -1
- package/dist/date/DateRangePicker.js +5 -5
- package/dist/date/DateRangePicker.js.map +1 -1
- package/dist/date/DateSegmentGroup.js +5 -5
- package/dist/date/DateSegmentGroup.js.map +1 -1
- package/dist/date/QuarterCalendar.js +6 -6
- package/dist/date/QuarterCalendar.js.map +1 -1
- package/dist/date/RangeCalendar.js +3 -3
- package/dist/date/RangeCalendar.js.map +1 -1
- package/dist/date/SegmentedField.js +3 -3
- package/dist/date/SegmentedField.js.map +1 -1
- package/dist/date/TimeField.js +3 -3
- package/dist/date/TimeField.js.map +1 -1
- package/dist/date/TimePicker.js +4 -4
- package/dist/date/TimePicker.js.map +1 -1
- package/dist/date/date-segments.js +1 -1
- package/dist/date/date-segments.js.map +1 -1
- package/dist/date/picker-popover.js +3 -2
- package/dist/date/picker-popover.js.map +1 -1
- package/dist/diagram/Diagram.js +12 -12
- package/dist/diagram/Diagram.js.map +1 -1
- package/dist/diagram/DiagramLanes.js +3 -3
- package/dist/diagram/DiagramLanes.js.map +1 -1
- package/dist/diagram/DiagramMinimap.js +4 -4
- package/dist/diagram/DiagramMinimap.js.map +1 -1
- package/dist/display/Accordion.js +6 -6
- package/dist/display/Accordion.js.map +1 -1
- package/dist/display/AnimatedNumber.js +4 -4
- package/dist/display/AnimatedNumber.js.map +1 -1
- package/dist/display/Avatar.js +4 -4
- package/dist/display/Avatar.js.map +1 -1
- package/dist/display/Checklist.js +7 -7
- package/dist/display/Checklist.js.map +1 -1
- package/dist/display/Collapse.js +8 -5
- package/dist/display/Collapse.js.map +1 -1
- package/dist/display/Collapsible.js +3 -3
- package/dist/display/Collapsible.js.map +1 -1
- package/dist/display/Descriptions.js +5 -5
- package/dist/display/Descriptions.js.map +1 -1
- package/dist/display/Ellipsis.js +3 -3
- package/dist/display/Ellipsis.js.map +1 -1
- package/dist/display/FieldCompare.js +4 -4
- package/dist/display/FieldCompare.js.map +1 -1
- package/dist/display/Gauge.js +11 -11
- package/dist/display/Gauge.js.map +1 -1
- package/dist/display/Image.js +4 -4
- package/dist/display/Image.js.map +1 -1
- package/dist/display/Marquee.js +3 -3
- package/dist/display/Marquee.js.map +1 -1
- package/dist/display/ProportionBar.js +4 -4
- package/dist/display/ProportionBar.js.map +1 -1
- package/dist/display/Sortable.js +4 -4
- package/dist/display/Sortable.js.map +1 -1
- package/dist/display/Sparkline.js +6 -6
- package/dist/display/Sparkline.js.map +1 -1
- package/dist/display/Stat.js +3 -3
- package/dist/display/Stat.js.map +1 -1
- package/dist/display/Timeline.js +7 -7
- package/dist/display/Timeline.js.map +1 -1
- package/dist/display/Tree.js +10 -10
- package/dist/display/Tree.js.map +1 -1
- package/dist/display/VirtualList.js +7 -7
- package/dist/display/VirtualList.js.map +1 -1
- package/dist/display/Watermark.js +3 -3
- package/dist/display/Watermark.js.map +1 -1
- package/dist/docs-kit/CodeGroup.js +5 -5
- package/dist/docs-kit/CodeGroup.js.map +1 -1
- package/dist/docs-kit/CodePreview.js +6 -6
- package/dist/docs-kit/CodePreview.js.map +1 -1
- package/dist/docs-kit/PropsTable.js +4 -4
- package/dist/docs-kit/PropsTable.js.map +1 -1
- package/dist/feedback/Alert.js +5 -5
- package/dist/feedback/Alert.js.map +1 -1
- package/dist/feedback/Banner.js +5 -5
- package/dist/feedback/Banner.js.map +1 -1
- package/dist/feedback/Empty.js +4 -4
- package/dist/feedback/Empty.js.map +1 -1
- package/dist/feedback/LoadingBar.js +20 -13
- package/dist/feedback/LoadingBar.js.map +1 -1
- package/dist/feedback/LoadingOverlay.js +3 -3
- package/dist/feedback/LoadingOverlay.js.map +1 -1
- package/dist/feedback/Progress.js +14 -14
- package/dist/feedback/Progress.js.map +1 -1
- package/dist/feedback/Result.js +4 -4
- package/dist/feedback/Result.js.map +1 -1
- package/dist/feedback/SkeletonText.js +3 -3
- package/dist/feedback/SkeletonText.js.map +1 -1
- package/dist/feedback/Toast.js +6 -6
- package/dist/feedback/Toast.js.map +1 -1
- package/dist/form/Field.js +7 -7
- package/dist/form/Field.js.map +1 -1
- package/dist/form/Form.js +3 -3
- package/dist/form/Form.js.map +1 -1
- package/dist/form/FormErrorSummary.js +3 -3
- package/dist/form/FormErrorSummary.js.map +1 -1
- package/dist/form/FormSection.js +6 -6
- package/dist/form/FormSection.js.map +1 -1
- package/dist/index.d.ts +11 -6
- package/dist/index.js +10 -5
- package/dist/input/Autocomplete.js +3 -3
- package/dist/input/Autocomplete.js.map +1 -1
- package/dist/input/Cascader.js +7 -7
- package/dist/input/Cascader.js.map +1 -1
- package/dist/input/CharacterCount.js +3 -3
- package/dist/input/CharacterCount.js.map +1 -1
- package/dist/input/Checkbox.js +5 -5
- package/dist/input/Checkbox.js.map +1 -1
- package/dist/input/ColorField.js +4 -4
- package/dist/input/ColorField.js.map +1 -1
- package/dist/input/Combobox.js +4 -4
- package/dist/input/Combobox.js.map +1 -1
- package/dist/input/ConditionBuilder.js +15 -14
- package/dist/input/ConditionBuilder.js.map +1 -1
- package/dist/input/FileDrop.js +3 -3
- package/dist/input/FileDrop.js.map +1 -1
- package/dist/input/Listbox.js +9 -9
- package/dist/input/Listbox.js.map +1 -1
- package/dist/input/Mentions.js +3 -3
- package/dist/input/Mentions.js.map +1 -1
- package/dist/input/NumberField.js +3 -3
- package/dist/input/NumberField.js.map +1 -1
- package/dist/input/NumberRangeField.js +5 -5
- package/dist/input/NumberRangeField.js.map +1 -1
- package/dist/input/OtpField.js +5 -5
- package/dist/input/OtpField.js.map +1 -1
- package/dist/input/PeoplePicker.js +6 -6
- package/dist/input/PeoplePicker.js.map +1 -1
- package/dist/input/PeoplePickerPanel.js +4 -4
- package/dist/input/PeoplePickerPanel.js.map +1 -1
- package/dist/input/Radio.js +4 -4
- package/dist/input/Radio.js.map +1 -1
- package/dist/input/Rating.js +5 -5
- package/dist/input/Rating.js.map +1 -1
- package/dist/input/Select.js +4 -4
- package/dist/input/Select.js.map +1 -1
- package/dist/input/SelectedTags.js +4 -4
- package/dist/input/SelectedTags.js.map +1 -1
- package/dist/input/Slider.js +6 -6
- package/dist/input/Slider.js.map +1 -1
- package/dist/input/Switch.js +3 -3
- package/dist/input/Switch.js.map +1 -1
- package/dist/input/TagsInput.js +5 -5
- package/dist/input/TagsInput.js.map +1 -1
- package/dist/input/TextField.js +5 -5
- package/dist/input/TextField.js.map +1 -1
- package/dist/input/Textarea.js +4 -4
- package/dist/input/Textarea.js.map +1 -1
- package/dist/input/ToggleGroup.js +4 -4
- package/dist/input/ToggleGroup.js.map +1 -1
- package/dist/input/Transfer.js +8 -8
- package/dist/input/Transfer.js.map +1 -1
- package/dist/input/TreeSelect.js +6 -6
- package/dist/input/TreeSelect.js.map +1 -1
- package/dist/input/Upload.js +8 -8
- package/dist/input/Upload.js.map +1 -1
- package/dist/layout/Affix.js +4 -4
- package/dist/layout/Affix.js.map +1 -1
- package/dist/layout/ResizeHandle.d.ts +6 -2
- package/dist/layout/ResizeHandle.js +32 -9
- package/dist/layout/ResizeHandle.js.map +1 -1
- package/dist/layout/ScrollArea.js +3 -3
- package/dist/layout/ScrollArea.js.map +1 -1
- package/dist/layout/Separator.js +3 -3
- package/dist/layout/Separator.js.map +1 -1
- package/dist/layout/Splitter.js +3 -3
- package/dist/layout/Splitter.js.map +1 -1
- package/dist/layout/ViewportFill.d.ts +2 -2
- package/dist/layout/ViewportFill.js +4 -4
- package/dist/layout/ViewportFill.js.map +1 -1
- package/dist/navigation/Breadcrumbs.js +4 -4
- package/dist/navigation/Breadcrumbs.js.map +1 -1
- package/dist/navigation/NavBar.js +6 -6
- package/dist/navigation/NavBar.js.map +1 -1
- package/dist/navigation/NavList.d.ts +1 -0
- package/dist/navigation/NavList.js +20 -12
- package/dist/navigation/NavList.js.map +1 -1
- package/dist/navigation/NavMenu.d.ts +27 -0
- package/dist/navigation/NavMenu.js +54 -0
- package/dist/navigation/NavMenu.js.map +1 -0
- package/dist/navigation/PageJump.js +3 -3
- package/dist/navigation/PageJump.js.map +1 -1
- package/dist/navigation/PageSizeSelect.js +4 -4
- package/dist/navigation/PageSizeSelect.js.map +1 -1
- package/dist/navigation/Pagination.js +6 -6
- package/dist/navigation/Pagination.js.map +1 -1
- package/dist/navigation/PaginationBar.js +5 -5
- package/dist/navigation/PaginationBar.js.map +1 -1
- package/dist/navigation/Steps.js +4 -4
- package/dist/navigation/Steps.js.map +1 -1
- package/dist/navigation/TableOfContents.js +5 -5
- package/dist/navigation/TableOfContents.js.map +1 -1
- package/dist/navigation/Tabs.js +7 -7
- package/dist/navigation/Tabs.js.map +1 -1
- package/dist/navigation/Toolbar.js +3 -3
- package/dist/navigation/Toolbar.js.map +1 -1
- package/dist/overlay/AlertDialog.js +4 -4
- package/dist/overlay/AlertDialog.js.map +1 -1
- package/dist/overlay/CommandPalette.js +4 -4
- package/dist/overlay/CommandPalette.js.map +1 -1
- package/dist/overlay/ContextMenu.js +3 -3
- package/dist/overlay/ContextMenu.js.map +1 -1
- package/dist/overlay/Dialog.js +4 -4
- package/dist/overlay/Dialog.js.map +1 -1
- package/dist/overlay/Drawer.js +5 -5
- package/dist/overlay/Drawer.js.map +1 -1
- package/dist/overlay/HoverCard.js +5 -3
- package/dist/overlay/HoverCard.js.map +1 -1
- package/dist/overlay/Lightbox.js +5 -5
- package/dist/overlay/Lightbox.js.map +1 -1
- package/dist/overlay/Menu.js +7 -7
- package/dist/overlay/Menu.js.map +1 -1
- package/dist/overlay/ModalCore.js +9 -9
- package/dist/overlay/ModalCore.js.map +1 -1
- package/dist/overlay/Popconfirm.js +6 -6
- package/dist/overlay/Popconfirm.js.map +1 -1
- package/dist/overlay/Popover.js +5 -3
- package/dist/overlay/Popover.js.map +1 -1
- package/dist/overlay/Tooltip.js +5 -3
- package/dist/overlay/Tooltip.js.map +1 -1
- package/dist/overlay/Tour.js +8 -8
- package/dist/overlay/Tour.js.map +1 -1
- package/dist/overlay/detached.d.ts +2 -0
- package/dist/overlay/detached.js +73 -42
- package/dist/overlay/detached.js.map +1 -1
- package/dist/patterns/ActionBar.js +3 -3
- package/dist/patterns/ActionBar.js.map +1 -1
- package/dist/patterns/AppShell.d.ts +28 -1
- package/dist/patterns/AppShell.js +158 -52
- package/dist/patterns/AppShell.js.map +1 -1
- package/dist/patterns/AuthLayout.d.ts +28 -0
- package/dist/patterns/AuthLayout.js +77 -0
- package/dist/patterns/AuthLayout.js.map +1 -0
- package/dist/patterns/CommentThread.js +9 -9
- package/dist/patterns/CommentThread.js.map +1 -1
- package/dist/patterns/DataImport.js +4 -4
- package/dist/patterns/DataImport.js.map +1 -1
- package/dist/patterns/DeadlineBadge.js +3 -3
- package/dist/patterns/DeadlineBadge.js.map +1 -1
- package/dist/patterns/FilterBar.d.ts +11 -1
- package/dist/patterns/FilterBar.js +39 -10
- package/dist/patterns/FilterBar.js.map +1 -1
- package/dist/patterns/FormDialog.d.ts +18 -0
- package/dist/patterns/FormDialog.js +46 -0
- package/dist/patterns/FormDialog.js.map +1 -0
- package/dist/patterns/KanbanBoard.js +8 -8
- package/dist/patterns/KanbanBoard.js.map +1 -1
- package/dist/patterns/LayoutSwitcher.d.ts +12 -0
- package/dist/patterns/LayoutSwitcher.js +79 -0
- package/dist/patterns/LayoutSwitcher.js.map +1 -0
- package/dist/patterns/NotificationCenter.js +8 -8
- package/dist/patterns/NotificationCenter.js.map +1 -1
- package/dist/patterns/PageTabs.d.ts +12 -7
- package/dist/patterns/PageTabs.js +41 -29
- package/dist/patterns/PageTabs.js.map +1 -1
- package/dist/patterns/QueryTable.d.ts +7 -1
- package/dist/patterns/QueryTable.js +116 -81
- package/dist/patterns/QueryTable.js.map +1 -1
- package/dist/patterns/SlideCaptcha.d.ts +80 -0
- package/dist/patterns/SlideCaptcha.js +354 -0
- package/dist/patterns/SlideCaptcha.js.map +1 -0
- package/dist/patterns/StepsForm.js +7 -7
- package/dist/patterns/StepsForm.js.map +1 -1
- package/dist/patterns/ThemeSwitcher.js +3 -3
- package/dist/patterns/ThemeSwitcher.js.map +1 -1
- package/dist/patterns/index.d.ts +7 -3
- package/dist/patterns/index.js +5 -1
- package/dist/patterns/messages.d.ts +37 -1
- package/dist/patterns/messages.js +38 -2
- package/dist/patterns/messages.js.map +1 -1
- package/dist/table/ColumnHeaderActions.js +3 -3
- package/dist/table/ColumnHeaderActions.js.map +1 -1
- package/dist/table/ColumnResizer.js +3 -3
- package/dist/table/ColumnResizer.js.map +1 -1
- package/dist/table/ColumnSettings.js +5 -5
- package/dist/table/ColumnSettings.js.map +1 -1
- package/dist/table/ColumnTable.js +8 -8
- package/dist/table/ColumnTable.js.map +1 -1
- package/dist/table/DataTable.js +8 -8
- package/dist/table/DataTable.js.map +1 -1
- package/dist/table/DataTableView.js +21 -21
- package/dist/table/DataTableView.js.map +1 -1
- package/dist/table/PivotTable.js +3 -3
- package/dist/table/PivotTable.js.map +1 -1
- package/dist/table/SelectionBar.js +4 -4
- package/dist/table/SelectionBar.js.map +1 -1
- package/dist/table/TreeDataTable.js +5 -5
- package/dist/table/TreeDataTable.js.map +1 -1
- package/dist/theme-customize/ThemeCustomizer.js +7 -6
- package/dist/theme-customize/ThemeCustomizer.js.map +1 -1
- package/dist/theme-customize/ThemeThumbnail.js +3 -3
- package/dist/theme-customize/ThemeThumbnail.js.map +1 -1
- package/dist/three/background/AuroraBackground.js +3 -3
- package/dist/three/background/AuroraBackground.js.map +1 -1
- package/dist/three/background/MeshGradient.js +3 -3
- package/dist/three/background/MeshGradient.js.map +1 -1
- package/dist/three/background/ShaderBackground.js +3 -3
- package/dist/three/background/ShaderBackground.js.map +1 -1
- package/dist/three/core/EffectSurface.js +3 -3
- package/dist/three/core/EffectSurface.js.map +1 -1
- package/dist/three/core/ThreeCanvas.js +4 -4
- package/dist/three/core/ThreeCanvas.js.map +1 -1
- package/dist/three/effects/Confetti.js +3 -3
- package/dist/three/effects/Confetti.js.map +1 -1
- package/dist/three/effects/ParticleField.js +3 -3
- package/dist/three/effects/ParticleField.js.map +1 -1
- package/dist/three/effects/WaveGrid.js +3 -3
- package/dist/three/effects/WaveGrid.js.map +1 -1
- package/dist/three/globe/Globe.js +3 -3
- package/dist/three/globe/Globe.js.map +1 -1
- package/dist/three/model/ModelViewer.js +3 -3
- package/dist/three/model/ModelViewer.js.map +1 -1
- package/dist/utils/data/number.d.ts +5 -0
- package/dist/utils/data/number.js +8 -1
- package/dist/utils/data/number.js.map +1 -1
- package/dist/utils/dom/element.d.ts +3 -1
- package/dist/utils/dom/element.js +8 -4
- package/dist/utils/dom/element.js.map +1 -1
- package/dist/utils/dom/fullscreen.d.ts +1 -1
- package/dist/utils/dom/fullscreen.js +12 -8
- package/dist/utils/dom/fullscreen.js.map +1 -1
- package/dist/utils/dom/mounted.d.ts +7 -0
- package/dist/utils/dom/mounted.js +16 -0
- package/dist/utils/dom/mounted.js.map +1 -0
- package/dist/utils/dom/overflow.js +2 -1
- package/dist/utils/dom/overflow.js.map +1 -1
- package/dist/utils/dom/pan-zoom.js +2 -1
- package/dist/utils/dom/pan-zoom.js.map +1 -1
- package/dist/utils/props.js +3 -13
- package/dist/utils/props.js.map +1 -1
- package/dist/utils/request/http.d.ts +12 -0
- package/dist/utils/request/http.js +6 -2
- package/dist/utils/request/http.js.map +1 -1
- package/dist/utils/request/use-request.js +4 -2
- package/dist/utils/request/use-request.js.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Toast.js","names":["_isVNode","_vtp_readonlyProp","_createVNode","_mergeProps","_vtp_restProps","_vue_shallowRef","_vue_ref"],"sources":["../../src/feedback/Toast.tsx"],"sourcesContent":["/**\n * 通知(toast):操作结果的短暂提示,几秒后自动消失。控制器 + 显示区域(参考 Sonner、Radix Toast、React Aria Toast):\n *\n * // toaster.ts:应用里创建一次,组件内外(请求拦截器、store)都能导入使用\n * export const toaster = createToaster();\n * // App:放一个显示区域\n * <Toaster controller={toaster} placement=\"bottom-end\" />\n * // 任意位置\n * toaster.show({ tone: 'success', title: '已保存' }); // 或 toaster.success('已保存')\n * const id = toaster.show({ title: '已删除', action: <Button onClick={() => undo(id)}>撤销</Button> });\n * toaster.loading('正在导出', { id: 'export' }); …; toaster.success('导出完成', { id: 'export' }); // 同一个 id 换掉原来那条\n * await toaster.promise(upload(file), { loading: { title: '正在上传' }, success: { title: '上传完成' }, error: (e) => ({ title: String(e) }) });\n *\n * 控制器是普通对象,不经 Provider 注入(COMPONENTS.md §1 的“命令式能力”),所以组件外也能用。\n * 写了 id 的通知不重复:同一个 id 再 show 时换掉那一条(重新计时),不再加一条(接口重试、连点保存时只留一条)。\n *\n * - 每条通知就是 Alert(surface 外观加阴影),图标、标题、正文、操作、关闭按钮都复用它;加载中用 Alert 的 loading。\n * - 计时:默认 5 秒;指针在区域上、焦点在区域里、页面切到后台时暂停;加载中的、duration 为 Infinity 的不自动消失。\n * - 无障碍:区域是带名称的 landmark(section),F8 跳到通知区域,焦点在一条通知里时 Esc 关闭它;\n * 列表 aria-live=\"polite\",新通知被读屏念出。\n * 关闭一条后焦点交给相邻的通知,没有时回到进入通知区域之前的位置。\n * - 区域传送到 body(或最上层的模态里),用 ConfigScope 带上当前的主题与语言。\n * - 顶层:区域是 popover=\"manual\",显示在页面之上;模态打开时外面的元素都是 inert 的(顶层也不例外),\n * 所以这时区域移进最上层的模态 dialog 里(topModal),通知里的按钮仍可点击、可 Tab 到。\n * - 动画:进场淡入并从边缘移入(@starting-style),离场时高度收起(复用 Collapse)后从列表移除。\n * - 横向滑动(触摸、鼠标拖动)超过宽度的 35% 就关闭,否则弹回(startMove;同 Sonner、Radix Toast 的滑动关闭)。\n */\nimport { utils } from 'animejs';\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport {\n nextTick,\n onBeforeUnmount,\n onMounted,\n shallowReactive,\n Teleport,\n watch,\n type VNodeChild,\n} from 'vue';\nimport type { IconData } from '../base/Icon';\nimport { useLang } from '../config/context';\nimport { ConfigScope } from '../config/Provider';\nimport { Collapse, type CollapseProps } from '../display/Collapse';\nimport { useAnimate } from '../utils/motion/animation';\nimport { useLayoutAnimation } from '../utils/motion/layout';\nimport { usePresence } from '../utils/motion/presence';\nimport { startMove } from '../utils/dom/move';\nimport { topModal } from '../utils/dom/overlay';\nimport { toElement } from '../utils/dom/element';\nimport { useHotkeys, type HotkeyMap } from '../utils/dom/hotkeys';\nimport { focusAfterRemoval } from '../utils/dom/focus';\nimport { usePageVisible } from '../utils/dom/page-visibility';\nimport { mergeAttrs, mergePartProps, type PartProps } from '../utils/props';\nimport type { StatusTone } from '../utils/style/tone-styles';\nimport { Alert, type AlertProps } from './Alert';\nimport { FlexOrderedList, type FlexProps } from '../layout/Flex';\nimport { t } from '../config/theme/tokens';\n\n/** 一条通知的内容与行为 */\nexport interface ToastOptions {\n /** 标题(加粗的第一行) */\n title?: VNodeChild;\n /** 正文 */\n description?: VNodeChild;\n /** 色调,默认 neutral */\n tone?: StatusTone;\n /** 图标;不写时按色调选,false 不显示 */\n icon?: IconData | false;\n /** 进行中:图标换成加载指示,不自动消失 */\n loading?: boolean;\n /** 右侧的操作(如“撤销”按钮) */\n action?: VNodeChild;\n /** 显示关闭按钮,默认 true */\n closable?: boolean;\n /** 显示多久(毫秒)后自动消失;Infinity 不自动消失;不写时用 createToaster 的默认值 */\n duration?: number;\n /** 重要:读屏立即打断播报(aria-live=\"assertive\"),用于出错、需要马上处理的通知;默认按礼貌的顺序念 */\n important?: boolean;\n /** 编号:同一个编号的通知只有一条,再 show 时换掉它;不写时自动生成 */\n id?: string;\n /** 开始关闭时调用(自动消失或被关闭);同一条通知只调用一次 */\n onClose?: (reason: keyof ToastCloseReasons) => void;\n}\n\n/** 快捷方法的选项:标题由第一个参数给出 */\nexport type ToastShortcutOptions = Omit<ToastOptions, 'title' | 'tone'>;\n\n/** 通知为什么关闭 */\nexport interface ToastCloseReasons {\n /** 到时间自动消失 */\n timeout: unknown;\n /** 用户点了关闭、滑走、按了 Esc,或代码调用了 dismiss */\n dismissed: unknown;\n}\n\n/** 列表里的一条通知 */\nexport interface ToastRecord extends ToastOptions {\n /** show 返回的编号 */\n readonly id: string;\n /** 正在离场(已关闭,等离场过渡结束后移除) */\n readonly closing: boolean;\n /** 本轮自动关闭的计时:总时长(毫秒)与编号(重新计时时换一个);不自动关闭(加载中、Infinity)时为 null */\n readonly countdown: { readonly duration: number; readonly id: number } | null;\n}\n\n/** promise 各阶段的通知:固定内容,或由结果算出 */\nexport type ToastMessage<T> = ToastOptions | ((value: T) => ToastOptions);\n\n/** toaster.promise 的三个阶段 */\nexport interface ToastPromiseMessages<T> {\n /** 进行中(自动带上 loading) */\n loading: ToastOptions;\n /** 成功,默认色调 success */\n success: ToastMessage<T>;\n /** 失败,默认色调 danger */\n error: ToastMessage<unknown>;\n}\n\n/** createToaster 的选项 */\nexport interface ToasterOptions {\n /** 默认显示时长(毫秒),默认 5000 */\n duration?: number;\n}\n\n/** 通知控制器:createToaster() 创建,交给 <Toaster controller>,在任何地方调用 */\nexport interface ToasterController {\n /** 当前的通知,旧的在前 */\n readonly toasts: readonly ToastRecord[];\n /** 计时是否暂停(悬停、聚焦、页面隐藏时) */\n readonly paused: boolean;\n /** 显示一条通知,返回它的编号;options.id 已有时换掉那一条(不与它合并) */\n show(options: ToastOptions): string;\n /** 成功的通知:`toaster.success('已保存')` */\n success(title: VNodeChild, options?: ToastShortcutOptions): string;\n /** 出错的通知(读屏立即播报) */\n error(title: VNodeChild, options?: ToastShortcutOptions): string;\n /** 警告的通知 */\n warning(title: VNodeChild, options?: ToastShortcutOptions): string;\n /** 提示的通知 */\n info(title: VNodeChild, options?: ToastShortcutOptions): string;\n /** 进行中的通知(不自动消失):完成后用同一个 id 调 success / error 换掉它,或 dismiss */\n loading(title: VNodeChild, options?: ToastShortcutOptions): string;\n /** 改一条通知的内容(如加载完成);计时按新的 duration 重新开始 */\n update(id: string, options: ToastOptions): void;\n /** 关闭一条通知;不给编号时关闭全部 */\n dismiss(id?: string): void;\n /** 进行中 → 成功 / 失败:同一条通知依次更新;返回原 Promise 的结果(失败时照样抛出) */\n promise<T>(promise: Promise<T>, messages: ToastPromiseMessages<T>): Promise<T>;\n /** 暂停 / 继续所有通知的计时:Toaster 在悬停、聚焦、页面隐藏时调用 */\n pause(): void;\n /** 继续计时,见 pause */\n resume(): void;\n /** 从列表里移除(Toaster 在离场过渡结束后调用;直接调用则没有离场动画) */\n remove(id: string): void;\n /** 最多显示几条(Toaster 的 max):只给显示出来的通知计时,没轮到的等它出现再开始 */\n setLimit(limit: number): void;\n}\n\n/** 控制器内部可写的通知记录 */\ntype MutableToast = { -readonly [K in keyof ToastRecord]: ToastRecord[K] };\n\ninterface Timer {\n remaining: number;\n startedAt: number;\n handle?: ReturnType<typeof setTimeout>;\n}\n\nlet nextId = 0;\n\n/** 创建通知控制器 */\nexport function createToaster({\n duration: defaultDuration = 5000,\n}: ToasterOptions = {}): ToasterController {\n // 浅层响应:列表增删与每条通知的字段变化会触发渲染;内容是 VNode,不需要(也不该)深层代理\n const toasts = shallowReactive<MutableToast[]>([]);\n const timers = new Map<string, Timer>();\n const state = shallowReactive({ paused: false });\n let countdowns = 0;\n let limit = Infinity;\n\n const find = (id: string) => toasts.find((toast) => toast.id === id);\n\n function start(id: string, timer: Timer) {\n timer.startedAt = Date.now();\n timer.handle = setTimeout(() => close(id, 'timeout'), timer.remaining);\n }\n\n function close(id: string, reason: keyof ToastCloseReasons) {\n const toast = find(id);\n if (!toast || toast.closing) return;\n toast.closing = true;\n toast.countdown = null;\n clearTimeout(timers.get(toast.id)?.handle);\n timers.delete(toast.id);\n toast.onClose?.(reason);\n }\n\n function stop(timer: Timer) {\n if (timer.handle === undefined) return;\n clearTimeout(timer.handle);\n timer.handle = undefined;\n timer.remaining -= Date.now() - timer.startedAt;\n }\n\n /** 计时与显示对齐:显示出来的(最新的 limit 条)在走,其余停着;暂停时都停 */\n function sync() {\n const shown = new Set(toasts.slice(-limit).map((toast) => toast.id));\n for (const [id, timer] of timers) {\n const running = timer.handle !== undefined;\n if (!state.paused && shown.has(id)) {\n if (!running) start(id, timer);\n } else if (running) stop(timer);\n }\n }\n\n function schedule(toast: MutableToast) {\n clearTimeout(timers.get(toast.id)?.handle);\n timers.delete(toast.id);\n const duration = toast.loading ? Infinity : (toast.duration ?? defaultDuration);\n toast.countdown = Number.isFinite(duration) ? { duration, id: ++countdowns } : null;\n if (!toast.countdown) return;\n const timer: Timer = { remaining: duration, startedAt: 0 };\n timers.set(toast.id, timer);\n sync();\n }\n\n const resolve = <T,>(message: ToastMessage<T>, value: T) =>\n typeof message === 'function' ? message(value) : message;\n\n const controller: ToasterController = {\n toasts,\n get paused() {\n return state.paused;\n },\n show(options) {\n const existing = options.id === undefined ? undefined : find(options.id);\n if (existing && !existing.closing) {\n // 换掉整条(不与上一条合并):上一条的“进行中”、不可关闭都不留着\n for (const key of Object.keys(existing) as (keyof MutableToast)[]) {\n if (key !== 'id' && key !== 'closing' && key !== 'countdown') delete existing[key];\n }\n controller.update(existing.id, options);\n return existing.id;\n }\n // 同一个编号的那条正在离场:先移走,新的接着出现\n if (existing) controller.remove(existing.id);\n const id = options.id ?? `toast-${++nextId}`;\n toasts.push(shallowReactive({ ...options, id, closing: false, countdown: null }));\n schedule(find(id)!);\n return id;\n },\n success: (title, options) => controller.show({ ...options, title, tone: 'success' }),\n error: (title, options) =>\n controller.show({ important: true, ...options, title, tone: 'danger' }),\n warning: (title, options) => controller.show({ ...options, title, tone: 'warning' }),\n info: (title, options) => controller.show({ ...options, title, tone: 'info' }),\n loading: (title, options) =>\n controller.show({ closable: false, ...options, title, tone: 'neutral', loading: true }),\n update(id, options) {\n const toast = find(id);\n if (!toast || toast.closing) return;\n Object.assign(toast, options);\n schedule(toast);\n },\n dismiss(id) {\n for (const toast of id == null ? [...toasts] : [find(id)]) {\n if (toast) close(toast.id, 'dismissed');\n }\n },\n async promise(promise, { loading, success, error }) {\n const id = controller.show({ closable: false, ...loading, loading: true });\n try {\n const value = await promise;\n controller.update(id, {\n tone: 'success',\n closable: true,\n ...resolve(success, value),\n loading: false,\n });\n return value;\n } catch (reason) {\n controller.update(id, {\n tone: 'danger',\n closable: true,\n ...resolve(error, reason),\n loading: false,\n });\n throw reason;\n }\n },\n pause() {\n if (state.paused) return;\n state.paused = true;\n sync();\n },\n resume() {\n if (!state.paused) return;\n state.paused = false;\n sync();\n },\n remove(id) {\n const index = toasts.findIndex((toast) => toast.id === id);\n if (index >= 0) toasts.splice(index, 1);\n clearTimeout(timers.get(id)?.handle);\n timers.delete(id);\n // 腾出一个位置:下一条没轮到的通知出现,开始计时\n sync();\n },\n setLimit(next) {\n limit = next;\n sync();\n },\n };\n return controller;\n}\n\n/** 通知显示在视口的哪个位置 */\nexport interface ToastPlacements {\n /** 左上(从右到左排版时右上) */\n 'top-start': unknown;\n /** 顶部居中 */\n top: unknown;\n /** 右上(从右到左排版时左上) */\n 'top-end': unknown;\n /** 左下(从右到左排版时右下) */\n 'bottom-start': unknown;\n /** 底部居中 */\n bottom: unknown;\n /** 右下(默认;从右到左排版时左下) */\n 'bottom-end': unknown;\n}\n\n/** 未声明的属性落到区域(section)上;aria-label 默认取语言包(通知) */\nexport interface ToasterProps extends PartProps<'section', 'popover' | 'tabindex'> {\n /** createToaster() 创建的控制器 */\n controller: ToasterController;\n /** 显示位置,默认 bottom-end */\n placement?: keyof ToastPlacements;\n /** 最多同时显示几条,默认 3;更早的先不显示,前面的关闭后补上 */\n max?: number;\n /** 叠成一摞(同 Sonner):只露出最新的一条,更早的缩小叠在后面;悬停或聚焦时展开,展开收起有布局动画。默认 false(平铺) */\n stack?: boolean;\n /** 在自动关闭的通知底部显示剩余时间条(暂停计时时一起停),默认 false */\n progress?: boolean;\n /** 列表(ol)的部件属性 */\n propsList?: Omit<Partial<FlexProps<'ol'>>, 'aria-live'>;\n /** 每一项(li)的部件属性 */\n propsItem?: PartProps<'li'> | ((toast: ToastRecord) => PartProps<'li'>);\n /** 每条通知(Alert)的部件属性 */\n propsToast?: Partial<AlertProps> | ((toast: ToastRecord) => Partial<AlertProps>);\n /** 剩余时间条的部件属性 */\n propsProgress?: PartProps<'span'>;\n /** 每一项里收起高度的 Collapse 的部件属性(离场时它收起,下面的通知补上来) */\n propsCollapse?: Partial<CollapseProps>;\n}\n\nconst countdown = css.keyframes({\n from: { transform: 'scaleX(1)' },\n to: { transform: 'scaleX(0)' },\n});\n\n/** Toaster 的样式:region、list、listFromTop、item、itemFromBottom、itemFromTop 等 */\nexport const toastStyles = css.create({\n region: {\n // 原生 popover 默认居中、带边框与底色,改为贴着视口的一角\n position: 'fixed',\n inset: 'auto',\n margin: 0,\n padding: '_lg',\n borderWidth: 0,\n backgroundColor: 'transparent',\n overflow: 'visible',\n boxSizing: 'border-box',\n width: 'min(26em, 100vw)',\n maxHeight: '100dvh',\n // 区域本身不挡住下面页面的点击,只有通知能点\n pointerEvents: 'none',\n outlineStyle: 'none',\n },\n item: {\n pointerEvents: 'auto',\n // 纵向仍可滚动页面,横向交给滑动关闭\n touchAction: 'pan-y',\n opacity: { default: 1, '@starting-style': 0 },\n transition: `opacity ${t.durations.md} ${t.easings.enter}, translate ${t.durations.md} ${t.easings.enter}`,\n },\n itemFromBottom: { translate: { default: 'none', '@starting-style': '0 0.75em' } },\n itemFromTop: { translate: { default: 'none', '@starting-style': '0 -0.75em' } },\n itemClosing: {\n opacity: 0,\n transition: `opacity ${t.durations.sm} ${t.easings.exit}`,\n },\n // 间距放在每一项里:离场收起高度时间距一起收掉;剩余时间条按它定位\n gap: { position: 'relative', paddingBlock: '_xs' },\n toast: { boxShadow: '_lg' },\n // 贴着通知底边的细条,从满到空;时长是这条通知的计时(不是主题时长),由动态样式给出\n countdown: {\n position: 'absolute',\n insetInline: '_md',\n bottom: `calc(${t.space.xs} + ${t.borderWidths.xs})`,\n height: '_borderWidths.sm',\n borderRadius: '_full',\n backgroundColor: `color-mix(in srgb, ${t.colors.textMuted} 50%, transparent)`,\n transformOrigin: { default: 'left', ':dir(rtl)': 'right' },\n animationName: countdown,\n animationTimingFunction: 'linear',\n animationFillMode: 'forwards',\n pointerEvents: 'none',\n },\n countdownPaused: { animationPlayState: 'paused' },\n // 叠放:每一项都定位、按深度排层叠顺序;最新的一条留在文档流里撑起高度,更早的绝对定位叠在它后面\n stacked: {\n position: 'relative',\n transition: `opacity ${t.durations.md} ${t.easings.standard}, translate ${t.durations.md} ${t.easings.standard}, scale ${t.durations.md} ${t.easings.standard}`,\n },\n stackedBehind: { position: 'absolute', insetInline: 0 },\n stackedHidden: { opacity: 0 },\n stackedFromTop: { top: 0 },\n stackedFromBottom: { bottom: 0 },\n});\n\n// 深度是运行时的数,用动态样式;单独一个 create,见 COMPONENTS.md §4\n/** Toaster 的样式:叠放时第 depth 层(0 是最新的)的位移、缩放与层叠顺序 */\nexport const toastStackStyles = css.create({\n depth: (offset: string, scale: number, zIndex: number) => ({ translate: offset, scale, zIndex }),\n});\n\n// 时长来自每条通知,用动态样式;单独一个 create,见 COMPONENTS.md §4\n/** Toaster 的样式:剩余时间条的时长 */\nexport const toastCountdownStyles = css.create({\n duration: (ms: string) => ({ animationDuration: ms }),\n});\n\n/** Toaster 的样式:top-start、top、top-end、bottom-start、bottom、bottom-end */\nexport const toastPlacementStyles = css.create({\n 'top-start': { top: 0, insetInlineStart: 0 },\n top: { top: 0, insetInline: 0, marginInline: 'auto' },\n 'top-end': { top: 0, insetInlineEnd: 0 },\n 'bottom-start': { bottom: 0, insetInlineStart: 0 },\n bottom: { bottom: 0, insetInline: 0, marginInline: 'auto' },\n 'bottom-end': { bottom: 0, insetInlineEnd: 0 },\n});\n\n/** Toaster 里每条通知的 props */\nexport interface ToastItemProps {\n /** 这条通知 */\n toast: ToastRecord;\n /** 所属的通知器:关闭、暂停计时 */\n controller: ToasterController;\n /** Toaster 在顶部:进场从上方滑入 */\n fromTop: boolean;\n /** 最后一条被关闭时焦点回到这里 */\n fallbackFocus: () => HTMLElement | null;\n /** 每条通知外层(li)的部件属性 */\n propsItem: ToasterProps['propsItem'];\n /** 通知本身(Alert)的部件属性 */\n propsToast: ToasterProps['propsToast'];\n /** 叠放时的深度(0 是最新的一条);平铺或展开时为 null */\n depth: number | null;\n /** 显示剩余时间条 */\n progress: boolean;\n /** 剩余时间条的部件属性 */\n propsProgress: ToasterProps['propsProgress'];\n /** 收起高度的 Collapse 的部件属性 */\n propsCollapse: ToasterProps['propsCollapse'];\n}\n\n/** 滑动超过宽度的这个比例就关闭 */\nconst SWIPE_RATIO = 0.35;\n\n/** 叠放的一层:每深一层错开 0.75em、缩小 5%,第三层之后隐藏 */\nfunction stackSx(depth: number, fromTop: boolean) {\n const offset = `0 ${(fromTop ? 0.75 : -0.75) * depth}em`;\n return [\n toastStyles.stacked,\n depth > 0 && [\n toastStyles.stackedBehind,\n fromTop ? toastStyles.stackedFromTop : toastStyles.stackedFromBottom,\n ],\n toastStackStyles.depth(offset, 1 - depth * 0.05, 100 - depth),\n depth > 2 && toastStyles.stackedHidden,\n ];\n}\n\n/** 列表里的一项:Collapse 负责离场时收起高度,收起后从控制器里移除 */\nexport const ToastItem = _component(\n ({\n toast,\n controller,\n fromTop,\n fallbackFocus,\n depth,\n progress,\n propsItem,\n propsToast,\n propsProgress,\n propsCollapse,\n }: ToastItemProps) => {\n let item: HTMLElement | null = null;\n const animate = useAnimate();\n\n const presence = usePresence(() => !toast.closing, {\n onExited: () => controller.remove(toast.id),\n });\n\n function close() {\n if (item) focusAfterRemoval(item, fallbackFocus());\n controller.dismiss(toast.id);\n }\n\n // 焦点在通知里(操作、关闭按钮)时 Esc 关闭它(同 Radix Toast);不能关闭的通知不处理\n function onKeydown(event: KeyboardEvent) {\n if (event.key !== 'Escape' || toast.closable === false || toast.closing) return;\n event.preventDefault();\n event.stopPropagation();\n close();\n }\n\n // 横向滑动关闭:拖动中直接写元素的样式(不触发渲染)。松开时过了阈值就沿滑动方向滑出再关闭,\n // 不够就用弹簧弹回(Anime.js,时长取主题;减少动态效果时立即到位)。不能关闭的通知(promise 的加载中)不跟手\n function onPointerdown(event: PointerEvent) {\n const element = item;\n if (!element || toast.closing || toast.closable === false) return;\n if ((event.target as Element).closest('button, a, input, textarea, select')) return;\n const width = element.getBoundingClientRect().width;\n startMove(event, {\n threshold: 4,\n onMove: ({ deltaX }) => {\n // 拖动中跟手:去掉进出场的 CSS 过渡,不然每一帧都在追赶\n element.style.transition = 'none';\n void utils.set(element, {\n x: deltaX,\n opacity: Math.max(0, 1 - Math.abs(deltaX) / width),\n });\n },\n onEnd: ({ deltaX, moved, cancelled }) => {\n // 弹回后去掉内联样式:之后的离场淡出要靠 CSS 的 opacity\n const settled = () => {\n element.style.transition = '';\n element.style.removeProperty('transform');\n element.style.removeProperty('opacity');\n };\n if (moved && !cancelled && Math.abs(deltaX) > width * SWIPE_RATIO) {\n const out = { x: Math.sign(deltaX) * width, opacity: 0 };\n void animate(element, out, { duration: 'sm', ease: 'exit' }).then(() => {\n close();\n element.style.transition = '';\n });\n return;\n }\n void animate(element, { x: 0, opacity: 1 }, { bounce: 0.35 }).then(settled);\n },\n });\n }\n\n return () => {\n const itemProps = typeof propsItem === 'function' ? propsItem(toast) : propsItem;\n const toastProps = typeof propsToast === 'function' ? propsToast(toast) : propsToast;\n return (\n <li\n {...mergeAttrs<'li'>(\n {\n sx: [\n toastStyles.item,\n fromTop ? toastStyles.itemFromTop : toastStyles.itemFromBottom,\n depth != null && stackSx(depth, fromTop),\n toast.closing && toastStyles.itemClosing,\n ],\n },\n itemProps,\n {\n // 重要的通知打断播报(同 Sonner);其余随列表的 aria-live=\"polite\"\n 'aria-live': toast.important ? 'assertive' : undefined,\n 'aria-atomic': toast.important || undefined,\n ref: (element: unknown) => {\n item = toElement(element);\n presence.ref(element);\n },\n onPointerdown,\n onKeydown,\n },\n )}\n data-state={presence.state}\n >\n <Collapse\n {...mergePartProps<CollapseProps>(\n { propsContent: { sx: toastStyles.gap } },\n propsCollapse,\n )}\n _open={!toast.closing}\n >\n <Alert\n {...mergePartProps<AlertProps>(\n {\n variant: 'surface',\n tone: toast.tone ?? 'neutral',\n icon: toast.icon,\n loading: toast.loading,\n title: toast.title,\n description: toast.description,\n action: toast.action,\n sx: toastStyles.toast,\n },\n toastProps,\n { onClose: toast.closable === false ? undefined : close },\n )}\n />\n {progress && toast.countdown && (\n <span\n key={toast.countdown.id}\n {...mergeAttrs<'span'>({ sx: toastStyles.countdown }, propsProgress, {\n sx: [\n toastCountdownStyles.duration(`${toast.countdown.duration}ms`),\n controller.paused && toastStyles.countdownPaused,\n ],\n 'aria-hidden': true,\n })}\n />\n )}\n </Collapse>\n </li>\n );\n };\n },\n { name: 'ToastItem' },\n);\n\n/** 通知的显示区域:放一个即可;模态打开时自动移进最上层的模态 */\nexport const Toaster = _component(\n ({\n controller,\n placement = 'bottom-end',\n max = 3,\n stack = false,\n progress = false,\n propsList,\n propsItem,\n propsToast,\n propsProgress,\n propsCollapse,\n ...rest\n }: ToasterProps) => {\n const lang = useLang();\n let region = _state.raw<HTMLElement | null>(null);\n let hovering = _state(false);\n let focused = _state(false);\n const pageVisible = usePageVisible();\n // 叠放时悬停或聚焦就展开;展开、收起时各项从旧位置平移过去\n let list = _state.raw<HTMLElement | null>(null);\n let expanded = _state(false);\n const layout = useLayoutAnimation(() => list, { children: ':scope > li' });\n watch(\n () => hovering || focused,\n (next) => {\n if (stack) void layout.change(() => (expanded = next));\n else expanded = next;\n },\n );\n // 进入通知区域之前焦点所在的元素:最后一条通知被关闭后焦点回到这里\n let outsideFocus: HTMLElement | null = null;\n\n _effect(() => {\n if (hovering || focused || !pageVisible.value) controller.pause();\n else controller.resume();\n });\n\n // 移动到别的父元素(Teleport 换目标)时浏览器会关掉 popover,移完重新打开\n const reopen = () => {\n if (region?.isConnected && !region.matches(':popover-open')) region.showPopover();\n };\n watch(topModal, () => void nextTick(reopen));\n\n // F8 跳到通知区域(同 Radix Toast);输入框里也能用\n const noHotkeys: HotkeyMap = {};\n useHotkeys(\n () => (region && controller.toasts.length ? { F8: () => region?.focus() } : noHotkeys),\n { inInputs: true },\n );\n onMounted(reopen);\n // 只给显示出来的通知计时:告诉控制器最多显示几条\n watch(\n () => max,\n (limit) => controller.setLimit(limit),\n { immediate: true },\n );\n onBeforeUnmount(() => {\n controller.setLimit(Infinity);\n controller.resume();\n });\n\n function onFocusin(event: FocusEvent) {\n focused = true;\n const from = event.relatedTarget;\n if (from instanceof HTMLElement && !region?.contains(from)) outsideFocus = from;\n }\n function onFocusout(event: FocusEvent) {\n const to = event.relatedTarget;\n if (!(to instanceof Node && region?.contains(to))) focused = false;\n }\n\n return () => {\n const fromTop = placement.startsWith('top');\n const visible = controller.toasts.slice(-max);\n // 深度只数还在显示的:正在离场的不占一层\n let depth = visible.filter((toast) => !toast.closing).length;\n const depths = visible.map((toast) => (toast.closing ? depth : --depth));\n const stacked = stack && !expanded;\n return (\n <Teleport to={topModal() ?? 'body'}>\n <ConfigScope>\n <section\n {...mergeAttrs<'section'>(\n {\n sx: [toastStyles.region, toastPlacementStyles[placement]],\n 'aria-label': lang.toasterLabel,\n },\n rest,\n {\n ref: _toRef(region),\n popover: 'manual',\n tabindex: -1,\n onPointerenter: () => (hovering = true),\n onPointerleave: () => (hovering = false),\n onFocusin,\n onFocusout,\n },\n )}\n data-placement={placement}\n data-expanded={stack ? String(!stacked) : undefined}\n >\n <FlexOrderedList\n {...mergePartProps<FlexProps<'ol'>>(\n // 新通知靠近所在的边缘:在顶部时最新的在最上面\n { direction: fromTop ? 'column-reverse' : 'column' },\n propsList,\n {\n ref: (value: unknown) => (list = toElement(value)),\n 'aria-live': 'polite',\n },\n )}\n >\n {visible.map((toast, index) => (\n <ToastItem\n key={toast.id}\n depth={stacked ? depths[index]! : null}\n toast={toast}\n controller={controller}\n fromTop={fromTop}\n fallbackFocus={() => outsideFocus}\n propsItem={propsItem}\n propsToast={propsToast}\n progress={progress}\n propsProgress={propsProgress}\n propsCollapse={propsCollapse}\n />\n ))}\n </FlexOrderedList>\n </section>\n </ConfigScope>\n </Teleport>\n );\n };\n },\n);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Toast.js","names":["_isVNode","_vtp_readonlyProp","_createVNode","_mergeProps","_vtp_restProps","_vue_shallowRef","_vue_ref"],"sources":["../../src/feedback/Toast.tsx"],"sourcesContent":["/**\n * 通知(toast):操作结果的短暂提示,几秒后自动消失。控制器 + 显示区域(参考 Sonner、Radix Toast、React Aria Toast):\n *\n * // toaster.ts:应用里创建一次,组件内外(请求拦截器、store)都能导入使用\n * export const toaster = createToaster();\n * // App:放一个显示区域\n * <Toaster controller={toaster} placement=\"bottom-end\" />\n * // 任意位置\n * toaster.show({ tone: 'success', title: '已保存' }); // 或 toaster.success('已保存')\n * const id = toaster.show({ title: '已删除', action: <Button onClick={() => undo(id)}>撤销</Button> });\n * toaster.loading('正在导出', { id: 'export' }); …; toaster.success('导出完成', { id: 'export' }); // 同一个 id 换掉原来那条\n * await toaster.promise(upload(file), { loading: { title: '正在上传' }, success: { title: '上传完成' }, error: (e) => ({ title: String(e) }) });\n *\n * 控制器是普通对象,不经 Provider 注入(COMPONENTS.md §1 的“命令式能力”),所以组件外也能用。\n * 写了 id 的通知不重复:同一个 id 再 show 时换掉那一条(重新计时),不再加一条(接口重试、连点保存时只留一条)。\n *\n * - 每条通知就是 Alert(surface 外观加阴影),图标、标题、正文、操作、关闭按钮都复用它;加载中用 Alert 的 loading。\n * - 计时:默认 5 秒;指针在区域上、焦点在区域里、页面切到后台时暂停;加载中的、duration 为 Infinity 的不自动消失。\n * - 无障碍:区域是带名称的 landmark(section),F8 跳到通知区域,焦点在一条通知里时 Esc 关闭它;\n * 列表 aria-live=\"polite\",新通知被读屏念出。\n * 关闭一条后焦点交给相邻的通知,没有时回到进入通知区域之前的位置。\n * - 区域传送到 body(或最上层的模态里),用 ConfigScope 带上当前的主题与语言。\n * - 顶层:区域是 popover=\"manual\",显示在页面之上;模态打开时外面的元素都是 inert 的(顶层也不例外),\n * 所以这时区域移进最上层的模态 dialog 里(topModal),通知里的按钮仍可点击、可 Tab 到。\n * - 动画:进场淡入并从边缘移入(@starting-style),离场时高度收起(复用 Collapse)后从列表移除。\n * - 横向滑动(触摸、鼠标拖动)超过宽度的 35% 就关闭,否则弹回(startMove;同 Sonner、Radix Toast 的滑动关闭)。\n */\nimport { utils } from 'animejs';\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport {\n nextTick,\n onBeforeUnmount,\n onMounted,\n shallowReactive,\n Teleport,\n watch,\n type VNodeChild,\n} from 'vue';\nimport type { IconData } from '../base/Icon';\nimport { useLang } from '../config/context';\nimport { ConfigScope } from '../config/Provider';\nimport { Collapse, type CollapseProps } from '../display/Collapse';\nimport { useAnimate } from '../utils/motion/animation';\nimport { useLayoutAnimation } from '../utils/motion/layout';\nimport { usePresence } from '../utils/motion/presence';\nimport { startMove } from '../utils/dom/move';\nimport { topModal } from '../utils/dom/overlay';\nimport { toElement } from '../utils/dom/element';\nimport { useHotkeys, type HotkeyMap } from '../utils/dom/hotkeys';\nimport { focusAfterRemoval } from '../utils/dom/focus';\nimport { usePageVisible } from '../utils/dom/page-visibility';\nimport { mergeAttrs, mergePartProps, type PartProps } from '../utils/props';\nimport type { StatusTone } from '../utils/style/tone-styles';\nimport { Alert, type AlertProps } from './Alert';\nimport { FlexOrderedList, type FlexProps } from '../layout/Flex';\nimport { t } from '../config/theme/tokens';\n\n/** 一条通知的内容与行为 */\nexport interface ToastOptions {\n /** 标题(加粗的第一行) */\n title?: VNodeChild;\n /** 正文 */\n description?: VNodeChild;\n /** 色调,默认 neutral */\n tone?: StatusTone;\n /** 图标;不写时按色调选,false 不显示 */\n icon?: IconData | false;\n /** 进行中:图标换成加载指示,不自动消失 */\n loading?: boolean;\n /** 右侧的操作(如“撤销”按钮) */\n action?: VNodeChild;\n /** 显示关闭按钮,默认 true */\n closable?: boolean;\n /** 显示多久(毫秒)后自动消失;Infinity 不自动消失;不写时用 createToaster 的默认值 */\n duration?: number;\n /** 重要:读屏立即打断播报(aria-live=\"assertive\"),用于出错、需要马上处理的通知;默认按礼貌的顺序念 */\n important?: boolean;\n /** 编号:同一个编号的通知只有一条,再 show 时换掉它;不写时自动生成 */\n id?: string;\n /** 开始关闭时调用(自动消失或被关闭);同一条通知只调用一次 */\n onClose?: (reason: keyof ToastCloseReasons) => void;\n}\n\n/** 快捷方法的选项:标题由第一个参数给出 */\nexport type ToastShortcutOptions = Omit<ToastOptions, 'title' | 'tone'>;\n\n/** 通知为什么关闭 */\nexport interface ToastCloseReasons {\n /** 到时间自动消失 */\n timeout: unknown;\n /** 用户点了关闭、滑走、按了 Esc,或代码调用了 dismiss */\n dismissed: unknown;\n}\n\n/** 列表里的一条通知 */\nexport interface ToastRecord extends ToastOptions {\n /** show 返回的编号 */\n readonly id: string;\n /** 正在离场(已关闭,等离场过渡结束后移除) */\n readonly closing: boolean;\n /** 本轮自动关闭的计时:总时长(毫秒)与编号(重新计时时换一个);不自动关闭(加载中、Infinity)时为 null */\n readonly countdown: { readonly duration: number; readonly id: number } | null;\n}\n\n/** promise 各阶段的通知:固定内容,或由结果算出 */\nexport type ToastMessage<T> = ToastOptions | ((value: T) => ToastOptions);\n\n/** toaster.promise 的三个阶段 */\nexport interface ToastPromiseMessages<T> {\n /** 进行中(自动带上 loading) */\n loading: ToastOptions;\n /** 成功,默认色调 success */\n success: ToastMessage<T>;\n /** 失败,默认色调 danger */\n error: ToastMessage<unknown>;\n}\n\n/** createToaster 的选项 */\nexport interface ToasterOptions {\n /** 默认显示时长(毫秒),默认 5000 */\n duration?: number;\n}\n\n/** 通知控制器:createToaster() 创建,交给 <Toaster controller>,在任何地方调用 */\nexport interface ToasterController {\n /** 当前的通知,旧的在前 */\n readonly toasts: readonly ToastRecord[];\n /** 计时是否暂停(悬停、聚焦、页面隐藏时) */\n readonly paused: boolean;\n /** 显示一条通知,返回它的编号;options.id 已有时换掉那一条(不与它合并) */\n show(options: ToastOptions): string;\n /** 成功的通知:`toaster.success('已保存')` */\n success(title: VNodeChild, options?: ToastShortcutOptions): string;\n /** 出错的通知(读屏立即播报) */\n error(title: VNodeChild, options?: ToastShortcutOptions): string;\n /** 警告的通知 */\n warning(title: VNodeChild, options?: ToastShortcutOptions): string;\n /** 提示的通知 */\n info(title: VNodeChild, options?: ToastShortcutOptions): string;\n /** 进行中的通知(不自动消失):完成后用同一个 id 调 success / error 换掉它,或 dismiss */\n loading(title: VNodeChild, options?: ToastShortcutOptions): string;\n /** 改一条通知的内容(如加载完成);计时按新的 duration 重新开始 */\n update(id: string, options: ToastOptions): void;\n /** 关闭一条通知;不给编号时关闭全部 */\n dismiss(id?: string): void;\n /** 进行中 → 成功 / 失败:同一条通知依次更新;返回原 Promise 的结果(失败时照样抛出) */\n promise<T>(promise: Promise<T>, messages: ToastPromiseMessages<T>): Promise<T>;\n /** 暂停 / 继续所有通知的计时:Toaster 在悬停、聚焦、页面隐藏时调用 */\n pause(): void;\n /** 继续计时,见 pause */\n resume(): void;\n /** 从列表里移除(Toaster 在离场过渡结束后调用;直接调用则没有离场动画) */\n remove(id: string): void;\n /** 最多显示几条(Toaster 的 max):只给显示出来的通知计时,没轮到的等它出现再开始 */\n setLimit(limit: number): void;\n}\n\n/** 控制器内部可写的通知记录 */\ntype MutableToast = { -readonly [K in keyof ToastRecord]: ToastRecord[K] };\n\ninterface Timer {\n remaining: number;\n startedAt: number;\n handle?: ReturnType<typeof setTimeout>;\n}\n\nlet nextId = 0;\n\n/** 创建通知控制器 */\nexport function createToaster({\n duration: defaultDuration = 5000,\n}: ToasterOptions = {}): ToasterController {\n // 浅层响应:列表增删与每条通知的字段变化会触发渲染;内容是 VNode,不需要(也不该)深层代理\n const toasts = shallowReactive<MutableToast[]>([]);\n const timers = new Map<string, Timer>();\n const state = shallowReactive({ paused: false });\n let countdowns = 0;\n let limit = Infinity;\n\n const find = (id: string) => toasts.find((toast) => toast.id === id);\n\n function start(id: string, timer: Timer) {\n timer.startedAt = Date.now();\n timer.handle = setTimeout(() => close(id, 'timeout'), timer.remaining);\n }\n\n function close(id: string, reason: keyof ToastCloseReasons) {\n const toast = find(id);\n if (!toast || toast.closing) return;\n toast.closing = true;\n toast.countdown = null;\n clearTimeout(timers.get(toast.id)?.handle);\n timers.delete(toast.id);\n toast.onClose?.(reason);\n }\n\n function stop(timer: Timer) {\n if (timer.handle === undefined) return;\n clearTimeout(timer.handle);\n timer.handle = undefined;\n timer.remaining -= Date.now() - timer.startedAt;\n }\n\n /** 计时与显示对齐:显示出来的(最新的 limit 条)在走,其余停着;暂停时都停 */\n function sync() {\n const shown = new Set(toasts.slice(-limit).map((toast) => toast.id));\n for (const [id, timer] of timers) {\n const running = timer.handle !== undefined;\n if (!state.paused && shown.has(id)) {\n if (!running) start(id, timer);\n } else if (running) stop(timer);\n }\n }\n\n function schedule(toast: MutableToast) {\n clearTimeout(timers.get(toast.id)?.handle);\n timers.delete(toast.id);\n const duration = toast.loading ? Infinity : (toast.duration ?? defaultDuration);\n toast.countdown = Number.isFinite(duration) ? { duration, id: ++countdowns } : null;\n if (!toast.countdown) return;\n const timer: Timer = { remaining: duration, startedAt: 0 };\n timers.set(toast.id, timer);\n sync();\n }\n\n const resolve = <T,>(message: ToastMessage<T>, value: T) =>\n typeof message === 'function' ? message(value) : message;\n\n const controller: ToasterController = {\n toasts,\n get paused() {\n return state.paused;\n },\n show(options) {\n const existing = options.id === undefined ? undefined : find(options.id);\n if (existing && !existing.closing) {\n // 换掉整条(不与上一条合并):上一条的“进行中”、不可关闭都不留着\n for (const key of Object.keys(existing) as (keyof MutableToast)[]) {\n if (key !== 'id' && key !== 'closing' && key !== 'countdown') delete existing[key];\n }\n controller.update(existing.id, options);\n return existing.id;\n }\n // 同一个编号的那条正在离场:先移走,新的接着出现\n if (existing) controller.remove(existing.id);\n const id = options.id ?? `toast-${++nextId}`;\n toasts.push(shallowReactive({ ...options, id, closing: false, countdown: null }));\n schedule(find(id)!);\n return id;\n },\n success: (title, options) => controller.show({ ...options, title, tone: 'success' }),\n error: (title, options) =>\n controller.show({ important: true, ...options, title, tone: 'danger' }),\n warning: (title, options) => controller.show({ ...options, title, tone: 'warning' }),\n info: (title, options) => controller.show({ ...options, title, tone: 'info' }),\n loading: (title, options) =>\n controller.show({ closable: false, ...options, title, tone: 'neutral', loading: true }),\n update(id, options) {\n const toast = find(id);\n if (!toast || toast.closing) return;\n Object.assign(toast, options);\n schedule(toast);\n },\n dismiss(id) {\n for (const toast of id == null ? [...toasts] : [find(id)]) {\n if (toast) close(toast.id, 'dismissed');\n }\n },\n async promise(promise, { loading, success, error }) {\n const id = controller.show({ closable: false, ...loading, loading: true });\n try {\n const value = await promise;\n controller.update(id, {\n tone: 'success',\n closable: true,\n ...resolve(success, value),\n loading: false,\n });\n return value;\n } catch (reason) {\n controller.update(id, {\n tone: 'danger',\n closable: true,\n ...resolve(error, reason),\n loading: false,\n });\n throw reason;\n }\n },\n pause() {\n if (state.paused) return;\n state.paused = true;\n sync();\n },\n resume() {\n if (!state.paused) return;\n state.paused = false;\n sync();\n },\n remove(id) {\n const index = toasts.findIndex((toast) => toast.id === id);\n if (index >= 0) toasts.splice(index, 1);\n clearTimeout(timers.get(id)?.handle);\n timers.delete(id);\n // 腾出一个位置:下一条没轮到的通知出现,开始计时\n sync();\n },\n setLimit(next) {\n limit = next;\n sync();\n },\n };\n return controller;\n}\n\n/** 通知显示在视口的哪个位置 */\nexport interface ToastPlacements {\n /** 左上(从右到左排版时右上) */\n 'top-start': unknown;\n /** 顶部居中 */\n top: unknown;\n /** 右上(从右到左排版时左上) */\n 'top-end': unknown;\n /** 左下(从右到左排版时右下) */\n 'bottom-start': unknown;\n /** 底部居中 */\n bottom: unknown;\n /** 右下(默认;从右到左排版时左下) */\n 'bottom-end': unknown;\n}\n\n/** 未声明的属性落到区域(section)上;aria-label 默认取语言包(通知) */\nexport interface ToasterProps extends PartProps<'section', 'popover' | 'tabindex'> {\n /** createToaster() 创建的控制器 */\n controller: ToasterController;\n /** 显示位置,默认 bottom-end */\n placement?: keyof ToastPlacements;\n /** 最多同时显示几条,默认 3;更早的先不显示,前面的关闭后补上 */\n max?: number;\n /** 叠成一摞(同 Sonner):只露出最新的一条,更早的缩小叠在后面;悬停或聚焦时展开,展开收起有布局动画。默认 false(平铺) */\n stack?: boolean;\n /** 在自动关闭的通知底部显示剩余时间条(暂停计时时一起停),默认 false */\n progress?: boolean;\n /** 列表(ol)的部件属性 */\n propsList?: Omit<Partial<FlexProps<'ol'>>, 'aria-live'>;\n /** 每一项(li)的部件属性 */\n propsItem?: PartProps<'li'> | ((toast: ToastRecord) => PartProps<'li'>);\n /** 每条通知(Alert)的部件属性 */\n propsToast?: Partial<AlertProps> | ((toast: ToastRecord) => Partial<AlertProps>);\n /** 剩余时间条的部件属性 */\n propsProgress?: PartProps<'span'>;\n /** 每一项里收起高度的 Collapse 的部件属性(离场时它收起,下面的通知补上来) */\n propsCollapse?: Partial<CollapseProps>;\n}\n\nconst countdown = css.keyframes({\n from: { transform: 'scaleX(1)' },\n to: { transform: 'scaleX(0)' },\n});\n\n/** Toaster 的样式:region、list、listFromTop、item、itemFromBottom、itemFromTop 等 */\nexport const toastStyles = css.create({\n region: {\n // 原生 popover 默认居中、带边框与底色,改为贴着视口的一角\n position: 'fixed',\n inset: 'auto',\n margin: 0,\n padding: '_lg',\n borderWidth: 0,\n backgroundColor: 'transparent',\n overflow: 'visible',\n boxSizing: 'border-box',\n width: 'min(26em, 100vw)',\n maxHeight: '100dvh',\n // 区域本身不挡住下面页面的点击,只有通知能点\n pointerEvents: 'none',\n outlineStyle: 'none',\n },\n item: {\n pointerEvents: 'auto',\n // 纵向仍可滚动页面,横向交给滑动关闭\n touchAction: 'pan-y',\n opacity: { default: 1, '@starting-style': 0 },\n transition: `opacity ${t.durations.md} ${t.easings.enter}, translate ${t.durations.md} ${t.easings.enter}`,\n },\n itemFromBottom: { translate: { default: 'none', '@starting-style': '0 0.75em' } },\n itemFromTop: { translate: { default: 'none', '@starting-style': '0 -0.75em' } },\n itemClosing: {\n opacity: 0,\n transition: `opacity ${t.durations.sm} ${t.easings.exit}`,\n },\n // 间距放在每一项里:离场收起高度时间距一起收掉;剩余时间条按它定位\n gap: { position: 'relative', paddingBlock: '_xs' },\n toast: { boxShadow: '_lg' },\n // 贴着通知底边的细条,从满到空;时长是这条通知的计时(不是主题时长),由动态样式给出\n countdown: {\n position: 'absolute',\n insetInline: '_md',\n bottom: `calc(${t.space.xs} + ${t.borderWidths.xs})`,\n height: '_borderWidths.sm',\n borderRadius: '_full',\n backgroundColor: `color-mix(in srgb, ${t.colors.textMuted} 50%, transparent)`,\n transformOrigin: { default: 'left', ':dir(rtl)': 'right' },\n animationName: countdown,\n animationTimingFunction: 'linear',\n animationFillMode: 'forwards',\n pointerEvents: 'none',\n },\n countdownPaused: { animationPlayState: 'paused' },\n // 叠放:每一项都定位、按深度排层叠顺序;最新的一条留在文档流里撑起高度,更早的绝对定位叠在它后面\n stacked: {\n position: 'relative',\n transition: `opacity ${t.durations.md} ${t.easings.standard}, translate ${t.durations.md} ${t.easings.standard}, scale ${t.durations.md} ${t.easings.standard}`,\n },\n stackedBehind: { position: 'absolute', insetInline: 0 },\n stackedHidden: { opacity: 0 },\n stackedFromTop: { top: 0 },\n stackedFromBottom: { bottom: 0 },\n});\n\n// 深度是运行时的数,用动态样式;单独一个 create,见 COMPONENTS.md §4\n/** Toaster 的样式:叠放时第 depth 层(0 是最新的)的位移、缩放与层叠顺序 */\nexport const toastStackStyles = css.create({\n depth: (offset: string, scale: number, zIndex: number) => ({ translate: offset, scale, zIndex }),\n});\n\n// 时长来自每条通知,用动态样式;单独一个 create,见 COMPONENTS.md §4\n/** Toaster 的样式:剩余时间条的时长 */\nexport const toastCountdownStyles = css.create({\n duration: (ms: string) => ({ animationDuration: ms }),\n});\n\n/** Toaster 的样式:top-start、top、top-end、bottom-start、bottom、bottom-end */\nexport const toastPlacementStyles = css.create({\n 'top-start': { top: 0, insetInlineStart: 0 },\n top: { top: 0, insetInline: 0, marginInline: 'auto' },\n 'top-end': { top: 0, insetInlineEnd: 0 },\n 'bottom-start': { bottom: 0, insetInlineStart: 0 },\n bottom: { bottom: 0, insetInline: 0, marginInline: 'auto' },\n 'bottom-end': { bottom: 0, insetInlineEnd: 0 },\n});\n\n/** Toaster 里每条通知的 props */\nexport interface ToastItemProps {\n /** 这条通知 */\n toast: ToastRecord;\n /** 所属的通知器:关闭、暂停计时 */\n controller: ToasterController;\n /** Toaster 在顶部:进场从上方滑入 */\n fromTop: boolean;\n /** 最后一条被关闭时焦点回到这里 */\n fallbackFocus: () => HTMLElement | null;\n /** 每条通知外层(li)的部件属性 */\n propsItem: ToasterProps['propsItem'];\n /** 通知本身(Alert)的部件属性 */\n propsToast: ToasterProps['propsToast'];\n /** 叠放时的深度(0 是最新的一条);平铺或展开时为 null */\n depth: number | null;\n /** 显示剩余时间条 */\n progress: boolean;\n /** 剩余时间条的部件属性 */\n propsProgress: ToasterProps['propsProgress'];\n /** 收起高度的 Collapse 的部件属性 */\n propsCollapse: ToasterProps['propsCollapse'];\n}\n\n/** 滑动超过宽度的这个比例就关闭 */\nconst SWIPE_RATIO = 0.35;\n\n/** 叠放的一层:每深一层错开 0.75em、缩小 5%,第三层之后隐藏 */\nfunction stackSx(depth: number, fromTop: boolean) {\n const offset = `0 ${(fromTop ? 0.75 : -0.75) * depth}em`;\n return [\n toastStyles.stacked,\n depth > 0 && [\n toastStyles.stackedBehind,\n fromTop ? toastStyles.stackedFromTop : toastStyles.stackedFromBottom,\n ],\n toastStackStyles.depth(offset, 1 - depth * 0.05, 100 - depth),\n depth > 2 && toastStyles.stackedHidden,\n ];\n}\n\n/** 列表里的一项:Collapse 负责离场时收起高度,收起后从控制器里移除 */\nexport const ToastItem = _component(\n ({\n toast,\n controller,\n fromTop,\n fallbackFocus,\n depth,\n progress,\n propsItem,\n propsToast,\n propsProgress,\n propsCollapse,\n }: ToastItemProps) => {\n let item: HTMLElement | null = null;\n const animate = useAnimate();\n\n const presence = usePresence(() => !toast.closing, {\n onExited: () => controller.remove(toast.id),\n });\n\n function close() {\n if (item) focusAfterRemoval(item, fallbackFocus());\n controller.dismiss(toast.id);\n }\n\n // 焦点在通知里(操作、关闭按钮)时 Esc 关闭它(同 Radix Toast);不能关闭的通知不处理\n function onKeydown(event: KeyboardEvent) {\n if (event.key !== 'Escape' || toast.closable === false || toast.closing) return;\n event.preventDefault();\n event.stopPropagation();\n close();\n }\n\n // 横向滑动关闭:拖动中直接写元素的样式(不触发渲染)。松开时过了阈值就沿滑动方向滑出再关闭,\n // 不够就用弹簧弹回(Anime.js,时长取主题;减少动态效果时立即到位)。不能关闭的通知(promise 的加载中)不跟手\n function onPointerdown(event: PointerEvent) {\n const element = item;\n if (!element || toast.closing || toast.closable === false) return;\n if ((event.target as Element).closest('button, a, input, textarea, select')) return;\n const width = element.getBoundingClientRect().width;\n startMove(event, {\n threshold: 4,\n onMove: ({ deltaX }) => {\n // 拖动中跟手:去掉进出场的 CSS 过渡,不然每一帧都在追赶\n element.style.transition = 'none';\n void utils.set(element, {\n x: deltaX,\n opacity: Math.max(0, 1 - Math.abs(deltaX) / width),\n });\n },\n onEnd: ({ deltaX, moved, cancelled }) => {\n // 弹回后去掉内联样式:之后的离场淡出要靠 CSS 的 opacity\n const settled = () => {\n element.style.transition = '';\n element.style.removeProperty('transform');\n element.style.removeProperty('opacity');\n };\n if (moved && !cancelled && Math.abs(deltaX) > width * SWIPE_RATIO) {\n const out = { x: Math.sign(deltaX) * width, opacity: 0 };\n void animate(element, out, { duration: 'sm', ease: 'exit' }).then(() => {\n close();\n element.style.transition = '';\n });\n return;\n }\n void animate(element, { x: 0, opacity: 1 }, { bounce: 0.35 }).then(settled);\n },\n });\n }\n\n return () => {\n const itemProps = typeof propsItem === 'function' ? propsItem(toast) : propsItem;\n const toastProps = typeof propsToast === 'function' ? propsToast(toast) : propsToast;\n return (\n <li\n {...mergeAttrs<'li'>(\n {\n sx: [\n toastStyles.item,\n fromTop ? toastStyles.itemFromTop : toastStyles.itemFromBottom,\n depth != null && stackSx(depth, fromTop),\n toast.closing && toastStyles.itemClosing,\n ],\n },\n itemProps,\n {\n // 重要的通知打断播报(同 Sonner);其余随列表的 aria-live=\"polite\"\n 'aria-live': toast.important ? 'assertive' : undefined,\n 'aria-atomic': toast.important || undefined,\n ref: (element: unknown) => {\n item = toElement(element);\n presence.ref(element);\n },\n onPointerdown,\n onKeydown,\n },\n )}\n data-state={presence.state}\n >\n <Collapse\n {...mergePartProps<CollapseProps>(\n { propsContent: { sx: toastStyles.gap } },\n propsCollapse,\n )}\n _open={!toast.closing}\n >\n <Alert\n {...mergePartProps<AlertProps>(\n {\n variant: 'surface',\n tone: toast.tone ?? 'neutral',\n icon: toast.icon,\n loading: toast.loading,\n title: toast.title,\n description: toast.description,\n action: toast.action,\n sx: toastStyles.toast,\n },\n toastProps,\n { onClose: toast.closable === false ? undefined : close },\n )}\n />\n {progress && toast.countdown && (\n <span\n key={toast.countdown.id}\n {...mergeAttrs<'span'>({ sx: toastStyles.countdown }, propsProgress, {\n sx: [\n toastCountdownStyles.duration(`${toast.countdown.duration}ms`),\n controller.paused && toastStyles.countdownPaused,\n ],\n 'aria-hidden': true,\n })}\n />\n )}\n </Collapse>\n </li>\n );\n };\n },\n { name: 'ToastItem' },\n);\n\n/** 通知的显示区域:放一个即可;模态打开时自动移进最上层的模态 */\nexport const Toaster = _component(\n ({\n controller,\n placement = 'bottom-end',\n max = 3,\n stack = false,\n progress = false,\n propsList,\n propsItem,\n propsToast,\n propsProgress,\n propsCollapse,\n ...rest\n }: ToasterProps) => {\n const lang = useLang();\n let region = _state.raw<HTMLElement | null>(null);\n let hovering = _state(false);\n let focused = _state(false);\n const pageVisible = usePageVisible();\n // 叠放时悬停或聚焦就展开;展开、收起时各项从旧位置平移过去\n let list = _state.raw<HTMLElement | null>(null);\n let expanded = _state(false);\n const layout = useLayoutAnimation(() => list, { children: ':scope > li' });\n watch(\n () => hovering || focused,\n (next) => {\n if (stack) void layout.change(() => (expanded = next));\n else expanded = next;\n },\n );\n // 进入通知区域之前焦点所在的元素:最后一条通知被关闭后焦点回到这里\n let outsideFocus: HTMLElement | null = null;\n\n _effect(() => {\n if (hovering || focused || !pageVisible.value) controller.pause();\n else controller.resume();\n });\n\n // 移动到别的父元素(Teleport 换目标)时浏览器会关掉 popover,移完重新打开\n const reopen = () => {\n if (region?.isConnected && !region.matches(':popover-open')) region.showPopover();\n };\n watch(topModal, () => void nextTick(reopen));\n\n // F8 跳到通知区域(同 Radix Toast);输入框里也能用\n const noHotkeys: HotkeyMap = {};\n useHotkeys(\n () => (region && controller.toasts.length ? { F8: () => region?.focus() } : noHotkeys),\n { inInputs: true },\n );\n onMounted(reopen);\n // 只给显示出来的通知计时:告诉控制器最多显示几条\n watch(\n () => max,\n (limit) => controller.setLimit(limit),\n { immediate: true },\n );\n onBeforeUnmount(() => {\n controller.setLimit(Infinity);\n controller.resume();\n });\n\n function onFocusin(event: FocusEvent) {\n focused = true;\n const from = event.relatedTarget;\n if (from instanceof HTMLElement && !region?.contains(from)) outsideFocus = from;\n }\n function onFocusout(event: FocusEvent) {\n const to = event.relatedTarget;\n if (!(to instanceof Node && region?.contains(to))) focused = false;\n }\n\n return () => {\n const fromTop = placement.startsWith('top');\n const visible = controller.toasts.slice(-max);\n // 深度只数还在显示的:正在离场的不占一层\n let depth = visible.filter((toast) => !toast.closing).length;\n const depths = visible.map((toast) => (toast.closing ? depth : --depth));\n const stacked = stack && !expanded;\n return (\n <Teleport to={topModal() ?? 'body'}>\n <ConfigScope>\n <section\n {...mergeAttrs<'section'>(\n {\n sx: [toastStyles.region, toastPlacementStyles[placement]],\n 'aria-label': lang.toasterLabel,\n },\n rest,\n {\n ref: _toRef(region),\n popover: 'manual',\n tabindex: -1,\n onPointerenter: () => (hovering = true),\n onPointerleave: () => (hovering = false),\n onFocusin,\n onFocusout,\n },\n )}\n data-placement={placement}\n data-expanded={stack ? String(!stacked) : undefined}\n >\n <FlexOrderedList\n {...mergePartProps<FlexProps<'ol'>>(\n // 新通知靠近所在的边缘:在顶部时最新的在最上面\n { direction: fromTop ? 'column-reverse' : 'column' },\n propsList,\n {\n ref: (value: unknown) => (list = toElement(value)),\n 'aria-live': 'polite',\n },\n )}\n >\n {visible.map((toast, index) => (\n <ToastItem\n key={toast.id}\n depth={stacked ? depths[index]! : null}\n toast={toast}\n controller={controller}\n fromTop={fromTop}\n fallbackFocus={() => outsideFocus}\n propsItem={propsItem}\n propsToast={propsToast}\n progress={progress}\n propsProgress={propsProgress}\n propsCollapse={propsCollapse}\n />\n ))}\n </FlexOrderedList>\n </section>\n </ConfigScope>\n </Teleport>\n );\n };\n },\n);\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8JA,SAAA,QAAA,GAAA;CAAA,OAAA,OAAA,MAAA,cAAA,OAAA,UAAA,SAAA,KAAA,CAAA,MAAA,qBAAA,CAAAA,QAAA,CAAA;AAAA;AASA,IAAI,SAAS;;AAGb,SAAgB,cAAc,EAC5B,UAAU,kBAAkB,QACV,CAAC,GAAsB;CAEzC,MAAM,SAAS,gBAAgC,CAAA,CAAE;CACjD,MAAM,yBAAS,IAAI,IAAmB;CACtC,MAAM,QAAQ,gBAAgB,EAAE,QAAQ,MAAM,CAAC;CAC/C,IAAI,aAAa;CACjB,IAAI,QAAQ;CAEZ,MAAM,QAAQ,OAAe,OAAO,MAAM,UAAU,MAAM,OAAO,EAAE;CAEnE,SAAS,MAAM,IAAY,OAAc;EACvC,MAAM,YAAY,KAAK,IAAI;EAC3B,MAAM,SAAS,iBAAiB,MAAM,IAAI,SAAS,GAAG,MAAM,SAAS;CACvE;CAEA,SAAS,MAAM,IAAY,QAAiC;EAC1D,MAAM,QAAQ,KAAK,EAAE;EACrB,IAAI,CAAC,SAAS,MAAM,SAAS;EAC7B,MAAM,UAAU;EAChB,MAAM,YAAY;EAClB,aAAa,OAAO,IAAI,MAAM,EAAE,CAAC,EAAE,MAAM;EACzC,OAAO,OAAO,MAAM,EAAE;EACtB,MAAM,UAAU,MAAM;CACxB;CAEA,SAAS,KAAK,OAAc;EAC1B,IAAI,MAAM,WAAW,KAAA,GAAW;EAChC,aAAa,MAAM,MAAM;EACzB,MAAM,SAAS,KAAA;EACf,MAAM,aAAa,KAAK,IAAI,IAAI,MAAM;CACxC;;CAGA,SAAS,OAAO;EACd,MAAM,QAAQ,IAAI,IAAI,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,UAAU,MAAM,EAAE,CAAC;EACnE,KAAK,MAAM,CAAC,IAAI,UAAU,QAAQ;GAChC,MAAM,UAAU,MAAM,WAAW,KAAA;GACjC,IAAI,CAAC,MAAM,UAAU,MAAM,IAAI,EAAE,GAC3B;QAAA,CAAC,SAAS,MAAM,IAAI,KAAK;GAAA,OACxB,IAAI,SAAS,KAAK,KAAK;EAChC;CACF;CAEA,SAAS,SAAS,OAAqB;EACrC,aAAa,OAAO,IAAI,MAAM,EAAE,CAAC,EAAE,MAAM;EACzC,OAAO,OAAO,MAAM,EAAE;EACtB,MAAM,WAAW,MAAM,UAAU,WAAY,MAAM,YAAY;EAC/D,MAAM,YAAY,OAAO,SAAS,QAAQ,IAAI;GAAE;GAAU,IAAI,EAAE;EAAW,IAAI;EAC/E,IAAI,CAAC,MAAM,WAAW;EACtB,MAAM,QAAe;GAAE,WAAW;GAAU,WAAW;EAAE;EACzD,OAAO,IAAI,MAAM,IAAI,KAAK;EAC1B,KAAK;CACP;CAEA,MAAM,WAAe,SAA0B,UAC7C,OAAO,YAAY,aAAa,QAAQ,KAAK,IAAI;CAEnD,MAAM,aAAgC;EACpC;EACA,IAAI,SAAS;GACX,OAAO,MAAM;EACf;EACA,KAAK,SAAS;GACZ,MAAM,WAAW,QAAQ,OAAO,KAAA,IAAY,KAAA,IAAY,KAAK,QAAQ,EAAE;GACvE,IAAI,YAAY,CAAC,SAAS,SAAS;IAEjC,KAAK,MAAM,OAAO,OAAO,KAAK,QAAQ,GACpC,IAAI,QAAQ,QAAQ,QAAQ,aAAa,QAAQ,aAAa,OAAO,SAAS;IAEhF,WAAW,OAAO,SAAS,IAAI,OAAO;IACtC,OAAO,SAAS;GAClB;GAEA,IAAI,UAAU,WAAW,OAAO,SAAS,EAAE;GAC3C,MAAM,KAAK,QAAQ,MAAM,SAAS,EAAE;GACpC,OAAO,KAAK,gBAAgB;IAAE,GAAG;IAAS;IAAI,SAAS;IAAO,WAAW;GAAK,CAAC,CAAC;GAChF,SAAS,KAAK,EAAE,CAAE;GAClB,OAAO;EACT;EACA,UAAU,OAAO,YAAY,WAAW,KAAK;GAAE,GAAG;GAAS;GAAO,MAAM;EAAU,CAAC;EACnF,QAAQ,OAAO,YACb,WAAW,KAAK;GAAE,WAAW;GAAM,GAAG;GAAS;GAAO,MAAM;EAAS,CAAC;EACxE,UAAU,OAAO,YAAY,WAAW,KAAK;GAAE,GAAG;GAAS;GAAO,MAAM;EAAU,CAAC;EACnF,OAAO,OAAO,YAAY,WAAW,KAAK;GAAE,GAAG;GAAS;GAAO,MAAM;EAAO,CAAC;EAC7E,UAAU,OAAO,YACf,WAAW,KAAK;GAAE,UAAU;GAAO,GAAG;GAAS;GAAO,MAAM;GAAW,SAAS;EAAK,CAAC;EACxF,OAAO,IAAI,SAAS;GAClB,MAAM,QAAQ,KAAK,EAAE;GACrB,IAAI,CAAC,SAAS,MAAM,SAAS;GAC7B,OAAO,OAAO,OAAO,OAAO;GAC5B,SAAS,KAAK;EAChB;EACA,QAAQ,IAAI;GACV,KAAK,MAAM,SAAS,MAAM,OAAO,CAAC,GAAG,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC,GACtD,IAAI,OAAO,MAAM,MAAM,IAAI,WAAW;EAE1C;EACA,MAAM,QAAQ,SAAS,EAAE,SAAS,SAAS,SAAS;GAClD,MAAM,KAAK,WAAW,KAAK;IAAE,UAAU;IAAO,GAAG;IAAS,SAAS;GAAK,CAAC;GACzE,IAAI;IACF,MAAM,QAAQ,MAAM;IACpB,WAAW,OAAO,IAAI;KACpB,MAAM;KACN,UAAU;KACV,GAAG,QAAQ,SAAS,KAAK;KACzB,SAAS;IACX,CAAC;IACD,OAAO;GACT,SAAS,QAAQ;IACf,WAAW,OAAO,IAAI;KACpB,MAAM;KACN,UAAU;KACV,GAAG,QAAQ,OAAO,MAAM;KACxB,SAAS;IACX,CAAC;IACD,MAAM;GACR;EACF;EACA,QAAQ;GACN,IAAI,MAAM,QAAQ;GAClB,MAAM,SAAS;GACf,KAAK;EACP;EACA,SAAS;GACP,IAAI,CAAC,MAAM,QAAQ;GACnB,MAAM,SAAS;GACf,KAAK;EACP;EACA,OAAO,IAAI;GACT,MAAM,QAAQ,OAAO,WAAW,UAAU,MAAM,OAAO,EAAE;GACzD,IAAI,SAAS,GAAG,OAAO,OAAO,OAAO,CAAC;GACtC,aAAa,OAAO,IAAI,EAAE,CAAC,EAAE,MAAM;GACnC,OAAO,OAAO,EAAE;GAEhB,KAAK;EACP;EACA,SAAS,MAAM;GACb,QAAQ;GACR,KAAK;EACP;CACF;CACA,OAAO;AACT;;;AA0CA,IAAM,YAAY,IAAI,UAAU;CAC9B,MAAM,EAAE,WAAW,YAAY;CAC/B,IAAI,EAAE,WAAW,YAAY;AAC/B,CAAC;;AAGD,IAAa,cAAc,IAAI,OAAO;CACpC,QAAQ;EAEN,UAAU;EACV,OAAO;EACP,QAAQ;EACR,SAAS;EACT,aAAa;EACb,iBAAiB;EACjB,UAAU;EACV,WAAW;EACX,OAAO;EACP,WAAW;EAEX,eAAe;EACf,cAAc;CAChB;CACA,MAAM;EACJ,eAAe;EAEf,aAAa;EACb,SAAS;GAAE,SAAS;GAAG,mBAAmB;EAAE;EAC5C,YAAY,WAAW,EAAE,UAAU,GAAE,GAAI,EAAE,QAAQ,MAAK,cAAe,EAAE,UAAU,GAAE,GAAI,EAAE,QAAQ;CACrG;CACA,gBAAgB,EAAE,WAAW;EAAE,SAAS;EAAQ,mBAAmB;CAAW,EAAE;CAChF,aAAa,EAAE,WAAW;EAAE,SAAS;EAAQ,mBAAmB;CAAY,EAAE;CAC9E,aAAa;EACX,SAAS;EACT,YAAY,WAAW,EAAE,UAAU,GAAE,GAAI,EAAE,QAAQ;CACrD;CAEA,KAAK;EAAE,UAAU;EAAY,cAAc;CAAM;CACjD,OAAO,EAAE,WAAW,MAAM;CAE1B,WAAW;EACT,UAAU;EACV,aAAa;EACb,QAAQ,QAAQ,EAAE,MAAM,GAAE,KAAM,EAAE,aAAa,GAAE;EACjD,QAAQ;EACR,cAAc;EACd,iBAAiB,sBAAsB,EAAE,OAAO,UAAS;EACzD,iBAAiB;GAAE,SAAS;GAAQ,aAAa;EAAQ;EACzD,eAAe;EACf,yBAAyB;EACzB,mBAAmB;EACnB,eAAe;CACjB;CACA,iBAAiB,EAAE,oBAAoB,SAAS;CAEhD,SAAS;EACP,UAAU;EACV,YAAY,WAAW,EAAE,UAAU,GAAE,GAAI,EAAE,QAAQ,SAAQ,cAAe,EAAE,UAAU,GAAE,GAAI,EAAE,QAAQ,SAAQ,UAAW,EAAE,UAAU,GAAE,GAAI,EAAE,QAAQ;CACvJ;CACA,eAAe;EAAE,UAAU;EAAY,aAAa;CAAE;CACtD,eAAe,EAAE,SAAS,EAAE;CAC5B,gBAAgB,EAAE,KAAK,EAAE;CACzB,mBAAmB,EAAE,QAAQ,EAAE;AACjC,CAAC;;AAID,IAAa,mBAAmB,IAAI,OAAO,EACzC,QAAQ,QAAgB,OAAe,YAAoB;CAAE,WAAW;CAAQ;CAAO;AAAO,GAChG,CAAC;;AAID,IAAa,uBAAuB,IAAI,OAAO,EAC7C,WAAW,QAAgB,EAAE,mBAAmB,GAAG,GACrD,CAAC;;AAGD,IAAa,uBAAuB,IAAI,OAAO;CAC7C,aAAa;EAAE,KAAK;EAAG,kBAAkB;CAAE;CAC3C,KAAK;EAAE,KAAK;EAAG,aAAa;EAAG,cAAc;CAAO;CACpD,WAAW;EAAE,KAAK;EAAG,gBAAgB;CAAE;CACvC,gBAAgB;EAAE,QAAQ;EAAG,kBAAkB;CAAE;CACjD,QAAQ;EAAE,QAAQ;EAAG,aAAa;EAAG,cAAc;CAAO;CAC1D,cAAc;EAAE,QAAQ;EAAG,gBAAgB;CAAE;AAC/C,CAAC;;;AA2BD,IAAM,cAAc;;AAGpB,SAAS,QAAQ,OAAe,SAAkB;CAChD,MAAM,SAAS,MAAM,UAAU,MAAO,QAAS,MAAK;CACpD,OAAO;EACL,YAAY;EACZ,QAAQ,KAAK,CACX,YAAY,eACZ,UAAU,YAAY,iBAAiB,YAAY,iBAAiB;EAEtE,iBAAiB,MAAM,QAAQ,IAAI,QAAQ,KAAM,MAAM,KAAK;EAC5D,QAAQ,KAAK,YAAY;CAAa;AAE1C;;AAGA,IAAa,YAAY,0BACvB,QAAA,SAWsB;CAAA,MAAA,QAAAC,aAAA,QAAA,OAAA;CAAA,MAAA,aAAAA,aAAA,QAAA,YAAA;CAAA,MAAA,UAAAA,aAAA,QAAA,SAAA;CAAA,MAAA,gBAAAA,aAAA,QAAA,eAAA;CAAA,MAAA,QAAAA,aAAA,QAAA,OAAA;CAAA,MAAA,WAAAA,aAAA,QAAA,UAAA;CAAA,MAAA,YAAAA,aAAA,QAAA,WAAA;CAAA,MAAA,aAAAA,aAAA,QAAA,YAAA;CAAA,MAAA,gBAAAA,aAAA,QAAA,eAAA;CAAA,MAAA,gBAAAA,aAAA,QAAA,eAAA;CACpB,IAAI,OAA2B;CAC/B,MAAM,UAAU,WAAW;CAE3B,MAAM,WAAW,kBAAkB,CAAC,MAAA,MAAM,SAAS,EACjD,gBAAgB,WAAA,MAAW,OAAO,MAAA,MAAM,EAAE,EAC5C,CAAC;CAED,SAAS,QAAQ;EACf,IAAI,MAAM,kBAAkB,MAAM,cAAA,MAAc,CAAC;EACjD,WAAA,MAAW,QAAQ,MAAA,MAAM,EAAE;CAC7B;CAGA,SAAS,UAAU,OAAsB;EACvC,IAAI,MAAM,QAAQ,YAAY,MAAA,MAAM,aAAa,SAAS,MAAA,MAAM,SAAS;EACzE,MAAM,eAAe;EACrB,MAAM,gBAAgB;EACtB,MAAM;CACR;CAIA,SAAS,cAAc,OAAqB;EAC1C,MAAM,UAAU;EAChB,IAAI,CAAC,WAAW,MAAA,MAAM,WAAW,MAAA,MAAM,aAAa,OAAO;EAC3D,IAAK,MAAM,OAAmB,QAAQ,oCAAoC,GAAG;EAC7E,MAAM,QAAQ,QAAQ,sBAAsB,CAAC,CAAC;EAC9C,UAAU,OAAO;GACf,WAAW;GACX,SAAS,EAAE,aAAa;IAEtB,QAAQ,MAAM,aAAa;IAC3B,MAAW,IAAI,SAAS;KACtB,GAAG;KACH,SAAS,KAAK,IAAI,GAAG,IAAI,KAAK,IAAI,MAAM,IAAI,KAAK;IACnD,CAAC;GACH;GACA,QAAQ,EAAE,QAAQ,OAAO,gBAAgB;IAEvC,MAAM,gBAAgB;KACpB,QAAQ,MAAM,aAAa;KAC3B,QAAQ,MAAM,eAAe,WAAW;KACxC,QAAQ,MAAM,eAAe,SAAS;IACxC;IACA,IAAI,SAAS,CAAC,aAAa,KAAK,IAAI,MAAM,IAAI,QAAQ,aAAa;KACjE,MAAM,MAAM;MAAE,GAAG,KAAK,KAAK,MAAM,IAAI;MAAO,SAAS;KAAE;KACvD,QAAa,SAAS,KAAK;MAAE,UAAU;MAAM,MAAM;KAAO,CAAC,CAAC,CAAC,WAAW;MACtE,MAAM;MACN,QAAQ,MAAM,aAAa;KAC7B,CAAC;KACD;IACF;IACA,QAAa,SAAS;KAAE,GAAG;KAAG,SAAS;IAAE,GAAG,EAAE,QAAQ,IAAK,CAAC,CAAC,CAAC,KAAK,OAAO;GAC5E;EACF,CAAC;CACH;CAEA,aAAa;EACX,MAAM,YAAY,OAAA,UAAA,UAAqB,aAAa,UAAA,MAAA,MAAA,KAAe,IAAC,UAAA;EACpE,MAAM,aAAa,OAAA,WAAA,UAAsB,aAAa,WAAA,MAAA,MAAA,KAAgB,IAAC,WAAA;EACvE,OAAAC,YAAA,MAAAC,cAEQ,WACF,EACE,IAAI;GACF,YAAY;GACZ,QAAA,QAAU,YAAY,cAAc,YAAY;GAChD,MAAA,SAAS,QAAQ,QAAO,MAAA,OAAA,QAAA,KAAe;GACvC,MAAA,MAAM,WAAW,YAAY;EAAW,EAE5C,GACA,WACA;GAEE,aAAa,MAAA,MAAM,YAAY,cAAc,KAAA;GAC7C,eAAe,MAAA,MAAM,aAAa,KAAA;GAClC,MAAM,YAAqB;IACzB,OAAO,UAAU,OAAO;IACxB,SAAS,IAAI,OAAO;GACtB;GACA;GACA;EACF,CACF,GAAC,EAAA,cACW,SAAS,MAAK,CAAA,GAAA,CAAAD,YAAA,UAAAC,cAGpB,eACF,EAAE,cAAc,EAAE,IAAI,YAAY,IAAI,EAAE,GAAC,cAAA,KAE3C,GAAC,EAAA,SACM,CAAC,MAAA,MAAM,QAAO,CAAA,GAAA,EAAA,eAAA,CAAAD,YAAA,OAGf,eACF;GACE,SAAS;GACT,MAAM,MAAA,MAAM,QAAQ;GACpB,MAAM,MAAA,MAAM;GACZ,SAAS,MAAA,MAAM;GACf,OAAO,MAAA,MAAM;GACb,aAAa,MAAA,MAAM;GACnB,QAAQ,MAAA,MAAM;GACd,IAAI,YAAY;EAClB,GACA,YACA,EAAE,SAAS,MAAA,MAAM,aAAa,QAAQ,KAAA,IAAY,MAAM,CAC1D,GAAC,IAAA,GAEF,SAAA,SAAY,MAAA,MAAM,aAASA,YAAA,QAAAC,cAAA,EAAA,OAEnB,MAAA,MAAM,UAAU,GAAE,GACnB,WAAmB,EAAE,IAAI,YAAY,UAAU,GAAC,cAAA,OAAiB;GACnE,IAAI,CAAA,IAAA,KACF,qBAAqB,gBAAQ,CAAC,GAAG,MAAA,MAAM,UAAU,SAAQ,GAAI,GAAA,IAAA,GAAA,IAAA,aAC7D,WAAA,MAAW,UAAU,YAAY,iBAAe,IAAA,CAAA;GAElD,eAAe;EACjB,CAAC,CAAC,GAAA,IAAA,CAEL,EAAA,CAAA,CAAA,CAAA;CAIT;AACF,GACA;CAAA,OAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;CAAA;CAAE,MAAM;AAAY,CACtB;;AAGA,IAAa,UAAU,0BACrB,SAAA,UAYoB;CAAA,MAAA,SAAA,CAAA;CAAA,MAAA,aAAAF,aAAA,SAAA,YAAA;CAAA,MAAA,YAAAA,aAAA,SAAA,aAVN,YAAY;CAAA,MAAA,MAAAA,aAAA,SAAA,OAClB,CAAC;CAAA,MAAA,QAAAA,aAAA,SAAA,SACC,KAAK;CAAA,MAAA,WAAAA,aAAA,SAAA,YACF,KAAK;CAAA,MAAA,YAAAA,aAAA,SAAA,WAAA;CAAA,MAAA,YAAAA,aAAA,SAAA,WAAA;CAAA,MAAA,aAAAA,aAAA,SAAA,YAAA;CAAA,MAAA,gBAAAA,aAAA,SAAA,eAAA;CAAA,MAAA,gBAAAA,aAAA,SAAA,eAAA;CAAA,MAMb,OAAIG,UAAA,SAAA,OAAA,IAAA;CAEP,MAAM,OAAO,QAAQ;CACrB,IAAI,SAAMC,WAAkC,IAAI;CAChD,IAAI,WAAQC,IAAU,KAAK;CAC3B,IAAI,UAAOA,IAAU,KAAK;CAC1B,MAAM,cAAc,eAAe;CAEnC,IAAI,OAAID,WAAkC,IAAI;CAC9C,IAAI,WAAQC,IAAU,KAAK;CAC3B,MAAM,SAAS,yBAAmB,KAAA,OAAY,EAAE,UAAU,cAAc,CAAC;CACzE,YACQ,SAAA,SAAA,QAAA,QACL,SAAS;EACR,IAAA,MAAA,OAAW,OAAY,aAAc,SAAA,QAAW,IAAK;OAChD,SAAA,QAAW;CAClB,CACF;CAEA,IAAI,eAAmC;CAEvC,aAAc;EACZ,IAAI,SAAA,SAAA,QAAA,SAAuB,CAAC,YAAY,OAAO,WAAA,MAAW,MAAM;OAC3D,WAAA,MAAW,OAAO;CACzB,CAAC;CAGD,MAAM,eAAe;EACnB,IAAI,OAAA,OAAQ,eAAe,CAAC,OAAA,MAAO,QAAQ,eAAe,GAAG,OAAA,MAAO,YAAY;CAClF;CACA,MAAM,gBAAgB,KAAK,SAAS,MAAM,CAAC;CAG3C,MAAM,YAAuB,CAAC;CAC9B,iBACS,OAAA,SAAU,WAAA,MAAW,OAAO,SAAS,EAAE,UAAU,OAAA,OAAQ,MAAM,EAAE,IAAI,WAC5E,EAAE,UAAU,KAAK,CACnB;CACA,UAAU,MAAM;CAEhB,YACE,IAAA,QACC,UAAU,WAAA,MAAW,SAAS,KAAK,GACpC,EAAE,WAAW,KAAK,CACpB;CACA,sBAAsB;EACpB,WAAA,MAAW,SAAS,QAAQ;EAC5B,WAAA,MAAW,OAAO;CACpB,CAAC;CAED,SAAS,UAAU,OAAmB;EACpC,QAAA,QAAU;EACV,MAAM,OAAO,MAAM;EACnB,IAAI,gBAAgB,eAAe,CAAC,OAAA,OAAQ,SAAS,IAAI,GAAG,eAAe;CAC7E;CACA,SAAS,WAAW,OAAmB;EACrC,MAAM,KAAK,MAAM;EACjB,IAAI,EAAE,cAAc,QAAQ,OAAA,OAAQ,SAAS,EAAE,IAAI,QAAA,QAAU;CAC/D;CAEA,aAAa;EAAA,IAAA;EACX,MAAM,UAAU,UAAA,MAAU,WAAW,KAAK;EAC1C,MAAM,UAAU,WAAA,MAAW,OAAO,MAAM,CAAA,IAAA,KAAI;EAE5C,IAAI,QAAQ,QAAQ,QAAQ,UAAU,CAAC,MAAM,OAAO,CAAC,CAAC;EACtD,MAAM,SAAS,QAAQ,KAAK,UAAW,MAAM,UAAU,QAAQ,EAAE,KAAM;EACvE,MAAM,UAAU,MAAA,SAAS,CAAA,SAAA;EACzB,OAAAJ,YAAA,UAAA,EAAA,MACgB,SAAS,KAAK,OAAM,GAAA,EAAA,eAAA,CAAAA,YAAA,aAAA,MAAA,EAAA,eAAA,CAAAA,YAAA,WAAAC,cAGxB,WACF;GACE,IAAI,CAAC,YAAY,QAAQ,qBAAoB,UAAA,MAAW;GACxD,cAAc,KAAK;EACrB,GACA,MACA;GACE,KAAG;GACH,SAAS;GACT,UAAU;GACV,sBAAuB,SAAA,QAAW;GAClC,sBAAuB,SAAA,QAAW;GAClC;GACA;EACF,CACF,GAAC;GAAA,kBAAA,UAAA;GAAA,iBAEc,MAAA,QAAQ,OAAO,CAAC,OAAO,IAAI,KAAA;EAAS,CAAA,GAAA,CAAAD,YAAA,iBAG7C,eAEF,EAAE,WAAW,UAAU,mBAAmB,SAAS,GAAC,UAAA,OAEpD;GACE,MAAM,UAAoB,KAAA,QAAO,UAAU,KAAK;GAChD,aAAa;EACf,CACF,GAAC,QAAA,QAEA,QAAQ,KAAK,OAAO,UAAKA,YAAA,WAAA;GAAA,OAEjB,MAAM;GAAE,SACN,UAAU,OAAO,SAAU;GAAI,SAC/B;GAAK,cAAA,WAAA;GAAA,WAEH;GAAO,iBAAA,OAAA,OAAA,OAAA,WACK;GAAY,aAAA,UAAA;GAAA,cAAA,WAAA;GAAA,YAAA,SAAA;GAAA,iBAAA,cAAA;GAAA,iBAAA,cAAA;EAAA,GAAA,IAAA,CAOpC,CAAC,IAAA,QAAA,EAAA,eAAA,CAAA,KAAA,EAAA,CAAA,CAAA,CAAA,CAAA,EAAA,CAAA,CAAA,EAAA,CAAA;CAMd;AACF,GAAC;CAAA,MAAA;CAAA,OAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;CAAA;AAAA,CACH"}
|
package/dist/form/Field.js
CHANGED
|
@@ -7,8 +7,8 @@ import { plainText } from "../utils/data/text.js";
|
|
|
7
7
|
import { Text as Text$1 } from "../base/Text.js";
|
|
8
8
|
import { FieldLayoutContext } from "./field-layout.js";
|
|
9
9
|
import { FormContext } from "./Form.js";
|
|
10
|
-
import { Fragment, cloneVNode, createVNode, isVNode,
|
|
11
|
-
import { _component, childrenProp, createContext, effect, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
10
|
+
import { Fragment, cloneVNode, createVNode, isVNode, onBeforeUnmount, unref, useId, watch } from "vue";
|
|
11
|
+
import { _component, childrenProp, createContext, effect, mergeJsxProps, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
12
12
|
import * as css from "@zerodep-dev/vue-tsx-pro-tsx/styles";
|
|
13
13
|
import { Asterisk } from "@lucide/icons";
|
|
14
14
|
//#region src/form/Field.tsx
|
|
@@ -112,7 +112,7 @@ var Label = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
112
112
|
const propsRequired = readonlyProp(_props, "propsRequired");
|
|
113
113
|
const children = childrenProp(_props);
|
|
114
114
|
const rest = restProps(_props, _ctx, false);
|
|
115
|
-
return () => createVNode("label",
|
|
115
|
+
return () => createVNode("label", mergeJsxProps(mergeAttrs({ sx: [labelStyles.root, disabled.value && labelStyles.disabled] }, rest, { onClick: focusGroupControl }), { "data-disabled": disabled.value || void 0 }), [children.value, required.value && createVNode(Icon, mergePartProps({
|
|
116
116
|
icon: Asterisk,
|
|
117
117
|
sx: labelStyles.required
|
|
118
118
|
}, propsRequired.value), null)]);
|
|
@@ -278,13 +278,13 @@ var Field = /*#__PURE__*/ _component((_props2, _ctx2) => {
|
|
|
278
278
|
const start = placement === "start" && !labelHidden.value;
|
|
279
279
|
const width = labelWidth.value ?? layout?.labelWidth ?? DEFAULT_LABEL_WIDTH;
|
|
280
280
|
const align = labelAlign.value ?? layout?.labelAlign ?? "end";
|
|
281
|
-
const hints = [hasContent(description.value) && createVNode(Text$1,
|
|
281
|
+
const hints = [hasContent(description.value) && createVNode(Text$1, mergeJsxProps(mergePartProps({
|
|
282
282
|
tone: "muted",
|
|
283
283
|
sx: fieldStyles.hint
|
|
284
284
|
}, propsDescription.value, { id: ids.description }), { "key": "__site0" }), {
|
|
285
285
|
default: () => [unref(description)],
|
|
286
286
|
$stable: true
|
|
287
|
-
}), showError() && createVNode(Text$1,
|
|
287
|
+
}), showError() && createVNode(Text$1, mergeJsxProps(mergePartProps({
|
|
288
288
|
tone: "danger",
|
|
289
289
|
sx: [fieldStyles.hint, fieldStyles.error]
|
|
290
290
|
}, propsError.value, { id: ids.error }), { "key": "__site1" }), {
|
|
@@ -294,11 +294,11 @@ var Field = /*#__PURE__*/ _component((_props2, _ctx2) => {
|
|
|
294
294
|
}, null), message()],
|
|
295
295
|
$stable: true
|
|
296
296
|
})];
|
|
297
|
-
return createVNode("div",
|
|
297
|
+
return createVNode("div", mergeJsxProps(mergeAttrs({ sx: start ? [fieldStyles.start, css.bind(fieldColumnStyles.columns, () => [typeof width === "number" ? `${width}px` : width])] : fieldStyles.root }, rest, { onFocusout }), {
|
|
298
298
|
"data-invalid": (invalid.value ?? message() != null) || void 0,
|
|
299
299
|
"data-disabled": isDisabled() || void 0,
|
|
300
300
|
"data-label-placement": placement
|
|
301
|
-
}), [hasContent(label.value) && createVNode(Label,
|
|
301
|
+
}), [hasContent(label.value) && createVNode(Label, mergeJsxProps(mergePartProps({
|
|
302
302
|
required: isRequired(),
|
|
303
303
|
disabled: isDisabled(),
|
|
304
304
|
sx: labelHidden.value ? visuallyHiddenStyles.hidden : start && (align === "end" ? fieldStyles.labelEnd : fieldStyles.labelStart)
|
package/dist/form/Field.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Field.js","names":["_vtp_readonlyProp","_vtp_childrenProp","_vtp_restProps","_createVNode","_mergeProps","Text","_vue_unref"],"sources":["../../src/form/Field.tsx"],"sourcesContent":["/**\n * 表单字段:标签 + 控件 + 说明 + 错误信息,并把它们关联起来(参考 Ark UI Field、React Aria 的 Label / Description / FieldError)。\n *\n * <Field label=\"邮箱\" description=\"用于找回密码\" error={errors.email} required>\n * <TextField _value={form.email} type=\"email\" />\n * </Field>\n *\n * Field 经上下文告诉里面的控件:控件的 id(标签的 for 指向它)、标签的 id(不是原生输入的控件用 aria-labelledby)、\n * 说明与错误的 id(aria-describedby),以及 invalid / required / disabled / readonly。\n * 控件上显式写的状态优先于 Field 的。控件用 useFieldControl 读取这些信息,单独使用(不在 Field 里)时照常工作。\n * 在 Form 里写了 name 时,错误信息取自表单控制器(useForm),焦点离开字段时报告失焦(何时显示错误见 useForm);\n * 里面只有一个控件、控件上没写值时自动绑定表单的这个字段(valueProp 决定写 _value 还是 _checked),\n * 标签与控件登记给表单,错误汇总(FormErrorSummary)据此列出字段名、跳到控件。\n *\n * 标签可以放在起始边(`labelPlacement=\"start\"`):标签一列、控件一列,标签与控件的第一行文字按基线对齐,\n * 多行文本框、勾选组也对得齐。通常在 Form、FormSection 上统一设置(见 field-layout),整组的只读、不可用同样从那里来。\n *\n * 部件:Root(div)、Label(Label)、Control(div,标签在起始边时包住控件、说明与错误)、\n * Description(Text)、Error(Text,带警示图标)。\n */\nimport { Asterisk } from '@lucide/icons';\nimport { createContext, _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport {\n cloneVNode,\n Fragment,\n isVNode,\n onBeforeUnmount,\n useId,\n watch,\n type VNode,\n type VNodeChild,\n} from 'vue';\nimport { Icon, type IconProps } from '../base/Icon';\nimport { Text, type TextProps } from '../base/Text';\nimport { visuallyHiddenStyles } from '../base/VisuallyHidden';\nimport { plainText } from '../utils/data/text';\nimport { focusControl } from '../utils/dom/focus';\nimport { hasContent, mergeAttrs, mergePartProps, type PartProps } from '../utils/props';\nimport { statusIcons } from '../utils/style/tone-styles';\nimport {\n FieldLayoutContext,\n type FieldLabelAligns,\n type FieldLabelPlacements,\n} from './field-layout';\nimport { FormContext } from './Form';\nimport type { FieldConstraints } from './form-constraints';\n\nconst NO_CONSTRAINTS: FieldConstraints = Object.freeze({});\n\n/** Field 交给控件的信息 */\nexport interface FieldState {\n /** 控件(主元素)的 id,标签的 for 指向它 */\n readonly controlId: string;\n /** 标签的 id:控件不是原生输入元素(滑块、单选组)时用 aria-labelledby 指向它 */\n readonly labelId: string | undefined;\n /** 说明与错误信息的 id,空格分隔,给 aria-describedby */\n readonly describedBy: string | undefined;\n /** 校验未通过 */\n readonly invalid: boolean;\n /** 必填 */\n readonly required: boolean;\n /** 不可用 */\n readonly disabled: boolean;\n /** 只读 */\n readonly readonly: boolean;\n /** 从表单 schema 推出的约束(在 Form 里写了 name 时);控件据此加 maxlength、min / max 等 */\n readonly constraints: FieldConstraints;\n /**\n * 认领 Field 的 id 与标签:Field 里有几个控件时(一组勾选框),只有第一个控件拿 Field 的 id、用它的标签当名字,\n * 其余控件留着自己的名字(勾选框自己的文字),id 也不重复。认领到了返回 true\n */\n claim?(token: symbol): boolean;\n /** 认领的控件卸载了:下一个控件可以认领 */\n release?(token: symbol): void;\n}\n\n/** Field 的上下文:自定义控件用 useFieldControl 接入,一般不直接读它;null 表示子树不属于任何 Field */\nexport const FieldContext = createContext<FieldState | null>('Field');\n\n/** 控件自己的状态(不写时取 Field 的) */\nexport interface FieldControlOptions {\n /** 控件上显式写的 id */\n id?: string;\n /** 校验未通过 */\n invalid?: boolean;\n /** 必填;在 Form 里写了 name 时默认按表单的 schema(如 z.string().min(1)) */\n required?: boolean;\n /** 不可用 */\n disabled?: boolean;\n /** 只读 */\n readonly?: boolean;\n}\n\n/** useFieldControl 的选项 */\nexport interface FieldControlUse {\n /** 输入交给里面的另一个控件(DatePicker 里的 DateField):子树仍属于 Field,由那个控件接 */\n delegate?: boolean;\n}\n\n/** 控件合并 Field 之后的状态与关联属性 */\nexport interface FieldControl {\n /** 控件的 id:显式写的优先,其次是 Field 给的 */\n id: string | undefined;\n /** 不是原生输入元素的控件用它作 aria-labelledby */\n labelledBy: string | undefined;\n /** aria-describedby */\n describedBy: string | undefined;\n /** 校验未通过 */\n invalid: boolean;\n /** 必填 */\n required: boolean;\n /** 不可用 */\n disabled: boolean;\n /** 只读 */\n readonly: boolean;\n /** 从表单 schema 推出的约束;不在 Field 里、或推不出时为空对象。控件上显式写的属性优先 */\n constraints: FieldConstraints;\n}\n\n/**\n * 所在的表单重置时(form.reset()、type=\"reset\" 的按钮)调用:控件丢掉自己还没写回的输入(日期分段打了一半)。\n * 值本身由表单改回去;只是半截输入时值一直是 null,重置前后没有变化,控件不知道要清。在 setup 里调用\n */\nexport function useFormReset(onReset: () => void): void {\n const form = FormContext.inject(null);\n if (form) watch(() => form.resetCount, onReset);\n}\n\n/**\n * 控件读取所在 Field 的信息(在 setup 里调用,返回的函数在渲染时调用):\n * 控件上显式写的状态优先;不在 Field 里时用控件自己的,不可用与只读再看所在的 Form、FormSection。\n * 控件里再嵌的控件(Select 的搜索框、区间的两个框、时间面板的列表)不是 Field 的控件,\n * 拿到 Field 的 id、说明与必填会重复 id、让表单提交被空的搜索框拦住:调用后子树与 Field 隔开,\n * 除非写了 delegate\n */\nexport function useFieldControl(\n read: () => FieldControlOptions,\n { delegate = false }: FieldControlUse = {},\n): () => FieldControl {\n const field = FieldContext.inject(null);\n if (!delegate) FieldContext.provide(null);\n const layout = field ? null : FieldLayoutContext.inject(null);\n // 把输入交给里面控件的(delegate)不认领:由里面那个控件认领;它自己仍用 Field 的标签\n const token = Symbol('field-control');\n const owns = field == null ? false : delegate || (field.claim?.(token) ?? true);\n if (field && !delegate) onBeforeUnmount(() => field.release?.(token));\n return () => {\n const own = read();\n return {\n id: own.id ?? (owns ? field?.controlId : undefined),\n labelledBy: owns ? field?.labelId : undefined,\n describedBy: field?.describedBy,\n invalid: own.invalid ?? field?.invalid ?? false,\n required: own.required ?? field?.required ?? false,\n disabled: own.disabled ?? field?.disabled ?? layout?.disabled ?? false,\n readonly: own.readonly ?? field?.readonly ?? layout?.readonly ?? false,\n constraints: field?.constraints ?? NO_CONSTRAINTS,\n };\n };\n}\n\n/** 未声明的属性落到 label 上 */\nexport interface LabelProps extends PartProps<'label'> {\n /** 必填:文字后加一个星号图标(对读屏隐藏,必填由控件的 required 告诉读屏) */\n required?: boolean;\n /** 不可用:变淡 */\n disabled?: boolean;\n /** 必填标记的部件属性 */\n propsRequired?: Partial<IconProps>;\n children?: VNodeChild;\n}\n\n/** Field 的样式:root、disabled、required */\nexport const labelStyles = css.create({\n root: {\n display: 'inline-flex',\n alignItems: 'baseline',\n gap: '_xs',\n fontSize: '_sm',\n fontWeight: '_medium',\n lineHeight: '_tight',\n color: '_text',\n },\n disabled: { opacity: '_disabled' },\n required: { alignSelf: 'flex-start', width: '0.7em', height: '0.7em', color: '_dangerText' },\n});\n\n/**\n * for 指向的不是原生能被标签关联的元素(单选组、滑块组、日期分段这类 role=\"group\")时,点标签浏览器什么也不做:\n * 改为聚焦它里面第一个能聚焦的控件(同点原生输入框的标签)\n */\nfunction focusGroupControl(event: MouseEvent) {\n const label = event.currentTarget as HTMLLabelElement;\n if (event.defaultPrevented || label.control || !label.htmlFor) return;\n const target = label.ownerDocument.getElementById(label.htmlFor);\n if (target) focusControl(target);\n}\n\n/** 表单标签(label):for 指向控件;Field 里自动关联,单独使用时自己写 for */\nexport const Label = _component(\n ({ required = false, disabled = false, propsRequired, children, ...rest }: LabelProps) =>\n () => (\n <label\n {...mergeAttrs<'label'>(\n { sx: [labelStyles.root, disabled && labelStyles.disabled] },\n rest,\n { onClick: focusGroupControl },\n )}\n data-disabled={disabled || undefined}\n >\n {children}\n {required && (\n <Icon\n {...mergePartProps<IconProps>(\n { icon: Asterisk, sx: labelStyles.required },\n propsRequired,\n )}\n />\n )}\n </label>\n ),\n);\n\n/** 未声明的属性落到外层 div 上 */\nexport interface FieldProps extends PartProps<'div'> {\n /** 在 Form 里对应的字段路径(如 `email`、`address.city`):错误取自表单控制器,只有一个控件时自动绑定它的值 */\n name?: string;\n /**\n * 自动绑定值时写到控件的哪个属性:默认 _value;控件声明了 _checked 而没有 _value(Switch、单个 Checkbox)时是 _checked\n */\n valueProp?: '_value' | '_checked';\n /** 标签 */\n label?: VNodeChild;\n /** 控件下方的说明 */\n description?: VNodeChild;\n /** 错误信息:给了(且 invalid 不是 false)就显示,并把控件标为出错;在 Form 里写了 name 时默认取表单的错误 */\n error?: VNodeChild;\n /** 校验未通过;不写时有 error 就算出错 */\n invalid?: boolean;\n /** 必填 */\n required?: boolean;\n /** 不可用;不写时取所在 Form、FormSection 的 */\n disabled?: boolean;\n /** 只读;不写时取所在 Form、FormSection 的 */\n readonly?: boolean;\n /** 标签放在哪;不写时取所在 Form、FormSection 的,都没写时在上方 */\n labelPlacement?: keyof FieldLabelPlacements;\n /** 标签在起始边时标签列的宽度:数字是 px,也可以写 CSS 长度;默认 `'8em'` */\n labelWidth?: number | string;\n /** 标签在起始边时文字的对齐,默认 end(贴着控件) */\n labelAlign?: keyof FieldLabelAligns;\n /**\n * 标签只给读屏:视觉上隐藏,仍是控件的名称(表格式的重复行只在第一行显示列名、紧凑的筛选条);\n * 这时不占标签列,控件单独一列\n */\n labelHidden?: boolean;\n /** 标签的部件属性 */\n propsLabel?: Partial<LabelProps>;\n /** 控件列(标签在起始边时包住控件、说明与错误的 div)的部件属性 */\n propsControl?: PartProps<'div'>;\n /** 说明的部件属性 */\n propsDescription?: Partial<TextProps>;\n /** 错误信息的部件属性 */\n propsError?: Partial<TextProps>;\n /** 控件 */\n children?: VNodeChild;\n}\n\n/** 控件的组件声明了这个属性(运行时的 props 定义) */\nfunction declares(node: VNode, key: string): boolean {\n const declared = (node.type as { props?: unknown }).props;\n if (Array.isArray(declared)) return declared.includes(key);\n return typeof declared === 'object' && declared !== null && key in declared;\n}\n\n/** Field 里唯一的控件:一个组件的 VNode(数组里只有一项也算);不是时 undefined */\nfunction soleControl(children: VNodeChild): VNode | undefined {\n const node = Array.isArray(children) && children.length === 1 ? children[0] : children;\n if (!isVNode(node) || typeof node.type === 'string' || node.type === Fragment) return undefined;\n return node;\n}\n\n/** 标签在起始边时默认的标签列宽度:放得下七八个汉字 */\nconst DEFAULT_LABEL_WIDTH = '8em';\n\n/** Field 的样式:root、start、label…、control、hint、error、errorIcon */\nexport const fieldStyles = css.create({\n root: { display: 'flex', flexDirection: 'column', gap: '_xs', minWidth: 0 },\n // 标签一列、控件一列;第一行按基线对齐:标签与输入框里的文字、勾选项的文字在同一条线上\n start: {\n display: 'grid',\n columnGap: '_md',\n rowGap: '_xs',\n alignItems: 'baseline',\n },\n labelStart: { justifySelf: 'start', textAlign: 'start' },\n labelEnd: { justifySelf: 'end', textAlign: 'end' },\n // 没有标签时控件仍放在第二列,与上下的字段对齐\n controlWithoutLabel: { gridColumnStart: '2' },\n control: { display: 'flex', flexDirection: 'column', gap: '_xs', minWidth: 0 },\n hint: { fontSize: '_sm', lineHeight: '_normal' },\n error: { display: 'inline-flex', alignItems: 'flex-start', gap: '_xs' },\n errorIcon: { flexShrink: 0, marginBlock: '0.25em' },\n});\n\n/** Field 的动态样式:标签列的宽度 */\nexport const fieldColumnStyles = css.create({\n columns: (labelWidth: string) => ({ gridTemplateColumns: `${labelWidth} minmax(0, 1fr)` }),\n});\n\n/** 表单字段:标签、控件、说明、错误信息,自动关联 id 与状态 */\nexport const Field = _component(\n ({\n name,\n valueProp,\n label,\n description,\n error,\n invalid,\n required,\n disabled,\n readonly,\n labelPlacement,\n labelWidth,\n labelAlign,\n labelHidden = false,\n propsLabel,\n propsControl,\n propsDescription,\n propsError,\n children,\n ...rest\n }: FieldProps) => {\n const id = useId();\n const ids = {\n control: `${id}-control`,\n label: `${id}-label`,\n description: `${id}-description`,\n error: `${id}-error`,\n };\n const form = FormContext.inject(null);\n const layout = FieldLayoutContext.inject(null);\n // error={cond && '…'} 条件不成立时是 false:当作没写,用表单的错误\n const message = () =>\n hasContent(error) ? error : name != null ? form?.visibleError(name) : undefined;\n const showError = () => hasContent(message()) && invalid !== false;\n const constraints = () => (name != null && form ? form.constraints(name) : NO_CONSTRAINTS);\n const isRequired = () => required ?? constraints().required ?? false;\n const isDisabled = () => disabled ?? layout?.disabled ?? false;\n const isReadonly = () => readonly ?? layout?.readonly ?? false;\n\n // 第一个接入的控件认领 id 与标签(见 FieldState.claim)\n let owner: symbol | null = null;\n FieldContext.provide({\n claim: (token) => {\n owner ??= token;\n return owner === token;\n },\n release: (token) => {\n if (owner === token) owner = null;\n },\n controlId: ids.control,\n get labelId() {\n return hasContent(label) ? ids.label : undefined;\n },\n get describedBy() {\n const parts = [hasContent(description) && ids.description, showError() && ids.error];\n return parts.filter(Boolean).join(' ') || undefined;\n },\n get invalid() {\n return invalid ?? hasContent(message());\n },\n get required() {\n return isRequired();\n },\n get constraints() {\n return constraints();\n },\n get disabled() {\n return isDisabled();\n },\n get readonly() {\n return isReadonly();\n },\n });\n\n // 登记给表单:错误汇总据此显示字段名、跳到控件\n _effect(() =>\n name != null && form\n ? form.register(name, { label: plainText(label) ?? name, controlId: ids.control })\n : undefined,\n );\n\n /** 只有一个控件、控件上没写值时,自动绑定表单的这个字段(同 Ant Design Form.Item 的 name) */\n function bindValue(content: VNodeChild): VNodeChild {\n const control = name != null && form ? soleControl(content) : undefined;\n if (!control || !form) return content;\n const key =\n valueProp ??\n (declares(control, '_checked') && !declares(control, '_value') ? '_checked' : '_value');\n if (control.props && key in control.props) return content;\n const path = name!;\n return cloneVNode(control, {\n [key]: form.getValue(path),\n [`onUpdate:${key}`]: (value: unknown) => form.setValue(path, value),\n });\n }\n\n // 焦点离开整个字段(不是在字段内部移动)时报告失焦\n function onFocusout(event: FocusEvent) {\n const next = event.relatedTarget;\n const field = event.currentTarget as HTMLElement;\n if (name != null && !(next instanceof Node && field.contains(next))) form?.blur(name);\n }\n\n return () => {\n const placement = labelPlacement ?? layout?.labelPlacement ?? 'top';\n const start = placement === 'start' && !labelHidden;\n const width = labelWidth ?? layout?.labelWidth ?? DEFAULT_LABEL_WIDTH;\n const align = labelAlign ?? layout?.labelAlign ?? 'end';\n const hints = [\n hasContent(description) && (\n <Text\n {...mergePartProps<TextProps>(\n { tone: 'muted', sx: fieldStyles.hint },\n propsDescription,\n {\n id: ids.description,\n },\n )}\n >\n {description}\n </Text>\n ),\n showError() && (\n <Text\n {...mergePartProps<TextProps>(\n { tone: 'danger', sx: [fieldStyles.hint, fieldStyles.error] },\n propsError,\n { id: ids.error },\n )}\n >\n <Icon icon={statusIcons.danger} sx={fieldStyles.errorIcon} />\n {message()}\n </Text>\n ),\n ];\n return (\n <div\n {...mergeAttrs<'div'>(\n {\n sx: start\n ? [\n fieldStyles.start,\n fieldColumnStyles.columns(typeof width === 'number' ? `${width}px` : width),\n ]\n : fieldStyles.root,\n },\n rest,\n { onFocusout },\n )}\n data-invalid={(invalid ?? message() != null) || undefined}\n data-disabled={isDisabled() || undefined}\n data-label-placement={placement}\n >\n {hasContent(label) && (\n <Label\n {...mergePartProps<LabelProps>(\n {\n required: isRequired(),\n disabled: isDisabled(),\n sx: labelHidden\n ? visuallyHiddenStyles.hidden\n : start && (align === 'end' ? fieldStyles.labelEnd : fieldStyles.labelStart),\n },\n propsLabel,\n { id: ids.label, for: ids.control },\n )}\n >\n {label}\n </Label>\n )}\n {start ? (\n <div\n {...mergeAttrs<'div'>(\n {\n sx: [fieldStyles.control, !hasContent(label) && fieldStyles.controlWithoutLabel],\n },\n propsControl,\n )}\n >\n {bindValue(children)}\n {hints}\n </div>\n ) : (\n [bindValue(children), hints]\n )}\n </div>\n );\n };\n },\n);\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgDA,IAAM,iBAAmC,OAAO,OAAO,CAAC,CAAC;;;AA8BzD,IAAa,eAAe,cAAiC,OAAO;;;;;;;;AA8CpE,SAAgB,aAAa,SAA2B;CACtD,MAAM,OAAO,YAAY,OAAO,IAAI;CACpC,IAAI,MAAM,YAAY,KAAK,YAAY,OAAO;AAChD;;;;;;;;AASA,SAAgB,gBACd,MACA,EAAE,WAAW,UAA2B,CAAC,GACrB;CACpB,MAAM,QAAQ,aAAa,OAAO,IAAI;CACtC,IAAI,CAAC,UAAU,aAAa,QAAQ,IAAI;CACxC,MAAM,SAAS,QAAQ,OAAO,mBAAmB,OAAO,IAAI;CAE5D,MAAM,QAAQ,OAAO,eAAe;CACpC,MAAM,OAAO,SAAS,OAAO,QAAQ,aAAa,MAAM,QAAQ,KAAK,KAAK;CAC1E,IAAI,SAAS,CAAC,UAAU,sBAAsB,MAAM,UAAU,KAAK,CAAC;CACpE,aAAa;EACX,MAAM,MAAM,KAAK;EACjB,OAAO;GACL,IAAI,IAAI,OAAO,OAAO,OAAO,YAAY,KAAA;GACzC,YAAY,OAAO,OAAO,UAAU,KAAA;GACpC,aAAa,OAAO;GACpB,SAAS,IAAI,WAAW,OAAO,WAAW;GAC1C,UAAU,IAAI,YAAY,OAAO,YAAY;GAC7C,UAAU,IAAI,YAAY,OAAO,YAAY,QAAQ,YAAY;GACjE,UAAU,IAAI,YAAY,OAAO,YAAY,QAAQ,YAAY;GACjE,aAAa,OAAO,eAAe;EACrC;CACF;AACF;;;AAcA,IAAa,cAAc,IAAI,OAAO;CACpC,MAAM;EACJ,SAAS;EACT,YAAY;EACZ,KAAK;EACL,UAAU;EACV,YAAY;EACZ,YAAY;EACZ,OAAO;CACT;CACA,UAAU,EAAE,SAAS,YAAY;CACjC,UAAU;EAAE,WAAW;EAAc,OAAO;EAAS,QAAQ;EAAS,OAAO;CAAc;AAC7F,CAAC;;;;;AAMD,SAAS,kBAAkB,OAAmB;CAC5C,MAAM,QAAQ,MAAM;CACpB,IAAI,MAAM,oBAAoB,MAAM,WAAW,CAAC,MAAM,SAAS;CAC/D,MAAM,SAAS,MAAM,cAAc,eAAe,MAAM,OAAO;CAC/D,IAAI,QAAQ,aAAa,MAAM;AACjC;;AAGA,IAAa,QAAQ,0BACnB,QAAA,SAAA;CAAA,MAAA,WAAAA,aAAA,QAAA,YAAc,KAAK;CAAA,MAAA,WAAAA,aAAA,QAAA,YAAa,KAAK;CAAA,MAAA,gBAAAA,aAAA,QAAA,eAAA;CAAA,MAAA,WAAAC,aAAA,MAAA;CAAA,MAA8B,OAAIC,UAAA,QAAA,MAAA,KAAA;CAAA,aACrEC,YAAA,SAAAC,WAEQ,WACF,EAAE,IAAI,CAAC,YAAY,MAAM,SAAA,SAAY,YAAY,QAAQ,EAAE,GAC3D,MACA,EAAE,SAAS,kBAAkB,CAC/B,GAAC,EAAA,iBACc,SAAA,SAAY,KAAA,EAAS,CAAA,GAAA,CAAA,SAAA,OAGnC,SAAA,SAAAD,YAAA,MAEO,eACF;EAAE,MAAM;EAAU,IAAI,YAAY;CAAS,GAAC,cAAA,KAE9C,GAAC,IAAA,CAEJ,CAAA;AAEJ,GAAA;CAAA,MAAA;CAAA,OAAA;EAAA;EAAA;EAAA;CAAA;AAAA,CACL;;;AAgDA,SAAS,SAAS,MAAa,KAAsB;CACnD,MAAM,WAAY,KAAK,KAA6B;CACpD,IAAI,MAAM,QAAQ,QAAQ,GAAG,OAAO,SAAS,SAAS,GAAG;CACzD,OAAO,OAAO,aAAa,YAAY,aAAa,QAAQ,OAAO;AACrE;;AAGA,SAAS,YAAY,UAAyC;CAC5D,MAAM,OAAO,MAAM,QAAQ,QAAQ,KAAK,SAAS,WAAW,IAAI,SAAS,KAAK;CAC9E,IAAI,CAAC,QAAQ,IAAI,KAAK,OAAO,KAAK,SAAS,YAAY,KAAK,SAAS,UAAU,OAAO,KAAA;CACtF,OAAO;AACT;;AAGA,IAAM,sBAAsB;;AAG5B,IAAa,cAAc,IAAI,OAAO;CACpC,MAAM;EAAE,SAAS;EAAQ,eAAe;EAAU,KAAK;EAAO,UAAU;CAAE;CAE1E,OAAO;EACL,SAAS;EACT,WAAW;EACX,QAAQ;EACR,YAAY;CACd;CACA,YAAY;EAAE,aAAa;EAAS,WAAW;CAAQ;CACvD,UAAU;EAAE,aAAa;EAAO,WAAW;CAAM;CAEjD,qBAAqB,EAAE,iBAAiB,IAAI;CAC5C,SAAS;EAAE,SAAS;EAAQ,eAAe;EAAU,KAAK;EAAO,UAAU;CAAE;CAC7E,MAAM;EAAE,UAAU;EAAO,YAAY;CAAU;CAC/C,OAAO;EAAE,SAAS;EAAe,YAAY;EAAc,KAAK;CAAM;CACtE,WAAW;EAAE,YAAY;EAAG,aAAa;CAAS;AACpD,CAAC;;AAGD,IAAa,oBAAoB,IAAI,OAAO,EAC1C,UAAU,gBAAwB,EAAE,qBAAqB,GAAG,WAAU,iBAAkB,GAC1F,CAAC;;AAGD,IAAa,QAAQ,0BACnB,SAAA,UAoBkB;CAAA,MAAA,OAAAH,aAAA,SAAA,MAAA;CAAA,MAAA,YAAAA,aAAA,SAAA,WAAA;CAAA,MAAA,QAAAA,aAAA,SAAA,OAAA;CAAA,MAAA,cAAAA,aAAA,SAAA,aAAA;CAAA,MAAA,QAAAA,aAAA,SAAA,OAAA;CAAA,MAAA,UAAAA,aAAA,SAAA,SAAA;CAAA,MAAA,WAAAA,aAAA,SAAA,UAAA;CAAA,MAAA,WAAAA,aAAA,SAAA,UAAA;CAAA,MAAA,WAAAA,aAAA,SAAA,UAAA;CAAA,MAAA,iBAAAA,aAAA,SAAA,gBAAA;CAAA,MAAA,aAAAA,aAAA,SAAA,YAAA;CAAA,MAAA,aAAAA,aAAA,SAAA,YAAA;CAAA,MAAA,cAAAA,aAAA,SAAA,eAPF,KAAK;CAAA,MAAA,aAAAA,aAAA,SAAA,YAAA;CAAA,MAAA,eAAAA,aAAA,SAAA,cAAA;CAAA,MAAA,mBAAAA,aAAA,SAAA,kBAAA;CAAA,MAAA,aAAAA,aAAA,SAAA,YAAA;CAAA,MAAA,WAAAC,aAAA,OAAA;CAAA,MAMhB,OAAIC,UAAA,SAAA,OAAA,KAAA;CAEP,MAAM,KAAK,MAAM;CACjB,MAAM,MAAM;EACV,SAAS,GAAG,GAAE;EACd,OAAO,GAAG,GAAE;EACZ,aAAa,GAAG,GAAE;EAClB,OAAO,GAAG,GAAE;CACd;CACA,MAAM,OAAO,YAAY,OAAO,IAAI;CACpC,MAAM,SAAS,mBAAmB,OAAO,IAAI;CAE7C,MAAM,gBACJ,WAAU,MAAA,KAAM,IAAC,MAAA,QAAW,KAAA,SAAQ,OAAO,MAAM,aAAY,KAAA,KAAK,IAAI,KAAA;CACxE,MAAM,kBAAkB,WAAW,QAAQ,CAAC,KAAK,QAAA,UAAY;CAC7D,MAAM,oBAAqB,KAAA,SAAQ,QAAQ,OAAO,KAAK,YAAW,KAAA,KAAK,IAAI;CAC3E,MAAM,mBAAmB,SAAA,SAAY,YAAY,CAAC,CAAC,YAAY;CAC/D,MAAM,mBAAmB,SAAA,SAAY,QAAQ,YAAY;CACzD,MAAM,mBAAmB,SAAA,SAAY,QAAQ,YAAY;CAGzD,IAAI,QAAuB;CAC3B,aAAa,QAAQ;EACnB,QAAQ,UAAU;GAChB,UAAU;GACV,OAAO,UAAU;EACnB;EACA,UAAU,UAAU;GAClB,IAAI,UAAU,OAAO,QAAQ;EAC/B;EACA,WAAW,IAAI;EACf,IAAI,UAAU;GACZ,OAAO,WAAU,MAAA,KAAM,IAAI,IAAI,QAAQ,KAAA;EACzC;EACA,IAAI,cAAc;GAEhB,OAAO,CADQ,WAAU,YAAA,KAAY,KAAK,IAAI,aAAa,UAAU,KAAK,IAAI,KACvE,CAAK,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,GAAG,KAAK,KAAA;EAC5C;EACA,IAAI,UAAU;GACZ,OAAO,QAAA,SAAW,WAAW,QAAQ,CAAC;EACxC;EACA,IAAI,WAAW;GACb,OAAO,WAAW;EACpB;EACA,IAAI,cAAc;GAChB,OAAO,YAAY;EACrB;EACA,IAAI,WAAW;GACb,OAAO,WAAW;EACpB;EACA,IAAI,WAAW;GACb,OAAO,WAAW;EACpB;CACF,CAAC;CAGD,aACE,KAAA,SAAQ,QAAQ,OACZ,KAAK,SAAQ,KAAA,OAAO;EAAE,OAAO,UAAS,MAAA,KAAM,KAAC,KAAA;EAAU,WAAW,IAAI;CAAQ,CAAC,IAC/E,KAAA,CAAS;;CAIf,SAAS,UAAU,SAAiC;EAClD,MAAM,UAAU,KAAA,SAAQ,QAAQ,OAAO,YAAY,OAAO,IAAI,KAAA;EAC9D,IAAI,CAAC,WAAW,CAAC,MAAM,OAAO;EAC9B,MAAM,MACJ,UAAA,UACC,SAAS,SAAS,UAAU,KAAK,CAAC,SAAS,SAAS,QAAQ,IAAI,aAAa;EAChF,IAAI,QAAQ,SAAS,OAAO,QAAQ,OAAO,OAAO;EAClD,MAAM,OAAI,KAAA;EACV,OAAO,WAAW,SAAS;IACxB,MAAM,KAAK,SAAS,IAAI;IACxB,YAAY,SAAS,UAAmB,KAAK,SAAS,MAAM,KAAK;EACpE,CAAC;CACH;CAGA,SAAS,WAAW,OAAmB;EACrC,MAAM,OAAO,MAAM;EACnB,MAAM,QAAQ,MAAM;EACpB,IAAI,KAAA,SAAQ,QAAQ,EAAE,gBAAgB,QAAQ,MAAM,SAAS,IAAI,IAAI,MAAM,KAAI,KAAA,KAAK;CACtF;CAEA,aAAa;EACX,MAAM,YAAY,eAAA,SAAkB,QAAQ,kBAAkB;EAC9D,MAAM,QAAQ,cAAc,WAAW,CAAA,YAAA;EACvC,MAAM,QAAQ,WAAA,SAAc,QAAQ,cAAc;EAClD,MAAM,QAAQ,WAAA,SAAc,QAAQ,cAAc;EAClD,MAAM,QAAQ,CACZ,WAAU,YAAA,KAAY,KAACC,YAAAE,QAAAD,WAEf,eACF;GAAE,MAAM;GAAS,IAAI,YAAY;EAAK,GAAC,iBAAA,OAEvC,EACE,IAAI,IAAI,YACV,CACF,GAAC,EAAA,OAAA,UAAA,CAAA,GAAA;GAAA,eAAA,CAAAE,MAAA,WAAA,CAAA;GAAA,SAAA;EAAA,CAAA,GAKL,UAAU,KAACH,YAAAE,QAAAD,WAEH,eACF;GAAE,MAAM;GAAU,IAAI,CAAC,YAAY,MAAM,YAAY,KAAK;EAAE,GAAC,WAAA,OAE7D,EAAE,IAAI,IAAI,MAAM,CAClB,GAAC,EAAA,OAAA,UAAA,CAAA,GAAA;GAAA,eAAA,CAAAD,YAAA,MAAA;IAAA,QAEW,YAAY;IAAM,MAAM,YAAY;GAAS,GAAA,IAAA,GACxD,QAAQ,CAAC;GAAA,SAAA;EAAA,CAAA,CAEb;EAEH,OAAAA,YAAA,OAAAC,WAEQ,WACF,EACE,IAAI,QACA,CACE,YAAY,OAAK,IAAA,KACjB,kBAAkB,eAAO,CAAC,OAAO,UAAU,WAAW,GAAG,MAAK,MAAO,KAAK,CAAA,CAAA,IAE5E,YAAY,KAClB,GACA,MACA,EAAE,WAAW,CACf,GAAC;GAAA,iBACc,QAAA,SAAW,QAAQ,KAAK,SAAS,KAAA;GAAS,iBAC1C,WAAW,KAAK,KAAA;GAAS,wBAClB;EAAS,CAAA,GAAA,CAE9B,WAAU,MAAA,KAAM,KAACD,YAAA,OAAAC,WAEV,eACF;GACE,UAAU,WAAW;GACrB,UAAU,WAAW;GACrB,IAAI,YAAA,QACA,qBAAqB,SACrB,UAAU,UAAU,QAAQ,YAAY,WAAW,YAAY;EACrE,GAAC,WAAA,OAED;GAAE,IAAI,IAAI;GAAO,KAAK,IAAI;EAAQ,CACpC,GAAC,EAAA,OAAA,UAAA,CAAA,GAAA;GAAA,eAAA,CAAAE,MAAA,KAAA,CAAA;GAAA,SAAA;EAAA,CAAA,GAKJ,QAAKH,YAAA,OAEE,WACF,EACE,IAAI,CAAC,YAAY,SAAS,CAAC,WAAU,MAAA,KAAM,KAAK,YAAY,mBAAmB,EACjF,GAAC,aAAA,KAEH,GAAC,CAEA,UAAS,SAAA,KAAS,GAClB,KAAK,CAAA,IAGR,CAAC,UAAS,SAAA,KAAS,GAAG,KAAK,CAC5B,CAAA;CAGP;AACF,GAAC;CAAA,MAAA;CAAA,OAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;CAAA;AAAA,CACH"}
|
|
1
|
+
{"version":3,"file":"Field.js","names":["_vtp_readonlyProp","_vtp_childrenProp","_vtp_restProps","_createVNode","_mergeProps","Text","_vue_unref"],"sources":["../../src/form/Field.tsx"],"sourcesContent":["/**\n * 表单字段:标签 + 控件 + 说明 + 错误信息,并把它们关联起来(参考 Ark UI Field、React Aria 的 Label / Description / FieldError)。\n *\n * <Field label=\"邮箱\" description=\"用于找回密码\" error={errors.email} required>\n * <TextField _value={form.email} type=\"email\" />\n * </Field>\n *\n * Field 经上下文告诉里面的控件:控件的 id(标签的 for 指向它)、标签的 id(不是原生输入的控件用 aria-labelledby)、\n * 说明与错误的 id(aria-describedby),以及 invalid / required / disabled / readonly。\n * 控件上显式写的状态优先于 Field 的。控件用 useFieldControl 读取这些信息,单独使用(不在 Field 里)时照常工作。\n * 在 Form 里写了 name 时,错误信息取自表单控制器(useForm),焦点离开字段时报告失焦(何时显示错误见 useForm);\n * 里面只有一个控件、控件上没写值时自动绑定表单的这个字段(valueProp 决定写 _value 还是 _checked),\n * 标签与控件登记给表单,错误汇总(FormErrorSummary)据此列出字段名、跳到控件。\n *\n * 标签可以放在起始边(`labelPlacement=\"start\"`):标签一列、控件一列,标签与控件的第一行文字按基线对齐,\n * 多行文本框、勾选组也对得齐。通常在 Form、FormSection 上统一设置(见 field-layout),整组的只读、不可用同样从那里来。\n *\n * 部件:Root(div)、Label(Label)、Control(div,标签在起始边时包住控件、说明与错误)、\n * Description(Text)、Error(Text,带警示图标)。\n */\nimport { Asterisk } from '@lucide/icons';\nimport { createContext, _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport {\n cloneVNode,\n Fragment,\n isVNode,\n onBeforeUnmount,\n useId,\n watch,\n type VNode,\n type VNodeChild,\n} from 'vue';\nimport { Icon, type IconProps } from '../base/Icon';\nimport { Text, type TextProps } from '../base/Text';\nimport { visuallyHiddenStyles } from '../base/VisuallyHidden';\nimport { plainText } from '../utils/data/text';\nimport { focusControl } from '../utils/dom/focus';\nimport { hasContent, mergeAttrs, mergePartProps, type PartProps } from '../utils/props';\nimport { statusIcons } from '../utils/style/tone-styles';\nimport {\n FieldLayoutContext,\n type FieldLabelAligns,\n type FieldLabelPlacements,\n} from './field-layout';\nimport { FormContext } from './Form';\nimport type { FieldConstraints } from './form-constraints';\n\nconst NO_CONSTRAINTS: FieldConstraints = Object.freeze({});\n\n/** Field 交给控件的信息 */\nexport interface FieldState {\n /** 控件(主元素)的 id,标签的 for 指向它 */\n readonly controlId: string;\n /** 标签的 id:控件不是原生输入元素(滑块、单选组)时用 aria-labelledby 指向它 */\n readonly labelId: string | undefined;\n /** 说明与错误信息的 id,空格分隔,给 aria-describedby */\n readonly describedBy: string | undefined;\n /** 校验未通过 */\n readonly invalid: boolean;\n /** 必填 */\n readonly required: boolean;\n /** 不可用 */\n readonly disabled: boolean;\n /** 只读 */\n readonly readonly: boolean;\n /** 从表单 schema 推出的约束(在 Form 里写了 name 时);控件据此加 maxlength、min / max 等 */\n readonly constraints: FieldConstraints;\n /**\n * 认领 Field 的 id 与标签:Field 里有几个控件时(一组勾选框),只有第一个控件拿 Field 的 id、用它的标签当名字,\n * 其余控件留着自己的名字(勾选框自己的文字),id 也不重复。认领到了返回 true\n */\n claim?(token: symbol): boolean;\n /** 认领的控件卸载了:下一个控件可以认领 */\n release?(token: symbol): void;\n}\n\n/** Field 的上下文:自定义控件用 useFieldControl 接入,一般不直接读它;null 表示子树不属于任何 Field */\nexport const FieldContext = createContext<FieldState | null>('Field');\n\n/** 控件自己的状态(不写时取 Field 的) */\nexport interface FieldControlOptions {\n /** 控件上显式写的 id */\n id?: string;\n /** 校验未通过 */\n invalid?: boolean;\n /** 必填;在 Form 里写了 name 时默认按表单的 schema(如 z.string().min(1)) */\n required?: boolean;\n /** 不可用 */\n disabled?: boolean;\n /** 只读 */\n readonly?: boolean;\n}\n\n/** useFieldControl 的选项 */\nexport interface FieldControlUse {\n /** 输入交给里面的另一个控件(DatePicker 里的 DateField):子树仍属于 Field,由那个控件接 */\n delegate?: boolean;\n}\n\n/** 控件合并 Field 之后的状态与关联属性 */\nexport interface FieldControl {\n /** 控件的 id:显式写的优先,其次是 Field 给的 */\n id: string | undefined;\n /** 不是原生输入元素的控件用它作 aria-labelledby */\n labelledBy: string | undefined;\n /** aria-describedby */\n describedBy: string | undefined;\n /** 校验未通过 */\n invalid: boolean;\n /** 必填 */\n required: boolean;\n /** 不可用 */\n disabled: boolean;\n /** 只读 */\n readonly: boolean;\n /** 从表单 schema 推出的约束;不在 Field 里、或推不出时为空对象。控件上显式写的属性优先 */\n constraints: FieldConstraints;\n}\n\n/**\n * 所在的表单重置时(form.reset()、type=\"reset\" 的按钮)调用:控件丢掉自己还没写回的输入(日期分段打了一半)。\n * 值本身由表单改回去;只是半截输入时值一直是 null,重置前后没有变化,控件不知道要清。在 setup 里调用\n */\nexport function useFormReset(onReset: () => void): void {\n const form = FormContext.inject(null);\n if (form) watch(() => form.resetCount, onReset);\n}\n\n/**\n * 控件读取所在 Field 的信息(在 setup 里调用,返回的函数在渲染时调用):\n * 控件上显式写的状态优先;不在 Field 里时用控件自己的,不可用与只读再看所在的 Form、FormSection。\n * 控件里再嵌的控件(Select 的搜索框、区间的两个框、时间面板的列表)不是 Field 的控件,\n * 拿到 Field 的 id、说明与必填会重复 id、让表单提交被空的搜索框拦住:调用后子树与 Field 隔开,\n * 除非写了 delegate\n */\nexport function useFieldControl(\n read: () => FieldControlOptions,\n { delegate = false }: FieldControlUse = {},\n): () => FieldControl {\n const field = FieldContext.inject(null);\n if (!delegate) FieldContext.provide(null);\n const layout = field ? null : FieldLayoutContext.inject(null);\n // 把输入交给里面控件的(delegate)不认领:由里面那个控件认领;它自己仍用 Field 的标签\n const token = Symbol('field-control');\n const owns = field == null ? false : delegate || (field.claim?.(token) ?? true);\n if (field && !delegate) onBeforeUnmount(() => field.release?.(token));\n return () => {\n const own = read();\n return {\n id: own.id ?? (owns ? field?.controlId : undefined),\n labelledBy: owns ? field?.labelId : undefined,\n describedBy: field?.describedBy,\n invalid: own.invalid ?? field?.invalid ?? false,\n required: own.required ?? field?.required ?? false,\n disabled: own.disabled ?? field?.disabled ?? layout?.disabled ?? false,\n readonly: own.readonly ?? field?.readonly ?? layout?.readonly ?? false,\n constraints: field?.constraints ?? NO_CONSTRAINTS,\n };\n };\n}\n\n/** 未声明的属性落到 label 上 */\nexport interface LabelProps extends PartProps<'label'> {\n /** 必填:文字后加一个星号图标(对读屏隐藏,必填由控件的 required 告诉读屏) */\n required?: boolean;\n /** 不可用:变淡 */\n disabled?: boolean;\n /** 必填标记的部件属性 */\n propsRequired?: Partial<IconProps>;\n children?: VNodeChild;\n}\n\n/** Field 的样式:root、disabled、required */\nexport const labelStyles = css.create({\n root: {\n display: 'inline-flex',\n alignItems: 'baseline',\n gap: '_xs',\n fontSize: '_sm',\n fontWeight: '_medium',\n lineHeight: '_tight',\n color: '_text',\n },\n disabled: { opacity: '_disabled' },\n required: { alignSelf: 'flex-start', width: '0.7em', height: '0.7em', color: '_dangerText' },\n});\n\n/**\n * for 指向的不是原生能被标签关联的元素(单选组、滑块组、日期分段这类 role=\"group\")时,点标签浏览器什么也不做:\n * 改为聚焦它里面第一个能聚焦的控件(同点原生输入框的标签)\n */\nfunction focusGroupControl(event: MouseEvent) {\n const label = event.currentTarget as HTMLLabelElement;\n if (event.defaultPrevented || label.control || !label.htmlFor) return;\n const target = label.ownerDocument.getElementById(label.htmlFor);\n if (target) focusControl(target);\n}\n\n/** 表单标签(label):for 指向控件;Field 里自动关联,单独使用时自己写 for */\nexport const Label = _component(\n ({ required = false, disabled = false, propsRequired, children, ...rest }: LabelProps) =>\n () => (\n <label\n {...mergeAttrs<'label'>(\n { sx: [labelStyles.root, disabled && labelStyles.disabled] },\n rest,\n { onClick: focusGroupControl },\n )}\n data-disabled={disabled || undefined}\n >\n {children}\n {required && (\n <Icon\n {...mergePartProps<IconProps>(\n { icon: Asterisk, sx: labelStyles.required },\n propsRequired,\n )}\n />\n )}\n </label>\n ),\n);\n\n/** 未声明的属性落到外层 div 上 */\nexport interface FieldProps extends PartProps<'div'> {\n /** 在 Form 里对应的字段路径(如 `email`、`address.city`):错误取自表单控制器,只有一个控件时自动绑定它的值 */\n name?: string;\n /**\n * 自动绑定值时写到控件的哪个属性:默认 _value;控件声明了 _checked 而没有 _value(Switch、单个 Checkbox)时是 _checked\n */\n valueProp?: '_value' | '_checked';\n /** 标签 */\n label?: VNodeChild;\n /** 控件下方的说明 */\n description?: VNodeChild;\n /** 错误信息:给了(且 invalid 不是 false)就显示,并把控件标为出错;在 Form 里写了 name 时默认取表单的错误 */\n error?: VNodeChild;\n /** 校验未通过;不写时有 error 就算出错 */\n invalid?: boolean;\n /** 必填 */\n required?: boolean;\n /** 不可用;不写时取所在 Form、FormSection 的 */\n disabled?: boolean;\n /** 只读;不写时取所在 Form、FormSection 的 */\n readonly?: boolean;\n /** 标签放在哪;不写时取所在 Form、FormSection 的,都没写时在上方 */\n labelPlacement?: keyof FieldLabelPlacements;\n /** 标签在起始边时标签列的宽度:数字是 px,也可以写 CSS 长度;默认 `'8em'` */\n labelWidth?: number | string;\n /** 标签在起始边时文字的对齐,默认 end(贴着控件) */\n labelAlign?: keyof FieldLabelAligns;\n /**\n * 标签只给读屏:视觉上隐藏,仍是控件的名称(表格式的重复行只在第一行显示列名、紧凑的筛选条);\n * 这时不占标签列,控件单独一列\n */\n labelHidden?: boolean;\n /** 标签的部件属性 */\n propsLabel?: Partial<LabelProps>;\n /** 控件列(标签在起始边时包住控件、说明与错误的 div)的部件属性 */\n propsControl?: PartProps<'div'>;\n /** 说明的部件属性 */\n propsDescription?: Partial<TextProps>;\n /** 错误信息的部件属性 */\n propsError?: Partial<TextProps>;\n /** 控件 */\n children?: VNodeChild;\n}\n\n/** 控件的组件声明了这个属性(运行时的 props 定义) */\nfunction declares(node: VNode, key: string): boolean {\n const declared = (node.type as { props?: unknown }).props;\n if (Array.isArray(declared)) return declared.includes(key);\n return typeof declared === 'object' && declared !== null && key in declared;\n}\n\n/** Field 里唯一的控件:一个组件的 VNode(数组里只有一项也算);不是时 undefined */\nfunction soleControl(children: VNodeChild): VNode | undefined {\n const node = Array.isArray(children) && children.length === 1 ? children[0] : children;\n if (!isVNode(node) || typeof node.type === 'string' || node.type === Fragment) return undefined;\n return node;\n}\n\n/** 标签在起始边时默认的标签列宽度:放得下七八个汉字 */\nconst DEFAULT_LABEL_WIDTH = '8em';\n\n/** Field 的样式:root、start、label…、control、hint、error、errorIcon */\nexport const fieldStyles = css.create({\n root: { display: 'flex', flexDirection: 'column', gap: '_xs', minWidth: 0 },\n // 标签一列、控件一列;第一行按基线对齐:标签与输入框里的文字、勾选项的文字在同一条线上\n start: {\n display: 'grid',\n columnGap: '_md',\n rowGap: '_xs',\n alignItems: 'baseline',\n },\n labelStart: { justifySelf: 'start', textAlign: 'start' },\n labelEnd: { justifySelf: 'end', textAlign: 'end' },\n // 没有标签时控件仍放在第二列,与上下的字段对齐\n controlWithoutLabel: { gridColumnStart: '2' },\n control: { display: 'flex', flexDirection: 'column', gap: '_xs', minWidth: 0 },\n hint: { fontSize: '_sm', lineHeight: '_normal' },\n error: { display: 'inline-flex', alignItems: 'flex-start', gap: '_xs' },\n errorIcon: { flexShrink: 0, marginBlock: '0.25em' },\n});\n\n/** Field 的动态样式:标签列的宽度 */\nexport const fieldColumnStyles = css.create({\n columns: (labelWidth: string) => ({ gridTemplateColumns: `${labelWidth} minmax(0, 1fr)` }),\n});\n\n/** 表单字段:标签、控件、说明、错误信息,自动关联 id 与状态 */\nexport const Field = _component(\n ({\n name,\n valueProp,\n label,\n description,\n error,\n invalid,\n required,\n disabled,\n readonly,\n labelPlacement,\n labelWidth,\n labelAlign,\n labelHidden = false,\n propsLabel,\n propsControl,\n propsDescription,\n propsError,\n children,\n ...rest\n }: FieldProps) => {\n const id = useId();\n const ids = {\n control: `${id}-control`,\n label: `${id}-label`,\n description: `${id}-description`,\n error: `${id}-error`,\n };\n const form = FormContext.inject(null);\n const layout = FieldLayoutContext.inject(null);\n // error={cond && '…'} 条件不成立时是 false:当作没写,用表单的错误\n const message = () =>\n hasContent(error) ? error : name != null ? form?.visibleError(name) : undefined;\n const showError = () => hasContent(message()) && invalid !== false;\n const constraints = () => (name != null && form ? form.constraints(name) : NO_CONSTRAINTS);\n const isRequired = () => required ?? constraints().required ?? false;\n const isDisabled = () => disabled ?? layout?.disabled ?? false;\n const isReadonly = () => readonly ?? layout?.readonly ?? false;\n\n // 第一个接入的控件认领 id 与标签(见 FieldState.claim)\n let owner: symbol | null = null;\n FieldContext.provide({\n claim: (token) => {\n owner ??= token;\n return owner === token;\n },\n release: (token) => {\n if (owner === token) owner = null;\n },\n controlId: ids.control,\n get labelId() {\n return hasContent(label) ? ids.label : undefined;\n },\n get describedBy() {\n const parts = [hasContent(description) && ids.description, showError() && ids.error];\n return parts.filter(Boolean).join(' ') || undefined;\n },\n get invalid() {\n return invalid ?? hasContent(message());\n },\n get required() {\n return isRequired();\n },\n get constraints() {\n return constraints();\n },\n get disabled() {\n return isDisabled();\n },\n get readonly() {\n return isReadonly();\n },\n });\n\n // 登记给表单:错误汇总据此显示字段名、跳到控件\n _effect(() =>\n name != null && form\n ? form.register(name, { label: plainText(label) ?? name, controlId: ids.control })\n : undefined,\n );\n\n /** 只有一个控件、控件上没写值时,自动绑定表单的这个字段(同 Ant Design Form.Item 的 name) */\n function bindValue(content: VNodeChild): VNodeChild {\n const control = name != null && form ? soleControl(content) : undefined;\n if (!control || !form) return content;\n const key =\n valueProp ??\n (declares(control, '_checked') && !declares(control, '_value') ? '_checked' : '_value');\n if (control.props && key in control.props) return content;\n const path = name!;\n return cloneVNode(control, {\n [key]: form.getValue(path),\n [`onUpdate:${key}`]: (value: unknown) => form.setValue(path, value),\n });\n }\n\n // 焦点离开整个字段(不是在字段内部移动)时报告失焦\n function onFocusout(event: FocusEvent) {\n const next = event.relatedTarget;\n const field = event.currentTarget as HTMLElement;\n if (name != null && !(next instanceof Node && field.contains(next))) form?.blur(name);\n }\n\n return () => {\n const placement = labelPlacement ?? layout?.labelPlacement ?? 'top';\n const start = placement === 'start' && !labelHidden;\n const width = labelWidth ?? layout?.labelWidth ?? DEFAULT_LABEL_WIDTH;\n const align = labelAlign ?? layout?.labelAlign ?? 'end';\n const hints = [\n hasContent(description) && (\n <Text\n {...mergePartProps<TextProps>(\n { tone: 'muted', sx: fieldStyles.hint },\n propsDescription,\n {\n id: ids.description,\n },\n )}\n >\n {description}\n </Text>\n ),\n showError() && (\n <Text\n {...mergePartProps<TextProps>(\n { tone: 'danger', sx: [fieldStyles.hint, fieldStyles.error] },\n propsError,\n { id: ids.error },\n )}\n >\n <Icon icon={statusIcons.danger} sx={fieldStyles.errorIcon} />\n {message()}\n </Text>\n ),\n ];\n return (\n <div\n {...mergeAttrs<'div'>(\n {\n sx: start\n ? [\n fieldStyles.start,\n fieldColumnStyles.columns(typeof width === 'number' ? `${width}px` : width),\n ]\n : fieldStyles.root,\n },\n rest,\n { onFocusout },\n )}\n data-invalid={(invalid ?? message() != null) || undefined}\n data-disabled={isDisabled() || undefined}\n data-label-placement={placement}\n >\n {hasContent(label) && (\n <Label\n {...mergePartProps<LabelProps>(\n {\n required: isRequired(),\n disabled: isDisabled(),\n sx: labelHidden\n ? visuallyHiddenStyles.hidden\n : start && (align === 'end' ? fieldStyles.labelEnd : fieldStyles.labelStart),\n },\n propsLabel,\n { id: ids.label, for: ids.control },\n )}\n >\n {label}\n </Label>\n )}\n {start ? (\n <div\n {...mergeAttrs<'div'>(\n {\n sx: [fieldStyles.control, !hasContent(label) && fieldStyles.controlWithoutLabel],\n },\n propsControl,\n )}\n >\n {bindValue(children)}\n {hints}\n </div>\n ) : (\n [bindValue(children), hints]\n )}\n </div>\n );\n };\n },\n);\n"],"mappings":"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"}
|
package/dist/form/Form.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { mergeAttrs } from "../utils/props.js";
|
|
2
2
|
import { focusControl } from "../utils/dom/focus.js";
|
|
3
3
|
import { provideFieldLayout } from "./field-layout.js";
|
|
4
|
-
import { createVNode,
|
|
5
|
-
import { _component, childrenProp, createContext, readonlyProp, restProps, untrack } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
4
|
+
import { createVNode, nextTick, shallowRef } from "vue";
|
|
5
|
+
import { _component, childrenProp, createContext, mergeJsxProps, readonlyProp, restProps, untrack } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
6
6
|
//#region src/form/Form.tsx
|
|
7
7
|
/** 表单控制器(Field 按 name 读取错误、报告失焦) */
|
|
8
8
|
var FormContext = createContext("Form");
|
|
@@ -39,7 +39,7 @@ var Form = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
39
39
|
event.preventDefault();
|
|
40
40
|
controller.value.reset();
|
|
41
41
|
}
|
|
42
|
-
return () => createVNode("form",
|
|
42
|
+
return () => createVNode("form", mergeJsxProps(mergeAttrs({}, rest, {
|
|
43
43
|
ref: form,
|
|
44
44
|
novalidate: true,
|
|
45
45
|
onSubmit: (event) => void onSubmit(event),
|
package/dist/form/Form.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Form.js","names":["_vtp_readonlyProp","_vtp_childrenProp","_vtp_restProps","_vue_shallowRef","_vtp_untrack","_createVNode","_mergeProps"],"sources":["../../src/form/Form.tsx"],"sourcesContent":["/**\n * 表单(form):把 useForm 的控制器交给里面的 Field。提交时先校验,没通过就把焦点移到错误汇总(有 FormErrorSummary 时)\n * 或第一个出错的控件上;\n * 原生的 reset(type=\"reset\" 的按钮)回到初始值。\n *\n * <Form controller={form}>\n * <form.Field name=\"email\" label=\"邮箱\"><TextField _value={form.values.email} /></form.Field>\n * <Button type=\"submit\" loading={form.submitting}>保存</Button>\n * </Form>\n *\n * 加了 novalidate:校验与错误信息由 useForm 与 Field 负责,不弹浏览器自带的气泡;控件上的 required 仍告诉读屏“必填”。\n *\n * 整张表单的标签布局(labelPlacement、labelWidth、labelAlign)与只读、不可用在这里统一设置,见 field-layout。\n */\nimport { createContext, _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport { nextTick, type VNodeChild } from 'vue';\nimport { focusControl } from '../utils/dom/focus';\nimport { mergeAttrs, type PartProps } from '../utils/props';\nimport { provideFieldLayout, type FieldLayout } from './field-layout';\nimport type { FormControllerBase } from './useForm';\n\n/** 表单控制器(Field 按 name 读取错误、报告失焦) */\nexport const FormContext = createContext<FormControllerBase>('Form');\n\n/** 提交与重置由组件管理;未声明的属性落到 form 上 */\nexport interface FormProps extends PartProps<'form', 'novalidate'>, FieldLayout {\n /** useForm() 创建的控制器 */\n controller: FormControllerBase;\n children?: VNodeChild;\n}\n\n/** 表单:提交时校验并聚焦第一个出错的控件,重置回到初始值 */\nexport const Form = _component(\n ({\n controller,\n labelPlacement,\n labelWidth,\n labelAlign,\n disabled = false,\n readonly = false,\n children,\n ...rest\n }: FormProps) => {\n let form = _state.raw<HTMLFormElement | null>(null);\n // 控制器在表单存在期间不换:只取当前值提供下去\n FormContext.provide(_untrack(controller));\n provideFieldLayout(() => ({ labelPlacement, labelWidth, labelAlign, disabled, readonly }));\n\n async function onSubmit(event: Event) {\n event.preventDefault();\n if (await controller.submit()) return;\n // 等错误渲染出来(aria-invalid 写到控件上)再移焦点\n await nextTick();\n // 有错误汇总时先到它那里(读屏念出有几项、各是什么),从它的链接跳到字段\n const summary = form?.querySelector<HTMLElement>('[data-form-error-summary]');\n if (summary) return summary.focus();\n const invalid = form?.querySelector<HTMLElement>('[aria-invalid=\"true\"]');\n // 出错的可能是一组控件(RadioGroup 的 radiogroup 本身不能聚焦):聚焦它里面第一个能聚焦的\n if (invalid) focusControl(invalid);\n }\n\n function onReset(event: Event) {\n event.preventDefault();\n controller.reset();\n }\n\n return () => (\n <form\n {...mergeAttrs<'form'>({}, rest, {\n ref: _toRef(form),\n novalidate: true,\n onSubmit: (event: Event) => void onSubmit(event),\n onReset,\n })}\n data-submitting={controller.submitting || undefined}\n data-disabled={disabled || undefined}\n data-readonly={readonly || undefined}\n >\n {children}\n </form>\n );\n },\n);\n"],"mappings":";;;;;;;AAsBA,IAAa,cAAc,cAAkC,MAAM;;;AAUnE,IAAa,OAAO,0BAClB,QAAA,SASiB;CAAA,MAAA,aAAAA,aAAA,QAAA,YAAA;CAAA,MAAA,iBAAAA,aAAA,QAAA,gBAAA;CAAA,MAAA,aAAAA,aAAA,QAAA,YAAA;CAAA,MAAA,aAAAA,aAAA,QAAA,YAAA;CAAA,MAAA,WAAAA,aAAA,QAAA,YAJJ,KAAK;CAAA,MAAA,WAAAA,aAAA,QAAA,YACL,KAAK;CAAA,MAAA,WAAAC,aAAA,MAAA;CAAA,MAEb,OAAIC,UAAA,QAAA,MAAA,KAAA;CAEP,IAAI,OAAIC,WAAsC,IAAI;CAElD,YAAY,QAAOC,cAAA,WAAA,KAAA,CAAqB;CACxC,0BAA0B;EAAE,gBAAc,eAAA;EAAE,YAAU,WAAA;EAAE,YAAU,WAAA;EAAE,UAAQ,SAAA;EAAE,UAAQ,SAAA;CAAC,EAAE;CAEzF,eAAe,SAAS,OAAc;EACpC,MAAM,eAAe;EACrB,IAAI,MAAM,WAAA,MAAW,OAAO,GAAG;EAE/B,MAAM,SAAS;EAEf,MAAM,UAAU,KAAA,OAAM,cAA2B,2BAA2B;EAC5E,IAAI,SAAS,OAAO,QAAQ,MAAM;EAClC,MAAM,UAAU,KAAA,OAAM,cAA2B,yBAAuB;EAExE,IAAI,SAAS,aAAa,OAAO;CACnC;CAEA,SAAS,QAAQ,OAAc;EAC7B,MAAM,eAAe;EACrB,WAAA,MAAW,MAAM;CACnB;CAEA,aAAOC,YAAA,QAAAC,
|
|
1
|
+
{"version":3,"file":"Form.js","names":["_vtp_readonlyProp","_vtp_childrenProp","_vtp_restProps","_vue_shallowRef","_vtp_untrack","_createVNode","_mergeProps"],"sources":["../../src/form/Form.tsx"],"sourcesContent":["/**\n * 表单(form):把 useForm 的控制器交给里面的 Field。提交时先校验,没通过就把焦点移到错误汇总(有 FormErrorSummary 时)\n * 或第一个出错的控件上;\n * 原生的 reset(type=\"reset\" 的按钮)回到初始值。\n *\n * <Form controller={form}>\n * <form.Field name=\"email\" label=\"邮箱\"><TextField _value={form.values.email} /></form.Field>\n * <Button type=\"submit\" loading={form.submitting}>保存</Button>\n * </Form>\n *\n * 加了 novalidate:校验与错误信息由 useForm 与 Field 负责,不弹浏览器自带的气泡;控件上的 required 仍告诉读屏“必填”。\n *\n * 整张表单的标签布局(labelPlacement、labelWidth、labelAlign)与只读、不可用在这里统一设置,见 field-layout。\n */\nimport { createContext, _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport { nextTick, type VNodeChild } from 'vue';\nimport { focusControl } from '../utils/dom/focus';\nimport { mergeAttrs, type PartProps } from '../utils/props';\nimport { provideFieldLayout, type FieldLayout } from './field-layout';\nimport type { FormControllerBase } from './useForm';\n\n/** 表单控制器(Field 按 name 读取错误、报告失焦) */\nexport const FormContext = createContext<FormControllerBase>('Form');\n\n/** 提交与重置由组件管理;未声明的属性落到 form 上 */\nexport interface FormProps extends PartProps<'form', 'novalidate'>, FieldLayout {\n /** useForm() 创建的控制器 */\n controller: FormControllerBase;\n children?: VNodeChild;\n}\n\n/** 表单:提交时校验并聚焦第一个出错的控件,重置回到初始值 */\nexport const Form = _component(\n ({\n controller,\n labelPlacement,\n labelWidth,\n labelAlign,\n disabled = false,\n readonly = false,\n children,\n ...rest\n }: FormProps) => {\n let form = _state.raw<HTMLFormElement | null>(null);\n // 控制器在表单存在期间不换:只取当前值提供下去\n FormContext.provide(_untrack(controller));\n provideFieldLayout(() => ({ labelPlacement, labelWidth, labelAlign, disabled, readonly }));\n\n async function onSubmit(event: Event) {\n event.preventDefault();\n if (await controller.submit()) return;\n // 等错误渲染出来(aria-invalid 写到控件上)再移焦点\n await nextTick();\n // 有错误汇总时先到它那里(读屏念出有几项、各是什么),从它的链接跳到字段\n const summary = form?.querySelector<HTMLElement>('[data-form-error-summary]');\n if (summary) return summary.focus();\n const invalid = form?.querySelector<HTMLElement>('[aria-invalid=\"true\"]');\n // 出错的可能是一组控件(RadioGroup 的 radiogroup 本身不能聚焦):聚焦它里面第一个能聚焦的\n if (invalid) focusControl(invalid);\n }\n\n function onReset(event: Event) {\n event.preventDefault();\n controller.reset();\n }\n\n return () => (\n <form\n {...mergeAttrs<'form'>({}, rest, {\n ref: _toRef(form),\n novalidate: true,\n onSubmit: (event: Event) => void onSubmit(event),\n onReset,\n })}\n data-submitting={controller.submitting || undefined}\n data-disabled={disabled || undefined}\n data-readonly={readonly || undefined}\n >\n {children}\n </form>\n );\n },\n);\n"],"mappings":";;;;;;;AAsBA,IAAa,cAAc,cAAkC,MAAM;;;AAUnE,IAAa,OAAO,0BAClB,QAAA,SASiB;CAAA,MAAA,aAAAA,aAAA,QAAA,YAAA;CAAA,MAAA,iBAAAA,aAAA,QAAA,gBAAA;CAAA,MAAA,aAAAA,aAAA,QAAA,YAAA;CAAA,MAAA,aAAAA,aAAA,QAAA,YAAA;CAAA,MAAA,WAAAA,aAAA,QAAA,YAJJ,KAAK;CAAA,MAAA,WAAAA,aAAA,QAAA,YACL,KAAK;CAAA,MAAA,WAAAC,aAAA,MAAA;CAAA,MAEb,OAAIC,UAAA,QAAA,MAAA,KAAA;CAEP,IAAI,OAAIC,WAAsC,IAAI;CAElD,YAAY,QAAOC,cAAA,WAAA,KAAA,CAAqB;CACxC,0BAA0B;EAAE,gBAAc,eAAA;EAAE,YAAU,WAAA;EAAE,YAAU,WAAA;EAAE,UAAQ,SAAA;EAAE,UAAQ,SAAA;CAAC,EAAE;CAEzF,eAAe,SAAS,OAAc;EACpC,MAAM,eAAe;EACrB,IAAI,MAAM,WAAA,MAAW,OAAO,GAAG;EAE/B,MAAM,SAAS;EAEf,MAAM,UAAU,KAAA,OAAM,cAA2B,2BAA2B;EAC5E,IAAI,SAAS,OAAO,QAAQ,MAAM;EAClC,MAAM,UAAU,KAAA,OAAM,cAA2B,yBAAuB;EAExE,IAAI,SAAS,aAAa,OAAO;CACnC;CAEA,SAAS,QAAQ,OAAc;EAC7B,MAAM,eAAe;EACrB,WAAA,MAAW,MAAM;CACnB;CAEA,aAAOC,YAAA,QAAAC,cAEC,WAAmB,CAAC,GAAG,MAAM;EAC/B,KAAG;EACH,YAAY;EACZ,WAAW,UAAiB,KAAK,SAAS,KAAK;EAC/C;CACF,CAAC,GAAC;EAAA,mBACe,WAAA,MAAW,cAAc,KAAA;EAAS,iBACpC,SAAA,SAAY,KAAA;EAAS,iBACrB,SAAA,SAAY,KAAA;CAAS,CAAA,GAAA,CAAA,SAAA,KAAA,CAAA;AAK1C,GAAC;CAAA,MAAA;CAAA,OAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;CAAA;AAAA,CACH"}
|
|
@@ -5,8 +5,8 @@ import { Icon } from "../base/Icon.js";
|
|
|
5
5
|
import { focusControl } from "../utils/dom/focus.js";
|
|
6
6
|
import { statusIcons } from "../utils/style/tone-styles.js";
|
|
7
7
|
import { FormContext } from "./Form.js";
|
|
8
|
-
import { createVNode
|
|
9
|
-
import { _component, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
8
|
+
import { createVNode } from "vue";
|
|
9
|
+
import { _component, mergeJsxProps, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
10
10
|
import * as css from "@zerodep-dev/vue-tsx-pro-tsx/styles";
|
|
11
11
|
//#region src/form/FormErrorSummary.tsx
|
|
12
12
|
/** 未声明的属性落到外层 div 上 */
|
|
@@ -82,7 +82,7 @@ var FormErrorSummary = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
82
82
|
return () => {
|
|
83
83
|
const list = items();
|
|
84
84
|
if (list.length === 0) return null;
|
|
85
|
-
return createVNode("div",
|
|
85
|
+
return createVNode("div", mergeJsxProps(mergeAttrs({ sx: [formErrorSummaryStyles.root, focusRingStyles.visible] }, rest, { tabindex: -1 }), { "data-form-error-summary": "" }), [createVNode("div", mergeAttrs({ sx: formErrorSummaryStyles.title }, propsTitle.value), [createVNode(Icon, { "icon": statusIcons.danger }, null), title.value ? title.value(list.length) : lang.formErrorSummary(list.length)]), createVNode("ul", mergeAttrs({ sx: formErrorSummaryStyles.list }, propsList.value), [list.map((item) => createVNode("li", { "key": item.path }, [item.controlId ? createVNode("a", mergeAttrs({ sx: formErrorSummaryStyles.link }, propsLink.value, {
|
|
86
86
|
href: `#${item.controlId}`,
|
|
87
87
|
onClick: (event) => jump(event, item.controlId)
|
|
88
88
|
}), [item.label ? lang.formErrorItem(item.label, item.message) : item.message]) : item.message]))])]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FormErrorSummary.js","names":["_vtp_readonlyProp","_vtp_restProps","_createVNode","_mergeProps"],"sources":["../../src/form/FormErrorSummary.tsx"],"sourcesContent":["/**\n * 错误汇总:提交没通过时在表单顶上列出各项错误(“有 3 项需要修改”),每项是链接,点了跳到那个字段的控件。\n * 放在 Form 里(通常是第一个);提交失败时 Form 把焦点移到它上面,读屏念出有几项、各是什么(GOV.UK 的 Error summary)。\n *\n * <Form controller={form}>\n * <FormErrorSummary />\n * <form.Field name=\"email\" label=\"邮箱\"><TextField /></form.Field>\n * </Form>\n *\n * 只列显示着的错误(失焦过、提交过的字段),按字段在表单里出现的顺序;改对一项它就从列表里消失,全改对了汇总收起。\n * 整份表单的错误(不属于哪个字段)排在最前,不带链接。字段名取自 Field 的标签。\n * 部件:Root(div,role=\"alert\" 之外可以聚焦)、Title(div)、List(ul)、Link(a)。\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport type { VNodeChild } from 'vue';\nimport { Icon } from '../base/Icon';\nimport { useLang } from '../config/context';\nimport { focusControl } from '../utils/dom/focus';\nimport { mergeAttrs, type PartProps } from '../utils/props';\nimport { focusRingStyles } from '../utils/style/styles';\nimport { statusIcons } from '../utils/style/tone-styles';\nimport { FormContext } from './Form';\n\n/** 未声明的属性落到外层 div 上 */\nexport interface FormErrorSummaryProps extends PartProps<'div', 'title'> {\n /** 标题;不写时取语言包(有 N 项需要修改) */\n title?: (count: number) => VNodeChild;\n /** 标题的部件属性 */\n propsTitle?: PartProps<'div'>;\n /** 列表的部件属性 */\n propsList?: PartProps<'ul'>;\n /** 每一项链接的部件属性 */\n propsLink?: PartProps<'a'>;\n}\n\n/** FormErrorSummary 的样式:root、title、list、link */\nexport const formErrorSummaryStyles = css.create({\n root: {\n display: 'flex',\n flexDirection: 'column',\n gap: '_sm',\n padding: '_md',\n borderWidth: '_xs',\n borderStyle: 'solid',\n borderColor: '_dangerBorder',\n borderRadius: '_md',\n backgroundColor: '_dangerSoft',\n color: '_text',\n },\n title: {\n display: 'flex',\n alignItems: 'center',\n gap: '_xs',\n fontWeight: '_semibold',\n color: '_dangerText',\n },\n list: { margin: 0, paddingInlineStart: '1.5em', display: 'grid', gap: '_xs' },\n link: {\n color: '_dangerText',\n textDecoration: { default: 'underline', ':hover': 'none' },\n textUnderlineOffset: '0.2em',\n },\n});\n\n/** 错误汇总:提交没通过时列出各项错误,点了跳到字段 */\nexport const FormErrorSummary = _component(\n ({ title, propsTitle, propsList, propsLink, ...rest }: FormErrorSummaryProps) => {\n const lang = useLang();\n const form = FormContext.inject(null);\n\n /** 显示着的错误:整份表单的在前,其余按字段在表单里出现的顺序 */\n const items = () => {\n if (!form || form.submitCount === 0) return [];\n const fields = [...form.fields].flatMap(([path, field]) => {\n const message = form.visibleError(path);\n return message ? [{ path, label: field.label, controlId: field.controlId, message }] : [];\n });\n return form.formError\n ? [{ path: '', label: '', controlId: '', message: form.formError }, ...fields]\n : fields;\n };\n\n function jump(event: MouseEvent, controlId: string) {\n event.preventDefault();\n const control = document.getElementById(controlId);\n if (control) focusControl(control);\n }\n\n return () => {\n const list = items();\n if (list.length === 0) return null;\n return (\n <div\n {...mergeAttrs<'div'>(\n { sx: [formErrorSummaryStyles.root, focusRingStyles.visible] },\n rest,\n {\n // 提交失败时 Form 把焦点移到这里\n tabindex: -1,\n },\n )}\n data-form-error-summary=\"\"\n >\n <div {...mergeAttrs<'div'>({ sx: formErrorSummaryStyles.title }, propsTitle)}>\n <Icon icon={statusIcons.danger} />\n {title ? title(list.length) : lang.formErrorSummary(list.length)}\n </div>\n <ul {...mergeAttrs<'ul'>({ sx: formErrorSummaryStyles.list }, propsList)}>\n {list.map((item) => (\n <li key={item.path}>\n {item.controlId ? (\n <a\n {...mergeAttrs<'a'>({ sx: formErrorSummaryStyles.link }, propsLink, {\n href: `#${item.controlId}`,\n onClick: (event: MouseEvent) => jump(event, item.controlId),\n })}\n >\n {item.label ? lang.formErrorItem(item.label, item.message) : item.message}\n </a>\n ) : (\n item.message\n )}\n </li>\n ))}\n </ul>\n </div>\n );\n };\n },\n);\n"],"mappings":";;;;;;;;;;;;;AAqCA,IAAa,yBAAyB,IAAI,OAAO;CAC/C,MAAM;EACJ,SAAS;EACT,eAAe;EACf,KAAK;EACL,SAAS;EACT,aAAa;EACb,aAAa;EACb,aAAa;EACb,cAAc;EACd,iBAAiB;EACjB,OAAO;CACT;CACA,OAAO;EACL,SAAS;EACT,YAAY;EACZ,KAAK;EACL,YAAY;EACZ,OAAO;CACT;CACA,MAAM;EAAE,QAAQ;EAAG,oBAAoB;EAAS,SAAS;EAAQ,KAAK;CAAM;CAC5E,MAAM;EACJ,OAAO;EACP,gBAAgB;GAAE,SAAS;GAAa,UAAU;EAAO;EACzD,qBAAqB;CACvB;AACF,CAAC;;AAGD,IAAa,mBAAmB,0BAC9B,QAAA,SAAiF;CAAA,MAAA,QAAAA,aAAA,QAAA,OAAA;CAAA,MAAA,aAAAA,aAAA,QAAA,YAAA;CAAA,MAAA,YAAAA,aAAA,QAAA,WAAA;CAAA,MAAA,YAAAA,aAAA,QAAA,WAAA;CAAA,MAAlC,OAAIC,UAAA,QAAA,MAAA,IAAA;CACjD,MAAM,OAAO,QAAQ;CACrB,MAAM,OAAO,YAAY,OAAO,IAAI;;CAGpC,MAAM,cAAc;EAClB,IAAI,CAAC,QAAQ,KAAK,gBAAgB,GAAG,OAAO,CAAA;EAC5C,MAAM,SAAS,CAAC,GAAG,KAAK,MAAM,CAAC,CAAC,SAAS,CAAC,MAAM,WAAW;GACzD,MAAM,UAAU,KAAK,aAAa,IAAI;GACtC,OAAO,UAAU,CAAC;IAAE;IAAM,OAAO,MAAM;IAAO,WAAW,MAAM;IAAW;GAAQ,CAAC,IAAI,CAAA;EACzF,CAAC;EACD,OAAO,KAAK,YACR,CAAC;GAAE,MAAM;GAAI,OAAO;GAAI,WAAW;GAAI,SAAS,KAAK;EAAU,GAAG,GAAG,MAAM,IAC3E;CACN;CAEA,SAAS,KAAK,OAAmB,WAAmB;EAClD,MAAM,eAAe;EACrB,MAAM,UAAU,SAAS,eAAe,SAAS;EACjD,IAAI,SAAS,aAAa,OAAO;CACnC;CAEA,aAAa;EACX,MAAM,OAAO,MAAM;EACnB,IAAI,KAAK,WAAW,GAAG,OAAO;EAC9B,OAAAC,YAAA,OAAAC,
|
|
1
|
+
{"version":3,"file":"FormErrorSummary.js","names":["_vtp_readonlyProp","_vtp_restProps","_createVNode","_mergeProps"],"sources":["../../src/form/FormErrorSummary.tsx"],"sourcesContent":["/**\n * 错误汇总:提交没通过时在表单顶上列出各项错误(“有 3 项需要修改”),每项是链接,点了跳到那个字段的控件。\n * 放在 Form 里(通常是第一个);提交失败时 Form 把焦点移到它上面,读屏念出有几项、各是什么(GOV.UK 的 Error summary)。\n *\n * <Form controller={form}>\n * <FormErrorSummary />\n * <form.Field name=\"email\" label=\"邮箱\"><TextField /></form.Field>\n * </Form>\n *\n * 只列显示着的错误(失焦过、提交过的字段),按字段在表单里出现的顺序;改对一项它就从列表里消失,全改对了汇总收起。\n * 整份表单的错误(不属于哪个字段)排在最前,不带链接。字段名取自 Field 的标签。\n * 部件:Root(div,role=\"alert\" 之外可以聚焦)、Title(div)、List(ul)、Link(a)。\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport type { VNodeChild } from 'vue';\nimport { Icon } from '../base/Icon';\nimport { useLang } from '../config/context';\nimport { focusControl } from '../utils/dom/focus';\nimport { mergeAttrs, type PartProps } from '../utils/props';\nimport { focusRingStyles } from '../utils/style/styles';\nimport { statusIcons } from '../utils/style/tone-styles';\nimport { FormContext } from './Form';\n\n/** 未声明的属性落到外层 div 上 */\nexport interface FormErrorSummaryProps extends PartProps<'div', 'title'> {\n /** 标题;不写时取语言包(有 N 项需要修改) */\n title?: (count: number) => VNodeChild;\n /** 标题的部件属性 */\n propsTitle?: PartProps<'div'>;\n /** 列表的部件属性 */\n propsList?: PartProps<'ul'>;\n /** 每一项链接的部件属性 */\n propsLink?: PartProps<'a'>;\n}\n\n/** FormErrorSummary 的样式:root、title、list、link */\nexport const formErrorSummaryStyles = css.create({\n root: {\n display: 'flex',\n flexDirection: 'column',\n gap: '_sm',\n padding: '_md',\n borderWidth: '_xs',\n borderStyle: 'solid',\n borderColor: '_dangerBorder',\n borderRadius: '_md',\n backgroundColor: '_dangerSoft',\n color: '_text',\n },\n title: {\n display: 'flex',\n alignItems: 'center',\n gap: '_xs',\n fontWeight: '_semibold',\n color: '_dangerText',\n },\n list: { margin: 0, paddingInlineStart: '1.5em', display: 'grid', gap: '_xs' },\n link: {\n color: '_dangerText',\n textDecoration: { default: 'underline', ':hover': 'none' },\n textUnderlineOffset: '0.2em',\n },\n});\n\n/** 错误汇总:提交没通过时列出各项错误,点了跳到字段 */\nexport const FormErrorSummary = _component(\n ({ title, propsTitle, propsList, propsLink, ...rest }: FormErrorSummaryProps) => {\n const lang = useLang();\n const form = FormContext.inject(null);\n\n /** 显示着的错误:整份表单的在前,其余按字段在表单里出现的顺序 */\n const items = () => {\n if (!form || form.submitCount === 0) return [];\n const fields = [...form.fields].flatMap(([path, field]) => {\n const message = form.visibleError(path);\n return message ? [{ path, label: field.label, controlId: field.controlId, message }] : [];\n });\n return form.formError\n ? [{ path: '', label: '', controlId: '', message: form.formError }, ...fields]\n : fields;\n };\n\n function jump(event: MouseEvent, controlId: string) {\n event.preventDefault();\n const control = document.getElementById(controlId);\n if (control) focusControl(control);\n }\n\n return () => {\n const list = items();\n if (list.length === 0) return null;\n return (\n <div\n {...mergeAttrs<'div'>(\n { sx: [formErrorSummaryStyles.root, focusRingStyles.visible] },\n rest,\n {\n // 提交失败时 Form 把焦点移到这里\n tabindex: -1,\n },\n )}\n data-form-error-summary=\"\"\n >\n <div {...mergeAttrs<'div'>({ sx: formErrorSummaryStyles.title }, propsTitle)}>\n <Icon icon={statusIcons.danger} />\n {title ? title(list.length) : lang.formErrorSummary(list.length)}\n </div>\n <ul {...mergeAttrs<'ul'>({ sx: formErrorSummaryStyles.list }, propsList)}>\n {list.map((item) => (\n <li key={item.path}>\n {item.controlId ? (\n <a\n {...mergeAttrs<'a'>({ sx: formErrorSummaryStyles.link }, propsLink, {\n href: `#${item.controlId}`,\n onClick: (event: MouseEvent) => jump(event, item.controlId),\n })}\n >\n {item.label ? lang.formErrorItem(item.label, item.message) : item.message}\n </a>\n ) : (\n item.message\n )}\n </li>\n ))}\n </ul>\n </div>\n );\n };\n },\n);\n"],"mappings":";;;;;;;;;;;;;AAqCA,IAAa,yBAAyB,IAAI,OAAO;CAC/C,MAAM;EACJ,SAAS;EACT,eAAe;EACf,KAAK;EACL,SAAS;EACT,aAAa;EACb,aAAa;EACb,aAAa;EACb,cAAc;EACd,iBAAiB;EACjB,OAAO;CACT;CACA,OAAO;EACL,SAAS;EACT,YAAY;EACZ,KAAK;EACL,YAAY;EACZ,OAAO;CACT;CACA,MAAM;EAAE,QAAQ;EAAG,oBAAoB;EAAS,SAAS;EAAQ,KAAK;CAAM;CAC5E,MAAM;EACJ,OAAO;EACP,gBAAgB;GAAE,SAAS;GAAa,UAAU;EAAO;EACzD,qBAAqB;CACvB;AACF,CAAC;;AAGD,IAAa,mBAAmB,0BAC9B,QAAA,SAAiF;CAAA,MAAA,QAAAA,aAAA,QAAA,OAAA;CAAA,MAAA,aAAAA,aAAA,QAAA,YAAA;CAAA,MAAA,YAAAA,aAAA,QAAA,WAAA;CAAA,MAAA,YAAAA,aAAA,QAAA,WAAA;CAAA,MAAlC,OAAIC,UAAA,QAAA,MAAA,IAAA;CACjD,MAAM,OAAO,QAAQ;CACrB,MAAM,OAAO,YAAY,OAAO,IAAI;;CAGpC,MAAM,cAAc;EAClB,IAAI,CAAC,QAAQ,KAAK,gBAAgB,GAAG,OAAO,CAAA;EAC5C,MAAM,SAAS,CAAC,GAAG,KAAK,MAAM,CAAC,CAAC,SAAS,CAAC,MAAM,WAAW;GACzD,MAAM,UAAU,KAAK,aAAa,IAAI;GACtC,OAAO,UAAU,CAAC;IAAE;IAAM,OAAO,MAAM;IAAO,WAAW,MAAM;IAAW;GAAQ,CAAC,IAAI,CAAA;EACzF,CAAC;EACD,OAAO,KAAK,YACR,CAAC;GAAE,MAAM;GAAI,OAAO;GAAI,WAAW;GAAI,SAAS,KAAK;EAAU,GAAG,GAAG,MAAM,IAC3E;CACN;CAEA,SAAS,KAAK,OAAmB,WAAmB;EAClD,MAAM,eAAe;EACrB,MAAM,UAAU,SAAS,eAAe,SAAS;EACjD,IAAI,SAAS,aAAa,OAAO;CACnC;CAEA,aAAa;EACX,MAAM,OAAO,MAAM;EACnB,IAAI,KAAK,WAAW,GAAG,OAAO;EAC9B,OAAAC,YAAA,OAAAC,cAEQ,WACF,EAAE,IAAI,CAAC,uBAAuB,MAAM,gBAAgB,OAAO,EAAE,GAC7D,MACA,EAEE,UAAU,GACZ,CACF,GAAC,EAAA,2BAAA,GAAA,CAAA,GAAA,CAAAD,YAAA,OAGQ,WAAkB,EAAE,IAAI,uBAAuB,MAAM,GAAC,WAAA,KAAY,GAAC,CAAAA,YAAA,MAAA,EAAA,QAC9D,YAAY,OAAM,GAAA,IAAA,GAC7B,MAAA,QAAQ,MAAA,MAAM,KAAK,MAAM,IAAI,KAAK,iBAAiB,KAAK,MAAM,CAAC,CAAA,GAAAA,YAAA,MAE1D,WAAiB,EAAE,IAAI,uBAAuB,KAAK,GAAC,UAAA,KAAW,GAAC,CACrE,KAAK,KAAK,SAAIA,YAAA,MAAA,EAAA,OACJ,KAAK,KAAI,GAAA,CACf,KAAK,YAASA,YAAA,KAEP,WAAgB,EAAE,IAAI,uBAAuB,KAAK,GAAC,UAAA,OAAa;GAClE,MAAM,IAAI,KAAK;GACf,UAAU,UAAsB,KAAK,OAAO,KAAK,SAAS;EAC5D,CAAC,GAAC,CAED,KAAK,QAAQ,KAAK,cAAc,KAAK,OAAO,KAAK,OAAO,IAAI,KAAK,OAAO,CAAA,IAG3E,KAAK,OACN,CAAA,CAEJ,CAAC,CAAA,CAAA,CAAA;CAIV;AACF,GAAC;CAAA,MAAA;CAAA,OAAA;EAAA;EAAA;EAAA;EAAA;CAAA;AAAA,CACH"}
|
package/dist/form/FormSection.js
CHANGED
|
@@ -8,8 +8,8 @@ import { Text as Text$1 } from "../base/Text.js";
|
|
|
8
8
|
import { provideGridColumns } from "../layout/Grid.js";
|
|
9
9
|
import { Collapse } from "../display/Collapse.js";
|
|
10
10
|
import { provideFieldLayout } from "./field-layout.js";
|
|
11
|
-
import { createVNode, h, isVNode,
|
|
12
|
-
import { _component, bindable, childrenProp, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
11
|
+
import { createVNode, h, isVNode, unref, useId } from "vue";
|
|
12
|
+
import { _component, bindable, childrenProp, mergeJsxProps, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
13
13
|
import * as css from "@zerodep-dev/vue-tsx-pro-tsx/styles";
|
|
14
14
|
import { ChevronDown } from "@lucide/icons";
|
|
15
15
|
//#region src/form/FormSection.tsx
|
|
@@ -156,7 +156,7 @@ var FormSection = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
156
156
|
} : { columns: 1 });
|
|
157
157
|
return () => {
|
|
158
158
|
const state = !collapsible.value || open.value ? "open" : "closed";
|
|
159
|
-
const titleContent = collapsible.value ? createVNode(ButtonBase,
|
|
159
|
+
const titleContent = collapsible.value ? createVNode(ButtonBase, mergeJsxProps(mergePartProps({ sx: formSectionStyles.trigger }, propsTrigger.value, {
|
|
160
160
|
id: ids.title,
|
|
161
161
|
"aria-expanded": open.value,
|
|
162
162
|
"aria-controls": ids.body,
|
|
@@ -179,17 +179,17 @@ var FormSection = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
179
179
|
ref: bodyRef,
|
|
180
180
|
...collapsible.value ? {} : { id: ids.body }
|
|
181
181
|
}), [children.value]);
|
|
182
|
-
return createVNode("div",
|
|
182
|
+
return createVNode("div", mergeJsxProps(mergeAttrs({ sx: formSectionStyles.root }, rest, {
|
|
183
183
|
role: hasContent(title.value) ? "group" : void 0,
|
|
184
184
|
"aria-labelledby": hasContent(title.value) ? ids.title : void 0,
|
|
185
185
|
"aria-describedby": hasContent(description.value) ? ids.description : void 0
|
|
186
|
-
}), { "data-state": collapsible.value ? state : void 0 }), [(title.value != null || extra.value != null) && createVNode("div", mergeAttrs({ sx: formSectionStyles.header }, propsHeader.value), [createVNode("div", css.attrs(formSectionStyles.headerText), [hasContent(title.value) && h(`h${headingLevel.value}`, mergeAttrs({ sx: formSectionStyles.heading }, propsHeading.value), [titleContent]), hasContent(description.value) && createVNode(Text$1,
|
|
186
|
+
}), { "data-state": collapsible.value ? state : void 0 }), [(title.value != null || extra.value != null) && createVNode("div", mergeAttrs({ sx: formSectionStyles.header }, propsHeader.value), [createVNode("div", css.attrs(formSectionStyles.headerText), [hasContent(title.value) && h(`h${headingLevel.value}`, mergeAttrs({ sx: formSectionStyles.heading }, propsHeading.value), [titleContent]), hasContent(description.value) && createVNode(Text$1, mergeJsxProps(mergePartProps({
|
|
187
187
|
tone: "muted",
|
|
188
188
|
sx: formSectionStyles.description
|
|
189
189
|
}, propsDescription.value, { id: ids.description }), { "key": "__site1" }), {
|
|
190
190
|
default: () => [unref(description)],
|
|
191
191
|
$stable: true
|
|
192
|
-
})]), hasContent(extra.value) && createVNode("div", mergeAttrs({ sx: formSectionStyles.extra }, propsExtra.value), [extra.value])]), collapsible.value ? createVNode(Collapse,
|
|
192
|
+
})]), hasContent(extra.value) && createVNode("div", mergeAttrs({ sx: formSectionStyles.extra }, propsExtra.value), [extra.value])]), collapsible.value ? createVNode(Collapse, mergeJsxProps(mergePartProps({}, propsCollapse.value, { id: ids.body }), {
|
|
193
193
|
"_open": open.value,
|
|
194
194
|
"onUpdate:_open": _cache[0] || (_cache[0] = ($event) => open.value = $event)
|
|
195
195
|
}), _isSlot(body) ? body : { default: () => [body] }) : body]);
|