niuma-ui 1.0.2 → 1.1.1
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 +37 -1
- package/package.json +1 -1
- package/src/__tests__/RsDatePicker.spec.ts +21 -0
- package/src/__tests__/RsDialog.spec.ts +61 -0
- package/src/__tests__/RsForm.spec.ts +5 -5
- package/src/__tests__/RsFormItem.spec.ts +478 -0
- package/src/__tests__/RsFormList.spec.ts +109 -0
- package/src/__tests__/RsInput.spec.ts +93 -0
- package/src/__tests__/RsInputNumber.spec.ts +33 -0
- package/src/__tests__/RsSelect.spec.ts +423 -1
- package/src/__tests__/RsSwitch.spec.ts +30 -0
- package/src/__tests__/RsTimePicker.spec.ts +9 -0
- package/src/__tests__/RsTooltip.spec.ts +43 -0
- package/src/__tests__/css-token.spec.ts +51 -0
- package/src/__tests__/date-picker-utils.spec.ts +49 -0
- package/src/__tests__/dialog-utils.spec.ts +43 -0
- package/src/__tests__/form-path.spec.ts +46 -0
- package/src/__tests__/form-rules.spec.ts +63 -0
- package/src/__tests__/theme.spec.ts +2 -0
- package/src/components/RsAlert.vue +1 -1
- package/src/components/RsAvatar.vue +1 -1
- package/src/components/RsBadge.vue +1 -1
- package/src/components/RsBreadcrumb.vue +1 -1
- package/src/components/RsButton.vue +4 -4
- package/src/components/RsCalendarGrid.vue +3 -3
- package/src/components/RsCard.vue +4 -4
- package/src/components/RsCodeBlock.vue +4 -4
- package/src/components/RsCodeEditor.vue +8 -8
- package/src/components/RsConfirmDialog.vue +2 -2
- package/src/components/RsContextMenu.vue +6 -4
- package/src/components/RsDatePicker.vue +75 -31
- package/src/components/RsDateTimePicker.vue +9 -1
- package/src/components/RsDescriptions.vue +2 -2
- package/src/components/RsDialog.vue +26 -16
- package/src/components/RsDrawer.vue +1 -1
- package/src/components/RsDropdown.vue +1 -1
- package/src/components/RsDynamicTags.vue +18 -4
- package/src/components/RsEmpty.vue +1 -1
- package/src/components/RsForm.vue +68 -1
- package/src/components/RsFormItem.vue +423 -0
- package/src/components/RsFormItemControl.vue +21 -0
- package/src/components/RsFormList.vue +117 -0
- package/src/components/RsInput.vue +411 -18
- package/src/components/RsInputNumber.vue +13 -4
- package/src/components/RsLink.vue +1 -1
- package/src/components/RsMarkdown.vue +5 -5
- package/src/components/RsMenu.vue +2 -2
- package/src/components/RsMonacoEditor.vue +9 -3
- package/src/components/RsPagination.vue +1 -1
- package/src/components/RsProseEditor.vue +3 -3
- package/src/components/RsSelect.vue +332 -611
- package/src/components/RsSidebarGroup.vue +1 -1
- package/src/components/RsSplitPane.vue +22 -1
- package/src/components/RsStatCard.vue +2 -2
- package/src/components/RsSteps.vue +2 -2
- package/src/components/RsSwitch.vue +20 -7
- package/src/components/RsTabs.vue +9 -9
- package/src/components/RsTag.vue +1 -1
- package/src/components/RsTerminal.vue +58 -19
- package/src/components/RsTimePicker.vue +28 -4
- package/src/components/RsTimePickerColumns.vue +4 -4
- package/src/components/RsToaster.vue +7 -7
- package/src/components/RsTooltip.vue +10 -1
- package/src/components/RsTooltipProvider.vue +11 -1
- package/src/components/RsTree.vue +6 -6
- package/src/components/RsUpload.vue +2 -2
- package/src/components/date-picker-utils.ts +113 -2
- package/src/components/dialog-utils.ts +37 -2
- package/src/components/form-item-control.ts +55 -0
- package/src/components/form-path.ts +92 -0
- package/src/components/form-rules.ts +64 -10
- package/src/components/form-utils.ts +94 -2
- package/src/components/select-utils.ts +272 -12
- package/src/components/table/RsTableCellEditor.vue +13 -5
- package/src/components/table/RsTableHeaderFilter.vue +1 -1
- package/src/components/table/rs-table.css +1 -1
- package/src/components/use-rs-select.ts +436 -0
- package/src/composables/useRsConfig.ts +10 -3
- package/src/composables/useRsI18n.ts +16 -5
- package/src/composables/useRsTableCore.ts +3 -1
- package/src/composables/useRsTableHeadless.ts +10 -2
- package/src/index.ts +69 -1
- package/src/locale/interpolate.ts +32 -0
- package/src/locale/messages.ts +10 -0
- package/src/styles.css +68 -12
- package/src/theme/brand.example.css +4 -1
- package/src/theme/css-token.ts +107 -0
- package/src/theme/types.ts +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,40 @@
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [1.1.1] - 2026-08-14
|
|
10
|
+
|
|
11
|
+
### 修复
|
|
12
|
+
|
|
13
|
+
- `RsTableCellEditor`:单元格 Select 的 `update:model-value` 对齐 `RsSelectModelValue`(含 number / 多选 / labelInValue)。
|
|
14
|
+
- `useRsTableCore`:`compact` / `size` 为 undefined 时回退默认值,避免宿主 `vue-tsc` 失败。
|
|
15
|
+
- `useRsTableHeadless`:`rowKey` 区分无参 getter 与行访问器,避免误当成需传入 row 的函数调用。
|
|
16
|
+
|
|
17
|
+
## [1.1.0] - 2026-08-14
|
|
18
|
+
|
|
19
|
+
### 新增
|
|
20
|
+
|
|
21
|
+
- `RsFormItem`:对标 Ant Design Form.Item / Element `ElFormItem`。声明 `name` 后成为字段唯一注册点;内置 `RsInput` 等在 Item 内不再重复注册。支持 `help` / `extra` / `validateStatus` / `#label` / `noStyle`。
|
|
22
|
+
- `RsForm.model`、NamePath(`user.email`)、`getFieldsValue` / `setFieldsValue` / `scrollToField`、`validateMessages`。
|
|
23
|
+
- `RsFormList`:动态数组字段(`add` / `remove` / `move`),子 Item 相对 name 自动拼前缀。
|
|
24
|
+
- `RsFormItem.dependencies`:依赖字段变化时重校验;`validator(value, { getFieldValue, getFieldsValue })` 可读整表。
|
|
25
|
+
- `RsSwitch.checkedValue` / `uncheckedValue`:自定义打开/关闭写入 v-model 的值(默认 `true` / `false`,既有 boolean 用法不变)。
|
|
26
|
+
- `RsDatePicker` / `RsDateTimePicker`:`valueFormat` 对齐 Element Plus / Ant Design Vue——展示仍为墙钟,绑定可选用 `string`(默认)、`timestamp`、`iso`(本地偏移 RFC3339),或任意 dayjs 模板。`iso` 空值为 `null`。
|
|
27
|
+
- `RsSelect`:`option.value` / `v-model` 支持 `string | number`(数字 `0` 可回显);`filterOption` / `optionFilterProp` / `filterSort`;多选 `maxTagCount` / `maxTagTextLength` / `multipleLimit` / `tokenSeparators`;`labelInValue`;`fieldNames` / `optionLabelProp`;`v-model:searchValue`;`listHeight` / `placement` / `popupClassName` / `status` / `showArrow` / `getPopupContainer`;事件 `select` / `deselect` / `clear`;插槽 `#prefix` `#option` `#tag` `#header` `#footer` `#dropdownRender` `#suffixIcon` `#clearIcon` `#empty` `#loading`。选择逻辑抽到 `use-rs-select.ts`。
|
|
28
|
+
- `RsInput`:框内 affix 顺序固定为字数 / 清除 / 密码显隐 / 自定义 `suffix`;新增框外连体 `addonBefore` / `addonAfter`(及同名插槽);IME `compositionstart/end` 期间不触发校验与 `pressEnter`。
|
|
29
|
+
- `RsInput.addonAfterIcon` / `addonAfterIconLabel` / `addonAfterClick`:选择器等后置图标按钮走连体 `addonAfter`,由组件自绘;连体外壳统一外边框,避免 input/addon 拼缝。
|
|
30
|
+
- `hasByNamePath`:判断 NamePath 是否已存在于对象树(含叶子为 null / undefined)。
|
|
31
|
+
- 排版子系统 token:`--rs-font-serif`、`--rs-code-font-*`、`--rs-prose-font-*`、`--rs-terminal-font-*`。JS 导出 `readCssVar` / `readCssLengthPx` / `readCodeFontFamily` / `readTerminalFontFamily` 等,供 Monaco / xterm 读取。
|
|
32
|
+
|
|
33
|
+
### 变更
|
|
34
|
+
|
|
35
|
+
- Form 校验 / `resetFields` 只收集带 `name` 的字段(无 name 不进字段表,避免弹层搜索框误校验)。请给控件加 `name` 或包 `RsFormItem`。
|
|
36
|
+
- 封装开源组件排版对齐 token:CodeMirror / Monaco / xterm / marked / vue-sonner 不再硬编码字体栈或 px。`RsTerminal` 未传 `fontFamily` / `fontSize` / `fontWeight` 时改为读 CSS token(默认 `--rs-font-size-sm` / regular,不再写死 13px / 300)。组件字重改为 `--rs-font-weight-*`。
|
|
37
|
+
- `.rs-field--label-left`:有 `--rs-field-label-width` 时固定标签列宽(不再 `max-content` 撑开),栅格内控件起点对齐;过长标签省略。
|
|
38
|
+
- `RsFormItem`:子控件已绑定 v-model 时不再覆盖(避免 switch 的 Y/N 被注入成非 boolean)。
|
|
39
|
+
- `RsDatePicker` / `RsTimePicker` / `RsSelect`:校验失败时红框画在 trigger 上(与 `RsInput` 一致)。未声明的 attrs(`class` / `style` / `aria-*`)同样落到 trigger;`v-model` 与事件签名不变。
|
|
40
|
+
- `RsTooltip` / `RsTooltipProvider`:默认开启 `ignoreNonKeyboardFocus`(仅 `:focus-visible` 因焦点打开 tip),避免 Dialog/Popover 关闭回焦后误开提示;悬停与键盘聚焦行为不变。
|
|
41
|
+
- `RsInputNumber` / `RsTabs`:用 `v-on` 条件对象绑定 `wheel`(避免 `@wheel="cond ? fn : undefined"` 被编译成常驻包装函数仍挂非 passive 监听);Tabs 导航 `@scroll` 改为 passive。
|
|
42
|
+
|
|
9
43
|
## [1.0.2] - 2026-08-13
|
|
10
44
|
|
|
11
45
|
### 新增
|
|
@@ -72,7 +106,9 @@
|
|
|
72
106
|
|
|
73
107
|
- 1.0 之前的私有 tag(如 `v0.1.0`)仅作历史记录;新接入请依赖 `v1.0.0` 及之后版本。
|
|
74
108
|
|
|
75
|
-
[Unreleased]: https://github.com/Blair-Shang/niuma-ui/compare/v1.
|
|
109
|
+
[Unreleased]: https://github.com/Blair-Shang/niuma-ui/compare/v1.1.1...HEAD
|
|
110
|
+
[1.1.1]: https://github.com/Blair-Shang/niuma-ui/releases/tag/v1.1.1
|
|
111
|
+
[1.1.0]: https://github.com/Blair-Shang/niuma-ui/releases/tag/v1.1.0
|
|
76
112
|
[1.0.2]: https://github.com/Blair-Shang/niuma-ui/releases/tag/v1.0.2
|
|
77
113
|
[1.0.1]: https://github.com/Blair-Shang/niuma-ui/releases/tag/v1.0.1
|
|
78
114
|
[1.0.0]: https://github.com/Blair-Shang/niuma-ui/releases/tag/v1.0.0
|
package/package.json
CHANGED
|
@@ -10,6 +10,18 @@ describe('RsDatePicker', () => {
|
|
|
10
10
|
expect(wrapper.classes()).toContain('rs-field')
|
|
11
11
|
})
|
|
12
12
|
|
|
13
|
+
it('displays iso v-model as wall-clock text', () => {
|
|
14
|
+
const wrapper = mount(RsDatePicker, {
|
|
15
|
+
props: {
|
|
16
|
+
modelValue: '2025-06-16T14:30:00+08:00',
|
|
17
|
+
withTime: true,
|
|
18
|
+
valueFormat: 'iso',
|
|
19
|
+
},
|
|
20
|
+
})
|
|
21
|
+
expect(wrapper.find('.rs-date-picker__trigger--placeholder').exists()).toBe(false)
|
|
22
|
+
expect(wrapper.find('.rs-date-picker__value').text()).toBe('2025-06-16 14:30:00')
|
|
23
|
+
})
|
|
24
|
+
|
|
13
25
|
it('shows formatted value on trigger when model is set', () => {
|
|
14
26
|
const wrapper = mount(RsDatePicker, {
|
|
15
27
|
props: { modelValue: '2025-06-16' },
|
|
@@ -85,4 +97,13 @@ describe('RsDatePicker', () => {
|
|
|
85
97
|
})
|
|
86
98
|
expect(wrapper.find('.rs-date-picker__trigger').attributes('disabled')).toBeDefined()
|
|
87
99
|
})
|
|
100
|
+
|
|
101
|
+
it('marks invalid on the trigger instead of the field wrapper', () => {
|
|
102
|
+
const wrapper = mount(RsDatePicker, { props: { modelValue: '', invalid: true } })
|
|
103
|
+
expect(wrapper.find('.rs-date-picker__trigger').classes()).toContain(
|
|
104
|
+
'rs-date-picker__trigger--invalid',
|
|
105
|
+
)
|
|
106
|
+
expect(wrapper.find('.rs-date-picker__trigger').attributes('aria-invalid')).toBe('true')
|
|
107
|
+
expect(wrapper.attributes('aria-invalid')).toBeUndefined()
|
|
108
|
+
})
|
|
88
109
|
})
|
|
@@ -69,6 +69,23 @@ describe('RsDialog', () => {
|
|
|
69
69
|
wrapper.unmount()
|
|
70
70
|
})
|
|
71
71
|
|
|
72
|
+
it('centers window layout when width is a viewport percentage', async () => {
|
|
73
|
+
const wrapper = mount(RsDialog, {
|
|
74
|
+
props: { open: true, title: '百分比宽度', width: '90%' },
|
|
75
|
+
attachTo: document.body,
|
|
76
|
+
})
|
|
77
|
+
await flushPromises()
|
|
78
|
+
const content = document.body.querySelector('.rs-dialog__content') as HTMLElement
|
|
79
|
+
const width = Number.parseFloat(content.style.width)
|
|
80
|
+
const left = Number.parseFloat(content.style.left)
|
|
81
|
+
const available = window.innerWidth - 32
|
|
82
|
+
const expectedWidth = Math.min(available, Math.max(320, Math.round(available * 0.9)))
|
|
83
|
+
expect(width).toBe(expectedWidth)
|
|
84
|
+
expect(left).toBeCloseTo(16 + (available - expectedWidth) / 2, 1)
|
|
85
|
+
expect(content.style.maxWidth).toBe('')
|
|
86
|
+
wrapper.unmount()
|
|
87
|
+
})
|
|
88
|
+
|
|
72
89
|
it('applies window layout with resize handles by default', async () => {
|
|
73
90
|
const wrapper = mount(RsDialog, {
|
|
74
91
|
props: { open: true, title: '测试', width: 'sm' },
|
|
@@ -366,6 +383,50 @@ describe('RsDialog', () => {
|
|
|
366
383
|
await flushPromises()
|
|
367
384
|
expect(target.querySelector('.rs-dialog__content')).not.toBeNull()
|
|
368
385
|
wrapper.unmount()
|
|
386
|
+
target.remove()
|
|
387
|
+
})
|
|
388
|
+
|
|
389
|
+
it('re-teleports to body while fullscreen, then restores custom target', async () => {
|
|
390
|
+
const target = document.createElement('div')
|
|
391
|
+
target.id = 'rs-dialog-fullscreen-target'
|
|
392
|
+
document.body.appendChild(target)
|
|
393
|
+
|
|
394
|
+
const wrapper = mount(RsDialog, {
|
|
395
|
+
props: {
|
|
396
|
+
open: true,
|
|
397
|
+
title: '全屏挂载',
|
|
398
|
+
layout: 'window',
|
|
399
|
+
fullscreenable: true,
|
|
400
|
+
teleportTo: '#rs-dialog-fullscreen-target',
|
|
401
|
+
},
|
|
402
|
+
attachTo: document.body,
|
|
403
|
+
})
|
|
404
|
+
|
|
405
|
+
await flushPromises()
|
|
406
|
+
expect(target.querySelector('.rs-dialog__content')).not.toBeNull()
|
|
407
|
+
|
|
408
|
+
const enterFullscreenBtn = target.querySelectorAll('.rs-dialog__actions button')[0] as HTMLElement
|
|
409
|
+
await enterFullscreenBtn.click()
|
|
410
|
+
await flushPromises()
|
|
411
|
+
|
|
412
|
+
expect(target.querySelector('.rs-dialog__content')).toBeNull()
|
|
413
|
+
const bodyContent = document.body.querySelector('.rs-dialog__content') as HTMLElement
|
|
414
|
+
expect(bodyContent).not.toBeNull()
|
|
415
|
+
expect(bodyContent.classList.contains('rs-dialog__content--fullscreen')).toBe(true)
|
|
416
|
+
expect(target.contains(bodyContent)).toBe(false)
|
|
417
|
+
|
|
418
|
+
const exitFullscreenBtn = document.body.querySelectorAll(
|
|
419
|
+
'.rs-dialog__actions button',
|
|
420
|
+
)[0] as HTMLElement
|
|
421
|
+
await exitFullscreenBtn.click()
|
|
422
|
+
await flushPromises()
|
|
423
|
+
expect(target.querySelector('.rs-dialog__content')).not.toBeNull()
|
|
424
|
+
expect(
|
|
425
|
+
target.querySelector('.rs-dialog__content')?.classList.contains('rs-dialog__content--fullscreen'),
|
|
426
|
+
).toBe(false)
|
|
427
|
+
|
|
428
|
+
wrapper.unmount()
|
|
429
|
+
target.remove()
|
|
369
430
|
})
|
|
370
431
|
|
|
371
432
|
it('passes modal=false to DialogRoot for non-modal usage', async () => {
|
|
@@ -125,7 +125,7 @@ describe('RsForm', () => {
|
|
|
125
125
|
},
|
|
126
126
|
template: `
|
|
127
127
|
<RsForm ref="formRef">
|
|
128
|
-
<RsInput v-model="email" rule="email" required />
|
|
128
|
+
<RsInput v-model="email" name="email" rule="email" required />
|
|
129
129
|
</RsForm>
|
|
130
130
|
`,
|
|
131
131
|
})
|
|
@@ -146,7 +146,7 @@ describe('RsForm', () => {
|
|
|
146
146
|
},
|
|
147
147
|
template: `
|
|
148
148
|
<RsForm ref="formRef">
|
|
149
|
-
<RsInput v-model="email" rule="email" required />
|
|
149
|
+
<RsInput v-model="email" name="email" rule="email" required />
|
|
150
150
|
</RsForm>
|
|
151
151
|
`,
|
|
152
152
|
})
|
|
@@ -167,7 +167,7 @@ describe('RsForm', () => {
|
|
|
167
167
|
},
|
|
168
168
|
template: `
|
|
169
169
|
<RsForm ref="formRef">
|
|
170
|
-
<RsInput v-model="name" />
|
|
170
|
+
<RsInput v-model="name" name="name" />
|
|
171
171
|
</RsForm>
|
|
172
172
|
`,
|
|
173
173
|
})
|
|
@@ -191,7 +191,7 @@ describe('RsForm', () => {
|
|
|
191
191
|
},
|
|
192
192
|
template: `
|
|
193
193
|
<RsForm ref="formRef">
|
|
194
|
-
<RsInput v-model="email" rule="email" required />
|
|
194
|
+
<RsInput v-model="email" name="email" rule="email" required />
|
|
195
195
|
</RsForm>
|
|
196
196
|
`,
|
|
197
197
|
})
|
|
@@ -238,7 +238,7 @@ describe('RsForm', () => {
|
|
|
238
238
|
},
|
|
239
239
|
template: `
|
|
240
240
|
<RsForm ref="formRef">
|
|
241
|
-
<RsSelect v-model="role" :options="roleOptions" required />
|
|
241
|
+
<RsSelect v-model="role" name="role" :options="roleOptions" required />
|
|
242
242
|
</RsForm>
|
|
243
243
|
`,
|
|
244
244
|
})
|