@veltra/desktop 1.7.10 → 1.7.12

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.
Files changed (42) hide show
  1. package/dist/components/batch-edit/batch-edit-form.js +2 -6
  2. package/dist/components/batch-edit/batch-edit-form.js.map +1 -1
  3. package/dist/components/batch-edit/batch-edit.js +0 -1
  4. package/dist/components/batch-edit/batch-edit.js.map +1 -1
  5. package/dist/components/button/button.js +0 -1
  6. package/dist/components/button/button.js.map +1 -1
  7. package/dist/components/drawer/drawer.js +18 -10
  8. package/dist/components/drawer/drawer.js.map +1 -1
  9. package/dist/components/drawer/style2.css +15 -0
  10. package/dist/components/form/form.js.map +1 -1
  11. package/dist/components/form/form.vue_vue_type_script_setup_true_lang.js +12 -13
  12. package/dist/components/form/form.vue_vue_type_script_setup_true_lang.js.map +1 -1
  13. package/dist/components/form/use-node-interceptor.js +31 -3
  14. package/dist/components/form/use-node-interceptor.js.map +1 -1
  15. package/dist/components/form-item/form-item.js.map +1 -1
  16. package/dist/components/form-item/form-item.vue_vue_type_script_setup_true_lang.js +14 -8
  17. package/dist/components/form-item/form-item.vue_vue_type_script_setup_true_lang.js.map +1 -1
  18. package/dist/types/action.d.ts +6 -1
  19. package/dist/types/batch-edit.d.ts +0 -2
  20. package/dist/types/button.d.ts +5 -1
  21. package/dist/types/drawer.d.ts +6 -3
  22. package/dist/types/form.d.ts +0 -2
  23. package/dist/types/layout.d.ts +2 -0
  24. package/package.json +5 -5
  25. package/src/components/batch-edit/__test__/batch-edit.test.ts +0 -138
  26. package/src/components/batch-edit/batch-edit-form.vue +2 -16
  27. package/src/components/button/__test__/button.test.ts +48 -0
  28. package/src/components/button/button.vue +1 -8
  29. package/src/components/drawer/__test__/drawer.test.ts +118 -0
  30. package/src/components/drawer/drawer.vue +5 -2
  31. package/src/components/drawer/style.scss +15 -0
  32. package/src/components/form/__test__/form.test.ts +167 -31
  33. package/src/components/form/form.vue +10 -11
  34. package/src/components/form/use-node-interceptor.ts +36 -2
  35. package/src/components/form-item/__test__/form-item.test.ts +40 -10
  36. package/src/components/form-item/form-item.vue +14 -17
  37. package/src/types/action.ts +6 -1
  38. package/src/types/batch-edit.ts +0 -2
  39. package/src/types/button.ts +5 -1
  40. package/src/types/drawer.ts +6 -3
  41. package/src/types/form.ts +0 -2
  42. package/src/types/layout.ts +2 -0
@@ -1,41 +1,22 @@
1
1
  import { describe, expect, it } from 'vitest'
2
- import { createApp, h, nextTick, reactive } from 'vue'
2
+ import { createApp, defineComponent, h, nextTick, reactive, ref } from 'vue'
3
3
 
4
+ import { UInput } from '../../input'
4
5
  import { USelect } from '../../select'
5
6
  import { UForm } from '../index'
6
7
 
7
- const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
8
-
9
8
  const behaviorOptions = [
10
9
  { label: '事件', value: 'event' },
11
10
  { label: '接口', value: 'api' }
12
11
  ]
13
12
 
