niuma-ui 1.0.0 → 1.0.2
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/CHANGELOG.md +50 -1
- package/NOTICE +2 -2
- package/package.json +13 -5
- package/src/__tests__/RsButton.spec.ts +47 -0
- package/src/__tests__/RsCard.spec.ts +160 -1
- package/src/__tests__/RsConfirmDialog.spec.ts +127 -0
- package/src/__tests__/RsContextMenu.spec.ts +12 -0
- package/src/__tests__/RsDateTimePicker.spec.ts +11 -0
- package/src/__tests__/RsDescriptions.spec.ts +50 -0
- package/src/__tests__/RsDialog.spec.ts +117 -0
- package/src/__tests__/RsDrawer.spec.ts +96 -3
- package/src/__tests__/RsDynamicTags.spec.ts +52 -0
- package/src/__tests__/RsForm.spec.ts +245 -1
- package/src/__tests__/RsInput.spec.ts +15 -1
- package/src/__tests__/RsMenu.spec.ts +406 -14
- package/src/__tests__/RsPagination.spec.ts +67 -2
- package/src/__tests__/RsSelect.spec.ts +42 -1
- package/src/__tests__/RsSwitch.spec.ts +44 -0
- package/src/__tests__/RsTable.a11y.spec.ts +83 -0
- package/src/__tests__/RsTable.dom-baseline.spec.ts +87 -0
- package/src/__tests__/RsTable.perf.spec.ts +23 -4
- package/src/__tests__/RsTable.spec.ts +63 -8
- package/src/__tests__/RsTable.ssr.spec.ts +71 -0
- package/src/__tests__/RsTable.summary.spec.ts +45 -0
- package/src/__tests__/RsTabs.spec.ts +102 -4
- package/src/__tests__/RsTerminal.spec.ts +1 -1
- package/src/__tests__/RsTimePicker.spec.ts +11 -0
- package/src/__tests__/RsUpload.spec.ts +62 -6
- package/src/__tests__/createRsDialog.spec.ts +145 -0
- package/src/__tests__/form-rules.spec.ts +76 -0
- package/src/__tests__/perf-budgets.ts +32 -0
- package/src/__tests__/rs-table-api.spec.ts +206 -0
- package/src/__tests__/rs-table-compat-matrix.spec.ts +108 -0
- package/src/__tests__/rs-table-echarts-adapter.spec.ts +43 -0
- package/src/__tests__/rs-table-grid-nav.spec.ts +63 -0
- package/src/__tests__/rs-table-isolation.spec.ts +158 -0
- package/src/__tests__/split-pane-utils.spec.ts +21 -0
- package/src/__tests__/table-chart-utils.spec.ts +88 -0
- package/src/__tests__/table-summary-utils.spec.ts +62 -0
- package/src/__tests__/table-utils.spec.ts +124 -0
- package/src/__tests__/theme.spec.ts +25 -0
- package/src/__tests__/useRsTableColumnResize.spec.ts +25 -0
- package/src/__tests__/useRsTableCore.perf.spec.ts +170 -0
- package/src/components/RsAlert.vue +212 -0
- package/src/components/RsButton.vue +145 -21
- package/src/components/RsCard.vue +200 -16
- package/src/components/RsConfirmDialog.vue +215 -28
- package/src/components/RsContextMenu.vue +51 -13
- package/src/components/RsDatePicker.vue +267 -34
- package/src/components/RsDateTimePicker.vue +19 -2
- package/src/components/RsDescriptions.vue +186 -0
- package/src/components/RsDescriptionsItem.vue +32 -0
- package/src/components/RsDialog.vue +363 -51
- package/src/components/RsDivider.vue +89 -0
- package/src/components/RsDrawer.vue +558 -33
- package/src/components/RsDropdown.vue +33 -2
- package/src/components/RsDynamicTags.vue +517 -0
- package/src/components/RsForm.vue +97 -8
- package/src/components/RsInput.vue +80 -82
- package/src/components/RsInputNumber.vue +64 -23
- package/src/components/RsLabel.vue +20 -5
- package/src/components/RsLoadingBar.vue +151 -0
- package/src/components/RsMenu.vue +73 -15
- package/src/components/RsMenuItems.vue +147 -9
- package/src/components/RsPagination.vue +93 -14
- package/src/components/RsRadio.vue +81 -0
- package/src/components/RsRadioItem.vue +156 -0
- package/src/components/RsSelect.vue +67 -58
- package/src/components/RsSplitPane.vue +69 -1
- package/src/components/RsSwitch.vue +179 -0
- package/src/components/RsTable.vue +653 -2555
- package/src/components/RsTabs.vue +415 -83
- package/src/components/RsTag.vue +191 -0
- package/src/components/RsTerminal.vue +3 -3
- package/src/components/RsTimePicker.vue +44 -5
- package/src/components/RsTimePickerColumns.vue +136 -77
- package/src/components/RsToaster.vue +58 -18
- package/src/components/RsTree.vue +39 -21
- package/src/components/RsUpload.vue +145 -12
- package/src/components/RsVNodeHost.vue +20 -0
- package/src/components/button-utils.ts +39 -0
- package/src/components/context-menu-utils.ts +6 -0
- package/src/components/date-picker-utils.ts +84 -4
- package/src/components/descriptions-utils.ts +24 -0
- package/src/components/dialog-utils.ts +147 -0
- package/src/components/dialog-window.ts +74 -9
- package/src/components/drawer-utils.ts +73 -0
- package/src/components/form-rules.ts +257 -0
- package/src/components/form-utils.ts +52 -2
- package/src/components/menu-utils.ts +11 -0
- package/src/components/overlay-utils.ts +3 -0
- package/src/components/radio-utils.ts +12 -0
- package/src/components/reka.ts +5 -0
- package/src/components/select-utils.ts +21 -2
- package/src/components/split-pane-utils.ts +65 -12
- package/src/components/table/RsTableBody.vue +217 -0
- package/src/components/table/RsTableBodyRow.vue +90 -1
- package/src/components/table/RsTableColGroup.vue +41 -0
- package/src/components/table/RsTableHeader.vue +158 -0
- package/src/components/table/RsTableSummaryRow.vue +52 -0
- package/src/components/table/rs-table-api.ts +99 -0
- package/src/components/table/rs-table-compat-matrix.ts +71 -0
- package/src/components/table/rs-table-echarts-adapter.ts +81 -0
- package/src/components/table/rs-table-feature-host.ts +178 -0
- package/src/components/table/rs-table-grid-nav.ts +122 -0
- package/src/components/table/rs-table-header-types.ts +47 -0
- package/src/components/table/rs-table-module-registry.ts +72 -0
- package/src/components/table/rs-table-props.ts +274 -0
- package/src/components/table/rs-table-view-context.ts +258 -0
- package/src/components/table/rs-table.css +84 -6
- package/src/components/table/table-chart-utils.ts +165 -0
- package/src/components/table/table-features.ts +284 -0
- package/src/components/table/table-summary-utils.ts +194 -0
- package/src/components/table-utils.ts +452 -2
- package/src/components/tabs-utils.ts +142 -2
- package/src/components/terminal-utils.ts +1 -1
- package/src/components/terminal-wheel.ts +1 -1
- package/src/components/time-picker-utils.ts +76 -3
- package/src/components/upload-utils.ts +122 -0
- package/src/components/virtual-list-utils.ts +15 -2
- package/src/composables/assembleRsTableApi.ts +79 -0
- package/src/composables/bindRsTableViewContext.ts +269 -0
- package/src/composables/createRsDialog.ts +357 -0
- package/src/composables/createRsTableEditEmitBridge.ts +38 -0
- package/src/composables/useRsLoadingBar.ts +36 -0
- package/src/composables/useRsTable.ts +134 -0
- package/src/composables/useRsTableA11y.ts +52 -0
- package/src/composables/useRsTableChartBridge.ts +79 -0
- package/src/composables/useRsTableColumnLayout.ts +320 -0
- package/src/composables/useRsTableColumnResize.ts +244 -0
- package/src/composables/useRsTableColumnVirtual.ts +180 -0
- package/src/composables/useRsTableColumns.ts +200 -0
- package/src/composables/useRsTableContextMenu.ts +257 -0
- package/src/composables/useRsTableCore.ts +256 -0
- package/src/composables/useRsTableEditActions.ts +606 -0
- package/src/composables/useRsTableEditLayer.ts +251 -0
- package/src/composables/useRsTableEngine.ts +357 -0
- package/src/composables/useRsTableGridKeyboard.ts +123 -0
- package/src/composables/useRsTableHeadless.ts +137 -0
- package/src/composables/useRsTableInteraction.ts +433 -0
- package/src/composables/useRsTableScrollHost.ts +75 -0
- package/src/composables/useRsTableScrollLayout.ts +255 -0
- package/src/composables/useRsTableSelectionSource.ts +89 -0
- package/src/composables/useRsTableShell.ts +416 -0
- package/src/composables/useRsTableShellChrome.ts +170 -0
- package/src/composables/useRsTableSummary.ts +113 -0
- package/src/composables/useRsTableViewProvide.ts +263 -0
- package/src/composables/useRsTableVirtual.ts +154 -0
- package/src/composables/useRsTabsNav.ts +4 -0
- package/src/composables/useTableEdit.ts +1 -1
- package/src/icons/lucide.ts +2 -2
- package/src/index.ts +309 -9
- package/src/locale/messages.ts +30 -0
- package/src/styles.css +305 -52
- package/src/theme/brand.example.css +21 -2
- package/src/theme/presets.ts +4 -0
- package/src/theme/types.ts +67 -0
- package/src/components/RsTableColgroup.vue +0 -17
- package/src/components/RsTableHeaderRow.vue +0 -30
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
computed,
|
|
4
|
+
nextTick,
|
|
5
|
+
onBeforeUnmount,
|
|
6
|
+
onMounted,
|
|
7
|
+
ref,
|
|
8
|
+
toRef,
|
|
9
|
+
useAttrs,
|
|
10
|
+
useSlots,
|
|
11
|
+
watch,
|
|
12
|
+
} from 'vue'
|
|
3
13
|
import {
|
|
4
14
|
DialogContent,
|
|
5
15
|
DialogDescription,
|
|
@@ -11,28 +21,60 @@ import {
|
|
|
11
21
|
import { useRsI18n } from '../composables/useRsI18n'
|
|
12
22
|
import RsButton from './RsButton.vue'
|
|
13
23
|
import type { RsFeedbackTone } from './overlay-utils'
|
|
24
|
+
import {
|
|
25
|
+
isRsDialogWidthPreset,
|
|
26
|
+
resolveDialogOverlayStyle,
|
|
27
|
+
resolveRsDialogCssWidth,
|
|
28
|
+
runRsDialogBeforeClose,
|
|
29
|
+
type RsDialogBeforeClose,
|
|
30
|
+
type RsDialogCloseReason,
|
|
31
|
+
type RsDialogLayout,
|
|
32
|
+
type RsDialogWidth,
|
|
33
|
+
type RsDialogWidthPreset,
|
|
34
|
+
} from './dialog-utils'
|
|
14
35
|
import { useRsDialogWindow } from './dialog-window'
|
|
15
36
|
|
|
37
|
+
defineOptions({ inheritAttrs: false })
|
|
38
|
+
|
|
16
39
|
const open = defineModel<boolean>('open', { default: false })
|
|
17
40
|
|
|
18
41
|
const props = withDefaults(
|
|
19
42
|
defineProps<{
|
|
20
|
-
title
|
|
43
|
+
/** 标题文案;也可用 `#title` / `#header` 插槽。缺省时用空格满足无障碍 Title */
|
|
44
|
+
title?: string
|
|
21
45
|
description?: string
|
|
22
|
-
width?:
|
|
46
|
+
width?: RsDialogWidth
|
|
23
47
|
tone?: RsFeedbackTone
|
|
24
|
-
|
|
48
|
+
/**
|
|
49
|
+
* window:可拖拽/缩放工作窗(推荐)。
|
|
50
|
+
* confirm:历史兼容的居中轻量窗;新代码的确认/提示请用 RsConfirmDialog。
|
|
51
|
+
*/
|
|
52
|
+
layout?: RsDialogLayout
|
|
25
53
|
draggable?: boolean
|
|
26
54
|
resizable?: boolean
|
|
27
55
|
fullscreenable?: boolean
|
|
28
56
|
/** 是否模态(false 时不锁焦点/不挡背后交互,适合页签内浮层) */
|
|
29
57
|
modal?: boolean
|
|
30
58
|
showOverlay?: boolean
|
|
59
|
+
/**
|
|
60
|
+
* 遮罩不透明度 0–1;覆盖主题默认(最浅)。
|
|
61
|
+
* 例:0.08 最浅、0.35 适中、0.55 较深。
|
|
62
|
+
*/
|
|
63
|
+
overlayOpacity?: number
|
|
64
|
+
/** 遮罩模糊;number 为 px。默认主题为 0(无模糊,背后内容清晰) */
|
|
65
|
+
overlayBlur?: number | string
|
|
31
66
|
showClose?: boolean
|
|
32
67
|
/** 点击外部是否关闭;默认 false。与 modal 独立:非模态也可保持打开。 */
|
|
33
68
|
closeOnOverlayClick?: boolean
|
|
34
|
-
/**
|
|
35
|
-
|
|
69
|
+
/** 按 Esc 是否关闭;默认 true(与 Reka 默认一致) */
|
|
70
|
+
closeOnEsc?: boolean
|
|
71
|
+
/**
|
|
72
|
+
* DialogPortal 挂载目标
|
|
73
|
+
* - string / HTMLElement:Teleport 到指定节点
|
|
74
|
+
* - false:禁用 Teleport,就地渲染(不挂到 body / 全局挂载点)
|
|
75
|
+
* - undefined:走 Reka 默认(通常为 body,或 ConfigProvider.teleportTo)
|
|
76
|
+
*/
|
|
77
|
+
teleportTo?: string | HTMLElement | false
|
|
36
78
|
/**
|
|
37
79
|
* 延后挂载 #body 插槽,避免与打开动画/重组件 init 争抢主线程。
|
|
38
80
|
* 默认:window 布局为 true,confirm 为 false。
|
|
@@ -40,8 +82,25 @@ const props = withDefaults(
|
|
|
40
82
|
deferBodyMount?: boolean
|
|
41
83
|
/** 全屏/还原时是否播放 bounds 过渡(含编辑器时建议保持 false) */
|
|
42
84
|
boundsTransition?: boolean
|
|
85
|
+
/** 关闭前钩子;返回 false 可阻止关闭(支持 async) */
|
|
86
|
+
beforeClose?: RsDialogBeforeClose
|
|
87
|
+
/**
|
|
88
|
+
* 显示内置底部按钮区。默认 false,避免影响仅用 `#footer` 或不需要按钮的旧用法。
|
|
89
|
+
* 无 `#footer` 且为 true 时渲染取消/确定。
|
|
90
|
+
* 危险确认/提示请用 RsConfirmDialog,不要用本 footer 冒充确认 UI。
|
|
91
|
+
*/
|
|
92
|
+
showFooter?: boolean
|
|
93
|
+
showCancel?: boolean
|
|
94
|
+
showConfirm?: boolean
|
|
95
|
+
cancelText?: string
|
|
96
|
+
confirmText?: string
|
|
97
|
+
confirmLoading?: boolean
|
|
98
|
+
confirmVariant?: 'primary' | 'danger'
|
|
99
|
+
/** 点击确定后是否自动关闭(仍会走 beforeClose) */
|
|
100
|
+
autoCloseOnConfirm?: boolean
|
|
43
101
|
}>(),
|
|
44
102
|
{
|
|
103
|
+
title: '',
|
|
45
104
|
width: 'md',
|
|
46
105
|
tone: 'default',
|
|
47
106
|
layout: 'window',
|
|
@@ -52,20 +111,64 @@ const props = withDefaults(
|
|
|
52
111
|
showOverlay: false,
|
|
53
112
|
showClose: true,
|
|
54
113
|
closeOnOverlayClick: false,
|
|
114
|
+
closeOnEsc: true,
|
|
55
115
|
boundsTransition: false,
|
|
116
|
+
showFooter: false,
|
|
117
|
+
showCancel: true,
|
|
118
|
+
showConfirm: true,
|
|
119
|
+
confirmLoading: false,
|
|
120
|
+
confirmVariant: 'primary',
|
|
121
|
+
autoCloseOnConfirm: false,
|
|
56
122
|
},
|
|
57
123
|
)
|
|
58
124
|
|
|
125
|
+
const emit = defineEmits<{
|
|
126
|
+
openChange: [open: boolean]
|
|
127
|
+
afterOpen: []
|
|
128
|
+
afterClose: [reason: RsDialogCloseReason]
|
|
129
|
+
confirm: []
|
|
130
|
+
cancel: []
|
|
131
|
+
}>()
|
|
132
|
+
|
|
133
|
+
const attrs = useAttrs()
|
|
134
|
+
const slots = useSlots()
|
|
59
135
|
const { t } = useRsI18n()
|
|
136
|
+
|
|
60
137
|
const isWindowLayout = computed(() => props.layout === 'window')
|
|
61
138
|
const isConfirmLayout = computed(() => props.layout === 'confirm')
|
|
62
139
|
const enableDraggable = computed(() => props.draggable && isWindowLayout.value)
|
|
63
140
|
const enableResizable = computed(() => props.resizable && isWindowLayout.value)
|
|
64
141
|
const deferBodyMount = computed(() => props.deferBodyMount ?? isWindowLayout.value)
|
|
142
|
+
const overlayStyle = computed(() =>
|
|
143
|
+
resolveDialogOverlayStyle({
|
|
144
|
+
overlayOpacity: props.overlayOpacity,
|
|
145
|
+
overlayBlur: props.overlayBlur,
|
|
146
|
+
}),
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
const widthPreset = computed<RsDialogWidthPreset>(() =>
|
|
150
|
+
isRsDialogWidthPreset(props.width) ? props.width : 'md',
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
const initialWidthPx = computed(() => {
|
|
154
|
+
if (typeof props.width === 'number' && Number.isFinite(props.width) && props.width > 0) {
|
|
155
|
+
return props.width
|
|
156
|
+
}
|
|
157
|
+
if (typeof props.width === 'string') {
|
|
158
|
+
const trimmed = props.width.trim()
|
|
159
|
+
if (/^\d+(\.\d+)?px$/i.test(trimmed)) return Number.parseFloat(trimmed)
|
|
160
|
+
}
|
|
161
|
+
return undefined
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
const customCssWidth = computed(() => resolveRsDialogCssWidth(props.width))
|
|
65
165
|
|
|
66
166
|
const bodyReady = ref(false)
|
|
167
|
+
const closing = ref(false)
|
|
168
|
+
let pendingCloseReason: RsDialogCloseReason = 'programmatic'
|
|
67
169
|
let bodyMountFrameOuter = 0
|
|
68
170
|
let bodyMountFrameInner = 0
|
|
171
|
+
let afterOpenTimer = 0
|
|
69
172
|
|
|
70
173
|
function resetBodyMount(): void {
|
|
71
174
|
if (bodyMountFrameOuter) {
|
|
@@ -103,17 +206,152 @@ watch(
|
|
|
103
206
|
{ immediate: true },
|
|
104
207
|
)
|
|
105
208
|
|
|
106
|
-
|
|
209
|
+
function clearAfterOpenTimer(): void {
|
|
210
|
+
if (!afterOpenTimer) return
|
|
211
|
+
window.clearTimeout(afterOpenTimer)
|
|
212
|
+
afterOpenTimer = 0
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function queueAfterOpen(): void {
|
|
216
|
+
clearAfterOpenTimer()
|
|
217
|
+
// 略晚于打开动画,便于业务在可见后聚焦
|
|
218
|
+
afterOpenTimer = window.setTimeout(() => {
|
|
219
|
+
afterOpenTimer = 0
|
|
220
|
+
if (open.value) emit('afterOpen')
|
|
221
|
+
}, 230)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
watch(open, (isOpen, wasOpen) => {
|
|
225
|
+
if (isOpen && !wasOpen) {
|
|
226
|
+
emit('openChange', true)
|
|
227
|
+
queueAfterOpen()
|
|
228
|
+
return
|
|
229
|
+
}
|
|
230
|
+
if (!isOpen && wasOpen) {
|
|
231
|
+
emit('openChange', false)
|
|
232
|
+
// 父级直接改 v-model 关闭时补发 afterClose(requestClose 路径已发过)
|
|
233
|
+
if (!closing.value) emit('afterClose', 'programmatic')
|
|
234
|
+
}
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
onMounted(() => {
|
|
238
|
+
if (import.meta.env.DEV && props.layout === 'confirm') {
|
|
239
|
+
console.warn(
|
|
240
|
+
'[RsDialog] layout:"confirm" 已弃用。确认/提示请使用 RsConfirmDialog / rsConfirm;RsDialog 仅用于工作窗与表单。',
|
|
241
|
+
)
|
|
242
|
+
}
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
onBeforeUnmount(() => {
|
|
246
|
+
resetBodyMount()
|
|
247
|
+
clearAfterOpenTimer()
|
|
248
|
+
})
|
|
107
249
|
|
|
108
250
|
const showBodyContent = computed(() => !deferBodyMount.value || bodyReady.value)
|
|
109
251
|
|
|
252
|
+
const showBuiltinFooter = computed(
|
|
253
|
+
() => props.showFooter && !slots.footer && (props.showCancel || props.showConfirm),
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
const resolvedTitle = computed(() => {
|
|
257
|
+
const text = props.title?.trim()
|
|
258
|
+
return text || ' '
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
const contentClass = computed(() => {
|
|
262
|
+
const presetClass = isRsDialogWidthPreset(props.width)
|
|
263
|
+
? `rs-dialog__content--${props.width}`
|
|
264
|
+
: 'rs-dialog__content--custom-width'
|
|
265
|
+
return [
|
|
266
|
+
`rs-dialog__content--${props.layout}`,
|
|
267
|
+
presetClass,
|
|
268
|
+
`rs-dialog__content--tone-${props.tone}`,
|
|
269
|
+
{
|
|
270
|
+
'rs-dialog__content--fullscreen': isFullscreen.value,
|
|
271
|
+
'rs-dialog__content--draggable': enableDraggable.value,
|
|
272
|
+
'rs-dialog__content--bounds-transition': boundsTransitionEnabled.value,
|
|
273
|
+
},
|
|
274
|
+
]
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
const contentStyle = computed(() => {
|
|
278
|
+
const base = isWindowLayout.value ? dialogStyle.value : undefined
|
|
279
|
+
const cssW = customCssWidth.value
|
|
280
|
+
// 数值/px 已通过 initialWidth 进入 window bounds;其它自定义宽度用 CSS 覆盖
|
|
281
|
+
if (!cssW) return base
|
|
282
|
+
if (isWindowLayout.value && initialWidthPx.value != null) return base
|
|
283
|
+
return {
|
|
284
|
+
...(base ?? {}),
|
|
285
|
+
width: isConfirmLayout.value ? undefined : cssW,
|
|
286
|
+
maxWidth: cssW,
|
|
287
|
+
}
|
|
288
|
+
})
|
|
289
|
+
|
|
290
|
+
async function requestClose(reason: RsDialogCloseReason): Promise<boolean> {
|
|
291
|
+
if (!open.value || closing.value) return false
|
|
292
|
+
closing.value = true
|
|
293
|
+
pendingCloseReason = reason
|
|
294
|
+
try {
|
|
295
|
+
const allowed = await runRsDialogBeforeClose(props.beforeClose, reason)
|
|
296
|
+
if (!allowed) return false
|
|
297
|
+
open.value = false
|
|
298
|
+
await nextTick()
|
|
299
|
+
emit('afterClose', reason)
|
|
300
|
+
return true
|
|
301
|
+
} finally {
|
|
302
|
+
closing.value = false
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/** DialogRoot 受控更新:打开直通;关闭统一走 beforeClose */
|
|
307
|
+
async function onUpdateOpen(next: boolean): Promise<void> {
|
|
308
|
+
if (next) {
|
|
309
|
+
open.value = true
|
|
310
|
+
return
|
|
311
|
+
}
|
|
312
|
+
await requestClose(pendingCloseReason)
|
|
313
|
+
pendingCloseReason = 'programmatic'
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function onEscapeKeyDown(event: Event): void {
|
|
317
|
+
if (!props.closeOnEsc || props.confirmLoading) {
|
|
318
|
+
event.preventDefault()
|
|
319
|
+
return
|
|
320
|
+
}
|
|
321
|
+
pendingCloseReason = 'escape'
|
|
322
|
+
}
|
|
323
|
+
|
|
110
324
|
/** 是否因外部交互关闭:与 modal 无关;非模态仍允许背后操作,仅控制是否 dismiss。 */
|
|
111
325
|
function onPointerDownOutside(event: Event): void {
|
|
112
|
-
if (!props.closeOnOverlayClick
|
|
326
|
+
if (!props.closeOnOverlayClick || props.confirmLoading) {
|
|
327
|
+
event.preventDefault()
|
|
328
|
+
return
|
|
329
|
+
}
|
|
330
|
+
pendingCloseReason = 'overlay'
|
|
113
331
|
}
|
|
114
332
|
|
|
115
333
|
function onInteractOutside(event: Event): void {
|
|
116
|
-
if (!props.closeOnOverlayClick
|
|
334
|
+
if (!props.closeOnOverlayClick || props.confirmLoading) {
|
|
335
|
+
event.preventDefault()
|
|
336
|
+
return
|
|
337
|
+
}
|
|
338
|
+
pendingCloseReason = 'overlay'
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
async function onHeaderCloseClick(): Promise<void> {
|
|
342
|
+
await requestClose('close')
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
async function onBuiltinCancel(): Promise<void> {
|
|
346
|
+
emit('cancel')
|
|
347
|
+
await requestClose('cancel')
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
async function onBuiltinConfirm(): Promise<void> {
|
|
351
|
+
emit('confirm')
|
|
352
|
+
if (props.autoCloseOnConfirm) {
|
|
353
|
+
await requestClose('confirm')
|
|
354
|
+
}
|
|
117
355
|
}
|
|
118
356
|
|
|
119
357
|
const {
|
|
@@ -121,38 +359,61 @@ const {
|
|
|
121
359
|
boundsTransitionEnabled,
|
|
122
360
|
dialogStyle,
|
|
123
361
|
resizeHandles,
|
|
362
|
+
setPanelEl,
|
|
124
363
|
toggleFullscreen,
|
|
125
364
|
onHeaderPointerDown,
|
|
126
365
|
onResizePointerDown,
|
|
127
366
|
} = useRsDialogWindow({
|
|
128
367
|
open,
|
|
129
|
-
widthPreset
|
|
368
|
+
widthPreset,
|
|
369
|
+
initialWidth: initialWidthPx,
|
|
130
370
|
draggable: enableDraggable,
|
|
131
371
|
resizable: enableResizable,
|
|
132
372
|
compact: isConfirmLayout,
|
|
133
373
|
boundsTransition: toRef(props, 'boundsTransition'),
|
|
134
374
|
})
|
|
375
|
+
|
|
376
|
+
const contentRef = ref<{ $el?: HTMLElement } | HTMLElement | null>(null)
|
|
377
|
+
|
|
378
|
+
watch(
|
|
379
|
+
contentRef,
|
|
380
|
+
(inst) => {
|
|
381
|
+
if (!inst) {
|
|
382
|
+
setPanelEl(null)
|
|
383
|
+
return
|
|
384
|
+
}
|
|
385
|
+
const el = inst instanceof HTMLElement ? inst : (inst.$el ?? null)
|
|
386
|
+
setPanelEl(el instanceof HTMLElement ? el : null)
|
|
387
|
+
},
|
|
388
|
+
{ flush: 'post' },
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
defineExpose({
|
|
392
|
+
/** 请求关闭(走 beforeClose) */
|
|
393
|
+
close: (reason: RsDialogCloseReason = 'programmatic') => requestClose(reason),
|
|
394
|
+
/** 打开对话框 */
|
|
395
|
+
openDialog: () => {
|
|
396
|
+
open.value = true
|
|
397
|
+
},
|
|
398
|
+
})
|
|
135
399
|
</script>
|
|
136
400
|
|
|
137
401
|
<template>
|
|
138
|
-
<DialogRoot
|
|
139
|
-
<DialogPortal
|
|
140
|
-
|
|
402
|
+
<DialogRoot :open="open" :modal="modal" @update:open="onUpdateOpen">
|
|
403
|
+
<DialogPortal
|
|
404
|
+
:disabled="teleportTo === false"
|
|
405
|
+
:to="teleportTo === false ? undefined : teleportTo"
|
|
406
|
+
>
|
|
407
|
+
<DialogOverlay v-if="showOverlay" class="rs-dialog__overlay rs-motion-reduce" :style="overlayStyle" />
|
|
141
408
|
<DialogContent
|
|
409
|
+
ref="contentRef"
|
|
410
|
+
v-bind="attrs"
|
|
142
411
|
class="rs-dialog__content rs-motion-reduce"
|
|
143
|
-
:class="
|
|
144
|
-
|
|
145
|
-
`rs-dialog__content--${layout}`,
|
|
146
|
-
`rs-dialog__content--tone-${tone}`,
|
|
147
|
-
{
|
|
148
|
-
'rs-dialog__content--fullscreen': isFullscreen,
|
|
149
|
-
'rs-dialog__content--draggable': enableDraggable,
|
|
150
|
-
'rs-dialog__content--bounds-transition': boundsTransitionEnabled,
|
|
151
|
-
},
|
|
152
|
-
]"
|
|
153
|
-
:style="isWindowLayout ? dialogStyle : undefined"
|
|
412
|
+
:class="contentClass"
|
|
413
|
+
:style="contentStyle"
|
|
154
414
|
@pointer-down-outside="onPointerDownOutside"
|
|
155
415
|
@interact-outside="onInteractOutside"
|
|
416
|
+
@escape-key-down="onEscapeKeyDown"
|
|
156
417
|
>
|
|
157
418
|
<template v-if="enableResizable && !isFullscreen">
|
|
158
419
|
<div
|
|
@@ -164,15 +425,19 @@ const {
|
|
|
164
425
|
/>
|
|
165
426
|
</template>
|
|
166
427
|
<header class="rs-dialog__header" @pointerdown="onHeaderPointerDown">
|
|
167
|
-
<
|
|
168
|
-
<
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
428
|
+
<slot name="header">
|
|
429
|
+
<div class="rs-dialog__heading">
|
|
430
|
+
<DialogTitle class="rs-dialog__title">
|
|
431
|
+
<slot name="title">{{ resolvedTitle }}</slot>
|
|
432
|
+
</DialogTitle>
|
|
433
|
+
<DialogDescription
|
|
434
|
+
class="rs-dialog__description"
|
|
435
|
+
:class="{ 'rs-dialog__description--sr-only': !description && !$slots.description }"
|
|
436
|
+
>
|
|
437
|
+
<slot name="description">{{ description || resolvedTitle }}</slot>
|
|
438
|
+
</DialogDescription>
|
|
439
|
+
</div>
|
|
440
|
+
</slot>
|
|
176
441
|
<div class="rs-dialog__actions">
|
|
177
442
|
<RsButton
|
|
178
443
|
v-if="fullscreenable && isWindowLayout"
|
|
@@ -189,8 +454,9 @@ const {
|
|
|
189
454
|
size="sm"
|
|
190
455
|
icon-only
|
|
191
456
|
icon="x"
|
|
457
|
+
:disabled="confirmLoading"
|
|
192
458
|
:tooltip="t('dialog.close')"
|
|
193
|
-
@click="
|
|
459
|
+
@click="onHeaderCloseClick"
|
|
194
460
|
/>
|
|
195
461
|
</div>
|
|
196
462
|
</header>
|
|
@@ -200,8 +466,34 @@ const {
|
|
|
200
466
|
<slot name="body-placeholder" />
|
|
201
467
|
</div>
|
|
202
468
|
</div>
|
|
203
|
-
<footer v-if="$slots.footer" class="rs-dialog__footer">
|
|
204
|
-
<slot
|
|
469
|
+
<footer v-if="$slots.footer || showBuiltinFooter" class="rs-dialog__footer">
|
|
470
|
+
<slot
|
|
471
|
+
name="footer"
|
|
472
|
+
:confirm-loading="confirmLoading"
|
|
473
|
+
:on-confirm="onBuiltinConfirm"
|
|
474
|
+
:on-cancel="onBuiltinCancel"
|
|
475
|
+
>
|
|
476
|
+
<template v-if="showBuiltinFooter">
|
|
477
|
+
<RsButton
|
|
478
|
+
v-if="showCancel"
|
|
479
|
+
variant="default"
|
|
480
|
+
size="sm"
|
|
481
|
+
:disabled="confirmLoading"
|
|
482
|
+
@click="onBuiltinCancel"
|
|
483
|
+
>
|
|
484
|
+
{{ cancelText ?? t('common.cancel') }}
|
|
485
|
+
</RsButton>
|
|
486
|
+
<RsButton
|
|
487
|
+
v-if="showConfirm"
|
|
488
|
+
:variant="confirmVariant"
|
|
489
|
+
size="sm"
|
|
490
|
+
:loading="confirmLoading"
|
|
491
|
+
@click="onBuiltinConfirm"
|
|
492
|
+
>
|
|
493
|
+
{{ confirmText ?? t('common.confirm') }}
|
|
494
|
+
</RsButton>
|
|
495
|
+
</template>
|
|
496
|
+
</slot>
|
|
205
497
|
</footer>
|
|
206
498
|
</DialogContent>
|
|
207
499
|
</DialogPortal>
|
|
@@ -243,9 +535,10 @@ const {
|
|
|
243
535
|
overflow: hidden;
|
|
244
536
|
border-radius: var(--rs-radius-lg);
|
|
245
537
|
border: 1px solid var(--rs-dialog-border);
|
|
246
|
-
background: var(--rs-dialog-
|
|
538
|
+
background: var(--rs-dialog-bg);
|
|
247
539
|
box-shadow: var(--rs-dialog-shadow);
|
|
248
540
|
outline: none;
|
|
541
|
+
color: var(--rs-dialog-title-fg);
|
|
249
542
|
}
|
|
250
543
|
|
|
251
544
|
/* 亮色 confirm:轻微 vibrancy;window 用实底避免 CEF 下 blur 合成开销 */
|
|
@@ -349,7 +642,8 @@ const {
|
|
|
349
642
|
}
|
|
350
643
|
.rs-dialog__content--window.rs-dialog__content--sm,
|
|
351
644
|
.rs-dialog__content--window.rs-dialog__content--md,
|
|
352
|
-
.rs-dialog__content--window.rs-dialog__content--lg
|
|
645
|
+
.rs-dialog__content--window.rs-dialog__content--lg,
|
|
646
|
+
.rs-dialog__content--window.rs-dialog__content--custom-width {
|
|
353
647
|
max-width: none;
|
|
354
648
|
}
|
|
355
649
|
.rs-dialog__content--fullscreen {
|
|
@@ -359,26 +653,40 @@ const {
|
|
|
359
653
|
display: flex;
|
|
360
654
|
align-items: center;
|
|
361
655
|
justify-content: space-between;
|
|
362
|
-
gap:
|
|
656
|
+
gap: var(--rs-space-md);
|
|
363
657
|
flex-shrink: 0;
|
|
364
|
-
|
|
658
|
+
box-sizing: border-box;
|
|
659
|
+
height: var(--rs-dialog-header-height);
|
|
660
|
+
min-height: var(--rs-dialog-header-min-height);
|
|
661
|
+
padding: var(--rs-dialog-header-padding-y) var(--rs-dialog-header-padding-x);
|
|
365
662
|
background: var(--rs-dialog-header-bg);
|
|
366
663
|
border-bottom: 1px solid var(--rs-dialog-separator);
|
|
367
|
-
|
|
664
|
+
}
|
|
665
|
+
.rs-dialog__heading {
|
|
666
|
+
min-width: 0;
|
|
667
|
+
flex: 1;
|
|
668
|
+
display: flex;
|
|
669
|
+
flex-direction: column;
|
|
670
|
+
justify-content: center;
|
|
671
|
+
gap: 2px;
|
|
368
672
|
}
|
|
369
673
|
.rs-dialog__content--window.rs-dialog__content--draggable .rs-dialog__header {
|
|
370
|
-
cursor:
|
|
674
|
+
cursor: move;
|
|
371
675
|
user-select: none;
|
|
372
676
|
}
|
|
373
677
|
.rs-dialog__title {
|
|
374
678
|
margin: 0;
|
|
679
|
+
display: inline-flex;
|
|
680
|
+
align-items: center;
|
|
681
|
+
gap: var(--rs-space-sm);
|
|
375
682
|
font-size: var(--rs-font-size-base);
|
|
376
683
|
font-weight: 600;
|
|
377
|
-
letter-spacing: -0.
|
|
684
|
+
letter-spacing: -0.015em;
|
|
685
|
+
line-height: var(--rs-line-height-tight, 1.3);
|
|
378
686
|
color: var(--rs-dialog-title-fg);
|
|
379
687
|
}
|
|
380
688
|
.rs-dialog__description {
|
|
381
|
-
margin:
|
|
689
|
+
margin: var(--rs-space-xs) 0 0;
|
|
382
690
|
font-size: var(--rs-font-size-sm);
|
|
383
691
|
color: var(--rs-dialog-description-fg);
|
|
384
692
|
line-height: var(--rs-line-height-normal);
|
|
@@ -398,7 +706,8 @@ const {
|
|
|
398
706
|
flex: 1;
|
|
399
707
|
min-height: 0;
|
|
400
708
|
overflow: auto;
|
|
401
|
-
|
|
709
|
+
box-sizing: border-box;
|
|
710
|
+
padding: var(--rs-dialog-body-padding-y) var(--rs-dialog-body-padding-x);
|
|
402
711
|
background: var(--rs-dialog-body-bg);
|
|
403
712
|
}
|
|
404
713
|
.rs-dialog__content--window .rs-dialog__body {
|
|
@@ -411,20 +720,23 @@ const {
|
|
|
411
720
|
}
|
|
412
721
|
.rs-dialog__footer {
|
|
413
722
|
display: flex;
|
|
723
|
+
align-items: center;
|
|
414
724
|
justify-content: flex-end;
|
|
415
|
-
|
|
725
|
+
flex-wrap: wrap;
|
|
726
|
+
gap: var(--rs-space-sm);
|
|
416
727
|
flex-shrink: 0;
|
|
417
|
-
|
|
728
|
+
box-sizing: border-box;
|
|
729
|
+
height: var(--rs-dialog-footer-height);
|
|
730
|
+
min-height: var(--rs-dialog-footer-min-height);
|
|
731
|
+
padding: var(--rs-dialog-footer-padding-y) var(--rs-dialog-footer-padding-x);
|
|
418
732
|
background: var(--rs-dialog-footer-bg);
|
|
419
|
-
border-top: 1px solid var(--rs-dialog-
|
|
420
|
-
}
|
|
421
|
-
[data-rs-theme='light'] .rs-dialog__header {
|
|
422
|
-
box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.75);
|
|
733
|
+
border-top: 1px solid var(--rs-dialog-footer-border);
|
|
423
734
|
}
|
|
424
735
|
.rs-dialog__actions {
|
|
425
736
|
display: inline-flex;
|
|
426
737
|
align-items: center;
|
|
427
738
|
gap: var(--rs-space-xs);
|
|
739
|
+
flex-shrink: 0;
|
|
428
740
|
}
|
|
429
741
|
.rs-dialog__resize-handle {
|
|
430
742
|
position: absolute;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
withDefaults(
|
|
3
|
+
defineProps<{
|
|
4
|
+
orientation?: 'horizontal' | 'vertical'
|
|
5
|
+
dashed?: boolean
|
|
6
|
+
}>(),
|
|
7
|
+
{
|
|
8
|
+
orientation: 'horizontal',
|
|
9
|
+
dashed: false,
|
|
10
|
+
},
|
|
11
|
+
)
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<template>
|
|
15
|
+
<div
|
|
16
|
+
class="rs-divider"
|
|
17
|
+
:class="[
|
|
18
|
+
`rs-divider--${orientation}`,
|
|
19
|
+
{ 'rs-divider--dashed': dashed, 'rs-divider--with-label': $slots.default },
|
|
20
|
+
]"
|
|
21
|
+
role="separator"
|
|
22
|
+
:aria-orientation="orientation"
|
|
23
|
+
>
|
|
24
|
+
<span v-if="$slots.default && orientation === 'horizontal'" class="rs-divider__label">
|
|
25
|
+
<slot />
|
|
26
|
+
</span>
|
|
27
|
+
</div>
|
|
28
|
+
</template>
|
|
29
|
+
|
|
30
|
+
<style scoped>
|
|
31
|
+
.rs-divider {
|
|
32
|
+
flex-shrink: 0;
|
|
33
|
+
box-sizing: border-box;
|
|
34
|
+
border: 0;
|
|
35
|
+
background: transparent;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.rs-divider--horizontal {
|
|
39
|
+
display: flex;
|
|
40
|
+
align-items: center;
|
|
41
|
+
width: 100%;
|
|
42
|
+
margin: var(--rs-space-md) 0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.rs-divider--horizontal:not(.rs-divider--with-label)::before {
|
|
46
|
+
content: '';
|
|
47
|
+
display: block;
|
|
48
|
+
width: 100%;
|
|
49
|
+
border-top: 1px solid var(--rs-border);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.rs-divider--horizontal.rs-divider--dashed:not(.rs-divider--with-label)::before {
|
|
53
|
+
border-top-style: dashed;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.rs-divider--horizontal.rs-divider--with-label::before,
|
|
57
|
+
.rs-divider--horizontal.rs-divider--with-label::after {
|
|
58
|
+
content: '';
|
|
59
|
+
flex: 1 1 auto;
|
|
60
|
+
border-top: 1px solid var(--rs-border);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.rs-divider--horizontal.rs-divider--dashed.rs-divider--with-label::before,
|
|
64
|
+
.rs-divider--horizontal.rs-divider--dashed.rs-divider--with-label::after {
|
|
65
|
+
border-top-style: dashed;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.rs-divider__label {
|
|
69
|
+
flex-shrink: 0;
|
|
70
|
+
padding: 0 var(--rs-space-md);
|
|
71
|
+
font-size: var(--rs-font-size-xs);
|
|
72
|
+
color: var(--rs-muted);
|
|
73
|
+
white-space: nowrap;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.rs-divider--vertical {
|
|
77
|
+
display: inline-block;
|
|
78
|
+
align-self: stretch;
|
|
79
|
+
width: 0;
|
|
80
|
+
min-height: 1em;
|
|
81
|
+
margin: 0 var(--rs-space-md);
|
|
82
|
+
border-left: 1px solid var(--rs-border);
|
|
83
|
+
vertical-align: middle;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.rs-divider--vertical.rs-divider--dashed {
|
|
87
|
+
border-left-style: dashed;
|
|
88
|
+
}
|
|
89
|
+
</style>
|