@zerodep-dev/vue-tsx-pro-ui 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/base/Badge.js +3 -3
- package/dist/base/Badge.js.map +1 -1
- package/dist/base/Can.d.ts +12 -0
- package/dist/base/Can.js +20 -0
- package/dist/base/Can.js.map +1 -0
- package/dist/base/ClientOnly.d.ts +10 -0
- package/dist/base/ClientOnly.js +18 -0
- package/dist/base/ClientOnly.js.map +1 -0
- package/dist/base/CloseButton.js +3 -3
- package/dist/base/CloseButton.js.map +1 -1
- package/dist/base/CodeBlock.js +5 -5
- package/dist/base/CodeBlock.js.map +1 -1
- package/dist/base/CopyButton.js +3 -3
- package/dist/base/CopyButton.js.map +1 -1
- package/dist/base/Icon.js +3 -3
- package/dist/base/Icon.js.map +1 -1
- package/dist/base/Indicator.js +3 -3
- package/dist/base/Indicator.js.map +1 -1
- package/dist/base/StatusDot.js +3 -3
- package/dist/base/StatusDot.js.map +1 -1
- package/dist/base/Tag.js +5 -5
- package/dist/base/Tag.js.map +1 -1
- package/dist/base/button-core.js +4 -4
- package/dist/base/button-core.js.map +1 -1
- package/dist/charts/AreaChart.js +3 -3
- package/dist/charts/AreaChart.js.map +1 -1
- package/dist/charts/BarChart.js +3 -3
- package/dist/charts/BarChart.js.map +1 -1
- package/dist/charts/ChordChart.js +3 -3
- package/dist/charts/ChordChart.js.map +1 -1
- package/dist/charts/ComboChart.js +3 -3
- package/dist/charts/ComboChart.js.map +1 -1
- package/dist/charts/DonutChart.js +3 -3
- package/dist/charts/DonutChart.js.map +1 -1
- package/dist/charts/FunnelChart.js +5 -5
- package/dist/charts/FunnelChart.js.map +1 -1
- package/dist/charts/GraphChart.js +3 -3
- package/dist/charts/GraphChart.js.map +1 -1
- package/dist/charts/LineChart.js +3 -3
- package/dist/charts/LineChart.js.map +1 -1
- package/dist/charts/MapChart.js +7 -7
- package/dist/charts/MapChart.js.map +1 -1
- package/dist/charts/RadarChart.js +16 -15
- package/dist/charts/RadarChart.js.map +1 -1
- package/dist/charts/SankeyChart.js +3 -3
- package/dist/charts/SankeyChart.js.map +1 -1
- package/dist/charts/ScatterChart.js +3 -3
- package/dist/charts/ScatterChart.js.map +1 -1
- package/dist/charts/TimelineChart.js +6 -5
- package/dist/charts/TimelineChart.js.map +1 -1
- package/dist/charts/TreemapChart.js +3 -3
- package/dist/charts/TreemapChart.js.map +1 -1
- package/dist/charts/WaterfallChart.js +3 -3
- package/dist/charts/WaterfallChart.js.map +1 -1
- package/dist/charts/XYChart.js +9 -7
- package/dist/charts/XYChart.js.map +1 -1
- package/dist/charts/frame.js +10 -8
- package/dist/charts/frame.js.map +1 -1
- package/dist/charts/single.js +4 -3
- package/dist/charts/single.js.map +1 -1
- package/dist/config/Provider.d.ts +3 -1
- package/dist/config/Provider.js +9 -4
- package/dist/config/Provider.js.map +1 -1
- package/dist/config/context.d.ts +18 -0
- package/dist/config/context.js +16 -1
- package/dist/config/context.js.map +1 -1
- package/dist/config/createUi.d.ts +20 -3
- package/dist/config/createUi.js +80 -49
- package/dist/config/createUi.js.map +1 -1
- package/dist/config/layout/preference.d.ts +43 -0
- package/dist/config/layout/preference.js +108 -0
- package/dist/config/layout/preference.js.map +1 -0
- package/dist/date/Calendar.js +3 -3
- package/dist/date/Calendar.js.map +1 -1
- package/dist/date/CalendarBoard.js +9 -9
- package/dist/date/CalendarBoard.js.map +1 -1
- package/dist/date/CalendarView.js +7 -7
- package/dist/date/CalendarView.js.map +1 -1
- package/dist/date/DateField.js +3 -3
- package/dist/date/DateField.js.map +1 -1
- package/dist/date/DatePicker.js +5 -5
- package/dist/date/DatePicker.js.map +1 -1
- package/dist/date/DateRangePicker.js +5 -5
- package/dist/date/DateRangePicker.js.map +1 -1
- package/dist/date/DateSegmentGroup.js +5 -5
- package/dist/date/DateSegmentGroup.js.map +1 -1
- package/dist/date/QuarterCalendar.js +6 -6
- package/dist/date/QuarterCalendar.js.map +1 -1
- package/dist/date/RangeCalendar.js +3 -3
- package/dist/date/RangeCalendar.js.map +1 -1
- package/dist/date/SegmentedField.js +3 -3
- package/dist/date/SegmentedField.js.map +1 -1
- package/dist/date/TimeField.js +3 -3
- package/dist/date/TimeField.js.map +1 -1
- package/dist/date/TimePicker.js +4 -4
- package/dist/date/TimePicker.js.map +1 -1
- package/dist/date/date-segments.js +1 -1
- package/dist/date/date-segments.js.map +1 -1
- package/dist/date/picker-popover.js +3 -2
- package/dist/date/picker-popover.js.map +1 -1
- package/dist/diagram/Diagram.js +12 -12
- package/dist/diagram/Diagram.js.map +1 -1
- package/dist/diagram/DiagramLanes.js +3 -3
- package/dist/diagram/DiagramLanes.js.map +1 -1
- package/dist/diagram/DiagramMinimap.js +4 -4
- package/dist/diagram/DiagramMinimap.js.map +1 -1
- package/dist/display/Accordion.js +6 -6
- package/dist/display/Accordion.js.map +1 -1
- package/dist/display/AnimatedNumber.js +4 -4
- package/dist/display/AnimatedNumber.js.map +1 -1
- package/dist/display/Avatar.js +4 -4
- package/dist/display/Avatar.js.map +1 -1
- package/dist/display/Checklist.js +7 -7
- package/dist/display/Checklist.js.map +1 -1
- package/dist/display/Collapse.js +8 -5
- package/dist/display/Collapse.js.map +1 -1
- package/dist/display/Collapsible.js +3 -3
- package/dist/display/Collapsible.js.map +1 -1
- package/dist/display/Descriptions.js +5 -5
- package/dist/display/Descriptions.js.map +1 -1
- package/dist/display/Ellipsis.js +3 -3
- package/dist/display/Ellipsis.js.map +1 -1
- package/dist/display/FieldCompare.js +4 -4
- package/dist/display/FieldCompare.js.map +1 -1
- package/dist/display/Gauge.js +11 -11
- package/dist/display/Gauge.js.map +1 -1
- package/dist/display/Image.js +4 -4
- package/dist/display/Image.js.map +1 -1
- package/dist/display/Marquee.js +3 -3
- package/dist/display/Marquee.js.map +1 -1
- package/dist/display/ProportionBar.js +4 -4
- package/dist/display/ProportionBar.js.map +1 -1
- package/dist/display/Sortable.js +4 -4
- package/dist/display/Sortable.js.map +1 -1
- package/dist/display/Sparkline.js +6 -6
- package/dist/display/Sparkline.js.map +1 -1
- package/dist/display/Stat.js +3 -3
- package/dist/display/Stat.js.map +1 -1
- package/dist/display/Timeline.js +7 -7
- package/dist/display/Timeline.js.map +1 -1
- package/dist/display/Tree.js +10 -10
- package/dist/display/Tree.js.map +1 -1
- package/dist/display/VirtualList.js +7 -7
- package/dist/display/VirtualList.js.map +1 -1
- package/dist/display/Watermark.js +3 -3
- package/dist/display/Watermark.js.map +1 -1
- package/dist/docs-kit/CodeGroup.js +5 -5
- package/dist/docs-kit/CodeGroup.js.map +1 -1
- package/dist/docs-kit/CodePreview.js +6 -6
- package/dist/docs-kit/CodePreview.js.map +1 -1
- package/dist/docs-kit/PropsTable.js +4 -4
- package/dist/docs-kit/PropsTable.js.map +1 -1
- package/dist/feedback/Alert.js +5 -5
- package/dist/feedback/Alert.js.map +1 -1
- package/dist/feedback/Banner.js +5 -5
- package/dist/feedback/Banner.js.map +1 -1
- package/dist/feedback/Empty.js +4 -4
- package/dist/feedback/Empty.js.map +1 -1
- package/dist/feedback/LoadingBar.js +20 -13
- package/dist/feedback/LoadingBar.js.map +1 -1
- package/dist/feedback/LoadingOverlay.js +3 -3
- package/dist/feedback/LoadingOverlay.js.map +1 -1
- package/dist/feedback/Progress.js +14 -14
- package/dist/feedback/Progress.js.map +1 -1
- package/dist/feedback/Result.js +4 -4
- package/dist/feedback/Result.js.map +1 -1
- package/dist/feedback/SkeletonText.js +3 -3
- package/dist/feedback/SkeletonText.js.map +1 -1
- package/dist/feedback/Toast.js +6 -6
- package/dist/feedback/Toast.js.map +1 -1
- package/dist/form/Field.js +7 -7
- package/dist/form/Field.js.map +1 -1
- package/dist/form/Form.js +3 -3
- package/dist/form/Form.js.map +1 -1
- package/dist/form/FormErrorSummary.js +3 -3
- package/dist/form/FormErrorSummary.js.map +1 -1
- package/dist/form/FormSection.js +6 -6
- package/dist/form/FormSection.js.map +1 -1
- package/dist/index.d.ts +11 -6
- package/dist/index.js +10 -5
- package/dist/input/Autocomplete.js +3 -3
- package/dist/input/Autocomplete.js.map +1 -1
- package/dist/input/Cascader.js +7 -7
- package/dist/input/Cascader.js.map +1 -1
- package/dist/input/CharacterCount.js +3 -3
- package/dist/input/CharacterCount.js.map +1 -1
- package/dist/input/Checkbox.js +5 -5
- package/dist/input/Checkbox.js.map +1 -1
- package/dist/input/ColorField.js +4 -4
- package/dist/input/ColorField.js.map +1 -1
- package/dist/input/Combobox.js +4 -4
- package/dist/input/Combobox.js.map +1 -1
- package/dist/input/ConditionBuilder.js +15 -14
- package/dist/input/ConditionBuilder.js.map +1 -1
- package/dist/input/FileDrop.js +3 -3
- package/dist/input/FileDrop.js.map +1 -1
- package/dist/input/Listbox.js +9 -9
- package/dist/input/Listbox.js.map +1 -1
- package/dist/input/Mentions.js +3 -3
- package/dist/input/Mentions.js.map +1 -1
- package/dist/input/NumberField.js +3 -3
- package/dist/input/NumberField.js.map +1 -1
- package/dist/input/NumberRangeField.js +5 -5
- package/dist/input/NumberRangeField.js.map +1 -1
- package/dist/input/OtpField.js +5 -5
- package/dist/input/OtpField.js.map +1 -1
- package/dist/input/PeoplePicker.js +6 -6
- package/dist/input/PeoplePicker.js.map +1 -1
- package/dist/input/PeoplePickerPanel.js +4 -4
- package/dist/input/PeoplePickerPanel.js.map +1 -1
- package/dist/input/Radio.js +4 -4
- package/dist/input/Radio.js.map +1 -1
- package/dist/input/Rating.js +5 -5
- package/dist/input/Rating.js.map +1 -1
- package/dist/input/Select.js +4 -4
- package/dist/input/Select.js.map +1 -1
- package/dist/input/SelectedTags.js +4 -4
- package/dist/input/SelectedTags.js.map +1 -1
- package/dist/input/Slider.js +6 -6
- package/dist/input/Slider.js.map +1 -1
- package/dist/input/Switch.js +3 -3
- package/dist/input/Switch.js.map +1 -1
- package/dist/input/TagsInput.js +5 -5
- package/dist/input/TagsInput.js.map +1 -1
- package/dist/input/TextField.js +5 -5
- package/dist/input/TextField.js.map +1 -1
- package/dist/input/Textarea.js +4 -4
- package/dist/input/Textarea.js.map +1 -1
- package/dist/input/ToggleGroup.js +4 -4
- package/dist/input/ToggleGroup.js.map +1 -1
- package/dist/input/Transfer.js +8 -8
- package/dist/input/Transfer.js.map +1 -1
- package/dist/input/TreeSelect.js +6 -6
- package/dist/input/TreeSelect.js.map +1 -1
- package/dist/input/Upload.js +8 -8
- package/dist/input/Upload.js.map +1 -1
- package/dist/layout/Affix.js +4 -4
- package/dist/layout/Affix.js.map +1 -1
- package/dist/layout/ResizeHandle.d.ts +6 -2
- package/dist/layout/ResizeHandle.js +32 -9
- package/dist/layout/ResizeHandle.js.map +1 -1
- package/dist/layout/ScrollArea.js +3 -3
- package/dist/layout/ScrollArea.js.map +1 -1
- package/dist/layout/Separator.js +3 -3
- package/dist/layout/Separator.js.map +1 -1
- package/dist/layout/Splitter.js +3 -3
- package/dist/layout/Splitter.js.map +1 -1
- package/dist/layout/ViewportFill.d.ts +2 -2
- package/dist/layout/ViewportFill.js +4 -4
- package/dist/layout/ViewportFill.js.map +1 -1
- package/dist/navigation/Breadcrumbs.js +4 -4
- package/dist/navigation/Breadcrumbs.js.map +1 -1
- package/dist/navigation/NavBar.js +6 -6
- package/dist/navigation/NavBar.js.map +1 -1
- package/dist/navigation/NavList.d.ts +1 -0
- package/dist/navigation/NavList.js +20 -12
- package/dist/navigation/NavList.js.map +1 -1
- package/dist/navigation/NavMenu.d.ts +27 -0
- package/dist/navigation/NavMenu.js +54 -0
- package/dist/navigation/NavMenu.js.map +1 -0
- package/dist/navigation/PageJump.js +3 -3
- package/dist/navigation/PageJump.js.map +1 -1
- package/dist/navigation/PageSizeSelect.js +4 -4
- package/dist/navigation/PageSizeSelect.js.map +1 -1
- package/dist/navigation/Pagination.js +6 -6
- package/dist/navigation/Pagination.js.map +1 -1
- package/dist/navigation/PaginationBar.js +5 -5
- package/dist/navigation/PaginationBar.js.map +1 -1
- package/dist/navigation/Steps.js +4 -4
- package/dist/navigation/Steps.js.map +1 -1
- package/dist/navigation/TableOfContents.js +5 -5
- package/dist/navigation/TableOfContents.js.map +1 -1
- package/dist/navigation/Tabs.js +7 -7
- package/dist/navigation/Tabs.js.map +1 -1
- package/dist/navigation/Toolbar.js +3 -3
- package/dist/navigation/Toolbar.js.map +1 -1
- package/dist/overlay/AlertDialog.js +4 -4
- package/dist/overlay/AlertDialog.js.map +1 -1
- package/dist/overlay/CommandPalette.js +4 -4
- package/dist/overlay/CommandPalette.js.map +1 -1
- package/dist/overlay/ContextMenu.js +3 -3
- package/dist/overlay/ContextMenu.js.map +1 -1
- package/dist/overlay/Dialog.js +4 -4
- package/dist/overlay/Dialog.js.map +1 -1
- package/dist/overlay/Drawer.js +5 -5
- package/dist/overlay/Drawer.js.map +1 -1
- package/dist/overlay/HoverCard.js +5 -3
- package/dist/overlay/HoverCard.js.map +1 -1
- package/dist/overlay/Lightbox.js +5 -5
- package/dist/overlay/Lightbox.js.map +1 -1
- package/dist/overlay/Menu.js +7 -7
- package/dist/overlay/Menu.js.map +1 -1
- package/dist/overlay/ModalCore.js +9 -9
- package/dist/overlay/ModalCore.js.map +1 -1
- package/dist/overlay/Popconfirm.js +6 -6
- package/dist/overlay/Popconfirm.js.map +1 -1
- package/dist/overlay/Popover.js +5 -3
- package/dist/overlay/Popover.js.map +1 -1
- package/dist/overlay/Tooltip.js +5 -3
- package/dist/overlay/Tooltip.js.map +1 -1
- package/dist/overlay/Tour.js +8 -8
- package/dist/overlay/Tour.js.map +1 -1
- package/dist/overlay/detached.d.ts +2 -0
- package/dist/overlay/detached.js +73 -42
- package/dist/overlay/detached.js.map +1 -1
- package/dist/patterns/ActionBar.js +3 -3
- package/dist/patterns/ActionBar.js.map +1 -1
- package/dist/patterns/AppShell.d.ts +28 -1
- package/dist/patterns/AppShell.js +158 -52
- package/dist/patterns/AppShell.js.map +1 -1
- package/dist/patterns/AuthLayout.d.ts +28 -0
- package/dist/patterns/AuthLayout.js +77 -0
- package/dist/patterns/AuthLayout.js.map +1 -0
- package/dist/patterns/CommentThread.js +9 -9
- package/dist/patterns/CommentThread.js.map +1 -1
- package/dist/patterns/DataImport.js +4 -4
- package/dist/patterns/DataImport.js.map +1 -1
- package/dist/patterns/DeadlineBadge.js +3 -3
- package/dist/patterns/DeadlineBadge.js.map +1 -1
- package/dist/patterns/FilterBar.d.ts +11 -1
- package/dist/patterns/FilterBar.js +39 -10
- package/dist/patterns/FilterBar.js.map +1 -1
- package/dist/patterns/FormDialog.d.ts +18 -0
- package/dist/patterns/FormDialog.js +46 -0
- package/dist/patterns/FormDialog.js.map +1 -0
- package/dist/patterns/KanbanBoard.js +8 -8
- package/dist/patterns/KanbanBoard.js.map +1 -1
- package/dist/patterns/LayoutSwitcher.d.ts +12 -0
- package/dist/patterns/LayoutSwitcher.js +79 -0
- package/dist/patterns/LayoutSwitcher.js.map +1 -0
- package/dist/patterns/NotificationCenter.js +8 -8
- package/dist/patterns/NotificationCenter.js.map +1 -1
- package/dist/patterns/PageTabs.d.ts +12 -7
- package/dist/patterns/PageTabs.js +41 -29
- package/dist/patterns/PageTabs.js.map +1 -1
- package/dist/patterns/QueryTable.d.ts +7 -1
- package/dist/patterns/QueryTable.js +116 -81
- package/dist/patterns/QueryTable.js.map +1 -1
- package/dist/patterns/SlideCaptcha.d.ts +80 -0
- package/dist/patterns/SlideCaptcha.js +354 -0
- package/dist/patterns/SlideCaptcha.js.map +1 -0
- package/dist/patterns/StepsForm.js +7 -7
- package/dist/patterns/StepsForm.js.map +1 -1
- package/dist/patterns/ThemeSwitcher.js +3 -3
- package/dist/patterns/ThemeSwitcher.js.map +1 -1
- package/dist/patterns/index.d.ts +7 -3
- package/dist/patterns/index.js +5 -1
- package/dist/patterns/messages.d.ts +37 -1
- package/dist/patterns/messages.js +38 -2
- package/dist/patterns/messages.js.map +1 -1
- package/dist/table/ColumnHeaderActions.js +3 -3
- package/dist/table/ColumnHeaderActions.js.map +1 -1
- package/dist/table/ColumnResizer.js +3 -3
- package/dist/table/ColumnResizer.js.map +1 -1
- package/dist/table/ColumnSettings.js +5 -5
- package/dist/table/ColumnSettings.js.map +1 -1
- package/dist/table/ColumnTable.js +8 -8
- package/dist/table/ColumnTable.js.map +1 -1
- package/dist/table/DataTable.js +8 -8
- package/dist/table/DataTable.js.map +1 -1
- package/dist/table/DataTableView.js +21 -21
- package/dist/table/DataTableView.js.map +1 -1
- package/dist/table/PivotTable.js +3 -3
- package/dist/table/PivotTable.js.map +1 -1
- package/dist/table/SelectionBar.js +4 -4
- package/dist/table/SelectionBar.js.map +1 -1
- package/dist/table/TreeDataTable.js +5 -5
- package/dist/table/TreeDataTable.js.map +1 -1
- package/dist/theme-customize/ThemeCustomizer.js +7 -6
- package/dist/theme-customize/ThemeCustomizer.js.map +1 -1
- package/dist/theme-customize/ThemeThumbnail.js +3 -3
- package/dist/theme-customize/ThemeThumbnail.js.map +1 -1
- package/dist/three/background/AuroraBackground.js +3 -3
- package/dist/three/background/AuroraBackground.js.map +1 -1
- package/dist/three/background/MeshGradient.js +3 -3
- package/dist/three/background/MeshGradient.js.map +1 -1
- package/dist/three/background/ShaderBackground.js +3 -3
- package/dist/three/background/ShaderBackground.js.map +1 -1
- package/dist/three/core/EffectSurface.js +3 -3
- package/dist/three/core/EffectSurface.js.map +1 -1
- package/dist/three/core/ThreeCanvas.js +4 -4
- package/dist/three/core/ThreeCanvas.js.map +1 -1
- package/dist/three/effects/Confetti.js +3 -3
- package/dist/three/effects/Confetti.js.map +1 -1
- package/dist/three/effects/ParticleField.js +3 -3
- package/dist/three/effects/ParticleField.js.map +1 -1
- package/dist/three/effects/WaveGrid.js +3 -3
- package/dist/three/effects/WaveGrid.js.map +1 -1
- package/dist/three/globe/Globe.js +3 -3
- package/dist/three/globe/Globe.js.map +1 -1
- package/dist/three/model/ModelViewer.js +3 -3
- package/dist/three/model/ModelViewer.js.map +1 -1
- package/dist/utils/data/number.d.ts +5 -0
- package/dist/utils/data/number.js +8 -1
- package/dist/utils/data/number.js.map +1 -1
- package/dist/utils/dom/element.d.ts +3 -1
- package/dist/utils/dom/element.js +8 -4
- package/dist/utils/dom/element.js.map +1 -1
- package/dist/utils/dom/fullscreen.d.ts +1 -1
- package/dist/utils/dom/fullscreen.js +12 -8
- package/dist/utils/dom/fullscreen.js.map +1 -1
- package/dist/utils/dom/mounted.d.ts +7 -0
- package/dist/utils/dom/mounted.js +16 -0
- package/dist/utils/dom/mounted.js.map +1 -0
- package/dist/utils/dom/overflow.js +2 -1
- package/dist/utils/dom/overflow.js.map +1 -1
- package/dist/utils/dom/pan-zoom.js +2 -1
- package/dist/utils/dom/pan-zoom.js.map +1 -1
- package/dist/utils/props.js +3 -13
- package/dist/utils/props.js.map +1 -1
- package/dist/utils/request/http.d.ts +12 -0
- package/dist/utils/request/http.js +6 -2
- package/dist/utils/request/http.js.map +1 -1
- package/dist/utils/request/use-request.js +4 -2
- package/dist/utils/request/use-request.js.map +1 -1
- package/package.json +2 -2
package/dist/base/Badge.js
CHANGED
|
@@ -3,8 +3,8 @@ import { mergeAttrs, mergePartProps } from "../utils/props.js";
|
|
|
3
3
|
import { badgeParts, badgeRecipe } from "./badge-styles.js";
|
|
4
4
|
import { useRecipe } from "../config/styles.js";
|
|
5
5
|
import { Icon } from "./Icon.js";
|
|
6
|
-
import { createVNode
|
|
7
|
-
import { _component, childrenProp, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
6
|
+
import { createVNode } from "vue";
|
|
7
|
+
import { _component, childrenProp, mergeJsxProps, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
8
8
|
//#region src/base/Badge.tsx
|
|
9
9
|
/** 未声明的属性落到 span 上 */
|
|
10
10
|
/** 标记(span):状态色调 × 样式,字号默认 xs */
|
|
@@ -52,7 +52,7 @@ var StatusBadge = /*#__PURE__*/ _component((_props2, _ctx2) => {
|
|
|
52
52
|
const rest = restProps(_props2, _ctx2, true);
|
|
53
53
|
return () => {
|
|
54
54
|
const status = statuses.value.get(value.value);
|
|
55
|
-
return createVNode(Badge,
|
|
55
|
+
return createVNode(Badge, mergeJsxProps(mergePartProps({
|
|
56
56
|
tone: status.tone,
|
|
57
57
|
icon: status.icon,
|
|
58
58
|
dot: dot.value
|
package/dist/base/Badge.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Badge.js","names":["_vtp_readonlyProp","_vtp_childrenProp","_vtp_restProps","_createVNode","_mergeProps"],"sources":["../../src/base/Badge.tsx"],"sourcesContent":["/**\n * 标记(span):状态、计数、分类等短文字。只展示,不可交互;可以关闭或选择的用 Tag。\n *\n * <Badge tone=\"success\">已发货</Badge>\n * <Badge variant=\"solid\" tone=\"danger\" radius=\"full\">12</Badge>\n * <Badge tone=\"warning\" dot>待审核</Badge> 文字前一个同色的圆点:表格里的状态\n * <StatusBadge statuses={taskStatuses} value=\"verifying\" /> 名字与色调取自状态映射(createStatusMap)\n *\n * 部件:Root(span)、Icon、Dot(span,dot 时)。\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport type { VNodeChild } from 'vue';\nimport { useDefaults } from '../config/defaults';\nimport type { StatusMap } from '../utils/data/status';\nimport { mergeAttrs, mergePartProps, type PartProps } from '../utils/props';\nimport { badgeParts, badgeRecipe, type BadgeOptions } from './badge-styles';\nimport { useRecipe } from '../config/styles';\nimport { Icon, type IconData, type IconProps } from './Icon';\n\n/** 未声明的属性落到 span 上 */\nexport interface BadgeProps extends PartProps<'span'>, BadgeOptions {\n /** 文字前的图标 */\n icon?: IconData;\n /** 文字前一个同色的圆点(没有图标时):表格、列表里的状态,比整块底色更轻 */\n dot?: boolean;\n /** 图标的部件属性 */\n propsIcon?: Partial<IconProps>;\n /** 圆点的部件属性 */\n propsDot?: PartProps<'span', 'aria-hidden'>;\n children?: VNodeChild;\n}\n\n/** 标记(span):状态色调 × 样式,字号默认 xs */\nexport const Badge = _component(\n ({\n variant,\n tone,\n size,\n radius,\n icon,\n dot = false,\n propsIcon,\n propsDot,\n children,\n ...rest\n }: BadgeProps) => {\n const appearance = useDefaults('Badge', () => ({ variant, tone, size, radius }));\n const recipe = useRecipe(badgeRecipe);\n return () => {\n const styles = badgeParts(appearance(), recipe);\n return (\n <span {...mergeAttrs<'span'>(styles.root, rest)}>\n {icon ? (\n <Icon {...mergePartProps<IconProps>({ icon }, propsIcon)} />\n ) : (\n dot && <span {...mergeAttrs<'span'>(styles.dot, propsDot, { 'aria-hidden': true })} />\n )}\n {children}\n </span>\n );\n };\n },\n);\n\n/** 名字、色调、图标由状态映射决定;其余同 Badge */\nexport interface StatusBadgeProps<K extends string> extends Omit<\n BadgeProps,\n 'tone' | 'icon' | 'children'\n> {\n /** 状态映射(createStatusMap) */\n statuses: StatusMap<K>;\n /** 当前状态;不认识的 key 显示成中性色、名字就是 key */\n value: K | (string & {});\n}\n\n/** 状态标记:按状态映射显示名字与色调,没有图标的状态前画圆点 */\nexport const StatusBadge = _component(\n <K extends string>({ statuses, value, dot = true, ...rest }: StatusBadgeProps<K>) =>\n () => {\n const status = statuses.get(value);\n return (\n <Badge\n {...mergePartProps<BadgeProps>({ tone: status.tone, icon: status.icon, dot }, rest)}\n data-status={value}\n >\n {status.label}\n </Badge>\n );\n },\n);\n"],"mappings":";;;;;;;;;;AAiCA,IAAa,QAAQ,0BACnB,QAAA,SAWkB;CAAA,MAAA,UAAAA,aAAA,QAAA,SAAA;CAAA,MAAA,OAAAA,aAAA,QAAA,MAAA;CAAA,MAAA,OAAAA,aAAA,QAAA,MAAA;CAAA,MAAA,SAAAA,aAAA,QAAA,QAAA;CAAA,MAAA,OAAAA,aAAA,QAAA,MAAA;CAAA,MAAA,MAAAA,aAAA,QAAA,OALV,KAAK;CAAA,MAAA,YAAAA,aAAA,QAAA,WAAA;CAAA,MAAA,WAAAA,aAAA,QAAA,UAAA;CAAA,MAAA,WAAAC,aAAA,MAAA;CAAA,MAIR,OAAIC,UAAA,QAAA,MAAA,KAAA;CAEP,MAAM,aAAa,YAAY,gBAAgB;EAAE,SAAO,QAAA;EAAE,MAAI,KAAA;EAAE,MAAI,KAAA;EAAE,QAAM,OAAA;CAAC,EAAE;CAC/E,MAAM,SAAS,UAAU,WAAW;CACpC,aAAa;EACX,MAAM,SAAS,WAAW,WAAW,GAAG,MAAM;EAC9C,OAAAC,YAAA,QACY,WAAmB,OAAO,MAAM,IAAI,GAAC,CAC5C,KAAA,QAAAA,YAAA,MACW,eAA0B,EAAE,MAAI,KAAA,MAAC,GAAC,UAAA,KAAW,GAAC,IAAA,IAExD,IAAA,SAAAA,YAAA,QAAiB,WAAmB,OAAO,KAAG,SAAA,OAAY,EAAE,eAAe,KAAK,CAAC,GAAC,IAAA,GACnF,SAAA,KAAA,CAAA;CAIP;AACF,GAAC;CAAA,MAAA;CAAA,OAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;CAAA;AAAA,CACH;;;AAcA,IAAa,cAAc,0BACzB,SAAA,UAAA;CAAA,MAAA,WAAAH,aAAA,SAAA,UAAA;CAAA,MAAA,QAAAA,aAAA,SAAA,OAAA;CAAA,MAAA,MAAAA,aAAA,SAAA,OAA4C,IAAI;CAAA,MAAK,OAAIE,UAAA,SAAA,OAAA,IAAA;CAAA,aACjD;EACJ,MAAM,SAAS,SAAA,MAAS,IAAG,MAAA,KAAM;EACjC,OAAAC,YAAA,OAAAC,
|
|
1
|
+
{"version":3,"file":"Badge.js","names":["_vtp_readonlyProp","_vtp_childrenProp","_vtp_restProps","_createVNode","_mergeProps"],"sources":["../../src/base/Badge.tsx"],"sourcesContent":["/**\n * 标记(span):状态、计数、分类等短文字。只展示,不可交互;可以关闭或选择的用 Tag。\n *\n * <Badge tone=\"success\">已发货</Badge>\n * <Badge variant=\"solid\" tone=\"danger\" radius=\"full\">12</Badge>\n * <Badge tone=\"warning\" dot>待审核</Badge> 文字前一个同色的圆点:表格里的状态\n * <StatusBadge statuses={taskStatuses} value=\"verifying\" /> 名字与色调取自状态映射(createStatusMap)\n *\n * 部件:Root(span)、Icon、Dot(span,dot 时)。\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport type { VNodeChild } from 'vue';\nimport { useDefaults } from '../config/defaults';\nimport type { StatusMap } from '../utils/data/status';\nimport { mergeAttrs, mergePartProps, type PartProps } from '../utils/props';\nimport { badgeParts, badgeRecipe, type BadgeOptions } from './badge-styles';\nimport { useRecipe } from '../config/styles';\nimport { Icon, type IconData, type IconProps } from './Icon';\n\n/** 未声明的属性落到 span 上 */\nexport interface BadgeProps extends PartProps<'span'>, BadgeOptions {\n /** 文字前的图标 */\n icon?: IconData;\n /** 文字前一个同色的圆点(没有图标时):表格、列表里的状态,比整块底色更轻 */\n dot?: boolean;\n /** 图标的部件属性 */\n propsIcon?: Partial<IconProps>;\n /** 圆点的部件属性 */\n propsDot?: PartProps<'span', 'aria-hidden'>;\n children?: VNodeChild;\n}\n\n/** 标记(span):状态色调 × 样式,字号默认 xs */\nexport const Badge = _component(\n ({\n variant,\n tone,\n size,\n radius,\n icon,\n dot = false,\n propsIcon,\n propsDot,\n children,\n ...rest\n }: BadgeProps) => {\n const appearance = useDefaults('Badge', () => ({ variant, tone, size, radius }));\n const recipe = useRecipe(badgeRecipe);\n return () => {\n const styles = badgeParts(appearance(), recipe);\n return (\n <span {...mergeAttrs<'span'>(styles.root, rest)}>\n {icon ? (\n <Icon {...mergePartProps<IconProps>({ icon }, propsIcon)} />\n ) : (\n dot && <span {...mergeAttrs<'span'>(styles.dot, propsDot, { 'aria-hidden': true })} />\n )}\n {children}\n </span>\n );\n };\n },\n);\n\n/** 名字、色调、图标由状态映射决定;其余同 Badge */\nexport interface StatusBadgeProps<K extends string> extends Omit<\n BadgeProps,\n 'tone' | 'icon' | 'children'\n> {\n /** 状态映射(createStatusMap) */\n statuses: StatusMap<K>;\n /** 当前状态;不认识的 key 显示成中性色、名字就是 key */\n value: K | (string & {});\n}\n\n/** 状态标记:按状态映射显示名字与色调,没有图标的状态前画圆点 */\nexport const StatusBadge = _component(\n <K extends string>({ statuses, value, dot = true, ...rest }: StatusBadgeProps<K>) =>\n () => {\n const status = statuses.get(value);\n return (\n <Badge\n {...mergePartProps<BadgeProps>({ tone: status.tone, icon: status.icon, dot }, rest)}\n data-status={value}\n >\n {status.label}\n </Badge>\n );\n },\n);\n"],"mappings":";;;;;;;;;;AAiCA,IAAa,QAAQ,0BACnB,QAAA,SAWkB;CAAA,MAAA,UAAAA,aAAA,QAAA,SAAA;CAAA,MAAA,OAAAA,aAAA,QAAA,MAAA;CAAA,MAAA,OAAAA,aAAA,QAAA,MAAA;CAAA,MAAA,SAAAA,aAAA,QAAA,QAAA;CAAA,MAAA,OAAAA,aAAA,QAAA,MAAA;CAAA,MAAA,MAAAA,aAAA,QAAA,OALV,KAAK;CAAA,MAAA,YAAAA,aAAA,QAAA,WAAA;CAAA,MAAA,WAAAA,aAAA,QAAA,UAAA;CAAA,MAAA,WAAAC,aAAA,MAAA;CAAA,MAIR,OAAIC,UAAA,QAAA,MAAA,KAAA;CAEP,MAAM,aAAa,YAAY,gBAAgB;EAAE,SAAO,QAAA;EAAE,MAAI,KAAA;EAAE,MAAI,KAAA;EAAE,QAAM,OAAA;CAAC,EAAE;CAC/E,MAAM,SAAS,UAAU,WAAW;CACpC,aAAa;EACX,MAAM,SAAS,WAAW,WAAW,GAAG,MAAM;EAC9C,OAAAC,YAAA,QACY,WAAmB,OAAO,MAAM,IAAI,GAAC,CAC5C,KAAA,QAAAA,YAAA,MACW,eAA0B,EAAE,MAAI,KAAA,MAAC,GAAC,UAAA,KAAW,GAAC,IAAA,IAExD,IAAA,SAAAA,YAAA,QAAiB,WAAmB,OAAO,KAAG,SAAA,OAAY,EAAE,eAAe,KAAK,CAAC,GAAC,IAAA,GACnF,SAAA,KAAA,CAAA;CAIP;AACF,GAAC;CAAA,MAAA;CAAA,OAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;CAAA;AAAA,CACH;;;AAcA,IAAa,cAAc,0BACzB,SAAA,UAAA;CAAA,MAAA,WAAAH,aAAA,SAAA,UAAA;CAAA,MAAA,QAAAA,aAAA,SAAA,OAAA;CAAA,MAAA,MAAAA,aAAA,SAAA,OAA4C,IAAI;CAAA,MAAK,OAAIE,UAAA,SAAA,OAAA,IAAA;CAAA,aACjD;EACJ,MAAM,SAAS,SAAA,MAAS,IAAG,MAAA,KAAM;EACjC,OAAAC,YAAA,OAAAC,cAEQ,eAA2B;GAAE,MAAM,OAAO;GAAM,MAAM,OAAO;GAAM,KAAG,IAAA;EAAC,GAAG,IAAI,GAAC,EAAA,eAAA,MAAA,MAAA,CAAA,GAAA,EAAA,eAAA,CAGlF,OAAO,KAAK,EAAA,CAAA;CAGnB;AAAC,GAAA;CAAA,MAAA;CAAA,OAAA;EAAA;EAAA;EAAA;CAAA;AAAA,CACL"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { VNodeChild } from 'vue';
|
|
2
|
+
/** Can 的属性 */
|
|
3
|
+
export interface CanProps {
|
|
4
|
+
/** 需要的权限点;数组时有其中任意一个即可 */
|
|
5
|
+
permission: string | readonly string[];
|
|
6
|
+
/** 没有权限时显示的内容,默认不显示 */
|
|
7
|
+
fallback?: VNodeChild;
|
|
8
|
+
/** 有权限时显示的内容 */
|
|
9
|
+
children?: VNodeChild;
|
|
10
|
+
}
|
|
11
|
+
/** 按权限点显示:有权限才渲染 children,否则渲染 fallback */
|
|
12
|
+
export declare const Can: import("@zerodep-dev/vue-tsx-pro-tsx").TsxComponent<CanProps>;
|
package/dist/base/Can.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { useCan } from "../config/context.js";
|
|
2
|
+
import { Fragment, createVNode } from "vue";
|
|
3
|
+
import { _component, childrenProp, readonlyProp } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
4
|
+
//#region src/base/Can.tsx
|
|
5
|
+
/** Can 的属性 */
|
|
6
|
+
/** 按权限点显示:有权限才渲染 children,否则渲染 fallback */
|
|
7
|
+
var Can = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
8
|
+
const permission = readonlyProp(_props, "permission");
|
|
9
|
+
const fallback = readonlyProp(_props, "fallback");
|
|
10
|
+
const children = childrenProp(_props);
|
|
11
|
+
const can = useCan();
|
|
12
|
+
return () => createVNode(Fragment, null, [can(permission.value) ? children.value : fallback.value]);
|
|
13
|
+
}, {
|
|
14
|
+
name: "Can",
|
|
15
|
+
props: ["permission", "fallback"]
|
|
16
|
+
});
|
|
17
|
+
//#endregion
|
|
18
|
+
export { Can };
|
|
19
|
+
|
|
20
|
+
//# sourceMappingURL=Can.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Can.js","names":["_vtp_readonlyProp","_vtp_childrenProp","_createVNode","_Fragment"],"sources":["../../src/base/Can.tsx"],"sourcesContent":["/**\n * 按权限点显示:有权限才渲染里面的内容,没有时渲染 fallback(默认什么都不画)。权限从哪来由应用在 createUi / Provider 的\n * permissions 里接入(`(权限点) => 用户有没有`);没接入时一律放行。给数组时有其中任意一个就算有。\n *\n * <Can permission=\"cost:project:add\"><Button icon={Plus}>新建</Button></Can>\n * <Can permission={['cost:project:edit', 'cost:project:add']} fallback={<Small tone=\"muted\">只读</Small>}>…</Can>\n *\n * 只控制界面,不是安全边界:接口上的权限校验才是(见后端的 @RequirePermission)。要自己判断(禁用按钮、过滤菜单项)时用 useCan。\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport type { VNodeChild } from 'vue';\nimport { useCan } from '../config/context';\n\n/** Can 的属性 */\nexport interface CanProps {\n /** 需要的权限点;数组时有其中任意一个即可 */\n permission: string | readonly string[];\n /** 没有权限时显示的内容,默认不显示 */\n fallback?: VNodeChild;\n /** 有权限时显示的内容 */\n children?: VNodeChild;\n}\n\n/** 按权限点显示:有权限才渲染 children,否则渲染 fallback */\nexport const Can = _component(({ permission, fallback, children }: CanProps) => {\n const can = useCan();\n return () => <>{can(permission) ? children : fallback}</>;\n});\n"],"mappings":";;;;;;AAwBA,IAAa,MAAM,0BAAW,QAAA,SAAkD;CAAA,MAAA,aAAAA,aAAA,QAAA,YAAA;CAAA,MAAA,WAAAA,aAAA,QAAA,UAAA;CAAA,MAAA,WAAAC,aAAA,MAAA;CAC9E,MAAM,MAAM,OAAO;CACnB,aAAOC,YAAAC,UAAA,MAAA,CAAS,IAAG,WAAA,KAAW,IAAC,SAAA,QAAA,SAAA,KAAsB,CAAA;AACvD,GAAC;CAAA,MAAA;CAAA,OAAA,CAAA,cAAA,UAAA;AAAA,CAAA"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { VNodeChild } from 'vue';
|
|
2
|
+
/** ClientOnly 的属性 */
|
|
3
|
+
export interface ClientOnlyProps {
|
|
4
|
+
/** 只在浏览器里渲染的内容 */
|
|
5
|
+
children?: VNodeChild;
|
|
6
|
+
/** 服务端渲染与水合时先画的占位(骨架屏一类),默认什么都不画 */
|
|
7
|
+
fallback?: VNodeChild;
|
|
8
|
+
}
|
|
9
|
+
/** 只在浏览器里渲染的内容 */
|
|
10
|
+
export declare const ClientOnly: import("@zerodep-dev/vue-tsx-pro-tsx").TsxComponent<ClientOnlyProps>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { useMounted } from "../utils/dom/mounted.js";
|
|
2
|
+
import { _component, childrenProp, readonlyProp } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
3
|
+
//#region src/base/ClientOnly.tsx
|
|
4
|
+
/** ClientOnly 的属性 */
|
|
5
|
+
/** 只在浏览器里渲染的内容 */
|
|
6
|
+
var ClientOnly = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
7
|
+
const children = childrenProp(_props);
|
|
8
|
+
const fallback = readonlyProp(_props, "fallback");
|
|
9
|
+
const mounted = useMounted();
|
|
10
|
+
return () => mounted.value ? children.value : fallback.value;
|
|
11
|
+
}, {
|
|
12
|
+
name: "ClientOnly",
|
|
13
|
+
props: ["fallback"]
|
|
14
|
+
});
|
|
15
|
+
//#endregion
|
|
16
|
+
export { ClientOnly };
|
|
17
|
+
|
|
18
|
+
//# sourceMappingURL=ClientOnly.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ClientOnly.js","names":["_vtp_childrenProp","_vtp_readonlyProp"],"sources":["../../src/base/ClientOnly.tsx"],"sourcesContent":["/**\n * 只在浏览器里渲染的内容:服务端渲染与水合时画 fallback(默认什么都不画),挂载后才画 children。\n * 给只有浏览器才画得出来的东西用:按量出来的尺寸排版、canvas、依赖当前时刻或本机设置的显示,免得服务端与浏览器的结果对不上。\n * 没有服务端渲染的应用里,它只是晚一拍(挂载后的下一次渲染)才画出内容。\n *\n * <ClientOnly fallback={<Skeleton height=\"12rem\" />}>\n * <WorldMap data={sales} />\n * </ClientOnly>\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport type { VNodeChild } from 'vue';\nimport { useMounted } from '../utils/dom/mounted';\n\n/** ClientOnly 的属性 */\nexport interface ClientOnlyProps {\n /** 只在浏览器里渲染的内容 */\n children?: VNodeChild;\n /** 服务端渲染与水合时先画的占位(骨架屏一类),默认什么都不画 */\n fallback?: VNodeChild;\n}\n\n/** 只在浏览器里渲染的内容 */\nexport const ClientOnly = _component(({ children, fallback }: ClientOnlyProps) => {\n const mounted = useMounted();\n return () => (mounted.value ? children : fallback);\n});\n"],"mappings":";;;;;AAsBA,IAAa,aAAa,0BAAW,QAAA,SAA6C;CAAA,MAAA,WAAAA,aAAA,MAAA;CAAA,MAAA,WAAAC,aAAA,QAAA,UAAA;CAChF,MAAM,UAAU,WAAW;CAC3B,aAAc,QAAQ,QAAK,SAAA,QAAA,SAAA;AAC7B,GAAC;CAAA,MAAA;CAAA,OAAA,CAAA,UAAA;AAAA,CAAA"}
|
package/dist/base/CloseButton.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useLang } from "../config/context.js";
|
|
2
2
|
import { IconButton } from "./IconButton.js";
|
|
3
|
-
import { createVNode
|
|
4
|
-
import { _component, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
3
|
+
import { createVNode } from "vue";
|
|
4
|
+
import { _component, mergeJsxProps, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
5
5
|
import { CircleX, X } from "@lucide/icons";
|
|
6
6
|
//#region src/base/CloseButton.tsx
|
|
7
7
|
/**
|
|
@@ -19,7 +19,7 @@ function createPresetIconButton(name, icon, label) {
|
|
|
19
19
|
const ariaLabel = readonlyProp(_props, "ariaLabel");
|
|
20
20
|
const rest = restProps(_props, _ctx, true);
|
|
21
21
|
const lang = useLang();
|
|
22
|
-
return () => createVNode(IconButton,
|
|
22
|
+
return () => createVNode(IconButton, mergeJsxProps(rest, {
|
|
23
23
|
"icon": icon,
|
|
24
24
|
"aria-label": ariaLabel.value ?? label(lang)
|
|
25
25
|
}), null);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CloseButton.js","names":["_vtp_readonlyProp","_vtp_restProps","_createVNode","_mergeProps"],"sources":["../../src/base/CloseButton.tsx"],"sourcesContent":["/**\n * 常用的图标按钮预设:CloseButton(关闭弹层、标签、提示条)与 ClearButton(清空输入)。\n * 图标来自 Lucide,无障碍名称取语言包(lang.close / lang.clear),也可以用 aria-label 改。\n * 同一个工厂生成,各是一个组件(COMPONENTS.md §1)。\n *\n * <CloseButton onClick={close} />\n * <ClearButton size=\"sm\" onClick={() => (value = '')} />\n */\nimport { CircleX, X } from '@lucide/icons';\nimport { _component, type TsxComponent } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport { useLang } from '../config/context';\nimport type { Lang } from '../config/lang/lang';\nimport { IconButton, type IconButtonProps } from './IconButton';\nimport type { IconData } from './Icon';\n\n/** 未声明的属性落到 IconButton 上;图标固定,名称默认取语言包 */\nexport type PresetIconButtonProps = Omit<IconButtonProps, 'icon' | 'aria-label'> & {\n /** 无障碍名称,默认取语言包(关闭 / 清空) */\n 'aria-label'?: string;\n};\n\n/** 预设图标与名称的图标按钮(CloseButton、ClearButton 由它生成):名称默认取语言包,可以用 aria-label 覆盖 */\nexport function createPresetIconButton(\n name: string,\n icon: IconData,\n label: (lang: Lang) => string,\n): TsxComponent<PresetIconButtonProps> {\n return _component(\n ({ 'aria-label': ariaLabel, ...rest }: PresetIconButtonProps) => {\n const lang = useLang();\n return () => <IconButton {...rest} icon={icon} aria-label={ariaLabel ?? label(lang)} />;\n },\n { name },\n );\n}\n\n/** 关闭按钮:X 图标,名称“关闭” */\nexport const CloseButton = createPresetIconButton('CloseButton', X, (lang) => lang.close);\n/** 清空按钮:圆圈 X 图标,名称“清空”;输入框里清空内容用 */\nexport const ClearButton = createPresetIconButton('ClearButton', CircleX, (lang) => lang.clear);\n"],"mappings":";;;;;;;;;;;;;;;;AAsBA,SAAgB,uBACd,MACA,MACA,OACqC;CACrC,OAAO,0BACL,QAAA,SAAiE;EAAA,MAAA,YAAAA,aAAA,QAAA,WAAA;EAAA,MAAlC,OAAIC,UAAA,QAAA,MAAA,IAAA;EACjC,MAAM,OAAO,QAAQ;EACrB,aAAOC,YAAA,YAAAC,
|
|
1
|
+
{"version":3,"file":"CloseButton.js","names":["_vtp_readonlyProp","_vtp_restProps","_createVNode","_mergeProps"],"sources":["../../src/base/CloseButton.tsx"],"sourcesContent":["/**\n * 常用的图标按钮预设:CloseButton(关闭弹层、标签、提示条)与 ClearButton(清空输入)。\n * 图标来自 Lucide,无障碍名称取语言包(lang.close / lang.clear),也可以用 aria-label 改。\n * 同一个工厂生成,各是一个组件(COMPONENTS.md §1)。\n *\n * <CloseButton onClick={close} />\n * <ClearButton size=\"sm\" onClick={() => (value = '')} />\n */\nimport { CircleX, X } from '@lucide/icons';\nimport { _component, type TsxComponent } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport { useLang } from '../config/context';\nimport type { Lang } from '../config/lang/lang';\nimport { IconButton, type IconButtonProps } from './IconButton';\nimport type { IconData } from './Icon';\n\n/** 未声明的属性落到 IconButton 上;图标固定,名称默认取语言包 */\nexport type PresetIconButtonProps = Omit<IconButtonProps, 'icon' | 'aria-label'> & {\n /** 无障碍名称,默认取语言包(关闭 / 清空) */\n 'aria-label'?: string;\n};\n\n/** 预设图标与名称的图标按钮(CloseButton、ClearButton 由它生成):名称默认取语言包,可以用 aria-label 覆盖 */\nexport function createPresetIconButton(\n name: string,\n icon: IconData,\n label: (lang: Lang) => string,\n): TsxComponent<PresetIconButtonProps> {\n return _component(\n ({ 'aria-label': ariaLabel, ...rest }: PresetIconButtonProps) => {\n const lang = useLang();\n return () => <IconButton {...rest} icon={icon} aria-label={ariaLabel ?? label(lang)} />;\n },\n { name },\n );\n}\n\n/** 关闭按钮:X 图标,名称“关闭” */\nexport const CloseButton = createPresetIconButton('CloseButton', X, (lang) => lang.close);\n/** 清空按钮:圆圈 X 图标,名称“清空”;输入框里清空内容用 */\nexport const ClearButton = createPresetIconButton('ClearButton', CircleX, (lang) => lang.clear);\n"],"mappings":";;;;;;;;;;;;;;;;AAsBA,SAAgB,uBACd,MACA,MACA,OACqC;CACrC,OAAO,0BACL,QAAA,SAAiE;EAAA,MAAA,YAAAA,aAAA,QAAA,WAAA;EAAA,MAAlC,OAAIC,UAAA,QAAA,MAAA,IAAA;EACjC,MAAM,OAAO,QAAQ;EACrB,aAAOC,YAAA,YAAAC,cAAsB,MAAI;GAAA,QAAQ;GAAI,cAAc,UAAA,SAAa,MAAM,IAAI;EAAC,CAAA,GAAA,IAAA;CACrF,GACA;EAAA,OAAA,CAAA,WAAA;EAAE;CAAK,CACT;AACF;;AAGA,IAAa,cAAc,uBAAuB,eAAe,IAAI,SAAS,KAAK,KAAK;;AAExF,IAAa,cAAc,uBAAuB,eAAe,UAAU,SAAS,KAAK,KAAK"}
|
package/dist/base/CodeBlock.js
CHANGED
|
@@ -6,8 +6,8 @@ import { disclosureStyles } from "../utils/style/transitions.js";
|
|
|
6
6
|
import { ButtonBase } from "./ButtonBase.js";
|
|
7
7
|
import { ScrollArea } from "../layout/ScrollArea.js";
|
|
8
8
|
import { CopyButton } from "./CopyButton.js";
|
|
9
|
-
import { computed, createVNode,
|
|
10
|
-
import { _component, effect, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
9
|
+
import { computed, createVNode, ref, shallowRef } from "vue";
|
|
10
|
+
import { _component, effect, mergeJsxProps, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
11
11
|
import * as css from "@zerodep-dev/vue-tsx-pro-tsx/styles";
|
|
12
12
|
import { ChevronDown } from "@lucide/icons";
|
|
13
13
|
//#region src/base/CodeBlock.tsx
|
|
@@ -199,7 +199,7 @@ var CodeBlock = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
199
199
|
function renderLines() {
|
|
200
200
|
return (html.value == null ? splitLines(code.value, "\n") : splitLines(html.value, "<br>")).map((line, index) => {
|
|
201
201
|
const number = index + 1;
|
|
202
|
-
return createVNode("span",
|
|
202
|
+
return createVNode("span", mergeJsxProps({ "key": number }, mergeAttrs({ sx: [
|
|
203
203
|
codeBlockStyles.line,
|
|
204
204
|
marks.value.highlighted.has(number) && codeBlockStyles.highlighted,
|
|
205
205
|
marks.value.added.has(number) && codeBlockStyles.added,
|
|
@@ -219,8 +219,8 @@ var CodeBlock = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
219
219
|
}, propsCopy.value), null);
|
|
220
220
|
return createVNode("div", mergeAttrs({ sx: codeBlockStyles.wrapper }, propsRoot.value), [
|
|
221
221
|
hasContent(title.value) && createVNode("div", mergeAttrs({ sx: codeBlockStyles.header }, propsHeader.value), [createVNode("span", mergeAttrs({ sx: codeBlockStyles.title }, propsTitle.value), [title.value]), copyable.value && copyButton(true)]),
|
|
222
|
-
createVNode("div", mergeAttrs({ sx: codeBlockStyles.body }, propsBody.value), [createVNode(ScrollArea,
|
|
223
|
-
collapsible && createVNode(ButtonBase,
|
|
222
|
+
createVNode("div", mergeAttrs({ sx: codeBlockStyles.body }, propsBody.value), [createVNode(ScrollArea, mergeJsxProps({ "axis": "x" }, mergePartProps({}, propsScroll.value)), { default: () => [createVNode("pre", mergeJsxProps(mergeAttrs({ sx: [codeBlockStyles.root, copyable.value && title.value == null && codeBlockStyles.withCopy] }, rest, { sx: [wrap.value && codeBlockStyles.wrap, collapsed && [codeBlockStyles.collapsed, css.bind(codeBlockDynamicStyles.maxLines, () => [maxLines.value], true)]] }), { "data-lang": lang.value }), [byLine.value ? createVNode("code", codeProps, [renderLines()]) : html.value == null ? createVNode("code", codeProps, [code.value]) : createVNode("code", mergeJsxProps(codeProps, { "innerHTML": html.value }), null)])] }), copyable.value && title.value == null && copyButton(false)]),
|
|
223
|
+
collapsible && createVNode(ButtonBase, mergeJsxProps(mergePartProps({ sx: codeBlockStyles.expand }, propsExpand.value, {
|
|
224
224
|
"aria-expanded": expanded.value,
|
|
225
225
|
onClick: () => expanded.value = !expanded.value
|
|
226
226
|
}), { "key": "__site0" }), {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CodeBlock.js","names":["_vtp_readonlyProp","_vtp_restProps","_vue_shallowRef","_vue_ref","_vue_computed","_createVNode","_mergeProps"],"sources":["../../src/base/CodeBlock.tsx"],"sourcesContent":["/**\n * 代码块(pre > code)。高亮器经配置注入(createUi / Provider 的 highlighter,见 config/context.ts),\n * 本组件不依赖 Shiki:没有高亮器、或没给语言时显示纯文本。\n *\n * <CodeBlock code={source} lang=\"ts\" />\n * <CodeBlock code={source} lang=\"ts\" title=\"main.ts\" lineNumbers highlightLines=\"2,4-5\" maxLines={20} />\n *\n * - 高亮是异步的:先显示纯文本,结果到了再替换;等宽字体下两者排版相同,布局不跳动。\n * - 长行在 ScrollArea 里横向滚动:浮层滚动条不占高度;滚动区域可聚焦(带焦点环),键盘也能滚动。\n * - 复制按钮(CopyButton)在右上角,有标题时放在标题栏里;复制成功后经 aria-live 播报“已复制”。\n * - 行号、行高亮、新增与删除的行:按行渲染(高亮结果按 <br> 拆行;代码里的 < 都已转义,拆分是安全的);\n * 都不用时整段写进 innerHTML,不为每行建节点。行号不可选中,复制的是 code 本身。\n * - 长代码:maxLines 之外折叠,底部的按钮展开或收起。\n */\nimport { ChevronDown } from '@lucide/icons';\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 { useConfig, useLang } from '../config/context';\nimport { hasContent, mergeAttrs, mergePartProps, type PartProps } from '../utils/props';\nimport { disclosureStyles } from '../utils/style/transitions';\nimport { ScrollArea, type ScrollAreaProps } from '../layout/ScrollArea';\nimport { ButtonBase, type ButtonBaseProps } from './ButtonBase';\nimport { CopyButton, type CopyButtonProps } from './CopyButton';\nimport { Icon, type IconProps } from './Icon';\nimport { t } from '../config/theme/tokens';\n\n/** 行号的写法:`'1,3-5'` 或 `[1, 3, 4, 5]`(从 1 开始) */\nexport type LineSpec = string | readonly number[];\n\n/** 未声明的属性落到 pre 上;原生的 title 属性(悬停提示)让给标题栏 */\nexport interface CodeBlockProps extends PartProps<'pre', 'title'> {\n /** 代码文本 */\n code: string;\n /** 语言(如 ts、vue、bash),交给高亮器;不写时不高亮,高亮器不认识的语言也按纯文本显示 */\n lang?: string;\n /**\n * 高亮:false 时先按纯文本显示,改成 true 时才交给高亮器。看不见的代码(收起的示例源码)可以等要显示时再高亮,\n * 页面打开时不必一次高亮全部\n * @default true\n */\n highlight?: boolean;\n /** 标题栏:通常是文件名;有标题时复制按钮放在标题栏里 */\n title?: VNodeChild;\n /** 长行自动换行,而不是横向滚动 */\n wrap?: boolean;\n /** 显示复制按钮,默认 true */\n copyable?: boolean;\n /** 显示行号 */\n lineNumbers?: boolean;\n /** 强调的行:`'2,4-5'` */\n highlightLines?: LineSpec;\n /** 新增的行(diff):绿色底 */\n addedLines?: LineSpec;\n /** 删除的行(diff):红色底 */\n removedLines?: LineSpec;\n /** 超过这么多行时折叠,底部的按钮展开;不写时不折叠 */\n maxLines?: number;\n /** 内层 code 元素的部件属性 */\n propsCode?: PartProps<'code'>;\n /** 外层(包着标题栏、pre 与复制按钮的 div)的部件属性 */\n propsRoot?: PartProps<'div'>;\n /** 标题栏的部件属性 */\n propsHeader?: PartProps<'div'>;\n /** 标题栏里标题文字的部件属性 */\n propsTitle?: PartProps<'span'>;\n /** 代码区(包着滚动区域与右上角复制按钮的 div)的部件属性 */\n propsBody?: PartProps<'div'>;\n /** 按行渲染时每一行的部件属性 */\n propsLine?: PartProps<'span'>;\n /** 每行行号的部件属性 */\n propsLineNumber?: PartProps<'span', 'aria-hidden'>;\n /** 复制按钮的部件属性 */\n propsCopy?: Partial<CopyButtonProps>;\n /** 横向滚动区域(ScrollArea)的部件属性 */\n propsScroll?: Partial<ScrollAreaProps>;\n /** 展开、收起长代码的按钮的部件属性 */\n propsExpand?: Partial<ButtonBaseProps>;\n /** 展开按钮里箭头图标的部件属性;展开后的翻转由组件管理 */\n propsExpandIcon?: Partial<IconProps>;\n}\n\n/** `'1,3-5'` → Set{1, 3, 4, 5};数组原样收进集合 */\nexport function parseLineSpec(spec: LineSpec | undefined): Set<number> {\n if (spec == null) return new Set();\n if (typeof spec !== 'string') return new Set(spec);\n const lines = new Set<number>();\n for (const part of spec.split(',')) {\n const [from, to = from] = part.split('-').map((text) => Number(text.trim()));\n if (!Number.isInteger(from) || !Number.isInteger(to)) continue;\n for (let line = from!; line <= to!; line++) lines.add(line);\n }\n return lines;\n}\n\n/** CodeBlock 的样式 */\nexport const codeBlockStyles = css.create({\n // minWidth: 0:放在 grid / flex 里时外层不被最长的一行代码撑宽(滚动的是里面的 pre)\n wrapper: {\n minWidth: 0,\n borderWidth: '_xs',\n borderStyle: 'solid',\n borderColor: '_borderMuted',\n borderRadius: '_md',\n backgroundColor: '_surface',\n overflow: 'hidden',\n },\n header: {\n display: 'flex',\n alignItems: 'center',\n gap: '_sm',\n minHeight: `max(${t.sizes.control}, ${t.sizes.minTarget})`,\n paddingInlineStart: '_lg',\n paddingInlineEnd: '_xs',\n borderBottomWidth: '_xs',\n borderBottomStyle: 'solid',\n borderBottomColor: '_borderMuted',\n backgroundColor: '_surfaceMuted',\n color: '_textMuted',\n fontFamily: '_mono',\n fontSize: '0.85em',\n },\n title: {\n flexGrow: 1,\n minWidth: 0,\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n },\n body: { position: 'relative' },\n // 给复制按钮留出位置,第一行的末尾不被盖住\n withCopy: {\n paddingInlineEnd: `calc(${t.space.md} + max(${t.sizes.control}, ${t.sizes.minTarget}))`,\n },\n copy: { position: 'absolute', top: '_space.xs', insetInlineEnd: '_space.xs' },\n root: {\n boxSizing: 'border-box',\n margin: 0,\n paddingBlock: '_md',\n paddingInline: '_lg',\n color: '_text',\n fontFamily: '_mono',\n // 代码总是从左到右排:从右到左的页面里也不跟着镜像\n direction: 'ltr',\n // 代码字体的连字(=> 显示成 ⇒)会让照着抄的人看错,一律关掉\n fontVariantLigatures: 'none',\n // 相对周围文字略小:等宽字体的字面显得大;是比例不是字号档位\n fontSize: '0.9em',\n lineHeight: '_normal',\n tabSize: 2,\n },\n collapsed: { overflowY: 'hidden' },\n wrap: { whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' },\n // 继承 pre 的字体:浏览器默认给 code 设了 monospace 字号缩放\n code: { fontFamily: 'inherit', fontSize: 'inherit' },\n // 按行渲染时:code 至少与 pre 一样宽,行的底色铺满横向滚动的整行\n lines: { display: 'inline-block', minWidth: '100%' },\n line: {\n display: 'block',\n minHeight: '1lh',\n marginInline: `calc(-1 * ${t.space.lg})`,\n paddingInline: '_lg',\n },\n highlighted: { backgroundColor: '_fill' },\n added: { backgroundColor: '_codeInserted' },\n removed: { backgroundColor: '_codeDeleted' },\n lineNumber: {\n display: 'inline-block',\n minWidth: '2ch',\n marginInlineEnd: '_lg',\n color: '_codeComment',\n textAlign: 'end',\n userSelect: 'none',\n },\n expand: {\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n gap: '_xs',\n width: '_full',\n minHeight: `max(${t.sizes.control}, ${t.sizes.minTarget})`,\n borderTopWidth: '_xs',\n borderTopStyle: 'solid',\n borderTopColor: '_borderMuted',\n backgroundColor: { default: '_surfaceMuted', ':hover': '_fill' },\n color: '_textMuted',\n fontSize: '_sm',\n },\n expandOpen: { rotate: '180deg' },\n});\n\n/** 按行拆开;结尾的换行不算新的一行(文件末尾通常有一个),不然行号会多出一行空的 */\nfunction splitLines(text: string, separator: string): string[] {\n const lines = text.split(separator);\n if (lines.length > 1 && lines.at(-1) === '') lines.pop();\n return lines;\n}\n\n// 折叠后的高度按行数算,是任意值,用动态样式;单独一个 create,见 COMPONENTS.md §4\n/** CodeBlock 的动态样式:折叠后的最大高度 */\nexport const codeBlockDynamicStyles = css.create({\n maxLines: (lines: number) => ({ maxHeight: `calc(${lines} * 1lh + 2 * 0.75em)` }),\n});\n\n/** 代码块:高亮、复制、标题栏、行号、行高亮、diff、长代码折叠 */\nexport const CodeBlock = _component(\n ({\n code,\n lang,\n highlight = true,\n title,\n wrap = false,\n copyable = true,\n lineNumbers = false,\n highlightLines,\n addedLines,\n removedLines,\n maxLines,\n propsCode,\n propsRoot,\n propsHeader,\n propsTitle,\n propsBody,\n propsLine,\n propsLineNumber,\n propsCopy,\n propsScroll,\n propsExpand,\n propsExpandIcon,\n ...rest\n }: CodeBlockProps) => {\n const config = useConfig();\n const labels = useLang();\n // 当前代码的高亮结果;代码或语言变了先清空,回到纯文本\n let html = _state.raw<string | null>(null);\n let expanded = _state(false);\n\n _effect(() => {\n const highlighter = config.highlighter;\n html = null;\n if (!highlighter || !lang || !highlight) return;\n let stale = false;\n highlighter(code, lang).then(\n (result) => {\n if (!stale) html = result;\n },\n (error: unknown) => console.error(`CodeBlock 高亮 ${lang} 代码失败,显示纯文本`, error),\n );\n return () => (stale = true);\n });\n\n const marks = _derived.by(() => ({\n highlighted: parseLineSpec(highlightLines),\n added: parseLineSpec(addedLines),\n removed: parseLineSpec(removedLines),\n }));\n const lineCount = _derived(splitLines(code, '\\n').length);\n const byLine = _derived(\n lineNumbers || marks.highlighted.size + marks.added.size + marks.removed.size > 0,\n );\n\n /** 按行渲染:高亮结果按 <br> 拆,纯文本按换行拆 */\n function renderLines() {\n const lines = html == null ? splitLines(code, '\\n') : splitLines(html, '<br>');\n return lines.map((line, index) => {\n const number = index + 1;\n return (\n <span\n key={number}\n {...mergeAttrs<'span'>(\n {\n sx: [\n codeBlockStyles.line,\n marks.highlighted.has(number) && codeBlockStyles.highlighted,\n marks.added.has(number) && codeBlockStyles.added,\n marks.removed.has(number) && codeBlockStyles.removed,\n ],\n },\n propsLine,\n )}\n data-diff={\n marks.added.has(number) ? 'added' : marks.removed.has(number) ? 'removed' : undefined\n }\n >\n {lineNumbers && (\n <span\n {...mergeAttrs<'span'>({ sx: codeBlockStyles.lineNumber }, propsLineNumber, {\n 'aria-hidden': 'true',\n })}\n >\n {number}\n </span>\n )}\n {html == null ? line : <span innerHTML={line} />}\n </span>\n );\n });\n }\n\n return () => {\n const collapsible = maxLines != null && lineCount > maxLines;\n const collapsed = collapsible && !expanded;\n const codeProps = mergeAttrs<'code'>(\n { sx: [codeBlockStyles.code, byLine && codeBlockStyles.lines] },\n propsCode,\n );\n const copyButton = (inHeader: boolean) => (\n <CopyButton\n {...mergePartProps<CopyButtonProps>(\n { sx: !inHeader && codeBlockStyles.copy, size: 'sm', tone: 'neutral', value: code },\n propsCopy,\n )}\n />\n );\n return (\n <div {...mergeAttrs<'div'>({ sx: codeBlockStyles.wrapper }, propsRoot)}>\n {hasContent(title) && (\n <div {...mergeAttrs<'div'>({ sx: codeBlockStyles.header }, propsHeader)}>\n <span {...mergeAttrs<'span'>({ sx: codeBlockStyles.title }, propsTitle)}>\n {title}\n </span>\n {copyable && copyButton(true)}\n </div>\n )}\n <div {...mergeAttrs<'div'>({ sx: codeBlockStyles.body }, propsBody)}>\n <ScrollArea axis=\"x\" {...mergePartProps<ScrollAreaProps>({}, propsScroll)}>\n <pre\n {...mergeAttrs<'pre'>(\n {\n sx: [\n codeBlockStyles.root,\n copyable && title == null && codeBlockStyles.withCopy,\n ],\n },\n rest,\n {\n sx: [\n wrap && codeBlockStyles.wrap,\n collapsed && [\n codeBlockStyles.collapsed,\n codeBlockDynamicStyles.maxLines(maxLines!),\n ],\n ],\n },\n )}\n data-lang={lang}\n >\n {byLine ? (\n <code {...codeProps}>{renderLines()}</code>\n ) : html == null ? (\n <code {...codeProps}>{code}</code>\n ) : (\n <code {...codeProps} innerHTML={html} />\n )}\n </pre>\n </ScrollArea>\n {copyable && title == null && copyButton(false)}\n </div>\n {collapsible && (\n <ButtonBase\n {...mergePartProps<ButtonBaseProps>({ sx: codeBlockStyles.expand }, propsExpand, {\n 'aria-expanded': expanded,\n onClick: () => (expanded = !expanded),\n })}\n >\n {expanded ? labels.codeCollapse : labels.codeExpand}\n <Icon\n {...mergePartProps<IconProps>(\n { icon: ChevronDown, sx: disclosureStyles.icon },\n propsExpandIcon,\n { sx: expanded && codeBlockStyles.expandOpen },\n )}\n />\n </ButtonBase>\n )}\n </div>\n );\n };\n },\n);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CodeBlock.js","names":["_vtp_readonlyProp","_vtp_restProps","_vue_shallowRef","_vue_ref","_vue_computed","_createVNode","_mergeProps"],"sources":["../../src/base/CodeBlock.tsx"],"sourcesContent":["/**\n * 代码块(pre > code)。高亮器经配置注入(createUi / Provider 的 highlighter,见 config/context.ts),\n * 本组件不依赖 Shiki:没有高亮器、或没给语言时显示纯文本。\n *\n * <CodeBlock code={source} lang=\"ts\" />\n * <CodeBlock code={source} lang=\"ts\" title=\"main.ts\" lineNumbers highlightLines=\"2,4-5\" maxLines={20} />\n *\n * - 高亮是异步的:先显示纯文本,结果到了再替换;等宽字体下两者排版相同,布局不跳动。\n * - 长行在 ScrollArea 里横向滚动:浮层滚动条不占高度;滚动区域可聚焦(带焦点环),键盘也能滚动。\n * - 复制按钮(CopyButton)在右上角,有标题时放在标题栏里;复制成功后经 aria-live 播报“已复制”。\n * - 行号、行高亮、新增与删除的行:按行渲染(高亮结果按 <br> 拆行;代码里的 < 都已转义,拆分是安全的);\n * 都不用时整段写进 innerHTML,不为每行建节点。行号不可选中,复制的是 code 本身。\n * - 长代码:maxLines 之外折叠,底部的按钮展开或收起。\n */\nimport { ChevronDown } from '@lucide/icons';\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 { useConfig, useLang } from '../config/context';\nimport { hasContent, mergeAttrs, mergePartProps, type PartProps } from '../utils/props';\nimport { disclosureStyles } from '../utils/style/transitions';\nimport { ScrollArea, type ScrollAreaProps } from '../layout/ScrollArea';\nimport { ButtonBase, type ButtonBaseProps } from './ButtonBase';\nimport { CopyButton, type CopyButtonProps } from './CopyButton';\nimport { Icon, type IconProps } from './Icon';\nimport { t } from '../config/theme/tokens';\n\n/** 行号的写法:`'1,3-5'` 或 `[1, 3, 4, 5]`(从 1 开始) */\nexport type LineSpec = string | readonly number[];\n\n/** 未声明的属性落到 pre 上;原生的 title 属性(悬停提示)让给标题栏 */\nexport interface CodeBlockProps extends PartProps<'pre', 'title'> {\n /** 代码文本 */\n code: string;\n /** 语言(如 ts、vue、bash),交给高亮器;不写时不高亮,高亮器不认识的语言也按纯文本显示 */\n lang?: string;\n /**\n * 高亮:false 时先按纯文本显示,改成 true 时才交给高亮器。看不见的代码(收起的示例源码)可以等要显示时再高亮,\n * 页面打开时不必一次高亮全部\n * @default true\n */\n highlight?: boolean;\n /** 标题栏:通常是文件名;有标题时复制按钮放在标题栏里 */\n title?: VNodeChild;\n /** 长行自动换行,而不是横向滚动 */\n wrap?: boolean;\n /** 显示复制按钮,默认 true */\n copyable?: boolean;\n /** 显示行号 */\n lineNumbers?: boolean;\n /** 强调的行:`'2,4-5'` */\n highlightLines?: LineSpec;\n /** 新增的行(diff):绿色底 */\n addedLines?: LineSpec;\n /** 删除的行(diff):红色底 */\n removedLines?: LineSpec;\n /** 超过这么多行时折叠,底部的按钮展开;不写时不折叠 */\n maxLines?: number;\n /** 内层 code 元素的部件属性 */\n propsCode?: PartProps<'code'>;\n /** 外层(包着标题栏、pre 与复制按钮的 div)的部件属性 */\n propsRoot?: PartProps<'div'>;\n /** 标题栏的部件属性 */\n propsHeader?: PartProps<'div'>;\n /** 标题栏里标题文字的部件属性 */\n propsTitle?: PartProps<'span'>;\n /** 代码区(包着滚动区域与右上角复制按钮的 div)的部件属性 */\n propsBody?: PartProps<'div'>;\n /** 按行渲染时每一行的部件属性 */\n propsLine?: PartProps<'span'>;\n /** 每行行号的部件属性 */\n propsLineNumber?: PartProps<'span', 'aria-hidden'>;\n /** 复制按钮的部件属性 */\n propsCopy?: Partial<CopyButtonProps>;\n /** 横向滚动区域(ScrollArea)的部件属性 */\n propsScroll?: Partial<ScrollAreaProps>;\n /** 展开、收起长代码的按钮的部件属性 */\n propsExpand?: Partial<ButtonBaseProps>;\n /** 展开按钮里箭头图标的部件属性;展开后的翻转由组件管理 */\n propsExpandIcon?: Partial<IconProps>;\n}\n\n/** `'1,3-5'` → Set{1, 3, 4, 5};数组原样收进集合 */\nexport function parseLineSpec(spec: LineSpec | undefined): Set<number> {\n if (spec == null) return new Set();\n if (typeof spec !== 'string') return new Set(spec);\n const lines = new Set<number>();\n for (const part of spec.split(',')) {\n const [from, to = from] = part.split('-').map((text) => Number(text.trim()));\n if (!Number.isInteger(from) || !Number.isInteger(to)) continue;\n for (let line = from!; line <= to!; line++) lines.add(line);\n }\n return lines;\n}\n\n/** CodeBlock 的样式 */\nexport const codeBlockStyles = css.create({\n // minWidth: 0:放在 grid / flex 里时外层不被最长的一行代码撑宽(滚动的是里面的 pre)\n wrapper: {\n minWidth: 0,\n borderWidth: '_xs',\n borderStyle: 'solid',\n borderColor: '_borderMuted',\n borderRadius: '_md',\n backgroundColor: '_surface',\n overflow: 'hidden',\n },\n header: {\n display: 'flex',\n alignItems: 'center',\n gap: '_sm',\n minHeight: `max(${t.sizes.control}, ${t.sizes.minTarget})`,\n paddingInlineStart: '_lg',\n paddingInlineEnd: '_xs',\n borderBottomWidth: '_xs',\n borderBottomStyle: 'solid',\n borderBottomColor: '_borderMuted',\n backgroundColor: '_surfaceMuted',\n color: '_textMuted',\n fontFamily: '_mono',\n fontSize: '0.85em',\n },\n title: {\n flexGrow: 1,\n minWidth: 0,\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n },\n body: { position: 'relative' },\n // 给复制按钮留出位置,第一行的末尾不被盖住\n withCopy: {\n paddingInlineEnd: `calc(${t.space.md} + max(${t.sizes.control}, ${t.sizes.minTarget}))`,\n },\n copy: { position: 'absolute', top: '_space.xs', insetInlineEnd: '_space.xs' },\n root: {\n boxSizing: 'border-box',\n margin: 0,\n paddingBlock: '_md',\n paddingInline: '_lg',\n color: '_text',\n fontFamily: '_mono',\n // 代码总是从左到右排:从右到左的页面里也不跟着镜像\n direction: 'ltr',\n // 代码字体的连字(=> 显示成 ⇒)会让照着抄的人看错,一律关掉\n fontVariantLigatures: 'none',\n // 相对周围文字略小:等宽字体的字面显得大;是比例不是字号档位\n fontSize: '0.9em',\n lineHeight: '_normal',\n tabSize: 2,\n },\n collapsed: { overflowY: 'hidden' },\n wrap: { whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' },\n // 继承 pre 的字体:浏览器默认给 code 设了 monospace 字号缩放\n code: { fontFamily: 'inherit', fontSize: 'inherit' },\n // 按行渲染时:code 至少与 pre 一样宽,行的底色铺满横向滚动的整行\n lines: { display: 'inline-block', minWidth: '100%' },\n line: {\n display: 'block',\n minHeight: '1lh',\n marginInline: `calc(-1 * ${t.space.lg})`,\n paddingInline: '_lg',\n },\n highlighted: { backgroundColor: '_fill' },\n added: { backgroundColor: '_codeInserted' },\n removed: { backgroundColor: '_codeDeleted' },\n lineNumber: {\n display: 'inline-block',\n minWidth: '2ch',\n marginInlineEnd: '_lg',\n color: '_codeComment',\n textAlign: 'end',\n userSelect: 'none',\n },\n expand: {\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n gap: '_xs',\n width: '_full',\n minHeight: `max(${t.sizes.control}, ${t.sizes.minTarget})`,\n borderTopWidth: '_xs',\n borderTopStyle: 'solid',\n borderTopColor: '_borderMuted',\n backgroundColor: { default: '_surfaceMuted', ':hover': '_fill' },\n color: '_textMuted',\n fontSize: '_sm',\n },\n expandOpen: { rotate: '180deg' },\n});\n\n/** 按行拆开;结尾的换行不算新的一行(文件末尾通常有一个),不然行号会多出一行空的 */\nfunction splitLines(text: string, separator: string): string[] {\n const lines = text.split(separator);\n if (lines.length > 1 && lines.at(-1) === '') lines.pop();\n return lines;\n}\n\n// 折叠后的高度按行数算,是任意值,用动态样式;单独一个 create,见 COMPONENTS.md §4\n/** CodeBlock 的动态样式:折叠后的最大高度 */\nexport const codeBlockDynamicStyles = css.create({\n maxLines: (lines: number) => ({ maxHeight: `calc(${lines} * 1lh + 2 * 0.75em)` }),\n});\n\n/** 代码块:高亮、复制、标题栏、行号、行高亮、diff、长代码折叠 */\nexport const CodeBlock = _component(\n ({\n code,\n lang,\n highlight = true,\n title,\n wrap = false,\n copyable = true,\n lineNumbers = false,\n highlightLines,\n addedLines,\n removedLines,\n maxLines,\n propsCode,\n propsRoot,\n propsHeader,\n propsTitle,\n propsBody,\n propsLine,\n propsLineNumber,\n propsCopy,\n propsScroll,\n propsExpand,\n propsExpandIcon,\n ...rest\n }: CodeBlockProps) => {\n const config = useConfig();\n const labels = useLang();\n // 当前代码的高亮结果;代码或语言变了先清空,回到纯文本\n let html = _state.raw<string | null>(null);\n let expanded = _state(false);\n\n _effect(() => {\n const highlighter = config.highlighter;\n html = null;\n if (!highlighter || !lang || !highlight) return;\n let stale = false;\n highlighter(code, lang).then(\n (result) => {\n if (!stale) html = result;\n },\n (error: unknown) => console.error(`CodeBlock 高亮 ${lang} 代码失败,显示纯文本`, error),\n );\n return () => (stale = true);\n });\n\n const marks = _derived.by(() => ({\n highlighted: parseLineSpec(highlightLines),\n added: parseLineSpec(addedLines),\n removed: parseLineSpec(removedLines),\n }));\n const lineCount = _derived(splitLines(code, '\\n').length);\n const byLine = _derived(\n lineNumbers || marks.highlighted.size + marks.added.size + marks.removed.size > 0,\n );\n\n /** 按行渲染:高亮结果按 <br> 拆,纯文本按换行拆 */\n function renderLines() {\n const lines = html == null ? splitLines(code, '\\n') : splitLines(html, '<br>');\n return lines.map((line, index) => {\n const number = index + 1;\n return (\n <span\n key={number}\n {...mergeAttrs<'span'>(\n {\n sx: [\n codeBlockStyles.line,\n marks.highlighted.has(number) && codeBlockStyles.highlighted,\n marks.added.has(number) && codeBlockStyles.added,\n marks.removed.has(number) && codeBlockStyles.removed,\n ],\n },\n propsLine,\n )}\n data-diff={\n marks.added.has(number) ? 'added' : marks.removed.has(number) ? 'removed' : undefined\n }\n >\n {lineNumbers && (\n <span\n {...mergeAttrs<'span'>({ sx: codeBlockStyles.lineNumber }, propsLineNumber, {\n 'aria-hidden': 'true',\n })}\n >\n {number}\n </span>\n )}\n {html == null ? line : <span innerHTML={line} />}\n </span>\n );\n });\n }\n\n return () => {\n const collapsible = maxLines != null && lineCount > maxLines;\n const collapsed = collapsible && !expanded;\n const codeProps = mergeAttrs<'code'>(\n { sx: [codeBlockStyles.code, byLine && codeBlockStyles.lines] },\n propsCode,\n );\n const copyButton = (inHeader: boolean) => (\n <CopyButton\n {...mergePartProps<CopyButtonProps>(\n { sx: !inHeader && codeBlockStyles.copy, size: 'sm', tone: 'neutral', value: code },\n propsCopy,\n )}\n />\n );\n return (\n <div {...mergeAttrs<'div'>({ sx: codeBlockStyles.wrapper }, propsRoot)}>\n {hasContent(title) && (\n <div {...mergeAttrs<'div'>({ sx: codeBlockStyles.header }, propsHeader)}>\n <span {...mergeAttrs<'span'>({ sx: codeBlockStyles.title }, propsTitle)}>\n {title}\n </span>\n {copyable && copyButton(true)}\n </div>\n )}\n <div {...mergeAttrs<'div'>({ sx: codeBlockStyles.body }, propsBody)}>\n <ScrollArea axis=\"x\" {...mergePartProps<ScrollAreaProps>({}, propsScroll)}>\n <pre\n {...mergeAttrs<'pre'>(\n {\n sx: [\n codeBlockStyles.root,\n copyable && title == null && codeBlockStyles.withCopy,\n ],\n },\n rest,\n {\n sx: [\n wrap && codeBlockStyles.wrap,\n collapsed && [\n codeBlockStyles.collapsed,\n codeBlockDynamicStyles.maxLines(maxLines!),\n ],\n ],\n },\n )}\n data-lang={lang}\n >\n {byLine ? (\n <code {...codeProps}>{renderLines()}</code>\n ) : html == null ? (\n <code {...codeProps}>{code}</code>\n ) : (\n <code {...codeProps} innerHTML={html} />\n )}\n </pre>\n </ScrollArea>\n {copyable && title == null && copyButton(false)}\n </div>\n {collapsible && (\n <ButtonBase\n {...mergePartProps<ButtonBaseProps>({ sx: codeBlockStyles.expand }, propsExpand, {\n 'aria-expanded': expanded,\n onClick: () => (expanded = !expanded),\n })}\n >\n {expanded ? labels.codeCollapse : labels.codeExpand}\n <Icon\n {...mergePartProps<IconProps>(\n { icon: ChevronDown, sx: disclosureStyles.icon },\n propsExpandIcon,\n { sx: expanded && codeBlockStyles.expandOpen },\n )}\n />\n </ButtonBase>\n )}\n </div>\n );\n };\n },\n);\n"],"mappings":"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"}
|
package/dist/base/CopyButton.js
CHANGED
|
@@ -3,8 +3,8 @@ import { mergeAttrs, mergePartProps } from "../utils/props.js";
|
|
|
3
3
|
import { buttonContent, buttonPart, buttonSx, useButtonAppearance, useButtonBehavior } from "./button-core.js";
|
|
4
4
|
import { announce } from "../utils/dom/announcer.js";
|
|
5
5
|
import { useClipboard } from "../utils/dom/clipboard.js";
|
|
6
|
-
import { createVNode,
|
|
7
|
-
import { _component, readonlyProp, restProps, untrack } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
6
|
+
import { createVNode, ref } from "vue";
|
|
7
|
+
import { _component, mergeJsxProps, readonlyProp, restProps, untrack } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
8
8
|
import * as css from "@zerodep-dev/vue-tsx-pro-tsx/styles";
|
|
9
9
|
import { Check, Copy } from "@lucide/icons";
|
|
10
10
|
//#region src/base/CopyButton.tsx
|
|
@@ -74,7 +74,7 @@ var CopyButton = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
74
74
|
onCopied.value?.(true);
|
|
75
75
|
} else onCopied.value?.(false, error);
|
|
76
76
|
}
|
|
77
|
-
return () => createVNode("button",
|
|
77
|
+
return () => createVNode("button", mergeJsxProps(mergeAttrs({
|
|
78
78
|
...buttonPart,
|
|
79
79
|
sx: buttonSx(appearance(), {
|
|
80
80
|
disabled: disabled.value,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CopyButton.js","names":["_vtp_readonlyProp","_vtp_restProps","_vtp_untrack","_vue_ref","_createVNode","_mergeProps"],"sources":["../../src/base/CopyButton.tsx"],"sourcesContent":["/**\n * 复制按钮(button):点击把 value 复制到剪贴板,图标换成对勾(缩放弹出),读屏播报“已复制”,几秒后复原。\n * 外观同 IconButton(button-core.ts),默认 ghost 样式。\n *\n * <CopyButton value={apiKey} />\n * <CopyButton value={() => editor.getText()} aria-label=\"复制全文\" />\n * <CopyButton value={() => fetchSnippet(id)} /> 点击时才取(返回 Promise)\n */\nimport { Check, Copy } from '@lucide/icons';\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport { useLang } from '../config/context';\nimport { announce } from '../utils/dom/announcer';\nimport { useClipboard } from '../utils/dom/clipboard';\nimport { mergeAttrs, mergePartProps, type PartProps } from '../utils/props';\nimport {\n buttonContent,\n buttonPart,\n buttonSx,\n useButtonAppearance,\n useButtonBehavior,\n type ButtonOptions,\n} from './button-core';\nimport type { IconProps } from './Icon';\n\n/** 不可用、忙碌由组件管理;value 是要复制的文字(不是原生 value) */\nexport type CopyButtonManaged = 'disabled' | 'aria-disabled' | 'aria-busy' | 'value' | 'type';\n\n/** 未声明的属性落到 button 上 */\nexport interface CopyButtonProps\n extends PartProps<'button', CopyButtonManaged>, Omit<ButtonOptions, 'loading' | 'propsSpinner'> {\n /**\n * 要复制的文字;给函数时点击时才取值,可以返回 Promise(内容按需加载,等待时按钮转圈)。\n * 返回字符串时同步写入剪贴板;Safari 只认点击当下的写入,异步内容最好提前加载好\n */\n value: string | (() => string | Promise<string>);\n /** 按钮的无障碍名称,默认取语言包(复制) */\n 'aria-label'?: string;\n /** 对勾保持多久(毫秒)后复原,默认 2000 */\n timeout?: number;\n /**\n * 样式:默认不带底色\n * @default 'ghost'\n */\n variant?: ButtonOptions['variant'];\n /**\n * 色调:默认中性色\n * @default 'neutral'\n */\n tone?: ButtonOptions['tone'];\n /** 不可用 */\n disabled?: boolean;\n /** 复制完成后调用:是否成功,失败时还有原因(不安全的上下文、用户拒绝权限、取值出错) */\n onCopied?: (success: boolean, error?: unknown) => void;\n /** 图标的部件属性 */\n propsIcon?: Partial<IconProps>;\n}\n\nconst pop = css.keyframes({ from: { transform: 'scale(0.5)', opacity: 0 } });\n\n/** CopyButton 的样式:copied(对勾弹出;减少动态效果时时长为 0) */\nexport const copyButtonStyles = css.create({\n copied: {\n animationName: pop,\n animationDuration: '_durations.md',\n animationTimingFunction: '_easings.enter',\n },\n});\n\n/** 复制按钮:复制后显示对勾并播报 */\nexport const CopyButton = _component(\n ({\n value,\n 'aria-label': label,\n timeout,\n variant,\n tone,\n size,\n radius,\n ripple,\n disabled = false,\n onCopied,\n propsIcon,\n ...rest\n }: CopyButtonProps) => {\n const lang = useLang();\n const clipboard = useClipboard({ timeout: _untrack(timeout) });\n // 异步取值时转圈、忽略重复点击\n let pending = _state(false);\n const appearance = useButtonAppearance(\n () => ({ variant, tone, size, radius }),\n 'ghost',\n // 只有图标的按钮多是工具栏、卡片角上的次要操作:默认中性色,一排图标不会满屏品牌色\n 'neutral',\n );\n const behavior = useButtonBehavior(\n () => ({ disabled, loading: pending, ripple }),\n () => () => void copy(),\n );\n\n // 点击处理里不等它:取值出错也在这里收住,经 onCopied 告诉使用方,不留下没人处理的 rejection\n async function copy() {\n let success = false;\n let error: unknown;\n try {\n const text = typeof value === 'function' ? value() : value;\n pending = typeof text !== 'string';\n // 字符串在点击当下就写入(不先 await),Safari 才认\n success = await clipboard.copy(typeof text === 'string' ? text : await text);\n error = clipboard.error;\n } catch (reason) {\n error = reason;\n } finally {\n pending = false;\n }\n if (success) {\n // 经共用的播报区念“已复制”:按钮里的 aria-live 区域各家读屏念不念不一定\n announce(lang.copied);\n onCopied?.(true);\n } else onCopied?.(false, error);\n }\n\n return () => (\n <button\n {...mergeAttrs<'button'>(\n {\n ...buttonPart,\n sx: buttonSx(appearance(), { disabled, square: true }),\n 'aria-label': label ?? lang.copy,\n },\n rest,\n { ...behavior(), type: 'button' },\n )}\n data-state={clipboard.copied ? 'copied' : 'idle'}\n >\n {buttonContent(\n {\n icon: clipboard.copied ? Check : Copy,\n // 换图标时换一个元素,弹出动画才会重播\n propsIcon: mergePartProps<Partial<IconProps>>(propsIcon ?? {}, {\n key: clipboard.copied ? 'copied' : 'copy',\n sx: clipboard.copied && copyButtonStyles.copied,\n }),\n },\n { loading: pending },\n )}\n </button>\n );\n },\n);\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AA0DA,IAAM,MAAM,IAAI,UAAU,EAAE,MAAM;CAAE,WAAW;CAAc,SAAS;AAAE,EAAE,CAAC;;AAG3E,IAAa,mBAAmB,IAAI,OAAO,EACzC,QAAQ;CACN,eAAe;CACf,mBAAmB;CACnB,yBAAyB;AAC3B,EACF,CAAC;;AAGD,IAAa,aAAa,0BACxB,QAAA,SAauB;CAAA,MAAA,QAAAA,aAAA,QAAA,OAAA;CAAA,MAAA,QAAAA,aAAA,QAAA,WAAA;CAAA,MAAA,UAAAA,aAAA,QAAA,SAAA;CAAA,MAAA,UAAAA,aAAA,QAAA,SAAA;CAAA,MAAA,OAAAA,aAAA,QAAA,MAAA;CAAA,MAAA,OAAAA,aAAA,QAAA,MAAA;CAAA,MAAA,SAAAA,aAAA,QAAA,QAAA;CAAA,MAAA,SAAAA,aAAA,QAAA,QAAA;CAAA,MAAA,WAAAA,aAAA,QAAA,YAJV,KAAK;CAAA,MAAA,WAAAA,aAAA,QAAA,UAAA;CAAA,MAAA,YAAAA,aAAA,QAAA,WAAA;CAAA,MAGb,OAAIC,UAAA,QAAA,MAAA,IAAA;CAEP,MAAM,OAAO,QAAQ;CACrB,MAAM,YAAY,aAAa,EAAE,SAAOC,cAAA,QAAA,KAAA,EAAoB,CAAC;CAE7D,IAAI,UAAOC,IAAU,KAAK;CAC1B,MAAM,aAAa,2BACV;EAAE,SAAO,QAAA;EAAE,MAAI,KAAA;EAAE,MAAI,KAAA;EAAE,QAAM,OAAA;CAAC,IACrC,SAEA,SACF;CACA,MAAM,WAAW,yBACR;EAAE,UAAQ,SAAA;EAAE,SAAO,QAAA;EAAW,QAAM,OAAA;CAAC,gBAChC,KAAK,KAAK,CACxB;CAGA,eAAe,OAAO;EACpB,IAAI,UAAU;EACd,IAAI;EACJ,IAAI;GACF,MAAM,OAAO,OAAA,MAAA,UAAiB,aAAa,MAAA,MAAM,IAAC,MAAA;GAClD,QAAA,QAAU,OAAO,SAAS;GAE1B,UAAU,MAAM,UAAU,KAAK,OAAO,SAAS,WAAW,OAAO,MAAM,IAAI;GAC3E,QAAQ,UAAU;EACpB,SAAS,QAAQ;GACf,QAAQ;EACV,UAAU;GACR,QAAA,QAAU;EACZ;EACA,IAAI,SAAS;GAEX,SAAS,KAAK,MAAM;GACpB,SAAA,QAAW,IAAI;EACjB,OAAO,SAAA,QAAW,OAAO,KAAK;CAChC;CAEA,aAAOC,YAAA,UAAAC,
|
|
1
|
+
{"version":3,"file":"CopyButton.js","names":["_vtp_readonlyProp","_vtp_restProps","_vtp_untrack","_vue_ref","_createVNode","_mergeProps"],"sources":["../../src/base/CopyButton.tsx"],"sourcesContent":["/**\n * 复制按钮(button):点击把 value 复制到剪贴板,图标换成对勾(缩放弹出),读屏播报“已复制”,几秒后复原。\n * 外观同 IconButton(button-core.ts),默认 ghost 样式。\n *\n * <CopyButton value={apiKey} />\n * <CopyButton value={() => editor.getText()} aria-label=\"复制全文\" />\n * <CopyButton value={() => fetchSnippet(id)} /> 点击时才取(返回 Promise)\n */\nimport { Check, Copy } from '@lucide/icons';\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport { useLang } from '../config/context';\nimport { announce } from '../utils/dom/announcer';\nimport { useClipboard } from '../utils/dom/clipboard';\nimport { mergeAttrs, mergePartProps, type PartProps } from '../utils/props';\nimport {\n buttonContent,\n buttonPart,\n buttonSx,\n useButtonAppearance,\n useButtonBehavior,\n type ButtonOptions,\n} from './button-core';\nimport type { IconProps } from './Icon';\n\n/** 不可用、忙碌由组件管理;value 是要复制的文字(不是原生 value) */\nexport type CopyButtonManaged = 'disabled' | 'aria-disabled' | 'aria-busy' | 'value' | 'type';\n\n/** 未声明的属性落到 button 上 */\nexport interface CopyButtonProps\n extends PartProps<'button', CopyButtonManaged>, Omit<ButtonOptions, 'loading' | 'propsSpinner'> {\n /**\n * 要复制的文字;给函数时点击时才取值,可以返回 Promise(内容按需加载,等待时按钮转圈)。\n * 返回字符串时同步写入剪贴板;Safari 只认点击当下的写入,异步内容最好提前加载好\n */\n value: string | (() => string | Promise<string>);\n /** 按钮的无障碍名称,默认取语言包(复制) */\n 'aria-label'?: string;\n /** 对勾保持多久(毫秒)后复原,默认 2000 */\n timeout?: number;\n /**\n * 样式:默认不带底色\n * @default 'ghost'\n */\n variant?: ButtonOptions['variant'];\n /**\n * 色调:默认中性色\n * @default 'neutral'\n */\n tone?: ButtonOptions['tone'];\n /** 不可用 */\n disabled?: boolean;\n /** 复制完成后调用:是否成功,失败时还有原因(不安全的上下文、用户拒绝权限、取值出错) */\n onCopied?: (success: boolean, error?: unknown) => void;\n /** 图标的部件属性 */\n propsIcon?: Partial<IconProps>;\n}\n\nconst pop = css.keyframes({ from: { transform: 'scale(0.5)', opacity: 0 } });\n\n/** CopyButton 的样式:copied(对勾弹出;减少动态效果时时长为 0) */\nexport const copyButtonStyles = css.create({\n copied: {\n animationName: pop,\n animationDuration: '_durations.md',\n animationTimingFunction: '_easings.enter',\n },\n});\n\n/** 复制按钮:复制后显示对勾并播报 */\nexport const CopyButton = _component(\n ({\n value,\n 'aria-label': label,\n timeout,\n variant,\n tone,\n size,\n radius,\n ripple,\n disabled = false,\n onCopied,\n propsIcon,\n ...rest\n }: CopyButtonProps) => {\n const lang = useLang();\n const clipboard = useClipboard({ timeout: _untrack(timeout) });\n // 异步取值时转圈、忽略重复点击\n let pending = _state(false);\n const appearance = useButtonAppearance(\n () => ({ variant, tone, size, radius }),\n 'ghost',\n // 只有图标的按钮多是工具栏、卡片角上的次要操作:默认中性色,一排图标不会满屏品牌色\n 'neutral',\n );\n const behavior = useButtonBehavior(\n () => ({ disabled, loading: pending, ripple }),\n () => () => void copy(),\n );\n\n // 点击处理里不等它:取值出错也在这里收住,经 onCopied 告诉使用方,不留下没人处理的 rejection\n async function copy() {\n let success = false;\n let error: unknown;\n try {\n const text = typeof value === 'function' ? value() : value;\n pending = typeof text !== 'string';\n // 字符串在点击当下就写入(不先 await),Safari 才认\n success = await clipboard.copy(typeof text === 'string' ? text : await text);\n error = clipboard.error;\n } catch (reason) {\n error = reason;\n } finally {\n pending = false;\n }\n if (success) {\n // 经共用的播报区念“已复制”:按钮里的 aria-live 区域各家读屏念不念不一定\n announce(lang.copied);\n onCopied?.(true);\n } else onCopied?.(false, error);\n }\n\n return () => (\n <button\n {...mergeAttrs<'button'>(\n {\n ...buttonPart,\n sx: buttonSx(appearance(), { disabled, square: true }),\n 'aria-label': label ?? lang.copy,\n },\n rest,\n { ...behavior(), type: 'button' },\n )}\n data-state={clipboard.copied ? 'copied' : 'idle'}\n >\n {buttonContent(\n {\n icon: clipboard.copied ? Check : Copy,\n // 换图标时换一个元素,弹出动画才会重播\n propsIcon: mergePartProps<Partial<IconProps>>(propsIcon ?? {}, {\n key: clipboard.copied ? 'copied' : 'copy',\n sx: clipboard.copied && copyButtonStyles.copied,\n }),\n },\n { loading: pending },\n )}\n </button>\n );\n },\n);\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AA0DA,IAAM,MAAM,IAAI,UAAU,EAAE,MAAM;CAAE,WAAW;CAAc,SAAS;AAAE,EAAE,CAAC;;AAG3E,IAAa,mBAAmB,IAAI,OAAO,EACzC,QAAQ;CACN,eAAe;CACf,mBAAmB;CACnB,yBAAyB;AAC3B,EACF,CAAC;;AAGD,IAAa,aAAa,0BACxB,QAAA,SAauB;CAAA,MAAA,QAAAA,aAAA,QAAA,OAAA;CAAA,MAAA,QAAAA,aAAA,QAAA,WAAA;CAAA,MAAA,UAAAA,aAAA,QAAA,SAAA;CAAA,MAAA,UAAAA,aAAA,QAAA,SAAA;CAAA,MAAA,OAAAA,aAAA,QAAA,MAAA;CAAA,MAAA,OAAAA,aAAA,QAAA,MAAA;CAAA,MAAA,SAAAA,aAAA,QAAA,QAAA;CAAA,MAAA,SAAAA,aAAA,QAAA,QAAA;CAAA,MAAA,WAAAA,aAAA,QAAA,YAJV,KAAK;CAAA,MAAA,WAAAA,aAAA,QAAA,UAAA;CAAA,MAAA,YAAAA,aAAA,QAAA,WAAA;CAAA,MAGb,OAAIC,UAAA,QAAA,MAAA,IAAA;CAEP,MAAM,OAAO,QAAQ;CACrB,MAAM,YAAY,aAAa,EAAE,SAAOC,cAAA,QAAA,KAAA,EAAoB,CAAC;CAE7D,IAAI,UAAOC,IAAU,KAAK;CAC1B,MAAM,aAAa,2BACV;EAAE,SAAO,QAAA;EAAE,MAAI,KAAA;EAAE,MAAI,KAAA;EAAE,QAAM,OAAA;CAAC,IACrC,SAEA,SACF;CACA,MAAM,WAAW,yBACR;EAAE,UAAQ,SAAA;EAAE,SAAO,QAAA;EAAW,QAAM,OAAA;CAAC,gBAChC,KAAK,KAAK,CACxB;CAGA,eAAe,OAAO;EACpB,IAAI,UAAU;EACd,IAAI;EACJ,IAAI;GACF,MAAM,OAAO,OAAA,MAAA,UAAiB,aAAa,MAAA,MAAM,IAAC,MAAA;GAClD,QAAA,QAAU,OAAO,SAAS;GAE1B,UAAU,MAAM,UAAU,KAAK,OAAO,SAAS,WAAW,OAAO,MAAM,IAAI;GAC3E,QAAQ,UAAU;EACpB,SAAS,QAAQ;GACf,QAAQ;EACV,UAAU;GACR,QAAA,QAAU;EACZ;EACA,IAAI,SAAS;GAEX,SAAS,KAAK,MAAM;GACpB,SAAA,QAAW,IAAI;EACjB,OAAO,SAAA,QAAW,OAAO,KAAK;CAChC;CAEA,aAAOC,YAAA,UAAAC,cAEC,WACF;EACE,GAAG;EACH,IAAI,SAAS,WAAW,GAAG;GAAE,UAAQ,SAAA;GAAE,QAAQ;EAAK,CAAC;EACrD,cAAc,MAAA,SAAS,KAAK;CAC9B,GACA,MACA;EAAE,GAAG,SAAS;EAAG,MAAM;CAAS,CAClC,GAAC,EAAA,cACW,UAAU,SAAS,WAAW,OAAM,CAAA,GAAA,CAE/C,cACC;EACE,MAAM,UAAU,SAAS,QAAQ;EAEjC,WAAW,eAAmC,UAAA,SAAa,CAAC,GAAG;GAC7D,KAAK,UAAU,SAAS,WAAW;GACnC,IAAI,UAAU,UAAU,iBAAiB;EAC3C,CAAC;CACH,GACA,EAAE,SAAO,QAAA,MAAU,CACrB,CAAC,CAAA;AAGP,GAAC;CAAA,MAAA;CAAA,OAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;CAAA;AAAA,CACH"}
|
package/dist/base/Icon.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { mergeAttrs } from "../utils/props.js";
|
|
2
2
|
import { fontSizeStyles, toneStyles } from "../utils/style/typography.js";
|
|
3
3
|
import { useAnimate } from "../utils/motion/animation.js";
|
|
4
|
-
import { createVNode,
|
|
5
|
-
import { _component, readonlyProp, restProps, untrack } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
4
|
+
import { createVNode, onMounted, shallowRef, watch } from "vue";
|
|
5
|
+
import { _component, mergeJsxProps, readonlyProp, restProps, untrack } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
6
6
|
import * as css from "@zerodep-dev/vue-tsx-pro-tsx/styles";
|
|
7
7
|
import { createDrawable, utils } from "animejs";
|
|
8
8
|
//#region src/base/Icon.tsx
|
|
@@ -36,7 +36,7 @@ var Icon = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
36
36
|
if (untrack(() => draw.value) !== void 0) useDraw(() => svg.value, () => draw.value === true);
|
|
37
37
|
return () => {
|
|
38
38
|
const labelled = rest["aria-label"] != null || rest["aria-labelledby"] != null;
|
|
39
|
-
return createVNode("svg",
|
|
39
|
+
return createVNode("svg", mergeJsxProps(mergeAttrs({ sx: [
|
|
40
40
|
iconStyles.icon,
|
|
41
41
|
typeof size.value === "string" && fontSizeStyles[size.value],
|
|
42
42
|
tone.value && toneStyles[tone.value],
|
package/dist/base/Icon.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Icon.js","names":["_vtp_readonlyProp","_vtp_restProps","_vue_shallowRef","_vtp_untrack","_createVNode","_mergeProps"],"sources":["../../src/base/Icon.tsx"],"sourcesContent":["/**\n * 图标:渲染 Lucide 格式的图标数据。使用方安装 @lucide/icons 后按名导入(可摇树):\n *\n * <Icon icon={Search} /> 装饰性:对读屏隐藏\n * <Icon icon={Trash} aria-label=\"删除\" /> 有名称:role=\"img\"\n *\n * 自定义图标按同一格式写 `{ size?, node: [[标签, 属性, 子节点?], …] }` 即可。\n */\nimport type { LucideIconData } from '@lucide/icons';\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport { createDrawable, utils } from 'animejs';\nimport { onMounted, watch } from 'vue';\nimport { useAnimate } from '../utils/motion/animation';\nimport { mergeAttrs, type PartProps } from '../utils/props';\nimport { fontSizeStyles, toneStyles, type TextSize, type Tone } from '../utils/style/typography';\n\n/** 图标数据:Lucide 的标准格式 */\nexport type IconData = LucideIconData;\n\n/** 绘制与无障碍相关的属性由组件管理 */\nexport type IconManaged =\n | 'viewBox'\n | 'fill'\n | 'stroke'\n | 'stroke-width'\n | 'stroke-linecap'\n | 'stroke-linejoin'\n | 'role'\n | 'aria-hidden';\n\n/** 未声明的属性落到 svg 上;传了 aria-label 或 aria-labelledby 才对读屏可见 */\nexport interface IconProps extends PartProps<'svg', IconManaged> {\n /** 图标数据:`import { Search } from '@lucide/icons'` */\n icon: IconData;\n /** 不写时为 1em,跟随所在文字;档位对应主题字号,数字为像素 */\n size?: TextSize | number;\n /** 色调(状态图标:成功、警告…);不写时跟随文字颜色 */\n tone?: Tone;\n /** 线宽(24 格画布上的单位),默认取主题的 strokeWidths.md */\n strokeWidth?: number;\n /** 从右到左排版时水平翻转:箭头、chevron 这类有方向的图标 */\n flipInRtl?: boolean;\n /**\n * 描线动画(一笔画出):挂载时为 true 就在挂载时播,之后每次由 false 变为 true 再播(Anime.js 的 createDrawable,\n * 时长取主题,减少动态效果时直接画好)。要在挂载时就给出(true / false),不给的图标不建动画\n */\n draw?: boolean;\n}\n\n/** Icon 的样式:icon、flip */\nexport const iconStyles = css.create({\n icon: {\n width: '1em',\n height: '1em',\n flexShrink: 0,\n // 与文字基线对齐\n verticalAlign: '-0.125em',\n // 写成 CSS 而不是 stroke-width 属性:主题能统一调整,子元素照常继承\n strokeWidth: '_md',\n },\n flip: { transform: { default: null, ':dir(rtl)': 'scaleX(-1)' } },\n});\n\n/** 图标(svg):渲染 Lucide 格式的图标数据;不传 aria-label 时是装饰性的,对读屏隐藏 */\nexport const Icon = _component(\n ({ icon, size, tone, strokeWidth, flipInRtl = false, draw, ...rest }: IconProps) => {\n let svg = _state.raw<SVGSVGElement | null>(null);\n // 大多数图标不描线:不给 draw 时不建监听与动画\n if (_untrack(draw) !== undefined)\n useDraw(\n () => svg,\n () => draw === true,\n );\n return () => {\n const labelled = rest['aria-label'] != null || rest['aria-labelledby'] != null;\n return (\n <svg\n {...mergeAttrs<'svg'>(\n {\n sx: [\n iconStyles.icon,\n typeof size === 'string' && fontSizeStyles[size],\n tone && toneStyles[tone],\n flipInRtl && iconStyles.flip,\n ],\n },\n rest,\n {\n style: {\n fontSize: typeof size === 'number' ? `${size}px` : undefined,\n strokeWidth,\n },\n viewBox: viewBox(icon),\n fill: 'none',\n stroke: 'currentColor',\n 'stroke-linecap': 'round',\n 'stroke-linejoin': 'round',\n role: labelled ? 'img' : undefined,\n 'aria-hidden': labelled ? undefined : 'true',\n ref: _toRef(svg),\n },\n )}\n innerHTML={iconMarkup(icon)}\n />\n );\n };\n },\n);\n\n/** 描线:draw 变为 true(含挂载时)就从无到有画出每一笔 */\nfunction useDraw(element: () => SVGSVGElement | null, draw: () => boolean) {\n const animate = useAnimate();\n const play = () => {\n const svg = element();\n if (!svg || !draw()) return;\n const strokes = createDrawable([...svg.children] as SVGGeometryElement[]);\n // 先擦掉再画:挂载时在浏览器绘制前就擦掉,不会先闪一下完整的图标\n void utils.set(strokes, { draw: '0 0' });\n void animate(strokes, { draw: '0 1' }, { duration: 'xl', ease: 'enter', scope: svg });\n };\n onMounted(play);\n watch(draw, play, { flush: 'post' });\n}\n\n/** Lucide 图标默认画在 24×24 的画布上;自定义图标可以给出自己的 size 或 width / height */\nfunction viewBox(icon: IconData) {\n const width = icon.width ?? icon.size ?? 24;\n const height = icon.height ?? icon.size ?? 24;\n return `0 0 ${width} ${height}`;\n}\n\n/** Lucide 图标数据里的一个节点:[标签, 属性, 子节点] */\nexport type IconNode = IconData['node'][number];\n\nconst markupCache = new WeakMap<IconData, string>();\n\n/**\n * 图标的子元素(path、circle…)拼成的 SVG 标记,按图标数据缓存,作为 svg 的 innerHTML。\n * 不为每条路径建虚拟节点:一千个图标挂载 39ms → 19ms,按钮、菜单项、勾选框里都有图标\n */\nexport function iconMarkup(icon: IconData): string {\n let markup = markupCache.get(icon);\n if (markup === undefined) markupCache.set(icon, (markup = icon.node.map(nodeMarkup).join('')));\n return markup;\n}\n\nfunction nodeMarkup([tag, attrs, children]: IconNode): string {\n if (!/^[a-z][a-z0-9-]*$/i.test(tag)) throw new Error(`图标数据里的标签名不合法:${tag}`);\n // 节点里的 key 是给虚拟节点用的,不是 SVG 属性\n const attributes = Object.entries(attrs)\n .filter(([name]) => name !== 'key')\n .map(([name, value]) => ` ${name}=\"${escapeAttribute(String(value))}\"`)\n .join('');\n return `<${tag}${attributes}>${children?.map(nodeMarkup).join('') ?? ''}</${tag}>`;\n}\n\nconst escapeAttribute = (value: string) =>\n value.replaceAll('&', '&').replaceAll('\"', '"').replaceAll('<', '<');\n"],"mappings":";;;;;;;;;;;;AAmDA,IAAa,aAAa,IAAI,OAAO;CACnC,MAAM;EACJ,OAAO;EACP,QAAQ;EACR,YAAY;EAEZ,eAAe;EAEf,aAAa;CACf;CACA,MAAM,EAAE,WAAW;EAAE,SAAS;EAAM,aAAa;CAAa,EAAE;AAClE,CAAC;;AAGD,IAAa,OAAO,0BAClB,QAAA,SAAoF;CAAA,MAAA,OAAAA,aAAA,QAAA,MAAA;CAAA,MAAA,OAAAA,aAAA,QAAA,MAAA;CAAA,MAAA,OAAAA,aAAA,QAAA,MAAA;CAAA,MAAA,cAAAA,aAAA,QAAA,aAAA;CAAA,MAAA,YAAAA,aAAA,QAAA,aAAtC,KAAK;CAAA,MAAA,OAAAA,aAAA,QAAA,MAAA;CAAA,MAAW,OAAIC,UAAA,QAAA,MAAA,IAAA;CAChE,IAAI,MAAGC,WAAoC,IAAI;CAE/C,IAAIC,cAAA,KAAA,KAAA,MAAmB,KAAA,GACrB,cACE,IAAA,aACM,KAAA,UAAS,IACjB;CACF,aAAa;EACX,MAAM,WAAW,KAAK,iBAAiB,QAAQ,KAAK,sBAAsB;EAC1E,OAAAC,YAAA,OAAAC,
|
|
1
|
+
{"version":3,"file":"Icon.js","names":["_vtp_readonlyProp","_vtp_restProps","_vue_shallowRef","_vtp_untrack","_createVNode","_mergeProps"],"sources":["../../src/base/Icon.tsx"],"sourcesContent":["/**\n * 图标:渲染 Lucide 格式的图标数据。使用方安装 @lucide/icons 后按名导入(可摇树):\n *\n * <Icon icon={Search} /> 装饰性:对读屏隐藏\n * <Icon icon={Trash} aria-label=\"删除\" /> 有名称:role=\"img\"\n *\n * 自定义图标按同一格式写 `{ size?, node: [[标签, 属性, 子节点?], …] }` 即可。\n */\nimport type { LucideIconData } from '@lucide/icons';\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport { createDrawable, utils } from 'animejs';\nimport { onMounted, watch } from 'vue';\nimport { useAnimate } from '../utils/motion/animation';\nimport { mergeAttrs, type PartProps } from '../utils/props';\nimport { fontSizeStyles, toneStyles, type TextSize, type Tone } from '../utils/style/typography';\n\n/** 图标数据:Lucide 的标准格式 */\nexport type IconData = LucideIconData;\n\n/** 绘制与无障碍相关的属性由组件管理 */\nexport type IconManaged =\n | 'viewBox'\n | 'fill'\n | 'stroke'\n | 'stroke-width'\n | 'stroke-linecap'\n | 'stroke-linejoin'\n | 'role'\n | 'aria-hidden';\n\n/** 未声明的属性落到 svg 上;传了 aria-label 或 aria-labelledby 才对读屏可见 */\nexport interface IconProps extends PartProps<'svg', IconManaged> {\n /** 图标数据:`import { Search } from '@lucide/icons'` */\n icon: IconData;\n /** 不写时为 1em,跟随所在文字;档位对应主题字号,数字为像素 */\n size?: TextSize | number;\n /** 色调(状态图标:成功、警告…);不写时跟随文字颜色 */\n tone?: Tone;\n /** 线宽(24 格画布上的单位),默认取主题的 strokeWidths.md */\n strokeWidth?: number;\n /** 从右到左排版时水平翻转:箭头、chevron 这类有方向的图标 */\n flipInRtl?: boolean;\n /**\n * 描线动画(一笔画出):挂载时为 true 就在挂载时播,之后每次由 false 变为 true 再播(Anime.js 的 createDrawable,\n * 时长取主题,减少动态效果时直接画好)。要在挂载时就给出(true / false),不给的图标不建动画\n */\n draw?: boolean;\n}\n\n/** Icon 的样式:icon、flip */\nexport const iconStyles = css.create({\n icon: {\n width: '1em',\n height: '1em',\n flexShrink: 0,\n // 与文字基线对齐\n verticalAlign: '-0.125em',\n // 写成 CSS 而不是 stroke-width 属性:主题能统一调整,子元素照常继承\n strokeWidth: '_md',\n },\n flip: { transform: { default: null, ':dir(rtl)': 'scaleX(-1)' } },\n});\n\n/** 图标(svg):渲染 Lucide 格式的图标数据;不传 aria-label 时是装饰性的,对读屏隐藏 */\nexport const Icon = _component(\n ({ icon, size, tone, strokeWidth, flipInRtl = false, draw, ...rest }: IconProps) => {\n let svg = _state.raw<SVGSVGElement | null>(null);\n // 大多数图标不描线:不给 draw 时不建监听与动画\n if (_untrack(draw) !== undefined)\n useDraw(\n () => svg,\n () => draw === true,\n );\n return () => {\n const labelled = rest['aria-label'] != null || rest['aria-labelledby'] != null;\n return (\n <svg\n {...mergeAttrs<'svg'>(\n {\n sx: [\n iconStyles.icon,\n typeof size === 'string' && fontSizeStyles[size],\n tone && toneStyles[tone],\n flipInRtl && iconStyles.flip,\n ],\n },\n rest,\n {\n style: {\n fontSize: typeof size === 'number' ? `${size}px` : undefined,\n strokeWidth,\n },\n viewBox: viewBox(icon),\n fill: 'none',\n stroke: 'currentColor',\n 'stroke-linecap': 'round',\n 'stroke-linejoin': 'round',\n role: labelled ? 'img' : undefined,\n 'aria-hidden': labelled ? undefined : 'true',\n ref: _toRef(svg),\n },\n )}\n innerHTML={iconMarkup(icon)}\n />\n );\n };\n },\n);\n\n/** 描线:draw 变为 true(含挂载时)就从无到有画出每一笔 */\nfunction useDraw(element: () => SVGSVGElement | null, draw: () => boolean) {\n const animate = useAnimate();\n const play = () => {\n const svg = element();\n if (!svg || !draw()) return;\n const strokes = createDrawable([...svg.children] as SVGGeometryElement[]);\n // 先擦掉再画:挂载时在浏览器绘制前就擦掉,不会先闪一下完整的图标\n void utils.set(strokes, { draw: '0 0' });\n void animate(strokes, { draw: '0 1' }, { duration: 'xl', ease: 'enter', scope: svg });\n };\n onMounted(play);\n watch(draw, play, { flush: 'post' });\n}\n\n/** Lucide 图标默认画在 24×24 的画布上;自定义图标可以给出自己的 size 或 width / height */\nfunction viewBox(icon: IconData) {\n const width = icon.width ?? icon.size ?? 24;\n const height = icon.height ?? icon.size ?? 24;\n return `0 0 ${width} ${height}`;\n}\n\n/** Lucide 图标数据里的一个节点:[标签, 属性, 子节点] */\nexport type IconNode = IconData['node'][number];\n\nconst markupCache = new WeakMap<IconData, string>();\n\n/**\n * 图标的子元素(path、circle…)拼成的 SVG 标记,按图标数据缓存,作为 svg 的 innerHTML。\n * 不为每条路径建虚拟节点:一千个图标挂载 39ms → 19ms,按钮、菜单项、勾选框里都有图标\n */\nexport function iconMarkup(icon: IconData): string {\n let markup = markupCache.get(icon);\n if (markup === undefined) markupCache.set(icon, (markup = icon.node.map(nodeMarkup).join('')));\n return markup;\n}\n\nfunction nodeMarkup([tag, attrs, children]: IconNode): string {\n if (!/^[a-z][a-z0-9-]*$/i.test(tag)) throw new Error(`图标数据里的标签名不合法:${tag}`);\n // 节点里的 key 是给虚拟节点用的,不是 SVG 属性\n const attributes = Object.entries(attrs)\n .filter(([name]) => name !== 'key')\n .map(([name, value]) => ` ${name}=\"${escapeAttribute(String(value))}\"`)\n .join('');\n return `<${tag}${attributes}>${children?.map(nodeMarkup).join('') ?? ''}</${tag}>`;\n}\n\nconst escapeAttribute = (value: string) =>\n value.replaceAll('&', '&').replaceAll('\"', '"').replaceAll('<', '<');\n"],"mappings":"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"}
|
package/dist/base/Indicator.js
CHANGED
|
@@ -5,8 +5,8 @@ import { mergeAttrs } from "../utils/props.js";
|
|
|
5
5
|
import { useRecipe } from "../config/styles.js";
|
|
6
6
|
import { VisuallyHidden } from "./VisuallyHidden.js";
|
|
7
7
|
import { statusToneSx } from "../utils/style/tone-styles.js";
|
|
8
|
-
import { createVNode
|
|
9
|
-
import { _component, childrenProp, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
8
|
+
import { createVNode } from "vue";
|
|
9
|
+
import { _component, childrenProp, mergeJsxProps, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
10
10
|
import * as css from "@zerodep-dev/vue-tsx-pro-tsx/styles";
|
|
11
11
|
//#region src/base/Indicator.tsx
|
|
12
12
|
/** 角标在被标记元素的哪个角(按书写方向) */
|
|
@@ -129,7 +129,7 @@ var Indicator = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
129
129
|
overlap: overlap.value,
|
|
130
130
|
tone: shownTone
|
|
131
131
|
});
|
|
132
|
-
return createVNode("span", mergeAttrs(s.root, rest), [children.value, visible && createVNode("span",
|
|
132
|
+
return createVNode("span", mergeAttrs(s.root, rest), [children.value, visible && createVNode("span", mergeJsxProps(mergeAttrs({
|
|
133
133
|
...s.badge,
|
|
134
134
|
sx: [statusToneSx("solid", shownTone), s.badge.sx]
|
|
135
135
|
}, propsBadge.value), { "data-placement": placement.value }), [!dot.value && createVNode("span", mergeAttrs(s.count, propsCount.value, { "aria-hidden": true }), [text]), createVNode(VisuallyHidden, { "key": "__site0" }, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Indicator.js","names":["_vtp_readonlyProp","_vtp_childrenProp","_vtp_restProps","_createVNode","_mergeProps"],"sources":["../../src/base/Indicator.tsx"],"sourcesContent":["/**\n * 角标(span):叠在头像、图标按钮右上角的计数或小圆点(未读消息、在线状态)。\n * 包住要标记的元素,角标绝对定位在它的角上;计数超过 max 显示“99+”。\n *\n * <Indicator count={unread}><IconButton icon={Bell} aria-label=\"通知\" /></Indicator>\n * <Indicator dot tone=\"success\" placement=\"bottom-end\" overlap=\"circular\" label=\"在线\"><Avatar name=\"张三\" /></Indicator>\n *\n * 读屏:角标里的数字对读屏隐藏,另有一句完整的说明(label,默认取语言包的“N 条新消息”“有新消息”),念在被标记的元素之后。\n * count 为 0(且没有 showZero)、也没有 dot 时不显示角标。部件:Root(外层 span)、Badge(角标)、Count(计数文字)。\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 { useLang } from '../config/context';\nimport { useDefaults } from '../config/defaults';\nimport { useRecipe } from '../config/styles';\nimport { mergeAttrs, type PartProps } from '../utils/props';\nimport { statusToneSx, type StatusTone } from '../utils/style/tone-styles';\nimport { VisuallyHidden } from './VisuallyHidden';\nimport { t } from '../config/theme/tokens';\n\n/** 角标在被标记元素的哪个角(按书写方向) */\nexport interface IndicatorPlacements {\n /** 右上(默认;从右到左排版时左上) */\n 'top-end': unknown;\n /** 右下:头像的在线状态 */\n 'bottom-end': unknown;\n /** 左上 */\n 'top-start': unknown;\n /** 左下 */\n 'bottom-start': unknown;\n}\n\n/** 被标记元素的形状:决定角标往里收多少 */\nexport interface IndicatorOverlaps {\n /** 方形(默认):角标中心在角上 */\n rectangular: unknown;\n /** 圆形(圆形头像):角标收到圆的边上 */\n circular: unknown;\n}\n\n/** Indicator 的外观选项:可以在 ComponentDefaults 里统一设置 */\nexport interface IndicatorOptions {\n /** 色调,默认 danger */\n tone?: StatusTone;\n}\n\n/** 未声明的属性落到外层 span 上 */\nexport interface IndicatorProps extends PartProps<'span'>, IndicatorOptions {\n /** 计数;超过 max 显示“max+” */\n count?: number;\n /** 计数的上限,默认 99 */\n max?: number;\n /** 只显示小圆点(不显示数字) */\n dot?: boolean;\n /** count 为 0 时也显示 */\n showZero?: boolean;\n /** 位置,默认 top-end */\n placement?: keyof IndicatorPlacements;\n /** 被标记元素的形状,默认 rectangular */\n overlap?: keyof IndicatorOverlaps;\n /** 给读屏的说明;默认取语言包(“N 条新消息”“有新消息”) */\n label?: string;\n /** 角标的部件属性 */\n propsBadge?: PartProps<'span'>;\n /** 角标里计数文字的部件属性(读屏念的是 label,计数文字对读屏隐藏) */\n propsCount?: PartProps<'span', 'aria-hidden'>;\n /** 被标记的元素 */\n children?: VNodeChild;\n}\n\n/**\n * 角标的配方:部件 root(外层)、badge(角标)、count(计数文字);位置、形状、圆点是档位。\n * 角标的实底色调来自共用的 statusToneSx,排在配方之前,createUi / Provider 的 styles.Indicator 能盖过它\n */\nexport const indicatorRecipe = css.recipe({\n name: 'Indicator',\n parts: ['root', 'badge', 'count'],\n base: {\n root: { position: 'relative', display: 'inline-flex', verticalAlign: 'middle', flexShrink: 0 },\n badge: {\n position: 'absolute',\n zIndex: 1,\n boxSizing: 'border-box',\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n borderRadius: '_full',\n // 一圈页面底色,和被标记的元素分开\n boxShadow: `0 0 0 ${t.borderWidths.sm} ${t.colors.background}`,\n fontWeight: '_semibold',\n lineHeight: 1,\n fontVariantNumeric: 'tabular-nums',\n whiteSpace: 'nowrap',\n pointerEvents: 'none',\n // 圆点只靠颜色表示“有新消息”:Windows 高对比下保留色调,不被系统改成背景色\n forcedColorAdjust: 'none',\n },\n },\n variants: {\n // 圆点或计数:角标的尺寸\n dot: {\n true: { badge: { width: '0.625em', height: '0.625em' } },\n false: {\n badge: { minWidth: '1.6em', height: '1.6em', paddingInline: '0.4em', fontSize: '0.75em' },\n },\n },\n // 角标的中心对准角:按书写方向的位移\n placement: {\n 'top-end': {\n badge: {\n top: 0,\n insetInlineEnd: 0,\n translate: { default: '50% -50%', ':dir(rtl)': '-50% -50%' },\n },\n },\n 'bottom-end': {\n badge: {\n bottom: 0,\n insetInlineEnd: 0,\n translate: { default: '50% 50%', ':dir(rtl)': '-50% 50%' },\n },\n },\n 'top-start': {\n badge: {\n top: 0,\n insetInlineStart: 0,\n translate: { default: '-50% -50%', ':dir(rtl)': '50% -50%' },\n },\n },\n 'bottom-start': {\n badge: {\n bottom: 0,\n insetInlineStart: 0,\n translate: { default: '-50% 50%', ':dir(rtl)': '50% 50%' },\n },\n },\n },\n overlap: {\n rectangular: {},\n // 圆形:角落到圆边的距离约是边长的 14.6%(1 - 1/√2 的一半),角标收进去这么多\n circular: { badge: { margin: '14.6%' } },\n },\n },\n});\n\n/** 角标的部件(createUi / Provider 的 styles.Indicator 按它定制) */\nexport type IndicatorPart = (typeof indicatorRecipe.parts)[number];\n\n/** 角标:叠在被标记元素角上的计数或小圆点 */\nexport const Indicator = _component(\n ({\n count,\n max = 99,\n dot = false,\n showZero = false,\n tone,\n placement = 'top-end',\n overlap = 'rectangular',\n label,\n propsBadge,\n propsCount,\n children,\n ...rest\n }: IndicatorProps) => {\n const lang = useLang();\n const appearance = useDefaults('Indicator', () => ({ tone }), { tone: 'danger' });\n const recipe = useRecipe(indicatorRecipe);\n return () => {\n const visible = dot || (count != null && (count > 0 || showZero));\n const text = count == null ? '' : count > max ? `${max}+` : String(count);\n const shownTone = appearance().tone;\n const s = recipe({ dot, placement, overlap, tone: shownTone });\n return (\n <span {...mergeAttrs<'span'>(s.root, rest)}>\n {children}\n {visible && (\n <span\n {...mergeAttrs<'span'>(\n { ...s.badge, sx: [statusToneSx('solid', shownTone), s.badge.sx] },\n propsBadge,\n )}\n data-placement={placement}\n >\n {!dot && (\n <span {...mergeAttrs<'span'>(s.count, propsCount, { 'aria-hidden': true })}>\n {text}\n </span>\n )}\n <VisuallyHidden>\n {label ?? (dot ? lang.indicatorDot : lang.indicatorCount(count ?? 0))}\n </VisuallyHidden>\n </span>\n )}\n </span>\n );\n };\n },\n);\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA2EA,IAAa,kBAAkB,IAAI,OAAO;CACxC,MAAM;CACN,OAAO;EAAC;EAAQ;EAAS;CAAO;CAChC,MAAM;EACJ,MAAM;GAAE,UAAU;GAAY,SAAS;GAAe,eAAe;GAAU,YAAY;EAAE;EAC7F,OAAO;GACL,UAAU;GACV,QAAQ;GACR,WAAW;GACX,SAAS;GACT,YAAY;GACZ,gBAAgB;GAChB,cAAc;GAEd,WAAW,SAAS,EAAE,aAAa,GAAE,GAAI,EAAE,OAAO;GAClD,YAAY;GACZ,YAAY;GACZ,oBAAoB;GACpB,YAAY;GACZ,eAAe;GAEf,mBAAmB;EACrB;CACF;CACA,UAAU;EAER,KAAK;GACH,MAAM,EAAE,OAAO;IAAE,OAAO;IAAW,QAAQ;GAAU,EAAE;GACvD,OAAO,EACL,OAAO;IAAE,UAAU;IAAS,QAAQ;IAAS,eAAe;IAAS,UAAU;GAAS,EAC1F;EACF;EAEA,WAAW;GACT,WAAW,EACT,OAAO;IACL,KAAK;IACL,gBAAgB;IAChB,WAAW;KAAE,SAAS;KAAY,aAAa;IAAY;GAC7D,EACF;GACA,cAAc,EACZ,OAAO;IACL,QAAQ;IACR,gBAAgB;IAChB,WAAW;KAAE,SAAS;KAAW,aAAa;IAAW;GAC3D,EACF;GACA,aAAa,EACX,OAAO;IACL,KAAK;IACL,kBAAkB;IAClB,WAAW;KAAE,SAAS;KAAa,aAAa;IAAW;GAC7D,EACF;GACA,gBAAgB,EACd,OAAO;IACL,QAAQ;IACR,kBAAkB;IAClB,WAAW;KAAE,SAAS;KAAY,aAAa;IAAU;GAC3D,EACF;EACF;EACA,SAAS;GACP,aAAa,CAAC;GAEd,UAAU,EAAE,OAAO,EAAE,QAAQ,QAAQ,EAAE;EACzC;CACF;AACF,CAAC;;;AAMD,IAAa,YAAY,0BACvB,QAAA,SAasB;CAAA,MAAA,QAAAA,aAAA,QAAA,OAAA;CAAA,MAAA,MAAAA,aAAA,QAAA,OAXd,EAAE;CAAA,MAAA,MAAAA,aAAA,QAAA,OACF,KAAK;CAAA,MAAA,WAAAA,aAAA,QAAA,YACA,KAAK;CAAA,MAAA,OAAAA,aAAA,QAAA,MAAA;CAAA,MAAA,YAAAA,aAAA,QAAA,aAEJ,SAAS;CAAA,MAAA,UAAAA,aAAA,QAAA,WACX,aAAa;CAAA,MAAA,QAAAA,aAAA,QAAA,OAAA;CAAA,MAAA,aAAAA,aAAA,QAAA,YAAA;CAAA,MAAA,aAAAA,aAAA,QAAA,YAAA;CAAA,MAAA,WAAAC,aAAA,MAAA;CAAA,MAKpB,OAAIC,UAAA,QAAA,MAAA,KAAA;CAEP,MAAM,OAAO,QAAQ;CACrB,MAAM,aAAa,YAAY,oBAAoB,EAAE,MAAI,KAAA,MAAC,IAAI,EAAE,MAAM,SAAS,CAAC;CAChF,MAAM,SAAS,UAAU,eAAe;CACxC,aAAa;EACX,MAAM,UAAU,IAAA,SAAQ,MAAA,SAAS,SAAS,MAAA,QAAQ,KAAC,SAAA;EACnD,MAAM,OAAO,MAAA,SAAS,OAAO,KAAK,MAAA,QAAA,IAAA,QAAc,GAAA,IAAA,MAAA,KAAY,OAAM,MAAA,KAAM;EACxE,MAAM,YAAY,WAAW,CAAC,CAAC;EAC/B,MAAM,IAAI,OAAO;GAAE,KAAG,IAAA;GAAE,WAAS,UAAA;GAAE,SAAO,QAAA;GAAE,MAAM;EAAU,CAAC;EAC7D,OAAAC,YAAA,QACY,WAAmB,EAAE,MAAM,IAAI,GAAC,CAAA,SAAA,OAEvC,WAAOA,YAAA,QAAAC,
|
|
1
|
+
{"version":3,"file":"Indicator.js","names":["_vtp_readonlyProp","_vtp_childrenProp","_vtp_restProps","_createVNode","_mergeProps"],"sources":["../../src/base/Indicator.tsx"],"sourcesContent":["/**\n * 角标(span):叠在头像、图标按钮右上角的计数或小圆点(未读消息、在线状态)。\n * 包住要标记的元素,角标绝对定位在它的角上;计数超过 max 显示“99+”。\n *\n * <Indicator count={unread}><IconButton icon={Bell} aria-label=\"通知\" /></Indicator>\n * <Indicator dot tone=\"success\" placement=\"bottom-end\" overlap=\"circular\" label=\"在线\"><Avatar name=\"张三\" /></Indicator>\n *\n * 读屏:角标里的数字对读屏隐藏,另有一句完整的说明(label,默认取语言包的“N 条新消息”“有新消息”),念在被标记的元素之后。\n * count 为 0(且没有 showZero)、也没有 dot 时不显示角标。部件:Root(外层 span)、Badge(角标)、Count(计数文字)。\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 { useLang } from '../config/context';\nimport { useDefaults } from '../config/defaults';\nimport { useRecipe } from '../config/styles';\nimport { mergeAttrs, type PartProps } from '../utils/props';\nimport { statusToneSx, type StatusTone } from '../utils/style/tone-styles';\nimport { VisuallyHidden } from './VisuallyHidden';\nimport { t } from '../config/theme/tokens';\n\n/** 角标在被标记元素的哪个角(按书写方向) */\nexport interface IndicatorPlacements {\n /** 右上(默认;从右到左排版时左上) */\n 'top-end': unknown;\n /** 右下:头像的在线状态 */\n 'bottom-end': unknown;\n /** 左上 */\n 'top-start': unknown;\n /** 左下 */\n 'bottom-start': unknown;\n}\n\n/** 被标记元素的形状:决定角标往里收多少 */\nexport interface IndicatorOverlaps {\n /** 方形(默认):角标中心在角上 */\n rectangular: unknown;\n /** 圆形(圆形头像):角标收到圆的边上 */\n circular: unknown;\n}\n\n/** Indicator 的外观选项:可以在 ComponentDefaults 里统一设置 */\nexport interface IndicatorOptions {\n /** 色调,默认 danger */\n tone?: StatusTone;\n}\n\n/** 未声明的属性落到外层 span 上 */\nexport interface IndicatorProps extends PartProps<'span'>, IndicatorOptions {\n /** 计数;超过 max 显示“max+” */\n count?: number;\n /** 计数的上限,默认 99 */\n max?: number;\n /** 只显示小圆点(不显示数字) */\n dot?: boolean;\n /** count 为 0 时也显示 */\n showZero?: boolean;\n /** 位置,默认 top-end */\n placement?: keyof IndicatorPlacements;\n /** 被标记元素的形状,默认 rectangular */\n overlap?: keyof IndicatorOverlaps;\n /** 给读屏的说明;默认取语言包(“N 条新消息”“有新消息”) */\n label?: string;\n /** 角标的部件属性 */\n propsBadge?: PartProps<'span'>;\n /** 角标里计数文字的部件属性(读屏念的是 label,计数文字对读屏隐藏) */\n propsCount?: PartProps<'span', 'aria-hidden'>;\n /** 被标记的元素 */\n children?: VNodeChild;\n}\n\n/**\n * 角标的配方:部件 root(外层)、badge(角标)、count(计数文字);位置、形状、圆点是档位。\n * 角标的实底色调来自共用的 statusToneSx,排在配方之前,createUi / Provider 的 styles.Indicator 能盖过它\n */\nexport const indicatorRecipe = css.recipe({\n name: 'Indicator',\n parts: ['root', 'badge', 'count'],\n base: {\n root: { position: 'relative', display: 'inline-flex', verticalAlign: 'middle', flexShrink: 0 },\n badge: {\n position: 'absolute',\n zIndex: 1,\n boxSizing: 'border-box',\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n borderRadius: '_full',\n // 一圈页面底色,和被标记的元素分开\n boxShadow: `0 0 0 ${t.borderWidths.sm} ${t.colors.background}`,\n fontWeight: '_semibold',\n lineHeight: 1,\n fontVariantNumeric: 'tabular-nums',\n whiteSpace: 'nowrap',\n pointerEvents: 'none',\n // 圆点只靠颜色表示“有新消息”:Windows 高对比下保留色调,不被系统改成背景色\n forcedColorAdjust: 'none',\n },\n },\n variants: {\n // 圆点或计数:角标的尺寸\n dot: {\n true: { badge: { width: '0.625em', height: '0.625em' } },\n false: {\n badge: { minWidth: '1.6em', height: '1.6em', paddingInline: '0.4em', fontSize: '0.75em' },\n },\n },\n // 角标的中心对准角:按书写方向的位移\n placement: {\n 'top-end': {\n badge: {\n top: 0,\n insetInlineEnd: 0,\n translate: { default: '50% -50%', ':dir(rtl)': '-50% -50%' },\n },\n },\n 'bottom-end': {\n badge: {\n bottom: 0,\n insetInlineEnd: 0,\n translate: { default: '50% 50%', ':dir(rtl)': '-50% 50%' },\n },\n },\n 'top-start': {\n badge: {\n top: 0,\n insetInlineStart: 0,\n translate: { default: '-50% -50%', ':dir(rtl)': '50% -50%' },\n },\n },\n 'bottom-start': {\n badge: {\n bottom: 0,\n insetInlineStart: 0,\n translate: { default: '-50% 50%', ':dir(rtl)': '50% 50%' },\n },\n },\n },\n overlap: {\n rectangular: {},\n // 圆形:角落到圆边的距离约是边长的 14.6%(1 - 1/√2 的一半),角标收进去这么多\n circular: { badge: { margin: '14.6%' } },\n },\n },\n});\n\n/** 角标的部件(createUi / Provider 的 styles.Indicator 按它定制) */\nexport type IndicatorPart = (typeof indicatorRecipe.parts)[number];\n\n/** 角标:叠在被标记元素角上的计数或小圆点 */\nexport const Indicator = _component(\n ({\n count,\n max = 99,\n dot = false,\n showZero = false,\n tone,\n placement = 'top-end',\n overlap = 'rectangular',\n label,\n propsBadge,\n propsCount,\n children,\n ...rest\n }: IndicatorProps) => {\n const lang = useLang();\n const appearance = useDefaults('Indicator', () => ({ tone }), { tone: 'danger' });\n const recipe = useRecipe(indicatorRecipe);\n return () => {\n const visible = dot || (count != null && (count > 0 || showZero));\n const text = count == null ? '' : count > max ? `${max}+` : String(count);\n const shownTone = appearance().tone;\n const s = recipe({ dot, placement, overlap, tone: shownTone });\n return (\n <span {...mergeAttrs<'span'>(s.root, rest)}>\n {children}\n {visible && (\n <span\n {...mergeAttrs<'span'>(\n { ...s.badge, sx: [statusToneSx('solid', shownTone), s.badge.sx] },\n propsBadge,\n )}\n data-placement={placement}\n >\n {!dot && (\n <span {...mergeAttrs<'span'>(s.count, propsCount, { 'aria-hidden': true })}>\n {text}\n </span>\n )}\n <VisuallyHidden>\n {label ?? (dot ? lang.indicatorDot : lang.indicatorCount(count ?? 0))}\n </VisuallyHidden>\n </span>\n )}\n </span>\n );\n };\n },\n);\n"],"mappings":"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"}
|
package/dist/base/StatusDot.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { mergeAttrs } from "../utils/props.js";
|
|
2
|
-
import { createVNode
|
|
3
|
-
import { _component, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
2
|
+
import { createVNode } from "vue";
|
|
3
|
+
import { _component, mergeJsxProps, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
4
4
|
import * as css from "@zerodep-dev/vue-tsx-pro-tsx/styles";
|
|
5
5
|
//#region src/base/StatusDot.tsx
|
|
6
6
|
/** 色点的大小 */
|
|
@@ -39,7 +39,7 @@ var StatusDot = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
39
39
|
const size = readonlyProp(_props, "size", "sm");
|
|
40
40
|
const label = readonlyProp(_props, "label");
|
|
41
41
|
const rest = restProps(_props, _ctx, true);
|
|
42
|
-
return () => createVNode("span",
|
|
42
|
+
return () => createVNode("span", mergeJsxProps(mergeAttrs({ sx: [
|
|
43
43
|
statusDotStyles.root,
|
|
44
44
|
statusDotStyles[size.value],
|
|
45
45
|
color.value ? css.bind(statusDotDynamicStyles.color, () => [color.value], true) : statusDotToneStyles[tone.value]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StatusDot.js","names":["_vtp_readonlyProp","_vtp_restProps","_createVNode","_mergeProps"],"sources":["../../src/base/StatusDot.tsx"],"sourcesContent":["/**\n * 状态色点:一个小圆点标出状态或类别(未读、新到、退回、图例),比 Badge 更轻。\n *\n * <StatusDot tone=\"danger\" /> 装饰:对读屏隐藏,含义写在旁边的文字里\n * <StatusDot tone=\"accent\" label=\"未读\" /> 点本身就是信息:读屏念 label\n *\n * 色调与 Badge 同名(neutral 是次要文字色);color 给任意 CSS 颜色(图表的系列色)。\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport { mergeAttrs, type PartProps } from '../utils/props';\nimport type { StatusTone } from '../utils/style/tone-styles';\n\n/** 色点的大小 */\nexport interface StatusDotSizes {\n /** 0.5rem(默认):列表、通知 */\n sm: unknown;\n /** 0.625rem:图例 */\n md: unknown;\n}\n\n/** 未声明的属性落到 span 上 */\nexport interface StatusDotProps extends PartProps<'span', 'role' | 'aria-hidden'> {\n /**\n * 色调\n * @default 'accent'\n */\n tone?: StatusTone;\n /** 任意 CSS 颜色;写了时不看 tone */\n color?: string;\n /**\n * 大小\n * @default 'sm'\n */\n size?: keyof StatusDotSizes;\n /** 读屏念的含义;不写时色点只是装饰(aria-hidden) */\n label?: string;\n}\n\n/** StatusDot 的样式:root、sm、md */\nexport const statusDotStyles = css.create({\n root: { display: 'inline-block', flexShrink: 0, borderRadius: '_full' },\n sm: { width: '0.5rem', height: '0.5rem' },\n md: { width: '0.625rem', height: '0.625rem' },\n});\n\n/** StatusDot 的色调 */\nexport const statusDotToneStyles = css.create({\n neutral: { backgroundColor: '_textMuted' },\n accent: { backgroundColor: '_accent' },\n info: { backgroundColor: '_info' },\n success: { backgroundColor: '_success' },\n warning: { backgroundColor: '_warning' },\n danger: { backgroundColor: '_danger' },\n});\n\n/** StatusDot 的动态样式:任意颜色 */\nexport const statusDotDynamicStyles = css.create({\n color: (color: string) => ({ backgroundColor: color }),\n});\n\n/** 状态色点 */\nexport const StatusDot = _component(\n ({ tone = 'accent', color, size = 'sm', label, ...rest }: StatusDotProps) =>\n () => (\n <span\n {...mergeAttrs<'span'>(\n {\n sx: [\n statusDotStyles.root,\n statusDotStyles[size],\n color ? statusDotDynamicStyles.color(color) : statusDotToneStyles[tone],\n ],\n },\n rest,\n label ? { role: 'img', 'aria-label': label } : { 'aria-hidden': true },\n )}\n data-tone={color ? undefined : tone}\n />\n ),\n);\n"],"mappings":";;;;;;;;AAwCA,IAAa,kBAAkB,IAAI,OAAO;CACxC,MAAM;EAAE,SAAS;EAAgB,YAAY;EAAG,cAAc;CAAQ;CACtE,IAAI;EAAE,OAAO;EAAU,QAAQ;CAAS;CACxC,IAAI;EAAE,OAAO;EAAY,QAAQ;CAAW;AAC9C,CAAC;;AAGD,IAAa,sBAAsB,IAAI,OAAO;CAC5C,SAAS,EAAE,iBAAiB,aAAa;CACzC,QAAQ,EAAE,iBAAiB,UAAU;CACrC,MAAM,EAAE,iBAAiB,QAAQ;CACjC,SAAS,EAAE,iBAAiB,WAAW;CACvC,SAAS,EAAE,iBAAiB,WAAW;CACvC,QAAQ,EAAE,iBAAiB,UAAU;AACvC,CAAC;;AAGD,IAAa,yBAAyB,IAAI,OAAO,EAC/C,QAAQ,WAAmB,EAAE,iBAAiB,MAAM,GACtD,CAAC;;AAGD,IAAa,YAAY,0BACvB,QAAA,SAAA;CAAA,MAAA,OAAAA,aAAA,QAAA,QAAU,QAAQ;CAAA,MAAA,QAAAA,aAAA,QAAA,OAAA;CAAA,MAAA,OAAAA,aAAA,QAAA,QAAgB,IAAI;CAAA,MAAA,QAAAA,aAAA,QAAA,OAAA;CAAA,MAAY,OAAIC,UAAA,QAAA,MAAA,IAAA;CAAA,aACpDC,YAAA,QAAAC,
|
|
1
|
+
{"version":3,"file":"StatusDot.js","names":["_vtp_readonlyProp","_vtp_restProps","_createVNode","_mergeProps"],"sources":["../../src/base/StatusDot.tsx"],"sourcesContent":["/**\n * 状态色点:一个小圆点标出状态或类别(未读、新到、退回、图例),比 Badge 更轻。\n *\n * <StatusDot tone=\"danger\" /> 装饰:对读屏隐藏,含义写在旁边的文字里\n * <StatusDot tone=\"accent\" label=\"未读\" /> 点本身就是信息:读屏念 label\n *\n * 色调与 Badge 同名(neutral 是次要文字色);color 给任意 CSS 颜色(图表的系列色)。\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport { mergeAttrs, type PartProps } from '../utils/props';\nimport type { StatusTone } from '../utils/style/tone-styles';\n\n/** 色点的大小 */\nexport interface StatusDotSizes {\n /** 0.5rem(默认):列表、通知 */\n sm: unknown;\n /** 0.625rem:图例 */\n md: unknown;\n}\n\n/** 未声明的属性落到 span 上 */\nexport interface StatusDotProps extends PartProps<'span', 'role' | 'aria-hidden'> {\n /**\n * 色调\n * @default 'accent'\n */\n tone?: StatusTone;\n /** 任意 CSS 颜色;写了时不看 tone */\n color?: string;\n /**\n * 大小\n * @default 'sm'\n */\n size?: keyof StatusDotSizes;\n /** 读屏念的含义;不写时色点只是装饰(aria-hidden) */\n label?: string;\n}\n\n/** StatusDot 的样式:root、sm、md */\nexport const statusDotStyles = css.create({\n root: { display: 'inline-block', flexShrink: 0, borderRadius: '_full' },\n sm: { width: '0.5rem', height: '0.5rem' },\n md: { width: '0.625rem', height: '0.625rem' },\n});\n\n/** StatusDot 的色调 */\nexport const statusDotToneStyles = css.create({\n neutral: { backgroundColor: '_textMuted' },\n accent: { backgroundColor: '_accent' },\n info: { backgroundColor: '_info' },\n success: { backgroundColor: '_success' },\n warning: { backgroundColor: '_warning' },\n danger: { backgroundColor: '_danger' },\n});\n\n/** StatusDot 的动态样式:任意颜色 */\nexport const statusDotDynamicStyles = css.create({\n color: (color: string) => ({ backgroundColor: color }),\n});\n\n/** 状态色点 */\nexport const StatusDot = _component(\n ({ tone = 'accent', color, size = 'sm', label, ...rest }: StatusDotProps) =>\n () => (\n <span\n {...mergeAttrs<'span'>(\n {\n sx: [\n statusDotStyles.root,\n statusDotStyles[size],\n color ? statusDotDynamicStyles.color(color) : statusDotToneStyles[tone],\n ],\n },\n rest,\n label ? { role: 'img', 'aria-label': label } : { 'aria-hidden': true },\n )}\n data-tone={color ? undefined : tone}\n />\n ),\n);\n"],"mappings":";;;;;;;;AAwCA,IAAa,kBAAkB,IAAI,OAAO;CACxC,MAAM;EAAE,SAAS;EAAgB,YAAY;EAAG,cAAc;CAAQ;CACtE,IAAI;EAAE,OAAO;EAAU,QAAQ;CAAS;CACxC,IAAI;EAAE,OAAO;EAAY,QAAQ;CAAW;AAC9C,CAAC;;AAGD,IAAa,sBAAsB,IAAI,OAAO;CAC5C,SAAS,EAAE,iBAAiB,aAAa;CACzC,QAAQ,EAAE,iBAAiB,UAAU;CACrC,MAAM,EAAE,iBAAiB,QAAQ;CACjC,SAAS,EAAE,iBAAiB,WAAW;CACvC,SAAS,EAAE,iBAAiB,WAAW;CACvC,QAAQ,EAAE,iBAAiB,UAAU;AACvC,CAAC;;AAGD,IAAa,yBAAyB,IAAI,OAAO,EAC/C,QAAQ,WAAmB,EAAE,iBAAiB,MAAM,GACtD,CAAC;;AAGD,IAAa,YAAY,0BACvB,QAAA,SAAA;CAAA,MAAA,OAAAA,aAAA,QAAA,QAAU,QAAQ;CAAA,MAAA,QAAAA,aAAA,QAAA,OAAA;CAAA,MAAA,OAAAA,aAAA,QAAA,QAAgB,IAAI;CAAA,MAAA,QAAAA,aAAA,QAAA,OAAA;CAAA,MAAY,OAAIC,UAAA,QAAA,MAAA,IAAA;CAAA,aACpDC,YAAA,QAAAC,cAEQ,WACF,EACE,IAAI;EACF,gBAAgB;EAChB,gBAAe,KAAA;EACf,MAAA,QAAA,IAAA,KAAQ,uBAAuB,aAAK,CAAA,MAAA,KAAA,GAAA,IAAA,IAAU,oBAAmB,KAAA;CAAM,EAE3E,GACA,MACA,MAAA,QAAQ;EAAE,MAAM;EAAO,cAAY,MAAA;CAAQ,IAAI,EAAE,eAAe,KAAK,CACvE,GAAC,EAAA,aACU,MAAA,QAAQ,KAAA,IAAS,KAAA,MAAO,CAAA,GAAA,IAAA;AAEtC,GAAA;CAAA,MAAA;CAAA,OAAA;EAAA;EAAA;EAAA;EAAA;CAAA;AAAA,CACL"}
|
package/dist/base/Tag.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { t } from "../config/theme/tokens.js";
|
|
2
2
|
import { useLang } from "../config/context.js";
|
|
3
3
|
import { useDefaults } from "../config/defaults.js";
|
|
4
|
-
import { mergeAttrs, mergePartProps, mergeProps
|
|
4
|
+
import { mergeAttrs, mergePartProps, mergeProps } from "../utils/props.js";
|
|
5
5
|
import { badgeParts, badgeRecipe } from "./badge-styles.js";
|
|
6
6
|
import { useRecipe } from "../config/styles.js";
|
|
7
7
|
import { Icon } from "./Icon.js";
|
|
8
8
|
import { CloseButton } from "./CloseButton.js";
|
|
9
9
|
import { useDismissible } from "../utils/motion/presence.js";
|
|
10
10
|
import { plainText } from "../utils/data/text.js";
|
|
11
|
-
import { createVNode
|
|
12
|
-
import { _component, childrenProp, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
11
|
+
import { createVNode } from "vue";
|
|
12
|
+
import { _component, childrenProp, mergeJsxProps, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
13
13
|
import * as css from "@zerodep-dev/vue-tsx-pro-tsx/styles";
|
|
14
14
|
//#region src/base/Tag.tsx
|
|
15
15
|
/** 未声明的属性落到 span 上 */
|
|
@@ -54,7 +54,7 @@ var Tag = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
54
54
|
return () => {
|
|
55
55
|
const text = plainText(children.value);
|
|
56
56
|
const label = removeLabel.value ?? (text ? `${lang.remove} ${text}` : lang.remove);
|
|
57
|
-
return createVNode("span",
|
|
57
|
+
return createVNode("span", mergeJsxProps(mergeAttrs({
|
|
58
58
|
...badgeParts(appearance(), recipe).root,
|
|
59
59
|
sx: [
|
|
60
60
|
badgeParts(appearance(), recipe).root.sx,
|
|
@@ -65,7 +65,7 @@ var Tag = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
65
65
|
}, rest, { ref: dismissible.ref }), { "data-state": dismissible.state }), [
|
|
66
66
|
icon.value && createVNode(Icon, mergePartProps({ icon: icon.value }, propsIcon.value), null),
|
|
67
67
|
children.value,
|
|
68
|
-
onRemove.value && createVNode(CloseButton, mergeProps
|
|
68
|
+
onRemove.value && createVNode(CloseButton, mergeProps({
|
|
69
69
|
sx: tagStyles.remove,
|
|
70
70
|
"aria-label": label,
|
|
71
71
|
radius: "full"
|