@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
package/dist/overlay/Popover.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { defaultLabelledBy, mergeAttrs } from "../utils/props.js";
|
|
2
2
|
import { transitions } from "../utils/style/transitions.js";
|
|
3
|
+
import { useMounted } from "../utils/dom/mounted.js";
|
|
3
4
|
import { focusFirst } from "../utils/dom/focus.js";
|
|
4
5
|
import { toElement } from "../utils/dom/element.js";
|
|
5
6
|
import { useLazyContent } from "../utils/motion/presence.js";
|
|
6
7
|
import { floatingStyles, useAnchoredPopover } from "../utils/dom/floating.js";
|
|
7
8
|
import { openSetter } from "../utils/dom/open-change.js";
|
|
8
|
-
import { Fragment, createVNode,
|
|
9
|
-
import { _component, bindable, childrenProp, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
9
|
+
import { Fragment, createVNode, shallowRef, useId } from "vue";
|
|
10
|
+
import { _component, bindable, childrenProp, mergeJsxProps, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
10
11
|
import * as css from "@zerodep-dev/vue-tsx-pro-tsx/styles";
|
|
11
12
|
//#region src/overlay/Popover.tsx
|
|
12
13
|
/** 交给触发器的属性:展开收起的通用属性,加上弹层需要的 id、ref 与 aria-haspopup */
|
|
@@ -29,6 +30,7 @@ var Popover = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
29
30
|
const lazy = readonlyProp(_props, "lazy", true);
|
|
30
31
|
const children = childrenProp(_props);
|
|
31
32
|
const rest = restProps(_props, _ctx, false);
|
|
33
|
+
const mounted = useMounted();
|
|
32
34
|
const id = useId();
|
|
33
35
|
const showContent = useLazyContent(() => open.value, () => lazy.value);
|
|
34
36
|
const triggerId = `${id}-trigger`;
|
|
@@ -56,7 +58,7 @@ var Popover = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
56
58
|
id: triggerId,
|
|
57
59
|
"aria-controls": id,
|
|
58
60
|
"aria-haspopup": "dialog"
|
|
59
|
-
}), createVNode("div",
|
|
61
|
+
}), mounted.value && createVNode("div", mergeJsxProps(mergeAttrs({
|
|
60
62
|
sx: [
|
|
61
63
|
floatingStyles.anchored,
|
|
62
64
|
floatingStyles.surface,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Popover.js","names":["_vtp_bindable","_vtp_readonlyProp","_vtp_childrenProp","_vtp_restProps","_vue_shallowRef","_createVNode","_Fragment","_mergeProps"],"sources":["../../src/overlay/Popover.tsx"],"sourcesContent":["/**\n * 弹出框(非模态):点击触发器在旁边弹出一块内容(筛选条件、简短表单、详情),点击外部或 Esc 关闭。\n * 原生 popover=\"auto\" 负责顶层渲染、Esc、点击外部与嵌套顺序,floating-ui 负责定位(utils/dom/floating)。\n * 打开时焦点移到第一个可聚焦元素(有 autofocus 的优先),关闭后回到触发器。参考 Radix Popover。\n *\n * <Popover _open={open} trigger={(props) => <Button {...props}>筛选</Button>}>…</Popover>\n *\n * 弹出层是 role=\"dialog\",默认以触发器的文字作为名称(aria-labelledby);内容有自己的标题时用 aria-labelledby 指向它。\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport { useId, type VNodeChild } from 'vue';\nimport {\n floatingStyles,\n useAnchoredPopover,\n type FloatingOptions,\n type FloatingPlacement,\n} from '../utils/dom/floating';\nimport { toElement } from '../utils/dom/element';\nimport { focusFirst } from '../utils/dom/focus';\nimport { openSetter, type OpenChangeProps } from '../utils/dom/open-change';\nimport { useLazyContent, type LazyContentOptions } from '../utils/motion/presence';\nimport { defaultLabelledBy, mergeAttrs, type PartProps, type TriggerProps } from '../utils/props';\nimport { transitions } from '../utils/style/transitions';\n\n/** 交给触发器的属性:展开收起的通用属性,加上弹层需要的 id、ref 与 aria-haspopup */\nexport interface PopoverTriggerProps extends TriggerProps {\n /** 弹出层默认以触发器的文字作为无障碍名称(aria-labelledby) */\n id: string;\n /** 取得触发器元素:定位的参照、关闭后焦点回到这里 */\n ref: (element: unknown) => void;\n /** 告诉读屏这个按钮会打开对话框类的弹层 */\n 'aria-haspopup': 'dialog';\n}\n\n/** 未声明的属性落到弹出层的 div 上 */\nexport interface PopoverProps\n extends\n PartProps<'div', 'id' | 'popover' | 'role'>,\n Pick<FloatingOptions, 'offset'>,\n LazyContentOptions,\n OpenChangeProps {\n /** 是否打开:父级写 _open={x};Esc 与点击外部会关闭并写回 */\n _open?: boolean;\n /** 弹出位置,默认 bottom-start;空间不够时自动翻转 */\n placement?: FloatingPlacement;\n /** 显示指向触发器的箭头 */\n arrow?: boolean;\n /**\n * 弹出层的角色,默认 dialog;要用户在确认与取消之间选择时(Popconfirm)用 alertdialog\n * @default 'dialog'\n */\n role?: 'dialog' | 'alertdialog';\n /** 触发器:`(props) => <Button {...props}>更多</Button>` */\n trigger: (props: PopoverTriggerProps) => VNodeChild;\n /** 箭头的部件属性 */\n propsArrow?: PartProps<'span'>;\n children?: VNodeChild;\n}\n\n/** Popover 的样式:panel */\nexport const popoverStyles = css.create({\n panel: { padding: '_md', maxWidth: 'calc(100vw - 2em)' },\n});\n\n/** 弹出框:原生 popover + floating-ui 定位,打开时焦点进入、关闭后回到触发器 */\nexport const Popover = _component(\n ({\n _open: open = _bindable(false),\n onOpenChange,\n placement = 'bottom-start',\n offset,\n arrow = false,\n role = 'dialog',\n trigger,\n propsArrow,\n lazy = true,\n children,\n ...rest\n }: PopoverProps) => {\n const id = useId();\n const showContent = useLazyContent(\n () => open,\n () => lazy,\n );\n const triggerId = `${id}-trigger`;\n let anchor = _state.raw<HTMLElement | null>(null);\n let panel = _state.raw<HTMLElement | null>(null);\n let arrowElement = _state.raw<HTMLElement | null>(null);\n const setOpen = openSetter(\n () => open,\n (next) => (open = next),\n () => onOpenChange,\n );\n // 打开时焦点移入(等第一次定位、可见之后),Esc 与点击外部关闭后写回 open、焦点回到触发器\n const { onToggle } = useAnchoredPopover(\n () => ({ open, anchor, panel, floating: { placement, offset, arrow: arrowElement } }),\n { close: (reason, event) => setOpen(false, reason, event), shown: focusFirst },\n );\n\n return () => (\n <>\n {trigger({\n ref: (value) => (anchor = toElement(value)),\n onClick: (event: MouseEvent) => setOpen(!open, 'trigger', event),\n 'aria-expanded': open,\n id: triggerId,\n 'aria-controls': id,\n 'aria-haspopup': 'dialog',\n })}\n <div\n
|
|
1
|
+
{"version":3,"file":"Popover.js","names":["_vtp_bindable","_vtp_readonlyProp","_vtp_childrenProp","_vtp_restProps","_vue_shallowRef","_createVNode","_Fragment","_mergeProps"],"sources":["../../src/overlay/Popover.tsx"],"sourcesContent":["/**\n * 弹出框(非模态):点击触发器在旁边弹出一块内容(筛选条件、简短表单、详情),点击外部或 Esc 关闭。\n * 原生 popover=\"auto\" 负责顶层渲染、Esc、点击外部与嵌套顺序,floating-ui 负责定位(utils/dom/floating)。\n * 打开时焦点移到第一个可聚焦元素(有 autofocus 的优先),关闭后回到触发器。参考 Radix Popover。\n *\n * <Popover _open={open} trigger={(props) => <Button {...props}>筛选</Button>}>…</Popover>\n *\n * 弹出层是 role=\"dialog\",默认以触发器的文字作为名称(aria-labelledby);内容有自己的标题时用 aria-labelledby 指向它。\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport { useId, type VNodeChild } from 'vue';\nimport {\n floatingStyles,\n useAnchoredPopover,\n type FloatingOptions,\n type FloatingPlacement,\n} from '../utils/dom/floating';\nimport { toElement } from '../utils/dom/element';\nimport { focusFirst } from '../utils/dom/focus';\nimport { openSetter, type OpenChangeProps } from '../utils/dom/open-change';\nimport { useLazyContent, type LazyContentOptions } from '../utils/motion/presence';\nimport { defaultLabelledBy, mergeAttrs, type PartProps, type TriggerProps } from '../utils/props';\nimport { transitions } from '../utils/style/transitions';\nimport { useMounted } from '../utils/dom/mounted';\n\n/** 交给触发器的属性:展开收起的通用属性,加上弹层需要的 id、ref 与 aria-haspopup */\nexport interface PopoverTriggerProps extends TriggerProps {\n /** 弹出层默认以触发器的文字作为无障碍名称(aria-labelledby) */\n id: string;\n /** 取得触发器元素:定位的参照、关闭后焦点回到这里 */\n ref: (element: unknown) => void;\n /** 告诉读屏这个按钮会打开对话框类的弹层 */\n 'aria-haspopup': 'dialog';\n}\n\n/** 未声明的属性落到弹出层的 div 上 */\nexport interface PopoverProps\n extends\n PartProps<'div', 'id' | 'popover' | 'role'>,\n Pick<FloatingOptions, 'offset'>,\n LazyContentOptions,\n OpenChangeProps {\n /** 是否打开:父级写 _open={x};Esc 与点击外部会关闭并写回 */\n _open?: boolean;\n /** 弹出位置,默认 bottom-start;空间不够时自动翻转 */\n placement?: FloatingPlacement;\n /** 显示指向触发器的箭头 */\n arrow?: boolean;\n /**\n * 弹出层的角色,默认 dialog;要用户在确认与取消之间选择时(Popconfirm)用 alertdialog\n * @default 'dialog'\n */\n role?: 'dialog' | 'alertdialog';\n /** 触发器:`(props) => <Button {...props}>更多</Button>` */\n trigger: (props: PopoverTriggerProps) => VNodeChild;\n /** 箭头的部件属性 */\n propsArrow?: PartProps<'span'>;\n children?: VNodeChild;\n}\n\n/** Popover 的样式:panel */\nexport const popoverStyles = css.create({\n panel: { padding: '_md', maxWidth: 'calc(100vw - 2em)' },\n});\n\n/** 弹出框:原生 popover + floating-ui 定位,打开时焦点进入、关闭后回到触发器 */\nexport const Popover = _component(\n ({\n _open: open = _bindable(false),\n onOpenChange,\n placement = 'bottom-start',\n offset,\n arrow = false,\n role = 'dialog',\n trigger,\n propsArrow,\n lazy = true,\n children,\n ...rest\n }: PopoverProps) => {\n // 面板挂载后才渲染:关着时本来就看不见;服务端渲染时也不输出,触发器在 <p> 里时 <div> 不会把段落提前截断,水合对得上\n const mounted = useMounted();\n const id = useId();\n const showContent = useLazyContent(\n () => open,\n () => lazy,\n );\n const triggerId = `${id}-trigger`;\n let anchor = _state.raw<HTMLElement | null>(null);\n let panel = _state.raw<HTMLElement | null>(null);\n let arrowElement = _state.raw<HTMLElement | null>(null);\n const setOpen = openSetter(\n () => open,\n (next) => (open = next),\n () => onOpenChange,\n );\n // 打开时焦点移入(等第一次定位、可见之后),Esc 与点击外部关闭后写回 open、焦点回到触发器\n const { onToggle } = useAnchoredPopover(\n () => ({ open, anchor, panel, floating: { placement, offset, arrow: arrowElement } }),\n { close: (reason, event) => setOpen(false, reason, event), shown: focusFirst },\n );\n\n return () => (\n <>\n {trigger({\n ref: (value) => (anchor = toElement(value)),\n onClick: (event: MouseEvent) => setOpen(!open, 'trigger', event),\n 'aria-expanded': open,\n id: triggerId,\n 'aria-controls': id,\n 'aria-haspopup': 'dialog',\n })}\n {mounted.value && (\n <div\n {...mergeAttrs<'div'>(\n {\n sx: [\n floatingStyles.anchored,\n floatingStyles.surface,\n popoverStyles.panel,\n transitions.scale,\n arrow && floatingStyles.withArrow,\n ],\n 'aria-labelledby': defaultLabelledBy(rest, triggerId),\n },\n rest,\n { ref: _toRef(panel), id, popover: 'auto', role, onToggle },\n )}\n data-state={open ? 'open' : 'closed'}\n >\n {showContent() && children}\n {arrow && (\n <span\n {...mergeAttrs<'span'>({ sx: floatingStyles.arrow }, propsArrow, {\n ref: _toRef(arrowElement),\n 'aria-hidden': true,\n })}\n />\n )}\n </div>\n )}\n </>\n );\n },\n);\n"],"mappings":";;;;;;;;;;;;;;;AA8DA,IAAa,gBAAgB,IAAI,OAAO,EACtC,OAAO;CAAE,SAAS;CAAO,UAAU;AAAoB,EACzD,CAAC;;AAGD,IAAa,UAAU,0BACrB,QAAA,SAYoB;CAAA,MAAA,OAAAA,SAAA,QAAA,eAXM,OAAK,KAAA,IAAA;CAAA,MAAA,eAAAC,aAAA,QAAA,cAAA;CAAA,MAAA,YAAAA,aAAA,QAAA,aAEjB,cAAc;CAAA,MAAA,SAAAA,aAAA,QAAA,QAAA;CAAA,MAAA,QAAAA,aAAA,QAAA,SAElB,KAAK;CAAA,MAAA,OAAAA,aAAA,QAAA,QACN,QAAQ;CAAA,MAAA,UAAAA,aAAA,QAAA,SAAA;CAAA,MAAA,aAAAA,aAAA,QAAA,YAAA;CAAA,MAAA,OAAAA,aAAA,QAAA,QAGR,IAAI;CAAA,MAAA,WAAAC,aAAA,MAAA;CAAA,MAER,OAAIC,UAAA,QAAA,MAAA,KAAA;CAGP,MAAM,UAAU,WAAW;CAC3B,MAAM,KAAK,MAAM;CACjB,MAAM,cAAc,qBAClB,KAAA,aACA,KAAA,KACF;CACA,MAAM,YAAY,GAAG,GAAE;CACvB,IAAI,SAAMC,WAAkC,IAAI;CAChD,IAAI,QAAKA,WAAkC,IAAI;CAC/C,IAAI,eAAYA,WAAkC,IAAI;CACtD,MAAM,UAAU,iBACd,KAAA,QACC,SAAU,KAAA,QAAO,YAClB,aAAA,KACF;CAEA,MAAM,EAAE,aAAa,0BACZ;EAAE,MAAI,KAAA;EAAE,QAAM,OAAA;EAAE,OAAK,MAAA;EAAE,UAAU;GAAE,WAAS,UAAA;GAAE,QAAM,OAAA;GAAE,OAAK,aAAA;EAAe;CAAE,IACnF;EAAE,QAAQ,QAAQ,UAAU,QAAQ,OAAO,QAAQ,KAAK;EAAG,OAAO;CAAW,CAC/E;CAEA,aAAOC,YAAAC,UAAA,MAAA,CAEF,QAAA,MAAQ;EACP,MAAM,UAAW,OAAA,QAAS,UAAU,KAAK;EACzC,UAAU,UAAsB,QAAQ,CAAA,KAAA,OAAO,WAAW,KAAK;EAC/D,iBAAe,KAAA;EACf,IAAI;EACJ,iBAAiB;EACjB,iBAAiB;CACnB,CAAC,GACA,QAAQ,SAAKD,YAAA,OAAAE,cAEN,WACF;EACE,IAAI;GACF,eAAe;GACf,eAAe;GACf,cAAc;GACd,YAAY;GACZ,MAAA,SAAS,eAAe;EAAS;EAEnC,mBAAmB,kBAAkB,MAAM,SAAS;CACtD,GACA,MACA;EAAE,KAAG;EAAiB;EAAI,SAAS;EAAQ,MAAI,KAAA;EAAE;CAAS,CAC5D,GAAC,EAAA,cACW,KAAA,QAAO,SAAS,SAAQ,CAAA,GAAA,CAEnC,YAAY,KAAC,SAAA,OACb,MAAA,SAAAF,YAAA,QAEO,WAAmB,EAAE,IAAI,eAAe,MAAM,GAAC,WAAA,OAAc;EAC/D,KAAG;EACH,eAAe;CACjB,CAAC,GAAC,IAAA,CAEL,CAAA,CAEJ,CAAA;AAGP,GAAC;CAAA,MAAA;CAAA,OAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;CAAA;AAAA,CACH"}
|
package/dist/overlay/Tooltip.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { mergeAttrs } from "../utils/props.js";
|
|
2
2
|
import { transitions } from "../utils/style/transitions.js";
|
|
3
|
+
import { useMounted } from "../utils/dom/mounted.js";
|
|
3
4
|
import { floatingStyles } from "../utils/dom/floating.js";
|
|
4
5
|
import { createHoverGroup } from "../utils/dom/hover-intent.js";
|
|
5
6
|
import { openSetter } from "../utils/dom/open-change.js";
|
|
6
7
|
import { useHoverLayer } from "../utils/dom/hover-layer.js";
|
|
7
|
-
import { Fragment, createVNode
|
|
8
|
-
import { _component, bindable, childrenProp, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
8
|
+
import { Fragment, createVNode } from "vue";
|
|
9
|
+
import { _component, bindable, childrenProp, mergeJsxProps, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
9
10
|
import * as css from "@zerodep-dev/vue-tsx-pro-tsx/styles";
|
|
10
11
|
//#region src/overlay/Tooltip.tsx
|
|
11
12
|
/** 交给触发器的属性:展开到触发元素上(`{...props}`) */
|
|
@@ -41,6 +42,7 @@ var Tooltip = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
41
42
|
const propsArrow = readonlyProp(_props, "propsArrow");
|
|
42
43
|
const children = childrenProp(_props);
|
|
43
44
|
const rest = restProps(_props, _ctx, false);
|
|
45
|
+
const mounted = useMounted();
|
|
44
46
|
const layer = useHoverLayer(tooltips, () => ({
|
|
45
47
|
open: open.value,
|
|
46
48
|
disabled: disabled.value,
|
|
@@ -53,7 +55,7 @@ var Tooltip = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
53
55
|
ref: layer.anchorRef,
|
|
54
56
|
"aria-describedby": layer.id,
|
|
55
57
|
...layer.hover.trigger
|
|
56
|
-
}), createVNode("div",
|
|
58
|
+
}), mounted.value && createVNode("div", mergeJsxProps(mergeAttrs({ sx: [
|
|
57
59
|
floatingStyles.anchored,
|
|
58
60
|
tooltipStyles.panel,
|
|
59
61
|
transitions.fade,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Tooltip.js","names":["_vtp_bindable","_vtp_readonlyProp","_vtp_childrenProp","_vtp_restProps","_createVNode","_Fragment","_mergeProps"],"sources":["../../src/overlay/Tooltip.tsx"],"sourcesContent":["/**\n * 文字提示(role=\"tooltip\"):指针停在触发器上、或用键盘聚焦它时,显示一句简短说明(图标按钮的用途、快捷键)。\n * 参考 WAI-ARIA Tooltip 模式、Radix Tooltip、React Aria Tooltip:\n *\n * <Tooltip trigger={(props) => <IconButton {...props} icon={Copy} aria-label=\"复制\" />}>复制(Ctrl+C)</Tooltip>\n *\n * - 指针停留 delay 毫秒后显示;刚有提示关掉(skipDelay 内)时直接显示,在一排按钮上移动不用每次等;\n * - 键盘聚焦(:focus-visible)立即显示,失焦、Esc、按下触发器时隐藏;触屏不显示(没有悬停);\n * - 指针可以移到提示上而不消失(WCAG 1.4.13);同一时间只显示一个;\n * - 提示是对触发器的补充说明(aria-describedby):触发器自己要有名称,提示里不放可交互的内容。\n *\n * 用 popover=\"manual\" 渲染在顶层:它不是 auto,不会关掉正打开的菜单或弹出框。开关、定位、Esc 见 useHoverLayer(与 HoverCard 共用)。\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 {\n floatingStyles,\n type FloatingOptions,\n type FloatingPlacement,\n} from '../utils/dom/floating';\nimport { createHoverGroup } from '../utils/dom/hover-intent';\nimport { openSetter, type OpenChangeProps } from '../utils/dom/open-change';\nimport { useHoverLayer } from '../utils/dom/hover-layer';\nimport { mergeAttrs, type PartProps } from '../utils/props';\nimport { transitions } from '../utils/style/transitions';\n\n/** 交给触发器的属性:展开到触发元素上(`{...props}`) */\nexport interface TooltipTriggerProps {\n /** 取得触发器元素:定位的参照 */\n ref: (element: unknown) => void;\n /** 提示是对触发器的补充说明 */\n 'aria-describedby': string;\n /** 指针进入:等 delay 后显示 */\n onPointerenter: (event: PointerEvent) => void;\n /** 指针离开:稍后隐藏(可以移到提示上) */\n onPointerleave: (event: PointerEvent) => void;\n /** 按下(点击)触发器时隐藏 */\n onPointerdown: (event: PointerEvent) => void;\n /** 键盘聚焦时立即显示 */\n onFocus: (event: FocusEvent) => void;\n /** 失焦时隐藏 */\n onBlur: (event: FocusEvent) => void;\n}\n\n/** 未声明的属性落到提示的 div 上 */\nexport interface TooltipProps\n extends\n PartProps<'div', 'id' | 'popover' | 'role'>,\n Pick<FloatingOptions, 'offset'>,\n OpenChangeProps {\n /** 是否显示:父级可以写 _open={x} 控制或得知;一般不需要 */\n _open?: boolean;\n /** 显示位置,默认 top;空间不够时自动翻转 */\n placement?: FloatingPlacement;\n /** 指针停留多久后显示(毫秒),默认 500 */\n delay?: number;\n /** 不显示(触发器照常渲染) */\n disabled?: boolean;\n /** 显示指向触发器的箭头 */\n arrow?: boolean;\n /** 触发器:`(props) => <IconButton {...props} … />` */\n trigger: (props: TooltipTriggerProps) => VNodeChild;\n /** 箭头的部件属性 */\n propsArrow?: PartProps<'span'>;\n /** 提示文字 */\n children?: VNodeChild;\n}\n\n/** 指针离开后多久隐藏(毫秒):留出移到提示上的时间 */\nconst CLOSE_DELAY = 100;\n\n/** 所有提示一组:同一时间只显示一个,刚关掉一个时下一个不用等 delay */\nconst tooltips = createHoverGroup();\n\n/** Tooltip 的样式:panel */\nexport const tooltipStyles = css.create({\n panel: {\n boxSizing: 'border-box',\n maxWidth: 'min(20em, calc(100vw - 2em))',\n paddingBlock: '_xs',\n paddingInline: '_sm',\n borderWidth: 0,\n borderRadius: '_sm',\n backgroundColor: '_inverseSurface',\n color: '_inverseText',\n fontSize: '_sm',\n lineHeight: '_normal',\n overflowWrap: 'break-word',\n },\n});\n\n/** 文字提示:悬停或键盘聚焦时显示在触发器旁边 */\nexport const Tooltip = _component(\n ({\n _open: open = _bindable(false),\n onOpenChange,\n placement = 'top',\n offset,\n delay = 500,\n disabled = false,\n arrow = false,\n trigger,\n propsArrow,\n children,\n ...rest\n }: TooltipProps) => {\n const layer = useHoverLayer(\n tooltips,\n () => ({ open, disabled, placement, offset, delay, closeDelay: CLOSE_DELAY }),\n openSetter(\n () => open,\n (next) => (open = next),\n () => onOpenChange,\n ),\n );\n\n return () => (\n <>\n {trigger({\n ref: layer.anchorRef,\n 'aria-describedby': layer.id,\n ...layer.hover.trigger,\n })}\n <div\n
|
|
1
|
+
{"version":3,"file":"Tooltip.js","names":["_vtp_bindable","_vtp_readonlyProp","_vtp_childrenProp","_vtp_restProps","_createVNode","_Fragment","_mergeProps"],"sources":["../../src/overlay/Tooltip.tsx"],"sourcesContent":["/**\n * 文字提示(role=\"tooltip\"):指针停在触发器上、或用键盘聚焦它时,显示一句简短说明(图标按钮的用途、快捷键)。\n * 参考 WAI-ARIA Tooltip 模式、Radix Tooltip、React Aria Tooltip:\n *\n * <Tooltip trigger={(props) => <IconButton {...props} icon={Copy} aria-label=\"复制\" />}>复制(Ctrl+C)</Tooltip>\n *\n * - 指针停留 delay 毫秒后显示;刚有提示关掉(skipDelay 内)时直接显示,在一排按钮上移动不用每次等;\n * - 键盘聚焦(:focus-visible)立即显示,失焦、Esc、按下触发器时隐藏;触屏不显示(没有悬停);\n * - 指针可以移到提示上而不消失(WCAG 1.4.13);同一时间只显示一个;\n * - 提示是对触发器的补充说明(aria-describedby):触发器自己要有名称,提示里不放可交互的内容。\n *\n * 用 popover=\"manual\" 渲染在顶层:它不是 auto,不会关掉正打开的菜单或弹出框。开关、定位、Esc 见 useHoverLayer(与 HoverCard 共用)。\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 {\n floatingStyles,\n type FloatingOptions,\n type FloatingPlacement,\n} from '../utils/dom/floating';\nimport { createHoverGroup } from '../utils/dom/hover-intent';\nimport { openSetter, type OpenChangeProps } from '../utils/dom/open-change';\nimport { useHoverLayer } from '../utils/dom/hover-layer';\nimport { mergeAttrs, type PartProps } from '../utils/props';\nimport { transitions } from '../utils/style/transitions';\nimport { useMounted } from '../utils/dom/mounted';\n\n/** 交给触发器的属性:展开到触发元素上(`{...props}`) */\nexport interface TooltipTriggerProps {\n /** 取得触发器元素:定位的参照 */\n ref: (element: unknown) => void;\n /** 提示是对触发器的补充说明 */\n 'aria-describedby': string;\n /** 指针进入:等 delay 后显示 */\n onPointerenter: (event: PointerEvent) => void;\n /** 指针离开:稍后隐藏(可以移到提示上) */\n onPointerleave: (event: PointerEvent) => void;\n /** 按下(点击)触发器时隐藏 */\n onPointerdown: (event: PointerEvent) => void;\n /** 键盘聚焦时立即显示 */\n onFocus: (event: FocusEvent) => void;\n /** 失焦时隐藏 */\n onBlur: (event: FocusEvent) => void;\n}\n\n/** 未声明的属性落到提示的 div 上 */\nexport interface TooltipProps\n extends\n PartProps<'div', 'id' | 'popover' | 'role'>,\n Pick<FloatingOptions, 'offset'>,\n OpenChangeProps {\n /** 是否显示:父级可以写 _open={x} 控制或得知;一般不需要 */\n _open?: boolean;\n /** 显示位置,默认 top;空间不够时自动翻转 */\n placement?: FloatingPlacement;\n /** 指针停留多久后显示(毫秒),默认 500 */\n delay?: number;\n /** 不显示(触发器照常渲染) */\n disabled?: boolean;\n /** 显示指向触发器的箭头 */\n arrow?: boolean;\n /** 触发器:`(props) => <IconButton {...props} … />` */\n trigger: (props: TooltipTriggerProps) => VNodeChild;\n /** 箭头的部件属性 */\n propsArrow?: PartProps<'span'>;\n /** 提示文字 */\n children?: VNodeChild;\n}\n\n/** 指针离开后多久隐藏(毫秒):留出移到提示上的时间 */\nconst CLOSE_DELAY = 100;\n\n/** 所有提示一组:同一时间只显示一个,刚关掉一个时下一个不用等 delay */\nconst tooltips = createHoverGroup();\n\n/** Tooltip 的样式:panel */\nexport const tooltipStyles = css.create({\n panel: {\n boxSizing: 'border-box',\n maxWidth: 'min(20em, calc(100vw - 2em))',\n paddingBlock: '_xs',\n paddingInline: '_sm',\n borderWidth: 0,\n borderRadius: '_sm',\n backgroundColor: '_inverseSurface',\n color: '_inverseText',\n fontSize: '_sm',\n lineHeight: '_normal',\n overflowWrap: 'break-word',\n },\n});\n\n/** 文字提示:悬停或键盘聚焦时显示在触发器旁边 */\nexport const Tooltip = _component(\n ({\n _open: open = _bindable(false),\n onOpenChange,\n placement = 'top',\n offset,\n delay = 500,\n disabled = false,\n arrow = false,\n trigger,\n propsArrow,\n children,\n ...rest\n }: TooltipProps) => {\n // 面板挂载后才渲染:关着时本来就看不见;服务端渲染时也不输出,触发器在 <p> 里时 <div> 不会把段落提前截断,水合对得上\n const mounted = useMounted();\n const layer = useHoverLayer(\n tooltips,\n () => ({ open, disabled, placement, offset, delay, closeDelay: CLOSE_DELAY }),\n openSetter(\n () => open,\n (next) => (open = next),\n () => onOpenChange,\n ),\n );\n\n return () => (\n <>\n {trigger({\n ref: layer.anchorRef,\n 'aria-describedby': layer.id,\n ...layer.hover.trigger,\n })}\n {mounted.value && (\n <div\n {...mergeAttrs<'div'>(\n {\n sx: [\n floatingStyles.anchored,\n tooltipStyles.panel,\n transitions.fade,\n arrow && floatingStyles.withArrow,\n ],\n },\n rest,\n {\n ref: layer.panelRef,\n id: layer.id,\n popover: 'manual',\n role: 'tooltip',\n // 指针移到提示上不消失,离开提示后再隐藏\n ...layer.hover.panel,\n },\n )}\n data-state={open && !disabled ? 'open' : 'closed'}\n >\n {children}\n {arrow && <span {...layer.arrowProps(propsArrow)} />}\n </div>\n )}\n </>\n );\n },\n);\n"],"mappings":";;;;;;;;;;;;;;AAuEA,IAAM,cAAc;;AAGpB,IAAM,WAAW,iBAAiB;;AAGlC,IAAa,gBAAgB,IAAI,OAAO,EACtC,OAAO;CACL,WAAW;CACX,UAAU;CACV,cAAc;CACd,eAAe;CACf,aAAa;CACb,cAAc;CACd,iBAAiB;CACjB,OAAO;CACP,UAAU;CACV,YAAY;CACZ,cAAc;AAChB,EACF,CAAC;;AAGD,IAAa,UAAU,0BACrB,QAAA,SAYoB;CAAA,MAAA,OAAAA,SAAA,QAAA,eAXM,OAAK,KAAA,IAAA;CAAA,MAAA,eAAAC,aAAA,QAAA,cAAA;CAAA,MAAA,YAAAA,aAAA,QAAA,aAEjB,KAAK;CAAA,MAAA,SAAAA,aAAA,QAAA,QAAA;CAAA,MAAA,QAAAA,aAAA,QAAA,SAET,GAAG;CAAA,MAAA,WAAAA,aAAA,QAAA,YACA,KAAK;CAAA,MAAA,QAAAA,aAAA,QAAA,SACR,KAAK;CAAA,MAAA,UAAAA,aAAA,QAAA,SAAA;CAAA,MAAA,aAAAA,aAAA,QAAA,YAAA;CAAA,MAAA,WAAAC,aAAA,MAAA;CAAA,MAIV,OAAIC,UAAA,QAAA,MAAA,KAAA;CAGP,MAAM,UAAU,WAAW;CAC3B,MAAM,QAAQ,cACZ,iBACO;EAAE,MAAI,KAAA;EAAE,UAAQ,SAAA;EAAE,WAAS,UAAA;EAAE,QAAM,OAAA;EAAE,OAAK,MAAA;EAAE,YAAY;CAAY,IAC3E,iBACE,KAAA,QACC,SAAU,KAAA,QAAO,YAClB,aAAA,KACF,CACF;CAEA,aAAOC,YAAAC,UAAA,MAAA,CAEF,QAAA,MAAQ;EACP,KAAK,MAAM;EACX,oBAAoB,MAAM;EAC1B,GAAG,MAAM,MAAM;CACjB,CAAC,GACA,QAAQ,SAAKD,YAAA,OAAAE,cAEN,WACF,EACE,IAAI;EACF,eAAe;EACf,cAAc;EACd,YAAY;EACZ,MAAA,SAAS,eAAe;CAAS,EAErC,GACA,MACA;EACE,KAAK,MAAM;EACX,IAAI,MAAM;EACV,SAAS;EACT,MAAM;EAEN,GAAG,MAAM,MAAM;CACjB,CACF,GAAC,EAAA,cACW,KAAA,SAAQ,CAAA,SAAA,QAAY,SAAS,SAAQ,CAAA,GAAA,CAAA,SAAA,OAGhD,MAAA,SAAAF,YAAA,QAAmB,MAAM,WAAU,WAAA,KAAW,GAAC,IAAA,CAAI,CAAA,CAEvD,CAAA;AAGP,GAAC;CAAA,MAAA;CAAA,OAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;CAAA;AAAA,CACH"}
|
package/dist/overlay/Tour.js
CHANGED
|
@@ -11,8 +11,8 @@ import { Flex } from "../layout/Flex.js";
|
|
|
11
11
|
import { anchorFloating, floatingStyles } from "../utils/dom/floating.js";
|
|
12
12
|
import { openSetter } from "../utils/dom/open-change.js";
|
|
13
13
|
import { isEditable } from "../utils/dom/hotkeys.js";
|
|
14
|
-
import { createVNode,
|
|
15
|
-
import { _component, bindable, effect, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
14
|
+
import { createVNode, nextTick, onBeforeUnmount, ref, shallowRef, useId } from "vue";
|
|
15
|
+
import { _component, bindable, effect, mergeJsxProps, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
16
16
|
import * as css from "@zerodep-dev/vue-tsx-pro-tsx/styles";
|
|
17
17
|
import { autoUpdate } from "@floating-ui/dom";
|
|
18
18
|
//#region src/overlay/Tour.tsx
|
|
@@ -215,7 +215,7 @@ var Tour = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
215
215
|
});
|
|
216
216
|
return () => {
|
|
217
217
|
const currentStep = step();
|
|
218
|
-
return createVNode("dialog",
|
|
218
|
+
return createVNode("dialog", mergeJsxProps(mergeAttrs({ sx: tourStyles.root }, rest, {
|
|
219
219
|
ref: dialog,
|
|
220
220
|
"aria-labelledby": titleId,
|
|
221
221
|
"aria-describedby": currentStep?.content != null ? contentId : void 0,
|
|
@@ -241,14 +241,14 @@ var Tour = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
241
241
|
sx: tourStyles.title
|
|
242
242
|
}, propsTitle.value, { id: titleId }), { default: () => [currentStep.title] }),
|
|
243
243
|
currentStep.content != null && createVNode("div", mergeAttrs({ sx: tourStyles.content }, propsContent.value, { id: contentId }), [currentStep.content]),
|
|
244
|
-
createVNode(Flex,
|
|
244
|
+
createVNode(Flex, mergeJsxProps(mergePartProps({
|
|
245
245
|
align: "center",
|
|
246
246
|
gap: "sm",
|
|
247
247
|
wrap: true,
|
|
248
248
|
sx: tourStyles.footer
|
|
249
249
|
}, propsFooter.value), { "key": "__site4" }), {
|
|
250
250
|
default: () => [
|
|
251
|
-
createVNode(Text$1,
|
|
251
|
+
createVNode(Text$1, mergeJsxProps(mergePartProps({
|
|
252
252
|
size: "sm",
|
|
253
253
|
tone: "muted",
|
|
254
254
|
sx: tourStyles.progress
|
|
@@ -256,7 +256,7 @@ var Tour = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
256
256
|
default: () => [lang.tourProgress(current.value + 1, steps.value.length)],
|
|
257
257
|
$stable: true
|
|
258
258
|
}),
|
|
259
|
-
!last() && createVNode(Button,
|
|
259
|
+
!last() && createVNode(Button, mergeJsxProps(mergePartProps({
|
|
260
260
|
variant: "ghost",
|
|
261
261
|
tone: "neutral",
|
|
262
262
|
size: "sm"
|
|
@@ -264,14 +264,14 @@ var Tour = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
264
264
|
default: () => [lang.tourSkip],
|
|
265
265
|
$stable: true
|
|
266
266
|
}),
|
|
267
|
-
current.value > 0 && createVNode(Button,
|
|
267
|
+
current.value > 0 && createVNode(Button, mergeJsxProps(mergePartProps({
|
|
268
268
|
variant: "outline",
|
|
269
269
|
size: "sm"
|
|
270
270
|
}, propsPrevious.value, { onClick: () => current.value-- }), { "key": "__site2" }), {
|
|
271
271
|
default: () => [lang.tourPrevious],
|
|
272
272
|
$stable: true
|
|
273
273
|
}),
|
|
274
|
-
createVNode(Button,
|
|
274
|
+
createVNode(Button, mergeJsxProps(mergePartProps({ size: "sm" }, propsNext.value, { onClick: next }), { "key": "__site3" }), {
|
|
275
275
|
default: () => [last() ? lang.tourFinish : lang.tourNext],
|
|
276
276
|
$stable: true
|
|
277
277
|
})
|
package/dist/overlay/Tour.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Tour.js","names":["_vtp_readonlyProp","_vtp_bindable","_vtp_restProps","_vue_shallowRef","_vue_ref","_createVNode","_mergeProps","Text"],"sources":["../../src/overlay/Tour.tsx"],"sourcesContent":["/**\n * 新手引导(原生模态 dialog):一步一步高亮页面上的元素并说明。遮罩盖住整页,目标处镂空;步骤卡片贴着目标显示。\n *\n * <Tour\n * _open={touring}\n * _current={step}\n * steps={[\n * { target: '#search', title: '搜索', content: '按名称或编号查找订单' },\n * { target: () => exportButton, title: '导出', content: '导出为 Excel', placement: 'left' },\n * { title: '就这些', content: '随时可以在帮助菜单里重新查看' }, // 没有目标:卡片居中\n * ]}\n * />\n *\n * - 模态:背景 inert、Esc 关闭、焦点在卡片里循环,关闭后焦点回到打开前的位置(同 Dialog,见 Modal.tsx);\n * - 目标:选择器、元素或返回元素的函数;切换步骤时滚动到视口中间,滚动、缩放、目标大小变化时镂空与卡片跟着动;\n * 找不到目标时卡片居中、整页遮罩;\n * - 卡片:封面(可选的插图)、标题(dialog 的名称)、内容(描述)、步数、上一步 / 下一步(最后一步是“完成”)、跳过;\n * - 键盘:→ / ← 下一步 / 上一步(从右到左排版时反过来;焦点在输入框里时不处理),Esc 关闭;\n * - open、current 可受控;点“完成”时调用 onFinish,跳过或 Esc 时只关闭。\n *\n * 部件:Root(dialog,整屏透明)、Spotlight(镂空)、Panel(卡片)、Cover(封面)、Title、Content、Footer、\n * Progress(步数)、Skip(跳过)、Previous(上一步)、Next(下一步 / 完成)。\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport { autoUpdate } from '@floating-ui/dom';\nimport { nextTick, onBeforeUnmount, useId, type VNodeChild } from 'vue';\nimport { Button, type ButtonProps } from '../base/Button';\nimport { H2, type HeadingProps } from '../base/Heading';\nimport { Text, type TextProps } from '../base/Text';\nimport { useLang } from '../config/context';\nimport { Flex, type FlexProps } from '../layout/Flex';\nimport { clamp } from '../utils/data/number';\nimport { isRtl } from '../utils/dom/direction';\nimport { isEditable } from '../utils/dom/hotkeys';\nimport { anchorFloating, floatingStyles, type FloatingPlacement } from '../utils/dom/floating';\nimport {\n openSetter,\n type OpenChangeDetails,\n type OpenChangeProps,\n type OpenChangeReasons,\n} from '../utils/dom/open-change';\nimport { openModalDialog } from '../utils/dom/overlay';\nimport { mergeAttrs, mergePartProps, type PartProps } from '../utils/props';\nimport { t } from '../config/theme/tokens';\n\n/** 引导的一步 */\nexport interface TourStep {\n /** 高亮的目标:选择器、元素或返回元素的函数;不写(或找不到)时卡片居中 */\n target?: string | HTMLElement | (() => HTMLElement | null | undefined);\n /** 标题 */\n title: VNodeChild;\n /** 说明 */\n content?: VNodeChild;\n /** 标题上方的封面:插图、截图、短视频 */\n cover?: VNodeChild;\n /** 卡片在目标的哪一侧,默认 bottom;空间不够时翻转 */\n placement?: FloatingPlacement;\n}\n\n/** 打开、无障碍关联由组件管理;未声明的属性落到 dialog 上 */\nexport interface TourProps\n extends PartProps<'dialog', 'open' | 'aria-labelledby' | 'aria-describedby'>, OpenChangeProps {\n /** 各步 */\n steps: readonly TourStep[];\n /** 是否显示:父级写 _open={x} */\n _open?: boolean;\n /** 当前第几步,从 0 开始:父级写 _current={n} */\n _current?: number;\n /** 镂空比目标大出多少(px),默认 6 */\n padding?: number;\n /** 点了最后一步的“完成” */\n onFinish?: () => void;\n /** 镂空的部件属性 */\n propsSpotlight?: PartProps<'div'>;\n /** 卡片的部件属性 */\n propsPanel?: PartProps<'div'>;\n /** 封面的部件属性 */\n propsCover?: PartProps<'div'>;\n /** 标题(dialog 的名称)的部件属性 */\n propsTitle?: Omit<Partial<HeadingProps>, 'id'>;\n /** 说明(dialog 的描述)的部件属性 */\n propsContent?: PartProps<'div', 'id'>;\n /** 底部一行(步数与按钮)的部件属性 */\n propsFooter?: Partial<FlexProps>;\n /** 步数(第几步 / 共几步)的部件属性 */\n propsProgress?: Partial<TextProps>;\n /** 跳过按钮的部件属性;点击由组件管理 */\n propsSkip?: Omit<Partial<ButtonProps>, 'onClick'>;\n /** 上一步按钮的部件属性;点击由组件管理 */\n propsPrevious?: Omit<Partial<ButtonProps>, 'onClick'>;\n /** 下一步(最后一步是完成)按钮的部件属性;点击由组件管理,可以设 disabled(完成某个操作后才能继续) */\n propsNext?: Omit<Partial<ButtonProps>, 'onClick'>;\n}\n\n/** Tour 的样式:root、spotlight、dimmed、panel、cover、title、content、footer、progress */\nexport const tourStyles = css.create({\n // 整屏透明的 dialog:遮罩由镂空的阴影画,原生 ::backdrop 不用\n root: {\n position: 'fixed',\n inset: 0,\n width: '100%',\n height: '100%',\n maxWidth: 'none',\n maxHeight: 'none',\n margin: 0,\n padding: 0,\n borderWidth: 0,\n backgroundColor: 'transparent',\n overflow: 'hidden',\n '::backdrop': { backgroundColor: 'transparent' },\n },\n // 一个盒子画成镂空:外面一圈巨大的阴影就是遮罩\n spotlight: {\n position: 'fixed',\n borderRadius: '_md',\n boxShadow: `0 0 0 100vmax ${t.colors.overlay}`,\n pointerEvents: 'none',\n transition: `left ${t.durations.md} ${t.easings.standard}, top ${t.durations.md} ${t.easings.standard}, width ${t.durations.md} ${t.easings.standard}, height ${t.durations.md} ${t.easings.standard}`,\n },\n // 没有目标:整页遮罩\n dimmed: { inset: 0, borderRadius: 0, backgroundColor: '_overlay', boxShadow: 'none' },\n panel: {\n display: 'grid',\n gap: '_sm',\n width: 'min(22rem, calc(100vw - 2rem))',\n padding: '_lg',\n lineHeight: '_normal',\n },\n centered: { left: '50%', top: '50%', transform: 'translate(-50%, -50%)' },\n // 封面贴着卡片的上、左、右边(盖住内边距),圆角随卡片\n cover: {\n display: 'grid',\n overflow: 'hidden',\n marginBlockStart: `calc(-1 * ${t.space.lg})`,\n marginInline: `calc(-1 * ${t.space.lg})`,\n marginBlockEnd: '_xs',\n borderStartStartRadius: 'inherit',\n borderStartEndRadius: 'inherit',\n },\n title: { margin: 0 },\n content: { color: '_textMuted' },\n footer: { marginBlockStart: '_xs' },\n progress: { marginInlineEnd: 'auto' },\n});\n\n// 镂空的位置是任意值,用动态样式;单独一个 create,见 COMPONENTS.md §4\n/** Tour 的样式:rect(镂空的位置与大小) */\nexport const tourDynamicStyles = css.create({\n rect: (left: number, top: number, width: number, height: number) => ({\n left,\n top,\n width,\n height,\n }),\n});\n\n/** 读出这一步的目标元素 */\nfunction resolveTarget(target: TourStep['target']): HTMLElement | null {\n if (!target) return null;\n if (typeof target === 'string') return document.querySelector<HTMLElement>(target);\n if (typeof target === 'function') return target() ?? null;\n return target;\n}\n\n/** 新手引导:遮罩镂空高亮目标,卡片说明,一步一步往下走 */\nexport const Tour = _component(\n ({\n steps,\n _open: open = _bindable(false),\n onOpenChange,\n _current: current = _bindable(0),\n padding = 6,\n onFinish,\n propsSpotlight,\n propsPanel,\n propsCover,\n propsTitle,\n propsContent,\n propsFooter,\n propsProgress,\n propsSkip,\n propsPrevious,\n propsNext,\n ...rest\n }: TourProps) => {\n const lang = useLang();\n const id = useId();\n const titleId = `${id}-title`;\n const contentId = `${id}-content`;\n let dialog = _state.raw<HTMLDialogElement | null>(null);\n let panel = _state.raw<HTMLElement | null>(null);\n // 镂空的位置;没有目标时为 null(整页遮罩、卡片居中)\n let rect = _state<{ left: number; top: number; width: number; height: number } | null>(null);\n let release: (() => void) | undefined;\n let stopFollow: (() => void) | undefined;\n const setOpen = openSetter(\n () => open,\n (next) => (open = next),\n () => onOpenChange,\n );\n // 这次关闭的原因:跳过是 close、完成是 confirm,没记下的是 Esc\n let closing: OpenChangeDetails | undefined;\n\n const step = () => steps[clamp(current, 0, steps.length - 1)];\n const last = () => current >= steps.length - 1;\n\n // 与对话框同一套模态行为(含锁定页面滚动:引导期间页面不跟着滚,目标由引导自己滚进视口)\n function show() {\n if (!dialog || dialog.open) return;\n release = openModalDialog(dialog);\n }\n\n /** 跟随这一步的目标:滚到视口中间,之后镂空与卡片随滚动、缩放、大小变化更新 */\n async function follow() {\n stopFollow?.();\n stopFollow = undefined;\n await nextTick();\n const target = resolveTarget(step()?.target);\n if (!panel) return;\n if (!target) {\n rect = null;\n // 上一步定位时写的内联位置清掉,卡片回到居中\n Object.assign(panel.style, { left: '', top: '', visibility: '', transformOrigin: '' });\n panel.focus({ preventScroll: true });\n return;\n }\n target.scrollIntoView({ block: 'center', inline: 'nearest' });\n const anchored = anchorFloating(target, panel, {\n placement: step()?.placement ?? 'bottom',\n offset: padding + 8,\n crossAxisFallback: true,\n });\n const stopSpotlight = autoUpdate(target, panel, () => {\n const box = target.getBoundingClientRect();\n rect = {\n left: box.left - padding,\n top: box.top - padding,\n width: box.width + padding * 2,\n height: box.height + padding * 2,\n };\n });\n stopFollow = () => {\n anchored.stop();\n stopSpotlight();\n };\n await anchored.ready;\n panel.focus({ preventScroll: true });\n }\n\n function close(reason: keyof OpenChangeReasons, event?: Event) {\n closing = { reason, event };\n dialog?.close();\n }\n\n // 不论以什么方式关闭(Esc、跳过、完成、open 改为 false),都会触发原生 close 事件\n function onClose() {\n stopFollow?.();\n stopFollow = undefined;\n release?.();\n release = undefined;\n const { reason, event } = closing ?? { reason: 'escape' };\n closing = undefined;\n setOpen(false, reason, event);\n }\n\n function next(event?: Event) {\n if (!last()) {\n current++;\n return;\n }\n close('confirm', event);\n onFinish?.();\n }\n\n /** → / ← 切换步骤(从右到左排版时反过来);焦点在可编辑的元素里时留给它,下一步按钮不可用时不前进 */\n function onKeydown(event: KeyboardEvent) {\n if (event.key !== 'ArrowRight' && event.key !== 'ArrowLeft') return;\n if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) return;\n if (isEditable(event.target)) return;\n const forward = (event.key === 'ArrowRight') !== (panel !== null && isRtl(panel));\n if (forward && !propsNext?.disabled) next(event);\n else if (!forward && current > 0) current--;\n else return;\n event.preventDefault();\n }\n\n _effect(() => {\n if (open) show();\n else if (dialog?.open) dialog.close();\n });\n // 打开、换步骤时重新跟随目标\n _effect(() => {\n void current;\n if (open) void follow();\n });\n onBeforeUnmount(() => {\n stopFollow?.();\n release?.();\n });\n\n return () => {\n const currentStep = step();\n return (\n <dialog\n {...mergeAttrs<'dialog'>({ sx: tourStyles.root }, rest, {\n ref: _toRef(dialog),\n 'aria-labelledby': titleId,\n 'aria-describedby': currentStep?.content != null ? contentId : undefined,\n onClose,\n onKeydown,\n })}\n data-step={current}\n >\n <div\n {...mergeAttrs<'div'>(\n {\n sx: [\n tourStyles.spotlight,\n rect\n ? tourDynamicStyles.rect(rect.left, rect.top, rect.width, rect.height)\n : tourStyles.dimmed,\n ],\n },\n propsSpotlight,\n { 'aria-hidden': true },\n )}\n />\n {currentStep && (\n <div\n {...mergeAttrs<'div'>(\n {\n sx: [\n floatingStyles.anchored,\n floatingStyles.surface,\n tourStyles.panel,\n !rect && tourStyles.centered,\n ],\n },\n propsPanel,\n { ref: _toRef(panel), tabindex: -1 },\n )}\n >\n {currentStep.cover != null && (\n <div {...mergeAttrs<'div'>({ sx: tourStyles.cover }, propsCover)}>\n {currentStep.cover}\n </div>\n )}\n <H2\n {...mergePartProps<HeadingProps>({ size: 'md', sx: tourStyles.title }, propsTitle, {\n id: titleId,\n })}\n >\n {currentStep.title}\n </H2>\n {currentStep.content != null && (\n <div\n {...mergeAttrs<'div'>({ sx: tourStyles.content }, propsContent, {\n id: contentId,\n })}\n >\n {currentStep.content}\n </div>\n )}\n <Flex\n {...mergePartProps<FlexProps>(\n { align: 'center', gap: 'sm', wrap: true, sx: tourStyles.footer },\n propsFooter,\n )}\n >\n <Text\n {...mergePartProps<TextProps>(\n { size: 'sm', tone: 'muted', sx: tourStyles.progress },\n propsProgress,\n )}\n >\n {lang.tourProgress(current + 1, steps.length)}\n </Text>\n {!last() && (\n <Button\n {...mergePartProps<ButtonProps>(\n { variant: 'ghost', tone: 'neutral', size: 'sm' },\n propsSkip,\n { onClick: (event: MouseEvent) => close('close', event) },\n )}\n >\n {lang.tourSkip}\n </Button>\n )}\n {current > 0 && (\n <Button\n {...mergePartProps<ButtonProps>(\n { variant: 'outline', size: 'sm' },\n propsPrevious,\n { onClick: () => current-- },\n )}\n >\n {lang.tourPrevious}\n </Button>\n )}\n <Button\n {...mergePartProps<ButtonProps>({ size: 'sm' }, propsNext, { onClick: next })}\n >\n {last() ? lang.tourFinish : lang.tourNext}\n </Button>\n </Flex>\n </div>\n )}\n </dialog>\n );\n };\n },\n { name: 'Tour' },\n);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Tour.js","names":["_vtp_readonlyProp","_vtp_bindable","_vtp_restProps","_vue_shallowRef","_vue_ref","_createVNode","_mergeProps","Text"],"sources":["../../src/overlay/Tour.tsx"],"sourcesContent":["/**\n * 新手引导(原生模态 dialog):一步一步高亮页面上的元素并说明。遮罩盖住整页,目标处镂空;步骤卡片贴着目标显示。\n *\n * <Tour\n * _open={touring}\n * _current={step}\n * steps={[\n * { target: '#search', title: '搜索', content: '按名称或编号查找订单' },\n * { target: () => exportButton, title: '导出', content: '导出为 Excel', placement: 'left' },\n * { title: '就这些', content: '随时可以在帮助菜单里重新查看' }, // 没有目标:卡片居中\n * ]}\n * />\n *\n * - 模态:背景 inert、Esc 关闭、焦点在卡片里循环,关闭后焦点回到打开前的位置(同 Dialog,见 Modal.tsx);\n * - 目标:选择器、元素或返回元素的函数;切换步骤时滚动到视口中间,滚动、缩放、目标大小变化时镂空与卡片跟着动;\n * 找不到目标时卡片居中、整页遮罩;\n * - 卡片:封面(可选的插图)、标题(dialog 的名称)、内容(描述)、步数、上一步 / 下一步(最后一步是“完成”)、跳过;\n * - 键盘:→ / ← 下一步 / 上一步(从右到左排版时反过来;焦点在输入框里时不处理),Esc 关闭;\n * - open、current 可受控;点“完成”时调用 onFinish,跳过或 Esc 时只关闭。\n *\n * 部件:Root(dialog,整屏透明)、Spotlight(镂空)、Panel(卡片)、Cover(封面)、Title、Content、Footer、\n * Progress(步数)、Skip(跳过)、Previous(上一步)、Next(下一步 / 完成)。\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport { autoUpdate } from '@floating-ui/dom';\nimport { nextTick, onBeforeUnmount, useId, type VNodeChild } from 'vue';\nimport { Button, type ButtonProps } from '../base/Button';\nimport { H2, type HeadingProps } from '../base/Heading';\nimport { Text, type TextProps } from '../base/Text';\nimport { useLang } from '../config/context';\nimport { Flex, type FlexProps } from '../layout/Flex';\nimport { clamp } from '../utils/data/number';\nimport { isRtl } from '../utils/dom/direction';\nimport { isEditable } from '../utils/dom/hotkeys';\nimport { anchorFloating, floatingStyles, type FloatingPlacement } from '../utils/dom/floating';\nimport {\n openSetter,\n type OpenChangeDetails,\n type OpenChangeProps,\n type OpenChangeReasons,\n} from '../utils/dom/open-change';\nimport { openModalDialog } from '../utils/dom/overlay';\nimport { mergeAttrs, mergePartProps, type PartProps } from '../utils/props';\nimport { t } from '../config/theme/tokens';\n\n/** 引导的一步 */\nexport interface TourStep {\n /** 高亮的目标:选择器、元素或返回元素的函数;不写(或找不到)时卡片居中 */\n target?: string | HTMLElement | (() => HTMLElement | null | undefined);\n /** 标题 */\n title: VNodeChild;\n /** 说明 */\n content?: VNodeChild;\n /** 标题上方的封面:插图、截图、短视频 */\n cover?: VNodeChild;\n /** 卡片在目标的哪一侧,默认 bottom;空间不够时翻转 */\n placement?: FloatingPlacement;\n}\n\n/** 打开、无障碍关联由组件管理;未声明的属性落到 dialog 上 */\nexport interface TourProps\n extends PartProps<'dialog', 'open' | 'aria-labelledby' | 'aria-describedby'>, OpenChangeProps {\n /** 各步 */\n steps: readonly TourStep[];\n /** 是否显示:父级写 _open={x} */\n _open?: boolean;\n /** 当前第几步,从 0 开始:父级写 _current={n} */\n _current?: number;\n /** 镂空比目标大出多少(px),默认 6 */\n padding?: number;\n /** 点了最后一步的“完成” */\n onFinish?: () => void;\n /** 镂空的部件属性 */\n propsSpotlight?: PartProps<'div'>;\n /** 卡片的部件属性 */\n propsPanel?: PartProps<'div'>;\n /** 封面的部件属性 */\n propsCover?: PartProps<'div'>;\n /** 标题(dialog 的名称)的部件属性 */\n propsTitle?: Omit<Partial<HeadingProps>, 'id'>;\n /** 说明(dialog 的描述)的部件属性 */\n propsContent?: PartProps<'div', 'id'>;\n /** 底部一行(步数与按钮)的部件属性 */\n propsFooter?: Partial<FlexProps>;\n /** 步数(第几步 / 共几步)的部件属性 */\n propsProgress?: Partial<TextProps>;\n /** 跳过按钮的部件属性;点击由组件管理 */\n propsSkip?: Omit<Partial<ButtonProps>, 'onClick'>;\n /** 上一步按钮的部件属性;点击由组件管理 */\n propsPrevious?: Omit<Partial<ButtonProps>, 'onClick'>;\n /** 下一步(最后一步是完成)按钮的部件属性;点击由组件管理,可以设 disabled(完成某个操作后才能继续) */\n propsNext?: Omit<Partial<ButtonProps>, 'onClick'>;\n}\n\n/** Tour 的样式:root、spotlight、dimmed、panel、cover、title、content、footer、progress */\nexport const tourStyles = css.create({\n // 整屏透明的 dialog:遮罩由镂空的阴影画,原生 ::backdrop 不用\n root: {\n position: 'fixed',\n inset: 0,\n width: '100%',\n height: '100%',\n maxWidth: 'none',\n maxHeight: 'none',\n margin: 0,\n padding: 0,\n borderWidth: 0,\n backgroundColor: 'transparent',\n overflow: 'hidden',\n '::backdrop': { backgroundColor: 'transparent' },\n },\n // 一个盒子画成镂空:外面一圈巨大的阴影就是遮罩\n spotlight: {\n position: 'fixed',\n borderRadius: '_md',\n boxShadow: `0 0 0 100vmax ${t.colors.overlay}`,\n pointerEvents: 'none',\n transition: `left ${t.durations.md} ${t.easings.standard}, top ${t.durations.md} ${t.easings.standard}, width ${t.durations.md} ${t.easings.standard}, height ${t.durations.md} ${t.easings.standard}`,\n },\n // 没有目标:整页遮罩\n dimmed: { inset: 0, borderRadius: 0, backgroundColor: '_overlay', boxShadow: 'none' },\n panel: {\n display: 'grid',\n gap: '_sm',\n width: 'min(22rem, calc(100vw - 2rem))',\n padding: '_lg',\n lineHeight: '_normal',\n },\n centered: { left: '50%', top: '50%', transform: 'translate(-50%, -50%)' },\n // 封面贴着卡片的上、左、右边(盖住内边距),圆角随卡片\n cover: {\n display: 'grid',\n overflow: 'hidden',\n marginBlockStart: `calc(-1 * ${t.space.lg})`,\n marginInline: `calc(-1 * ${t.space.lg})`,\n marginBlockEnd: '_xs',\n borderStartStartRadius: 'inherit',\n borderStartEndRadius: 'inherit',\n },\n title: { margin: 0 },\n content: { color: '_textMuted' },\n footer: { marginBlockStart: '_xs' },\n progress: { marginInlineEnd: 'auto' },\n});\n\n// 镂空的位置是任意值,用动态样式;单独一个 create,见 COMPONENTS.md §4\n/** Tour 的样式:rect(镂空的位置与大小) */\nexport const tourDynamicStyles = css.create({\n rect: (left: number, top: number, width: number, height: number) => ({\n left,\n top,\n width,\n height,\n }),\n});\n\n/** 读出这一步的目标元素 */\nfunction resolveTarget(target: TourStep['target']): HTMLElement | null {\n if (!target) return null;\n if (typeof target === 'string') return document.querySelector<HTMLElement>(target);\n if (typeof target === 'function') return target() ?? null;\n return target;\n}\n\n/** 新手引导:遮罩镂空高亮目标,卡片说明,一步一步往下走 */\nexport const Tour = _component(\n ({\n steps,\n _open: open = _bindable(false),\n onOpenChange,\n _current: current = _bindable(0),\n padding = 6,\n onFinish,\n propsSpotlight,\n propsPanel,\n propsCover,\n propsTitle,\n propsContent,\n propsFooter,\n propsProgress,\n propsSkip,\n propsPrevious,\n propsNext,\n ...rest\n }: TourProps) => {\n const lang = useLang();\n const id = useId();\n const titleId = `${id}-title`;\n const contentId = `${id}-content`;\n let dialog = _state.raw<HTMLDialogElement | null>(null);\n let panel = _state.raw<HTMLElement | null>(null);\n // 镂空的位置;没有目标时为 null(整页遮罩、卡片居中)\n let rect = _state<{ left: number; top: number; width: number; height: number } | null>(null);\n let release: (() => void) | undefined;\n let stopFollow: (() => void) | undefined;\n const setOpen = openSetter(\n () => open,\n (next) => (open = next),\n () => onOpenChange,\n );\n // 这次关闭的原因:跳过是 close、完成是 confirm,没记下的是 Esc\n let closing: OpenChangeDetails | undefined;\n\n const step = () => steps[clamp(current, 0, steps.length - 1)];\n const last = () => current >= steps.length - 1;\n\n // 与对话框同一套模态行为(含锁定页面滚动:引导期间页面不跟着滚,目标由引导自己滚进视口)\n function show() {\n if (!dialog || dialog.open) return;\n release = openModalDialog(dialog);\n }\n\n /** 跟随这一步的目标:滚到视口中间,之后镂空与卡片随滚动、缩放、大小变化更新 */\n async function follow() {\n stopFollow?.();\n stopFollow = undefined;\n await nextTick();\n const target = resolveTarget(step()?.target);\n if (!panel) return;\n if (!target) {\n rect = null;\n // 上一步定位时写的内联位置清掉,卡片回到居中\n Object.assign(panel.style, { left: '', top: '', visibility: '', transformOrigin: '' });\n panel.focus({ preventScroll: true });\n return;\n }\n target.scrollIntoView({ block: 'center', inline: 'nearest' });\n const anchored = anchorFloating(target, panel, {\n placement: step()?.placement ?? 'bottom',\n offset: padding + 8,\n crossAxisFallback: true,\n });\n const stopSpotlight = autoUpdate(target, panel, () => {\n const box = target.getBoundingClientRect();\n rect = {\n left: box.left - padding,\n top: box.top - padding,\n width: box.width + padding * 2,\n height: box.height + padding * 2,\n };\n });\n stopFollow = () => {\n anchored.stop();\n stopSpotlight();\n };\n await anchored.ready;\n panel.focus({ preventScroll: true });\n }\n\n function close(reason: keyof OpenChangeReasons, event?: Event) {\n closing = { reason, event };\n dialog?.close();\n }\n\n // 不论以什么方式关闭(Esc、跳过、完成、open 改为 false),都会触发原生 close 事件\n function onClose() {\n stopFollow?.();\n stopFollow = undefined;\n release?.();\n release = undefined;\n const { reason, event } = closing ?? { reason: 'escape' };\n closing = undefined;\n setOpen(false, reason, event);\n }\n\n function next(event?: Event) {\n if (!last()) {\n current++;\n return;\n }\n close('confirm', event);\n onFinish?.();\n }\n\n /** → / ← 切换步骤(从右到左排版时反过来);焦点在可编辑的元素里时留给它,下一步按钮不可用时不前进 */\n function onKeydown(event: KeyboardEvent) {\n if (event.key !== 'ArrowRight' && event.key !== 'ArrowLeft') return;\n if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) return;\n if (isEditable(event.target)) return;\n const forward = (event.key === 'ArrowRight') !== (panel !== null && isRtl(panel));\n if (forward && !propsNext?.disabled) next(event);\n else if (!forward && current > 0) current--;\n else return;\n event.preventDefault();\n }\n\n _effect(() => {\n if (open) show();\n else if (dialog?.open) dialog.close();\n });\n // 打开、换步骤时重新跟随目标\n _effect(() => {\n void current;\n if (open) void follow();\n });\n onBeforeUnmount(() => {\n stopFollow?.();\n release?.();\n });\n\n return () => {\n const currentStep = step();\n return (\n <dialog\n {...mergeAttrs<'dialog'>({ sx: tourStyles.root }, rest, {\n ref: _toRef(dialog),\n 'aria-labelledby': titleId,\n 'aria-describedby': currentStep?.content != null ? contentId : undefined,\n onClose,\n onKeydown,\n })}\n data-step={current}\n >\n <div\n {...mergeAttrs<'div'>(\n {\n sx: [\n tourStyles.spotlight,\n rect\n ? tourDynamicStyles.rect(rect.left, rect.top, rect.width, rect.height)\n : tourStyles.dimmed,\n ],\n },\n propsSpotlight,\n { 'aria-hidden': true },\n )}\n />\n {currentStep && (\n <div\n {...mergeAttrs<'div'>(\n {\n sx: [\n floatingStyles.anchored,\n floatingStyles.surface,\n tourStyles.panel,\n !rect && tourStyles.centered,\n ],\n },\n propsPanel,\n { ref: _toRef(panel), tabindex: -1 },\n )}\n >\n {currentStep.cover != null && (\n <div {...mergeAttrs<'div'>({ sx: tourStyles.cover }, propsCover)}>\n {currentStep.cover}\n </div>\n )}\n <H2\n {...mergePartProps<HeadingProps>({ size: 'md', sx: tourStyles.title }, propsTitle, {\n id: titleId,\n })}\n >\n {currentStep.title}\n </H2>\n {currentStep.content != null && (\n <div\n {...mergeAttrs<'div'>({ sx: tourStyles.content }, propsContent, {\n id: contentId,\n })}\n >\n {currentStep.content}\n </div>\n )}\n <Flex\n {...mergePartProps<FlexProps>(\n { align: 'center', gap: 'sm', wrap: true, sx: tourStyles.footer },\n propsFooter,\n )}\n >\n <Text\n {...mergePartProps<TextProps>(\n { size: 'sm', tone: 'muted', sx: tourStyles.progress },\n propsProgress,\n )}\n >\n {lang.tourProgress(current + 1, steps.length)}\n </Text>\n {!last() && (\n <Button\n {...mergePartProps<ButtonProps>(\n { variant: 'ghost', tone: 'neutral', size: 'sm' },\n propsSkip,\n { onClick: (event: MouseEvent) => close('close', event) },\n )}\n >\n {lang.tourSkip}\n </Button>\n )}\n {current > 0 && (\n <Button\n {...mergePartProps<ButtonProps>(\n { variant: 'outline', size: 'sm' },\n propsPrevious,\n { onClick: () => current-- },\n )}\n >\n {lang.tourPrevious}\n </Button>\n )}\n <Button\n {...mergePartProps<ButtonProps>({ size: 'sm' }, propsNext, { onClick: next })}\n >\n {last() ? lang.tourFinish : lang.tourNext}\n </Button>\n </Flex>\n </div>\n )}\n </dialog>\n );\n };\n },\n { name: 'Tour' },\n);\n"],"mappings":"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"}
|
|
@@ -7,6 +7,8 @@ export interface DetachedDialogHost {
|
|
|
7
7
|
/** 对话框关上了(原生 close 事件里调用):等离场过渡播完卸载 */
|
|
8
8
|
closed: () => void;
|
|
9
9
|
}
|
|
10
|
+
/** 在 setup 里调用,返回 renderDetached(content, container):渲染到组件树之外的元素里,调用的组件卸载时一起卸载 */
|
|
11
|
+
export declare function useDetachedRender(): (content: VNodeChild | null, container: Element) => void;
|
|
10
12
|
/** 在 setup 里调用,返回 mount(content):把对话框渲染到组件树之外 */
|
|
11
13
|
export declare function useDetachedDialog(): (content: (host: DetachedDialogHost) => VNodeChild) => void;
|
|
12
14
|
/** 命令式浮层(useDialog、useDrawer)的选项:浮层的属性(_open 由句柄管理),content 是正文 */
|
package/dist/overlay/detached.js
CHANGED
|
@@ -1,55 +1,86 @@
|
|
|
1
1
|
import { ConfigContext, useConfig } from "../config/context.js";
|
|
2
2
|
import { afterTransitions } from "../utils/style/transitions.js";
|
|
3
3
|
import { ConfigScope } from "../config/Provider.js";
|
|
4
|
-
import { createVNode, getCurrentInstance,
|
|
5
|
-
import { _component } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
4
|
+
import { createVNode, getCurrentInstance, getCurrentScope, h, onScopeDispose, render, shallowRef } from "vue";
|
|
5
|
+
import { _component, readonlyProp } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
6
6
|
//#region src/overlay/detached.tsx
|
|
7
|
-
/**
|
|
8
|
-
* 命令式的对话框(useConfirm、useFormDialog、useDialog、useDrawer)共用的挂载:对话框渲染到 body 末尾、组件树之外,
|
|
9
|
-
* 沿用调用处的配置(主题、语言、默认值,经 ConfigScope)与应用上下文(全局组件);关上后等离场过渡播完再卸载。
|
|
10
|
-
*
|
|
11
|
-
* const mount = useDetachedDialog(); // setup 里
|
|
12
|
-
* mount((host) => <Dialog _open={host.open} onClose={host.closed}>…</Dialog>);
|
|
13
|
-
*
|
|
14
|
-
* useDetachedOverlay 在它上面加一层句柄(close、update、closed),useDialog、useDrawer 用它。
|
|
15
|
-
*/
|
|
16
7
|
/** 交给对话框内容的控制:开着没有、关上了 */
|
|
17
|
-
|
|
18
|
-
|
|
8
|
+
/** 组件树之外的根:提供调用处的配置,内容包在 ConfigScope 里(主题变量、语言) */
|
|
9
|
+
var DetachedRoot = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
10
|
+
const config = readonlyProp(_props, "config");
|
|
11
|
+
const content = readonlyProp(_props, "content");
|
|
12
|
+
ConfigContext.provide(config.value);
|
|
13
|
+
return () => createVNode(ConfigScope, { "key": "__site0" }, {
|
|
14
|
+
default: () => [content.value()],
|
|
15
|
+
$stable: true
|
|
16
|
+
});
|
|
17
|
+
}, {
|
|
18
|
+
name: "DetachedRoot",
|
|
19
|
+
props: ["config", "content"]
|
|
20
|
+
});
|
|
21
|
+
/** 调用处的配置与应用上下文(在 setup 里取),交给组件树之外的根 */
|
|
22
|
+
function captureContext() {
|
|
23
|
+
return {
|
|
24
|
+
config: useConfig(),
|
|
25
|
+
appContext: getCurrentInstance()?.appContext ?? null
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function renderInto(container, { config, appContext }, content) {
|
|
29
|
+
const vnode = h(DetachedRoot, {
|
|
30
|
+
config,
|
|
31
|
+
content
|
|
32
|
+
});
|
|
33
|
+
vnode.appContext = appContext;
|
|
34
|
+
render(vnode, container);
|
|
35
|
+
}
|
|
36
|
+
/** 在 setup 里调用,返回 renderDetached(content, container):渲染到组件树之外的元素里,调用的组件卸载时一起卸载 */
|
|
37
|
+
function useDetachedRender() {
|
|
38
|
+
const context = captureContext();
|
|
39
|
+
const containers = /* @__PURE__ */ new Set();
|
|
40
|
+
if (getCurrentScope()) onScopeDispose(() => {
|
|
41
|
+
for (const container of containers) render(null, container);
|
|
42
|
+
containers.clear();
|
|
43
|
+
});
|
|
44
|
+
return (content, container) => {
|
|
45
|
+
if (content == null) {
|
|
46
|
+
containers.delete(container);
|
|
47
|
+
render(null, container);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
containers.add(container);
|
|
51
|
+
renderInto(container, context, () => content);
|
|
52
|
+
};
|
|
19
53
|
}
|
|
20
54
|
/** 在 setup 里调用,返回 mount(content):把对话框渲染到组件树之外 */
|
|
21
55
|
function useDetachedDialog() {
|
|
22
|
-
const
|
|
23
|
-
|
|
56
|
+
const context = captureContext();
|
|
57
|
+
/** 开着的对话框:调用的组件卸载时关上 */
|
|
58
|
+
const live = /* @__PURE__ */ new Set();
|
|
59
|
+
if (getCurrentScope()) onScopeDispose(() => {
|
|
60
|
+
for (const host of live) host.open = false;
|
|
61
|
+
});
|
|
24
62
|
return (content) => {
|
|
25
63
|
const container = document.createElement("div");
|
|
26
64
|
document.body.append(container);
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
return () => {
|
|
47
|
-
let _slot;
|
|
48
|
-
return createVNode(ConfigScope, null, _isSlot(_slot = content(host)) ? _slot : { default: () => [_slot] });
|
|
49
|
-
};
|
|
50
|
-
}, { name: "DetachedDialog" }));
|
|
51
|
-
vnode.appContext = appContext;
|
|
52
|
-
render(vnode, container);
|
|
65
|
+
const open = shallowRef(true);
|
|
66
|
+
const host = {
|
|
67
|
+
get open() {
|
|
68
|
+
return open.value;
|
|
69
|
+
},
|
|
70
|
+
set open(value) {
|
|
71
|
+
open.value = value;
|
|
72
|
+
},
|
|
73
|
+
closed() {
|
|
74
|
+
live.delete(host);
|
|
75
|
+
const dialog = container.querySelector("dialog");
|
|
76
|
+
(dialog ? afterTransitions(dialog) : Promise.resolve()).then(() => {
|
|
77
|
+
render(null, container);
|
|
78
|
+
container.remove();
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
live.add(host);
|
|
83
|
+
renderInto(container, context, () => content(host));
|
|
53
84
|
};
|
|
54
85
|
}
|
|
55
86
|
/** 命令式浮层(useDialog、useDrawer)的选项:浮层的属性(_open 由句柄管理),content 是正文 */
|
|
@@ -104,6 +135,6 @@ function useDetachedOverlay(renderOverlay) {
|
|
|
104
135
|
};
|
|
105
136
|
}
|
|
106
137
|
//#endregion
|
|
107
|
-
export { useDetachedDialog, useDetachedOverlay };
|
|
138
|
+
export { useDetachedDialog, useDetachedOverlay, useDetachedRender };
|
|
108
139
|
|
|
109
140
|
//# sourceMappingURL=detached.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"detached.js","names":["
|
|
1
|
+
{"version":3,"file":"detached.js","names":["_vtp_readonlyProp","_createVNode"],"sources":["../../src/overlay/detached.tsx"],"sourcesContent":["/**\n * 渲染到组件树之外:沿用调用处的配置(主题、语言、默认值,经 ConfigScope)与应用上下文(全局组件),\n * 生命周期跟着调用的组件——它卸载时一起收掉,不留孤儿。\n *\n * const renderDetached = useDetachedRender(); // setup 里;第三方库管的元素(图表的提示框)\n * renderDetached(<Tip />, element); // 再调一次换内容,传 null 卸载;组件卸载时自动卸载\n *\n * const mount = useDetachedDialog(); // setup 里;命令式的对话框(useConfirm、useFormDialog)\n * mount((host) => <Dialog _open={host.open} onClose={host.closed}>…</Dialog>);\n *\n * 对话框渲染到 body 末尾,关上后等离场过渡播完再卸载;调用的组件卸载时(例如切换了路由)把开着的对话框关上,\n * 照常走关闭的流程,等着结果的 `await confirm()` 也拿到结果。\n * useDetachedOverlay 在 useDetachedDialog 上面加一层句柄(close、update、closed),useDialog、useDrawer 用它。\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport {\n getCurrentInstance,\n getCurrentScope,\n h,\n onScopeDispose,\n render,\n shallowRef,\n type AppContext,\n type VNodeChild,\n} from 'vue';\nimport { ConfigContext, useConfig, type UiConfig } from '../config/context';\nimport { ConfigScope } from '../config/Provider';\nimport type { OpenChangeProps, OpenChangeReasons } from '../utils/dom/open-change';\nimport { afterTransitions } from '../utils/style/transitions';\n\n/** 交给对话框内容的控制:开着没有、关上了 */\nexport interface DetachedDialogHost {\n /** 对话框开着:对话框写 _open={host.open},改成 false 就关上 */\n open: boolean;\n /** 对话框关上了(原生 close 事件里调用):等离场过渡播完卸载 */\n closed: () => void;\n}\n\n/** 组件树之外的根:提供调用处的配置,内容包在 ConfigScope 里(主题变量、语言) */\nconst DetachedRoot = _component(\n ({ config, content }: { config: UiConfig; content: () => VNodeChild }) => {\n ConfigContext.provide(config);\n return () => <ConfigScope>{content()}</ConfigScope>;\n },\n);\n\n/** 调用处的配置与应用上下文(在 setup 里取),交给组件树之外的根 */\nfunction captureContext() {\n return { config: useConfig(), appContext: getCurrentInstance()?.appContext ?? null };\n}\n\nfunction renderInto(\n container: Element,\n { config, appContext }: { config: UiConfig; appContext: AppContext | null },\n content: () => VNodeChild,\n) {\n const vnode = h(DetachedRoot, { config, content });\n vnode.appContext = appContext;\n render(vnode, container);\n}\n\n/** 在 setup 里调用,返回 renderDetached(content, container):渲染到组件树之外的元素里,调用的组件卸载时一起卸载 */\nexport function useDetachedRender(): (content: VNodeChild | null, container: Element) => void {\n const context = captureContext();\n const containers = new Set<Element>();\n if (getCurrentScope()) {\n onScopeDispose(() => {\n for (const container of containers) render(null, container);\n containers.clear();\n });\n }\n return (content, container) => {\n if (content == null) {\n containers.delete(container);\n render(null, container);\n return;\n }\n containers.add(container);\n renderInto(container, context, () => content);\n };\n}\n\n/** 在 setup 里调用,返回 mount(content):把对话框渲染到组件树之外 */\nexport function useDetachedDialog(): (content: (host: DetachedDialogHost) => VNodeChild) => void {\n const context = captureContext();\n /** 开着的对话框:调用的组件卸载时关上 */\n const live = new Set<DetachedDialogHost>();\n if (getCurrentScope()) {\n onScopeDispose(() => {\n for (const host of live) host.open = false;\n });\n }\n\n return (content) => {\n const container = document.createElement('div');\n document.body.append(container);\n const open = shallowRef(true);\n const host: DetachedDialogHost = {\n get open() {\n return open.value;\n },\n set open(value) {\n open.value = value;\n },\n closed() {\n live.delete(host);\n const dialog = container.querySelector('dialog');\n void (dialog ? afterTransitions(dialog) : Promise.resolve()).then(() => {\n render(null, container);\n container.remove();\n });\n },\n };\n live.add(host);\n renderInto(container, context, () => content(host));\n };\n}\n\n/** 命令式浮层(useDialog、useDrawer)的选项:浮层的属性(_open 由句柄管理),content 是正文 */\nexport type DetachedOverlayOptions<P> = Omit<P, '_open' | 'children'> & {\n /** 正文;给函数时拿到句柄,正文里的按钮可以自己关上、改标题 */\n content?: VNodeChild | ((handle: DetachedOverlayHandle<P>) => VNodeChild);\n};\n\n/** useDialog().open、useDrawer().open 返回的句柄;方法不依赖 this,可以直接传出去(onSaved={handle.close}) */\nexport interface DetachedOverlayHandle<P> {\n /** 关上(onOpenChange 的原因是 imperative);已经关上时什么也不做 */\n readonly close: () => void;\n /** 改属性(标题、宽度、确认按钮的文字、正文……),没写的保持不变 */\n readonly update: (patch: Partial<DetachedOverlayOptions<P>>) => void;\n /** 关上之后 resolve,得到关上的原因:确认是 confirm,Esc、遮罩、关闭按钮、close() 各是 escape、outside、close、imperative */\n readonly closed: Promise<keyof OpenChangeReasons>;\n}\n\n/** 原生 dialog 的 close 事件 */\ntype DialogCloseEvent = Event & { currentTarget: HTMLDialogElement };\n\n/** 浮层要支持的:onOpenChange 报告原因,onClose 落到原生 dialog 上 */\ninterface DetachedOverlayProps extends OpenChangeProps {\n onClose?: (event: DialogCloseEvent) => void;\n}\n\n/**\n * 命令式浮层:在 setup 里调用,返回 open(options)。renderOverlay 把属性、开关与正文交给具体的浮层(Dialog、Drawer):\n *\n * useDetachedOverlay<DialogProps>((props, host, children) => <Dialog {...props} _open={host.open}>{children}</Dialog>);\n */\nexport function useDetachedOverlay<P extends DetachedOverlayProps>(\n renderOverlay: (\n props: Omit<P, '_open' | 'children'>,\n host: DetachedDialogHost,\n children: VNodeChild,\n ) => VNodeChild,\n): (options: DetachedOverlayOptions<P>) => DetachedOverlayHandle<P> {\n const mount = useDetachedDialog();\n return (options) => {\n const state = shallowRef(options);\n let host: DetachedDialogHost | undefined;\n let reason: keyof OpenChangeReasons = 'imperative';\n let resolveClosed!: (reason: keyof OpenChangeReasons) => void;\n const handle: DetachedOverlayHandle<P> = {\n close: () => {\n if (!host?.open) return;\n reason = 'imperative';\n host.open = false;\n state.value.onOpenChange?.(false, { reason });\n },\n update: (patch) => {\n state.value = { ...state.value, ...patch };\n },\n closed: new Promise((resolve) => (resolveClosed = resolve)),\n };\n mount((current) => {\n host = current;\n const { content, onOpenChange, onClose, ...props } = state.value;\n const overlayProps = {\n ...props,\n onOpenChange: (open, details) => {\n if (!open) reason = details.reason;\n onOpenChange?.(open, details);\n },\n onClose: (event: DialogCloseEvent) => {\n onClose?.(event);\n // 原生 close 事件先到这里、后到浮层自己(它在那里报告原因):等它报告完再 resolve\n queueMicrotask(() => resolveClosed(reason));\n current.closed();\n },\n } as Omit<P, '_open' | 'children'> & DetachedOverlayProps;\n return renderOverlay(\n overlayProps,\n current,\n typeof content === 'function' ? content(handle) : content,\n );\n });\n options.onOpenChange?.(true, { reason: 'imperative' });\n return handle;\n };\n}\n"],"mappings":";;;;;;;;AAuCA,IAAM,eAAe,0BACnB,QAAA,SAA0E;CAAA,MAAA,SAAAA,aAAA,QAAA,QAAA;CAAA,MAAA,UAAAA,aAAA,QAAA,SAAA;CACxE,cAAc,QAAO,OAAA,KAAO;CAC5B,aAAOC,YAAA,aAAA,EAAA,OAAA,UAAA,GAAA;EAAA,eAAA,CAAoB,QAAA,MAAQ,CAAC;EAAA,SAAA;CAAA,CAAA;AACtC,GAAC;CAAA,MAAA;CAAA,OAAA,CAAA,UAAA,SAAA;AAAA,CACH;;AAGA,SAAS,iBAAiB;CACxB,OAAO;EAAE,QAAQ,UAAU;EAAG,YAAY,mBAAmB,CAAC,EAAE,cAAc;CAAK;AACrF;AAEA,SAAS,WACP,WACA,EAAE,QAAQ,cACV,SACA;CACA,MAAM,QAAQ,EAAE,cAAc;EAAE;EAAQ;CAAQ,CAAC;CACjD,MAAM,aAAa;CACnB,OAAO,OAAO,SAAS;AACzB;;AAGA,SAAgB,oBAA8E;CAC5F,MAAM,UAAU,eAAe;CAC/B,MAAM,6BAAa,IAAI,IAAa;CACpC,IAAI,gBAAgB,GAClB,qBAAqB;EACnB,KAAK,MAAM,aAAa,YAAY,OAAO,MAAM,SAAS;EAC1D,WAAW,MAAM;CACnB,CAAC;CAEH,QAAQ,SAAS,cAAc;EAC7B,IAAI,WAAW,MAAM;GACnB,WAAW,OAAO,SAAS;GAC3B,OAAO,MAAM,SAAS;GACtB;EACF;EACA,WAAW,IAAI,SAAS;EACxB,WAAW,WAAW,eAAe,OAAO;CAC9C;AACF;;AAGA,SAAgB,oBAAiF;CAC/F,MAAM,UAAU,eAAe;;CAE/B,MAAM,uBAAO,IAAI,IAAwB;CACzC,IAAI,gBAAgB,GAClB,qBAAqB;EACnB,KAAK,MAAM,QAAQ,MAAM,KAAK,OAAO;CACvC,CAAC;CAGH,QAAQ,YAAY;EAClB,MAAM,YAAY,SAAS,cAAc,KAAK;EAC9C,SAAS,KAAK,OAAO,SAAS;EAC9B,MAAM,OAAO,WAAW,IAAI;EAC5B,MAAM,OAA2B;GAC/B,IAAI,OAAO;IACT,OAAO,KAAK;GACd;GACA,IAAI,KAAK,OAAO;IACd,KAAK,QAAQ;GACf;GACA,SAAS;IACP,KAAK,OAAO,IAAI;IAChB,MAAM,SAAS,UAAU,cAAc,QAAQ;IAC/C,CAAM,SAAS,iBAAiB,MAAM,IAAI,QAAQ,QAAQ,EAAA,CAAG,WAAW;KACtE,OAAO,MAAM,SAAS;KACtB,UAAU,OAAO;IACnB,CAAC;GACH;EACF;EACA,KAAK,IAAI,IAAI;EACb,WAAW,WAAW,eAAe,QAAQ,IAAI,CAAC;CACpD;AACF;;;;;;;;;;AA+BA,SAAgB,mBACd,eAKkE;CAClE,MAAM,QAAQ,kBAAkB;CAChC,QAAQ,YAAY;EAClB,MAAM,QAAQ,WAAW,OAAO;EAChC,IAAI;EACJ,IAAI,SAAkC;EACtC,IAAI;EACJ,MAAM,SAAmC;GACvC,aAAa;IACX,IAAI,CAAC,MAAM,MAAM;IACjB,SAAS;IACT,KAAK,OAAO;IACZ,MAAM,MAAM,eAAe,OAAO,EAAE,OAAO,CAAC;GAC9C;GACA,SAAS,UAAU;IACjB,MAAM,QAAQ;KAAE,GAAG,MAAM;KAAO,GAAG;IAAM;GAC3C;GACA,QAAQ,IAAI,SAAS,YAAa,gBAAgB,OAAQ;EAC5D;EACA,OAAO,YAAY;GACjB,OAAO;GACP,MAAM,EAAE,SAAS,cAAc,SAAS,GAAG,UAAU,MAAM;GAc3D,OAAO,cACL;IAbA,GAAG;IACH,eAAe,MAAM,YAAY;KAC/B,IAAI,CAAC,MAAM,SAAS,QAAQ;KAC5B,eAAe,MAAM,OAAO;IAC9B;IACA,UAAU,UAA4B;KACpC,UAAU,KAAK;KAEf,qBAAqB,cAAc,MAAM,CAAC;KAC1C,QAAQ,OAAO;IACjB;GAGA,GACA,SACA,OAAO,YAAY,aAAa,QAAQ,MAAM,IAAI,OACpD;EACF,CAAC;EACD,QAAQ,eAAe,MAAM,EAAE,QAAQ,aAAa,CAAC;EACrD,OAAO;CACT;AACF"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { t } from "../config/theme/tokens.js";
|
|
2
2
|
import { hasContent, mergeAttrs } from "../utils/props.js";
|
|
3
3
|
import { useIntersectionObserver } from "../utils/dom/intersection.js";
|
|
4
|
-
import { createVNode,
|
|
5
|
-
import { _component, childrenProp, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
4
|
+
import { createVNode, ref, shallowRef } from "vue";
|
|
5
|
+
import { _component, childrenProp, mergeJsxProps, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
6
6
|
import * as css from "@zerodep-dev/vue-tsx-pro-tsx/styles";
|
|
7
7
|
//#region src/patterns/ActionBar.tsx
|
|
8
8
|
/** 未声明的属性落到外层 div 上 */
|
|
@@ -55,7 +55,7 @@ var ActionBar = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
55
55
|
let root = shallowRef(null);
|
|
56
56
|
let stuck = ref(false);
|
|
57
57
|
useIntersectionObserver(() => sticky.value ? root.value : null, ([entry]) => stuck.value = !!entry && entry.isIntersecting && entry.intersectionRatio < 1, () => ({ threshold: [0, 1] }));
|
|
58
|
-
return () => createVNode("div",
|
|
58
|
+
return () => createVNode("div", mergeJsxProps(mergeAttrs({ sx: [
|
|
59
59
|
actionBarStyles.root,
|
|
60
60
|
sticky.value && actionBarStyles.sticky,
|
|
61
61
|
sticky.value && stuck.value && actionBarStyles.stuck
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ActionBar.js","names":["_vtp_readonlyProp","_vtp_childrenProp","_vtp_restProps","_vue_shallowRef","_vue_ref","_createVNode","_mergeProps"],"sources":["../../src/patterns/ActionBar.tsx"],"sourcesContent":["/**\n * 吸底的操作栏(div):长表单、详情页底部的“取消 / 保存草稿 / 提交”,滚动时一直贴在滚动区域的底边,不用滚到底才能提交。\n *\n * <ActionBar start={form.dirty ? '有未保存的改动' : undefined}>\n * <Button variant=\"ghost\" tone=\"neutral\">取消</Button>\n * <Button type=\"submit\" loading={form.submitting}>提交</Button>\n * </ActionBar>\n *\n * 放在要跟着滚动的内容的最后(表单里、卡片里、页面主区里都行):position: sticky 贴着最近的滚动容器的底边。\n * 下面还有被盖住的内容时(贴在底边、还没滚到它原本的位置)带阴影、data-stuck,提示还能往下滚;滚到底时与内容平齐。\n * start 是起始边的说明(未保存的改动、校验汇总),children 是结束边的按钮。\n *\n * 部件:Root(div)、Start(div)、Actions(div)。\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 { useIntersectionObserver } from '../utils/dom/intersection';\nimport { hasContent, mergeAttrs, type PartProps } from '../utils/props';\nimport { t } from '../config/theme/tokens';\n\n/** 未声明的属性落到外层 div 上 */\nexport interface ActionBarProps extends PartProps<'div'> {\n /** 起始边的内容:未保存的改动、已选几项、校验汇总 */\n start?: VNodeChild;\n /**\n * 贴在滚动区域的底边,默认 true;false 时是普通的一行(如对话框里已经固定的底栏)\n * @default true\n */\n sticky?: boolean;\n /** 起始边内容的部件属性 */\n propsStart?: PartProps<'div'>;\n /** 按钮区的部件属性 */\n propsActions?: PartProps<'div'>;\n /** 按钮:靠结束边排开 */\n children?: VNodeChild;\n}\n\n/** ActionBar 的样式:root、sticky、stuck、start、actions */\nexport const actionBarStyles = css.create({\n root: {\n display: 'flex',\n alignItems: 'center',\n flexWrap: 'wrap',\n gap: '_md',\n paddingBlock: '_md',\n paddingInline: '_lg',\n borderBlockStartWidth: '_xs',\n borderBlockStartStyle: 'solid',\n borderBlockStartColor: '_borderMuted',\n backgroundColor: '_surface',\n transition: `box-shadow ${t.durations.md} ${t.easings.standard}`,\n },\n // 往下多出一条线宽:贴着底边时有 1px 在滚动区域外,交叉比例小于 1,据此判断“盖着下面的内容”\n sticky: { position: 'sticky', insetBlockEnd: `calc(-1 * ${t.borderWidths.xs})`, zIndex: 1 },\n // 盖着下面的内容:阴影提示还能往下滚\n stuck: { boxShadow: '_md', borderBlockStartColor: '_border' },\n // 说明文字比所在文字小一档(0.93em ≈ 默认字号下的 sm)\n start: { flexGrow: 1, minWidth: 0, color: '_textMuted', fontSize: '0.93em' },\n actions: {\n display: 'flex',\n alignItems: 'center',\n flexWrap: 'wrap',\n gap: '_sm',\n marginInlineStart: 'auto',\n },\n});\n\n/** 吸底的操作栏:起始边的说明、结束边的按钮;盖着下面的内容时带阴影 */\nexport const ActionBar = _component(\n ({ start, sticky = true, propsStart, propsActions, children, ...rest }: ActionBarProps) => {\n let root = _state.raw<HTMLElement | null>(null);\n let stuck = _state(false);\n\n // 贴着底边时栏有一条线宽露在滚动区域外(见 sticky 样式):没有完整可见就是盖着下面的内容\n useIntersectionObserver(\n () => (sticky ? root : null),\n ([entry]) => (stuck = !!entry && entry.isIntersecting && entry.intersectionRatio < 1),\n () => ({ threshold: [0, 1] }),\n );\n\n return () => (\n <div\n {...mergeAttrs<'div'>(\n {\n sx: [\n actionBarStyles.root,\n sticky && actionBarStyles.sticky,\n sticky && stuck && actionBarStyles.stuck,\n ],\n },\n rest,\n { ref: _toRef(root) },\n )}\n data-stuck={(sticky && stuck) || undefined}\n >\n {hasContent(start) && (\n <div {...mergeAttrs<'div'>({ sx: actionBarStyles.start }, propsStart)}>{start}</div>\n )}\n <div {...mergeAttrs<'div'>({ sx: actionBarStyles.actions }, propsActions)}>{children}</div>\n </div>\n );\n },\n);\n"],"mappings":";;;;;;;;;AAuCA,IAAa,kBAAkB,IAAI,OAAO;CACxC,MAAM;EACJ,SAAS;EACT,YAAY;EACZ,UAAU;EACV,KAAK;EACL,cAAc;EACd,eAAe;EACf,uBAAuB;EACvB,uBAAuB;EACvB,uBAAuB;EACvB,iBAAiB;EACjB,YAAY,cAAc,EAAE,UAAU,GAAE,GAAI,EAAE,QAAQ;CACxD;CAEA,QAAQ;EAAE,UAAU;EAAU,eAAe,aAAa,EAAE,aAAa,GAAE;EAAK,QAAQ;CAAE;CAE1F,OAAO;EAAE,WAAW;EAAO,uBAAuB;CAAU;CAE5D,OAAO;EAAE,UAAU;EAAG,UAAU;EAAG,OAAO;EAAc,UAAU;CAAS;CAC3E,SAAS;EACP,SAAS;EACT,YAAY;EACZ,UAAU;EACV,KAAK;EACL,mBAAmB;CACrB;AACF,CAAC;;AAGD,IAAa,YAAY,0BACvB,QAAA,SAA2F;CAAA,MAAA,QAAAA,aAAA,QAAA,OAAA;CAAA,MAAA,SAAAA,aAAA,QAAA,UAAxE,IAAI;CAAA,MAAA,aAAAA,aAAA,QAAA,YAAA;CAAA,MAAA,eAAAA,aAAA,QAAA,cAAA;CAAA,MAAA,WAAAC,aAAA,MAAA;CAAA,MAAyC,OAAIC,UAAA,QAAA,MAAA,KAAA;CAClE,IAAI,OAAIC,WAAkC,IAAI;CAC9C,IAAI,QAAKC,IAAU,KAAK;CAGxB,8BACS,OAAA,QAAA,KAAA,QAAgB,OACtB,CAAC,WAAY,MAAA,QAAQ,CAAC,CAAC,SAAS,MAAM,kBAAkB,MAAM,oBAAoB,UAC5E,EAAE,WAAW,CAAC,GAAG,CAAC,EAAE,EAC7B;CAEA,aAAOC,YAAA,OAAAC,
|
|
1
|
+
{"version":3,"file":"ActionBar.js","names":["_vtp_readonlyProp","_vtp_childrenProp","_vtp_restProps","_vue_shallowRef","_vue_ref","_createVNode","_mergeProps"],"sources":["../../src/patterns/ActionBar.tsx"],"sourcesContent":["/**\n * 吸底的操作栏(div):长表单、详情页底部的“取消 / 保存草稿 / 提交”,滚动时一直贴在滚动区域的底边,不用滚到底才能提交。\n *\n * <ActionBar start={form.dirty ? '有未保存的改动' : undefined}>\n * <Button variant=\"ghost\" tone=\"neutral\">取消</Button>\n * <Button type=\"submit\" loading={form.submitting}>提交</Button>\n * </ActionBar>\n *\n * 放在要跟着滚动的内容的最后(表单里、卡片里、页面主区里都行):position: sticky 贴着最近的滚动容器的底边。\n * 下面还有被盖住的内容时(贴在底边、还没滚到它原本的位置)带阴影、data-stuck,提示还能往下滚;滚到底时与内容平齐。\n * start 是起始边的说明(未保存的改动、校验汇总),children 是结束边的按钮。\n *\n * 部件:Root(div)、Start(div)、Actions(div)。\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 { useIntersectionObserver } from '../utils/dom/intersection';\nimport { hasContent, mergeAttrs, type PartProps } from '../utils/props';\nimport { t } from '../config/theme/tokens';\n\n/** 未声明的属性落到外层 div 上 */\nexport interface ActionBarProps extends PartProps<'div'> {\n /** 起始边的内容:未保存的改动、已选几项、校验汇总 */\n start?: VNodeChild;\n /**\n * 贴在滚动区域的底边,默认 true;false 时是普通的一行(如对话框里已经固定的底栏)\n * @default true\n */\n sticky?: boolean;\n /** 起始边内容的部件属性 */\n propsStart?: PartProps<'div'>;\n /** 按钮区的部件属性 */\n propsActions?: PartProps<'div'>;\n /** 按钮:靠结束边排开 */\n children?: VNodeChild;\n}\n\n/** ActionBar 的样式:root、sticky、stuck、start、actions */\nexport const actionBarStyles = css.create({\n root: {\n display: 'flex',\n alignItems: 'center',\n flexWrap: 'wrap',\n gap: '_md',\n paddingBlock: '_md',\n paddingInline: '_lg',\n borderBlockStartWidth: '_xs',\n borderBlockStartStyle: 'solid',\n borderBlockStartColor: '_borderMuted',\n backgroundColor: '_surface',\n transition: `box-shadow ${t.durations.md} ${t.easings.standard}`,\n },\n // 往下多出一条线宽:贴着底边时有 1px 在滚动区域外,交叉比例小于 1,据此判断“盖着下面的内容”\n sticky: { position: 'sticky', insetBlockEnd: `calc(-1 * ${t.borderWidths.xs})`, zIndex: 1 },\n // 盖着下面的内容:阴影提示还能往下滚\n stuck: { boxShadow: '_md', borderBlockStartColor: '_border' },\n // 说明文字比所在文字小一档(0.93em ≈ 默认字号下的 sm)\n start: { flexGrow: 1, minWidth: 0, color: '_textMuted', fontSize: '0.93em' },\n actions: {\n display: 'flex',\n alignItems: 'center',\n flexWrap: 'wrap',\n gap: '_sm',\n marginInlineStart: 'auto',\n },\n});\n\n/** 吸底的操作栏:起始边的说明、结束边的按钮;盖着下面的内容时带阴影 */\nexport const ActionBar = _component(\n ({ start, sticky = true, propsStart, propsActions, children, ...rest }: ActionBarProps) => {\n let root = _state.raw<HTMLElement | null>(null);\n let stuck = _state(false);\n\n // 贴着底边时栏有一条线宽露在滚动区域外(见 sticky 样式):没有完整可见就是盖着下面的内容\n useIntersectionObserver(\n () => (sticky ? root : null),\n ([entry]) => (stuck = !!entry && entry.isIntersecting && entry.intersectionRatio < 1),\n () => ({ threshold: [0, 1] }),\n );\n\n return () => (\n <div\n {...mergeAttrs<'div'>(\n {\n sx: [\n actionBarStyles.root,\n sticky && actionBarStyles.sticky,\n sticky && stuck && actionBarStyles.stuck,\n ],\n },\n rest,\n { ref: _toRef(root) },\n )}\n data-stuck={(sticky && stuck) || undefined}\n >\n {hasContent(start) && (\n <div {...mergeAttrs<'div'>({ sx: actionBarStyles.start }, propsStart)}>{start}</div>\n )}\n <div {...mergeAttrs<'div'>({ sx: actionBarStyles.actions }, propsActions)}>{children}</div>\n </div>\n );\n },\n);\n"],"mappings":";;;;;;;;;AAuCA,IAAa,kBAAkB,IAAI,OAAO;CACxC,MAAM;EACJ,SAAS;EACT,YAAY;EACZ,UAAU;EACV,KAAK;EACL,cAAc;EACd,eAAe;EACf,uBAAuB;EACvB,uBAAuB;EACvB,uBAAuB;EACvB,iBAAiB;EACjB,YAAY,cAAc,EAAE,UAAU,GAAE,GAAI,EAAE,QAAQ;CACxD;CAEA,QAAQ;EAAE,UAAU;EAAU,eAAe,aAAa,EAAE,aAAa,GAAE;EAAK,QAAQ;CAAE;CAE1F,OAAO;EAAE,WAAW;EAAO,uBAAuB;CAAU;CAE5D,OAAO;EAAE,UAAU;EAAG,UAAU;EAAG,OAAO;EAAc,UAAU;CAAS;CAC3E,SAAS;EACP,SAAS;EACT,YAAY;EACZ,UAAU;EACV,KAAK;EACL,mBAAmB;CACrB;AACF,CAAC;;AAGD,IAAa,YAAY,0BACvB,QAAA,SAA2F;CAAA,MAAA,QAAAA,aAAA,QAAA,OAAA;CAAA,MAAA,SAAAA,aAAA,QAAA,UAAxE,IAAI;CAAA,MAAA,aAAAA,aAAA,QAAA,YAAA;CAAA,MAAA,eAAAA,aAAA,QAAA,cAAA;CAAA,MAAA,WAAAC,aAAA,MAAA;CAAA,MAAyC,OAAIC,UAAA,QAAA,MAAA,KAAA;CAClE,IAAI,OAAIC,WAAkC,IAAI;CAC9C,IAAI,QAAKC,IAAU,KAAK;CAGxB,8BACS,OAAA,QAAA,KAAA,QAAgB,OACtB,CAAC,WAAY,MAAA,QAAQ,CAAC,CAAC,SAAS,MAAM,kBAAkB,MAAM,oBAAoB,UAC5E,EAAE,WAAW,CAAC,GAAG,CAAC,EAAE,EAC7B;CAEA,aAAOC,YAAA,OAAAC,cAEC,WACF,EACE,IAAI;EACF,gBAAgB;EAChB,OAAA,SAAU,gBAAgB;EAC1B,OAAA,SAAA,MAAA,SAAmB,gBAAgB;CAAK,EAE5C,GACA,MACA,EAAE,KAAG,KAAe,CACtB,GAAC,EAAA,cACY,OAAA,SAAA,MAAA,SAAoB,KAAA,EAAS,CAAA,GAAA,CAEzC,WAAU,MAAA,KAAM,KAACD,YAAA,OACP,WAAkB,EAAE,IAAI,gBAAgB,MAAM,GAAC,WAAA,KAAY,GAAC,CAAA,MAAA,KAAA,CAAA,GACtEA,YAAA,OACQ,WAAkB,EAAE,IAAI,gBAAgB,QAAQ,GAAC,aAAA,KAAc,GAAC,CAAA,SAAA,KAAA,CAAA,CAAA,CAAA;AAG/E,GAAC;CAAA,MAAA;CAAA,OAAA;EAAA;EAAA;EAAA;EAAA;CAAA;AAAA,CACH"}
|