niuma-ui 1.1.1 → 1.1.5
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 +52 -1
- package/package.json +1 -1
- package/src/__tests__/RsDialog.spec.ts +13 -0
- package/src/__tests__/RsDrawer.spec.ts +48 -0
- package/src/__tests__/RsIcon.spec.ts +14 -0
- package/src/__tests__/RsSelect.spec.ts +96 -0
- package/src/__tests__/RsTable.spec.ts +17 -0
- package/src/__tests__/createRsDialog.spec.ts +17 -4
- package/src/components/RsDialog.vue +14 -9
- package/src/components/RsDrawer.vue +282 -3
- package/src/components/RsMonacoEditor.vue +14 -4
- package/src/components/RsSelect.vue +10 -0
- package/src/components/RsTerminal.vue +182 -78
- package/src/components/dialog-utils.ts +3 -2
- package/src/components/drawer-utils.ts +36 -0
- package/src/components/table/rs-table.css +3 -3
- package/src/components/table-utils.ts +2 -2
- package/src/components/terminal-utils.ts +22 -0
- package/src/components/terminal-wheel.ts +32 -7
- package/src/components/use-rs-select.ts +17 -2
- package/src/composables/createRsDialog.ts +58 -7
- package/src/composables/useRsTableEditLayer.ts +1 -1
- package/src/composables/useTableCellTooltip.ts +4 -3
- package/src/dev/vite-xterm-deps.ts +8 -0
- package/src/icons/custom/postgres.ts +52 -0
- package/src/icons/lucide.ts +36 -20
- package/src/index.ts +7 -0
- package/src/locale/messages.ts +2 -0
- package/src/monaco/index.ts +1 -0
- package/src/monaco/languages/index.ts +1 -0
- package/src/monaco/languages/types.ts +4 -0
- package/src/styles.css +15 -0
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,53 @@
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [1.1.5] - 2026-08-19
|
|
10
|
+
|
|
11
|
+
### 修复
|
|
12
|
+
|
|
13
|
+
- 命令式 `rsConfirm` / `openRsDialog`:销毁改为幂等,并在测试收尾走正规 `unmount`(不再先拆 Teleport 节点)。避免 Vue 卸载时 `nextSibling` 未处理异常导致 CI 失败。
|
|
14
|
+
|
|
15
|
+
## [1.1.4] - 2026-08-19
|
|
16
|
+
|
|
17
|
+
### 新增
|
|
18
|
+
|
|
19
|
+
- `RsDialog`:`layout="form"` 作为居中轻量表单/说明窗的正式布局;`layout="confirm"` 仍可用,开发环境继续警告并视作 form 的历史别名。确认/提示请用 `RsConfirmDialog` / `rsConfirm`。
|
|
20
|
+
- `RsSelect.fillSearchWithValue`:打开下拉时把当前选中项写入搜索框并参与过滤(默认 `false`,搜索框保持空白)。
|
|
21
|
+
- **RsIcon `postgres`**:PostgreSQL 官方大象品牌图标,token `--rs-icon-postgres-accent`。
|
|
22
|
+
- `MONACO_POSTGRESQL_LANGUAGE`(`postgresql`):官方 PostgreSQL 的 Monaco languageId,走 Bridge LSP;`RsMonacoEditor` 对 postgresql / clickhouse / sqlite / sqlserver / oracle 同样不再注册实例级补全。
|
|
23
|
+
- `RsTerminal`:`selectionChange` 事件;expose `getSelection` / `hasSelection` / `getGeometry` 与类型 `RsTerminalExpose` / `RsTerminalGeometry`。
|
|
24
|
+
- `src/dev/vite-xterm-deps.ts`:供宿主 Vite `optimizeDeps` 预构建 xterm,避免控制台首次加载 504。
|
|
25
|
+
|
|
26
|
+
### 变更
|
|
27
|
+
|
|
28
|
+
- `RsSelect`:`filterOption` 默认 `true`(修复 Vue 把 boolean 联合类型当成 Boolean prop,未传入即 `false`、可搜索列表永远不过滤)。
|
|
29
|
+
- `RsSelect`:`searchable` 一律走本地过滤并关掉 Reka 内置 filter,避免手输关键字时匹配项被挡住。
|
|
30
|
+
- `RsTerminal`:`inheritAttrs: false`,`class` / `style` 落到 `.rs-terminal`;`wheelScrollModifier="shift"` 仅在备用屏(vim/top)把滚轮转成方向键,普通 shell 滚历史。
|
|
31
|
+
- `RsTableColumn.tooltip`:只写格子里看不到的额外提示;可见文本被截断时用 `ellipsis`。
|
|
32
|
+
- `RsMonacoEditor`:当前行高亮改为整行 `line`(不再只画 gutter)。
|
|
33
|
+
|
|
34
|
+
### 修复
|
|
35
|
+
|
|
36
|
+
- `RsTable`:自定义 slot + `ellipsis` 也能挂溢出 tooltip;slot 不再 `max-width: 100%` 导致溢出检测失效。
|
|
37
|
+
- `RsTable` 共享 tooltip:z-index 改为 `--rs-z-dropdown`;`findTipHost` 兼容文本节点,避免悬停到文字时丢宿主。
|
|
38
|
+
- `RsTerminal`:宿主负字距不再让 xterm 行尾被裁;xterm 6 空 viewport 滚动条不再挡住最后一列;滑块对齐 RsScrollbar;flex 项 `min-width: 0` 以便列数随容器收缩。
|
|
39
|
+
|
|
40
|
+
## [1.1.3] - 2026-08-14
|
|
41
|
+
|
|
42
|
+
### 新增
|
|
43
|
+
|
|
44
|
+
- **RsDrawer 内边缘拖拽缩放**:默认 `resizable`(`size=full` 除外)。左右改宽、上下改高,可配 `minSize` / `maxSize`,支持键盘方向键。
|
|
45
|
+
|
|
46
|
+
### 修复
|
|
47
|
+
|
|
48
|
+
- **RsDrawer 贴边定位**:覆盖 Reka DialogContent 默认居中,避免抽屉被压成标题条。
|
|
49
|
+
|
|
50
|
+
## [1.1.2] - 2026-08-14
|
|
51
|
+
|
|
52
|
+
### 修复
|
|
53
|
+
|
|
54
|
+
- **RsIcon 在 pnpm/npm 宿主中为空**:不再用 `import.meta.glob` 扫 `node_modules/@lucide/vue`(该路径在 pnpm 虚拟 store 下不存在)。改为 `import * as LucideVue from '@lucide/vue'`,走正常包解析,宿主安装 `niuma-ui` 即可使用图标。
|
|
55
|
+
|
|
9
56
|
## [1.1.1] - 2026-08-14
|
|
10
57
|
|
|
11
58
|
### 修复
|
|
@@ -106,7 +153,11 @@
|
|
|
106
153
|
|
|
107
154
|
- 1.0 之前的私有 tag(如 `v0.1.0`)仅作历史记录;新接入请依赖 `v1.0.0` 及之后版本。
|
|
108
155
|
|
|
109
|
-
[Unreleased]: https://github.com/Blair-Shang/niuma-ui/compare/v1.1.
|
|
156
|
+
[Unreleased]: https://github.com/Blair-Shang/niuma-ui/compare/v1.1.5...HEAD
|
|
157
|
+
[1.1.5]: https://github.com/Blair-Shang/niuma-ui/releases/tag/v1.1.5
|
|
158
|
+
[1.1.4]: https://github.com/Blair-Shang/niuma-ui/releases/tag/v1.1.4
|
|
159
|
+
[1.1.3]: https://github.com/Blair-Shang/niuma-ui/releases/tag/v1.1.3
|
|
160
|
+
[1.1.2]: https://github.com/Blair-Shang/niuma-ui/releases/tag/v1.1.2
|
|
110
161
|
[1.1.1]: https://github.com/Blair-Shang/niuma-ui/releases/tag/v1.1.1
|
|
111
162
|
[1.1.0]: https://github.com/Blair-Shang/niuma-ui/releases/tag/v1.1.0
|
|
112
163
|
[1.0.2]: https://github.com/Blair-Shang/niuma-ui/releases/tag/v1.0.2
|
package/package.json
CHANGED
|
@@ -107,10 +107,23 @@ describe('RsDialog', () => {
|
|
|
107
107
|
await flushPromises()
|
|
108
108
|
const content = document.body.querySelector('.rs-dialog__content')
|
|
109
109
|
expect(content?.classList.contains('rs-dialog__content--confirm')).toBe(true)
|
|
110
|
+
expect(content?.classList.contains('rs-dialog__content--form')).toBe(true)
|
|
110
111
|
expect(content?.classList.contains('rs-dialog__content--sm')).toBe(true)
|
|
111
112
|
wrapper.unmount()
|
|
112
113
|
})
|
|
113
114
|
|
|
115
|
+
it('applies form layout without confirm deprecation class-only alias', async () => {
|
|
116
|
+
const wrapper = mount(RsDialog, {
|
|
117
|
+
props: { open: true, title: '表单', width: 'sm', layout: 'form', resizable: false, fullscreenable: false },
|
|
118
|
+
attachTo: document.body,
|
|
119
|
+
})
|
|
120
|
+
await flushPromises()
|
|
121
|
+
const content = document.body.querySelector('.rs-dialog__content')
|
|
122
|
+
expect(content?.classList.contains('rs-dialog__content--form')).toBe(true)
|
|
123
|
+
expect(content?.classList.contains('rs-dialog__content--confirm')).toBe(false)
|
|
124
|
+
wrapper.unmount()
|
|
125
|
+
})
|
|
126
|
+
|
|
114
127
|
it('renders footer slot', async () => {
|
|
115
128
|
const wrapper = mount(RsDialog, {
|
|
116
129
|
props: { open: true, title: '保存' },
|
|
@@ -235,4 +235,52 @@ describe('RsDrawer', () => {
|
|
|
235
235
|
expect(closeBtn.querySelector('.rs-btn__tooltip')?.textContent).toContain('Close')
|
|
236
236
|
wrapper.unmount()
|
|
237
237
|
})
|
|
238
|
+
|
|
239
|
+
it('renders an inner-edge resize handle by default', async () => {
|
|
240
|
+
const wrapper = mount(RsDrawer, {
|
|
241
|
+
props: { open: true, title: '帮助' },
|
|
242
|
+
attachTo: document.body,
|
|
243
|
+
})
|
|
244
|
+
await flushPromises()
|
|
245
|
+
const handle = document.body.querySelector('.rs-drawer__resize') as HTMLElement | null
|
|
246
|
+
expect(handle).toBeTruthy()
|
|
247
|
+
expect(handle?.getAttribute('aria-orientation')).toBe('vertical')
|
|
248
|
+
wrapper.unmount()
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
it('hides the resize handle when size is full or resizable is false', async () => {
|
|
252
|
+
const full = mount(RsDrawer, {
|
|
253
|
+
props: { open: true, title: '全屏', size: 'full' },
|
|
254
|
+
attachTo: document.body,
|
|
255
|
+
})
|
|
256
|
+
await flushPromises()
|
|
257
|
+
expect(document.body.querySelector('.rs-drawer__resize')).toBeNull()
|
|
258
|
+
full.unmount()
|
|
259
|
+
|
|
260
|
+
const locked = mount(RsDrawer, {
|
|
261
|
+
props: { open: true, title: '固定', resizable: false },
|
|
262
|
+
attachTo: document.body,
|
|
263
|
+
})
|
|
264
|
+
await flushPromises()
|
|
265
|
+
expect(document.body.querySelector('.rs-drawer__resize')).toBeNull()
|
|
266
|
+
locked.unmount()
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
it('widens a right drawer with ArrowLeft on the resize handle', async () => {
|
|
270
|
+
const wrapper = mount(RsDrawer, {
|
|
271
|
+
props: { open: true, title: '缩放', width: 360 },
|
|
272
|
+
attachTo: document.body,
|
|
273
|
+
})
|
|
274
|
+
await flushPromises()
|
|
275
|
+
const content = document.body.querySelector('.rs-drawer__content') as HTMLElement
|
|
276
|
+
Object.defineProperty(content, 'getBoundingClientRect', {
|
|
277
|
+
configurable: true,
|
|
278
|
+
value: () => ({ width: 360, height: 800, top: 0, left: 800, right: 1160, bottom: 800, x: 800, y: 0, toJSON: () => ({}) }),
|
|
279
|
+
})
|
|
280
|
+
const handle = document.body.querySelector('.rs-drawer__resize') as HTMLElement
|
|
281
|
+
await handle.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowLeft', bubbles: true }))
|
|
282
|
+
await flushPromises()
|
|
283
|
+
expect(content.style.width).toBe('376px')
|
|
284
|
+
wrapper.unmount()
|
|
285
|
+
})
|
|
238
286
|
})
|
|
@@ -109,6 +109,15 @@ describe('RsIcon', () => {
|
|
|
109
109
|
expect(isRsIconName('sqlserver')).toBe(true)
|
|
110
110
|
})
|
|
111
111
|
|
|
112
|
+
it('loads custom postgres icon', () => {
|
|
113
|
+
const icon = resolveLucideIcon('postgres')
|
|
114
|
+
expect(icon).toBeTruthy()
|
|
115
|
+
const wrapper = mount(RsIcon, { props: { name: 'postgres' } })
|
|
116
|
+
expect(wrapper.find('svg.rs-icon').exists()).toBe(true)
|
|
117
|
+
expect(wrapper.find('.postgres-icon__mark').exists()).toBe(true)
|
|
118
|
+
expect(isRsIconName('postgres')).toBe(true)
|
|
119
|
+
})
|
|
120
|
+
|
|
112
121
|
it('loads arbitrary lucide icon by name', () => {
|
|
113
122
|
const icon = resolveLucideIcon('trash-2')
|
|
114
123
|
expect(icon).toBeTruthy()
|
|
@@ -205,6 +214,11 @@ describe('RsIcon', () => {
|
|
|
205
214
|
expect(wrapper.find('svg').attributes('style')).toContain('--rs-icon-sqlserver-accent: #34C759')
|
|
206
215
|
})
|
|
207
216
|
|
|
217
|
+
it('applies accent color to custom postgres icon', () => {
|
|
218
|
+
const wrapper = mount(RsIcon, { props: { name: 'postgres', color: '#34C759' } })
|
|
219
|
+
expect(wrapper.find('svg').attributes('style')).toContain('--rs-icon-postgres-accent: #34C759')
|
|
220
|
+
})
|
|
221
|
+
|
|
208
222
|
it('applies rotate transform', () => {
|
|
209
223
|
const wrapper = mount(RsIcon, { props: { name: 'chevron-right', rotate: 90 } })
|
|
210
224
|
expect(wrapper.find('svg').attributes('style')).toContain('rotate(90deg)')
|
|
@@ -296,6 +296,68 @@ describe('RsSelect', () => {
|
|
|
296
296
|
expect(root.props('ignoreFilter')).toBe(true)
|
|
297
297
|
})
|
|
298
298
|
|
|
299
|
+
it('enables manual filter when searchable so query hides non-matches', async () => {
|
|
300
|
+
const typeOptions = [
|
|
301
|
+
{ label: 'INT', value: 'INT' },
|
|
302
|
+
{ label: 'BIGINT', value: 'BIGINT' },
|
|
303
|
+
{ label: 'NVARCHAR', value: 'NVARCHAR' },
|
|
304
|
+
{ label: 'NVARCHAR(MAX)', value: 'NVARCHAR(MAX)' },
|
|
305
|
+
]
|
|
306
|
+
const wrapper = mount(RsSelect, {
|
|
307
|
+
props: {
|
|
308
|
+
options: typeOptions,
|
|
309
|
+
modelValue: 'BIGINT',
|
|
310
|
+
searchable: true,
|
|
311
|
+
creatable: true,
|
|
312
|
+
},
|
|
313
|
+
attachTo: document.body,
|
|
314
|
+
})
|
|
315
|
+
const root = wrapper.getComponent({ name: 'ComboboxRoot' })
|
|
316
|
+
expect(root.props('ignoreFilter')).toBe(true)
|
|
317
|
+
|
|
318
|
+
await wrapper.find('.rs-select__trigger').trigger('click')
|
|
319
|
+
await flushPromises()
|
|
320
|
+
const input = document.querySelector('.rs-select__search') as HTMLInputElement | null
|
|
321
|
+
expect(input).toBeTruthy()
|
|
322
|
+
input!.value = 'NVA'
|
|
323
|
+
input!.dispatchEvent(new Event('input', { bubbles: true }))
|
|
324
|
+
await flushPromises()
|
|
325
|
+
|
|
326
|
+
const labels = [...document.querySelectorAll('.rs-select__item-label')].map(
|
|
327
|
+
(el) => el.textContent?.trim() ?? '',
|
|
328
|
+
)
|
|
329
|
+
expect(labels).toContain('NVARCHAR')
|
|
330
|
+
expect(labels).toContain('NVARCHAR(MAX)')
|
|
331
|
+
expect(labels).not.toContain('INT')
|
|
332
|
+
expect(labels).not.toContain('BIGINT')
|
|
333
|
+
expect(document.querySelector('.rs-select__item--create')?.textContent).toContain('NVA')
|
|
334
|
+
wrapper.unmount()
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
it('filterOption false keeps the full list while typing', async () => {
|
|
338
|
+
const wrapper = mount(RsSelect, {
|
|
339
|
+
props: {
|
|
340
|
+
options,
|
|
341
|
+
modelValue: '',
|
|
342
|
+
searchable: true,
|
|
343
|
+
filterOption: false,
|
|
344
|
+
},
|
|
345
|
+
attachTo: document.body,
|
|
346
|
+
})
|
|
347
|
+
await wrapper.find('.rs-select__trigger').trigger('click')
|
|
348
|
+
await flushPromises()
|
|
349
|
+
const input = document.querySelector('.rs-select__search') as HTMLInputElement | null
|
|
350
|
+
input!.value = 'zzz'
|
|
351
|
+
input!.dispatchEvent(new Event('input', { bubbles: true }))
|
|
352
|
+
await flushPromises()
|
|
353
|
+
const labels = [...document.querySelectorAll('.rs-select__item-label')].map(
|
|
354
|
+
(el) => el.textContent?.trim() ?? '',
|
|
355
|
+
)
|
|
356
|
+
expect(labels).toContain('GPT-4o')
|
|
357
|
+
expect(labels).toContain('Claude')
|
|
358
|
+
wrapper.unmount()
|
|
359
|
+
})
|
|
360
|
+
|
|
299
361
|
it('creatable enables search and commits typed value on Enter', async () => {
|
|
300
362
|
const wrapper = mount(RsSelect, {
|
|
301
363
|
props: { options, modelValue: '', creatable: true },
|
|
@@ -574,6 +636,40 @@ describe('RsSelect', () => {
|
|
|
574
636
|
wrapper.unmount()
|
|
575
637
|
})
|
|
576
638
|
|
|
639
|
+
it('does not fill search with the selected value when opening', async () => {
|
|
640
|
+
const wrapper = mount(RsSelect, {
|
|
641
|
+
props: { options, modelValue: 'claude', searchable: true },
|
|
642
|
+
attachTo: document.body,
|
|
643
|
+
})
|
|
644
|
+
await wrapper.find('.rs-select__trigger').trigger('click')
|
|
645
|
+
await flushPromises()
|
|
646
|
+
const input = document.querySelector('.rs-select__search') as HTMLInputElement | null
|
|
647
|
+
expect(input?.value).toBe('')
|
|
648
|
+
const labels = [...document.querySelectorAll('.rs-select__item-label')].map(
|
|
649
|
+
(el) => el.textContent?.trim() ?? '',
|
|
650
|
+
)
|
|
651
|
+
expect(labels).toContain('GPT-4o')
|
|
652
|
+
expect(labels).toContain('Claude')
|
|
653
|
+
wrapper.unmount()
|
|
654
|
+
})
|
|
655
|
+
|
|
656
|
+
it('fills search with the selected label when fillSearchWithValue', async () => {
|
|
657
|
+
const wrapper = mount(RsSelect, {
|
|
658
|
+
props: { options, modelValue: 'claude', searchable: true, fillSearchWithValue: true },
|
|
659
|
+
attachTo: document.body,
|
|
660
|
+
})
|
|
661
|
+
await wrapper.find('.rs-select__trigger').trigger('click')
|
|
662
|
+
await flushPromises()
|
|
663
|
+
const input = document.querySelector('.rs-select__search') as HTMLInputElement | null
|
|
664
|
+
expect(input?.value).toBe('Claude')
|
|
665
|
+
const labels = [...document.querySelectorAll('.rs-select__item-label')].map(
|
|
666
|
+
(el) => el.textContent?.trim() ?? '',
|
|
667
|
+
)
|
|
668
|
+
expect(labels).toContain('Claude')
|
|
669
|
+
expect(labels).not.toContain('GPT-4o')
|
|
670
|
+
wrapper.unmount()
|
|
671
|
+
})
|
|
672
|
+
|
|
577
673
|
it('renders dropdownRender instead of the default panel body', async () => {
|
|
578
674
|
const wrapper = mount(RsSelect, {
|
|
579
675
|
props: { options, modelValue: '' },
|
|
@@ -372,6 +372,23 @@ describe('RsTable', () => {
|
|
|
372
372
|
expect(cell.attributes('style')).toContain('width: 80px')
|
|
373
373
|
})
|
|
374
374
|
|
|
375
|
+
it('attaches overflow tooltip on slotted ellipsis columns', () => {
|
|
376
|
+
const wrapper = mount(RsTable, {
|
|
377
|
+
props: {
|
|
378
|
+
columns: [{ key: 'name', title: '名称', ellipsis: true, width: 80 }],
|
|
379
|
+
data: [{ id: '1', name: '很长的名字' }],
|
|
380
|
+
},
|
|
381
|
+
slots: {
|
|
382
|
+
name: ({ row }: { row: { name: string } }) => row.name,
|
|
383
|
+
},
|
|
384
|
+
})
|
|
385
|
+
const tip = wrapper.find('.rs-table__cell-tip')
|
|
386
|
+
expect(tip.exists()).toBe(true)
|
|
387
|
+
expect(tip.attributes('data-rs-table-tip-mode')).toBe('overflow')
|
|
388
|
+
expect(tip.attributes('data-rs-table-tip-text')).toBe('很长的名字')
|
|
389
|
+
wrapper.unmount()
|
|
390
|
+
})
|
|
391
|
+
|
|
375
392
|
it('shows shared cell tooltip on ellipsis overflow hover', async () => {
|
|
376
393
|
vi.useFakeTimers()
|
|
377
394
|
const wrapper = mount(RsTable, {
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { h } from 'vue'
|
|
2
2
|
import { afterEach, describe, expect, it } from 'vitest'
|
|
3
3
|
import { flushPromises } from '@vue/test-utils'
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
destroyAllRsDialogHosts,
|
|
6
|
+
openRsDialog,
|
|
7
|
+
rsConfirm,
|
|
8
|
+
} from '../composables/createRsDialog'
|
|
5
9
|
|
|
6
10
|
describe('createRsDialog', () => {
|
|
7
11
|
afterEach(() => {
|
|
8
|
-
|
|
9
|
-
el.closest('body > div')?.remove()
|
|
10
|
-
})
|
|
12
|
+
destroyAllRsDialogHosts()
|
|
11
13
|
})
|
|
12
14
|
|
|
13
15
|
it('rsConfirm resolves true when confirm clicked', async () => {
|
|
@@ -83,6 +85,17 @@ describe('createRsDialog', () => {
|
|
|
83
85
|
await expect(promise).resolves.toBe(true)
|
|
84
86
|
})
|
|
85
87
|
|
|
88
|
+
it('destroy does not throw after portal node was removed', async () => {
|
|
89
|
+
const handle = openRsDialog({
|
|
90
|
+
title: '残留',
|
|
91
|
+
showOverlay: false,
|
|
92
|
+
body: () => 'portal',
|
|
93
|
+
})
|
|
94
|
+
await flushPromises()
|
|
95
|
+
document.body.querySelector('.rs-dialog__content')?.closest('body > div')?.remove()
|
|
96
|
+
expect(() => handle.destroy()).not.toThrow()
|
|
97
|
+
})
|
|
98
|
+
|
|
86
99
|
it('openRsDialog mounts builtin footer and can destroy', async () => {
|
|
87
100
|
const handle = openRsDialog({
|
|
88
101
|
title: '命令式',
|
|
@@ -47,13 +47,14 @@ const props = withDefaults(
|
|
|
47
47
|
/**
|
|
48
48
|
* 宽度:`sm` / `md` / `lg`,或 number(px) / `px` / `rem` / `%`。
|
|
49
49
|
* window:打开时折成像素后居中(`%` 相对视口扣除 inset),之后可拖拽缩放。
|
|
50
|
-
* confirm:自定义宽度按 CSS 写入。
|
|
50
|
+
* form / confirm:自定义宽度按 CSS 写入。
|
|
51
51
|
*/
|
|
52
52
|
width?: RsDialogWidth
|
|
53
53
|
tone?: RsFeedbackTone
|
|
54
54
|
/**
|
|
55
|
-
* window
|
|
56
|
-
*
|
|
55
|
+
* window:可拖拽/缩放工作窗。
|
|
56
|
+
* form:居中轻量表单/说明窗(高度随内容,默认不可缩放)。
|
|
57
|
+
* confirm:历史别名,等同 form;确认/提示请用 RsConfirmDialog。
|
|
57
58
|
*/
|
|
58
59
|
layout?: RsDialogLayout
|
|
59
60
|
draggable?: boolean
|
|
@@ -143,8 +144,8 @@ const attrs = useAttrs()
|
|
|
143
144
|
const slots = useSlots()
|
|
144
145
|
const { t } = useRsI18n()
|
|
145
146
|
|
|
146
|
-
const
|
|
147
|
-
const
|
|
147
|
+
const isCompactLayout = computed(() => props.layout === 'form' || props.layout === 'confirm')
|
|
148
|
+
const isWindowLayout = computed(() => !isCompactLayout.value)
|
|
148
149
|
const enableDraggable = computed(() => props.draggable && isWindowLayout.value)
|
|
149
150
|
const enableResizable = computed(() => props.resizable && isWindowLayout.value)
|
|
150
151
|
const deferBodyMount = computed(() => props.deferBodyMount ?? isWindowLayout.value)
|
|
@@ -263,7 +264,8 @@ const contentClass = computed(() => {
|
|
|
263
264
|
? `rs-dialog__content--${props.width}`
|
|
264
265
|
: 'rs-dialog__content--custom-width'
|
|
265
266
|
return [
|
|
266
|
-
|
|
267
|
+
isCompactLayout.value ? 'rs-dialog__content--form' : 'rs-dialog__content--window',
|
|
268
|
+
props.layout === 'confirm' ? 'rs-dialog__content--confirm' : undefined,
|
|
267
269
|
presetClass,
|
|
268
270
|
`rs-dialog__content--tone-${props.tone}`,
|
|
269
271
|
{
|
|
@@ -368,7 +370,7 @@ const {
|
|
|
368
370
|
initialWidth: initialWidthPx,
|
|
369
371
|
draggable: enableDraggable,
|
|
370
372
|
resizable: enableResizable,
|
|
371
|
-
compact:
|
|
373
|
+
compact: isCompactLayout,
|
|
372
374
|
boundsTransition: toRef(props, 'boundsTransition'),
|
|
373
375
|
})
|
|
374
376
|
|
|
@@ -551,7 +553,8 @@ defineExpose({
|
|
|
551
553
|
color: var(--rs-dialog-title-fg);
|
|
552
554
|
}
|
|
553
555
|
|
|
554
|
-
/* 亮色
|
|
556
|
+
/* 亮色 form:轻微 vibrancy;window 用实底避免 CEF 下 blur 合成开销 */
|
|
557
|
+
[data-rs-theme='light'] .rs-dialog__content--form,
|
|
555
558
|
[data-rs-theme='light'] .rs-dialog__content--confirm {
|
|
556
559
|
background: color-mix(in srgb, var(--rs-dialog-bg) 94%, transparent);
|
|
557
560
|
backdrop-filter: blur(24px) saturate(180%);
|
|
@@ -560,10 +563,12 @@ defineExpose({
|
|
|
560
563
|
[data-rs-theme='light'] .rs-dialog__content--window {
|
|
561
564
|
background: var(--rs-dialog-bg);
|
|
562
565
|
}
|
|
563
|
-
/*
|
|
566
|
+
/* form 布局:缩放 + 淡入(居中,含 translate);苹果 sheet 缓动曲线 */
|
|
567
|
+
.rs-dialog__content--form[data-state='open'],
|
|
564
568
|
.rs-dialog__content--confirm[data-state='open'] {
|
|
565
569
|
animation: rs-dialog-pop-in 240ms cubic-bezier(0.32, 0.72, 0, 1);
|
|
566
570
|
}
|
|
571
|
+
.rs-dialog__content--form[data-state='closed'],
|
|
567
572
|
.rs-dialog__content--confirm[data-state='closed'] {
|
|
568
573
|
animation: rs-dialog-pop-out 180ms ease;
|
|
569
574
|
}
|