@zerodep-dev/vue-tsx-pro-ui 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/base/Badge.js +3 -3
- package/dist/base/Badge.js.map +1 -1
- package/dist/base/Can.d.ts +12 -0
- package/dist/base/Can.js +20 -0
- package/dist/base/Can.js.map +1 -0
- package/dist/base/ClientOnly.d.ts +10 -0
- package/dist/base/ClientOnly.js +18 -0
- package/dist/base/ClientOnly.js.map +1 -0
- package/dist/base/CloseButton.js +3 -3
- package/dist/base/CloseButton.js.map +1 -1
- package/dist/base/CodeBlock.js +5 -5
- package/dist/base/CodeBlock.js.map +1 -1
- package/dist/base/CopyButton.js +3 -3
- package/dist/base/CopyButton.js.map +1 -1
- package/dist/base/Icon.js +3 -3
- package/dist/base/Icon.js.map +1 -1
- package/dist/base/Indicator.js +3 -3
- package/dist/base/Indicator.js.map +1 -1
- package/dist/base/StatusDot.js +3 -3
- package/dist/base/StatusDot.js.map +1 -1
- package/dist/base/Tag.js +5 -5
- package/dist/base/Tag.js.map +1 -1
- package/dist/base/button-core.js +4 -4
- package/dist/base/button-core.js.map +1 -1
- package/dist/charts/AreaChart.js +3 -3
- package/dist/charts/AreaChart.js.map +1 -1
- package/dist/charts/BarChart.js +3 -3
- package/dist/charts/BarChart.js.map +1 -1
- package/dist/charts/ChordChart.js +3 -3
- package/dist/charts/ChordChart.js.map +1 -1
- package/dist/charts/ComboChart.js +3 -3
- package/dist/charts/ComboChart.js.map +1 -1
- package/dist/charts/DonutChart.js +3 -3
- package/dist/charts/DonutChart.js.map +1 -1
- package/dist/charts/FunnelChart.js +5 -5
- package/dist/charts/FunnelChart.js.map +1 -1
- package/dist/charts/GraphChart.js +3 -3
- package/dist/charts/GraphChart.js.map +1 -1
- package/dist/charts/LineChart.js +3 -3
- package/dist/charts/LineChart.js.map +1 -1
- package/dist/charts/MapChart.js +7 -7
- package/dist/charts/MapChart.js.map +1 -1
- package/dist/charts/RadarChart.js +16 -15
- package/dist/charts/RadarChart.js.map +1 -1
- package/dist/charts/SankeyChart.js +3 -3
- package/dist/charts/SankeyChart.js.map +1 -1
- package/dist/charts/ScatterChart.js +3 -3
- package/dist/charts/ScatterChart.js.map +1 -1
- package/dist/charts/TimelineChart.js +6 -5
- package/dist/charts/TimelineChart.js.map +1 -1
- package/dist/charts/TreemapChart.js +3 -3
- package/dist/charts/TreemapChart.js.map +1 -1
- package/dist/charts/WaterfallChart.js +3 -3
- package/dist/charts/WaterfallChart.js.map +1 -1
- package/dist/charts/XYChart.js +9 -7
- package/dist/charts/XYChart.js.map +1 -1
- package/dist/charts/frame.js +10 -8
- package/dist/charts/frame.js.map +1 -1
- package/dist/charts/single.js +4 -3
- package/dist/charts/single.js.map +1 -1
- package/dist/config/Provider.d.ts +3 -1
- package/dist/config/Provider.js +9 -4
- package/dist/config/Provider.js.map +1 -1
- package/dist/config/context.d.ts +18 -0
- package/dist/config/context.js +16 -1
- package/dist/config/context.js.map +1 -1
- package/dist/config/createUi.d.ts +20 -3
- package/dist/config/createUi.js +80 -49
- package/dist/config/createUi.js.map +1 -1
- package/dist/config/layout/preference.d.ts +43 -0
- package/dist/config/layout/preference.js +108 -0
- package/dist/config/layout/preference.js.map +1 -0
- package/dist/date/Calendar.js +3 -3
- package/dist/date/Calendar.js.map +1 -1
- package/dist/date/CalendarBoard.js +9 -9
- package/dist/date/CalendarBoard.js.map +1 -1
- package/dist/date/CalendarView.js +7 -7
- package/dist/date/CalendarView.js.map +1 -1
- package/dist/date/DateField.js +3 -3
- package/dist/date/DateField.js.map +1 -1
- package/dist/date/DatePicker.js +5 -5
- package/dist/date/DatePicker.js.map +1 -1
- package/dist/date/DateRangePicker.js +5 -5
- package/dist/date/DateRangePicker.js.map +1 -1
- package/dist/date/DateSegmentGroup.js +5 -5
- package/dist/date/DateSegmentGroup.js.map +1 -1
- package/dist/date/QuarterCalendar.js +6 -6
- package/dist/date/QuarterCalendar.js.map +1 -1
- package/dist/date/RangeCalendar.js +3 -3
- package/dist/date/RangeCalendar.js.map +1 -1
- package/dist/date/SegmentedField.js +3 -3
- package/dist/date/SegmentedField.js.map +1 -1
- package/dist/date/TimeField.js +3 -3
- package/dist/date/TimeField.js.map +1 -1
- package/dist/date/TimePicker.js +4 -4
- package/dist/date/TimePicker.js.map +1 -1
- package/dist/date/date-segments.js +1 -1
- package/dist/date/date-segments.js.map +1 -1
- package/dist/date/picker-popover.js +3 -2
- package/dist/date/picker-popover.js.map +1 -1
- package/dist/diagram/Diagram.js +12 -12
- package/dist/diagram/Diagram.js.map +1 -1
- package/dist/diagram/DiagramLanes.js +3 -3
- package/dist/diagram/DiagramLanes.js.map +1 -1
- package/dist/diagram/DiagramMinimap.js +4 -4
- package/dist/diagram/DiagramMinimap.js.map +1 -1
- package/dist/display/Accordion.js +6 -6
- package/dist/display/Accordion.js.map +1 -1
- package/dist/display/AnimatedNumber.js +4 -4
- package/dist/display/AnimatedNumber.js.map +1 -1
- package/dist/display/Avatar.js +4 -4
- package/dist/display/Avatar.js.map +1 -1
- package/dist/display/Checklist.js +7 -7
- package/dist/display/Checklist.js.map +1 -1
- package/dist/display/Collapse.js +8 -5
- package/dist/display/Collapse.js.map +1 -1
- package/dist/display/Collapsible.js +3 -3
- package/dist/display/Collapsible.js.map +1 -1
- package/dist/display/Descriptions.js +5 -5
- package/dist/display/Descriptions.js.map +1 -1
- package/dist/display/Ellipsis.js +3 -3
- package/dist/display/Ellipsis.js.map +1 -1
- package/dist/display/FieldCompare.js +4 -4
- package/dist/display/FieldCompare.js.map +1 -1
- package/dist/display/Gauge.js +11 -11
- package/dist/display/Gauge.js.map +1 -1
- package/dist/display/Image.js +4 -4
- package/dist/display/Image.js.map +1 -1
- package/dist/display/Marquee.js +3 -3
- package/dist/display/Marquee.js.map +1 -1
- package/dist/display/ProportionBar.js +4 -4
- package/dist/display/ProportionBar.js.map +1 -1
- package/dist/display/Sortable.js +4 -4
- package/dist/display/Sortable.js.map +1 -1
- package/dist/display/Sparkline.js +6 -6
- package/dist/display/Sparkline.js.map +1 -1
- package/dist/display/Stat.js +3 -3
- package/dist/display/Stat.js.map +1 -1
- package/dist/display/Timeline.js +7 -7
- package/dist/display/Timeline.js.map +1 -1
- package/dist/display/Tree.js +10 -10
- package/dist/display/Tree.js.map +1 -1
- package/dist/display/VirtualList.js +7 -7
- package/dist/display/VirtualList.js.map +1 -1
- package/dist/display/Watermark.js +3 -3
- package/dist/display/Watermark.js.map +1 -1
- package/dist/docs-kit/CodeGroup.js +5 -5
- package/dist/docs-kit/CodeGroup.js.map +1 -1
- package/dist/docs-kit/CodePreview.js +6 -6
- package/dist/docs-kit/CodePreview.js.map +1 -1
- package/dist/docs-kit/PropsTable.js +4 -4
- package/dist/docs-kit/PropsTable.js.map +1 -1
- package/dist/feedback/Alert.js +5 -5
- package/dist/feedback/Alert.js.map +1 -1
- package/dist/feedback/Banner.js +5 -5
- package/dist/feedback/Banner.js.map +1 -1
- package/dist/feedback/Empty.js +4 -4
- package/dist/feedback/Empty.js.map +1 -1
- package/dist/feedback/LoadingBar.js +20 -13
- package/dist/feedback/LoadingBar.js.map +1 -1
- package/dist/feedback/LoadingOverlay.js +3 -3
- package/dist/feedback/LoadingOverlay.js.map +1 -1
- package/dist/feedback/Progress.js +14 -14
- package/dist/feedback/Progress.js.map +1 -1
- package/dist/feedback/Result.js +4 -4
- package/dist/feedback/Result.js.map +1 -1
- package/dist/feedback/SkeletonText.js +3 -3
- package/dist/feedback/SkeletonText.js.map +1 -1
- package/dist/feedback/Toast.js +6 -6
- package/dist/feedback/Toast.js.map +1 -1
- package/dist/form/Field.js +7 -7
- package/dist/form/Field.js.map +1 -1
- package/dist/form/Form.js +3 -3
- package/dist/form/Form.js.map +1 -1
- package/dist/form/FormErrorSummary.js +3 -3
- package/dist/form/FormErrorSummary.js.map +1 -1
- package/dist/form/FormSection.js +6 -6
- package/dist/form/FormSection.js.map +1 -1
- package/dist/index.d.ts +11 -6
- package/dist/index.js +10 -5
- package/dist/input/Autocomplete.js +3 -3
- package/dist/input/Autocomplete.js.map +1 -1
- package/dist/input/Cascader.js +7 -7
- package/dist/input/Cascader.js.map +1 -1
- package/dist/input/CharacterCount.js +3 -3
- package/dist/input/CharacterCount.js.map +1 -1
- package/dist/input/Checkbox.js +5 -5
- package/dist/input/Checkbox.js.map +1 -1
- package/dist/input/ColorField.js +4 -4
- package/dist/input/ColorField.js.map +1 -1
- package/dist/input/Combobox.js +4 -4
- package/dist/input/Combobox.js.map +1 -1
- package/dist/input/ConditionBuilder.js +15 -14
- package/dist/input/ConditionBuilder.js.map +1 -1
- package/dist/input/FileDrop.js +3 -3
- package/dist/input/FileDrop.js.map +1 -1
- package/dist/input/Listbox.js +9 -9
- package/dist/input/Listbox.js.map +1 -1
- package/dist/input/Mentions.js +3 -3
- package/dist/input/Mentions.js.map +1 -1
- package/dist/input/NumberField.js +3 -3
- package/dist/input/NumberField.js.map +1 -1
- package/dist/input/NumberRangeField.js +5 -5
- package/dist/input/NumberRangeField.js.map +1 -1
- package/dist/input/OtpField.js +5 -5
- package/dist/input/OtpField.js.map +1 -1
- package/dist/input/PeoplePicker.js +6 -6
- package/dist/input/PeoplePicker.js.map +1 -1
- package/dist/input/PeoplePickerPanel.js +4 -4
- package/dist/input/PeoplePickerPanel.js.map +1 -1
- package/dist/input/Radio.js +4 -4
- package/dist/input/Radio.js.map +1 -1
- package/dist/input/Rating.js +5 -5
- package/dist/input/Rating.js.map +1 -1
- package/dist/input/Select.js +4 -4
- package/dist/input/Select.js.map +1 -1
- package/dist/input/SelectedTags.js +4 -4
- package/dist/input/SelectedTags.js.map +1 -1
- package/dist/input/Slider.js +6 -6
- package/dist/input/Slider.js.map +1 -1
- package/dist/input/Switch.js +3 -3
- package/dist/input/Switch.js.map +1 -1
- package/dist/input/TagsInput.js +5 -5
- package/dist/input/TagsInput.js.map +1 -1
- package/dist/input/TextField.js +5 -5
- package/dist/input/TextField.js.map +1 -1
- package/dist/input/Textarea.js +4 -4
- package/dist/input/Textarea.js.map +1 -1
- package/dist/input/ToggleGroup.js +4 -4
- package/dist/input/ToggleGroup.js.map +1 -1
- package/dist/input/Transfer.js +8 -8
- package/dist/input/Transfer.js.map +1 -1
- package/dist/input/TreeSelect.js +6 -6
- package/dist/input/TreeSelect.js.map +1 -1
- package/dist/input/Upload.js +8 -8
- package/dist/input/Upload.js.map +1 -1
- package/dist/layout/Affix.js +4 -4
- package/dist/layout/Affix.js.map +1 -1
- package/dist/layout/ResizeHandle.d.ts +6 -2
- package/dist/layout/ResizeHandle.js +32 -9
- package/dist/layout/ResizeHandle.js.map +1 -1
- package/dist/layout/ScrollArea.js +3 -3
- package/dist/layout/ScrollArea.js.map +1 -1
- package/dist/layout/Separator.js +3 -3
- package/dist/layout/Separator.js.map +1 -1
- package/dist/layout/Splitter.js +3 -3
- package/dist/layout/Splitter.js.map +1 -1
- package/dist/layout/ViewportFill.d.ts +2 -2
- package/dist/layout/ViewportFill.js +4 -4
- package/dist/layout/ViewportFill.js.map +1 -1
- package/dist/navigation/Breadcrumbs.js +4 -4
- package/dist/navigation/Breadcrumbs.js.map +1 -1
- package/dist/navigation/NavBar.js +6 -6
- package/dist/navigation/NavBar.js.map +1 -1
- package/dist/navigation/NavList.d.ts +1 -0
- package/dist/navigation/NavList.js +20 -12
- package/dist/navigation/NavList.js.map +1 -1
- package/dist/navigation/NavMenu.d.ts +27 -0
- package/dist/navigation/NavMenu.js +54 -0
- package/dist/navigation/NavMenu.js.map +1 -0
- package/dist/navigation/PageJump.js +3 -3
- package/dist/navigation/PageJump.js.map +1 -1
- package/dist/navigation/PageSizeSelect.js +4 -4
- package/dist/navigation/PageSizeSelect.js.map +1 -1
- package/dist/navigation/Pagination.js +6 -6
- package/dist/navigation/Pagination.js.map +1 -1
- package/dist/navigation/PaginationBar.js +5 -5
- package/dist/navigation/PaginationBar.js.map +1 -1
- package/dist/navigation/Steps.js +4 -4
- package/dist/navigation/Steps.js.map +1 -1
- package/dist/navigation/TableOfContents.js +5 -5
- package/dist/navigation/TableOfContents.js.map +1 -1
- package/dist/navigation/Tabs.js +7 -7
- package/dist/navigation/Tabs.js.map +1 -1
- package/dist/navigation/Toolbar.js +3 -3
- package/dist/navigation/Toolbar.js.map +1 -1
- package/dist/overlay/AlertDialog.js +4 -4
- package/dist/overlay/AlertDialog.js.map +1 -1
- package/dist/overlay/CommandPalette.js +4 -4
- package/dist/overlay/CommandPalette.js.map +1 -1
- package/dist/overlay/ContextMenu.js +3 -3
- package/dist/overlay/ContextMenu.js.map +1 -1
- package/dist/overlay/Dialog.js +4 -4
- package/dist/overlay/Dialog.js.map +1 -1
- package/dist/overlay/Drawer.js +5 -5
- package/dist/overlay/Drawer.js.map +1 -1
- package/dist/overlay/HoverCard.js +5 -3
- package/dist/overlay/HoverCard.js.map +1 -1
- package/dist/overlay/Lightbox.js +5 -5
- package/dist/overlay/Lightbox.js.map +1 -1
- package/dist/overlay/Menu.js +7 -7
- package/dist/overlay/Menu.js.map +1 -1
- package/dist/overlay/ModalCore.js +9 -9
- package/dist/overlay/ModalCore.js.map +1 -1
- package/dist/overlay/Popconfirm.js +6 -6
- package/dist/overlay/Popconfirm.js.map +1 -1
- package/dist/overlay/Popover.js +5 -3
- package/dist/overlay/Popover.js.map +1 -1
- package/dist/overlay/Tooltip.js +5 -3
- package/dist/overlay/Tooltip.js.map +1 -1
- package/dist/overlay/Tour.js +8 -8
- package/dist/overlay/Tour.js.map +1 -1
- package/dist/overlay/detached.d.ts +2 -0
- package/dist/overlay/detached.js +73 -42
- package/dist/overlay/detached.js.map +1 -1
- package/dist/patterns/ActionBar.js +3 -3
- package/dist/patterns/ActionBar.js.map +1 -1
- package/dist/patterns/AppShell.d.ts +28 -1
- package/dist/patterns/AppShell.js +158 -52
- package/dist/patterns/AppShell.js.map +1 -1
- package/dist/patterns/AuthLayout.d.ts +28 -0
- package/dist/patterns/AuthLayout.js +77 -0
- package/dist/patterns/AuthLayout.js.map +1 -0
- package/dist/patterns/CommentThread.js +9 -9
- package/dist/patterns/CommentThread.js.map +1 -1
- package/dist/patterns/DataImport.js +4 -4
- package/dist/patterns/DataImport.js.map +1 -1
- package/dist/patterns/DeadlineBadge.js +3 -3
- package/dist/patterns/DeadlineBadge.js.map +1 -1
- package/dist/patterns/FilterBar.d.ts +11 -1
- package/dist/patterns/FilterBar.js +39 -10
- package/dist/patterns/FilterBar.js.map +1 -1
- package/dist/patterns/FormDialog.d.ts +18 -0
- package/dist/patterns/FormDialog.js +46 -0
- package/dist/patterns/FormDialog.js.map +1 -0
- package/dist/patterns/KanbanBoard.js +8 -8
- package/dist/patterns/KanbanBoard.js.map +1 -1
- package/dist/patterns/LayoutSwitcher.d.ts +12 -0
- package/dist/patterns/LayoutSwitcher.js +79 -0
- package/dist/patterns/LayoutSwitcher.js.map +1 -0
- package/dist/patterns/NotificationCenter.js +8 -8
- package/dist/patterns/NotificationCenter.js.map +1 -1
- package/dist/patterns/PageTabs.d.ts +12 -7
- package/dist/patterns/PageTabs.js +41 -29
- package/dist/patterns/PageTabs.js.map +1 -1
- package/dist/patterns/QueryTable.d.ts +7 -1
- package/dist/patterns/QueryTable.js +116 -81
- package/dist/patterns/QueryTable.js.map +1 -1
- package/dist/patterns/SlideCaptcha.d.ts +80 -0
- package/dist/patterns/SlideCaptcha.js +354 -0
- package/dist/patterns/SlideCaptcha.js.map +1 -0
- package/dist/patterns/StepsForm.js +7 -7
- package/dist/patterns/StepsForm.js.map +1 -1
- package/dist/patterns/ThemeSwitcher.js +3 -3
- package/dist/patterns/ThemeSwitcher.js.map +1 -1
- package/dist/patterns/index.d.ts +7 -3
- package/dist/patterns/index.js +5 -1
- package/dist/patterns/messages.d.ts +37 -1
- package/dist/patterns/messages.js +38 -2
- package/dist/patterns/messages.js.map +1 -1
- package/dist/table/ColumnHeaderActions.js +3 -3
- package/dist/table/ColumnHeaderActions.js.map +1 -1
- package/dist/table/ColumnResizer.js +3 -3
- package/dist/table/ColumnResizer.js.map +1 -1
- package/dist/table/ColumnSettings.js +5 -5
- package/dist/table/ColumnSettings.js.map +1 -1
- package/dist/table/ColumnTable.js +8 -8
- package/dist/table/ColumnTable.js.map +1 -1
- package/dist/table/DataTable.js +8 -8
- package/dist/table/DataTable.js.map +1 -1
- package/dist/table/DataTableView.js +21 -21
- package/dist/table/DataTableView.js.map +1 -1
- package/dist/table/PivotTable.js +3 -3
- package/dist/table/PivotTable.js.map +1 -1
- package/dist/table/SelectionBar.js +4 -4
- package/dist/table/SelectionBar.js.map +1 -1
- package/dist/table/TreeDataTable.js +5 -5
- package/dist/table/TreeDataTable.js.map +1 -1
- package/dist/theme-customize/ThemeCustomizer.js +7 -6
- package/dist/theme-customize/ThemeCustomizer.js.map +1 -1
- package/dist/theme-customize/ThemeThumbnail.js +3 -3
- package/dist/theme-customize/ThemeThumbnail.js.map +1 -1
- package/dist/three/background/AuroraBackground.js +3 -3
- package/dist/three/background/AuroraBackground.js.map +1 -1
- package/dist/three/background/MeshGradient.js +3 -3
- package/dist/three/background/MeshGradient.js.map +1 -1
- package/dist/three/background/ShaderBackground.js +3 -3
- package/dist/three/background/ShaderBackground.js.map +1 -1
- package/dist/three/core/EffectSurface.js +3 -3
- package/dist/three/core/EffectSurface.js.map +1 -1
- package/dist/three/core/ThreeCanvas.js +4 -4
- package/dist/three/core/ThreeCanvas.js.map +1 -1
- package/dist/three/effects/Confetti.js +3 -3
- package/dist/three/effects/Confetti.js.map +1 -1
- package/dist/three/effects/ParticleField.js +3 -3
- package/dist/three/effects/ParticleField.js.map +1 -1
- package/dist/three/effects/WaveGrid.js +3 -3
- package/dist/three/effects/WaveGrid.js.map +1 -1
- package/dist/three/globe/Globe.js +3 -3
- package/dist/three/globe/Globe.js.map +1 -1
- package/dist/three/model/ModelViewer.js +3 -3
- package/dist/three/model/ModelViewer.js.map +1 -1
- package/dist/utils/data/number.d.ts +5 -0
- package/dist/utils/data/number.js +8 -1
- package/dist/utils/data/number.js.map +1 -1
- package/dist/utils/dom/element.d.ts +3 -1
- package/dist/utils/dom/element.js +8 -4
- package/dist/utils/dom/element.js.map +1 -1
- package/dist/utils/dom/fullscreen.d.ts +1 -1
- package/dist/utils/dom/fullscreen.js +12 -8
- package/dist/utils/dom/fullscreen.js.map +1 -1
- package/dist/utils/dom/mounted.d.ts +7 -0
- package/dist/utils/dom/mounted.js +16 -0
- package/dist/utils/dom/mounted.js.map +1 -0
- package/dist/utils/dom/overflow.js +2 -1
- package/dist/utils/dom/overflow.js.map +1 -1
- package/dist/utils/dom/pan-zoom.js +2 -1
- package/dist/utils/dom/pan-zoom.js.map +1 -1
- package/dist/utils/props.js +3 -13
- package/dist/utils/props.js.map +1 -1
- package/dist/utils/request/http.d.ts +12 -0
- package/dist/utils/request/http.js +6 -2
- package/dist/utils/request/http.js.map +1 -1
- package/dist/utils/request/use-request.js +4 -2
- package/dist/utils/request/use-request.js.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Accordion.js","names":["_isVNode","_vtp_readonlyProp","_vtp_bindable","_vtp_childrenProp","_vtp_restProps","_vue_shallowRef","_vue_computed","_createVNode","_mergeProps","_vtp_forwardChildren"],"sources":["../../src/display/Accordion.tsx"],"sourcesContent":["/**\n * 手风琴,复合组件:Accordion(div)> AccordionItem(标题按钮 + 展开收起的内容)。WAI-ARIA 的 Accordion 模式:\n * 每个标题是 heading 里的 button(aria-expanded、aria-controls),内容是 role=\"region\"、以标题为名称;\n * 标题都在 Tab 顺序里,另外 ↑ / ↓ 在标题之间移动(到头后循环),Home / End 到第一个、最后一个。参考 Radix Accordion。\n *\n * <Accordion _value={open} aria-label=\"常见问题\">\n * <AccordionItem value=\"ship\" title=\"多久发货?\">付款后 48 小时内。</AccordionItem>\n * <AccordionItem value=\"refund\" title=\"怎么退款?\">…</AccordionItem>\n * </Accordion>\n * <Accordion selectionMode=\"multiple\" _value={openList}>…</Accordion>\n *\n * 展开收起的动画同 Collapsible(Collapse 的 CSS 过渡)。部件见 AccordionItemProps。\n */\nimport { ChevronDown } from '@lucide/icons';\nimport { createContext, _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport { h, useId, type VNodeChild } from 'vue';\nimport { ButtonBase, type ButtonBaseProps } from '../base/ButtonBase';\nimport { Icon, type IconProps } from '../base/Icon';\nimport { selectedValues, toggleSelection, type SelectionProps } from '../utils/data/selection';\nimport { mergeAttrs, mergePartProps, type PartProps } from '../utils/props';\nimport { disclosureStyles } from '../utils/style/transitions';\nimport { fontSizeStyles, type TextSize } from '../utils/style/typography';\nimport { Collapse, type CollapseProps } from './Collapse';\n\n/** Accordion 交给 AccordionItem 的状态 */\nexport interface AccordionState {\n /** 展开着的项 */\n readonly expanded: readonly string[];\n /** 标题的层级(h2–h6) */\n readonly headingLevel: 2 | 3 | 4 | 5 | 6;\n /** 整组不可用 */\n readonly disabled: boolean;\n /** 展开或收起一项(单开 / 多开的规则由组决定) */\n toggle(value: string): void;\n}\n\n/** Accordion 的上下文:自定义的项可以 inject 它接入组 */\nexport const AccordionContext = createContext<AccordionState>('Accordion');\n\n/** 两种方式共有的属性;未声明的属性落到外层 div 上 */\ninterface AccordionBaseProps extends PartProps<'div'> {\n /** 单开时允许把唯一展开的那项也收起,默认 true */\n collapsible?: boolean;\n /** 标题的层级,按所在页面的大纲选,默认 3 */\n headingLevel?: 2 | 3 | 4 | 5 | 6;\n /** 整组不可用 */\n disabled?: boolean;\n /** 字号档位;不写时继承 */\n size?: TextSize;\n /** AccordionItem */\n children?: VNodeChild;\n}\n\n/** 单开(selectionMode=\"single\",value 是展开项的值)或多开(value 是数组) */\nexport type AccordionProps = AccordionBaseProps & SelectionProps;\n\n/** Accordion 的样式:root、item、heading、trigger、chevron、chevronOpen、content */\nexport const accordionStyles = css.create({\n root: { display: 'flex', flexDirection: 'column' },\n item: {\n borderBottomWidth: '_xs',\n borderBottomStyle: 'solid',\n borderBottomColor: '_borderMuted',\n },\n heading: { margin: 0, fontSize: 'inherit', fontWeight: 'inherit' },\n // 去掉按钮默认外观、焦点环、波纹、颜色过渡由 ButtonBase 给出\n trigger: {\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n gap: '_md',\n width: '_full',\n minHeight: '_minTarget',\n paddingBlock: '_md',\n paddingInline: '_sm',\n borderRadius: '_sm',\n backgroundColor: { default: 'transparent', ':hover': '_fill' },\n color: '_text',\n fontWeight: '_semibold',\n textAlign: 'start',\n },\n triggerDisabled: { opacity: '_disabled' },\n chevron: { flexShrink: 0, color: '_textMuted' },\n chevronOpen: { rotate: '180deg' },\n content: { paddingInline: '_sm', paddingBlockEnd: '_md', color: '_text' },\n});\n\n/** 手风琴:一组可展开收起的项,单开或多开 */\nexport const Accordion = _component(\n ({\n selectionMode = 'single',\n _value: value = _bindable<string | string[] | null>(null),\n collapsible = true,\n headingLevel = 3,\n disabled = false,\n size,\n children,\n ...rest\n }: AccordionProps) => {\n let root = _state.raw<HTMLElement | null>(null);\n const expanded = _derived(selectedValues(value));\n\n AccordionContext.provide({\n get expanded() {\n return expanded;\n },\n get headingLevel() {\n return headingLevel;\n },\n get disabled() {\n return disabled;\n },\n toggle(item) {\n const next = toggleSelection(expanded, item, {\n mode: selectionMode,\n required: !collapsible,\n });\n if (next === expanded) return;\n value = selectionMode === 'multiple' ? [...next] : (next[0] ?? null);\n },\n });\n\n // ↑ / ↓ 在标题之间移动(循环),Home / End 到两端;只在焦点在标题按钮上时处理\n function onKeydown(event: KeyboardEvent) {\n const target = event.target as HTMLElement;\n if (!root || !target.matches('[data-accordion-trigger]')) return;\n const triggers = [\n ...root.querySelectorAll<HTMLButtonElement>('[data-accordion-trigger]:not(:disabled)'),\n ].filter((trigger) => trigger.closest('[data-accordion]') === root);\n const index = triggers.indexOf(target as HTMLButtonElement);\n const next = {\n ArrowDown: triggers[(index + 1) % triggers.length],\n ArrowUp: triggers[(index - 1 + triggers.length) % triggers.length],\n Home: triggers[0],\n End: triggers.at(-1),\n }[event.key];\n if (!next) return;\n event.preventDefault();\n next.focus();\n }\n\n return () => (\n <div\n {...mergeAttrs<'div'>({ sx: [accordionStyles.root, size && fontSizeStyles[size]] }, rest, {\n ref: _toRef(root),\n onKeydown,\n })}\n data-accordion=\"\"\n data-disabled={disabled || undefined}\n >\n {children}\n </div>\n );\n },\n);\n\n/** 未声明的属性落到每一项的外层 div 上 */\nexport interface AccordionItemProps extends PartProps<'div', 'title'> {\n /** 这一项的值 */\n value: string;\n /** 标题 */\n title: VNodeChild;\n /** 不可用:标题按钮 disabled,不能展开收起 */\n disabled?: boolean;\n /** 标题(h2–h6)的部件属性 */\n propsHeading?: PartProps<'h3'>;\n /** 标题按钮的部件属性 */\n propsTrigger?: Omit<Partial<ButtonBaseProps>, 'aria-expanded' | 'aria-controls'>;\n /** 展开箭头的部件属性 */\n propsChevron?: Partial<IconProps>;\n /** 内容区(Collapse,role=\"region\")的部件属性 */\n propsContent?: Partial<CollapseProps>;\n /** 内容 */\n children?: VNodeChild;\n}\n\n/** 手风琴里的一项:标题按钮 + 展开收起的内容 */\nexport const AccordionItem = _component(\n ({\n value,\n title,\n disabled = false,\n propsHeading,\n propsTrigger,\n propsChevron,\n propsContent,\n children,\n ...rest\n }: AccordionItemProps) => {\n const accordion = AccordionContext.inject();\n const id = useId();\n const triggerId = `${id}-trigger`;\n const contentId = `${id}-content`;\n // Collapse 在页内搜索命中(beforematch)时会写回 open:经这个对象交给组切换\n const model = {\n get open() {\n return accordion.expanded.includes(value);\n },\n set open(next: boolean) {\n if (next !== model.open) accordion.toggle(value);\n },\n };\n\n return () => {\n const open = model.open;\n const off = disabled || accordion.disabled;\n const state = open ? 'open' : 'closed';\n const trigger = (\n <ButtonBase\n {...mergePartProps<ButtonBaseProps>(\n { sx: [accordionStyles.trigger, off && accordionStyles.triggerDisabled] },\n propsTrigger,\n {\n id: triggerId,\n disabled: off,\n 'aria-expanded': open,\n 'aria-controls': contentId,\n onClick: () => accordion.toggle(value),\n },\n )}\n data-accordion-trigger=\"\"\n data-state={state}\n >\n <span>{title}</span>\n <Icon\n {...mergePartProps<IconProps>(\n {\n icon: ChevronDown,\n sx: [\n accordionStyles.chevron,\n disclosureStyles.icon,\n open && accordionStyles.chevronOpen,\n ],\n },\n propsChevron,\n )}\n />\n </ButtonBase>\n );\n return (\n <div\n {...mergeAttrs<'div'>({ sx: accordionStyles.item }, rest)}\n data-state={state}\n data-disabled={off || undefined}\n >\n {/* 标签名随 headingLevel,用 h() 渲染 */}\n {h(\n `h${accordion.headingLevel}`,\n mergeAttrs<'h3'>({ sx: accordionStyles.heading }, propsHeading),\n [trigger],\n )}\n <Collapse\n {...mergePartProps<CollapseProps>(\n { propsContent: { sx: accordionStyles.content } },\n propsContent,\n { id: contentId, role: 'region', 'aria-labelledby': triggerId },\n )}\n _open={model.open}\n >\n {children}\n </Collapse>\n </div>\n );\n };\n },\n);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Accordion.js","names":["_isVNode","_vtp_readonlyProp","_vtp_bindable","_vtp_childrenProp","_vtp_restProps","_vue_shallowRef","_vue_computed","_createVNode","_mergeProps","_vtp_forwardChildren"],"sources":["../../src/display/Accordion.tsx"],"sourcesContent":["/**\n * 手风琴,复合组件:Accordion(div)> AccordionItem(标题按钮 + 展开收起的内容)。WAI-ARIA 的 Accordion 模式:\n * 每个标题是 heading 里的 button(aria-expanded、aria-controls),内容是 role=\"region\"、以标题为名称;\n * 标题都在 Tab 顺序里,另外 ↑ / ↓ 在标题之间移动(到头后循环),Home / End 到第一个、最后一个。参考 Radix Accordion。\n *\n * <Accordion _value={open} aria-label=\"常见问题\">\n * <AccordionItem value=\"ship\" title=\"多久发货?\">付款后 48 小时内。</AccordionItem>\n * <AccordionItem value=\"refund\" title=\"怎么退款?\">…</AccordionItem>\n * </Accordion>\n * <Accordion selectionMode=\"multiple\" _value={openList}>…</Accordion>\n *\n * 展开收起的动画同 Collapsible(Collapse 的 CSS 过渡)。部件见 AccordionItemProps。\n */\nimport { ChevronDown } from '@lucide/icons';\nimport { createContext, _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport { h, useId, type VNodeChild } from 'vue';\nimport { ButtonBase, type ButtonBaseProps } from '../base/ButtonBase';\nimport { Icon, type IconProps } from '../base/Icon';\nimport { selectedValues, toggleSelection, type SelectionProps } from '../utils/data/selection';\nimport { mergeAttrs, mergePartProps, type PartProps } from '../utils/props';\nimport { disclosureStyles } from '../utils/style/transitions';\nimport { fontSizeStyles, type TextSize } from '../utils/style/typography';\nimport { Collapse, type CollapseProps } from './Collapse';\n\n/** Accordion 交给 AccordionItem 的状态 */\nexport interface AccordionState {\n /** 展开着的项 */\n readonly expanded: readonly string[];\n /** 标题的层级(h2–h6) */\n readonly headingLevel: 2 | 3 | 4 | 5 | 6;\n /** 整组不可用 */\n readonly disabled: boolean;\n /** 展开或收起一项(单开 / 多开的规则由组决定) */\n toggle(value: string): void;\n}\n\n/** Accordion 的上下文:自定义的项可以 inject 它接入组 */\nexport const AccordionContext = createContext<AccordionState>('Accordion');\n\n/** 两种方式共有的属性;未声明的属性落到外层 div 上 */\ninterface AccordionBaseProps extends PartProps<'div'> {\n /** 单开时允许把唯一展开的那项也收起,默认 true */\n collapsible?: boolean;\n /** 标题的层级,按所在页面的大纲选,默认 3 */\n headingLevel?: 2 | 3 | 4 | 5 | 6;\n /** 整组不可用 */\n disabled?: boolean;\n /** 字号档位;不写时继承 */\n size?: TextSize;\n /** AccordionItem */\n children?: VNodeChild;\n}\n\n/** 单开(selectionMode=\"single\",value 是展开项的值)或多开(value 是数组) */\nexport type AccordionProps = AccordionBaseProps & SelectionProps;\n\n/** Accordion 的样式:root、item、heading、trigger、chevron、chevronOpen、content */\nexport const accordionStyles = css.create({\n root: { display: 'flex', flexDirection: 'column' },\n item: {\n borderBottomWidth: '_xs',\n borderBottomStyle: 'solid',\n borderBottomColor: '_borderMuted',\n },\n heading: { margin: 0, fontSize: 'inherit', fontWeight: 'inherit' },\n // 去掉按钮默认外观、焦点环、波纹、颜色过渡由 ButtonBase 给出\n trigger: {\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n gap: '_md',\n width: '_full',\n minHeight: '_minTarget',\n paddingBlock: '_md',\n paddingInline: '_sm',\n borderRadius: '_sm',\n backgroundColor: { default: 'transparent', ':hover': '_fill' },\n color: '_text',\n fontWeight: '_semibold',\n textAlign: 'start',\n },\n triggerDisabled: { opacity: '_disabled' },\n chevron: { flexShrink: 0, color: '_textMuted' },\n chevronOpen: { rotate: '180deg' },\n content: { paddingInline: '_sm', paddingBlockEnd: '_md', color: '_text' },\n});\n\n/** 手风琴:一组可展开收起的项,单开或多开 */\nexport const Accordion = _component(\n ({\n selectionMode = 'single',\n _value: value = _bindable<string | string[] | null>(null),\n collapsible = true,\n headingLevel = 3,\n disabled = false,\n size,\n children,\n ...rest\n }: AccordionProps) => {\n let root = _state.raw<HTMLElement | null>(null);\n const expanded = _derived(selectedValues(value));\n\n AccordionContext.provide({\n get expanded() {\n return expanded;\n },\n get headingLevel() {\n return headingLevel;\n },\n get disabled() {\n return disabled;\n },\n toggle(item) {\n const next = toggleSelection(expanded, item, {\n mode: selectionMode,\n required: !collapsible,\n });\n if (next === expanded) return;\n value = selectionMode === 'multiple' ? [...next] : (next[0] ?? null);\n },\n });\n\n // ↑ / ↓ 在标题之间移动(循环),Home / End 到两端;只在焦点在标题按钮上时处理\n function onKeydown(event: KeyboardEvent) {\n const target = event.target as HTMLElement;\n if (!root || !target.matches('[data-accordion-trigger]')) return;\n const triggers = [\n ...root.querySelectorAll<HTMLButtonElement>('[data-accordion-trigger]:not(:disabled)'),\n ].filter((trigger) => trigger.closest('[data-accordion]') === root);\n const index = triggers.indexOf(target as HTMLButtonElement);\n const next = {\n ArrowDown: triggers[(index + 1) % triggers.length],\n ArrowUp: triggers[(index - 1 + triggers.length) % triggers.length],\n Home: triggers[0],\n End: triggers.at(-1),\n }[event.key];\n if (!next) return;\n event.preventDefault();\n next.focus();\n }\n\n return () => (\n <div\n {...mergeAttrs<'div'>({ sx: [accordionStyles.root, size && fontSizeStyles[size]] }, rest, {\n ref: _toRef(root),\n onKeydown,\n })}\n data-accordion=\"\"\n data-disabled={disabled || undefined}\n >\n {children}\n </div>\n );\n },\n);\n\n/** 未声明的属性落到每一项的外层 div 上 */\nexport interface AccordionItemProps extends PartProps<'div', 'title'> {\n /** 这一项的值 */\n value: string;\n /** 标题 */\n title: VNodeChild;\n /** 不可用:标题按钮 disabled,不能展开收起 */\n disabled?: boolean;\n /** 标题(h2–h6)的部件属性 */\n propsHeading?: PartProps<'h3'>;\n /** 标题按钮的部件属性 */\n propsTrigger?: Omit<Partial<ButtonBaseProps>, 'aria-expanded' | 'aria-controls'>;\n /** 展开箭头的部件属性 */\n propsChevron?: Partial<IconProps>;\n /** 内容区(Collapse,role=\"region\")的部件属性 */\n propsContent?: Partial<CollapseProps>;\n /** 内容 */\n children?: VNodeChild;\n}\n\n/** 手风琴里的一项:标题按钮 + 展开收起的内容 */\nexport const AccordionItem = _component(\n ({\n value,\n title,\n disabled = false,\n propsHeading,\n propsTrigger,\n propsChevron,\n propsContent,\n children,\n ...rest\n }: AccordionItemProps) => {\n const accordion = AccordionContext.inject();\n const id = useId();\n const triggerId = `${id}-trigger`;\n const contentId = `${id}-content`;\n // Collapse 在页内搜索命中(beforematch)时会写回 open:经这个对象交给组切换\n const model = {\n get open() {\n return accordion.expanded.includes(value);\n },\n set open(next: boolean) {\n if (next !== model.open) accordion.toggle(value);\n },\n };\n\n return () => {\n const open = model.open;\n const off = disabled || accordion.disabled;\n const state = open ? 'open' : 'closed';\n const trigger = (\n <ButtonBase\n {...mergePartProps<ButtonBaseProps>(\n { sx: [accordionStyles.trigger, off && accordionStyles.triggerDisabled] },\n propsTrigger,\n {\n id: triggerId,\n disabled: off,\n 'aria-expanded': open,\n 'aria-controls': contentId,\n onClick: () => accordion.toggle(value),\n },\n )}\n data-accordion-trigger=\"\"\n data-state={state}\n >\n <span>{title}</span>\n <Icon\n {...mergePartProps<IconProps>(\n {\n icon: ChevronDown,\n sx: [\n accordionStyles.chevron,\n disclosureStyles.icon,\n open && accordionStyles.chevronOpen,\n ],\n },\n propsChevron,\n )}\n />\n </ButtonBase>\n );\n return (\n <div\n {...mergeAttrs<'div'>({ sx: accordionStyles.item }, rest)}\n data-state={state}\n data-disabled={off || undefined}\n >\n {/* 标签名随 headingLevel,用 h() 渲染 */}\n {h(\n `h${accordion.headingLevel}`,\n mergeAttrs<'h3'>({ sx: accordionStyles.heading }, propsHeading),\n [trigger],\n )}\n <Collapse\n {...mergePartProps<CollapseProps>(\n { propsContent: { sx: accordionStyles.content } },\n propsContent,\n { id: contentId, role: 'region', 'aria-labelledby': triggerId },\n )}\n _open={model.open}\n >\n {children}\n </Collapse>\n </div>\n );\n };\n },\n);\n"],"mappings":"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"}
|
|
@@ -4,8 +4,8 @@ import { toneStyles } from "../utils/style/typography.js";
|
|
|
4
4
|
import { VisuallyHidden } from "../base/VisuallyHidden.js";
|
|
5
5
|
import { useFormat } from "../config/lang/format.js";
|
|
6
6
|
import { useTween } from "../utils/motion/tween.js";
|
|
7
|
-
import { createVNode,
|
|
8
|
-
import { _component, effect, readonlyProp, restProps, untrack } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
7
|
+
import { createVNode, shallowRef } from "vue";
|
|
8
|
+
import { _component, effect, mergeJsxProps, readonlyProp, restProps, untrack } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
9
9
|
import * as css from "@zerodep-dev/vue-tsx-pro-tsx/styles";
|
|
10
10
|
//#region src/display/AnimatedNumber.tsx
|
|
11
11
|
/** 未声明的属性落到外层 span 上;原生的 prefix 属性(RDFa)让给前缀内容 */
|
|
@@ -61,13 +61,13 @@ var AnimatedNumber = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
61
61
|
down: "danger",
|
|
62
62
|
flat: tone.value
|
|
63
63
|
}[direction] : tone.value;
|
|
64
|
-
return createVNode("span",
|
|
64
|
+
return createVNode("span", mergeJsxProps(mergeAttrs({ sx: [animatedNumberStyles.root, color && toneStyles[color]] }, rest, { ref: (element) => root = element }), { "data-trend": trend.value ? direction : void 0 }), [
|
|
65
65
|
hasContent(prefix.value) && createVNode("span", mergeAttrs({}, propsPrefix.value), [prefix.value]),
|
|
66
66
|
createVNode("span", mergeAttrs({}, propsValue.value, {
|
|
67
67
|
"aria-hidden": true,
|
|
68
68
|
ref: valueElement
|
|
69
69
|
}), [untrack(() => shownText())]),
|
|
70
|
-
createVNode(VisuallyHidden,
|
|
70
|
+
createVNode(VisuallyHidden, mergeJsxProps(mergePartProps({}, propsLabel.value), { "key": "__site0" }), {
|
|
71
71
|
default: () => [text(value.value)],
|
|
72
72
|
$stable: true
|
|
73
73
|
}),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AnimatedNumber.js","names":["_vtp_readonlyProp","_vtp_restProps","_vtp_untrack","_vue_shallowRef","_createVNode","_mergeProps"],"sources":["../../src/display/AnimatedNumber.tsx"],"sourcesContent":["/**\n * 会滚动的数字(span):值变化时从旧值过渡到新值(useTween),按当前语言格式化,等宽数字不抖动。\n * 统计卡片的数值、金额、百分比、计数器。\n *\n * <AnimatedNumber value={revenue} format={{ style: 'currency' }} />\n * <AnimatedNumber value={0.123} format={{ style: 'percent', maximumFractionDigits: 1 }} trend prefix=\"较上周 \" />\n *\n * 读屏只读终值:滚动中的数字对读屏隐藏,旁边有一份隐藏的终值文字。需要播报变化时在外层加 aria-live。\n * 值可以是 number、bigint 或 Decimal:过渡的中间帧按浮点数算,停下时与读屏的终值按原值格式化,不丢精度。\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 { VisuallyHidden, type VisuallyHiddenProps } from '../base/VisuallyHidden';\nimport { useFormat, type NumericFormat } from '../config/lang/format';\nimport { compareNumeric, numericText, toNumber, type NumericValue } from '../utils/data/numeric';\nimport { useTween, type TweenOptions } from '../utils/motion/tween';\nimport { hasContent, mergeAttrs, mergePartProps, type PartProps } from '../utils/props';\nimport { toneStyles, type Tone } from '../utils/style/typography';\n\n/** 未声明的属性落到外层 span 上;原生的 prefix 属性(RDFa)让给前缀内容 */\nexport interface AnimatedNumberProps extends PartProps<'span', 'prefix'> {\n /** 数值:number、bigint 或 Decimal */\n value: NumericValue;\n /**\n * 格式:金额 `{ style: 'currency' }`(币种默认取语言包)、百分比 `{ style: 'percent' }`、\n * 以万为单位 `{ scale: 10_000, maximumFractionDigits: 2 }`\n */\n format?: NumericFormat;\n /** 过渡的时长档位,默认 lg */\n duration?: TweenOptions['duration'];\n /** 挂载时从这个值滚到 value;不给时直接显示 value */\n from?: NumericValue;\n /** 按正负着色:正数成功色、负数危险色(涨跌);data-trend 写 up / down / flat */\n trend?: boolean;\n /** 文字色调;给了 trend 时由正负决定 */\n tone?: Tone;\n /** 数字前的内容(如“¥”以外的说明文字、图标) */\n prefix?: VNodeChild;\n /** 数字后的内容(如单位“人”) */\n suffix?: VNodeChild;\n /** 前缀的部件属性 */\n propsPrefix?: PartProps<'span'>;\n /** 后缀的部件属性 */\n propsSuffix?: PartProps<'span'>;\n /** 滚动中的数字(对读屏隐藏)的部件属性 */\n propsValue?: PartProps<'span', 'aria-hidden'>;\n /** 给读屏的终值文字的部件属性 */\n propsLabel?: Partial<VisuallyHiddenProps>;\n}\n\n/** AnimatedNumber 的样式:root */\nexport const animatedNumberStyles = css.create({\n // 等宽数字:滚动时宽度不跳\n root: { fontVariantNumeric: 'tabular-nums', whiteSpace: 'nowrap' },\n});\n\n/** 终值的小数位数:滚动中的值按它取整,整数不会滚出一串小数 */\nfunction decimals(value: NumericValue) {\n return Math.min(20, numericText(value).split('.')[1]?.length ?? 0);\n}\n\n/** 会滚动的数字:值变化时过渡,读屏只读终值 */\nexport const AnimatedNumber = _component(\n ({\n value,\n format,\n duration = 'lg',\n from,\n trend = false,\n tone,\n prefix,\n suffix,\n propsPrefix,\n propsSuffix,\n propsValue,\n propsLabel,\n ...rest\n }: AnimatedNumberProps) => {\n const formatter = useFormat();\n let root: HTMLElement | null = null;\n // 时长按自己所在的主题范围读(子树里可能覆盖了主题)\n const start = _untrack(from);\n const tween = useTween(() => toNumber(value), {\n duration: _untrack(duration),\n from: start == null ? undefined : toNumber(start),\n scope: () => root,\n });\n\n const text = (number: NumericValue) => formatter.number(number, format);\n\n /** 过渡中当前显示的文字 */\n const shownText = () => {\n // 停在终值时按原值格式化:bigint、Decimal 不经过浮点数\n if (tween.current === toNumber(value)) return text(value);\n const places = 10 ** decimals(value);\n return text(Math.round(tween.current * places) / places);\n };\n // 过渡中每帧直接改文字,不为一个数字重新渲染整个组件;渲染里只取当前值、不追踪\n let valueElement = _state.raw<HTMLElement | null>(null);\n _effect(() => {\n const shown = shownText();\n if (valueElement) valueElement.textContent = shown;\n });\n\n return () => {\n const sign = compareNumeric(value, 0);\n const direction = sign > 0 ? 'up' : sign < 0 ? 'down' : 'flat';\n const color = trend\n ? ({ up: 'success', down: 'danger', flat: tone } as const)[direction]\n : tone;\n return (\n <span\n {...mergeAttrs<'span'>(\n { sx: [animatedNumberStyles.root, color && toneStyles[color]] },\n rest,\n { ref: (element: unknown) => (root = element as HTMLElement | null) },\n )}\n data-trend={trend ? direction : undefined}\n >\n {hasContent(prefix) && <span {...mergeAttrs<'span'>({}, propsPrefix)}>{prefix}</span>}\n <span\n {...mergeAttrs<'span'>({}, propsValue, {\n 'aria-hidden': true,\n ref: _toRef(valueElement),\n })}\n >\n {_untrack(shownText())}\n </span>\n <VisuallyHidden {...mergePartProps<VisuallyHiddenProps>({}, propsLabel)}>\n {text(value)}\n </VisuallyHidden>\n {hasContent(suffix) && <span {...mergeAttrs<'span'>({}, propsSuffix)}>{suffix}</span>}\n </span>\n );\n };\n },\n);\n"],"mappings":";;;;;;;;;;;;AAoDA,IAAa,uBAAuB,IAAI,OAAO,EAE7C,MAAM;CAAE,oBAAoB;CAAgB,YAAY;AAAS,EACnE,CAAC;;AAGD,SAAS,SAAS,OAAqB;CACrC,OAAO,KAAK,IAAI,IAAI,YAAY,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,EAAE,EAAE,UAAU,CAAC;AACnE;;AAGA,IAAa,iBAAiB,0BAC5B,QAAA,SAc2B;CAAA,MAAA,QAAAA,aAAA,QAAA,OAAA;CAAA,MAAA,SAAAA,aAAA,QAAA,QAAA;CAAA,MAAA,WAAAA,aAAA,QAAA,YAXd,IAAI;CAAA,MAAA,OAAAA,aAAA,QAAA,MAAA;CAAA,MAAA,QAAAA,aAAA,QAAA,SAEP,KAAK;CAAA,MAAA,OAAAA,aAAA,QAAA,MAAA;CAAA,MAAA,SAAAA,aAAA,QAAA,QAAA;CAAA,MAAA,SAAAA,aAAA,QAAA,QAAA;CAAA,MAAA,cAAAA,aAAA,QAAA,aAAA;CAAA,MAAA,cAAAA,aAAA,QAAA,aAAA;CAAA,MAAA,aAAAA,aAAA,QAAA,YAAA;CAAA,MAAA,aAAAA,aAAA,QAAA,YAAA;CAAA,MAQV,OAAIC,UAAA,QAAA,MAAA,IAAA;CAEP,MAAM,YAAY,UAAU;CAC5B,IAAI,OAA2B;CAE/B,MAAM,QAAKC,cAAA,KAAA,KAAA;CACX,MAAM,QAAQ,eAAe,SAAQ,MAAA,KAAM,GAAG;EAC5C,UAAQA,cAAA,SAAA,KAAA;EACR,MAAM,SAAS,OAAO,KAAA,IAAY,SAAS,KAAK;EAChD,aAAa;CACf,CAAC;CAED,MAAM,QAAQ,WAAyB,UAAU,OAAO,QAAM,OAAA,KAAQ;;CAGtE,MAAM,kBAAkB;EAEtB,IAAI,MAAM,YAAY,SAAQ,MAAA,KAAM,GAAG,OAAO,KAAI,MAAA,KAAM;EACxD,MAAM,SAAS,MAAM,SAAQ,MAAA,KAAM;EACnC,OAAO,KAAK,KAAK,MAAM,MAAM,UAAU,MAAM,IAAI,MAAM;CACzD;CAEA,IAAI,eAAYC,WAAkC,IAAI;CACtD,aAAc;EACZ,MAAM,QAAQ,UAAU;EACxB,IAAA,aAAA,OAAkB,aAAA,MAAa,cAAc;CAC/C,CAAC;CAED,aAAa;EACX,MAAM,OAAO,eAAc,MAAA,OAAQ,CAAC;EACpC,MAAM,YAAY,OAAO,IAAI,OAAO,OAAO,IAAI,SAAS;EACxD,MAAM,QAAQ,MAAA,QACT;GAAE,IAAI;GAAW,MAAM;GAAU,MAAI,KAAA;EAAO,EAAY,aAAU,KAAA;EAEvE,OAAAC,YAAA,QAAAC,
|
|
1
|
+
{"version":3,"file":"AnimatedNumber.js","names":["_vtp_readonlyProp","_vtp_restProps","_vtp_untrack","_vue_shallowRef","_createVNode","_mergeProps"],"sources":["../../src/display/AnimatedNumber.tsx"],"sourcesContent":["/**\n * 会滚动的数字(span):值变化时从旧值过渡到新值(useTween),按当前语言格式化,等宽数字不抖动。\n * 统计卡片的数值、金额、百分比、计数器。\n *\n * <AnimatedNumber value={revenue} format={{ style: 'currency' }} />\n * <AnimatedNumber value={0.123} format={{ style: 'percent', maximumFractionDigits: 1 }} trend prefix=\"较上周 \" />\n *\n * 读屏只读终值:滚动中的数字对读屏隐藏,旁边有一份隐藏的终值文字。需要播报变化时在外层加 aria-live。\n * 值可以是 number、bigint 或 Decimal:过渡的中间帧按浮点数算,停下时与读屏的终值按原值格式化,不丢精度。\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 { VisuallyHidden, type VisuallyHiddenProps } from '../base/VisuallyHidden';\nimport { useFormat, type NumericFormat } from '../config/lang/format';\nimport { compareNumeric, numericText, toNumber, type NumericValue } from '../utils/data/numeric';\nimport { useTween, type TweenOptions } from '../utils/motion/tween';\nimport { hasContent, mergeAttrs, mergePartProps, type PartProps } from '../utils/props';\nimport { toneStyles, type Tone } from '../utils/style/typography';\n\n/** 未声明的属性落到外层 span 上;原生的 prefix 属性(RDFa)让给前缀内容 */\nexport interface AnimatedNumberProps extends PartProps<'span', 'prefix'> {\n /** 数值:number、bigint 或 Decimal */\n value: NumericValue;\n /**\n * 格式:金额 `{ style: 'currency' }`(币种默认取语言包)、百分比 `{ style: 'percent' }`、\n * 以万为单位 `{ scale: 10_000, maximumFractionDigits: 2 }`\n */\n format?: NumericFormat;\n /** 过渡的时长档位,默认 lg */\n duration?: TweenOptions['duration'];\n /** 挂载时从这个值滚到 value;不给时直接显示 value */\n from?: NumericValue;\n /** 按正负着色:正数成功色、负数危险色(涨跌);data-trend 写 up / down / flat */\n trend?: boolean;\n /** 文字色调;给了 trend 时由正负决定 */\n tone?: Tone;\n /** 数字前的内容(如“¥”以外的说明文字、图标) */\n prefix?: VNodeChild;\n /** 数字后的内容(如单位“人”) */\n suffix?: VNodeChild;\n /** 前缀的部件属性 */\n propsPrefix?: PartProps<'span'>;\n /** 后缀的部件属性 */\n propsSuffix?: PartProps<'span'>;\n /** 滚动中的数字(对读屏隐藏)的部件属性 */\n propsValue?: PartProps<'span', 'aria-hidden'>;\n /** 给读屏的终值文字的部件属性 */\n propsLabel?: Partial<VisuallyHiddenProps>;\n}\n\n/** AnimatedNumber 的样式:root */\nexport const animatedNumberStyles = css.create({\n // 等宽数字:滚动时宽度不跳\n root: { fontVariantNumeric: 'tabular-nums', whiteSpace: 'nowrap' },\n});\n\n/** 终值的小数位数:滚动中的值按它取整,整数不会滚出一串小数 */\nfunction decimals(value: NumericValue) {\n return Math.min(20, numericText(value).split('.')[1]?.length ?? 0);\n}\n\n/** 会滚动的数字:值变化时过渡,读屏只读终值 */\nexport const AnimatedNumber = _component(\n ({\n value,\n format,\n duration = 'lg',\n from,\n trend = false,\n tone,\n prefix,\n suffix,\n propsPrefix,\n propsSuffix,\n propsValue,\n propsLabel,\n ...rest\n }: AnimatedNumberProps) => {\n const formatter = useFormat();\n let root: HTMLElement | null = null;\n // 时长按自己所在的主题范围读(子树里可能覆盖了主题)\n const start = _untrack(from);\n const tween = useTween(() => toNumber(value), {\n duration: _untrack(duration),\n from: start == null ? undefined : toNumber(start),\n scope: () => root,\n });\n\n const text = (number: NumericValue) => formatter.number(number, format);\n\n /** 过渡中当前显示的文字 */\n const shownText = () => {\n // 停在终值时按原值格式化:bigint、Decimal 不经过浮点数\n if (tween.current === toNumber(value)) return text(value);\n const places = 10 ** decimals(value);\n return text(Math.round(tween.current * places) / places);\n };\n // 过渡中每帧直接改文字,不为一个数字重新渲染整个组件;渲染里只取当前值、不追踪\n let valueElement = _state.raw<HTMLElement | null>(null);\n _effect(() => {\n const shown = shownText();\n if (valueElement) valueElement.textContent = shown;\n });\n\n return () => {\n const sign = compareNumeric(value, 0);\n const direction = sign > 0 ? 'up' : sign < 0 ? 'down' : 'flat';\n const color = trend\n ? ({ up: 'success', down: 'danger', flat: tone } as const)[direction]\n : tone;\n return (\n <span\n {...mergeAttrs<'span'>(\n { sx: [animatedNumberStyles.root, color && toneStyles[color]] },\n rest,\n { ref: (element: unknown) => (root = element as HTMLElement | null) },\n )}\n data-trend={trend ? direction : undefined}\n >\n {hasContent(prefix) && <span {...mergeAttrs<'span'>({}, propsPrefix)}>{prefix}</span>}\n <span\n {...mergeAttrs<'span'>({}, propsValue, {\n 'aria-hidden': true,\n ref: _toRef(valueElement),\n })}\n >\n {_untrack(shownText())}\n </span>\n <VisuallyHidden {...mergePartProps<VisuallyHiddenProps>({}, propsLabel)}>\n {text(value)}\n </VisuallyHidden>\n {hasContent(suffix) && <span {...mergeAttrs<'span'>({}, propsSuffix)}>{suffix}</span>}\n </span>\n );\n };\n },\n);\n"],"mappings":";;;;;;;;;;;;AAoDA,IAAa,uBAAuB,IAAI,OAAO,EAE7C,MAAM;CAAE,oBAAoB;CAAgB,YAAY;AAAS,EACnE,CAAC;;AAGD,SAAS,SAAS,OAAqB;CACrC,OAAO,KAAK,IAAI,IAAI,YAAY,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,EAAE,EAAE,UAAU,CAAC;AACnE;;AAGA,IAAa,iBAAiB,0BAC5B,QAAA,SAc2B;CAAA,MAAA,QAAAA,aAAA,QAAA,OAAA;CAAA,MAAA,SAAAA,aAAA,QAAA,QAAA;CAAA,MAAA,WAAAA,aAAA,QAAA,YAXd,IAAI;CAAA,MAAA,OAAAA,aAAA,QAAA,MAAA;CAAA,MAAA,QAAAA,aAAA,QAAA,SAEP,KAAK;CAAA,MAAA,OAAAA,aAAA,QAAA,MAAA;CAAA,MAAA,SAAAA,aAAA,QAAA,QAAA;CAAA,MAAA,SAAAA,aAAA,QAAA,QAAA;CAAA,MAAA,cAAAA,aAAA,QAAA,aAAA;CAAA,MAAA,cAAAA,aAAA,QAAA,aAAA;CAAA,MAAA,aAAAA,aAAA,QAAA,YAAA;CAAA,MAAA,aAAAA,aAAA,QAAA,YAAA;CAAA,MAQV,OAAIC,UAAA,QAAA,MAAA,IAAA;CAEP,MAAM,YAAY,UAAU;CAC5B,IAAI,OAA2B;CAE/B,MAAM,QAAKC,cAAA,KAAA,KAAA;CACX,MAAM,QAAQ,eAAe,SAAQ,MAAA,KAAM,GAAG;EAC5C,UAAQA,cAAA,SAAA,KAAA;EACR,MAAM,SAAS,OAAO,KAAA,IAAY,SAAS,KAAK;EAChD,aAAa;CACf,CAAC;CAED,MAAM,QAAQ,WAAyB,UAAU,OAAO,QAAM,OAAA,KAAQ;;CAGtE,MAAM,kBAAkB;EAEtB,IAAI,MAAM,YAAY,SAAQ,MAAA,KAAM,GAAG,OAAO,KAAI,MAAA,KAAM;EACxD,MAAM,SAAS,MAAM,SAAQ,MAAA,KAAM;EACnC,OAAO,KAAK,KAAK,MAAM,MAAM,UAAU,MAAM,IAAI,MAAM;CACzD;CAEA,IAAI,eAAYC,WAAkC,IAAI;CACtD,aAAc;EACZ,MAAM,QAAQ,UAAU;EACxB,IAAA,aAAA,OAAkB,aAAA,MAAa,cAAc;CAC/C,CAAC;CAED,aAAa;EACX,MAAM,OAAO,eAAc,MAAA,OAAQ,CAAC;EACpC,MAAM,YAAY,OAAO,IAAI,OAAO,OAAO,IAAI,SAAS;EACxD,MAAM,QAAQ,MAAA,QACT;GAAE,IAAI;GAAW,MAAM;GAAU,MAAI,KAAA;EAAO,EAAY,aAAU,KAAA;EAEvE,OAAAC,YAAA,QAAAC,cAEQ,WACF,EAAE,IAAI,CAAC,qBAAqB,MAAM,SAAS,WAAW,MAAM,EAAE,GAC9D,MACA,EAAE,MAAM,YAAsB,OAAO,QAA+B,CACtE,GAAC,EAAA,cACW,MAAA,QAAQ,YAAY,KAAA,EAAS,CAAA,GAAA;GAExC,WAAU,OAAA,KAAO,KAACD,YAAA,QAAc,WAAmB,CAAC,GAAC,YAAA,KAAa,GAAC,CAAA,OAAA,KAAA,CAAA;GAAiBA,YAAA,QAE/E,WAAmB,CAAC,GAAC,WAAA,OAAc;IACrC,eAAe;IACf,KAAG;GACL,CAAC,GAAC,CAAAF,cAEQ,UAAU,CAAC,CAAA,CAAA;GAAAE,YAAA,gBAAAC,cAEH,eAAoC,CAAC,GAAC,WAAA,KAAY,GAAC,EAAA,OAAA,UAAA,CAAA,GAAA;IAAA,eAAA,CACpE,KAAI,MAAA,KAAM,CAAC;IAAA,SAAA;GAAA,CAAA;GAEb,WAAU,OAAA,KAAO,KAACD,YAAA,QAAc,WAAmB,CAAC,GAAC,YAAA,KAAa,GAAC,CAAA,OAAA,KAAA,CAAA;EAAiB,CAAA;CAG3F;AACF,GAAC;CAAA,MAAA;CAAA,OAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;CAAA;AAAA,CACH"}
|
package/dist/display/Avatar.js
CHANGED
|
@@ -6,8 +6,8 @@ import { radiusStyles } from "../utils/style/styles.js";
|
|
|
6
6
|
import { useRecipe } from "../config/styles.js";
|
|
7
7
|
import { Icon } from "../base/Icon.js";
|
|
8
8
|
import { statusToneStyles } from "../utils/style/tone-styles.js";
|
|
9
|
-
import { createVNode,
|
|
10
|
-
import { _component, createContext, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
9
|
+
import { createVNode, ref, watch } from "vue";
|
|
10
|
+
import { _component, createContext, 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 { User } from "@lucide/icons";
|
|
13
13
|
//#region src/display/Avatar.tsx
|
|
@@ -118,7 +118,7 @@ var Avatar = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
118
118
|
grouped: !!group,
|
|
119
119
|
loading: status.value === "loading"
|
|
120
120
|
});
|
|
121
|
-
return createVNode("span",
|
|
121
|
+
return createVNode("span", mergeJsxProps(mergeAttrs({
|
|
122
122
|
...s.root,
|
|
123
123
|
sx: [
|
|
124
124
|
statusToneStyles[fill][color],
|
|
@@ -129,7 +129,7 @@ var Avatar = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
129
129
|
}, rest, name.value ? {
|
|
130
130
|
role: "img",
|
|
131
131
|
"aria-label": name.value
|
|
132
|
-
} : { "aria-hidden": true }), { "data-state": src.value ? status.value : "fallback" }), [src.value && status.value !== "error" && createVNode("img",
|
|
132
|
+
} : { "aria-hidden": true }), { "data-state": src.value ? status.value : "fallback" }), [src.value && status.value !== "error" && createVNode("img", mergeJsxProps({
|
|
133
133
|
"key": src.value,
|
|
134
134
|
"alt": ""
|
|
135
135
|
}, mergeAttrs(s.image, propsImage.value, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Avatar.js","names":["_vtp_readonlyProp","_vtp_restProps","_vue_ref","_createVNode","_mergeProps"],"sources":["../../src/display/Avatar.tsx"],"sourcesContent":["/**\n * 头像(span,role=\"img\"):显示图片,图片还没加载好或加载失败时显示后备内容(默认是名字的首字母)。\n * 名称写在外层(aria-label),图片本身是装饰(alt=\"\"),读屏不会念两遍;没有 name 时整个头像是装饰性的(对读屏隐藏)。\n *\n * <Avatar src={user.avatar} name={user.name} />\n * <Avatar name=\"张三\" size=\"lg\" radius=\"md\" />\n * <Avatar name=\"李四\" variant=\"solid\" tone=\"success\" />\n */\nimport { User } from '@lucide/icons';\nimport { createContext, _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport { watch, type VNodeChild } from 'vue';\nimport { Icon } from '../base/Icon';\nimport { useDefaults } from '../config/defaults';\nimport { useRecipe } from '../config/styles';\nimport { mergeAttrs, type PartProps } from '../utils/props';\nimport { statusToneStyles, type StatusTone } from '../utils/style/tone-styles';\nimport { fontSizeStyles, type TextSize } from '../utils/style/typography';\nimport { radiusStyles, type Radius } from '../utils/style/styles';\nimport { t } from '../config/theme/tokens';\n\n/** 后备内容(首字母、图标)的底色样式 */\nexport interface AvatarVariants {\n /** 浅色底(默认) */\n soft: unknown;\n /** 实底 */\n solid: unknown;\n}\n\n/** 头像的外观选项;可以用 createUi / Provider 的 defaults.Avatar 统一改默认值 */\nexport interface AvatarOptions {\n /** 字号档位,头像边长是它的 2.5 倍;不写时继承 */\n size?: TextSize;\n /** 圆角,默认 full(圆形);团队、组织、应用的图标常用 md(圆角方形) */\n radius?: Radius;\n /** 后备内容的底色样式,默认 soft */\n variant?: keyof AvatarVariants;\n /** 后备内容的色调(与 Button、Badge 等同一套),默认 accent */\n tone?: StatusTone;\n}\n\n/** 角色与读屏可见性由组件按 name 决定 */\nexport type AvatarManaged = 'role' | 'aria-hidden';\n\n/** 未声明的属性落到外层 span 上 */\nexport interface AvatarProps extends PartProps<'span', AvatarManaged>, AvatarOptions {\n /** 图片地址;不写或加载失败时显示后备内容 */\n src?: string;\n /** 名字:无障碍名称,也用来生成首字母;不写时头像是装饰性的 */\n name?: string;\n /** 后备内容,默认是名字的首字母,没有名字时是人像图标 */\n fallback?: VNodeChild;\n /** 图片的部件属性 */\n propsImage?: PartProps<'img', 'src' | 'alt'>;\n /** 后备内容的部件属性 */\n propsFallback?: PartProps<'span'>;\n}\n\n/** 在 AvatarGroup 里:Avatar 据此叠放(AvatarGroup 提供) */\nexport const AvatarGroupContext = createContext<{ readonly grouped: true }>('AvatarGroup');\n\n/**\n * 头像的配方:部件 root(外层)、image(图片)、fallback(后备内容);在头像组里、图片加载中是档位。\n * 后备内容的底色来自共用的 statusToneStyles,排在配方之前,createUi / Provider 的 styles.Avatar 能盖过它\n */\nexport const avatarRecipe = css.recipe({\n name: 'Avatar',\n parts: ['root', 'image', 'fallback'],\n base: {\n root: {\n position: 'relative',\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n flexShrink: 0,\n width: '2.5em',\n height: '2.5em',\n overflow: 'hidden',\n verticalAlign: 'middle',\n userSelect: 'none',\n },\n image: { width: '_full', height: '_full', objectFit: 'cover' },\n fallback: { fontWeight: '_medium', lineHeight: 1, textTransform: 'uppercase' },\n },\n variants: {\n // 头像组里:往前叠 0.5em,一圈页面底色的描边把相邻的分开(AvatarGroup 的“+N”也用它)\n grouped: {\n true: {\n root: {\n boxShadow: `0 0 0 ${t.borderWidths.sm} ${t.colors.background}`,\n marginInlineStart: { default: '-0.5em', ':first-child': 0 },\n },\n },\n },\n // 图片加载完之前先藏起来(仍在 DOM 里加载),后备内容垫在下面\n loading: { true: { image: { position: 'absolute', opacity: 0 } } },\n },\n});\n\n/** 头像的部件(createUi / Provider 的 styles.Avatar 按它定制) */\nexport type AvatarPart = (typeof avatarRecipe.parts)[number];\n\n/** 名字的首字母:中文取最后一个字(通常是名),其他语言取前两个词的首字母 */\nexport function initials(name: string): string {\n const trimmed = name.trim();\n if (!trimmed) return '';\n if (/[㐀-鿿]/.test(trimmed)) return trimmed.at(-1)!;\n const words = trimmed.split(/\\s+/);\n return (words[0]![0]! + (words.length > 1 ? words.at(-1)![0]! : '')).toUpperCase();\n}\n\n/** 头像:图片 + 后备内容(首字母或图标) */\nexport const Avatar = _component(\n ({\n src,\n name,\n fallback,\n size,\n radius,\n variant,\n tone,\n propsImage,\n propsFallback,\n ...rest\n }: AvatarProps) => {\n const appearance = useDefaults('Avatar', () => ({ size, radius, variant, tone }), {\n radius: 'full',\n variant: 'soft',\n tone: 'accent',\n });\n const recipe = useRecipe(avatarRecipe);\n const group = AvatarGroupContext.inject(null);\n // 图片状态:loading 加载中、loaded 已显示、error 失败(显示后备内容)\n let status = _state<'loading' | 'loaded' | 'error'>('loading');\n watch(\n () => src,\n () => (status = 'loading'),\n );\n\n return () => {\n const showFallback = !src || status !== 'loaded';\n const { size: fontSize, radius: corner, variant: fill, tone: color } = appearance();\n const s = recipe({\n variant: fill,\n tone: color,\n grouped: !!group,\n loading: status === 'loading',\n });\n return (\n <span\n {...mergeAttrs<'span'>(\n {\n ...s.root,\n sx: [\n statusToneStyles[fill][color],\n s.root.sx,\n radiusStyles[corner],\n fontSize && fontSizeStyles[fontSize],\n ],\n },\n rest,\n name ? { role: 'img', 'aria-label': name } : { 'aria-hidden': true },\n )}\n data-state={src ? status : 'fallback'}\n >\n {src && status !== 'error' && (\n <img\n key={src}\n alt=\"\"\n {...mergeAttrs<'img'>(s.image, propsImage, {\n src,\n onLoad: () => (status = 'loaded'),\n onError: () => (status = 'error'),\n })}\n />\n )}\n {showFallback && (\n <span {...mergeAttrs<'span'>(s.fallback, propsFallback, { 'aria-hidden': true })}>\n {fallback ?? (name ? initials(name) : <Icon icon={User} />)}\n </span>\n )}\n </span>\n );\n };\n },\n);\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AA2DA,IAAa,qBAAqB,cAA0C,aAAa;;;;;AAMzF,IAAa,eAAe,IAAI,OAAO;CACrC,MAAM;CACN,OAAO;EAAC;EAAQ;EAAS;CAAU;CACnC,MAAM;EACJ,MAAM;GACJ,UAAU;GACV,SAAS;GACT,YAAY;GACZ,gBAAgB;GAChB,YAAY;GACZ,OAAO;GACP,QAAQ;GACR,UAAU;GACV,eAAe;GACf,YAAY;EACd;EACA,OAAO;GAAE,OAAO;GAAS,QAAQ;GAAS,WAAW;EAAQ;EAC7D,UAAU;GAAE,YAAY;GAAW,YAAY;GAAG,eAAe;EAAY;CAC/E;CACA,UAAU;EAER,SAAS,EACP,MAAM,EACJ,MAAM;GACJ,WAAW,SAAS,EAAE,aAAa,GAAE,GAAI,EAAE,OAAO;GAClD,mBAAmB;IAAE,SAAS;IAAU,gBAAgB;GAAE;EAC5D,EACF,EACF;EAEA,SAAS,EAAE,MAAM,EAAE,OAAO;GAAE,UAAU;GAAY,SAAS;EAAE,EAAE,EAAE;CACnE;AACF,CAAC;;;AAMD,SAAgB,SAAS,MAAsB;CAC7C,MAAM,UAAU,KAAK,KAAK;CAC1B,IAAI,CAAC,SAAS,OAAO;CACrB,IAAI,QAAQ,KAAK,OAAO,GAAG,OAAO,QAAQ,GAAG,EAAE;CAC/C,MAAM,QAAQ,QAAQ,MAAM,KAAK;CACjC,QAAQ,MAAM,EAAE,CAAE,MAAO,MAAM,SAAS,IAAI,MAAM,GAAG,EAAE,CAAC,CAAE,KAAM,IAAA,CAAK,YAAY;AACnF;;AAGA,IAAa,SAAS,0BACpB,QAAA,SAWmB;CAAA,MAAA,MAAAA,aAAA,QAAA,KAAA;CAAA,MAAA,OAAAA,aAAA,QAAA,MAAA;CAAA,MAAA,WAAAA,aAAA,QAAA,UAAA;CAAA,MAAA,OAAAA,aAAA,QAAA,MAAA;CAAA,MAAA,SAAAA,aAAA,QAAA,QAAA;CAAA,MAAA,UAAAA,aAAA,QAAA,SAAA;CAAA,MAAA,OAAAA,aAAA,QAAA,MAAA;CAAA,MAAA,aAAAA,aAAA,QAAA,YAAA;CAAA,MAAA,gBAAAA,aAAA,QAAA,eAAA;CAAA,MADd,OAAIC,UAAA,QAAA,MAAA,IAAA;CAEP,MAAM,aAAa,YAAY,iBAAiB;EAAE,MAAI,KAAA;EAAE,QAAM,OAAA;EAAE,SAAO,QAAA;EAAE,MAAI,KAAA;CAAC,IAAI;EAChF,QAAQ;EACR,SAAS;EACT,MAAM;CACR,CAAC;CACD,MAAM,SAAS,UAAU,YAAY;CACrC,MAAM,QAAQ,mBAAmB,OAAO,IAAI;CAE5C,IAAI,SAAMC,IAA0C,SAAS;CAC7D,YACE,IAAA,aACO,OAAA,QAAS,SAClB;CAEA,aAAa;EACX,MAAM,eAAe,CAAA,IAAA,SAAQ,OAAA,UAAW;EACxC,MAAM,EAAE,MAAM,UAAU,QAAQ,QAAQ,SAAS,MAAM,MAAM,UAAU,WAAW;EAClF,MAAM,IAAI,OAAO;GACf,SAAS;GACT,MAAM;GACN,SAAS,CAAC,CAAC;GACX,SAAS,OAAA,UAAW;EACtB,CAAC;EACD,OAAAC,YAAA,QAAAC,
|
|
1
|
+
{"version":3,"file":"Avatar.js","names":["_vtp_readonlyProp","_vtp_restProps","_vue_ref","_createVNode","_mergeProps"],"sources":["../../src/display/Avatar.tsx"],"sourcesContent":["/**\n * 头像(span,role=\"img\"):显示图片,图片还没加载好或加载失败时显示后备内容(默认是名字的首字母)。\n * 名称写在外层(aria-label),图片本身是装饰(alt=\"\"),读屏不会念两遍;没有 name 时整个头像是装饰性的(对读屏隐藏)。\n *\n * <Avatar src={user.avatar} name={user.name} />\n * <Avatar name=\"张三\" size=\"lg\" radius=\"md\" />\n * <Avatar name=\"李四\" variant=\"solid\" tone=\"success\" />\n */\nimport { User } from '@lucide/icons';\nimport { createContext, _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport { watch, type VNodeChild } from 'vue';\nimport { Icon } from '../base/Icon';\nimport { useDefaults } from '../config/defaults';\nimport { useRecipe } from '../config/styles';\nimport { mergeAttrs, type PartProps } from '../utils/props';\nimport { statusToneStyles, type StatusTone } from '../utils/style/tone-styles';\nimport { fontSizeStyles, type TextSize } from '../utils/style/typography';\nimport { radiusStyles, type Radius } from '../utils/style/styles';\nimport { t } from '../config/theme/tokens';\n\n/** 后备内容(首字母、图标)的底色样式 */\nexport interface AvatarVariants {\n /** 浅色底(默认) */\n soft: unknown;\n /** 实底 */\n solid: unknown;\n}\n\n/** 头像的外观选项;可以用 createUi / Provider 的 defaults.Avatar 统一改默认值 */\nexport interface AvatarOptions {\n /** 字号档位,头像边长是它的 2.5 倍;不写时继承 */\n size?: TextSize;\n /** 圆角,默认 full(圆形);团队、组织、应用的图标常用 md(圆角方形) */\n radius?: Radius;\n /** 后备内容的底色样式,默认 soft */\n variant?: keyof AvatarVariants;\n /** 后备内容的色调(与 Button、Badge 等同一套),默认 accent */\n tone?: StatusTone;\n}\n\n/** 角色与读屏可见性由组件按 name 决定 */\nexport type AvatarManaged = 'role' | 'aria-hidden';\n\n/** 未声明的属性落到外层 span 上 */\nexport interface AvatarProps extends PartProps<'span', AvatarManaged>, AvatarOptions {\n /** 图片地址;不写或加载失败时显示后备内容 */\n src?: string;\n /** 名字:无障碍名称,也用来生成首字母;不写时头像是装饰性的 */\n name?: string;\n /** 后备内容,默认是名字的首字母,没有名字时是人像图标 */\n fallback?: VNodeChild;\n /** 图片的部件属性 */\n propsImage?: PartProps<'img', 'src' | 'alt'>;\n /** 后备内容的部件属性 */\n propsFallback?: PartProps<'span'>;\n}\n\n/** 在 AvatarGroup 里:Avatar 据此叠放(AvatarGroup 提供) */\nexport const AvatarGroupContext = createContext<{ readonly grouped: true }>('AvatarGroup');\n\n/**\n * 头像的配方:部件 root(外层)、image(图片)、fallback(后备内容);在头像组里、图片加载中是档位。\n * 后备内容的底色来自共用的 statusToneStyles,排在配方之前,createUi / Provider 的 styles.Avatar 能盖过它\n */\nexport const avatarRecipe = css.recipe({\n name: 'Avatar',\n parts: ['root', 'image', 'fallback'],\n base: {\n root: {\n position: 'relative',\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n flexShrink: 0,\n width: '2.5em',\n height: '2.5em',\n overflow: 'hidden',\n verticalAlign: 'middle',\n userSelect: 'none',\n },\n image: { width: '_full', height: '_full', objectFit: 'cover' },\n fallback: { fontWeight: '_medium', lineHeight: 1, textTransform: 'uppercase' },\n },\n variants: {\n // 头像组里:往前叠 0.5em,一圈页面底色的描边把相邻的分开(AvatarGroup 的“+N”也用它)\n grouped: {\n true: {\n root: {\n boxShadow: `0 0 0 ${t.borderWidths.sm} ${t.colors.background}`,\n marginInlineStart: { default: '-0.5em', ':first-child': 0 },\n },\n },\n },\n // 图片加载完之前先藏起来(仍在 DOM 里加载),后备内容垫在下面\n loading: { true: { image: { position: 'absolute', opacity: 0 } } },\n },\n});\n\n/** 头像的部件(createUi / Provider 的 styles.Avatar 按它定制) */\nexport type AvatarPart = (typeof avatarRecipe.parts)[number];\n\n/** 名字的首字母:中文取最后一个字(通常是名),其他语言取前两个词的首字母 */\nexport function initials(name: string): string {\n const trimmed = name.trim();\n if (!trimmed) return '';\n if (/[㐀-鿿]/.test(trimmed)) return trimmed.at(-1)!;\n const words = trimmed.split(/\\s+/);\n return (words[0]![0]! + (words.length > 1 ? words.at(-1)![0]! : '')).toUpperCase();\n}\n\n/** 头像:图片 + 后备内容(首字母或图标) */\nexport const Avatar = _component(\n ({\n src,\n name,\n fallback,\n size,\n radius,\n variant,\n tone,\n propsImage,\n propsFallback,\n ...rest\n }: AvatarProps) => {\n const appearance = useDefaults('Avatar', () => ({ size, radius, variant, tone }), {\n radius: 'full',\n variant: 'soft',\n tone: 'accent',\n });\n const recipe = useRecipe(avatarRecipe);\n const group = AvatarGroupContext.inject(null);\n // 图片状态:loading 加载中、loaded 已显示、error 失败(显示后备内容)\n let status = _state<'loading' | 'loaded' | 'error'>('loading');\n watch(\n () => src,\n () => (status = 'loading'),\n );\n\n return () => {\n const showFallback = !src || status !== 'loaded';\n const { size: fontSize, radius: corner, variant: fill, tone: color } = appearance();\n const s = recipe({\n variant: fill,\n tone: color,\n grouped: !!group,\n loading: status === 'loading',\n });\n return (\n <span\n {...mergeAttrs<'span'>(\n {\n ...s.root,\n sx: [\n statusToneStyles[fill][color],\n s.root.sx,\n radiusStyles[corner],\n fontSize && fontSizeStyles[fontSize],\n ],\n },\n rest,\n name ? { role: 'img', 'aria-label': name } : { 'aria-hidden': true },\n )}\n data-state={src ? status : 'fallback'}\n >\n {src && status !== 'error' && (\n <img\n key={src}\n alt=\"\"\n {...mergeAttrs<'img'>(s.image, propsImage, {\n src,\n onLoad: () => (status = 'loaded'),\n onError: () => (status = 'error'),\n })}\n />\n )}\n {showFallback && (\n <span {...mergeAttrs<'span'>(s.fallback, propsFallback, { 'aria-hidden': true })}>\n {fallback ?? (name ? initials(name) : <Icon icon={User} />)}\n </span>\n )}\n </span>\n );\n };\n },\n);\n"],"mappings":"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"}
|
|
@@ -5,8 +5,8 @@ import { focusRingStyles } from "../utils/style/styles.js";
|
|
|
5
5
|
import { Icon } from "../base/Icon.js";
|
|
6
6
|
import { VisuallyHidden } from "../base/VisuallyHidden.js";
|
|
7
7
|
import { Progress } from "../feedback/Progress.js";
|
|
8
|
-
import { createVNode,
|
|
9
|
-
import { _component, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
8
|
+
import { createVNode, useId } from "vue";
|
|
9
|
+
import { _component, mergeJsxProps, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
10
10
|
import * as css from "@zerodep-dev/vue-tsx-pro-tsx/styles";
|
|
11
11
|
import { Ban, CircleAlert, CircleCheck, CircleDashed, CircleMinus } from "@lucide/icons";
|
|
12
12
|
//#region src/display/Checklist.tsx
|
|
@@ -177,7 +177,7 @@ var Checklist = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
177
177
|
const state = statusOf(item);
|
|
178
178
|
if (!onCheckedChange.value) return createVNode("span", css.attrs(checklistStyles.status), [statusIcon(item), createVNode(VisuallyHidden, null, { default: () => [lang.checklistStatus[state]] })]);
|
|
179
179
|
const done = state === "done";
|
|
180
|
-
return createVNode("span", css.attrs(checklistStyles.status), [createVNode("button",
|
|
180
|
+
return createVNode("span", css.attrs(checklistStyles.status), [createVNode("button", mergeJsxProps({
|
|
181
181
|
"type": "button",
|
|
182
182
|
"role": "checkbox",
|
|
183
183
|
"aria-checked": done,
|
|
@@ -206,9 +206,9 @@ var Checklist = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
206
206
|
}
|
|
207
207
|
if (layout.value === "inline") return createVNode("ul", mergeAttrs({ sx: [checklistStyles.inline, size.value && fontSizeStyles[size.value]] }, rest), [items.value.map((item, index) => {
|
|
208
208
|
const labelId = `${id}-${index}`;
|
|
209
|
-
return createVNode("li",
|
|
209
|
+
return createVNode("li", mergeJsxProps({ "key": keyOf(item, index) }, css.attrs(checklistStyles.inlineItem)), [
|
|
210
210
|
status(item, labelId),
|
|
211
|
-
createVNode("span",
|
|
211
|
+
createVNode("span", mergeJsxProps({ "id": labelId }, css.attrs(labelSx(item))), [item.label]),
|
|
212
212
|
hasContent(item.date) && createVNode("span", css.attrs(checklistStyles.meta, statusOf(item) === "overdue" && checklistStyles.late), [item.date]),
|
|
213
213
|
item.evidence
|
|
214
214
|
]);
|
|
@@ -228,10 +228,10 @@ var Checklist = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
228
228
|
item.date,
|
|
229
229
|
item.evidence
|
|
230
230
|
].some(hasContent);
|
|
231
|
-
return createVNode("li",
|
|
231
|
+
return createVNode("li", mergeJsxProps({ "key": keyOf(item, index) }, mergeAttrs({ sx: checklistStyles.item }, own), { "data-status": statusOf(item) }), [
|
|
232
232
|
status(item, labelId),
|
|
233
233
|
createVNode("div", css.attrs(checklistStyles.body), [
|
|
234
|
-
createVNode("span",
|
|
234
|
+
createVNode("span", mergeJsxProps({ "id": labelId }, css.attrs(labelSx(item))), [item.label]),
|
|
235
235
|
hasContent(item.description) && createVNode("span", css.attrs(checklistStyles.description), [item.description]),
|
|
236
236
|
meta && createVNode("span", css.attrs(checklistStyles.meta), [
|
|
237
237
|
hasContent(item.owner) && createVNode("span", null, [item.owner]),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Checklist.js","names":["_vtp_readonlyProp","_vtp_restProps","_createVNode","_mergeProps"],"sources":["../../src/display/Checklist.tsx"],"sourcesContent":["/**\n * 完成清单:一组要做完的事,每项的状态(完成、待办、超时、不适用、受阻)与它的上下文——怎么做、谁负责、哪天、证据、去处理。\n * 里程碑与交付证据、结项材料、上线检查、新用户的引导任务。参考 Shopify Polaris 的 Setup guide、GitHub 的任务列表。\n *\n * <Checklist\n * title=\"结项材料\"\n * summary\n * items={[\n * { label: '验收报告', status: 'done', date: '10-12', evidence: <Link href={url}>验证报告.pdf</Link> },\n * { label: '财务复核意见', status: 'overdue', owner: '财务部', date: '计划 10-20', action: <Button>催办</Button> },\n * { label: '合同签订记录', description: '谈定价格与生效日期,采购经理签字', owner: '采购部' },\n * ]}\n * />\n * <Checklist layout=\"inline\" items={…} /> 一行排开、放不下换行(详情行、卡片的一角)\n * <Checklist layout=\"icons\" aria-label=\"里程碑\" items={…} /> 只有一排状态图标(表格的格子里),读屏念每一项\n * <Checklist items={tasks} onCheckedChange={(item, done) => save(item.key, done)} /> 可以勾选(上线检查、引导任务)\n *\n * summary 在上面显示“已完成 2 / 3”与进度条(不适用的不算)。没完成的名称是次要色,不适用的划掉;\n * 超时的日期用危险色。每项的状态写在给读屏的文字里。\n *\n * 部件:Root(div)、Summary(汇总行)、List(ul)、Item(li,可以按项计算)、Status(状态图标,可勾选时是按钮)、Label、Meta。\n */\nimport { Ban, CircleAlert, CircleCheck, CircleDashed, CircleMinus } from '@lucide/icons';\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport { useId, type VNodeChild } from 'vue';\nimport { Icon, type IconData } from '../base/Icon';\nimport { VisuallyHidden } from '../base/VisuallyHidden';\nimport { useLang } from '../config/context';\nimport { Progress } from '../feedback/Progress';\nimport { hasContent, mergeAttrs, type PartProps } from '../utils/props';\nimport { focusRingStyles } from '../utils/style/styles';\nimport { fontSizeStyles, type TextSize } from '../utils/style/typography';\n\n/** 一项的状态 */\nexport interface ChecklistStatuses {\n /** 完成 */\n done: unknown;\n /** 待办(默认) */\n todo: unknown;\n /** 超时:过了计划日期还没完成 */\n overdue: unknown;\n /** 受阻:等别人、缺前提 */\n blocked: unknown;\n /** 不适用:这一次不用做,不算进进度 */\n skipped: unknown;\n}\n\n/** 清单的一项 */\nexport interface ChecklistItem {\n /** 唯一的 key;不写时用 label(label 是文字时)或位置 */\n key?: string;\n /** 名称 */\n label: VNodeChild;\n /** 状态,默认 todo */\n status?: keyof ChecklistStatuses;\n /** 说明:怎么做、要什么材料、缺的由谁出具 */\n description?: VNodeChild;\n /** 负责人(谁处理、谁出具) */\n owner?: VNodeChild;\n /** 日期:完成的日期,或计划日期(超时时用危险色) */\n date?: VNodeChild;\n /** 证据:附件、报告的链接 */\n evidence?: VNodeChild;\n /** 操作:上传证据、去处理、催办(结束边) */\n action?: VNodeChild;\n}\n\n/** 清单的排法 */\nexport interface ChecklistLayouts {\n /** 一项一行(默认):状态、名称与说明、负责人 · 日期 · 证据,操作在结束边 */\n list: unknown;\n /** 一行排开、放不下换行:状态、名称、日期、证据 */\n inline: unknown;\n /** 只有一排状态图标:表格的格子里;悬停与读屏有每一项的说明 */\n icons: unknown;\n}\n\n/** 未声明的属性落到外层元素上(list 是 div,inline 是 ul,icons 是 span) */\nexport interface ChecklistProps extends PartProps<'div'> {\n /** 各项 */\n items: readonly ChecklistItem[];\n /**\n * 排法\n * @default 'list'\n */\n layout?: keyof ChecklistLayouts;\n /** 上面显示“已完成 2 / 3”与进度条(list 排法) */\n summary?: boolean;\n /** 字号档位;不写时继承 */\n size?: TextSize;\n /** 可以勾选:状态图标变成复选按钮,点了报告新的完成状态(不适用、受阻的不能勾) */\n onCheckedChange?: (item: ChecklistItem, done: boolean) => void;\n /** 每一项(li)的部件属性,可以按项计算 */\n propsItem?: PartProps<'li'> | ((item: ChecklistItem) => PartProps<'li'>);\n}\n\n/** Checklist 的样式 */\nexport const checklistStyles = css.create({\n root: { display: 'grid', gap: '_sm' },\n summary: {\n display: 'grid',\n gridTemplateColumns: 'auto 1fr',\n alignItems: 'center',\n gap: '_md',\n fontSize: '_sm',\n color: '_textMuted',\n },\n list: { display: 'grid', margin: 0, padding: 0, listStyle: 'none' },\n // 状态一列、内容一列、操作一列;状态贴着名称那一行\n item: {\n display: 'grid',\n gridTemplateColumns: 'auto minmax(0, 1fr) auto',\n columnGap: '_sm',\n alignItems: 'start',\n paddingBlock: '_xs',\n },\n status: {\n display: 'flex',\n alignItems: 'center',\n minHeight: '1lh',\n fontSize: '1.125em',\n },\n // 可以勾选时的按钮:只有图标,去掉按钮的外观\n toggle: {\n display: 'inline-flex',\n padding: 0,\n margin: 0,\n borderWidth: 0,\n backgroundColor: 'transparent',\n color: 'inherit',\n font: 'inherit',\n borderRadius: '_full',\n cursor: { default: 'pointer', ':disabled': 'default' },\n },\n body: { display: 'grid', gap: '2px', minWidth: 0 },\n label: { color: '_text', overflowWrap: 'anywhere' },\n labelPending: { color: '_textMuted' },\n labelSkipped: { color: '_textMuted', textDecorationLine: 'line-through' },\n description: { fontSize: '_sm', color: '_textMuted' },\n meta: {\n display: 'flex',\n flexWrap: 'wrap',\n alignItems: 'center',\n columnGap: '_sm',\n rowGap: '2px',\n fontSize: '_sm',\n color: '_textMuted',\n },\n late: { color: '_dangerText' },\n action: { display: 'flex', alignItems: 'center', gap: '_xs' },\n inline: {\n display: 'flex',\n flexWrap: 'wrap',\n columnGap: '_lg',\n rowGap: '_xs',\n margin: 0,\n padding: 0,\n listStyle: 'none',\n },\n inlineItem: { display: 'inline-flex', alignItems: 'center', gap: '_xs', whiteSpace: 'nowrap' },\n icons: { display: 'inline-flex', alignItems: 'center', gap: '2px' },\n});\n\n/** 各状态的图标与颜色 */\nexport const checklistStatusStyles = css.create({\n done: { color: '_successText' },\n todo: { color: '_textMuted' },\n overdue: { color: '_dangerText' },\n blocked: { color: '_warningText' },\n skipped: { color: '_textMuted' },\n});\n\nconst statusIcons: Record<keyof ChecklistStatuses, IconData> = {\n done: CircleCheck,\n todo: CircleDashed,\n overdue: CircleAlert,\n blocked: Ban,\n skipped: CircleMinus,\n};\n\nconst keyOf = (item: ChecklistItem, index: number) =>\n item.key ?? (typeof item.label === 'string' ? item.label : String(index));\n\nconst statusOf = (item: ChecklistItem): keyof ChecklistStatuses => item.status ?? 'todo';\n\n/** 完成清单 */\nexport const Checklist = _component(\n ({\n items,\n layout = 'list',\n summary = false,\n size,\n onCheckedChange,\n propsItem,\n ...rest\n }: ChecklistProps) => {\n const lang = useLang();\n const id = useId();\n\n const statusIcon = (item: ChecklistItem) => {\n const status = statusOf(item);\n return <Icon icon={statusIcons[status]} sx={checklistStatusStyles[status]} />;\n };\n\n /** 状态:可以勾选时是复选按钮(名称是这一项的名称),否则是图标加给读屏的状态文字 */\n const status = (item: ChecklistItem, labelId: string) => {\n const state = statusOf(item);\n if (!onCheckedChange)\n return (\n <span {...css.attrs(checklistStyles.status)}>\n {statusIcon(item)}\n <VisuallyHidden>{lang.checklistStatus[state]}</VisuallyHidden>\n </span>\n );\n const done = state === 'done';\n return (\n <span {...css.attrs(checklistStyles.status)}>\n <button\n type=\"button\"\n role=\"checkbox\"\n aria-checked={done}\n aria-labelledby={labelId}\n aria-describedby={`${labelId}-status`}\n disabled={state === 'skipped' || state === 'blocked'}\n onClick={() => onCheckedChange(item, !done)}\n {...css.attrs(checklistStyles.toggle, focusRingStyles.visible)}\n >\n {statusIcon(item)}\n </button>\n <VisuallyHidden id={`${labelId}-status`}>{lang.checklistStatus[state]}</VisuallyHidden>\n </span>\n );\n };\n\n const labelSx = (item: ChecklistItem) => {\n const state = statusOf(item);\n return [\n checklistStyles.label,\n state === 'skipped'\n ? checklistStyles.labelSkipped\n : state !== 'done' && checklistStyles.labelPending,\n ];\n };\n\n /** 读屏念的整组:名称与状态(icons 排法) */\n const iconsLabel = () =>\n lang.checklistIconsLabel(\n items.map((item) =>\n lang.checklistItemState(\n typeof item.label === 'string' ? item.label : '',\n lang.checklistStatus[statusOf(item)],\n ),\n ),\n );\n\n const counted = () => items.filter((item) => statusOf(item) !== 'skipped');\n const doneCount = () => counted().filter((item) => statusOf(item) === 'done').length;\n\n return () => {\n if (layout === 'icons') {\n const label = iconsLabel();\n return (\n <span\n {...mergeAttrs<'span'>(\n { sx: [checklistStyles.icons, size && fontSizeStyles[size]] },\n rest as PartProps<'span'>,\n { role: 'img', 'aria-label': label, title: label },\n )}\n >\n {items.map((item, index) => (\n <span key={keyOf(item, index)}>{statusIcon(item)}</span>\n ))}\n </span>\n );\n }\n\n if (layout === 'inline')\n return (\n <ul\n {...mergeAttrs<'ul'>(\n { sx: [checklistStyles.inline, size && fontSizeStyles[size]] },\n rest as PartProps<'ul'>,\n )}\n >\n {items.map((item, index) => {\n const labelId = `${id}-${index}`;\n return (\n <li key={keyOf(item, index)} {...css.attrs(checklistStyles.inlineItem)}>\n {status(item, labelId)}\n <span id={labelId} {...css.attrs(labelSx(item))}>\n {item.label}\n </span>\n {hasContent(item.date) && (\n <span\n {...css.attrs(\n checklistStyles.meta,\n statusOf(item) === 'overdue' && checklistStyles.late,\n )}\n >\n {item.date}\n </span>\n )}\n {item.evidence}\n </li>\n );\n })}\n </ul>\n );\n\n const total = counted().length;\n return (\n <div\n {...mergeAttrs<'div'>({ sx: [checklistStyles.root, size && fontSizeStyles[size]] }, rest)}\n >\n {summary && total > 0 && (\n <div {...css.attrs(checklistStyles.summary)}>\n <span>{lang.checklistSummary(doneCount(), total)}</span>\n <Progress\n value={doneCount()}\n max={total}\n size=\"xs\"\n tone={doneCount() === total ? 'success' : 'accent'}\n aria-label={lang.checklistSummary(doneCount(), total)}\n />\n </div>\n )}\n <ul {...css.attrs(checklistStyles.list)}>\n {items.map((item, index) => {\n const own = typeof propsItem === 'function' ? propsItem(item) : propsItem;\n const labelId = `${id}-${index}`;\n const meta = [item.owner, item.date, item.evidence].some(hasContent);\n return (\n <li\n key={keyOf(item, index)}\n {...mergeAttrs<'li'>({ sx: checklistStyles.item }, own)}\n data-status={statusOf(item)}\n >\n {status(item, labelId)}\n <div {...css.attrs(checklistStyles.body)}>\n <span id={labelId} {...css.attrs(labelSx(item))}>\n {item.label}\n </span>\n {hasContent(item.description) && (\n <span {...css.attrs(checklistStyles.description)}>{item.description}</span>\n )}\n {meta && (\n <span {...css.attrs(checklistStyles.meta)}>\n {hasContent(item.owner) && <span>{item.owner}</span>}\n {hasContent(item.date) && (\n <span\n {...css.attrs(statusOf(item) === 'overdue' && checklistStyles.late)}\n >\n {item.date}\n </span>\n )}\n {item.evidence}\n </span>\n )}\n </div>\n {hasContent(item.action) ? (\n <div {...css.attrs(checklistStyles.action)}>{item.action}</div>\n ) : (\n <span />\n )}\n </li>\n );\n })}\n </ul>\n </div>\n );\n };\n },\n);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Checklist.js","names":["_vtp_readonlyProp","_vtp_restProps","_createVNode","_mergeProps"],"sources":["../../src/display/Checklist.tsx"],"sourcesContent":["/**\n * 完成清单:一组要做完的事,每项的状态(完成、待办、超时、不适用、受阻)与它的上下文——怎么做、谁负责、哪天、证据、去处理。\n * 里程碑与交付证据、结项材料、上线检查、新用户的引导任务。参考 Shopify Polaris 的 Setup guide、GitHub 的任务列表。\n *\n * <Checklist\n * title=\"结项材料\"\n * summary\n * items={[\n * { label: '验收报告', status: 'done', date: '10-12', evidence: <Link href={url}>验证报告.pdf</Link> },\n * { label: '财务复核意见', status: 'overdue', owner: '财务部', date: '计划 10-20', action: <Button>催办</Button> },\n * { label: '合同签订记录', description: '谈定价格与生效日期,采购经理签字', owner: '采购部' },\n * ]}\n * />\n * <Checklist layout=\"inline\" items={…} /> 一行排开、放不下换行(详情行、卡片的一角)\n * <Checklist layout=\"icons\" aria-label=\"里程碑\" items={…} /> 只有一排状态图标(表格的格子里),读屏念每一项\n * <Checklist items={tasks} onCheckedChange={(item, done) => save(item.key, done)} /> 可以勾选(上线检查、引导任务)\n *\n * summary 在上面显示“已完成 2 / 3”与进度条(不适用的不算)。没完成的名称是次要色,不适用的划掉;\n * 超时的日期用危险色。每项的状态写在给读屏的文字里。\n *\n * 部件:Root(div)、Summary(汇总行)、List(ul)、Item(li,可以按项计算)、Status(状态图标,可勾选时是按钮)、Label、Meta。\n */\nimport { Ban, CircleAlert, CircleCheck, CircleDashed, CircleMinus } from '@lucide/icons';\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport * as css from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport { useId, type VNodeChild } from 'vue';\nimport { Icon, type IconData } from '../base/Icon';\nimport { VisuallyHidden } from '../base/VisuallyHidden';\nimport { useLang } from '../config/context';\nimport { Progress } from '../feedback/Progress';\nimport { hasContent, mergeAttrs, type PartProps } from '../utils/props';\nimport { focusRingStyles } from '../utils/style/styles';\nimport { fontSizeStyles, type TextSize } from '../utils/style/typography';\n\n/** 一项的状态 */\nexport interface ChecklistStatuses {\n /** 完成 */\n done: unknown;\n /** 待办(默认) */\n todo: unknown;\n /** 超时:过了计划日期还没完成 */\n overdue: unknown;\n /** 受阻:等别人、缺前提 */\n blocked: unknown;\n /** 不适用:这一次不用做,不算进进度 */\n skipped: unknown;\n}\n\n/** 清单的一项 */\nexport interface ChecklistItem {\n /** 唯一的 key;不写时用 label(label 是文字时)或位置 */\n key?: string;\n /** 名称 */\n label: VNodeChild;\n /** 状态,默认 todo */\n status?: keyof ChecklistStatuses;\n /** 说明:怎么做、要什么材料、缺的由谁出具 */\n description?: VNodeChild;\n /** 负责人(谁处理、谁出具) */\n owner?: VNodeChild;\n /** 日期:完成的日期,或计划日期(超时时用危险色) */\n date?: VNodeChild;\n /** 证据:附件、报告的链接 */\n evidence?: VNodeChild;\n /** 操作:上传证据、去处理、催办(结束边) */\n action?: VNodeChild;\n}\n\n/** 清单的排法 */\nexport interface ChecklistLayouts {\n /** 一项一行(默认):状态、名称与说明、负责人 · 日期 · 证据,操作在结束边 */\n list: unknown;\n /** 一行排开、放不下换行:状态、名称、日期、证据 */\n inline: unknown;\n /** 只有一排状态图标:表格的格子里;悬停与读屏有每一项的说明 */\n icons: unknown;\n}\n\n/** 未声明的属性落到外层元素上(list 是 div,inline 是 ul,icons 是 span) */\nexport interface ChecklistProps extends PartProps<'div'> {\n /** 各项 */\n items: readonly ChecklistItem[];\n /**\n * 排法\n * @default 'list'\n */\n layout?: keyof ChecklistLayouts;\n /** 上面显示“已完成 2 / 3”与进度条(list 排法) */\n summary?: boolean;\n /** 字号档位;不写时继承 */\n size?: TextSize;\n /** 可以勾选:状态图标变成复选按钮,点了报告新的完成状态(不适用、受阻的不能勾) */\n onCheckedChange?: (item: ChecklistItem, done: boolean) => void;\n /** 每一项(li)的部件属性,可以按项计算 */\n propsItem?: PartProps<'li'> | ((item: ChecklistItem) => PartProps<'li'>);\n}\n\n/** Checklist 的样式 */\nexport const checklistStyles = css.create({\n root: { display: 'grid', gap: '_sm' },\n summary: {\n display: 'grid',\n gridTemplateColumns: 'auto 1fr',\n alignItems: 'center',\n gap: '_md',\n fontSize: '_sm',\n color: '_textMuted',\n },\n list: { display: 'grid', margin: 0, padding: 0, listStyle: 'none' },\n // 状态一列、内容一列、操作一列;状态贴着名称那一行\n item: {\n display: 'grid',\n gridTemplateColumns: 'auto minmax(0, 1fr) auto',\n columnGap: '_sm',\n alignItems: 'start',\n paddingBlock: '_xs',\n },\n status: {\n display: 'flex',\n alignItems: 'center',\n minHeight: '1lh',\n fontSize: '1.125em',\n },\n // 可以勾选时的按钮:只有图标,去掉按钮的外观\n toggle: {\n display: 'inline-flex',\n padding: 0,\n margin: 0,\n borderWidth: 0,\n backgroundColor: 'transparent',\n color: 'inherit',\n font: 'inherit',\n borderRadius: '_full',\n cursor: { default: 'pointer', ':disabled': 'default' },\n },\n body: { display: 'grid', gap: '2px', minWidth: 0 },\n label: { color: '_text', overflowWrap: 'anywhere' },\n labelPending: { color: '_textMuted' },\n labelSkipped: { color: '_textMuted', textDecorationLine: 'line-through' },\n description: { fontSize: '_sm', color: '_textMuted' },\n meta: {\n display: 'flex',\n flexWrap: 'wrap',\n alignItems: 'center',\n columnGap: '_sm',\n rowGap: '2px',\n fontSize: '_sm',\n color: '_textMuted',\n },\n late: { color: '_dangerText' },\n action: { display: 'flex', alignItems: 'center', gap: '_xs' },\n inline: {\n display: 'flex',\n flexWrap: 'wrap',\n columnGap: '_lg',\n rowGap: '_xs',\n margin: 0,\n padding: 0,\n listStyle: 'none',\n },\n inlineItem: { display: 'inline-flex', alignItems: 'center', gap: '_xs', whiteSpace: 'nowrap' },\n icons: { display: 'inline-flex', alignItems: 'center', gap: '2px' },\n});\n\n/** 各状态的图标与颜色 */\nexport const checklistStatusStyles = css.create({\n done: { color: '_successText' },\n todo: { color: '_textMuted' },\n overdue: { color: '_dangerText' },\n blocked: { color: '_warningText' },\n skipped: { color: '_textMuted' },\n});\n\nconst statusIcons: Record<keyof ChecklistStatuses, IconData> = {\n done: CircleCheck,\n todo: CircleDashed,\n overdue: CircleAlert,\n blocked: Ban,\n skipped: CircleMinus,\n};\n\nconst keyOf = (item: ChecklistItem, index: number) =>\n item.key ?? (typeof item.label === 'string' ? item.label : String(index));\n\nconst statusOf = (item: ChecklistItem): keyof ChecklistStatuses => item.status ?? 'todo';\n\n/** 完成清单 */\nexport const Checklist = _component(\n ({\n items,\n layout = 'list',\n summary = false,\n size,\n onCheckedChange,\n propsItem,\n ...rest\n }: ChecklistProps) => {\n const lang = useLang();\n const id = useId();\n\n const statusIcon = (item: ChecklistItem) => {\n const status = statusOf(item);\n return <Icon icon={statusIcons[status]} sx={checklistStatusStyles[status]} />;\n };\n\n /** 状态:可以勾选时是复选按钮(名称是这一项的名称),否则是图标加给读屏的状态文字 */\n const status = (item: ChecklistItem, labelId: string) => {\n const state = statusOf(item);\n if (!onCheckedChange)\n return (\n <span {...css.attrs(checklistStyles.status)}>\n {statusIcon(item)}\n <VisuallyHidden>{lang.checklistStatus[state]}</VisuallyHidden>\n </span>\n );\n const done = state === 'done';\n return (\n <span {...css.attrs(checklistStyles.status)}>\n <button\n type=\"button\"\n role=\"checkbox\"\n aria-checked={done}\n aria-labelledby={labelId}\n aria-describedby={`${labelId}-status`}\n disabled={state === 'skipped' || state === 'blocked'}\n onClick={() => onCheckedChange(item, !done)}\n {...css.attrs(checklistStyles.toggle, focusRingStyles.visible)}\n >\n {statusIcon(item)}\n </button>\n <VisuallyHidden id={`${labelId}-status`}>{lang.checklistStatus[state]}</VisuallyHidden>\n </span>\n );\n };\n\n const labelSx = (item: ChecklistItem) => {\n const state = statusOf(item);\n return [\n checklistStyles.label,\n state === 'skipped'\n ? checklistStyles.labelSkipped\n : state !== 'done' && checklistStyles.labelPending,\n ];\n };\n\n /** 读屏念的整组:名称与状态(icons 排法) */\n const iconsLabel = () =>\n lang.checklistIconsLabel(\n items.map((item) =>\n lang.checklistItemState(\n typeof item.label === 'string' ? item.label : '',\n lang.checklistStatus[statusOf(item)],\n ),\n ),\n );\n\n const counted = () => items.filter((item) => statusOf(item) !== 'skipped');\n const doneCount = () => counted().filter((item) => statusOf(item) === 'done').length;\n\n return () => {\n if (layout === 'icons') {\n const label = iconsLabel();\n return (\n <span\n {...mergeAttrs<'span'>(\n { sx: [checklistStyles.icons, size && fontSizeStyles[size]] },\n rest as PartProps<'span'>,\n { role: 'img', 'aria-label': label, title: label },\n )}\n >\n {items.map((item, index) => (\n <span key={keyOf(item, index)}>{statusIcon(item)}</span>\n ))}\n </span>\n );\n }\n\n if (layout === 'inline')\n return (\n <ul\n {...mergeAttrs<'ul'>(\n { sx: [checklistStyles.inline, size && fontSizeStyles[size]] },\n rest as PartProps<'ul'>,\n )}\n >\n {items.map((item, index) => {\n const labelId = `${id}-${index}`;\n return (\n <li key={keyOf(item, index)} {...css.attrs(checklistStyles.inlineItem)}>\n {status(item, labelId)}\n <span id={labelId} {...css.attrs(labelSx(item))}>\n {item.label}\n </span>\n {hasContent(item.date) && (\n <span\n {...css.attrs(\n checklistStyles.meta,\n statusOf(item) === 'overdue' && checklistStyles.late,\n )}\n >\n {item.date}\n </span>\n )}\n {item.evidence}\n </li>\n );\n })}\n </ul>\n );\n\n const total = counted().length;\n return (\n <div\n {...mergeAttrs<'div'>({ sx: [checklistStyles.root, size && fontSizeStyles[size]] }, rest)}\n >\n {summary && total > 0 && (\n <div {...css.attrs(checklistStyles.summary)}>\n <span>{lang.checklistSummary(doneCount(), total)}</span>\n <Progress\n value={doneCount()}\n max={total}\n size=\"xs\"\n tone={doneCount() === total ? 'success' : 'accent'}\n aria-label={lang.checklistSummary(doneCount(), total)}\n />\n </div>\n )}\n <ul {...css.attrs(checklistStyles.list)}>\n {items.map((item, index) => {\n const own = typeof propsItem === 'function' ? propsItem(item) : propsItem;\n const labelId = `${id}-${index}`;\n const meta = [item.owner, item.date, item.evidence].some(hasContent);\n return (\n <li\n key={keyOf(item, index)}\n {...mergeAttrs<'li'>({ sx: checklistStyles.item }, own)}\n data-status={statusOf(item)}\n >\n {status(item, labelId)}\n <div {...css.attrs(checklistStyles.body)}>\n <span id={labelId} {...css.attrs(labelSx(item))}>\n {item.label}\n </span>\n {hasContent(item.description) && (\n <span {...css.attrs(checklistStyles.description)}>{item.description}</span>\n )}\n {meta && (\n <span {...css.attrs(checklistStyles.meta)}>\n {hasContent(item.owner) && <span>{item.owner}</span>}\n {hasContent(item.date) && (\n <span\n {...css.attrs(statusOf(item) === 'overdue' && checklistStyles.late)}\n >\n {item.date}\n </span>\n )}\n {item.evidence}\n </span>\n )}\n </div>\n {hasContent(item.action) ? (\n <div {...css.attrs(checklistStyles.action)}>{item.action}</div>\n ) : (\n <span />\n )}\n </li>\n );\n })}\n </ul>\n </div>\n );\n };\n },\n);\n"],"mappings":"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"}
|
package/dist/display/Collapse.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { t } from "../config/theme/tokens.js";
|
|
2
2
|
import { mergeAttrs } from "../utils/props.js";
|
|
3
3
|
import { afterTransitions } from "../utils/style/transitions.js";
|
|
4
|
+
import { useMounted } from "../utils/dom/mounted.js";
|
|
4
5
|
import { useLazyContent } from "../utils/motion/presence.js";
|
|
5
|
-
import { createVNode,
|
|
6
|
-
import { _component, bindable, childrenProp, effect, readonlyProp, restProps, untrack } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
6
|
+
import { createVNode, ref, shallowRef } from "vue";
|
|
7
|
+
import { _component, bindable, childrenProp, effect, mergeJsxProps, readonlyProp, restProps, untrack } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
7
8
|
import * as css from "@zerodep-dev/vue-tsx-pro-tsx/styles";
|
|
8
9
|
//#region src/display/Collapse.tsx
|
|
9
10
|
/** 未声明的属性落到外层的 div 上;要分区语义时在外面包 section */
|
|
@@ -31,6 +32,8 @@ var Collapse = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
31
32
|
let root = shallowRef(null);
|
|
32
33
|
let collapsed = ref(untrack(() => !open.value));
|
|
33
34
|
const showContent = useLazyContent(() => open.value, () => lazy.value);
|
|
35
|
+
const mounted = useMounted();
|
|
36
|
+
const untilFound = () => supportsUntilFound && mounted.value;
|
|
34
37
|
effect(() => {
|
|
35
38
|
if (open.value) {
|
|
36
39
|
collapsed.value = false;
|
|
@@ -43,11 +46,11 @@ var Collapse = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
43
46
|
});
|
|
44
47
|
return () => cancelled = true;
|
|
45
48
|
});
|
|
46
|
-
return () => createVNode("div",
|
|
49
|
+
return () => createVNode("div", mergeJsxProps(mergeAttrs({ sx: collapseStyles.root }, rest, {
|
|
47
50
|
sx: open.value && collapseStyles.open,
|
|
48
51
|
ref: root,
|
|
49
|
-
hidden: collapsed.value &&
|
|
50
|
-
inert: collapsed.value && !
|
|
52
|
+
hidden: collapsed.value && untilFound() ? "until-found" : void 0,
|
|
53
|
+
inert: collapsed.value && !untilFound() ? true : void 0,
|
|
51
54
|
onBeforematch: () => open.value = true
|
|
52
55
|
}), { "data-state": open.value ? "open" : "closed" }), [createVNode("div", mergeAttrs({ sx: collapseStyles.content }, propsContent.value), [showContent() && children.value])]);
|
|
53
56
|
}, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Collapse.js","names":["_vtp_bindable","_vtp_childrenProp","_vtp_readonlyProp","_vtp_restProps","_vue_shallowRef","_vue_ref","_vtp_untrack","_createVNode","_mergeProps"],"sources":["../../src/display/Collapse.tsx"],"sourcesContent":["/**\n * 展开收起的内容区,带高度过渡。触发器另外写,或者用 Collapsible。\n *\n * <Collapse _open={open}>内容</Collapse>\n *\n * 高度过渡用 grid-template-rows 0fr ↔ 1fr,各浏览器都能动画(interpolate-size 只有 Chromium)。\n * 收起并且过渡结束后:\n * - 支持 hidden=\"until-found\" 的浏览器加上它:页内搜索、锚点命中时浏览器自动展开,这里把 open 写回\n * - 不支持的(Safari)加 inert:收起的内容不能被 Tab 聚焦,读屏也读不到\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 { useLazyContent } from '../utils/motion/presence';\nimport { mergeAttrs, type PartProps } from '../utils/props';\nimport { afterTransitions } from '../utils/style/transitions';\nimport { t } from '../config/theme/tokens';\n\n/** 未声明的属性落到外层的 div 上;要分区语义时在外面包 section */\nexport interface CollapseProps extends PartProps<'div', 'hidden' | 'inert'> {\n /** 是否展开:父级写 _open={x};页内搜索命中收起的内容时自动展开并写回 */\n _open?: boolean;\n children?: VNodeChild;\n /** 内容层:外层负责高度过渡,内容层裁掉溢出 */\n propsContent?: PartProps<'div'>;\n /**\n * 第一次展开时才渲染内容,之后收起也保留(内容很重、多数人不会展开时用)。\n * 代价:还没展开过的内容页内搜索找不到\n */\n lazy?: boolean;\n}\n\nconst supportsUntilFound =\n typeof HTMLElement !== 'undefined' && 'onbeforematch' in HTMLElement.prototype;\n\n/** Collapse 的样式:root、open、content */\nexport const collapseStyles = css.create({\n root: {\n display: 'grid',\n gridTemplateRows: '0fr',\n transition: `grid-template-rows ${t.durations.md} ${t.easings.standard}`,\n },\n open: { gridTemplateRows: '1fr' },\n // 网格项默认的最小高度是内容高度,要设为 0 才能收到 0fr\n content: { minHeight: 0, overflow: 'hidden' },\n});\n\n/** 展开收起的内容区(div),带高度过渡;收起后可被页内搜索找到并自动展开 */\nexport const Collapse = _component(\n ({\n _open: open = _bindable(false),\n children,\n propsContent,\n lazy = false,\n ...rest\n }: CollapseProps) => {\n let root = _state.raw<HTMLElement | null>(null);\n // 完全收起(过渡已结束):这时才隐藏内容,收起的过程中内容仍要可见\n let collapsed = _state(_untrack(!open));\n const showContent = useLazyContent(\n () => open,\n () => lazy,\n );\n\n _effect(() => {\n if (open) {\n collapsed = false;\n return;\n }\n // 一开始就是收起的:没有过渡要等。不然每个收起的 Collapse 挂载时都要对整棵内容 getAnimations,\n // 一页十来个收起的源码就是十来次样式计算\n if (_untrack(collapsed)) return;\n let cancelled = false;\n void afterTransitions(root!).then(() => {\n if (!cancelled) collapsed = true;\n });\n return () => (cancelled = true);\n });\n\n return () => (\n <div\n {...mergeAttrs<'div'>({ sx: collapseStyles.root }, rest, {\n sx: open && collapseStyles.open,\n ref: _toRef(root),\n hidden: collapsed &&
|
|
1
|
+
{"version":3,"file":"Collapse.js","names":["_vtp_bindable","_vtp_childrenProp","_vtp_readonlyProp","_vtp_restProps","_vue_shallowRef","_vue_ref","_vtp_untrack","_createVNode","_mergeProps"],"sources":["../../src/display/Collapse.tsx"],"sourcesContent":["/**\n * 展开收起的内容区,带高度过渡。触发器另外写,或者用 Collapsible。\n *\n * <Collapse _open={open}>内容</Collapse>\n *\n * 高度过渡用 grid-template-rows 0fr ↔ 1fr,各浏览器都能动画(interpolate-size 只有 Chromium)。\n * 收起并且过渡结束后:\n * - 支持 hidden=\"until-found\" 的浏览器加上它:页内搜索、锚点命中时浏览器自动展开,这里把 open 写回\n * - 不支持的(Safari)加 inert:收起的内容不能被 Tab 聚焦,读屏也读不到\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 { useLazyContent } from '../utils/motion/presence';\nimport { useMounted } from '../utils/dom/mounted';\nimport { mergeAttrs, type PartProps } from '../utils/props';\nimport { afterTransitions } from '../utils/style/transitions';\nimport { t } from '../config/theme/tokens';\n\n/** 未声明的属性落到外层的 div 上;要分区语义时在外面包 section */\nexport interface CollapseProps extends PartProps<'div', 'hidden' | 'inert'> {\n /** 是否展开:父级写 _open={x};页内搜索命中收起的内容时自动展开并写回 */\n _open?: boolean;\n children?: VNodeChild;\n /** 内容层:外层负责高度过渡,内容层裁掉溢出 */\n propsContent?: PartProps<'div'>;\n /**\n * 第一次展开时才渲染内容,之后收起也保留(内容很重、多数人不会展开时用)。\n * 代价:还没展开过的内容页内搜索找不到\n */\n lazy?: boolean;\n}\n\nconst supportsUntilFound =\n typeof HTMLElement !== 'undefined' && 'onbeforematch' in HTMLElement.prototype;\n\n/** Collapse 的样式:root、open、content */\nexport const collapseStyles = css.create({\n root: {\n display: 'grid',\n gridTemplateRows: '0fr',\n transition: `grid-template-rows ${t.durations.md} ${t.easings.standard}`,\n },\n open: { gridTemplateRows: '1fr' },\n // 网格项默认的最小高度是内容高度,要设为 0 才能收到 0fr\n content: { minHeight: 0, overflow: 'hidden' },\n});\n\n/** 展开收起的内容区(div),带高度过渡;收起后可被页内搜索找到并自动展开 */\nexport const Collapse = _component(\n ({\n _open: open = _bindable(false),\n children,\n propsContent,\n lazy = false,\n ...rest\n }: CollapseProps) => {\n let root = _state.raw<HTMLElement | null>(null);\n // 完全收起(过渡已结束):这时才隐藏内容,收起的过程中内容仍要可见\n let collapsed = _state(_untrack(!open));\n const showContent = useLazyContent(\n () => open,\n () => lazy,\n );\n\n // 服务端不知道浏览器支不支持 until-found:挂载前按不支持渲染(与服务端的 HTML 一致),挂载后再换\n const mounted = useMounted();\n const untilFound = () => supportsUntilFound && mounted.value;\n\n _effect(() => {\n if (open) {\n collapsed = false;\n return;\n }\n // 一开始就是收起的:没有过渡要等。不然每个收起的 Collapse 挂载时都要对整棵内容 getAnimations,\n // 一页十来个收起的源码就是十来次样式计算\n if (_untrack(collapsed)) return;\n let cancelled = false;\n void afterTransitions(root!).then(() => {\n if (!cancelled) collapsed = true;\n });\n return () => (cancelled = true);\n });\n\n return () => (\n <div\n {...mergeAttrs<'div'>({ sx: collapseStyles.root }, rest, {\n sx: open && collapseStyles.open,\n ref: _toRef(root),\n hidden: collapsed && untilFound() ? 'until-found' : undefined,\n inert: collapsed && !untilFound() ? true : undefined,\n onBeforematch: () => (open = true),\n })}\n data-state={open ? 'open' : 'closed'}\n >\n <div {...mergeAttrs<'div'>({ sx: collapseStyles.content }, propsContent)}>\n {showContent() && children}\n </div>\n </div>\n );\n },\n);\n"],"mappings":";;;;;;;;;;AAiCA,IAAM,qBACJ,OAAO,gBAAgB,eAAe,mBAAmB,YAAY;;AAGvE,IAAa,iBAAiB,IAAI,OAAO;CACvC,MAAM;EACJ,SAAS;EACT,kBAAkB;EAClB,YAAY,sBAAsB,EAAE,UAAU,GAAE,GAAI,EAAE,QAAQ;CAChE;CACA,MAAM,EAAE,kBAAkB,MAAM;CAEhC,SAAS;EAAE,WAAW;EAAG,UAAU;CAAS;AAC9C,CAAC;;AAGD,IAAa,WAAW,0BACtB,QAAA,SAMqB;CAAA,MAAA,OAAAA,SAAA,QAAA,eALK,OAAK,KAAA,IAAA;CAAA,MAAA,WAAAC,aAAA,MAAA;CAAA,MAAA,eAAAC,aAAA,QAAA,cAAA;CAAA,MAAA,OAAAA,aAAA,QAAA,QAGtB,KAAK;CAAA,MACT,OAAIC,UAAA,QAAA,MAAA,KAAA;CAEP,IAAI,OAAIC,WAAkC,IAAI;CAE9C,IAAI,YAASC,IAAAC,cAAmB,CAAA,KAAA,KAAK,CAAA;CACrC,MAAM,cAAc,qBAClB,KAAA,aACA,KAAA,KACF;CAGA,MAAM,UAAU,WAAW;CAC3B,MAAM,mBAAmB,sBAAsB,QAAQ;CAEvD,aAAc;EACZ,IAAA,KAAA,OAAU;GACR,UAAA,QAAY;GACZ;EACF;EAGA,IAAAA,cAAA,UAAA,KAAA,GAAyB;EACzB,IAAI,YAAY;EAChB,iBAAqB,KAAA,KAAM,CAAC,CAAC,WAAW;GACtC,IAAI,CAAC,WAAW,UAAA,QAAY;EAC9B,CAAC;EACD,aAAc,YAAY;CAC5B,CAAC;CAED,aAAOC,YAAA,OAAAC,cAEC,WAAkB,EAAE,IAAI,eAAe,KAAK,GAAG,MAAM;EACvD,IAAI,KAAA,SAAQ,eAAe;EAC3B,KAAG;EACH,QAAQ,UAAA,SAAa,WAAW,IAAI,gBAAgB,KAAA;EACpD,OAAO,UAAA,SAAa,CAAC,WAAW,IAAI,OAAO,KAAA;EAC3C,qBAAsB,KAAA,QAAO;CAC/B,CAAC,GAAC,EAAA,cACU,KAAA,QAAO,SAAS,SAAQ,CAAA,GAAA,CAAAD,YAAA,OAE3B,WAAkB,EAAE,IAAI,eAAe,QAAQ,GAAC,aAAA,KAAc,GAAC,CACrE,YAAY,KAAC,SAAA,KAAY,CAAA,CAAA,CAAA;AAIlC,GAAC;CAAA,MAAA;CAAA,OAAA;EAAA;EAAA;EAAA;EAAA;CAAA;AAAA,CACH"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Collapse } from "./Collapse.js";
|
|
2
|
-
import { Fragment, computed, createVNode,
|
|
3
|
-
import { _component, bindable, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
2
|
+
import { Fragment, computed, createVNode, useId } from "vue";
|
|
3
|
+
import { _component, bindable, mergeJsxProps, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
4
4
|
//#region src/display/Collapsible.tsx
|
|
5
5
|
/** 未声明的属性落到 Collapse 上 */
|
|
6
6
|
/** 触发器 + 展开收起的内容区(Disclosure 模式),触发器自动带上 aria-expanded 与 aria-controls */
|
|
@@ -20,7 +20,7 @@ var Collapsible = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
20
20
|
"aria-expanded": open.value,
|
|
21
21
|
"aria-controls": contentId.value,
|
|
22
22
|
disabled: disabled.value || void 0
|
|
23
|
-
}), createVNode(Collapse,
|
|
23
|
+
}), createVNode(Collapse, mergeJsxProps(rest, {
|
|
24
24
|
"id": contentId.value,
|
|
25
25
|
"_open": open.value,
|
|
26
26
|
"onUpdate:_open": _cache[0] || (_cache[0] = ($event) => open.value = $event)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Collapsible.js","names":["_vtp_bindable","_vtp_readonlyProp","_vtp_restProps","_vue_computed","_createVNode","_Fragment","_mergeProps"],"sources":["../../src/display/Collapsible.tsx"],"sourcesContent":["/**\n * 可展开收起的区域:触发器 + Collapse(WAI-ARIA 的 Disclosure 模式)。Accordion 的每一项也建在 Collapse 上。\n *\n * <Collapsible trigger={(props) => <Button {...props}>高级选项</Button>}>…</Collapsible>\n *\n * 触发器用渲染函数而不是原生 <summary>:<summary> 必须是 <details> 的第一个子元素,触发器不能放在别处。\n * 键盘操作由触发器元素自己提供(按钮的 Enter、空格)。\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport { useId, type VNodeChild } from 'vue';\nimport type { TriggerProps } from '../utils/props';\nimport { Collapse, type CollapseProps } from './Collapse';\n\n/** 未声明的属性落到 Collapse 上 */\nexport interface CollapsibleProps extends CollapseProps {\n /** 触发器:`(props) => <Button {...props}>更多</Button>`,props 带 aria-expanded 与 aria-controls */\n trigger: (props: TriggerProps) => VNodeChild;\n /** 不可用:触发器带上 disabled,点击不切换(open 仍可由父级控制) */\n disabled?: boolean;\n}\n\n/** 触发器 + 展开收起的内容区(Disclosure 模式),触发器自动带上 aria-expanded 与 aria-controls */\nexport const Collapsible = _component(\n ({\n _open: open = _bindable(false),\n trigger,\n disabled = false,\n id,\n ...rest\n }: CollapsibleProps) => {\n const generatedId = useId();\n const contentId = _derived(id ?? generatedId);\n\n return () => (\n <>\n {trigger({\n onClick: () => {\n if (!disabled) open = !open;\n },\n 'aria-expanded': open,\n 'aria-controls': contentId,\n disabled: disabled || undefined,\n })}\n <Collapse {...rest} id={contentId} _open={open} />\n </>\n );\n },\n);\n"],"mappings":";;;;;;AAsBA,IAAa,cAAc,0BACzB,QAAA,SAMwB;CAAA,MAAA,SAAA,CAAA;CAAA,MAAA,OAAAA,SAAA,QAAA,eALE,OAAK,KAAA,IAAA;CAAA,MAAA,UAAAC,aAAA,QAAA,SAAA;CAAA,MAAA,WAAAA,aAAA,QAAA,YAElB,KAAK;CAAA,MAAA,KAAAA,aAAA,QAAA,IAAA;CAAA,MAEb,OAAIC,UAAA,QAAA,MAAA,IAAA;CAEP,MAAM,cAAc,MAAM;CAC1B,MAAM,YAASC,eAAY,GAAA,SAAM,WAAW;CAE5C,aAAOC,YAAAC,UAAA,MAAA,CAEF,QAAA,MAAQ;EACP,eAAe;GACb,IAAI,CAAA,SAAA,OAAW,KAAA,QAAO,CAAA,KAAA;EACxB;EACA,iBAAe,KAAA;EACf,iBAAe,UAAA;EACf,UAAU,SAAA,SAAY,KAAA;CACxB,CAAC,GAACD,YAAA,UAAAE,
|
|
1
|
+
{"version":3,"file":"Collapsible.js","names":["_vtp_bindable","_vtp_readonlyProp","_vtp_restProps","_vue_computed","_createVNode","_Fragment","_mergeProps"],"sources":["../../src/display/Collapsible.tsx"],"sourcesContent":["/**\n * 可展开收起的区域:触发器 + Collapse(WAI-ARIA 的 Disclosure 模式)。Accordion 的每一项也建在 Collapse 上。\n *\n * <Collapsible trigger={(props) => <Button {...props}>高级选项</Button>}>…</Collapsible>\n *\n * 触发器用渲染函数而不是原生 <summary>:<summary> 必须是 <details> 的第一个子元素,触发器不能放在别处。\n * 键盘操作由触发器元素自己提供(按钮的 Enter、空格)。\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport { useId, type VNodeChild } from 'vue';\nimport type { TriggerProps } from '../utils/props';\nimport { Collapse, type CollapseProps } from './Collapse';\n\n/** 未声明的属性落到 Collapse 上 */\nexport interface CollapsibleProps extends CollapseProps {\n /** 触发器:`(props) => <Button {...props}>更多</Button>`,props 带 aria-expanded 与 aria-controls */\n trigger: (props: TriggerProps) => VNodeChild;\n /** 不可用:触发器带上 disabled,点击不切换(open 仍可由父级控制) */\n disabled?: boolean;\n}\n\n/** 触发器 + 展开收起的内容区(Disclosure 模式),触发器自动带上 aria-expanded 与 aria-controls */\nexport const Collapsible = _component(\n ({\n _open: open = _bindable(false),\n trigger,\n disabled = false,\n id,\n ...rest\n }: CollapsibleProps) => {\n const generatedId = useId();\n const contentId = _derived(id ?? generatedId);\n\n return () => (\n <>\n {trigger({\n onClick: () => {\n if (!disabled) open = !open;\n },\n 'aria-expanded': open,\n 'aria-controls': contentId,\n disabled: disabled || undefined,\n })}\n <Collapse {...rest} id={contentId} _open={open} />\n </>\n );\n },\n);\n"],"mappings":";;;;;;AAsBA,IAAa,cAAc,0BACzB,QAAA,SAMwB;CAAA,MAAA,SAAA,CAAA;CAAA,MAAA,OAAAA,SAAA,QAAA,eALE,OAAK,KAAA,IAAA;CAAA,MAAA,UAAAC,aAAA,QAAA,SAAA;CAAA,MAAA,WAAAA,aAAA,QAAA,YAElB,KAAK;CAAA,MAAA,KAAAA,aAAA,QAAA,IAAA;CAAA,MAEb,OAAIC,UAAA,QAAA,MAAA,IAAA;CAEP,MAAM,cAAc,MAAM;CAC1B,MAAM,YAASC,eAAY,GAAA,SAAM,WAAW;CAE5C,aAAOC,YAAAC,UAAA,MAAA,CAEF,QAAA,MAAQ;EACP,eAAe;GACb,IAAI,CAAA,SAAA,OAAW,KAAA,QAAO,CAAA,KAAA;EACxB;EACA,iBAAe,KAAA;EACf,iBAAe,UAAA;EACf,UAAU,SAAA,SAAY,KAAA;CACxB,CAAC,GAACD,YAAA,UAAAE,cACY,MAAI;EAAA,MAAA,UAAA;EAAA,SAAA,KAAA;EAAA,kBAAA,OAAA,OAAA,OAAA,MAAA,WAAA,KAAA,QAAA;CAAA,CAAA,GAAA,IAAA,CAAA,CAAA;AAGxB,GAAC;CAAA,MAAA;CAAA,OAAA;EAAA;EAAA;EAAA;EAAA;EAAA;CAAA;AAAA,CACH"}
|
|
@@ -6,8 +6,8 @@ import { fontSizeStyles } from "../utils/style/typography.js";
|
|
|
6
6
|
import { useResizeObserver } from "../utils/dom/resize.js";
|
|
7
7
|
import { CopyButton } from "../base/CopyButton.js";
|
|
8
8
|
import { plainText } from "../utils/data/text.js";
|
|
9
|
-
import { createVNode,
|
|
10
|
-
import { _component, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
9
|
+
import { createVNode, ref, shallowRef } from "vue";
|
|
10
|
+
import { _component, 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
|
//#region src/display/Descriptions.tsx
|
|
13
13
|
/** 描述列表里的一项 */
|
|
@@ -138,14 +138,14 @@ var Descriptions = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
138
138
|
const count = () => auto() ? clamp(Math.floor(width.value / minItemWidth.value), 1, maxColumns.value) : columns.value;
|
|
139
139
|
/** 复制的文字:copyText,否则值是文字或数字时就是它 */
|
|
140
140
|
const copyTextOf = (item) => item.copyText ?? (typeof item.value === "string" || typeof item.value === "number" ? String(item.value) : "");
|
|
141
|
-
return () => createVNode("div",
|
|
141
|
+
return () => createVNode("div", mergeJsxProps(mergeAttrs({ sx: [descriptionsStyles.root, size.value && fontSizeStyles[size.value]] }, rest), { "data-layout": layout.value }), [hasContent(title.value) && createVNode("div", mergeAttrs({ sx: descriptionsStyles.title }, propsTitle.value), [title.value]), createVNode("dl", mergeAttrs({ sx: [
|
|
142
142
|
descriptionsStyles.list,
|
|
143
143
|
layout.value === "inline" ? descriptionsStyles.listInline : auto() ? css.bind(descriptionsGridStyles.autoColumns, () => [count()], true) : css.bind(descriptionsGridStyles.columns, () => [count()], true),
|
|
144
144
|
bordered.value && descriptionsStyles.listBordered
|
|
145
145
|
] }, propsList.value, { ref: list }), [items.value.map((item, index) => {
|
|
146
146
|
const own = typeof propsItem.value === "function" ? propsItem.value(item, index) : propsItem.value;
|
|
147
147
|
const span = clamp(item.span ?? 1, 1, count());
|
|
148
|
-
return createVNode("div",
|
|
148
|
+
return createVNode("div", mergeJsxProps({ "key": item.key ?? (typeof item.label === "string" ? item.label : index) }, mergeAttrs({ sx: [
|
|
149
149
|
layout.value === "vertical" ? descriptionsStyles.itemVertical : layout.value === "inline" ? descriptionsStyles.itemInline : descriptionsStyles.itemHorizontal,
|
|
150
150
|
bordered.value && descriptionsStyles.itemBordered,
|
|
151
151
|
descriptionsStyles.item,
|
|
@@ -154,7 +154,7 @@ var Descriptions = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
154
154
|
descriptionsStyles.value,
|
|
155
155
|
bordered.value && descriptionsStyles.cellBordered,
|
|
156
156
|
item.copyable && descriptionsStyles.valueCopyable
|
|
157
|
-
] }, propsValue.value), [item.value, item.copyable && createVNode(CopyButton,
|
|
157
|
+
] }, propsValue.value), [item.value, item.copyable && createVNode(CopyButton, mergeJsxProps(mergePartProps({
|
|
158
158
|
size: "xs",
|
|
159
159
|
sx: descriptionsStyles.copy,
|
|
160
160
|
"aria-label": lang.descriptionsCopy(plainText(item.label) ?? lang.copy)
|