@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
|
@@ -11,8 +11,8 @@ import { handleListKeydown, toListboxOption, useListState } from "../input/list-
|
|
|
11
11
|
import { controlParts, useControlAppearance } from "../input/control.js";
|
|
12
12
|
import { Dialog } from "./Dialog.js";
|
|
13
13
|
import { ListboxView, activeOptionId } from "../input/Listbox.js";
|
|
14
|
-
import { Fragment, createVNode,
|
|
15
|
-
import { _component, bindable, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
14
|
+
import { Fragment, createVNode, useId, vModelText, watch, withDirectives } from "vue";
|
|
15
|
+
import { _component, bindable, 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 { Search } from "@lucide/icons";
|
|
18
18
|
//#region src/overlay/CommandPalette.tsx
|
|
@@ -179,7 +179,7 @@ var CommandPalette = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
179
179
|
}
|
|
180
180
|
return () => {
|
|
181
181
|
const shell = controlParts(appearance(), {});
|
|
182
|
-
return createVNode(Fragment, null, [renderTrigger(shell), createVNode(Dialog,
|
|
182
|
+
return createVNode(Fragment, null, [renderTrigger(shell), createVNode(Dialog, mergeJsxProps(mergePartProps({
|
|
183
183
|
title: label.value ?? lang.searchLabel,
|
|
184
184
|
sx: commandPaletteStyles.dialog,
|
|
185
185
|
size: "sm",
|
|
@@ -193,7 +193,7 @@ var CommandPalette = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
193
193
|
}), { default: () => [createVNode("div", mergeAttrs({ sx: commandPaletteStyles.search }), [createVNode(Icon, {
|
|
194
194
|
"icon": Search,
|
|
195
195
|
"sx": shell.icon.sx
|
|
196
|
-
}, null), withDirectives(createVNode("input",
|
|
196
|
+
}, null), withDirectives(createVNode("input", mergeJsxProps(mergeAttrs({ sx: [shell.input.sx, commandPaletteStyles.input] }, propsInput.value, {
|
|
197
197
|
role: "combobox",
|
|
198
198
|
type: "search",
|
|
199
199
|
autofocus: true,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CommandPalette.js","names":["_vtp_bindable","_vtp_readonlyProp","_vtp_restProps","_createVNode","_Fragment","_mergeProps","_withDirectives","_vModelText"],"sources":["../../src/overlay/CommandPalette.tsx"],"sourcesContent":["/**\n * 命令面板:Mod+K(macOS 上 ⌘K,其他系统 Ctrl+K)或点触发按钮打开的搜索对话框,边输入边过滤,回车打开。\n * 文档站的站内搜索、后台的“跳转到…”都用它;数据由使用方给,选中后做什么也由使用方决定。\n *\n * <CommandPalette\n * items={pages}\n * toOption={(page) => ({ value: page.path, label: page.title, description: page.summary, group: page.section })}\n * onAction={(page) => router.push(page.path)}\n * />\n *\n * 结构是 Dialog(模态、焦点循环、Esc 关闭)里放一个 combobox 输入框与 ListboxView:\n * 焦点一直在输入框上,↑↓ / Page Up / Page Down 移动高亮(aria-activedescendant),Enter 选中;\n * 数据层与 Select、Combobox 相同(useListState),选项多时虚拟滚动。\n * 远程搜索与 Combobox 一样:_inputValue={keyword} 拿到输入,filter={false} 不再本地过滤,取数据时给 loading。\n */\nimport { Search } from '@lucide/icons';\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport { useId, watch, type VNodeChild } from 'vue';\nimport { ButtonBase, type ButtonBaseProps } from '../base/ButtonBase';\nimport { Icon } from '../base/Icon';\nimport { Kbd } from '../base/Kbd';\nimport { visuallyHiddenStyles } from '../base/VisuallyHidden';\nimport { useLang, useTextMatcher } from '../config/context';\nimport { controlParts, useControlAppearance, type ControlParts } from '../input/control';\nimport {\n ListboxView,\n activeOptionId,\n type ListboxViewProps,\n type OptionRenderState,\n} from '../input/Listbox';\nimport {\n handleListKeydown,\n useListState,\n type ListboxOption,\n type OptionMapping,\n toListboxOption,\n} from '../input/list-state';\nimport { ariaKeyShortcut, formatHotkey, useHotkeys } from '../utils/dom/hotkeys';\nimport { mergeAttrs, mergePartProps, type PartProps } from '../utils/props';\nimport { breakpoints } from '../config/theme/tokens';\nimport { openSetter } from '../utils/dom/open-change';\nimport { Dialog, type DialogProps } from './Dialog';\n\n/** 交给触发器的属性:展开到按钮上 */\nexport interface CommandPaletteTriggerProps {\n /** 打开面板 */\n onClick: (event: MouseEvent) => void;\n /** 告诉读屏这个按钮会打开对话框 */\n 'aria-haspopup': 'dialog';\n /** 面板是否打开 */\n 'aria-expanded': boolean;\n}\n\n/** 与数据类型无关的属性;未声明的属性落到对话框(dialog)上 */\nexport interface CommandPaletteBaseProps<T> extends Omit<\n DialogProps,\n 'open' | 'title' | 'description' | 'footer' | 'children' | 'onSelect'\n> {\n /** 是否打开:父级可以写 _open={x};快捷键、触发按钮、选中、Esc 都会改它 */\n _open?: boolean;\n /** 数据:ListboxOption、字符串,或任意数据加 toOption */\n items: readonly T[];\n /** 执行了一项(回车或点击):面板随即关闭 */\n onAction: (item: T) => void;\n /** 输入框里的文字:父级可以写 _inputValue={x}(远程搜索时据此取数据);每次打开清空 */\n _inputValue?: string;\n /** 过滤:默认按选项文字与说明匹配(忽略大小写与重音);函数自定义;false 不过滤(远程搜索,items 就是结果) */\n filter?: false | ((item: T, query: string) => boolean);\n /** 正在取结果(远程搜索):没有结果时列表里是骨架行,已有结果时顶部显示进度条 */\n loading?: boolean;\n /** 打开面板的快捷键,默认 'Mod+K';false 不注册 */\n hotkey?: string | false;\n /** 输入框的占位文字,默认取语言包(搜索文档) */\n placeholder?: string;\n /** 面板的名称(读屏念的),默认取语言包(搜索) */\n label?: string;\n /** 触发器:`(props) => <IconButton {...props} icon={Search} aria-label=\"搜索\" />`;默认是一个像搜索框的按钮,false 不渲染 */\n trigger?: ((props: CommandPaletteTriggerProps) => VNodeChild) | false;\n /** 自定义选项内容 */\n renderOption?: (item: T, state: OptionRenderState) => VNodeChild;\n /** 没有匹配时显示的内容;默认是 Empty(文案取语言包) */\n empty?: VNodeChild;\n /** 默认触发器的部件属性 */\n propsTrigger?: Partial<ButtonBaseProps>;\n /** 输入框的部件属性 */\n propsInput?: PartProps<'input'>;\n /** 列表的部件属性 */\n propsListbox?: Partial<ListboxViewProps<T>>;\n}\n\n/** 命令面板的属性;items 不是 ListboxOption 或字符串时要给 toOption */\nexport type CommandPaletteProps<T = ListboxOption> = CommandPaletteBaseProps<T> & OptionMapping<T>;\n\n/** CommandPalette 的样式:dialog、search、input、listbox、trigger、triggerText、triggerLabel */\nexport const commandPaletteStyles = css.create({\n // 停在视口上部而不是正中:结果变多变少时输入框不跳\n dialog: { marginBlockStart: '12dvh' },\n search: {\n display: 'flex',\n alignItems: 'center',\n gap: '_sm',\n paddingBlock: '_sm',\n paddingInline: '_md',\n borderBlockEndWidth: '_xs',\n borderBlockEndStyle: 'solid',\n borderBlockEndColor: '_border',\n fontSize: '_lg',\n },\n input: { paddingBlock: '_sm' },\n listbox: { maxHeight: 'min(24rem, 50dvh)' },\n body: { padding: 0 },\n // 窄屏(40rem 以下,sizes.sm)只留放大镜图标,不挤占顶栏\n trigger: {\n gap: '_sm',\n minWidth: { default: null, [breakpoints.sm]: '12rem' },\n color: '_textMuted',\n cursor: 'pointer',\n },\n triggerText: { display: { default: 'none', [breakpoints.sm]: 'initial' } },\n // 按钮的名称来自这段文字(与看得见的一致,WCAG 2.5.3):窄屏只留图标时视觉上隐藏,读屏仍念得到\n triggerLabel: {\n flexGrow: 1,\n textAlign: 'start',\n whiteSpace: 'nowrap',\n position: { default: 'absolute', [breakpoints.sm]: 'static' },\n width: { default: 1, [breakpoints.sm]: 'auto' },\n height: { default: 1, [breakpoints.sm]: 'auto' },\n overflow: { default: 'hidden', [breakpoints.sm]: 'visible' },\n clipPath: { default: 'inset(50%)', [breakpoints.sm]: 'none' },\n },\n});\n\n/** 命令面板:快捷键打开的搜索对话框 */\nexport const CommandPalette = _component(\n <T,>({\n _open: open = _bindable(false),\n onOpenChange,\n _inputValue: inputValue = _bindable(''),\n items,\n toOption,\n onAction,\n filter,\n loading = false,\n hotkey = 'Mod+K',\n placeholder,\n label,\n trigger,\n renderOption,\n empty,\n propsTrigger,\n propsInput,\n propsListbox,\n ...rest\n }: CommandPaletteProps<T>) => {\n const lang = useLang();\n const textMatcher = useTextMatcher();\n const listId = useId();\n const appearance = useControlAppearance(() => ({}));\n const optionOf = (item: T) => toListboxOption(item, toOption);\n const setOpen = openSetter(\n () => open,\n (next) => (open = next),\n () => onOpenChange,\n );\n\n function matches(item: T, text: string) {\n if (filter === false) return true;\n if (filter) return filter(item, text);\n const match = textMatcher();\n const option = optionOf(item);\n return match(option.label, text) || match(option.description ?? '', text);\n }\n\n const list = useListState({\n items: () => (inputValue ? items.filter((item) => matches(item, inputValue)) : items),\n toOption: optionOf,\n // 面板不记选择:选中就是执行\n selected: () => [],\n mode: () => 'single',\n onChange: () => {},\n });\n\n // 每次打开从空白开始;输入变了高亮第一个结果\n watch(\n () => open,\n (isOpen) => {\n if (isOpen) inputValue = '';\n },\n );\n // 远程搜索时结果晚到:结果变了也高亮第一个\n watch([() => inputValue, () => open, () => list.options.length], () => list.move('first'), {\n flush: 'post',\n });\n\n useHotkeys(() => (hotkey ? { [hotkey]: (event) => setOpen(!open, 'imperative', event) } : {}), {\n inInputs: true,\n });\n\n function pick(value: string, event?: Event) {\n const entry = list.options[list.indexOf(value)];\n if (!entry || entry.disabled) return;\n setOpen(false, 'select', event);\n onAction(entry.item);\n }\n\n function onKeydown(event: KeyboardEvent) {\n if (handleListKeydown(list, event, { homeEnd: false }, (value) => pick(value, event)))\n event.preventDefault();\n }\n\n const triggerProps = (): CommandPaletteTriggerProps => ({\n onClick: (event: MouseEvent) => setOpen(true, 'trigger', event),\n 'aria-haspopup': 'dialog',\n 'aria-expanded': open,\n });\n\n /** 默认的触发按钮:画成输入框外壳(只取样式,部件标记归面板自己) */\n function renderTrigger(shell: ControlParts) {\n if (trigger === false) return null;\n if (trigger) return trigger(triggerProps());\n const text = placeholder ?? lang.searchPlaceholder;\n return (\n <ButtonBase\n {...mergePartProps<ButtonBaseProps>(\n {\n sx: [shell.root.sx, commandPaletteStyles.trigger],\n // 快捷键由 aria-keyshortcuts 告诉读屏,显示它的 Kbd 不算进名称\n 'aria-keyshortcuts': hotkey ? ariaKeyShortcut(hotkey) : undefined,\n },\n propsTrigger,\n triggerProps(),\n )}\n >\n <Icon icon={Search} sx={shell.icon.sx} />\n <span {...mergeAttrs<'span'>({ sx: commandPaletteStyles.triggerLabel })}>{text}</span>\n {hotkey && (\n <Kbd\n sx={commandPaletteStyles.triggerText}\n aria-hidden={ariaKeyShortcut(hotkey) ? 'true' : undefined}\n >\n {formatHotkey(hotkey)}\n </Kbd>\n )}\n </ButtonBase>\n );\n }\n\n return () => {\n const shell = controlParts(appearance(), {});\n return (\n <>\n {renderTrigger(shell)}\n <Dialog\n {...mergePartProps<DialogProps>(\n {\n title: label ?? lang.searchLabel,\n sx: commandPaletteStyles.dialog,\n size: 'sm',\n closable: false,\n // 标题只给读屏:面板的名称\n propsHeader: { sx: visuallyHiddenStyles.hidden },\n propsBody: { propsContent: { sx: commandPaletteStyles.body } },\n },\n rest as Partial<DialogProps>,\n )}\n _open={open}\n onOpenChange={onOpenChange}\n >\n <div {...mergeAttrs<'div'>({ sx: commandPaletteStyles.search })}>\n <Icon icon={Search} sx={shell.icon.sx} />\n {/* _bind 编译为 v-model:输入法组合期间不过滤 */}\n <input\n {...mergeAttrs<'input'>(\n { sx: [shell.input.sx, commandPaletteStyles.input] },\n propsInput,\n {\n role: 'combobox',\n type: 'search',\n autofocus: true,\n autocomplete: 'off',\n enterkeyhint: 'go',\n placeholder: placeholder ?? lang.searchPlaceholder,\n 'aria-label': label ?? lang.searchLabel,\n 'aria-autocomplete': 'list',\n 'aria-expanded': true,\n 'aria-controls': listId,\n 'aria-activedescendant': activeOptionId(list, listId),\n onKeydown,\n },\n )}\n _value={inputValue}\n />\n </div>\n <ListboxView\n {...mergePartProps<ListboxViewProps<T>>(\n {\n sx: commandPaletteStyles.listbox,\n list,\n id: listId,\n renderOption,\n empty,\n loading,\n propsEmpty: { description: lang.searchEmpty },\n },\n propsListbox,\n { id: listId, 'aria-label': label ?? lang.searchLabel, onPick: pick },\n )}\n />\n </Dialog>\n </>\n );\n };\n },\n);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CommandPalette.js","names":["_vtp_bindable","_vtp_readonlyProp","_vtp_restProps","_createVNode","_Fragment","_mergeProps","_withDirectives","_vModelText"],"sources":["../../src/overlay/CommandPalette.tsx"],"sourcesContent":["/**\n * 命令面板:Mod+K(macOS 上 ⌘K,其他系统 Ctrl+K)或点触发按钮打开的搜索对话框,边输入边过滤,回车打开。\n * 文档站的站内搜索、后台的“跳转到…”都用它;数据由使用方给,选中后做什么也由使用方决定。\n *\n * <CommandPalette\n * items={pages}\n * toOption={(page) => ({ value: page.path, label: page.title, description: page.summary, group: page.section })}\n * onAction={(page) => router.push(page.path)}\n * />\n *\n * 结构是 Dialog(模态、焦点循环、Esc 关闭)里放一个 combobox 输入框与 ListboxView:\n * 焦点一直在输入框上,↑↓ / Page Up / Page Down 移动高亮(aria-activedescendant),Enter 选中;\n * 数据层与 Select、Combobox 相同(useListState),选项多时虚拟滚动。\n * 远程搜索与 Combobox 一样:_inputValue={keyword} 拿到输入,filter={false} 不再本地过滤,取数据时给 loading。\n */\nimport { Search } from '@lucide/icons';\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport { useId, watch, type VNodeChild } from 'vue';\nimport { ButtonBase, type ButtonBaseProps } from '../base/ButtonBase';\nimport { Icon } from '../base/Icon';\nimport { Kbd } from '../base/Kbd';\nimport { visuallyHiddenStyles } from '../base/VisuallyHidden';\nimport { useLang, useTextMatcher } from '../config/context';\nimport { controlParts, useControlAppearance, type ControlParts } from '../input/control';\nimport {\n ListboxView,\n activeOptionId,\n type ListboxViewProps,\n type OptionRenderState,\n} from '../input/Listbox';\nimport {\n handleListKeydown,\n useListState,\n type ListboxOption,\n type OptionMapping,\n toListboxOption,\n} from '../input/list-state';\nimport { ariaKeyShortcut, formatHotkey, useHotkeys } from '../utils/dom/hotkeys';\nimport { mergeAttrs, mergePartProps, type PartProps } from '../utils/props';\nimport { breakpoints } from '../config/theme/tokens';\nimport { openSetter } from '../utils/dom/open-change';\nimport { Dialog, type DialogProps } from './Dialog';\n\n/** 交给触发器的属性:展开到按钮上 */\nexport interface CommandPaletteTriggerProps {\n /** 打开面板 */\n onClick: (event: MouseEvent) => void;\n /** 告诉读屏这个按钮会打开对话框 */\n 'aria-haspopup': 'dialog';\n /** 面板是否打开 */\n 'aria-expanded': boolean;\n}\n\n/** 与数据类型无关的属性;未声明的属性落到对话框(dialog)上 */\nexport interface CommandPaletteBaseProps<T> extends Omit<\n DialogProps,\n 'open' | 'title' | 'description' | 'footer' | 'children' | 'onSelect'\n> {\n /** 是否打开:父级可以写 _open={x};快捷键、触发按钮、选中、Esc 都会改它 */\n _open?: boolean;\n /** 数据:ListboxOption、字符串,或任意数据加 toOption */\n items: readonly T[];\n /** 执行了一项(回车或点击):面板随即关闭 */\n onAction: (item: T) => void;\n /** 输入框里的文字:父级可以写 _inputValue={x}(远程搜索时据此取数据);每次打开清空 */\n _inputValue?: string;\n /** 过滤:默认按选项文字与说明匹配(忽略大小写与重音);函数自定义;false 不过滤(远程搜索,items 就是结果) */\n filter?: false | ((item: T, query: string) => boolean);\n /** 正在取结果(远程搜索):没有结果时列表里是骨架行,已有结果时顶部显示进度条 */\n loading?: boolean;\n /** 打开面板的快捷键,默认 'Mod+K';false 不注册 */\n hotkey?: string | false;\n /** 输入框的占位文字,默认取语言包(搜索文档) */\n placeholder?: string;\n /** 面板的名称(读屏念的),默认取语言包(搜索) */\n label?: string;\n /** 触发器:`(props) => <IconButton {...props} icon={Search} aria-label=\"搜索\" />`;默认是一个像搜索框的按钮,false 不渲染 */\n trigger?: ((props: CommandPaletteTriggerProps) => VNodeChild) | false;\n /** 自定义选项内容 */\n renderOption?: (item: T, state: OptionRenderState) => VNodeChild;\n /** 没有匹配时显示的内容;默认是 Empty(文案取语言包) */\n empty?: VNodeChild;\n /** 默认触发器的部件属性 */\n propsTrigger?: Partial<ButtonBaseProps>;\n /** 输入框的部件属性 */\n propsInput?: PartProps<'input'>;\n /** 列表的部件属性 */\n propsListbox?: Partial<ListboxViewProps<T>>;\n}\n\n/** 命令面板的属性;items 不是 ListboxOption 或字符串时要给 toOption */\nexport type CommandPaletteProps<T = ListboxOption> = CommandPaletteBaseProps<T> & OptionMapping<T>;\n\n/** CommandPalette 的样式:dialog、search、input、listbox、trigger、triggerText、triggerLabel */\nexport const commandPaletteStyles = css.create({\n // 停在视口上部而不是正中:结果变多变少时输入框不跳\n dialog: { marginBlockStart: '12dvh' },\n search: {\n display: 'flex',\n alignItems: 'center',\n gap: '_sm',\n paddingBlock: '_sm',\n paddingInline: '_md',\n borderBlockEndWidth: '_xs',\n borderBlockEndStyle: 'solid',\n borderBlockEndColor: '_border',\n fontSize: '_lg',\n },\n input: { paddingBlock: '_sm' },\n listbox: { maxHeight: 'min(24rem, 50dvh)' },\n body: { padding: 0 },\n // 窄屏(40rem 以下,sizes.sm)只留放大镜图标,不挤占顶栏\n trigger: {\n gap: '_sm',\n minWidth: { default: null, [breakpoints.sm]: '12rem' },\n color: '_textMuted',\n cursor: 'pointer',\n },\n triggerText: { display: { default: 'none', [breakpoints.sm]: 'initial' } },\n // 按钮的名称来自这段文字(与看得见的一致,WCAG 2.5.3):窄屏只留图标时视觉上隐藏,读屏仍念得到\n triggerLabel: {\n flexGrow: 1,\n textAlign: 'start',\n whiteSpace: 'nowrap',\n position: { default: 'absolute', [breakpoints.sm]: 'static' },\n width: { default: 1, [breakpoints.sm]: 'auto' },\n height: { default: 1, [breakpoints.sm]: 'auto' },\n overflow: { default: 'hidden', [breakpoints.sm]: 'visible' },\n clipPath: { default: 'inset(50%)', [breakpoints.sm]: 'none' },\n },\n});\n\n/** 命令面板:快捷键打开的搜索对话框 */\nexport const CommandPalette = _component(\n <T,>({\n _open: open = _bindable(false),\n onOpenChange,\n _inputValue: inputValue = _bindable(''),\n items,\n toOption,\n onAction,\n filter,\n loading = false,\n hotkey = 'Mod+K',\n placeholder,\n label,\n trigger,\n renderOption,\n empty,\n propsTrigger,\n propsInput,\n propsListbox,\n ...rest\n }: CommandPaletteProps<T>) => {\n const lang = useLang();\n const textMatcher = useTextMatcher();\n const listId = useId();\n const appearance = useControlAppearance(() => ({}));\n const optionOf = (item: T) => toListboxOption(item, toOption);\n const setOpen = openSetter(\n () => open,\n (next) => (open = next),\n () => onOpenChange,\n );\n\n function matches(item: T, text: string) {\n if (filter === false) return true;\n if (filter) return filter(item, text);\n const match = textMatcher();\n const option = optionOf(item);\n return match(option.label, text) || match(option.description ?? '', text);\n }\n\n const list = useListState({\n items: () => (inputValue ? items.filter((item) => matches(item, inputValue)) : items),\n toOption: optionOf,\n // 面板不记选择:选中就是执行\n selected: () => [],\n mode: () => 'single',\n onChange: () => {},\n });\n\n // 每次打开从空白开始;输入变了高亮第一个结果\n watch(\n () => open,\n (isOpen) => {\n if (isOpen) inputValue = '';\n },\n );\n // 远程搜索时结果晚到:结果变了也高亮第一个\n watch([() => inputValue, () => open, () => list.options.length], () => list.move('first'), {\n flush: 'post',\n });\n\n useHotkeys(() => (hotkey ? { [hotkey]: (event) => setOpen(!open, 'imperative', event) } : {}), {\n inInputs: true,\n });\n\n function pick(value: string, event?: Event) {\n const entry = list.options[list.indexOf(value)];\n if (!entry || entry.disabled) return;\n setOpen(false, 'select', event);\n onAction(entry.item);\n }\n\n function onKeydown(event: KeyboardEvent) {\n if (handleListKeydown(list, event, { homeEnd: false }, (value) => pick(value, event)))\n event.preventDefault();\n }\n\n const triggerProps = (): CommandPaletteTriggerProps => ({\n onClick: (event: MouseEvent) => setOpen(true, 'trigger', event),\n 'aria-haspopup': 'dialog',\n 'aria-expanded': open,\n });\n\n /** 默认的触发按钮:画成输入框外壳(只取样式,部件标记归面板自己) */\n function renderTrigger(shell: ControlParts) {\n if (trigger === false) return null;\n if (trigger) return trigger(triggerProps());\n const text = placeholder ?? lang.searchPlaceholder;\n return (\n <ButtonBase\n {...mergePartProps<ButtonBaseProps>(\n {\n sx: [shell.root.sx, commandPaletteStyles.trigger],\n // 快捷键由 aria-keyshortcuts 告诉读屏,显示它的 Kbd 不算进名称\n 'aria-keyshortcuts': hotkey ? ariaKeyShortcut(hotkey) : undefined,\n },\n propsTrigger,\n triggerProps(),\n )}\n >\n <Icon icon={Search} sx={shell.icon.sx} />\n <span {...mergeAttrs<'span'>({ sx: commandPaletteStyles.triggerLabel })}>{text}</span>\n {hotkey && (\n <Kbd\n sx={commandPaletteStyles.triggerText}\n aria-hidden={ariaKeyShortcut(hotkey) ? 'true' : undefined}\n >\n {formatHotkey(hotkey)}\n </Kbd>\n )}\n </ButtonBase>\n );\n }\n\n return () => {\n const shell = controlParts(appearance(), {});\n return (\n <>\n {renderTrigger(shell)}\n <Dialog\n {...mergePartProps<DialogProps>(\n {\n title: label ?? lang.searchLabel,\n sx: commandPaletteStyles.dialog,\n size: 'sm',\n closable: false,\n // 标题只给读屏:面板的名称\n propsHeader: { sx: visuallyHiddenStyles.hidden },\n propsBody: { propsContent: { sx: commandPaletteStyles.body } },\n },\n rest as Partial<DialogProps>,\n )}\n _open={open}\n onOpenChange={onOpenChange}\n >\n <div {...mergeAttrs<'div'>({ sx: commandPaletteStyles.search })}>\n <Icon icon={Search} sx={shell.icon.sx} />\n {/* _bind 编译为 v-model:输入法组合期间不过滤 */}\n <input\n {...mergeAttrs<'input'>(\n { sx: [shell.input.sx, commandPaletteStyles.input] },\n propsInput,\n {\n role: 'combobox',\n type: 'search',\n autofocus: true,\n autocomplete: 'off',\n enterkeyhint: 'go',\n placeholder: placeholder ?? lang.searchPlaceholder,\n 'aria-label': label ?? lang.searchLabel,\n 'aria-autocomplete': 'list',\n 'aria-expanded': true,\n 'aria-controls': listId,\n 'aria-activedescendant': activeOptionId(list, listId),\n onKeydown,\n },\n )}\n _value={inputValue}\n />\n </div>\n <ListboxView\n {...mergePartProps<ListboxViewProps<T>>(\n {\n sx: commandPaletteStyles.listbox,\n list,\n id: listId,\n renderOption,\n empty,\n loading,\n propsEmpty: { description: lang.searchEmpty },\n },\n propsListbox,\n { id: listId, 'aria-label': label ?? lang.searchLabel, onPick: pick },\n )}\n />\n </Dialog>\n </>\n );\n };\n },\n);\n"],"mappings":"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"}
|
|
@@ -2,8 +2,8 @@ import { useLang } from "../config/context.js";
|
|
|
2
2
|
import { mergeAttrs, mergePartProps } from "../utils/props.js";
|
|
3
3
|
import { openSetter } from "../utils/dom/open-change.js";
|
|
4
4
|
import { Menu } from "./Menu.js";
|
|
5
|
-
import { createVNode,
|
|
6
|
-
import { _component, bindable, childrenProp, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
5
|
+
import { createVNode, nextTick, shallowRef, unref, watch } from "vue";
|
|
6
|
+
import { _component, bindable, childrenProp, mergeJsxProps, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
7
7
|
//#region src/overlay/ContextMenu.tsx
|
|
8
8
|
/** 未声明的属性落到区域的 div 上 */
|
|
9
9
|
/** 右键菜单:区域 + 从指针位置弹出的 Menu */
|
|
@@ -46,7 +46,7 @@ var ContextMenu = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
46
46
|
const active = document.activeElement;
|
|
47
47
|
if (!active || active === document.body || active.closest("[role=\"menu\"]")) origin?.focus();
|
|
48
48
|
}, { flush: "post" });
|
|
49
|
-
return () => createVNode("div",
|
|
49
|
+
return () => createVNode("div", mergeJsxProps(mergeAttrs({}, rest, { onContextmenu }), { "data-state": open.value ? "open" : "closed" }), [children.value, createVNode(Menu, mergeJsxProps(mergePartProps({
|
|
50
50
|
trigger: () => null,
|
|
51
51
|
"aria-label": label.value ?? lang.contextMenuLabel
|
|
52
52
|
}, propsMenu.value, { point: point.value }), {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ContextMenu.js","names":["_vtp_bindable","_vtp_readonlyProp","_vtp_childrenProp","_vtp_restProps","_vue_shallowRef","_createVNode","_mergeProps","_vue_unref"],"sources":["../../src/overlay/ContextMenu.tsx"],"sourcesContent":["/**\n * 右键菜单(div):在一块区域里右键(或键盘的 Shift+F10、菜单键)时,从指针位置弹出菜单。\n * 菜单项与 Menu 完全相同(MenuItem、MenuCheckboxItem、MenuSub…),菜单本身就是一个以指针位置为锚点的 Menu。\n *\n * <ContextMenu menu={<><MenuItem onAction={rename}>重命名</MenuItem><MenuItem tone=\"danger\" onAction={remove}>删除</MenuItem></>}>\n * <FileCard file={file} />\n * </ContextMenu>\n *\n * 键盘打开时(contextmenu 事件不带指针坐标)从获得焦点的元素左下角弹出;关闭后焦点回到打开前的元素。\n * 区域本身不可聚焦:键盘用户要能打开,里面得有可聚焦的元素(同 Radix ContextMenu)。\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport { nextTick, watch, type VNodeChild } from 'vue';\nimport { useLang } from '../config/context';\nimport { openSetter, type OpenChangeProps } from '../utils/dom/open-change';\nimport { mergeAttrs, mergePartProps, type PartProps } from '../utils/props';\nimport { Menu, type MenuProps } from './Menu';\n\n/** 未声明的属性落到区域的 div 上 */\nexport interface ContextMenuProps extends PartProps<'div'>, OpenChangeProps {\n /** 菜单是否打开:父级可以写 _open={x} */\n _open?: boolean;\n /** 菜单项:同 Menu 的 children */\n menu: VNodeChild;\n /** 菜单的名称,默认取语言包(更多操作) */\n label?: string;\n /** 不弹出(浏览器自己的右键菜单照常出现) */\n disabled?: boolean;\n /** 菜单(Menu)的部件属性 */\n propsMenu?: Partial<MenuProps>;\n /** 区域里的内容 */\n children?: VNodeChild;\n}\n\n/** 右键菜单:区域 + 从指针位置弹出的 Menu */\nexport const ContextMenu = _component(\n ({\n _open: open = _bindable(false),\n onOpenChange,\n menu,\n label,\n disabled = false,\n propsMenu,\n children,\n ...rest\n }: ContextMenuProps) => {\n const lang = useLang();\n let point = _state.raw<{ x: number; y: number } | undefined>(undefined);\n // 打开前焦点所在的元素:关闭后还给它\n let origin: HTMLElement | null = null;\n const setOpen = openSetter(\n () => open,\n (next) => (open = next),\n () => onOpenChange,\n );\n\n function onContextmenu(event: MouseEvent) {\n const target = event.target as HTMLElement;\n // 菜单渲染在区域里:在菜单上右键不再打开\n if (disabled || target.closest('[role=\"menu\"]')) return;\n event.preventDefault();\n origin = document.activeElement instanceof HTMLElement ? document.activeElement : null;\n // 键盘打开时没有指针坐标:从获得焦点的元素左下角弹出\n if (event.clientX === 0 && event.clientY === 0) {\n const rect = target.getBoundingClientRect();\n point = { x: rect.left, y: rect.bottom };\n } else point = { x: event.clientX, y: event.clientY };\n // 已经开着时先关再开,菜单换到新的位置(仍算开着,不通知)\n if (open) {\n open = false;\n void nextTick(() => (open = true));\n } else setOpen(true, 'trigger', event);\n }\n\n watch(\n () => open,\n (next) => {\n if (next) return;\n const active = document.activeElement;\n if (!active || active === document.body || active.closest('[role=\"menu\"]')) origin?.focus();\n },\n { flush: 'post' },\n );\n\n return () => (\n <div\n {...mergeAttrs<'div'>({}, rest, { onContextmenu })}\n data-state={open ? 'open' : 'closed'}\n >\n {children}\n <Menu\n {...mergePartProps<MenuProps>(\n { trigger: () => null, 'aria-label': label ?? lang.contextMenuLabel },\n propsMenu,\n { point },\n )}\n _open={open}\n onOpenChange={onOpenChange}\n >\n {menu}\n </Menu>\n </div>\n );\n },\n);\n"],"mappings":";;;;;;;;;AAmCA,IAAa,cAAc,0BACzB,QAAA,SASwB;CAAA,MAAA,SAAA,CAAA;CAAA,MAAA,OAAAA,SAAA,QAAA,eARE,OAAK,KAAA,IAAA;CAAA,MAAA,eAAAC,aAAA,QAAA,cAAA;CAAA,MAAA,OAAAA,aAAA,QAAA,MAAA;CAAA,MAAA,QAAAA,aAAA,QAAA,OAAA;CAAA,MAAA,WAAAA,aAAA,QAAA,YAIlB,KAAK;CAAA,MAAA,YAAAA,aAAA,QAAA,WAAA;CAAA,MAAA,WAAAC,aAAA,MAAA;CAAA,MAGb,OAAIC,UAAA,QAAA,MAAA,KAAA;CAEP,MAAM,OAAO,QAAQ;CACrB,IAAI,QAAKC,WAAoD,KAAA,CAAS;CAEtE,IAAI,SAA6B;CACjC,MAAM,UAAU,iBACd,KAAA,QACC,SAAU,KAAA,QAAO,YAClB,aAAA,KACF;CAEA,SAAS,cAAc,OAAmB;EACxC,MAAM,SAAS,MAAM;EAErB,IAAI,SAAA,SAAY,OAAO,QAAQ,iBAAe,GAAG;EACjD,MAAM,eAAe;EACrB,SAAS,SAAS,yBAAyB,cAAc,SAAS,gBAAgB;EAElF,IAAI,MAAM,YAAY,KAAK,MAAM,YAAY,GAAG;GAC9C,MAAM,OAAO,OAAO,sBAAsB;GAC1C,MAAA,QAAQ;IAAE,GAAG,KAAK;IAAM,GAAG,KAAK;GAAO;EACzC,OAAO,MAAA,QAAQ;GAAE,GAAG,MAAM;GAAS,GAAG,MAAM;EAAQ;EAEpD,IAAA,KAAA,OAAU;GACR,KAAA,QAAO;GACP,eAAqB,KAAA,QAAO,IAAK;EACnC,OAAO,QAAQ,MAAM,WAAW,KAAK;CACvC;CAEA,YACE,KAAA,QACC,SAAS;EACR,IAAI,MAAM;EACV,MAAM,SAAS,SAAS;EACxB,IAAI,CAAC,UAAU,WAAW,SAAS,QAAQ,OAAO,QAAQ,iBAAe,GAAG,QAAQ,MAAM;CAC5F,GACA,EAAE,OAAO,OAAO,CAClB;CAEA,aAAOC,YAAA,OAAAC,
|
|
1
|
+
{"version":3,"file":"ContextMenu.js","names":["_vtp_bindable","_vtp_readonlyProp","_vtp_childrenProp","_vtp_restProps","_vue_shallowRef","_createVNode","_mergeProps","_vue_unref"],"sources":["../../src/overlay/ContextMenu.tsx"],"sourcesContent":["/**\n * 右键菜单(div):在一块区域里右键(或键盘的 Shift+F10、菜单键)时,从指针位置弹出菜单。\n * 菜单项与 Menu 完全相同(MenuItem、MenuCheckboxItem、MenuSub…),菜单本身就是一个以指针位置为锚点的 Menu。\n *\n * <ContextMenu menu={<><MenuItem onAction={rename}>重命名</MenuItem><MenuItem tone=\"danger\" onAction={remove}>删除</MenuItem></>}>\n * <FileCard file={file} />\n * </ContextMenu>\n *\n * 键盘打开时(contextmenu 事件不带指针坐标)从获得焦点的元素左下角弹出;关闭后焦点回到打开前的元素。\n * 区域本身不可聚焦:键盘用户要能打开,里面得有可聚焦的元素(同 Radix ContextMenu)。\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport { nextTick, watch, type VNodeChild } from 'vue';\nimport { useLang } from '../config/context';\nimport { openSetter, type OpenChangeProps } from '../utils/dom/open-change';\nimport { mergeAttrs, mergePartProps, type PartProps } from '../utils/props';\nimport { Menu, type MenuProps } from './Menu';\n\n/** 未声明的属性落到区域的 div 上 */\nexport interface ContextMenuProps extends PartProps<'div'>, OpenChangeProps {\n /** 菜单是否打开:父级可以写 _open={x} */\n _open?: boolean;\n /** 菜单项:同 Menu 的 children */\n menu: VNodeChild;\n /** 菜单的名称,默认取语言包(更多操作) */\n label?: string;\n /** 不弹出(浏览器自己的右键菜单照常出现) */\n disabled?: boolean;\n /** 菜单(Menu)的部件属性 */\n propsMenu?: Partial<MenuProps>;\n /** 区域里的内容 */\n children?: VNodeChild;\n}\n\n/** 右键菜单:区域 + 从指针位置弹出的 Menu */\nexport const ContextMenu = _component(\n ({\n _open: open = _bindable(false),\n onOpenChange,\n menu,\n label,\n disabled = false,\n propsMenu,\n children,\n ...rest\n }: ContextMenuProps) => {\n const lang = useLang();\n let point = _state.raw<{ x: number; y: number } | undefined>(undefined);\n // 打开前焦点所在的元素:关闭后还给它\n let origin: HTMLElement | null = null;\n const setOpen = openSetter(\n () => open,\n (next) => (open = next),\n () => onOpenChange,\n );\n\n function onContextmenu(event: MouseEvent) {\n const target = event.target as HTMLElement;\n // 菜单渲染在区域里:在菜单上右键不再打开\n if (disabled || target.closest('[role=\"menu\"]')) return;\n event.preventDefault();\n origin = document.activeElement instanceof HTMLElement ? document.activeElement : null;\n // 键盘打开时没有指针坐标:从获得焦点的元素左下角弹出\n if (event.clientX === 0 && event.clientY === 0) {\n const rect = target.getBoundingClientRect();\n point = { x: rect.left, y: rect.bottom };\n } else point = { x: event.clientX, y: event.clientY };\n // 已经开着时先关再开,菜单换到新的位置(仍算开着,不通知)\n if (open) {\n open = false;\n void nextTick(() => (open = true));\n } else setOpen(true, 'trigger', event);\n }\n\n watch(\n () => open,\n (next) => {\n if (next) return;\n const active = document.activeElement;\n if (!active || active === document.body || active.closest('[role=\"menu\"]')) origin?.focus();\n },\n { flush: 'post' },\n );\n\n return () => (\n <div\n {...mergeAttrs<'div'>({}, rest, { onContextmenu })}\n data-state={open ? 'open' : 'closed'}\n >\n {children}\n <Menu\n {...mergePartProps<MenuProps>(\n { trigger: () => null, 'aria-label': label ?? lang.contextMenuLabel },\n propsMenu,\n { point },\n )}\n _open={open}\n onOpenChange={onOpenChange}\n >\n {menu}\n </Menu>\n </div>\n );\n },\n);\n"],"mappings":";;;;;;;;;AAmCA,IAAa,cAAc,0BACzB,QAAA,SASwB;CAAA,MAAA,SAAA,CAAA;CAAA,MAAA,OAAAA,SAAA,QAAA,eARE,OAAK,KAAA,IAAA;CAAA,MAAA,eAAAC,aAAA,QAAA,cAAA;CAAA,MAAA,OAAAA,aAAA,QAAA,MAAA;CAAA,MAAA,QAAAA,aAAA,QAAA,OAAA;CAAA,MAAA,WAAAA,aAAA,QAAA,YAIlB,KAAK;CAAA,MAAA,YAAAA,aAAA,QAAA,WAAA;CAAA,MAAA,WAAAC,aAAA,MAAA;CAAA,MAGb,OAAIC,UAAA,QAAA,MAAA,KAAA;CAEP,MAAM,OAAO,QAAQ;CACrB,IAAI,QAAKC,WAAoD,KAAA,CAAS;CAEtE,IAAI,SAA6B;CACjC,MAAM,UAAU,iBACd,KAAA,QACC,SAAU,KAAA,QAAO,YAClB,aAAA,KACF;CAEA,SAAS,cAAc,OAAmB;EACxC,MAAM,SAAS,MAAM;EAErB,IAAI,SAAA,SAAY,OAAO,QAAQ,iBAAe,GAAG;EACjD,MAAM,eAAe;EACrB,SAAS,SAAS,yBAAyB,cAAc,SAAS,gBAAgB;EAElF,IAAI,MAAM,YAAY,KAAK,MAAM,YAAY,GAAG;GAC9C,MAAM,OAAO,OAAO,sBAAsB;GAC1C,MAAA,QAAQ;IAAE,GAAG,KAAK;IAAM,GAAG,KAAK;GAAO;EACzC,OAAO,MAAA,QAAQ;GAAE,GAAG,MAAM;GAAS,GAAG,MAAM;EAAQ;EAEpD,IAAA,KAAA,OAAU;GACR,KAAA,QAAO;GACP,eAAqB,KAAA,QAAO,IAAK;EACnC,OAAO,QAAQ,MAAM,WAAW,KAAK;CACvC;CAEA,YACE,KAAA,QACC,SAAS;EACR,IAAI,MAAM;EACV,MAAM,SAAS,SAAS;EACxB,IAAI,CAAC,UAAU,WAAW,SAAS,QAAQ,OAAO,QAAQ,iBAAe,GAAG,QAAQ,MAAM;CAC5F,GACA,EAAE,OAAO,OAAO,CAClB;CAEA,aAAOC,YAAA,OAAAC,cAEC,WAAkB,CAAC,GAAG,MAAM,EAAE,cAAc,CAAC,GAAC,EAAA,cACtC,KAAA,QAAO,SAAS,SAAQ,CAAA,GAAA,CAAA,SAAA,OAAAD,YAAA,MAAAC,cAI9B,eACF;EAAE,eAAe;EAAM,cAAc,MAAA,SAAS,KAAK;CAAiB,GAAC,UAAA,OAErE,EAAE,OAAK,MAAA,MAAC,CACV,GAAC;EAAA,SAAA,KAAA;EAAA,kBAAA,OAAA,OAAA,OAAA,MAAA,WAAA,KAAA,QAAA;EAAA,gBAAA,aAAA;EAAA,OAAA;CAAA,CAAA,GAAA;EAAA,eAAA,CAAAC,MAAA,IAAA,CAAA;EAAA,SAAA;CAAA,CAAA,CAAA,CAAA;AAQT,GAAC;CAAA,MAAA;CAAA,OAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;CAAA;AAAA,CACH"}
|
package/dist/overlay/Dialog.js
CHANGED
|
@@ -4,8 +4,8 @@ import { useRecipe } from "../config/styles.js";
|
|
|
4
4
|
import { transitions } from "../utils/style/transitions.js";
|
|
5
5
|
import { useDetachedOverlay } from "./detached.js";
|
|
6
6
|
import { ModalCore, modalPartStyles } from "./ModalCore.js";
|
|
7
|
-
import { createVNode, isVNode
|
|
8
|
-
import { _component, bindable, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
7
|
+
import { createVNode, isVNode } from "vue";
|
|
8
|
+
import { _component, bindable, mergeJsxProps, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
9
9
|
import * as css from "@zerodep-dev/vue-tsx-pro-tsx/styles";
|
|
10
10
|
//#region src/overlay/Dialog.tsx
|
|
11
11
|
/** 对话框的宽度档位,与 Container 相同(30–80rem),另加 full 占满视口 */
|
|
@@ -60,7 +60,7 @@ var Dialog = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
60
60
|
const rest = restProps(_props, _ctx, true);
|
|
61
61
|
const appearance = useDefaults("Dialog", () => ({ size: size.value }), { size: "xs" });
|
|
62
62
|
const recipe = useRecipe(dialogRecipe);
|
|
63
|
-
return () => createVNode(ModalCore,
|
|
63
|
+
return () => createVNode(ModalCore, mergeJsxProps(rest, {
|
|
64
64
|
"parts": recipe({ size: appearance().size }),
|
|
65
65
|
"panelSx": transitions.scale,
|
|
66
66
|
"_open": open.value,
|
|
@@ -80,7 +80,7 @@ var Dialog = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
80
80
|
* 句柄的 closed 得到关上的原因(确认是 confirm);只想问一句“确定吗”用 useConfirm。
|
|
81
81
|
*/
|
|
82
82
|
function useDialog() {
|
|
83
|
-
return { open: useDetachedOverlay((props, host, children) => createVNode(Dialog,
|
|
83
|
+
return { open: useDetachedOverlay((props, host, children) => createVNode(Dialog, mergeJsxProps(props, {
|
|
84
84
|
"_open": host.open,
|
|
85
85
|
"onUpdate:_open": ($event) => host.open = $event
|
|
86
86
|
}), _isSlot(children) ? children : { default: () => [children] })) };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Dialog.js","names":["_isVNode","_vtp_bindable","_vtp_readonlyProp","_vtp_restProps","_createVNode","_mergeProps"],"sources":["../../src/overlay/Dialog.tsx"],"sourcesContent":["/**\n * 模态对话框:居中显示,淡入放大。内核见 ModalCore.tsx,贴着视口一侧滑入的见 Drawer。\n *\n * <Dialog _open={editing} title=\"编辑资料\" footer={<Button onClick={save}>保存</Button>}>…</Dialog>\n *\n * 不想自己管 open(从表格的一行打开详情、在回调里弹出)时用命令式的 useDialog:\n *\n * const dialog = useDialog(); // setup 里\n * const handle = dialog.open({ title: '订单详情', size: 'md', content: () => <OrderDetail id={id} /> });\n * handle.update({ title: '订单详情(已发货)' }); handle.close(); await handle.closed;\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport type { ContainerSizes } from '../layout/Container';\nimport {\n useDetachedOverlay,\n type DetachedOverlayHandle,\n type DetachedOverlayOptions,\n} from './detached';\nimport { useDefaults } from '../config/defaults';\nimport { useRecipe } from '../config/styles';\nimport { transitions } from '../utils/style/transitions';\nimport { ModalCore, modalPartStyles, type ModalProps } from './ModalCore';\nimport { t } from '../config/theme/tokens';\n\n/** 对话框的宽度档位,与 Container 相同(30–80rem),另加 full 占满视口 */\nexport interface DialogSizes extends ContainerSizes {\n /** 占满视口(四周留出 1em) */\n full: unknown;\n}\n\n/** 对话框的外观选项;可以用 createUi / Provider 的 defaults.Dialog 统一改默认值(AlertDialog 也用它) */\nexport interface DialogOptions {\n /** 宽度档位,默认 xs(30rem);窄屏时不超出视口 */\n size?: keyof DialogSizes;\n}\n\n/** 未声明的属性落到 dialog 上 */\nexport interface DialogProps extends ModalProps, DialogOptions {}\n\n/**\n * 对话框的配方(Dialog、AlertDialog 共用):部件同 ModalCore(base 并进了 modalPartStyles);\n * root 是 dialog 元素,宽度占满可用空间、高度上限、圆角,宽度档位不超出视口,full 占满视口\n */\nexport const dialogRecipe = css.recipe({\n name: 'Dialog',\n parts: ['root', 'header', 'title', 'close', 'description', 'body', 'bodyContent', 'footer'],\n base: {\n ...modalPartStyles,\n root: {\n ...modalPartStyles.root,\n width: '_full',\n // 原生 dialog 已限制 max-width / max-height 不超出视口;这里只把高度上限放到四周各留 1em\n maxHeight: 'calc(100dvh - 2em)',\n borderRadius: '_xl',\n },\n },\n variants: {\n size: {\n xs: { root: { maxWidth: `min(${t.sizes.xs}, calc(100vw - 2em))` } },\n sm: { root: { maxWidth: `min(${t.sizes.sm}, calc(100vw - 2em))` } },\n md: { root: { maxWidth: `min(${t.sizes.md}, calc(100vw - 2em))` } },\n lg: { root: { maxWidth: `min(${t.sizes.lg}, calc(100vw - 2em))` } },\n xl: { root: { maxWidth: `min(${t.sizes.xl}, calc(100vw - 2em))` } },\n full: { root: { maxWidth: 'calc(100vw - 2em)', height: 'calc(100dvh - 2em)' } },\n },\n },\n});\n\n/** 对话框的部件(createUi / Provider 的 styles.Dialog 按它定制,AlertDialog 也用它) */\nexport type DialogPart = (typeof dialogRecipe.parts)[number];\n\n/** 模态对话框:居中显示,淡入放大 */\nexport const Dialog = _component(\n ({ _open: open = _bindable(false), size, ...rest }: DialogProps) => {\n const appearance = useDefaults('Dialog', () => ({ size }), { size: 'xs' });\n const recipe = useRecipe(dialogRecipe);\n return () => (\n <ModalCore\n {...rest}\n parts={recipe({ size: appearance().size })}\n panelSx={transitions.scale}\n _open={open}\n />\n );\n },\n);\n\n/** useDialog 返回的:open 打开一个对话框 */\nexport interface DialogApi {\n /** 打开对话框:属性同 Dialog(open 由句柄管理),content 是正文;返回句柄 */\n open(options: DetachedOverlayOptions<DialogProps>): DetachedOverlayHandle<DialogProps>;\n}\n\n/**\n * 命令式对话框:在组件 setup 里调用。对话框渲染到 body 末尾,沿用调用处的主题、语言等配置,关上后自动卸载。\n * 句柄的 closed 得到关上的原因(确认是 confirm);只想问一句“确定吗”用 useConfirm。\n */\nexport function useDialog(): DialogApi {\n const open = useDetachedOverlay<DialogProps>((props, host, children) => (\n <Dialog {...props} _open={host.open}>\n {children}\n </Dialog>\n ));\n return { open };\n}\n"],"mappings":";;;;;;;;;;;;;AAqCA,SAAA,QAAA,GAAA;CAAA,OAAA,OAAA,MAAA,cAAA,OAAA,UAAA,SAAA,KAAA,CAAA,MAAA,qBAAA,CAAAA,QAAA,CAAA;AAAA;;;;;AAOA,IAAa,eAAe,IAAI,OAAO;CACrC,MAAM;CACN,OAAO;EAAC;EAAQ;EAAU;EAAS;EAAS;EAAe;EAAQ;EAAe;CAAQ;CAC1F,MAAM;EACJ,GAAG;EACH,MAAM;GACJ,GAAG,gBAAgB;GACnB,OAAO;GAEP,WAAW;GACX,cAAc;EAChB;CACF;CACA,UAAU,EACR,MAAM;EACJ,IAAI,EAAE,MAAM,EAAE,UAAU,OAAO,EAAE,MAAM,GAAE,sBAAuB,EAAE;EAClE,IAAI,EAAE,MAAM,EAAE,UAAU,OAAO,EAAE,MAAM,GAAE,sBAAuB,EAAE;EAClE,IAAI,EAAE,MAAM,EAAE,UAAU,OAAO,EAAE,MAAM,GAAE,sBAAuB,EAAE;EAClE,IAAI,EAAE,MAAM,EAAE,UAAU,OAAO,EAAE,MAAM,GAAE,sBAAuB,EAAE;EAClE,IAAI,EAAE,MAAM,EAAE,UAAU,OAAO,EAAE,MAAM,GAAE,sBAAuB,EAAE;EAClE,MAAM,EAAE,MAAM;GAAE,UAAU;GAAqB,QAAQ;EAAqB,EAAE;CAChF,EACF;AACF,CAAC;;;AAMD,IAAa,SAAS,0BACpB,QAAA,SAAoE;CAAA,MAAA,SAAA,CAAA;CAAA,MAAA,OAAAC,SAAA,QAAA,eAAzC,OAAK,KAAA,IAAA;CAAA,MAAA,OAAAC,aAAA,QAAA,MAAA;CAAA,MAAY,OAAIC,UAAA,QAAA,MAAA,IAAA;CAC9C,MAAM,aAAa,YAAY,iBAAiB,EAAE,MAAI,KAAA,MAAC,IAAI,EAAE,MAAM,KAAK,CAAC;CACzE,MAAM,SAAS,UAAU,YAAY;CACrC,aAAOC,YAAA,WAAAC,
|
|
1
|
+
{"version":3,"file":"Dialog.js","names":["_isVNode","_vtp_bindable","_vtp_readonlyProp","_vtp_restProps","_createVNode","_mergeProps"],"sources":["../../src/overlay/Dialog.tsx"],"sourcesContent":["/**\n * 模态对话框:居中显示,淡入放大。内核见 ModalCore.tsx,贴着视口一侧滑入的见 Drawer。\n *\n * <Dialog _open={editing} title=\"编辑资料\" footer={<Button onClick={save}>保存</Button>}>…</Dialog>\n *\n * 不想自己管 open(从表格的一行打开详情、在回调里弹出)时用命令式的 useDialog:\n *\n * const dialog = useDialog(); // setup 里\n * const handle = dialog.open({ title: '订单详情', size: 'md', content: () => <OrderDetail id={id} /> });\n * handle.update({ title: '订单详情(已发货)' }); handle.close(); await handle.closed;\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport type { ContainerSizes } from '../layout/Container';\nimport {\n useDetachedOverlay,\n type DetachedOverlayHandle,\n type DetachedOverlayOptions,\n} from './detached';\nimport { useDefaults } from '../config/defaults';\nimport { useRecipe } from '../config/styles';\nimport { transitions } from '../utils/style/transitions';\nimport { ModalCore, modalPartStyles, type ModalProps } from './ModalCore';\nimport { t } from '../config/theme/tokens';\n\n/** 对话框的宽度档位,与 Container 相同(30–80rem),另加 full 占满视口 */\nexport interface DialogSizes extends ContainerSizes {\n /** 占满视口(四周留出 1em) */\n full: unknown;\n}\n\n/** 对话框的外观选项;可以用 createUi / Provider 的 defaults.Dialog 统一改默认值(AlertDialog 也用它) */\nexport interface DialogOptions {\n /** 宽度档位,默认 xs(30rem);窄屏时不超出视口 */\n size?: keyof DialogSizes;\n}\n\n/** 未声明的属性落到 dialog 上 */\nexport interface DialogProps extends ModalProps, DialogOptions {}\n\n/**\n * 对话框的配方(Dialog、AlertDialog 共用):部件同 ModalCore(base 并进了 modalPartStyles);\n * root 是 dialog 元素,宽度占满可用空间、高度上限、圆角,宽度档位不超出视口,full 占满视口\n */\nexport const dialogRecipe = css.recipe({\n name: 'Dialog',\n parts: ['root', 'header', 'title', 'close', 'description', 'body', 'bodyContent', 'footer'],\n base: {\n ...modalPartStyles,\n root: {\n ...modalPartStyles.root,\n width: '_full',\n // 原生 dialog 已限制 max-width / max-height 不超出视口;这里只把高度上限放到四周各留 1em\n maxHeight: 'calc(100dvh - 2em)',\n borderRadius: '_xl',\n },\n },\n variants: {\n size: {\n xs: { root: { maxWidth: `min(${t.sizes.xs}, calc(100vw - 2em))` } },\n sm: { root: { maxWidth: `min(${t.sizes.sm}, calc(100vw - 2em))` } },\n md: { root: { maxWidth: `min(${t.sizes.md}, calc(100vw - 2em))` } },\n lg: { root: { maxWidth: `min(${t.sizes.lg}, calc(100vw - 2em))` } },\n xl: { root: { maxWidth: `min(${t.sizes.xl}, calc(100vw - 2em))` } },\n full: { root: { maxWidth: 'calc(100vw - 2em)', height: 'calc(100dvh - 2em)' } },\n },\n },\n});\n\n/** 对话框的部件(createUi / Provider 的 styles.Dialog 按它定制,AlertDialog 也用它) */\nexport type DialogPart = (typeof dialogRecipe.parts)[number];\n\n/** 模态对话框:居中显示,淡入放大 */\nexport const Dialog = _component(\n ({ _open: open = _bindable(false), size, ...rest }: DialogProps) => {\n const appearance = useDefaults('Dialog', () => ({ size }), { size: 'xs' });\n const recipe = useRecipe(dialogRecipe);\n return () => (\n <ModalCore\n {...rest}\n parts={recipe({ size: appearance().size })}\n panelSx={transitions.scale}\n _open={open}\n />\n );\n },\n);\n\n/** useDialog 返回的:open 打开一个对话框 */\nexport interface DialogApi {\n /** 打开对话框:属性同 Dialog(open 由句柄管理),content 是正文;返回句柄 */\n open(options: DetachedOverlayOptions<DialogProps>): DetachedOverlayHandle<DialogProps>;\n}\n\n/**\n * 命令式对话框:在组件 setup 里调用。对话框渲染到 body 末尾,沿用调用处的主题、语言等配置,关上后自动卸载。\n * 句柄的 closed 得到关上的原因(确认是 confirm);只想问一句“确定吗”用 useConfirm。\n */\nexport function useDialog(): DialogApi {\n const open = useDetachedOverlay<DialogProps>((props, host, children) => (\n <Dialog {...props} _open={host.open}>\n {children}\n </Dialog>\n ));\n return { open };\n}\n"],"mappings":";;;;;;;;;;;;;AAqCA,SAAA,QAAA,GAAA;CAAA,OAAA,OAAA,MAAA,cAAA,OAAA,UAAA,SAAA,KAAA,CAAA,MAAA,qBAAA,CAAAA,QAAA,CAAA;AAAA;;;;;AAOA,IAAa,eAAe,IAAI,OAAO;CACrC,MAAM;CACN,OAAO;EAAC;EAAQ;EAAU;EAAS;EAAS;EAAe;EAAQ;EAAe;CAAQ;CAC1F,MAAM;EACJ,GAAG;EACH,MAAM;GACJ,GAAG,gBAAgB;GACnB,OAAO;GAEP,WAAW;GACX,cAAc;EAChB;CACF;CACA,UAAU,EACR,MAAM;EACJ,IAAI,EAAE,MAAM,EAAE,UAAU,OAAO,EAAE,MAAM,GAAE,sBAAuB,EAAE;EAClE,IAAI,EAAE,MAAM,EAAE,UAAU,OAAO,EAAE,MAAM,GAAE,sBAAuB,EAAE;EAClE,IAAI,EAAE,MAAM,EAAE,UAAU,OAAO,EAAE,MAAM,GAAE,sBAAuB,EAAE;EAClE,IAAI,EAAE,MAAM,EAAE,UAAU,OAAO,EAAE,MAAM,GAAE,sBAAuB,EAAE;EAClE,IAAI,EAAE,MAAM,EAAE,UAAU,OAAO,EAAE,MAAM,GAAE,sBAAuB,EAAE;EAClE,MAAM,EAAE,MAAM;GAAE,UAAU;GAAqB,QAAQ;EAAqB,EAAE;CAChF,EACF;AACF,CAAC;;;AAMD,IAAa,SAAS,0BACpB,QAAA,SAAoE;CAAA,MAAA,SAAA,CAAA;CAAA,MAAA,OAAAC,SAAA,QAAA,eAAzC,OAAK,KAAA,IAAA;CAAA,MAAA,OAAAC,aAAA,QAAA,MAAA;CAAA,MAAY,OAAIC,UAAA,QAAA,MAAA,IAAA;CAC9C,MAAM,aAAa,YAAY,iBAAiB,EAAE,MAAI,KAAA,MAAC,IAAI,EAAE,MAAM,KAAK,CAAC;CACzE,MAAM,SAAS,UAAU,YAAY;CACrC,aAAOC,YAAA,WAAAC,cAEC,MAAI;EAAA,SACD,OAAO,EAAE,MAAM,WAAW,CAAC,CAAC,KAAK,CAAC;EAAC,WACjC,YAAY;EAAK,SAAA,KAAA;EAAA,kBAAA,OAAA,OAAA,OAAA,MAAA,WAAA,KAAA,QAAA;CAAA,CAAA,GAAA,IAAA;AAIhC,GAAC;CAAA,MAAA;CAAA,OAAA;EAAA;EAAA;EAAA;CAAA;AAAA,CACH;;;;;;AAYA,SAAgB,YAAuB;CAMrC,OAAO,EAAE,MALI,oBAAiC,OAAO,MAAM,aAAQD,YAAA,QAAAC,cACrD,OAAK;EAAA,SAAS,KAAK;EAAI,mBAAA,WAAT,KAAK,OAAI;CAAA,CAAA,GAAA,QAChC,QAAQ,IAAR,WAAQ,EAAA,eAAA,CAAR,QAAQ,EAAA,CAAA,CAGJ,EAAK;AAChB"}
|
package/dist/overlay/Drawer.js
CHANGED
|
@@ -5,8 +5,8 @@ import { useRecipe } from "../config/styles.js";
|
|
|
5
5
|
import { ResizeHandle } from "../layout/ResizeHandle.js";
|
|
6
6
|
import { useDetachedOverlay } from "./detached.js";
|
|
7
7
|
import { ModalCore, modalPartStyles } from "./ModalCore.js";
|
|
8
|
-
import { createVNode, isVNode
|
|
9
|
-
import { _component, bindable, readonlyProp, restProps, untrack } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
8
|
+
import { createVNode, isVNode } from "vue";
|
|
9
|
+
import { _component, bindable, mergeJsxProps, readonlyProp, restProps, untrack } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
10
10
|
import * as css from "@zerodep-dev/vue-tsx-pro-tsx/styles";
|
|
11
11
|
//#region src/overlay/Drawer.tsx
|
|
12
12
|
/** 抽屉从哪一侧滑入;start / end 是逻辑方向,从右到左排版下左右互换 */
|
|
@@ -163,13 +163,13 @@ var Drawer = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
163
163
|
size: inline && width.value == null ? appearance().size : void 0,
|
|
164
164
|
resizable: resizing
|
|
165
165
|
});
|
|
166
|
-
return createVNode(ModalCore,
|
|
166
|
+
return createVNode(ModalCore, mergeJsxProps(rest, {
|
|
167
167
|
"_open": open.value,
|
|
168
168
|
"onUpdate:_open": _cache[0] || (_cache[0] = ($event) => open.value = $event),
|
|
169
169
|
"parts": s,
|
|
170
170
|
"style": inline && width.value != null ? { width: `${width.value}px` } : void 0,
|
|
171
171
|
"data-side": side.value,
|
|
172
|
-
"edge": resizing && createVNode(ResizeHandle,
|
|
172
|
+
"edge": resizing && createVNode(ResizeHandle, mergeJsxProps({
|
|
173
173
|
"aria-label": messages.drawerResize,
|
|
174
174
|
"minWidth": minWidth.value,
|
|
175
175
|
"maxWidth": typeof window === "undefined" ? void 0 : window.innerWidth - 48,
|
|
@@ -203,7 +203,7 @@ var Drawer = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
203
203
|
* drawer.open({ title: `订单 ${row.id}`, size: 'md', content: (handle) => <OrderForm id={row.id} onSaved={handle.close} /> });
|
|
204
204
|
*/
|
|
205
205
|
function useDrawer() {
|
|
206
|
-
return { open: useDetachedOverlay((props, host, children) => createVNode(Drawer,
|
|
206
|
+
return { open: useDetachedOverlay((props, host, children) => createVNode(Drawer, mergeJsxProps(props, {
|
|
207
207
|
"_open": host.open,
|
|
208
208
|
"onUpdate:_open": ($event) => host.open = $event
|
|
209
209
|
}), _isSlot(children) ? children : { default: () => [children] })) };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Drawer.js","names":["_isVNode","_vtp_bindable","_vtp_readonlyProp","_vtp_restProps","_vtp_untrack","_createVNode","_mergeProps"],"sources":["../../src/overlay/Drawer.tsx"],"sourcesContent":["/**\n * 抽屉:贴着视口一侧滑入的模态面板。内核见 Modal.tsx(与 Dialog、AlertDialog 共用)。\n *\n * <Drawer _open={filtering} side=\"end\" title=\"筛选\">…</Drawer>\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport type { ContainerSizes } from '../layout/Container';\nimport { useLang } from '../config/context';\nimport { useDefaults } from '../config/defaults';\nimport { useRecipe } from '../config/styles';\nimport { ResizeHandle, type ResizeHandleProps } from '../layout/ResizeHandle';\nimport {\n useDetachedOverlay,\n type DetachedOverlayHandle,\n type DetachedOverlayOptions,\n} from './detached';\nimport { ModalCore, modalPartStyles, type ModalProps } from './ModalCore';\nimport { t } from '../config/theme/tokens';\n\n/** 抽屉从哪一侧滑入;start / end 是逻辑方向,从右到左排版下左右互换 */\nexport interface DrawerSides {\n /** 起始边(从左到右排版时是左侧):导航菜单 */\n start: unknown;\n /** 结束边(默认,从左到右排版时是右侧):详情、筛选、表单 */\n end: unknown;\n /** 顶部 */\n top: unknown;\n /** 底部:手机上的操作面板 */\n bottom: unknown;\n}\n\n/** 抽屉的外观选项;可以用 createUi / Provider 的 defaults.Drawer 统一改默认值 */\nexport interface DrawerOptions {\n /** 宽度(左右两侧)档位,与 Container 相同,默认 xs(30rem);上下两侧时高度由内容决定,最多到视口高度 */\n size?: keyof ContainerSizes;\n /**\n * 靠正文的一边可以拖动调整宽度(也能聚焦后用 ← / →,双击回到初始宽度),能一直拖到只剩一窄条、\n * 让出后面的页面;只用于左右两侧\n * @default true\n */\n resizable?: boolean;\n}\n\n/** 未声明的属性落到 dialog 上 */\nexport interface DrawerProps extends ModalProps, DrawerOptions {\n /** 从哪一侧滑入,默认 end */\n side?: keyof DrawerSides;\n /** 宽度(px,左右两侧):写了就不按 size 的档位;父级写 _width={x} 记住拖动后的宽度 */\n _width?: number | null;\n /**\n * 拖动时的最小宽度(px):默认只留一窄条(几乎关上,还能拖回来);比内容窄时内容被裁掉,不挤成一列\n * @default 48\n */\n minWidth?: number;\n /** 宽度调整手柄的部件属性 */\n propsResizeHandle?: Partial<ResizeHandleProps>;\n}\n\n/**\n * 抽屉的配方:部件同 ModalCore(base 并进了 modalPartStyles);root 是 dialog 元素(去掉居中、按所在一侧定位与滑入),\n * 能拖动调整宽度时标题行、说明、正文、页脚保持最小宽度\n */\nexport const drawerRecipe = css.recipe({\n name: 'Drawer',\n parts: ['root', 'header', 'title', 'close', 'description', 'body', 'bodyContent', 'footer'],\n base: {\n ...modalPartStyles,\n root: {\n ...modalPartStyles.root,\n margin: 0,\n // 给了 width 时宽度写在行内,最多到视口宽\n maxWidth: '100vw',\n maxHeight: '100dvh',\n borderRadius: 0,\n // 进场从所在一侧滑入(@starting-style),离场滑回去(allow-discrete,离场过渡目前只有 Chromium);\n // “减少动态效果”时时长为 0。打开时 translate 是 none 而不是 0 0:否则抽屉会成为 fixed 子元素\n // (移进来的 Toaster)的包含块;关闭时不显示,移到所在一侧之外没有这个问题\n transition: {\n default: `translate ${t.durations.md} ${t.easings.exit}, display ${t.durations.md} allow-discrete, overlay ${t.durations.md} allow-discrete`,\n ':modal': `translate ${t.durations.lg} ${t.easings.enter}, display ${t.durations.lg} allow-discrete, overlay ${t.durations.lg} allow-discrete`,\n },\n },\n },\n variants: {\n // 左右两侧占满视口高度、宽度看档位,上下两侧占满视口宽度、高度由内容决定。\n // 位置用逻辑属性,从右到左排版自动换边;translate 是物理方向,用 :dir() 区分\n side: {\n start: {\n root: {\n insetBlock: 0,\n width: '_full',\n height: '100dvh',\n overflow: 'hidden',\n insetInlineStart: 0,\n insetInlineEnd: 'auto',\n borderInlineStartWidth: 0,\n translate: {\n default: '-100% 0',\n ':dir(rtl)': '100% 0',\n ':modal': {\n default: 'none',\n '@starting-style': { default: '-100% 0', ':dir(rtl)': '100% 0' },\n },\n },\n },\n },\n end: {\n root: {\n insetBlock: 0,\n width: '_full',\n height: '100dvh',\n overflow: 'hidden',\n insetInlineStart: 'auto',\n insetInlineEnd: 0,\n borderInlineEndWidth: 0,\n translate: {\n default: '100% 0',\n ':dir(rtl)': '-100% 0',\n ':modal': {\n default: 'none',\n '@starting-style': { default: '100% 0', ':dir(rtl)': '-100% 0' },\n },\n },\n },\n },\n top: {\n root: {\n insetInline: 0,\n width: '100vw',\n height: 'auto',\n top: 0,\n bottom: 'auto',\n borderBlockStartWidth: 0,\n translate: {\n default: '0 -100%',\n ':modal': { default: 'none', '@starting-style': '0 -100%' },\n },\n },\n },\n bottom: {\n root: {\n insetInline: 0,\n width: '100vw',\n height: 'auto',\n top: 'auto',\n bottom: 0,\n borderBlockEndWidth: 0,\n translate: {\n default: '0 100%',\n ':modal': { default: 'none', '@starting-style': '0 100%' },\n },\n },\n },\n },\n // 左右两侧、没给 width 时的宽度档位\n size: {\n xs: { root: { maxWidth: `min(${t.sizes.xs}, 100vw)` } },\n sm: { root: { maxWidth: `min(${t.sizes.sm}, 100vw)` } },\n md: { root: { maxWidth: `min(${t.sizes.md}, 100vw)` } },\n lg: { root: { maxWidth: `min(${t.sizes.lg}, 100vw)` } },\n xl: { root: { maxWidth: `min(${t.sizes.xl}, 100vw)` } },\n },\n // 能拖动调整宽度(左右两侧):拖窄以后内容保持能读的最小宽度,多出来的被面板裁掉\n resizable: {\n true: {\n header: { minWidth: 'min(20rem, 100vw - 3rem)' },\n description: { minWidth: 'min(20rem, 100vw - 3rem)' },\n body: { minWidth: 'min(20rem, 100vw - 3rem)' },\n footer: { minWidth: 'min(20rem, 100vw - 3rem)' },\n },\n },\n },\n});\n\n/** 抽屉的部件(createUi / Provider 的 styles.Drawer 按它定制) */\nexport type DrawerPart = (typeof drawerRecipe.parts)[number];\n\n/** 抽屉:贴着视口一侧滑入的模态面板 */\nexport const Drawer = _component(\n ({\n _open: open = _bindable(false),\n side = 'end',\n size,\n _width: width = _bindable<number | null>(null),\n resizable,\n minWidth = 48,\n propsResizeHandle,\n ...rest\n }: DrawerProps) => {\n const appearance = useDefaults('Drawer', () => ({ size, resizable }), {\n size: 'xs',\n resizable: true,\n });\n const messages = useLang();\n const recipe = useRecipe(drawerRecipe);\n // 双击回到打开时的宽度\n const initialWidth = _untrack(width);\n return () => {\n const inline = side === 'start' || side === 'end';\n const resizing = inline && appearance().resizable;\n const s = recipe({\n side,\n size: inline && width == null ? appearance().size : undefined,\n resizable: resizing,\n });\n return (\n <ModalCore\n {...rest}\n _open={open}\n parts={s}\n style={inline && width != null ? { width: `${width}px` } : undefined}\n data-side={side}\n edge={\n resizing && (\n <ResizeHandle\n aria-label={messages.drawerResize}\n minWidth={minWidth}\n maxWidth={typeof window === 'undefined' ? undefined : window.innerWidth - 48}\n defaultWidth={initialWidth ?? undefined}\n {...propsResizeHandle}\n edge={side === 'end' ? 'start' : 'end'}\n width={width ?? undefined}\n onWidthChange={(next: number) => (width = next)}\n />\n )\n }\n />\n );\n };\n },\n);\n\n/** useDrawer 返回的:open 打开一个抽屉 */\nexport interface DrawerApi {\n /** 打开抽屉:属性同 Drawer(open 由句柄管理),content 是正文;返回句柄 */\n open(options: DetachedOverlayOptions<DrawerProps>): DetachedOverlayHandle<DrawerProps>;\n}\n\n/**\n * 命令式抽屉:在组件 setup 里调用,用法同 useDialog。从表格的一行打开详情、编辑时不必在页面里放一个 Drawer 再管 open:\n *\n * const drawer = useDrawer();\n * drawer.open({ title: `订单 ${row.id}`, size: 'md', content: (handle) => <OrderForm id={row.id} onSaved={handle.close} /> });\n */\nexport function useDrawer(): DrawerApi {\n const open = useDetachedOverlay<DrawerProps>((props, host, children) => (\n <Drawer {...props} _open={host.open}>\n {children}\n </Drawer>\n ));\n return { open };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Drawer.js","names":["_isVNode","_vtp_bindable","_vtp_readonlyProp","_vtp_restProps","_vtp_untrack","_createVNode","_mergeProps"],"sources":["../../src/overlay/Drawer.tsx"],"sourcesContent":["/**\n * 抽屉:贴着视口一侧滑入的模态面板。内核见 Modal.tsx(与 Dialog、AlertDialog 共用)。\n *\n * <Drawer _open={filtering} side=\"end\" title=\"筛选\">…</Drawer>\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport type { ContainerSizes } from '../layout/Container';\nimport { useLang } from '../config/context';\nimport { useDefaults } from '../config/defaults';\nimport { useRecipe } from '../config/styles';\nimport { ResizeHandle, type ResizeHandleProps } from '../layout/ResizeHandle';\nimport {\n useDetachedOverlay,\n type DetachedOverlayHandle,\n type DetachedOverlayOptions,\n} from './detached';\nimport { ModalCore, modalPartStyles, type ModalProps } from './ModalCore';\nimport { t } from '../config/theme/tokens';\n\n/** 抽屉从哪一侧滑入;start / end 是逻辑方向,从右到左排版下左右互换 */\nexport interface DrawerSides {\n /** 起始边(从左到右排版时是左侧):导航菜单 */\n start: unknown;\n /** 结束边(默认,从左到右排版时是右侧):详情、筛选、表单 */\n end: unknown;\n /** 顶部 */\n top: unknown;\n /** 底部:手机上的操作面板 */\n bottom: unknown;\n}\n\n/** 抽屉的外观选项;可以用 createUi / Provider 的 defaults.Drawer 统一改默认值 */\nexport interface DrawerOptions {\n /** 宽度(左右两侧)档位,与 Container 相同,默认 xs(30rem);上下两侧时高度由内容决定,最多到视口高度 */\n size?: keyof ContainerSizes;\n /**\n * 靠正文的一边可以拖动调整宽度(也能聚焦后用 ← / →,双击回到初始宽度),能一直拖到只剩一窄条、\n * 让出后面的页面;只用于左右两侧\n * @default true\n */\n resizable?: boolean;\n}\n\n/** 未声明的属性落到 dialog 上 */\nexport interface DrawerProps extends ModalProps, DrawerOptions {\n /** 从哪一侧滑入,默认 end */\n side?: keyof DrawerSides;\n /** 宽度(px,左右两侧):写了就不按 size 的档位;父级写 _width={x} 记住拖动后的宽度 */\n _width?: number | null;\n /**\n * 拖动时的最小宽度(px):默认只留一窄条(几乎关上,还能拖回来);比内容窄时内容被裁掉,不挤成一列\n * @default 48\n */\n minWidth?: number;\n /** 宽度调整手柄的部件属性 */\n propsResizeHandle?: Partial<ResizeHandleProps>;\n}\n\n/**\n * 抽屉的配方:部件同 ModalCore(base 并进了 modalPartStyles);root 是 dialog 元素(去掉居中、按所在一侧定位与滑入),\n * 能拖动调整宽度时标题行、说明、正文、页脚保持最小宽度\n */\nexport const drawerRecipe = css.recipe({\n name: 'Drawer',\n parts: ['root', 'header', 'title', 'close', 'description', 'body', 'bodyContent', 'footer'],\n base: {\n ...modalPartStyles,\n root: {\n ...modalPartStyles.root,\n margin: 0,\n // 给了 width 时宽度写在行内,最多到视口宽\n maxWidth: '100vw',\n maxHeight: '100dvh',\n borderRadius: 0,\n // 进场从所在一侧滑入(@starting-style),离场滑回去(allow-discrete,离场过渡目前只有 Chromium);\n // “减少动态效果”时时长为 0。打开时 translate 是 none 而不是 0 0:否则抽屉会成为 fixed 子元素\n // (移进来的 Toaster)的包含块;关闭时不显示,移到所在一侧之外没有这个问题\n transition: {\n default: `translate ${t.durations.md} ${t.easings.exit}, display ${t.durations.md} allow-discrete, overlay ${t.durations.md} allow-discrete`,\n ':modal': `translate ${t.durations.lg} ${t.easings.enter}, display ${t.durations.lg} allow-discrete, overlay ${t.durations.lg} allow-discrete`,\n },\n },\n },\n variants: {\n // 左右两侧占满视口高度、宽度看档位,上下两侧占满视口宽度、高度由内容决定。\n // 位置用逻辑属性,从右到左排版自动换边;translate 是物理方向,用 :dir() 区分\n side: {\n start: {\n root: {\n insetBlock: 0,\n width: '_full',\n height: '100dvh',\n overflow: 'hidden',\n insetInlineStart: 0,\n insetInlineEnd: 'auto',\n borderInlineStartWidth: 0,\n translate: {\n default: '-100% 0',\n ':dir(rtl)': '100% 0',\n ':modal': {\n default: 'none',\n '@starting-style': { default: '-100% 0', ':dir(rtl)': '100% 0' },\n },\n },\n },\n },\n end: {\n root: {\n insetBlock: 0,\n width: '_full',\n height: '100dvh',\n overflow: 'hidden',\n insetInlineStart: 'auto',\n insetInlineEnd: 0,\n borderInlineEndWidth: 0,\n translate: {\n default: '100% 0',\n ':dir(rtl)': '-100% 0',\n ':modal': {\n default: 'none',\n '@starting-style': { default: '100% 0', ':dir(rtl)': '-100% 0' },\n },\n },\n },\n },\n top: {\n root: {\n insetInline: 0,\n width: '100vw',\n height: 'auto',\n top: 0,\n bottom: 'auto',\n borderBlockStartWidth: 0,\n translate: {\n default: '0 -100%',\n ':modal': { default: 'none', '@starting-style': '0 -100%' },\n },\n },\n },\n bottom: {\n root: {\n insetInline: 0,\n width: '100vw',\n height: 'auto',\n top: 'auto',\n bottom: 0,\n borderBlockEndWidth: 0,\n translate: {\n default: '0 100%',\n ':modal': { default: 'none', '@starting-style': '0 100%' },\n },\n },\n },\n },\n // 左右两侧、没给 width 时的宽度档位\n size: {\n xs: { root: { maxWidth: `min(${t.sizes.xs}, 100vw)` } },\n sm: { root: { maxWidth: `min(${t.sizes.sm}, 100vw)` } },\n md: { root: { maxWidth: `min(${t.sizes.md}, 100vw)` } },\n lg: { root: { maxWidth: `min(${t.sizes.lg}, 100vw)` } },\n xl: { root: { maxWidth: `min(${t.sizes.xl}, 100vw)` } },\n },\n // 能拖动调整宽度(左右两侧):拖窄以后内容保持能读的最小宽度,多出来的被面板裁掉\n resizable: {\n true: {\n header: { minWidth: 'min(20rem, 100vw - 3rem)' },\n description: { minWidth: 'min(20rem, 100vw - 3rem)' },\n body: { minWidth: 'min(20rem, 100vw - 3rem)' },\n footer: { minWidth: 'min(20rem, 100vw - 3rem)' },\n },\n },\n },\n});\n\n/** 抽屉的部件(createUi / Provider 的 styles.Drawer 按它定制) */\nexport type DrawerPart = (typeof drawerRecipe.parts)[number];\n\n/** 抽屉:贴着视口一侧滑入的模态面板 */\nexport const Drawer = _component(\n ({\n _open: open = _bindable(false),\n side = 'end',\n size,\n _width: width = _bindable<number | null>(null),\n resizable,\n minWidth = 48,\n propsResizeHandle,\n ...rest\n }: DrawerProps) => {\n const appearance = useDefaults('Drawer', () => ({ size, resizable }), {\n size: 'xs',\n resizable: true,\n });\n const messages = useLang();\n const recipe = useRecipe(drawerRecipe);\n // 双击回到打开时的宽度\n const initialWidth = _untrack(width);\n return () => {\n const inline = side === 'start' || side === 'end';\n const resizing = inline && appearance().resizable;\n const s = recipe({\n side,\n size: inline && width == null ? appearance().size : undefined,\n resizable: resizing,\n });\n return (\n <ModalCore\n {...rest}\n _open={open}\n parts={s}\n style={inline && width != null ? { width: `${width}px` } : undefined}\n data-side={side}\n edge={\n resizing && (\n <ResizeHandle\n aria-label={messages.drawerResize}\n minWidth={minWidth}\n maxWidth={typeof window === 'undefined' ? undefined : window.innerWidth - 48}\n defaultWidth={initialWidth ?? undefined}\n {...propsResizeHandle}\n edge={side === 'end' ? 'start' : 'end'}\n width={width ?? undefined}\n onWidthChange={(next: number) => (width = next)}\n />\n )\n }\n />\n );\n };\n },\n);\n\n/** useDrawer 返回的:open 打开一个抽屉 */\nexport interface DrawerApi {\n /** 打开抽屉:属性同 Drawer(open 由句柄管理),content 是正文;返回句柄 */\n open(options: DetachedOverlayOptions<DrawerProps>): DetachedOverlayHandle<DrawerProps>;\n}\n\n/**\n * 命令式抽屉:在组件 setup 里调用,用法同 useDialog。从表格的一行打开详情、编辑时不必在页面里放一个 Drawer 再管 open:\n *\n * const drawer = useDrawer();\n * drawer.open({ title: `订单 ${row.id}`, size: 'md', content: (handle) => <OrderForm id={row.id} onSaved={handle.close} /> });\n */\nexport function useDrawer(): DrawerApi {\n const open = useDetachedOverlay<DrawerProps>((props, host, children) => (\n <Drawer {...props} _open={host.open}>\n {children}\n </Drawer>\n ));\n return { open };\n}\n"],"mappings":"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"}
|
|
@@ -1,12 +1,13 @@
|
|
|
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 { useLazyContent } from "../utils/motion/presence.js";
|
|
4
5
|
import { floatingStyles } from "../utils/dom/floating.js";
|
|
5
6
|
import { createHoverGroup } from "../utils/dom/hover-intent.js";
|
|
6
7
|
import { openSetter } from "../utils/dom/open-change.js";
|
|
7
8
|
import { useHoverLayer } from "../utils/dom/hover-layer.js";
|
|
8
|
-
import { Fragment, createVNode
|
|
9
|
-
import { _component, bindable, childrenProp, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
9
|
+
import { Fragment, createVNode } 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/HoverCard.tsx
|
|
12
13
|
/** 交给触发器的属性:展开到触发元素上(`{...props}`) */
|
|
@@ -33,6 +34,7 @@ var HoverCard = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
33
34
|
const lazy = readonlyProp(_props, "lazy", true);
|
|
34
35
|
const children = childrenProp(_props);
|
|
35
36
|
const rest = restProps(_props, _ctx, false);
|
|
37
|
+
const mounted = useMounted();
|
|
36
38
|
const showContent = useLazyContent(() => open.value, () => lazy.value);
|
|
37
39
|
const layer = useHoverLayer(hoverCards, () => ({
|
|
38
40
|
open: open.value,
|
|
@@ -48,7 +50,7 @@ var HoverCard = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
48
50
|
onBlur: (event) => {
|
|
49
51
|
if (!layer.contains(event.relatedTarget)) layer.hover.closeNow("focus", event);
|
|
50
52
|
}
|
|
51
|
-
}), createVNode("div",
|
|
53
|
+
}), mounted.value && createVNode("div", mergeJsxProps(mergeAttrs({ sx: [
|
|
52
54
|
floatingStyles.anchored,
|
|
53
55
|
floatingStyles.surface,
|
|
54
56
|
hoverCardStyles.panel,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HoverCard.js","names":["_vtp_bindable","_vtp_readonlyProp","_vtp_childrenProp","_vtp_restProps","_createVNode","_Fragment","_mergeProps"],"sources":["../../src/overlay/HoverCard.tsx"],"sourcesContent":["/**\n * 悬停卡片:指针停在触发器(通常是链接、用户名)上、或用键盘聚焦它时,弹出一张富内容卡片(用户资料、链接预览)。\n * 开关时机同 Tooltip(useHoverIntent:停留后打开、可以移到卡片上、同组只开一个、刚关掉一个时下一个直接打开),\n * 定位同 Popover(floating-ui,可选箭头)。参考 Radix HoverCard。\n *\n * <HoverCard trigger={(props) => <Link {...props} href=\"/u/ada\">@ada</Link>}>\n * <Avatar name=\"Ada Lovelace\" /> …\n * </HoverCard>\n *\n * 卡片只是预览:里面的信息要在别处也能拿到(触屏没有悬停)。卡片里可以有链接、按钮:\n * 键盘聚焦触发器打开后按 Tab 进入卡片,焦点离开触发器与卡片时关闭;Esc 关闭。\n * 用 popover=\"manual\" 渲染在顶层,不会关掉正打开的菜单或弹出框。开关、定位、Esc 见 useHoverLayer(与 Tooltip 共用)。\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 { useLazyContent, type LazyContentOptions } from '../utils/motion/presence';\nimport { mergeAttrs, type PartProps } from '../utils/props';\nimport { transitions } from '../utils/style/transitions';\n\n/** 交给触发器的属性:展开到触发元素上(`{...props}`) */\nexport interface HoverCardTriggerProps {\n /** 取得触发器元素:定位的参照 */\n ref: (element: unknown) => void;\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 HoverCardProps\n extends\n PartProps<'div', 'id' | 'popover'>,\n Pick<FloatingOptions, 'offset'>,\n LazyContentOptions,\n OpenChangeProps {\n /** 是否打开:父级可以写 _open={x} 控制或得知 */\n _open?: boolean;\n /** 弹出位置,默认 bottom;空间不够时自动翻转 */\n placement?: FloatingPlacement;\n /** 指针停留多久后打开(毫秒),默认 700 */\n delay?: number;\n /** 指针离开多久后关闭(毫秒),默认 300 */\n closeDelay?: number;\n /** 不打开(触发器照常渲染) */\n disabled?: boolean;\n /** 显示指向触发器的箭头 */\n arrow?: boolean;\n /** 触发器:`(props) => <Link {...props} … />` */\n trigger: (props: HoverCardTriggerProps) => VNodeChild;\n /** 箭头的部件属性 */\n propsArrow?: PartProps<'span'>;\n /** 卡片内容 */\n children?: VNodeChild;\n}\n\n/** HoverCard 的样式:panel */\nexport const hoverCardStyles = css.create({\n panel: { padding: '_md', maxWidth: 'min(22em, calc(100vw - 2em))' },\n});\n\n/** 所有悬停卡片一组:同一时间只开一个 */\nconst hoverCards = createHoverGroup();\n\n/** 悬停卡片:悬停或键盘聚焦时在触发器旁边显示富内容 */\nexport const HoverCard = _component(\n ({\n _open: open = _bindable(false),\n onOpenChange,\n placement = 'bottom',\n offset,\n delay = 700,\n closeDelay = 300,\n disabled = false,\n arrow = false,\n trigger,\n propsArrow,\n lazy = true,\n children,\n ...rest\n }: HoverCardProps) => {\n const showContent = useLazyContent(\n () => open,\n () => lazy,\n );\n const layer = useHoverLayer(\n hoverCards,\n () => ({ open, disabled, placement, offset, delay, closeDelay }),\n openSetter(\n () => open,\n (next) => (open = next),\n () => onOpenChange,\n ),\n );\n\n return () => (\n <>\n {trigger({\n ref: layer.anchorRef,\n ...layer.hover.trigger,\n // Tab 进卡片(下一个可聚焦元素)时不关\n onBlur: (event: FocusEvent) => {\n if (!layer.contains(event.relatedTarget)) layer.hover.closeNow('focus', event);\n },\n })}\n <div\n
|
|
1
|
+
{"version":3,"file":"HoverCard.js","names":["_vtp_bindable","_vtp_readonlyProp","_vtp_childrenProp","_vtp_restProps","_createVNode","_Fragment","_mergeProps"],"sources":["../../src/overlay/HoverCard.tsx"],"sourcesContent":["/**\n * 悬停卡片:指针停在触发器(通常是链接、用户名)上、或用键盘聚焦它时,弹出一张富内容卡片(用户资料、链接预览)。\n * 开关时机同 Tooltip(useHoverIntent:停留后打开、可以移到卡片上、同组只开一个、刚关掉一个时下一个直接打开),\n * 定位同 Popover(floating-ui,可选箭头)。参考 Radix HoverCard。\n *\n * <HoverCard trigger={(props) => <Link {...props} href=\"/u/ada\">@ada</Link>}>\n * <Avatar name=\"Ada Lovelace\" /> …\n * </HoverCard>\n *\n * 卡片只是预览:里面的信息要在别处也能拿到(触屏没有悬停)。卡片里可以有链接、按钮:\n * 键盘聚焦触发器打开后按 Tab 进入卡片,焦点离开触发器与卡片时关闭;Esc 关闭。\n * 用 popover=\"manual\" 渲染在顶层,不会关掉正打开的菜单或弹出框。开关、定位、Esc 见 useHoverLayer(与 Tooltip 共用)。\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 { useLazyContent, type LazyContentOptions } from '../utils/motion/presence';\nimport { mergeAttrs, type PartProps } from '../utils/props';\nimport { transitions } from '../utils/style/transitions';\nimport { useMounted } from '../utils/dom/mounted';\n\n/** 交给触发器的属性:展开到触发元素上(`{...props}`) */\nexport interface HoverCardTriggerProps {\n /** 取得触发器元素:定位的参照 */\n ref: (element: unknown) => void;\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 HoverCardProps\n extends\n PartProps<'div', 'id' | 'popover'>,\n Pick<FloatingOptions, 'offset'>,\n LazyContentOptions,\n OpenChangeProps {\n /** 是否打开:父级可以写 _open={x} 控制或得知 */\n _open?: boolean;\n /** 弹出位置,默认 bottom;空间不够时自动翻转 */\n placement?: FloatingPlacement;\n /** 指针停留多久后打开(毫秒),默认 700 */\n delay?: number;\n /** 指针离开多久后关闭(毫秒),默认 300 */\n closeDelay?: number;\n /** 不打开(触发器照常渲染) */\n disabled?: boolean;\n /** 显示指向触发器的箭头 */\n arrow?: boolean;\n /** 触发器:`(props) => <Link {...props} … />` */\n trigger: (props: HoverCardTriggerProps) => VNodeChild;\n /** 箭头的部件属性 */\n propsArrow?: PartProps<'span'>;\n /** 卡片内容 */\n children?: VNodeChild;\n}\n\n/** HoverCard 的样式:panel */\nexport const hoverCardStyles = css.create({\n panel: { padding: '_md', maxWidth: 'min(22em, calc(100vw - 2em))' },\n});\n\n/** 所有悬停卡片一组:同一时间只开一个 */\nconst hoverCards = createHoverGroup();\n\n/** 悬停卡片:悬停或键盘聚焦时在触发器旁边显示富内容 */\nexport const HoverCard = _component(\n ({\n _open: open = _bindable(false),\n onOpenChange,\n placement = 'bottom',\n offset,\n delay = 700,\n closeDelay = 300,\n disabled = false,\n arrow = false,\n trigger,\n propsArrow,\n lazy = true,\n children,\n ...rest\n }: HoverCardProps) => {\n // 面板挂载后才渲染:关着时本来就看不见;服务端渲染时也不输出,触发器在 <p> 里时 <div> 不会把段落提前截断,水合对得上\n const mounted = useMounted();\n const showContent = useLazyContent(\n () => open,\n () => lazy,\n );\n const layer = useHoverLayer(\n hoverCards,\n () => ({ open, disabled, placement, offset, delay, closeDelay }),\n openSetter(\n () => open,\n (next) => (open = next),\n () => onOpenChange,\n ),\n );\n\n return () => (\n <>\n {trigger({\n ref: layer.anchorRef,\n ...layer.hover.trigger,\n // Tab 进卡片(下一个可聚焦元素)时不关\n onBlur: (event: FocusEvent) => {\n if (!layer.contains(event.relatedTarget)) layer.hover.closeNow('focus', event);\n },\n })}\n {mounted.value && (\n <div\n {...mergeAttrs<'div'>(\n {\n sx: [\n floatingStyles.anchored,\n floatingStyles.surface,\n hoverCardStyles.panel,\n transitions.scale,\n arrow && floatingStyles.withArrow,\n ],\n },\n rest,\n {\n ref: layer.panelRef,\n id: layer.id,\n popover: 'manual',\n ...layer.hover.panel,\n onFocusout: (event: FocusEvent) => {\n if (!layer.contains(event.relatedTarget)) layer.hover.closeNow('focus', event);\n },\n },\n )}\n data-state={open && !disabled ? 'open' : 'closed'}\n >\n {showContent() && children}\n {arrow && <span {...layer.arrowProps(propsArrow)} />}\n </div>\n )}\n </>\n );\n },\n);\n"],"mappings":";;;;;;;;;;;;;;;AAyEA,IAAa,kBAAkB,IAAI,OAAO,EACxC,OAAO;CAAE,SAAS;CAAO,UAAU;AAA+B,EACpE,CAAC;;AAGD,IAAM,aAAa,iBAAiB;;AAGpC,IAAa,YAAY,0BACvB,QAAA,SAcsB;CAAA,MAAA,OAAAA,SAAA,QAAA,eAbI,OAAK,KAAA,IAAA;CAAA,MAAA,eAAAC,aAAA,QAAA,cAAA;CAAA,MAAA,YAAAA,aAAA,QAAA,aAEjB,QAAQ;CAAA,MAAA,SAAAA,aAAA,QAAA,QAAA;CAAA,MAAA,QAAAA,aAAA,QAAA,SAEZ,GAAG;CAAA,MAAA,aAAAA,aAAA,QAAA,cACE,GAAG;CAAA,MAAA,WAAAA,aAAA,QAAA,YACL,KAAK;CAAA,MAAA,QAAAA,aAAA,QAAA,SACR,KAAK;CAAA,MAAA,UAAAA,aAAA,QAAA,SAAA;CAAA,MAAA,aAAAA,aAAA,QAAA,YAAA;CAAA,MAAA,OAAAA,aAAA,QAAA,QAGN,IAAI;CAAA,MAAA,WAAAC,aAAA,MAAA;CAAA,MAER,OAAIC,UAAA,QAAA,MAAA,KAAA;CAGP,MAAM,UAAU,WAAW;CAC3B,MAAM,cAAc,qBAClB,KAAA,aACA,KAAA,KACF;CACA,MAAM,QAAQ,cACZ,mBACO;EAAE,MAAI,KAAA;EAAE,UAAQ,SAAA;EAAE,WAAS,UAAA;EAAE,QAAM,OAAA;EAAE,OAAK,MAAA;EAAE,YAAU,WAAA;CAAC,IAC9D,iBACE,KAAA,QACC,SAAU,KAAA,QAAO,YAClB,aAAA,KACF,CACF;CAEA,aAAOC,YAAAC,UAAA,MAAA,CAEF,QAAA,MAAQ;EACP,KAAK,MAAM;EACX,GAAG,MAAM,MAAM;EAEf,SAAS,UAAsB;GAC7B,IAAI,CAAC,MAAM,SAAS,MAAM,aAAa,GAAG,MAAM,MAAM,SAAS,SAAS,KAAK;EAC/E;CACF,CAAC,GACA,QAAQ,SAAKD,YAAA,OAAAE,cAEN,WACF,EACE,IAAI;EACF,eAAe;EACf,eAAe;EACf,gBAAgB;EAChB,YAAY;EACZ,MAAA,SAAS,eAAe;CAAS,EAErC,GACA,MACA;EACE,KAAK,MAAM;EACX,IAAI,MAAM;EACV,SAAS;EACT,GAAG,MAAM,MAAM;EACf,aAAa,UAAsB;GACjC,IAAI,CAAC,MAAM,SAAS,MAAM,aAAa,GAAG,MAAM,MAAM,SAAS,SAAS,KAAK;EAC/E;CACF,CACF,GAAC,EAAA,cACW,KAAA,SAAQ,CAAA,SAAA,QAAY,SAAS,SAAQ,CAAA,GAAA,CAEhD,YAAY,KAAC,SAAA,OACb,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;EAAA;EAAA;CAAA;AAAA,CACH"}
|
package/dist/overlay/Lightbox.js
CHANGED
|
@@ -7,8 +7,8 @@ import { openModalDialog } from "../utils/dom/overlay.js";
|
|
|
7
7
|
import { IconLinkButton } from "../base/IconLinkButton.js";
|
|
8
8
|
import { openSetter } from "../utils/dom/open-change.js";
|
|
9
9
|
import { usePanZoom } from "../utils/dom/pan-zoom.js";
|
|
10
|
-
import { createTextVNode, createVNode,
|
|
11
|
-
import { _component, bindable, effect, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
10
|
+
import { createTextVNode, createVNode, nextTick, onBeforeUnmount, ref, shallowRef } from "vue";
|
|
11
|
+
import { _component, bindable, effect, mergeJsxProps, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
12
12
|
import * as css from "@zerodep-dev/vue-tsx-pro-tsx/styles";
|
|
13
13
|
import { ChevronLeft, ChevronRight, Download, RotateCcw, RotateCw, Undo2, X, ZoomIn, ZoomOut } from "@lucide/icons";
|
|
14
14
|
//#region src/overlay/Lightbox.tsx
|
|
@@ -268,13 +268,13 @@ var Lightbox = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
268
268
|
onKeydown
|
|
269
269
|
}), [
|
|
270
270
|
createVNode("div", css.attrs(lightboxStyles.toolbar), [
|
|
271
|
-
createVNode("span",
|
|
271
|
+
createVNode("span", mergeJsxProps(css.attrs(lightboxStyles.counter), { "aria-live": "polite" }), [total > 1 ? lang.lightboxCounter(index.value + 1, total) : ""]),
|
|
272
272
|
createVNode(IconButton, button({
|
|
273
273
|
icon: ZoomOut,
|
|
274
274
|
"aria-label": lang.lightboxZoomOut,
|
|
275
275
|
onClick: () => view.zoomOut()
|
|
276
276
|
}), null),
|
|
277
|
-
createVNode("span",
|
|
277
|
+
createVNode("span", mergeJsxProps(css.attrs(lightboxStyles.zoom), { "aria-hidden": "true" }), [Math.round(view.scale * 100), createTextVNode("%")]),
|
|
278
278
|
createVNode(IconButton, button({
|
|
279
279
|
icon: ZoomIn,
|
|
280
280
|
"aria-label": lang.lightboxZoomIn,
|
|
@@ -315,7 +315,7 @@ var Lightbox = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
315
315
|
tabindex: 0,
|
|
316
316
|
role: "img",
|
|
317
317
|
"aria-label": item?.alt
|
|
318
|
-
}), [item && createVNode("div", css.attrs(lightboxStyles.content, css.select(() => !view.interacting && lightboxStyles.smooth, true), css.bind(lightboxDynamicStyles.transform, () => [view.transform], true), css.bind(lightboxDynamicStyles.box, () => [width, height])), [createVNode("img",
|
|
318
|
+
}), [item && createVNode("div", css.attrs(lightboxStyles.content, css.select(() => !view.interacting && lightboxStyles.smooth, true), css.bind(lightboxDynamicStyles.transform, () => [view.transform], true), css.bind(lightboxDynamicStyles.box, () => [width, height])), [createVNode("img", mergeJsxProps({
|
|
319
319
|
"key": item.src,
|
|
320
320
|
"src": item.src,
|
|
321
321
|
"alt": "",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Lightbox.js","names":["_vtp_bindable","_vtp_readonlyProp","_vtp_restProps","_vue_shallowRef","_vue_ref","_createVNode","_mergeProps","_createTextVNode"],"sources":["../../src/overlay/Lightbox.tsx"],"sourcesContent":["/**\n * 图片预览(全屏的模态 dialog):深色背景上看大图——缩放、拖动、旋转、还原、下载,几张图时左右切换。\n * Image 的 preview 打开它(同一个 ImageGroup 里的图共用一个),也可以单独用。参考 Ant Design Image 的预览、Element Plus 的 image-viewer。\n *\n * <Lightbox _open={viewing} _index={at} items={[{ src: '/photos/1.jpg', alt: '支架正面' }, …]} />\n *\n * - 平移与缩放建在 usePanZoom 上:拖动平移,滚轮、触控板、捏合以指针为中心缩放,双击放大 / 还原;\n * 打开或换一张时整张图放进视口(不超过原大)。旋转以 90° 为一档,转完重新放进视口。\n * - 键盘:← / → 上一张、下一张,+ / − 缩放,0 还原,R 向右旋转,Esc 关闭;焦点在工具栏与图片区之间循环(模态)。\n * - 读屏:dialog 的名称是“图片预览”,图片区是 role=\"img\"、名称是图片的 alt;换一张时念出“第 2 张,共 5 张”。\n *\n * 部件:Root(dialog)、Toolbar(div)、Counter(span)、Viewport(div)、Image(img)、Previous、Next(IconButton)、Caption(div)。\n */\nimport {\n ChevronLeft,\n ChevronRight,\n Download,\n RotateCcw,\n RotateCw,\n Undo2,\n X,\n ZoomIn,\n ZoomOut,\n} from '@lucide/icons';\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport { nextTick, onBeforeUnmount, type VNodeChild } from 'vue';\nimport { IconButton, type IconButtonProps } from '../base/IconButton';\nimport { IconLinkButton, type IconLinkButtonProps } from '../base/IconLinkButton';\nimport { useLang } from '../config/context';\nimport { clamp } from '../utils/data/number';\nimport { openSetter, type OpenChangeProps, type OpenChangeReasons } from '../utils/dom/open-change';\nimport { openModalDialog } from '../utils/dom/overlay';\nimport { usePanZoom } from '../utils/dom/pan-zoom';\nimport { mergeAttrs, mergePartProps, type PartProps } from '../utils/props';\nimport { transitions } from '../utils/style/transitions';\n\n/** 一张图 */\nexport interface LightboxItem {\n /** 大图的地址 */\n src: string;\n /** 图片的说明(读屏念它) */\n alt: string;\n /** 图片下方的说明文字 */\n caption?: VNodeChild;\n /** 下载时的文件名;false 不显示下载按钮 */\n download?: string | false;\n}\n\n/** 打开、关联由组件管理;未声明的属性落到 dialog 上 */\nexport interface LightboxProps\n extends PartProps<'dialog', 'open' | 'aria-label' | 'aria-labelledby'>, OpenChangeProps {\n /** 是否打开:父级写 _open={x};Esc、关闭按钮会关上并写回 */\n _open?: boolean;\n /** 图片 */\n items: readonly LightboxItem[];\n /** 当前第几张,从 0 开始:父级可以写 _index={x} */\n _index?: number;\n /** 到头之后从另一头继续,默认 false */\n loop?: boolean;\n /** 工具栏按钮的部件属性 */\n propsButton?: Partial<IconButtonProps>;\n /** 图片区(视口)的部件属性 */\n propsViewport?: PartProps<'div'>;\n /** 说明文字的部件属性 */\n propsCaption?: PartProps<'div'>;\n}\n\n/** Lightbox 的样式:root、toolbar、counter、viewport、content、image、side、previous、next、caption、button */\nexport const lightboxStyles = css.create({\n root: {\n // 占满视口:原生 dialog 默认有最大宽高与边框\n boxSizing: 'border-box',\n width: '100vw',\n height: '100dvh',\n maxWidth: 'none',\n maxHeight: 'none',\n margin: 0,\n padding: 0,\n borderWidth: 0,\n display: { default: 'none', ':modal': 'flex' },\n flexDirection: 'column',\n // 图片预览总是深色背景:不跟主题变(看图片本身的颜色)\n backgroundColor: 'rgb(0 0 0 / 0.86)',\n color: 'white',\n colorScheme: 'dark',\n },\n toolbar: {\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'flex-end',\n gap: '_xs',\n paddingBlock: '_sm',\n paddingInline: '_md',\n flexShrink: 0,\n },\n counter: {\n marginInlineEnd: 'auto',\n fontVariantNumeric: 'tabular-nums',\n fontSize: '_sm',\n opacity: 0.8,\n },\n zoom: {\n minWidth: '3.5em',\n textAlign: 'center',\n fontSize: '_sm',\n fontVariantNumeric: 'tabular-nums',\n },\n viewport: {\n position: 'relative',\n flexGrow: 1,\n minHeight: 0,\n overflow: 'hidden',\n cursor: { default: 'grab', ':active': 'grabbing' },\n outline: 'none',\n },\n // 平移与缩放作用在它上面(transform-origin 左上角);它的大小是旋转之后图片占的框\n content: { position: 'absolute', top: 0, left: 0, transformOrigin: '0 0' },\n // 图片以框的中心旋转\n image: {\n position: 'absolute',\n top: '50%',\n left: '50%',\n maxWidth: 'none',\n userSelect: 'none',\n pointerEvents: 'none',\n },\n smooth: { transitionProperty: 'transform', transitionDuration: '_md' },\n side: {\n position: 'absolute',\n top: '50%',\n translate: '0 -50%',\n zIndex: 1,\n },\n previous: { insetInlineStart: '_md' },\n next: { insetInlineEnd: '_md' },\n caption: {\n flexShrink: 0,\n paddingBlock: '_md',\n paddingInline: '_xl',\n textAlign: 'center',\n fontSize: '_sm',\n opacity: 0.9,\n },\n // 深色背景上的按钮:文字白色,悬停时浅一点的底\n button: {\n color: 'white',\n backgroundColor: { default: 'transparent', ':hover': 'rgb(255 255 255 / 0.12)' },\n },\n});\n\n/** Lightbox 的动态样式:平移缩放、框的大小、图片的大小与旋转 */\nexport const lightboxDynamicStyles = css.create({\n transform: (transform: string) => ({ transform }),\n box: (width: number, height: number) => ({ width: `${width}px`, height: `${height}px` }),\n image: (width: number, height: number, degrees: number) => ({\n width: `${width}px`,\n height: `${height}px`,\n transform: `translate(-50%, -50%) rotate(${degrees}deg)`,\n }),\n});\n\n/** 图片预览:全屏 dialog,缩放、拖动、旋转、左右切换 */\nexport const Lightbox = _component(\n ({\n _open: open = _bindable(false),\n onOpenChange,\n items,\n _index: index = _bindable(0),\n loop = false,\n propsButton,\n propsViewport,\n propsCaption,\n ...rest\n }: LightboxProps) => {\n const lang = useLang();\n let dialog = _state.raw<HTMLDialogElement | null>(null);\n let viewport = _state.raw<HTMLElement | null>(null);\n // 当前图片的原始大小(加载后知道)与旋转(0、90、180、270)\n let natural = _state<{ width: number; height: number } | null>(null);\n let rotation = _state(0);\n let release: (() => void) | undefined;\n let closing: keyof OpenChangeReasons | undefined;\n const setOpen = openSetter(\n () => open,\n (next) => (open = next),\n () => onOpenChange,\n );\n\n const current = () => items[clamp(index, 0, Math.max(0, items.length - 1))];\n /** 旋转之后图片占的框 */\n const box = () => {\n if (!natural) return { width: 0, height: 0 };\n const sideways = rotation % 180 !== 0;\n return sideways\n ? { width: natural.height, height: natural.width }\n : { width: natural.width, height: natural.height };\n };\n\n const view = usePanZoom({\n viewport: () => viewport,\n min: 0.05,\n max: 8,\n bounds: () => (natural ? box() : undefined),\n initial: () => ({ x: 0, y: 0, scale: 1 }),\n });\n\n /** 整张图放进视口(不超过原大):打开、换一张、旋转之后 */\n function fitImage() {\n if (!natural) return;\n view.fit(box(), 24);\n }\n\n function go(step: 1 | -1) {\n if (items.length < 2) return;\n const next = index + step;\n if (next < 0 || next >= items.length) {\n if (!loop) return;\n index = (next + items.length) % items.length;\n } else index = next;\n }\n\n function rotate(step: 1 | -1) {\n rotation = (rotation + step * 90 + 360) % 360;\n void nextTick(fitImage);\n }\n\n function reset() {\n rotation = 0;\n void nextTick(fitImage);\n }\n\n function onLoad(event: Event) {\n const image = event.currentTarget as HTMLImageElement;\n natural = { width: image.naturalWidth, height: image.naturalHeight };\n void nextTick(fitImage);\n }\n\n function onKeydown(event: KeyboardEvent) {\n if (event.altKey || event.ctrlKey || event.metaKey) return;\n if (event.key === 'ArrowLeft' || event.key === 'ArrowRight') {\n // 从右到左排版时左右对调\n const rtl = dialog ? getComputedStyle(dialog).direction === 'rtl' : false;\n go((event.key === 'ArrowRight') !== rtl ? 1 : -1);\n event.preventDefault();\n } else if (event.key === 'r' || event.key === 'R') {\n rotate(1);\n event.preventDefault();\n }\n }\n\n function close() {\n closing = 'close';\n dialog?.close();\n }\n\n // 不论怎么关上(Esc、关闭按钮、open 改成 false),原生 close 事件都会来\n function onClose() {\n release?.();\n release = undefined;\n const reason = closing ?? 'escape';\n closing = undefined;\n setOpen(false, reason);\n }\n\n // 换一张:原图大小重新量,方向回正\n _effect(() => {\n void current()?.src;\n natural = null;\n rotation = 0;\n });\n _effect(() => {\n if (open && dialog && !dialog.open) {\n release = openModalDialog(dialog);\n void nextTick(() => viewport?.focus());\n } else if (!open && dialog?.open) dialog.close();\n });\n // 预先加载前后两张:切换时不闪\n _effect(() => {\n if (!open) return;\n for (const step of [-1, 1]) {\n const neighbour = items[index + step];\n if (neighbour) new Image().src = neighbour.src;\n }\n });\n onBeforeUnmount(() => release?.());\n\n const button = (props: Partial<IconButtonProps>) =>\n mergePartProps<IconButtonProps>(\n {\n icon: X,\n variant: 'ghost',\n tone: 'neutral',\n sx: lightboxStyles.button,\n 'aria-label': '',\n },\n propsButton,\n props,\n );\n\n return () => {\n const item = current();\n const total = items.length;\n const { width, height } = box();\n const canDownload = item && item.download !== false;\n return (\n <dialog\n {...mergeAttrs<'dialog'>({ sx: [lightboxStyles.root, transitions.backdrop] }, rest, {\n ref: _toRef(dialog),\n 'aria-label': lang.lightboxLabel,\n onClose,\n onKeydown,\n })}\n >\n <div {...css.attrs(lightboxStyles.toolbar)}>\n <span {...css.attrs(lightboxStyles.counter)} aria-live=\"polite\">\n {total > 1 ? lang.lightboxCounter(index + 1, total) : ''}\n </span>\n <IconButton\n {...button({\n icon: ZoomOut,\n 'aria-label': lang.lightboxZoomOut,\n onClick: () => view.zoomOut(),\n })}\n />\n <span {...css.attrs(lightboxStyles.zoom)} aria-hidden=\"true\">\n {Math.round(view.scale * 100)}%\n </span>\n <IconButton\n {...button({\n icon: ZoomIn,\n 'aria-label': lang.lightboxZoomIn,\n onClick: () => view.zoomIn(),\n })}\n />\n <IconButton\n {...button({\n icon: RotateCcw,\n 'aria-label': lang.lightboxRotateLeft,\n onClick: () => rotate(-1),\n })}\n />\n <IconButton\n {...button({\n icon: RotateCw,\n 'aria-label': lang.lightboxRotateRight,\n onClick: () => rotate(1),\n })}\n />\n <IconButton\n {...button({ icon: Undo2, 'aria-label': lang.lightboxReset, onClick: reset })}\n />\n {canDownload && (\n <IconLinkButton\n {...mergePartProps<IconLinkButtonProps>(\n {\n icon: Download,\n variant: 'ghost',\n tone: 'neutral',\n sx: lightboxStyles.button,\n 'aria-label': lang.lightboxDownload,\n href: item.src,\n },\n // 下载而不是打开:download 是文件名,空字符串时用地址里的名字\n { download: item.download ?? '' } as Partial<IconLinkButtonProps>,\n )}\n />\n )}\n <IconButton\n {...button({\n icon: X,\n 'aria-label': lang.close,\n onClick: close,\n })}\n />\n </div>\n <div\n {...mergeAttrs<'div'>({ sx: lightboxStyles.viewport }, propsViewport, {\n ...view.viewportProps,\n ref: _toRef(viewport),\n tabindex: 0,\n role: 'img',\n 'aria-label': item?.alt,\n })}\n >\n {item && (\n <div\n {...css.attrs(\n lightboxStyles.content,\n !view.interacting && lightboxStyles.smooth,\n lightboxDynamicStyles.transform(view.transform),\n lightboxDynamicStyles.box(width, height),\n )}\n >\n <img\n key={item.src}\n src={item.src}\n alt=\"\"\n draggable={false}\n onLoad={onLoad}\n {...css.attrs(\n lightboxStyles.image,\n natural && lightboxDynamicStyles.image(natural.width, natural.height, rotation),\n )}\n />\n </div>\n )}\n </div>\n {total > 1 && (loop || index > 0) && (\n <IconButton\n {...button({\n icon: ChevronLeft,\n propsIcon: { flipInRtl: true },\n radius: 'full',\n sx: [lightboxStyles.button, lightboxStyles.side, lightboxStyles.previous],\n 'aria-label': lang.lightboxPrevious,\n onClick: () => go(-1),\n })}\n />\n )}\n {total > 1 && (loop || index < total - 1) && (\n <IconButton\n {...button({\n icon: ChevronRight,\n propsIcon: { flipInRtl: true },\n radius: 'full',\n sx: [lightboxStyles.button, lightboxStyles.side, lightboxStyles.next],\n 'aria-label': lang.lightboxNext,\n onClick: () => go(1),\n })}\n />\n )}\n {item?.caption != null && (\n <div {...mergeAttrs<'div'>({ sx: lightboxStyles.caption }, propsCaption)}>\n {item.caption}\n </div>\n )}\n </dialog>\n );\n };\n },\n);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Lightbox.js","names":["_vtp_bindable","_vtp_readonlyProp","_vtp_restProps","_vue_shallowRef","_vue_ref","_createVNode","_mergeProps","_createTextVNode"],"sources":["../../src/overlay/Lightbox.tsx"],"sourcesContent":["/**\n * 图片预览(全屏的模态 dialog):深色背景上看大图——缩放、拖动、旋转、还原、下载,几张图时左右切换。\n * Image 的 preview 打开它(同一个 ImageGroup 里的图共用一个),也可以单独用。参考 Ant Design Image 的预览、Element Plus 的 image-viewer。\n *\n * <Lightbox _open={viewing} _index={at} items={[{ src: '/photos/1.jpg', alt: '支架正面' }, …]} />\n *\n * - 平移与缩放建在 usePanZoom 上:拖动平移,滚轮、触控板、捏合以指针为中心缩放,双击放大 / 还原;\n * 打开或换一张时整张图放进视口(不超过原大)。旋转以 90° 为一档,转完重新放进视口。\n * - 键盘:← / → 上一张、下一张,+ / − 缩放,0 还原,R 向右旋转,Esc 关闭;焦点在工具栏与图片区之间循环(模态)。\n * - 读屏:dialog 的名称是“图片预览”,图片区是 role=\"img\"、名称是图片的 alt;换一张时念出“第 2 张,共 5 张”。\n *\n * 部件:Root(dialog)、Toolbar(div)、Counter(span)、Viewport(div)、Image(img)、Previous、Next(IconButton)、Caption(div)。\n */\nimport {\n ChevronLeft,\n ChevronRight,\n Download,\n RotateCcw,\n RotateCw,\n Undo2,\n X,\n ZoomIn,\n ZoomOut,\n} from '@lucide/icons';\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport { nextTick, onBeforeUnmount, type VNodeChild } from 'vue';\nimport { IconButton, type IconButtonProps } from '../base/IconButton';\nimport { IconLinkButton, type IconLinkButtonProps } from '../base/IconLinkButton';\nimport { useLang } from '../config/context';\nimport { clamp } from '../utils/data/number';\nimport { openSetter, type OpenChangeProps, type OpenChangeReasons } from '../utils/dom/open-change';\nimport { openModalDialog } from '../utils/dom/overlay';\nimport { usePanZoom } from '../utils/dom/pan-zoom';\nimport { mergeAttrs, mergePartProps, type PartProps } from '../utils/props';\nimport { transitions } from '../utils/style/transitions';\n\n/** 一张图 */\nexport interface LightboxItem {\n /** 大图的地址 */\n src: string;\n /** 图片的说明(读屏念它) */\n alt: string;\n /** 图片下方的说明文字 */\n caption?: VNodeChild;\n /** 下载时的文件名;false 不显示下载按钮 */\n download?: string | false;\n}\n\n/** 打开、关联由组件管理;未声明的属性落到 dialog 上 */\nexport interface LightboxProps\n extends PartProps<'dialog', 'open' | 'aria-label' | 'aria-labelledby'>, OpenChangeProps {\n /** 是否打开:父级写 _open={x};Esc、关闭按钮会关上并写回 */\n _open?: boolean;\n /** 图片 */\n items: readonly LightboxItem[];\n /** 当前第几张,从 0 开始:父级可以写 _index={x} */\n _index?: number;\n /** 到头之后从另一头继续,默认 false */\n loop?: boolean;\n /** 工具栏按钮的部件属性 */\n propsButton?: Partial<IconButtonProps>;\n /** 图片区(视口)的部件属性 */\n propsViewport?: PartProps<'div'>;\n /** 说明文字的部件属性 */\n propsCaption?: PartProps<'div'>;\n}\n\n/** Lightbox 的样式:root、toolbar、counter、viewport、content、image、side、previous、next、caption、button */\nexport const lightboxStyles = css.create({\n root: {\n // 占满视口:原生 dialog 默认有最大宽高与边框\n boxSizing: 'border-box',\n width: '100vw',\n height: '100dvh',\n maxWidth: 'none',\n maxHeight: 'none',\n margin: 0,\n padding: 0,\n borderWidth: 0,\n display: { default: 'none', ':modal': 'flex' },\n flexDirection: 'column',\n // 图片预览总是深色背景:不跟主题变(看图片本身的颜色)\n backgroundColor: 'rgb(0 0 0 / 0.86)',\n color: 'white',\n colorScheme: 'dark',\n },\n toolbar: {\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'flex-end',\n gap: '_xs',\n paddingBlock: '_sm',\n paddingInline: '_md',\n flexShrink: 0,\n },\n counter: {\n marginInlineEnd: 'auto',\n fontVariantNumeric: 'tabular-nums',\n fontSize: '_sm',\n opacity: 0.8,\n },\n zoom: {\n minWidth: '3.5em',\n textAlign: 'center',\n fontSize: '_sm',\n fontVariantNumeric: 'tabular-nums',\n },\n viewport: {\n position: 'relative',\n flexGrow: 1,\n minHeight: 0,\n overflow: 'hidden',\n cursor: { default: 'grab', ':active': 'grabbing' },\n outline: 'none',\n },\n // 平移与缩放作用在它上面(transform-origin 左上角);它的大小是旋转之后图片占的框\n content: { position: 'absolute', top: 0, left: 0, transformOrigin: '0 0' },\n // 图片以框的中心旋转\n image: {\n position: 'absolute',\n top: '50%',\n left: '50%',\n maxWidth: 'none',\n userSelect: 'none',\n pointerEvents: 'none',\n },\n smooth: { transitionProperty: 'transform', transitionDuration: '_md' },\n side: {\n position: 'absolute',\n top: '50%',\n translate: '0 -50%',\n zIndex: 1,\n },\n previous: { insetInlineStart: '_md' },\n next: { insetInlineEnd: '_md' },\n caption: {\n flexShrink: 0,\n paddingBlock: '_md',\n paddingInline: '_xl',\n textAlign: 'center',\n fontSize: '_sm',\n opacity: 0.9,\n },\n // 深色背景上的按钮:文字白色,悬停时浅一点的底\n button: {\n color: 'white',\n backgroundColor: { default: 'transparent', ':hover': 'rgb(255 255 255 / 0.12)' },\n },\n});\n\n/** Lightbox 的动态样式:平移缩放、框的大小、图片的大小与旋转 */\nexport const lightboxDynamicStyles = css.create({\n transform: (transform: string) => ({ transform }),\n box: (width: number, height: number) => ({ width: `${width}px`, height: `${height}px` }),\n image: (width: number, height: number, degrees: number) => ({\n width: `${width}px`,\n height: `${height}px`,\n transform: `translate(-50%, -50%) rotate(${degrees}deg)`,\n }),\n});\n\n/** 图片预览:全屏 dialog,缩放、拖动、旋转、左右切换 */\nexport const Lightbox = _component(\n ({\n _open: open = _bindable(false),\n onOpenChange,\n items,\n _index: index = _bindable(0),\n loop = false,\n propsButton,\n propsViewport,\n propsCaption,\n ...rest\n }: LightboxProps) => {\n const lang = useLang();\n let dialog = _state.raw<HTMLDialogElement | null>(null);\n let viewport = _state.raw<HTMLElement | null>(null);\n // 当前图片的原始大小(加载后知道)与旋转(0、90、180、270)\n let natural = _state<{ width: number; height: number } | null>(null);\n let rotation = _state(0);\n let release: (() => void) | undefined;\n let closing: keyof OpenChangeReasons | undefined;\n const setOpen = openSetter(\n () => open,\n (next) => (open = next),\n () => onOpenChange,\n );\n\n const current = () => items[clamp(index, 0, Math.max(0, items.length - 1))];\n /** 旋转之后图片占的框 */\n const box = () => {\n if (!natural) return { width: 0, height: 0 };\n const sideways = rotation % 180 !== 0;\n return sideways\n ? { width: natural.height, height: natural.width }\n : { width: natural.width, height: natural.height };\n };\n\n const view = usePanZoom({\n viewport: () => viewport,\n min: 0.05,\n max: 8,\n bounds: () => (natural ? box() : undefined),\n initial: () => ({ x: 0, y: 0, scale: 1 }),\n });\n\n /** 整张图放进视口(不超过原大):打开、换一张、旋转之后 */\n function fitImage() {\n if (!natural) return;\n view.fit(box(), 24);\n }\n\n function go(step: 1 | -1) {\n if (items.length < 2) return;\n const next = index + step;\n if (next < 0 || next >= items.length) {\n if (!loop) return;\n index = (next + items.length) % items.length;\n } else index = next;\n }\n\n function rotate(step: 1 | -1) {\n rotation = (rotation + step * 90 + 360) % 360;\n void nextTick(fitImage);\n }\n\n function reset() {\n rotation = 0;\n void nextTick(fitImage);\n }\n\n function onLoad(event: Event) {\n const image = event.currentTarget as HTMLImageElement;\n natural = { width: image.naturalWidth, height: image.naturalHeight };\n void nextTick(fitImage);\n }\n\n function onKeydown(event: KeyboardEvent) {\n if (event.altKey || event.ctrlKey || event.metaKey) return;\n if (event.key === 'ArrowLeft' || event.key === 'ArrowRight') {\n // 从右到左排版时左右对调\n const rtl = dialog ? getComputedStyle(dialog).direction === 'rtl' : false;\n go((event.key === 'ArrowRight') !== rtl ? 1 : -1);\n event.preventDefault();\n } else if (event.key === 'r' || event.key === 'R') {\n rotate(1);\n event.preventDefault();\n }\n }\n\n function close() {\n closing = 'close';\n dialog?.close();\n }\n\n // 不论怎么关上(Esc、关闭按钮、open 改成 false),原生 close 事件都会来\n function onClose() {\n release?.();\n release = undefined;\n const reason = closing ?? 'escape';\n closing = undefined;\n setOpen(false, reason);\n }\n\n // 换一张:原图大小重新量,方向回正\n _effect(() => {\n void current()?.src;\n natural = null;\n rotation = 0;\n });\n _effect(() => {\n if (open && dialog && !dialog.open) {\n release = openModalDialog(dialog);\n void nextTick(() => viewport?.focus());\n } else if (!open && dialog?.open) dialog.close();\n });\n // 预先加载前后两张:切换时不闪\n _effect(() => {\n if (!open) return;\n for (const step of [-1, 1]) {\n const neighbour = items[index + step];\n if (neighbour) new Image().src = neighbour.src;\n }\n });\n onBeforeUnmount(() => release?.());\n\n const button = (props: Partial<IconButtonProps>) =>\n mergePartProps<IconButtonProps>(\n {\n icon: X,\n variant: 'ghost',\n tone: 'neutral',\n sx: lightboxStyles.button,\n 'aria-label': '',\n },\n propsButton,\n props,\n );\n\n return () => {\n const item = current();\n const total = items.length;\n const { width, height } = box();\n const canDownload = item && item.download !== false;\n return (\n <dialog\n {...mergeAttrs<'dialog'>({ sx: [lightboxStyles.root, transitions.backdrop] }, rest, {\n ref: _toRef(dialog),\n 'aria-label': lang.lightboxLabel,\n onClose,\n onKeydown,\n })}\n >\n <div {...css.attrs(lightboxStyles.toolbar)}>\n <span {...css.attrs(lightboxStyles.counter)} aria-live=\"polite\">\n {total > 1 ? lang.lightboxCounter(index + 1, total) : ''}\n </span>\n <IconButton\n {...button({\n icon: ZoomOut,\n 'aria-label': lang.lightboxZoomOut,\n onClick: () => view.zoomOut(),\n })}\n />\n <span {...css.attrs(lightboxStyles.zoom)} aria-hidden=\"true\">\n {Math.round(view.scale * 100)}%\n </span>\n <IconButton\n {...button({\n icon: ZoomIn,\n 'aria-label': lang.lightboxZoomIn,\n onClick: () => view.zoomIn(),\n })}\n />\n <IconButton\n {...button({\n icon: RotateCcw,\n 'aria-label': lang.lightboxRotateLeft,\n onClick: () => rotate(-1),\n })}\n />\n <IconButton\n {...button({\n icon: RotateCw,\n 'aria-label': lang.lightboxRotateRight,\n onClick: () => rotate(1),\n })}\n />\n <IconButton\n {...button({ icon: Undo2, 'aria-label': lang.lightboxReset, onClick: reset })}\n />\n {canDownload && (\n <IconLinkButton\n {...mergePartProps<IconLinkButtonProps>(\n {\n icon: Download,\n variant: 'ghost',\n tone: 'neutral',\n sx: lightboxStyles.button,\n 'aria-label': lang.lightboxDownload,\n href: item.src,\n },\n // 下载而不是打开:download 是文件名,空字符串时用地址里的名字\n { download: item.download ?? '' } as Partial<IconLinkButtonProps>,\n )}\n />\n )}\n <IconButton\n {...button({\n icon: X,\n 'aria-label': lang.close,\n onClick: close,\n })}\n />\n </div>\n <div\n {...mergeAttrs<'div'>({ sx: lightboxStyles.viewport }, propsViewport, {\n ...view.viewportProps,\n ref: _toRef(viewport),\n tabindex: 0,\n role: 'img',\n 'aria-label': item?.alt,\n })}\n >\n {item && (\n <div\n {...css.attrs(\n lightboxStyles.content,\n !view.interacting && lightboxStyles.smooth,\n lightboxDynamicStyles.transform(view.transform),\n lightboxDynamicStyles.box(width, height),\n )}\n >\n <img\n key={item.src}\n src={item.src}\n alt=\"\"\n draggable={false}\n onLoad={onLoad}\n {...css.attrs(\n lightboxStyles.image,\n natural && lightboxDynamicStyles.image(natural.width, natural.height, rotation),\n )}\n />\n </div>\n )}\n </div>\n {total > 1 && (loop || index > 0) && (\n <IconButton\n {...button({\n icon: ChevronLeft,\n propsIcon: { flipInRtl: true },\n radius: 'full',\n sx: [lightboxStyles.button, lightboxStyles.side, lightboxStyles.previous],\n 'aria-label': lang.lightboxPrevious,\n onClick: () => go(-1),\n })}\n />\n )}\n {total > 1 && (loop || index < total - 1) && (\n <IconButton\n {...button({\n icon: ChevronRight,\n propsIcon: { flipInRtl: true },\n radius: 'full',\n sx: [lightboxStyles.button, lightboxStyles.side, lightboxStyles.next],\n 'aria-label': lang.lightboxNext,\n onClick: () => go(1),\n })}\n />\n )}\n {item?.caption != null && (\n <div {...mergeAttrs<'div'>({ sx: lightboxStyles.caption }, propsCaption)}>\n {item.caption}\n </div>\n )}\n </dialog>\n );\n };\n },\n);\n"],"mappings":"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"}
|