14
- async function openSelect(host: HTMLElement) {
15
- const input = host.querySelector('input')!
16
- input.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
17
- await nextTick()
18
- await nextTick()
19
- }
20
-
21
- async function clickSelectOption(label: string) {
22
- const option = [...document.body.querySelectorAll<HTMLElement>('.u-select__option')].find(
23
- (el) => el.textContent === label
24
- )
25
- expect(option, `选项「${label}」应存在`).toBeTruthy()
26
- option!.dispatchEvent(new MouseEvent('click', { bubbles: true }))
27
- await nextTick()
28
- await sleep(50)
29
- }
30
-
31
13
  describe('UForm field events', () => {
32
- it('field:update and field:change are separate', async () => {
14
+ it('编程写入触发 field:update,不依赖用户操作', async () => {
33
15
  const host = document.createElement('div')
34
16
  document.body.appendChild(host)
35
17
 
36
18
  const model = reactive({ behavior: 'event' })
37
19
  const updates: { field: string; value: unknown }[] = []
38
- const changes: { field: string; args: unknown[] }[] = []
39
20
 
40
21
  const app = createApp({
41
22
  render() {
@@ -45,9 +26,6 @@ describe('UForm field events', () => {
45
26
  model,
46
27
  'onField:update': (field: string, value: unknown) => {
47
28
  updates.push({ field, value })
48
- },
49
- 'onField:change': (field: string, ...args: unknown[]) => {
50
- changes.push({ field, args })
51
29
  }
52
30
  },
53
31
  {
@@ -60,19 +38,177 @@ describe('UForm field events', () => {
60
38
 
61
39
  app.mount(host)
62
40
  updates.length = 0
63
- changes.length = 0
64
41
 
65
42
  model.behavior = 'api'
66
43
  await nextTick()
67
44
 
68
45
  expect(updates.some((item) => item.field === 'behavior' && item.value === 'api')).toBe(true)
69
- expect(changes).toHaveLength(0)
70
46
 
71
- await openSelect(host)
72
- await clickSelectOption('事件')
47
+ app.unmount()
48
+ host.remove()
49
+ })
50
+
51
+ it('父级重渲染时动态 props 同步到插槽控件', async () => {
52
+ const host = document.createElement('div')
53
+ document.body.appendChild(host)
54
+
55
+ const model = reactive({ username: '' })
56
+ const disabled = ref(false)
57
+ const placeholder = ref('第一次')
58
+
59
+ const app = createApp({
60
+ render() {
61
+ return h(
62
+ UForm,
63
+ { model },
64
+ {
65
+ default: () =>
66
+ h(UInput, {
67
+ field: 'username',
68
+ label: '账号',
69
+ disabled: disabled.value,
70
+ placeholder: placeholder.value
71
+ })
72
+ }
73
+ )
74
+ }
75
+ })
76
+
77
+ app.mount(host)
78
+
79
+ const input = () => host.querySelector('input')!
80
+ expect(input().disabled).toBe(false)
81
+ expect(input().placeholder).toBe('第一次')
82
+
83
+ disabled.value = true
84
+ placeholder.value = '第二次'
85
+ await nextTick()
86
+
87
+ expect(input().disabled).toBe(true)
88
+ expect(input().placeholder).toBe('第二次')
89
+
90
+ app.unmount()
91
+ host.remove()
92
+ })
93
+ })
94
+
95
+ /** 用 setup 期间生成的 id 断言实例身份 */
96
+ let probeSeq = 0
97
+ const Probe = defineComponent({
98
+ name: 'Probe',
99
+ props: { field: String, label: String, modelValue: String },
100
+ setup(props) {
101
+ const id = `p${++probeSeq}`
102
+ return () =>
103
+ h('div', { class: 'probe', 'data-field': props.field, 'data-id': id }, [
104
+ h('span', { class: 'probe-value' }, props.modelValue ?? '')
105
+ ])
106
+ }
107
+ })
108
+
109
+ describe('UForm slot identity', () => {
110
+ it('中间字段卸载后不复用相邻控件实例', async () => {
111
+ probeSeq = 0
112
+ const host = document.createElement('div')
113
+ document.body.appendChild(host)
114
+
115
+ const model = reactive({ a: 'A', b: 'B', c: 'C' })
116
+ const showB = ref(true)
117
+
118
+ const app = createApp({
119
+ render() {
120
+ return h(
121
+ UForm,
122
+ { model },
123
+ {
124
+ default: () => [
125
+ h(Probe, { field: 'a', label: 'A' }),
126
+ showB.value ? h(Probe, { field: 'b', label: 'B' }) : null,
127
+ h(Probe, { field: 'c', label: 'C' })
128
+ ]
129
+ }
130
+ )
131
+ }
132
+ })
133
+
134
+ app.mount(host)
135
+
136
+ const idOf = (field: string) =>
137
+ host.querySelector(`[data-field="${field}"]`)?.getAttribute('data-id')
138
+
139
+ const cId = idOf('c')
140
+ expect(cId).toBe('p3')
141
+
142
+ showB.value = false
143
+ await nextTick()
144
+
145
+ expect(host.querySelector('[data-field="b"]')).toBeNull()
146
+ expect(idOf('c')).toBe(cId)
147
+ expect(host.querySelector('[data-field="c"] .probe-value')!.textContent).toBe('C')
148
+
149
+ app.unmount()
150
+ host.remove()
151
+ })
152
+
153
+ it('重复 field 各自保有独立实例', async () => {
154
+ probeSeq = 0
155
+ const host = document.createElement('div')
156
+ document.body.appendChild(host)
157
+
158
+ const model = reactive({ name: '同名' })
159
+
160
+ const app = createApp({
161
+ render() {
162
+ return h(
163
+ UForm,
164
+ { model },
165
+ {
166
+ default: () => [
167
+ h(Probe, { field: 'name', label: '左' }),
168
+ h(Probe, { field: 'name', label: '右' })
169
+ ]
170
+ }
171
+ )
172
+ }
173
+ })
174
+
175
+ app.mount(host)
176
+
177
+ const probes = [...host.querySelectorAll('.probe')]
178
+ expect(probes).toHaveLength(2)
179
+ expect(probes[0]!.getAttribute('data-id')).toBe('p1')
180
+ expect(probes[1]!.getAttribute('data-id')).toBe('p2')
181
+ expect(probes[0]!.querySelector('.probe-value')!.textContent).toBe('同名')
182
+ expect(probes[1]!.querySelector('.probe-value')!.textContent).toBe('同名')
183
+
184
+ app.unmount()
185
+ host.remove()
186
+ })
187
+
188
+ it('showModified 变更前副本不抢走编辑控件', async () => {
189
+ const host = document.createElement('div')
190
+ document.body.appendChild(host)
191
+
192
+ const model = reactive({ name: '新值' })
193
+ const initialModel = { name: '旧值' }
194
+
195
+ const app = createApp({
196
+ render() {
197
+ return h(
198
+ UForm,
199
+ { model, initialModel, showModified: true },
200
+ { default: () => h(UInput, { field: 'name', label: '名称' }) }
201
+ )
202
+ }
203
+ })
204
+
205
+ app.mount(host)
206
+ await nextTick()
73
207
 
74
- expect(changes).toHaveLength(1)
75
- expect(changes[0]).toEqual({ field: 'behavior', args: [{ label: '事件', value: 'event' }] })
208
+ const live = host.querySelector('input') as HTMLInputElement | null
209
+ expect(live).toBeTruthy()
210
+ expect(live!.value).toBe('新值')
211
+ expect(host.querySelector('.u-form__data-before')?.textContent).toContain('旧值')
76
212
 
77
213
  app.unmount()
78
214
  host.remove()
@@ -8,8 +8,8 @@
8
8
  :class="[cls.b, bem.is('readonly', readonly)]"
9
9
  >
10
10
  <template
11
- v-for="{ node, isFormItem, formItemProps, field, modelValue } of getSlotsNodes()"
12
- :key="node.key"
11
+ v-for="{ node, isFormItem, formItemProps, field, modelValue, renderKey } of getSlotsNodes()"
12
+ :key="renderKey"
13
13
  >
14
14
  <u-form-item v-if="!isFormItem && field" v-bind="formItemProps">
15
15
  <component
@@ -19,7 +19,7 @@
19
19
  />
20
20
  <div v-if="shouldShowModified(field)" :class="cls.e('data-before')">
21
21
  <span :class="cls.e('changed-tag')">{{ modifiedLabel }}</span>
22
- <component :is="node" readonly :model-value="getBaselineFieldValue(field)" />
22
+ <component :is="createModifiedPreview(node, field)" />
23
23
  </div>
24
24
  </u-form-item>
25
25
 
@@ -32,14 +32,14 @@
32
32
  import { o } from '@cat-kit/core'
33
33
  import { bem } from '@veltra/utils'
34
34
  import { provideFormContext } from '@veltra/utils'
35
- import { nextTick, toRef, useTemplateRef } from 'vue'
35
+ import { nextTick, toRef, useTemplateRef, type VNode } from 'vue'
36
36
 
37
37
  import type { BreakCols, FormProps, _FormExposed, FormEmits } from '../../types'
38
38
  import { UFormItem } from '../form-item'
39
39
  import { UGrid } from '../grid'
40
40
  import { isFieldModified } from './is-field-modified'
41
41
  import { useFormFields } from './use-form-fields'
42
- import { useNodeInterceptor } from './use-node-interceptor'
42
+ import { forkUnmountedVNode, useNodeInterceptor } from './use-node-interceptor'
43
43
 
44
44
  defineOptions({ name: 'UForm' })
45
45
 
@@ -81,18 +81,13 @@ function handleFieldUpdate(field: string, value: any) {
81
81
  emit('field:update', field, value)
82
82
  }
83
83
 
84
- function handleFieldChange(field: string, ...args: any[]) {
85
- emit('field:change', field, ...args)
86
- }
87
-
88
84
  provideFormContext({
89
85
  formProps: props,
90
86
  registerField,
91
87
  unregisterField,
92
88
  validateFields: validate,
93
89
  shouldValidate,
94
- handleFieldUpdate,
95
- handleFieldChange
90
+ handleFieldUpdate
96
91
  })
97
92
 
98
93
  const { getSlotsNodes } = useNodeInterceptor()
@@ -106,6 +101,10 @@ function getBaselineFieldValue(field: string) {
106
101
  return o(getBaselineModel() ?? {}).get(field)
107
102
  }
108
103
 
104
+ function createModifiedPreview(node: VNode, field: string) {
105
+ return forkUnmountedVNode(node, { readonly: true, modelValue: getBaselineFieldValue(field) })
106
+ }
107
+
109
108
  function shouldShowModified(field: string) {
110
109
  if (!props.showModified) return false
111
110
  const baseline = getBaselineModel()
@@ -1,6 +1,6 @@
1
1
  import { o } from '@cat-kit/core'
2
2
  import { extractNormalVNodes } from '@veltra/utils'
3
- import { useSlots, type VNode } from 'vue'
3
+ import { cloneVNode, useSlots, type VNode } from 'vue'
4
4
 
5
5
  export type SlotRenderItem = {
6
6
  isFormItem: boolean
@@ -8,6 +8,28 @@ export type SlotRenderItem = {
8
8
  node: VNode
9
9
  field?: string
10
10
  modelValue?: any
11
+ /** v-for 身份:调用方 :key 优先,否则 type+field+出现次序 */
12
+ renderKey: string | number | symbol
13
+ }
14
+
15
+ function getVNodeTypeName(node: VNode): string {
16
+ const type = node.type as { name?: string; __name?: string } | string
17
+ if (typeof type === 'string') return type
18
+ if (typeof type === 'object' && type) return type.name || type.__name || ''
19
+ return ''
20
+ }
21
+
22
+ /**
23
+ * 从源 vnode 派生一份未挂载副本。
24
+ * Vue 的 cloneVNode 会拷贝 el/component,同一源 vnode 渲两次会把实例从编辑控件抢走。
25
+ */
26
+ export function forkUnmountedVNode(node: VNode, extra?: Record<string, unknown>): VNode {
27
+ const forked = extra ? cloneVNode(node, extra) : cloneVNode(node)
28
+ forked.el = null
29
+ forked.component = null
30
+ forked.anchor = null
31
+ forked.suspense = null
32
+ return forked
11
33
  }
12
34
 
13
35
  /**
@@ -24,6 +46,7 @@ export function useNodeInterceptor(): { getSlotsNodes: () => SlotRenderItem[] |
24
46
  const flattedNodes = extractNormalVNodes(nodes)
25
47
 
26
48
  const results: SlotRenderItem[] = []
49
+ const seen = new Map<string, number>()
27
50
 
28
51
  let i = 0
29
52
  while (i < flattedNodes.length) {
@@ -43,12 +66,23 @@ export function useNodeInterceptor(): { getSlotsNodes: () => SlotRenderItem[] |
43
66
  'field'
44
67
  ]) as Record<string, any>
45
68
 
69
+ let renderKey: string | number | symbol
70
+ if (node.key != null) {
71
+ renderKey = node.key
72
+ } else {
73
+ const base = `${getVNodeTypeName(node)}:${field ?? ''}`
74
+ const n = seen.get(base) ?? 0
75
+ seen.set(base, n + 1)
76
+ renderKey = n === 0 ? `uform:${base}` : `uform:${base}#${n}`
77
+ }
78
+
46
79
  results.push({
47
80
  isFormItem,
48
81
  formItemProps,
49
82
  node,
50
83
  field,
51
- modelValue: props?.['model-value'] ?? props?.modelValue
84
+ modelValue: props?.['model-value'] ?? props?.modelValue,
85
+ renderKey
52
86
  })
53
87
  }
54
88
 
@@ -1,7 +1,8 @@
1
1
  import { describe, expect, it } from 'vitest'
2
- import { createApp, h, nextTick, reactive } from 'vue'
2
+ import { createApp, h, nextTick, reactive, ref } from 'vue'
3
3
 
4
4
  import { UForm } from '../../form'
5
+ import { UInput } from '../../input'
5
6
  import { USelect } from '../../select'
6
7
  import { UFormItem } from '../index'
7
8
 
@@ -36,18 +37,12 @@ describe('UFormItem change', () => {
36
37
 
37
38
  const model = reactive({ behavior: 'event' })
38
39
  const itemChanges: unknown[][] = []
39
- const formChanges: { field: string; args: unknown[] }[] = []
40
40
 
41
41
  const app = createApp({
42
42
  render() {
43
43
  return h(
44
44
  UForm,
45
- {
46
- model,
47
- 'onField:change': (field: string, ...args: unknown[]) => {
48
- formChanges.push({ field, args })
49
- }
50
- },
45
+ { model },
51
46
  {
52
47
  default: () =>
53
48
  h(
@@ -81,8 +76,43 @@ describe('UFormItem change', () => {
81
76
  expect(itemChanges).toHaveLength(1)
82
77
  expect(itemChanges[0]![0]).toEqual({ label: '接口', value: 'api' })
83
78
 
84
- expect(formChanges).toHaveLength(1)
85
- expect(formChanges[0]).toEqual({ field: 'behavior', args: [{ label: '接口', value: 'api' }] })
79
+ app.unmount()
80
+ host.remove()
81
+ })
82
+
83
+ it('显式 UFormItem 内控件的动态 props 随父级更新', async () => {
84
+ const host = document.createElement('div')
85
+ document.body.appendChild(host)
86
+
87
+ const model = reactive({ name: '' })
88
+ const disabled = ref(false)
89
+
90
+ const app = createApp({
91
+ render() {
92
+ return h(
93
+ UForm,
94
+ { model },
95
+ {
96
+ default: () =>
97
+ h(
98
+ UFormItem,
99
+ { field: 'name', label: '姓名' },
100
+ { default: () => h(UInput, { modelValue: model.name, disabled: disabled.value }) }
101
+ )
102
+ }
103
+ )
104
+ }
105
+ })
106
+
107
+ app.mount(host)
108
+
109
+ const input = () => host.querySelector('input')!
110
+ expect(input().disabled).toBe(false)
111
+
112
+ disabled.value = true
113
+ await nextTick()
114
+
115
+ expect(input().disabled).toBe(true)
86
116
 
87
117
  app.unmount()
88
118
  host.remove()
@@ -17,7 +17,9 @@
17
17
 
18
18
  <section :class="cls.e('wrapper')">
19
19
  <div :class="cls.e('content')">
20
- <ContentSlot />
20
+ <ContentSlot>
21
+ <slot></slot>
22
+ </ContentSlot>
21
23
  </div>
22
24
 
23
25
  <!-- 只有表单控件处于非只读状态时,才显示错误提示 -->
@@ -35,16 +37,16 @@
35
37
  <script lang="tsx" setup>
36
38
  import { o } from '@cat-kit/core'
37
39
  import { useConfig, useFallbackProps } from '@veltra/compositions'
38
- import { bem, withUnit } from '@veltra/utils'
40
+ import { bem, extractNormalVNodes, withUnit } from '@veltra/utils'
39
41
  import { injectFormContext } from '@veltra/utils'
40
42
  import {
41
43
  type CSSProperties,
44
+ type SetupContext,
42
45
  type VNode,
43
46
  cloneVNode,
44
47
  computed,
45
48
  onBeforeUnmount,
46
49
  shallowRef,
47
- useSlots,
48
50
  watch
49
51
  } from 'vue'
50
52
 
@@ -60,8 +62,6 @@ const props = withDefaults(defineProps<FormItemProps>(), { readonly: undefined }
60
62
 
61
63
  const emit = defineEmits<FormItemEmits>()
62
64
 
63
- const slots = useSlots()
64
-
65
65
  defineSlots<{
66
66
  /** 标签插槽 */
67
67
  label?: () => any
@@ -69,14 +69,8 @@ defineSlots<{
69
69
  }>()
70
70
 
71
71
  /** 表单组件上下文 */
72
- const {
73
- formProps,
74
- registerField,
75
- unregisterField,
76
- shouldValidate,
77
- handleFieldUpdate,
78
- handleFieldChange
79
- } = injectFormContext()
72
+ const { formProps, registerField, unregisterField, shouldValidate, handleFieldUpdate } =
73
+ injectFormContext()
80
74
 
81
75
  function wrapControlChange(node: VNode) {
82
76
  if (!node || typeof node.type === 'symbol') return node
@@ -84,14 +78,17 @@ function wrapControlChange(node: VNode) {
84
78
  return cloneVNode(node, {
85
79
  onChange: (...args: any[]) => {
86
80
  emit('change', ...args)
87
- if (props.field) handleFieldChange?.(props.field, ...args)
88
81
  }
89
82
  })
90
83
  }
91
84
 
92
- function ContentSlot() {
93
- const nodes = slots.default?.() ?? []
94
- return nodes.map(wrapControlChange)
85
+ /**
86
+ * 承载默认插槽并拦截控件 change。
87
+ * 必须经填充插槽接收内容(而非直接读 UFormItem 的 $slots):
88
+ * 读父级 $slots 时组件自身没有更新路径,父级重渲染后动态 props(disabled、data 等)会冻结在首帧。
89
+ */
90
+ function ContentSlot(_: unknown, { slots }: SetupContext) {
91
+ return extractNormalVNodes(slots.default?.() ?? []).map(wrapControlChange)
95
92
  }
96
93
 
97
94
  const { config } = useConfig()
@@ -4,7 +4,12 @@ import type { ButtonProps } from './button'
4
4
 
5
5
  /** 操作组件属性 */
6
6
  export interface ActionProps extends ButtonProps {
7
- /** 是否需要确认 */
7
+ /**
8
+ * 是否需要确认:true 时点击只弹确认气泡(UPopConfirm),点气泡里的「确认」才触发 `run`
9
+ * @default false
10
+ * @description 不要在本项的 `run` 回调里再调 `messageConfirm`:`run` 触发时气泡已经确认过一次,
11
+ * 会变成两次确认。需要弹窗级确认时去掉 `needConfirm`,只在 `run` 里用 `messageConfirm`
12
+ */
8
13
  needConfirm?: boolean
9
14
 
10
15
  /**
@@ -115,8 +115,6 @@ export interface BatchEditProps extends TableProps {
115
115
 
116
116
  /** 批量编辑组件定义的事件 */
117
117
  export interface BatchEditEmits extends TableEmits {
118
- /** 表单控件 change,透传自 UForm */
119
- (e: 'field:change', field: string, ...args: any[]): void
120
118
  /** 更新数据 */
121
119
  (e: 'update:data', value: Record<string, any>[]): void
122
120
  /** 点击底部「新增一行」 */
@@ -4,7 +4,11 @@ import type { Component, ShallowRef } from 'vue'
4
4
  /** 按钮类型 */
5
5
  export type ButtonType = ColorType
6
6
 
7
- /** 按钮属性类型 */
7
+ /**
8
+ * 按钮属性类型
9
+ * @description 组件不注入默认 `aria-label`:可访问名来自默认插槽文本,
10
+ * 或你自己传入的 `aria-label`(透传到根元素);只有图标的按钮必须自行传 `aria-label`
11
+ */
8
12
  export interface ButtonProps extends ComponentProps {
9
13
  /** 按钮类型 */
10
14
  type?: ButtonType
@@ -15,16 +15,19 @@ export interface DrawerProps {
15
15
 
16
16
  /** 是否显示关闭按钮 */
17
17
  showClose?: boolean
18
- /** 抽屉标题 */
18
+ /**
19
+ * 抽屉标题;传入时在内容区上方渲染标题栏
20
+ * @description 不传则不渲染标题栏,内容仍全部来自默认插槽
21
+ */
19
22
  title?: string
20
23
  }
21
24
 
22
25
  /** 抽屉组件定义的事件 */
23
26
  export interface DrawerEmits {
24
27
  (e: 'update:modelValue', value: boolean): void
25
- /** 关闭时触发 */
28
+ /** 开始关闭时触发(点遮罩、点关闭按钮) */
26
29
  (e: 'close'): void
27
- /** 完全关闭后触发 */
30
+ /** 完全关闭后触发:抽屉与遮罩的退出动画结束、节点已移除时 */
28
31
  (e: 'closed'): void
29
32
  }
30
33
 
package/src/types/form.ts CHANGED
@@ -29,8 +29,6 @@ export interface FormProps extends ComponentProps {
29
29
  }
30
30
 
31
31
  export interface FormEmits {
32
- /** 控件 change 事件,仅用户操作触发 */
33
- (e: 'field:change', field: string, ...args: any[]): void
34
32
  /** model 字段值更新,含编程写入 */
35
33
  (e: 'field:update', field: string, value: any): void
36
34
  }
@@ -18,6 +18,8 @@ export interface LayoutProps {
18
18
  * const cols = ['200px', '1fr']
19
19
  * ```
20
20
  * [fr是什么?](https://developer.mozilla.org/zh-CN/docs/Web/CSS/flex_value)
21
+ * @description 默认插槽的每个直接子元素按顺序占一列,用自定义组件包一层时该组件必须是单根元素;
22
+ * 子元素渲染出 0 个(`v-if` 为假)或多个根节点时会挤动后面的列,此时自行给子元素写 `grid-column`
21
23
  */
22
24
  cols?: string[] | string
23
25