@veltra/desktop 1.2.24 → 1.2.26

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 (86) hide show
  1. package/dist/components/action/action-group.vue.d.ts +2 -2
  2. package/dist/components/action/action.vue.d.ts +1 -1
  3. package/dist/components/batch-edit/batch-edit.vue.d.ts +2 -2
  4. package/dist/components/cascade/cascade.js +5 -3
  5. package/dist/components/cascade/cascade.js.map +1 -1
  6. package/dist/components/cascade/cascade.vue.d.ts +3 -3
  7. package/dist/components/cascade/use-data-map.js +2 -1
  8. package/dist/components/cascade/use-data-map.js.map +1 -1
  9. package/dist/components/checkbox/checkbox-button.vue.d.ts +1 -1
  10. package/dist/components/checkbox-group/checkbox-group.js +10 -10
  11. package/dist/components/checkbox-group/checkbox-group.js.map +1 -1
  12. package/dist/components/checkbox-group/checkbox-group.vue.d.ts +2 -2
  13. package/dist/components/code-editor/basic-setup.js +2 -1
  14. package/dist/components/code-editor/basic-setup.js.map +1 -1
  15. package/dist/components/code-editor/code-editor.js +136 -79
  16. package/dist/components/code-editor/code-editor.js.map +1 -1
  17. package/dist/components/code-editor/code-editor.vue.d.ts +10 -2
  18. package/dist/components/code-editor/shell-extension.js +139 -0
  19. package/dist/components/code-editor/shell-extension.js.map +1 -0
  20. package/dist/components/code-editor/style.js +1 -0
  21. package/dist/components/code-editor/style2.css +15 -11
  22. package/dist/components/date-panel/date-panel.vue.d.ts +1 -1
  23. package/dist/components/date-picker/date-picker.vue.d.ts +1 -1
  24. package/dist/components/date-range-picker/date-range-picker.vue.d.ts +1 -1
  25. package/dist/components/multi-select/multi-select.js +31 -36
  26. package/dist/components/multi-select/multi-select.js.map +1 -1
  27. package/dist/components/multi-select/multi-select.vue.d.ts +2 -2
  28. package/dist/components/multi-tree-select/multi-tree-select.js +8 -6
  29. package/dist/components/multi-tree-select/multi-tree-select.js.map +1 -1
  30. package/dist/components/multi-tree-select/multi-tree-select.vue.d.ts +2 -2
  31. package/dist/components/pop-confirm/pop-confirm.vue.d.ts +1 -1
  32. package/dist/components/progress-nodes/progress-nodes.js +6 -4
  33. package/dist/components/progress-nodes/progress-nodes.js.map +1 -1
  34. package/dist/components/radio-group/radio-group.js +8 -6
  35. package/dist/components/radio-group/radio-group.js.map +1 -1
  36. package/dist/components/radio-group/radio-group.vue.d.ts +2 -2
  37. package/dist/components/select/select.js +13 -13
  38. package/dist/components/select/select.js.map +1 -1
  39. package/dist/components/select/select.vue.d.ts +2 -2
  40. package/dist/components/select/use-options.js +7 -4
  41. package/dist/components/select/use-options.js.map +1 -1
  42. package/dist/components/steps/steps.js +3 -2
  43. package/dist/components/steps/steps.js.map +1 -1
  44. package/dist/components/table/allocate-column-widths.js +32 -0
  45. package/dist/components/table/allocate-column-widths.js.map +1 -0
  46. package/dist/components/table/use-col-resize.js +2 -10
  47. package/dist/components/table/use-col-resize.js.map +1 -1
  48. package/dist/components/tip/tip.vue.d.ts +1 -1
  49. package/dist/components/tree/use-check.js +2 -1
  50. package/dist/components/tree/use-check.js.map +1 -1
  51. package/dist/components/tree/use-select.js +3 -1
  52. package/dist/components/tree/use-select.js.map +1 -1
  53. package/dist/components/tree/use-tree-nodes.js +4 -1
  54. package/dist/components/tree/use-tree-nodes.js.map +1 -1
  55. package/dist/components/tree-select/tree-select.js +6 -4
  56. package/dist/components/tree-select/tree-select.js.map +1 -1
  57. package/dist/components/tree-select/tree-select.vue.d.ts +2 -2
  58. package/dist/components/watermark/watermark.vue.d.ts +1 -1
  59. package/dist/node_modules/.bun/@codemirror_commands@6.10.4/node_modules/@codemirror/commands/dist/index.js +12 -1
  60. package/dist/node_modules/.bun/@codemirror_commands@6.10.4/node_modules/@codemirror/commands/dist/index.js.map +1 -1
  61. package/dist/style.js +1 -1
  62. package/dist/types/code-editor.d.ts +13 -2
  63. package/package.json +6 -6
  64. package/src/components/cascade/cascade.vue +6 -3
  65. package/src/components/cascade/use-data-map.ts +3 -2
  66. package/src/components/checkbox-group/checkbox-group.vue +10 -8
  67. package/src/components/code-editor/basic-setup.ts +3 -1
  68. package/src/components/code-editor/code-editor.vue +154 -96
  69. package/src/components/code-editor/shell-extension.ts +199 -0
  70. package/src/components/code-editor/style.scss +20 -13
  71. package/src/components/code-editor/style.ts +1 -0
  72. package/src/components/multi-select/multi-select.vue +31 -30
  73. package/src/components/multi-tree-select/multi-tree-select.vue +5 -2
  74. package/src/components/progress-nodes/progress-nodes.vue +7 -4
  75. package/src/components/radio-group/radio-group.vue +5 -1
  76. package/src/components/select/select.vue +13 -11
  77. package/src/components/select/use-options.ts +7 -4
  78. package/src/components/steps/steps.vue +3 -1
  79. package/src/components/table/__test__/col-width.test.ts +77 -0
  80. package/src/components/table/allocate-column-widths.ts +38 -0
  81. package/src/components/table/use-col-resize.ts +2 -19
  82. package/src/components/tree/use-check.ts +2 -1
  83. package/src/components/tree/use-select.ts +3 -1
  84. package/src/components/tree/use-tree-nodes.ts +5 -9
  85. package/src/components/tree-select/tree-select.vue +7 -4
  86. package/src/types/code-editor.ts +13 -2
@@ -0,0 +1,199 @@
1
+ import {
2
+ EditorSelection,
3
+ EditorState,
4
+ RangeSetBuilder,
5
+ StateField,
6
+ type Extension,
7
+ type Text
8
+ } from '@codemirror/state'
9
+ import { Decoration, type DecorationSet, EditorView } from '@codemirror/view'
10
+
11
+ /** 前后缀外壳配置 */
12
+ export interface ShellConfig {
13
+ prefix: string
14
+ suffix: string
15
+ }
16
+
17
+ /**
18
+ * 判断 Text 文档是否与外壳对齐。
19
+ * 仅 slice 前后缀边界,避免整篇 toString。
20
+ */
21
+ function textMatchesShell(doc: Text, config: ShellConfig): boolean {
22
+ const { prefix, suffix } = config
23
+ const len = doc.length
24
+ if (prefix.length + suffix.length > len) return false
25
+ if (prefix && doc.sliceString(0, prefix.length) !== prefix) return false
26
+ if (suffix && doc.sliceString(len - suffix.length) !== suffix) return false
27
+ return true
28
+ }
29
+
30
+ /** 计算正文区间在完整文档中的起止位置(始终落在 [0, docLength]) */
31
+ export function getShellRanges(docLength: number, config: ShellConfig) {
32
+ const prefixLen = config.prefix.length
33
+ const suffixLen = config.suffix.length
34
+ // 外壳与文档可能短暂不一致,区间必须可安全用于选区
35
+ const bodyStart = Math.min(prefixLen, docLength)
36
+ const bodyEnd = Math.max(bodyStart, docLength - Math.min(suffixLen, docLength - bodyStart))
37
+ return { prefixLen, suffixLen, bodyStart, bodyEnd }
38
+ }
39
+
40
+ /** 从 CodeMirror Text 提取正文(热路径,避免整篇 toString) */
41
+ export function extractBodyFromText(doc: Text, config: ShellConfig): string {
42
+ // 无外壳时直接 toString,跳过边界校验与二次 slice
43
+ if (!config.prefix && !config.suffix) return doc.toString()
44
+ if (!textMatchesShell(doc, config)) return doc.toString()
45
+ const { bodyStart, bodyEnd } = getShellRanges(doc.length, config)
46
+ return doc.sliceString(bodyStart, bodyEnd)
47
+ }
48
+
49
+ /**
50
+ * 判断 Text 是否已等于 prefix + body + suffix(用于 model → doc 同步短路)。
51
+ * 长度不等时 O(1);相等时只比较正文与边界,不分配完整文档字符串。
52
+ */
53
+ export function textEqualsShellDoc(doc: Text, body: string, config: ShellConfig): boolean {
54
+ const { prefix, suffix } = config
55
+ if (doc.length !== prefix.length + body.length + suffix.length) return false
56
+ if (!prefix && !suffix) return doc.toString() === body
57
+ if (!textMatchesShell(doc, config)) return false
58
+ return doc.sliceString(prefix.length, doc.length - suffix.length) === body
59
+ }
60
+
61
+ /** 将正文与前后缀拼成完整 CodeMirror 文档 */
62
+ export function buildFullDoc(body: string, config: ShellConfig): string {
63
+ return config.prefix + body + config.suffix
64
+ }
65
+
66
+ /** 选区是否已完全落在 [lo, hi] 内 */
67
+ function selectionWithin(selection: EditorSelection, lo: number, hi: number): boolean {
68
+ for (const range of selection.ranges) {
69
+ if (range.from < lo || range.to > hi) return false
70
+ }
71
+ return true
72
+ }
73
+
74
+ /** 将选区限制在正文区间内,并保证不超出文档长度 */
75
+ function clampSelection(
76
+ selection: EditorSelection,
77
+ bodyStart: number,
78
+ bodyEnd: number,
79
+ docLength = bodyEnd
80
+ ): EditorSelection {
81
+ const lo = Math.max(0, Math.min(bodyStart, docLength))
82
+ const hi = Math.max(lo, Math.min(bodyEnd, docLength))
83
+ const clamp = (pos: number) => Math.max(lo, Math.min(hi, pos))
84
+ let changed = false
85
+ const ranges = selection.ranges.map((range) => {
86
+ const anchor = clamp(range.anchor)
87
+ const head = clamp(range.head)
88
+ if (anchor !== range.anchor || head !== range.head) changed = true
89
+ return EditorSelection.range(anchor, head)
90
+ })
91
+ if (!changed) return selection
92
+ return EditorSelection.create(ranges, selection.mainIndex)
93
+ }
94
+
95
+ /**
96
+ * 将旧文档选区映射到新文档的正文区间(按正文内相对偏移)。
97
+ * 用于 prefix/suffix 切换时保持光标大致位置且始终合法。
98
+ */
99
+ export function mapSelectionToBody(
100
+ selection: EditorSelection,
101
+ from: { bodyStart: number; bodyEnd: number },
102
+ to: { bodyStart: number; bodyEnd: number },
103
+ docLength: number
104
+ ): EditorSelection {
105
+ const fromLen = Math.max(0, from.bodyEnd - from.bodyStart)
106
+ const toLen = Math.max(0, to.bodyEnd - to.bodyStart)
107
+ const mapPos = (pos: number) => {
108
+ const offset = Math.max(0, Math.min(fromLen, pos - from.bodyStart))
109
+ return to.bodyStart + Math.min(offset, toLen)
110
+ }
111
+ const ranges = selection.ranges.map((range) =>
112
+ EditorSelection.range(mapPos(range.anchor), mapPos(range.head))
113
+ )
114
+ return clampSelection(
115
+ EditorSelection.create(ranges, selection.mainIndex),
116
+ to.bodyStart,
117
+ to.bodyEnd,
118
+ docLength
119
+ )
120
+ }
121
+
122
+ const shellMark = Decoration.mark({ class: 'cm-shell' })
123
+
124
+ function buildShellDecorations(doc: Text, config: ShellConfig): DecorationSet {
125
+ if (!textMatchesShell(doc, config)) return Decoration.none
126
+
127
+ const { prefixLen, suffixLen, bodyEnd } = getShellRanges(doc.length, config)
128
+ const builder = new RangeSetBuilder<Decoration>()
129
+
130
+ if (prefixLen > 0) {
131
+ builder.add(0, prefixLen, shellMark)
132
+ }
133
+ if (suffixLen > 0 && bodyEnd < doc.length) {
134
+ builder.add(bodyEnd, doc.length, shellMark)
135
+ }
136
+
137
+ return builder.finish()
138
+ }
139
+
140
+ /** 变更是否触及外壳区间(相对 startState 文档坐标) */
141
+ function changesTouchShell(
142
+ changes: { iterChangedRanges: (f: (fromA: number, toA: number) => void) => void },
143
+ bodyStart: number,
144
+ bodyEnd: number
145
+ ): boolean {
146
+ let touches = false
147
+ changes.iterChangedRanges((fromA, toA) => {
148
+ if (fromA < bodyStart || toA > bodyEnd) touches = true
149
+ })
150
+ return touches
151
+ }
152
+
153
+ /**
154
+ * CodeMirror 扩展:将 prefix/suffix 作为文档内的只读区域,
155
+ * 通过 changeFilter 阻止编辑、transactionFilter 限制选区,并用装饰着色。
156
+ */
157
+ export function shellExtension(config: ShellConfig): Extension {
158
+ const hasShell = config.prefix.length > 0 || config.suffix.length > 0
159
+ if (!hasShell) return []
160
+
161
+ const shellField = StateField.define<DecorationSet>({
162
+ create(state) {
163
+ return buildShellDecorations(state.doc, config)
164
+ },
165
+ update(decorations, tr) {
166
+ if (!tr.docChanged) return decorations
167
+
168
+ const { bodyStart, bodyEnd } = getShellRanges(tr.startState.doc.length, config)
169
+ // 正文内编辑:外壳 mark 只需随 ChangeSet 映射,避免边界 slice / 重建
170
+ if (!changesTouchShell(tr.changes, bodyStart, bodyEnd) && decorations.size > 0) {
171
+ return decorations.map(tr.changes)
172
+ }
173
+ return buildShellDecorations(tr.newDoc, config)
174
+ },
175
+ provide: (f) => EditorView.decorations.from(f)
176
+ })
177
+
178
+ return [
179
+ shellField,
180
+ EditorState.changeFilter.of((tr) => {
181
+ if (!tr.docChanged) return true
182
+ const { bodyStart, bodyEnd } = getShellRanges(tr.startState.doc.length, config)
183
+ // 未触及外壳区间时直接放行,避免 sliceString
184
+ if (!changesTouchShell(tr.changes, bodyStart, bodyEnd)) return true
185
+ // 触及外壳:文档尚未对齐时放行(由组件侧全量替换同步)
186
+ return !textMatchesShell(tr.startState.doc, config)
187
+ }),
188
+ EditorState.transactionFilter.of((tr) => {
189
+ const { bodyStart, bodyEnd } = getShellRanges(tr.newDoc.length, config)
190
+ // 选区已在正文内:无需钳制,也无需校验外壳
191
+ if (selectionWithin(tr.newSelection, bodyStart, bodyEnd)) return tr
192
+ // 外壳与文档不一致时不钳选区,避免 Selection points outside of document
193
+ if (!textMatchesShell(tr.newDoc, config)) return tr
194
+ const selection = clampSelection(tr.newSelection, bodyStart, bodyEnd, tr.newDoc.length)
195
+ if (selection.eq(tr.newSelection)) return tr
196
+ return [tr, { selection }]
197
+ })
198
+ ]
199
+ }
@@ -1,6 +1,5 @@
1
1
  @use 'pkg:@veltra/styles/mixins' as m;
2
2
  @use 'pkg:@veltra/styles/functions' as fn;
3
- @use 'pkg:@veltra/styles/vars';
4
3
 
5
4
  $root-name: code-editor;
6
5
 
@@ -33,6 +32,19 @@ $root-name: code-editor;
33
32
  border-color: fn.use-var(color, primary);
34
33
  }
35
34
 
35
+ @include m.e(lang-select) {
36
+ position: absolute;
37
+ top: 2px;
38
+ right: 2px;
39
+ z-index: 2;
40
+ width: 88px;
41
+ border: none;
42
+
43
+ input {
44
+ text-align: right;
45
+ }
46
+ }
47
+
36
48
  .cm-editor {
37
49
  background: transparent;
38
50
  color: var(--u-code-editor-fg);
@@ -69,21 +81,12 @@ $root-name: code-editor;
69
81
  .cm-gutters {
70
82
  background-color: transparent;
71
83
  color: var(--u-code-editor-gutter-fg);
72
- border-right: 1px solid var(--u-code-editor-border);
84
+ // CodeMirror 默认 gutter 高度只跟文档行数走,min-height 只加在 content 上时
85
+ // border-right 会在高编辑器里半途断开;官方主题示例也推荐去掉这条边框
86
+ border: none;
73
87
  user-select: none;
74
88
  }
75
89
 
76
- .cm-gutterElement-phantom {
77
- color: var(--u-code-editor-gutter-fg);
78
- opacity: 0.45;
79
- padding: 0 3px 0 5px;
80
- text-align: right;
81
- white-space: nowrap;
82
- min-width: 1ch;
83
- box-sizing: border-box;
84
- pointer-events: none;
85
- }
86
-
87
90
  .cm-activeLineGutter,
88
91
  .cm-activeLine {
89
92
  background-color: var(--u-code-editor-active-bg);
@@ -97,6 +100,10 @@ $root-name: code-editor;
97
100
  .cm-cursor {
98
101
  border-left-color: fn.use-var(color, primary);
99
102
  }
103
+
104
+ .cm-shell {
105
+ color: var(--u-code-editor-gutter-fg);
106
+ }
100
107
  }
101
108
 
102
109
  @include m.is(dark) {
@@ -1 +1,2 @@
1
+ import '../select/style'
1
2
  import './style.scss'
@@ -144,7 +144,7 @@
144
144
  import { o as chainObj } from '@cat-kit/core'
145
145
  import { useFormFallbackProps, useVirtualizer } from '@veltra/compositions'
146
146
  import { ArrowDown, Close, Search } from '@veltra/icons/normal'
147
- import { bem, FORM_EMPTY_CONTENT } from '@veltra/utils'
147
+ import { bem, fieldKey, FORM_EMPTY_CONTENT } from '@veltra/utils'
148
148
  import { injectFormContext } from '@veltra/utils'
149
149
  import { computed, shallowRef, shallowReactive, watch, provide } from 'vue'
150
150
 
@@ -182,6 +182,9 @@ const emit = defineEmits<MultiSelectEmits>()
182
182
 
183
183
  const cls = bem('multi-select')
184
184
 
185
+ const labelKey = computed(() => fieldKey(props.labelKey, 'label'))
186
+ const valueKey = computed(() => fieldKey(props.valueKey, 'value'))
187
+
185
188
  const { formProps } = injectFormContext()
186
189
 
187
190
  const { size, disabled, readonly } = useFormFallbackProps([formProps ?? {}, props], {
@@ -202,11 +205,14 @@ const options = computed(() => {
202
205
  const base = rawOptions.value
203
206
  if (!props.creatable || !createdOptions.value.length) return base
204
207
 
205
- const { valueKey } = props
206
- const createdValues = new Set(createdOptions.value.map((o) => chainObj(o).get(valueKey)))
207
- const deduped = base.filter((o) => !(o.__isTemp && createdValues.has(chainObj(o).get(valueKey))))
208
- const dedupedValues = new Set(deduped.map((o) => chainObj(o).get(valueKey)))
209
- const toAdd = createdOptions.value.filter((o) => !dedupedValues.has(chainObj(o).get(valueKey)))
208
+ const createdValues = new Set(createdOptions.value.map((o) => chainObj(o).get(valueKey.value)))
209
+ const deduped = base.filter(
210
+ (o) => !(o.__isTemp && createdValues.has(chainObj(o).get(valueKey.value)))
211
+ )
212
+ const dedupedValues = new Set(deduped.map((o) => chainObj(o).get(valueKey.value)))
213
+ const toAdd = createdOptions.value.filter(
214
+ (o) => !dedupedValues.has(chainObj(o).get(valueKey.value))
215
+ )
210
216
  return [...toAdd, ...deduped]
211
217
  })
212
218
 
@@ -225,18 +231,17 @@ const { virtualizer, items } = useVirtualizer({
225
231
  })
226
232
 
227
233
  const virtualOptions = computed(() => {
228
- const { valueKey, labelKey } = props
229
234
  const _options = options.value
230
235
  return items.value.map((v) => {
231
236
  const option = _options[v.index]!
232
- const val = chainObj(option).get(valueKey)
237
+ const val = chainObj(option).get(valueKey.value)
233
238
  return {
234
239
  option,
235
240
  index: v.index,
236
241
  val,
237
242
  key: v.index,
238
243
  offset: v.start,
239
- label: chainObj(option).get(labelKey)
244
+ label: chainObj(option).get(labelKey.value)
240
245
  }
241
246
  })
242
247
  })
@@ -259,9 +264,8 @@ const indeterminate = computed(() => {
259
264
  })
260
265
 
261
266
  const optionsMap = computed(() => {
262
- const { valueKey } = props
263
267
  return new Map<string | number, Record<string, any>>(
264
- allOptions.value.map((option) => [option[valueKey], option])
268
+ allOptions.value.map((option) => [option[valueKey.value], option])
265
269
  )
266
270
  })
267
271
 
@@ -322,26 +326,25 @@ const handleDropdownVisible = (visible: boolean) => {
322
326
  }
323
327
 
324
328
  const handleCheck = (option: Record<string, any>, checked: boolean) => {
325
- const { valueKey, labelKey } = props
326
329
  if (checked) {
327
330
  if (option.__isTemp && props.creatable) {
328
331
  const created: Record<string, any> = {
329
- [labelKey]: chainObj(option).get(labelKey),
330
- [valueKey]: chainObj(option).get(valueKey)
332
+ [labelKey.value]: chainObj(option).get(labelKey.value),
333
+ [valueKey.value]: chainObj(option).get(valueKey.value)
331
334
  }
332
335
  createdOptions.value = [...createdOptions.value, created]
333
336
  checkedSet.add(created)
334
337
  internalChange = true
335
- model.value = [...(model.value ?? []), chainObj(created).get(valueKey)!]
338
+ model.value = [...(model.value ?? []), chainObj(created).get(valueKey.value)!]
336
339
  queryString.value = ''
337
340
  } else if (!checkedSet.has(option)) {
338
341
  checkedSet.add(option)
339
342
  internalChange = true
340
- model.value = [...(model.value ?? []), chainObj(option).get(valueKey)!]
343
+ model.value = [...(model.value ?? []), chainObj(option).get(valueKey.value)!]
341
344
  }
342
345
  } else {
343
346
  checkedSet.delete(option)
344
- const val = chainObj(option).get(valueKey)
347
+ const val = chainObj(option).get(valueKey.value)
345
348
  internalChange = true
346
349
  model.value = (model.value ?? []).filter((v) => v !== val)
347
350
  removeCreatedOption(option)
@@ -353,13 +356,12 @@ const handleCheck = (option: Record<string, any>, checked: boolean) => {
353
356
 
354
357
  /** 全选 */
355
358
  const handleCheckAll = (checked: boolean) => {
356
- const { valueKey } = props
357
359
  if (checked) {
358
360
  options.value.forEach((option) => {
359
361
  if (!option.__isTemp) checkedSet.add(option)
360
362
  })
361
363
  internalChange = true
362
- model.value = Array.from(checkedSet).map((o) => chainObj(o).get(valueKey)!)
364
+ model.value = Array.from(checkedSet).map((o) => chainObj(o).get(valueKey.value)!)
363
365
  } else {
364
366
  checkedSet.clear()
365
367
  internalChange = true
@@ -386,7 +388,7 @@ const handleClear = () => {
386
388
 
387
389
  const handleClose = (option: Record<string, any>) => {
388
390
  checkedSet.delete(option)
389
- const val = chainObj(option).get(props.valueKey)
391
+ const val = chainObj(option).get(valueKey.value)
390
392
  internalChange = true
391
393
  model.value = (model.value ?? []).filter((v) => v !== val)
392
394
  removeCreatedOption(option)
@@ -395,9 +397,8 @@ const handleClose = (option: Record<string, any>) => {
395
397
 
396
398
  const removeCreatedOption = (option: Record<string, any>) => {
397
399
  if (!props.creatable || !createdOptions.value.length) return
398
- const { valueKey } = props
399
- const val = chainObj(option).get(valueKey)
400
- createdOptions.value = createdOptions.value.filter((o) => chainObj(o).get(valueKey) !== val)
400
+ const val = chainObj(option).get(valueKey.value)
401
+ createdOptions.value = createdOptions.value.filter((o) => chainObj(o).get(valueKey.value) !== val)
401
402
  }
402
403
 
403
404
  const handleCreateByEnter = () => {
@@ -405,14 +406,12 @@ const handleCreateByEnter = () => {
405
406
  const qs = queryString.value?.trim()
406
407
  if (!qs) return
407
408
 
408
- const { labelKey, valueKey } = props
409
-
410
- const existingCreated = createdOptions.value.find((o) => chainObj(o).get(valueKey) === qs)
409
+ const existingCreated = createdOptions.value.find((o) => chainObj(o).get(valueKey.value) === qs)
411
410
  if (existingCreated) {
412
411
  if (!checkedSet.has(existingCreated)) {
413
412
  checkedSet.add(existingCreated)
414
413
  internalChange = true
415
- model.value = [...(model.value ?? []), chainObj(existingCreated).get(valueKey)!]
414
+ model.value = [...(model.value ?? []), chainObj(existingCreated).get(valueKey.value)!]
416
415
  }
417
416
  queryString.value = ''
418
417
  emitChange()
@@ -420,12 +419,14 @@ const handleCreateByEnter = () => {
420
419
  return
421
420
  }
422
421
 
423
- const exactMatch = rawOptions.value.find((o) => !o.__isTemp && chainObj(o).get(labelKey) === qs)
422
+ const exactMatch = rawOptions.value.find(
423
+ (o) => !o.__isTemp && chainObj(o).get(labelKey.value) === qs
424
+ )
424
425
  if (exactMatch) {
425
426
  if (!checkedSet.has(exactMatch)) {
426
427
  checkedSet.add(exactMatch)
427
428
  internalChange = true
428
- model.value = [...(model.value ?? []), chainObj(exactMatch).get(valueKey)!]
429
+ model.value = [...(model.value ?? []), chainObj(exactMatch).get(valueKey.value)!]
429
430
  }
430
431
  queryString.value = ''
431
432
  emitChange()
@@ -433,7 +434,7 @@ const handleCreateByEnter = () => {
433
434
  return
434
435
  }
435
436
 
436
- const created: Record<string, any> = { [labelKey]: qs, [valueKey]: qs }
437
+ const created: Record<string, any> = { [labelKey.value]: qs, [valueKey.value]: qs }
437
438
  createdOptions.value = [...createdOptions.value, created]
438
439
  checkedSet.add(created)
439
440
  internalChange = true
@@ -99,7 +99,7 @@
99
99
  import { dfs, o } from '@cat-kit/core'
100
100
  import { useFormFallbackProps } from '@veltra/compositions'
101
101
  import { ArrowDown, Close, Search } from '@veltra/icons/normal'
102
- import { bem, FORM_EMPTY_CONTENT } from '@veltra/utils'
102
+ import { bem, fieldKey, FORM_EMPTY_CONTENT } from '@veltra/utils'
103
103
  import { injectFormContext } from '@veltra/utils'
104
104
  import { computed, nextTick, shallowRef, watch } from 'vue'
105
105
 
@@ -131,6 +131,9 @@ const props = withDefaults(defineProps<MultiTreeSelectProps>(), {
131
131
  minWidth: '280px'
132
132
  })
133
133
 
134
+ const labelKey = computed(() => fieldKey(props.labelKey, 'label'))
135
+ const valueKey = computed(() => fieldKey(props.valueKey, 'value'))
136
+
134
137
  const treeProps = computed(() => {
135
138
  return o(props as Record<string, any>).omit([
136
139
  'tips',
@@ -239,7 +242,7 @@ watch(
239
242
  dfs(
240
243
  item,
241
244
  (v) => {
242
- newDict.set(v[props.valueKey], v)
245
+ newDict.set(v[valueKey.value], v)
243
246
  },
244
247
  props.childrenKey
245
248
  )
@@ -34,7 +34,7 @@
34
34
  <script lang="ts" setup>
35
35
  import { o } from '@cat-kit/core'
36
36
  import { useDrag, useResizeObserver } from '@veltra/compositions'
37
- import { bem } from '@veltra/utils'
37
+ import { bem, fieldKey } from '@veltra/utils'
38
38
  import { computed, shallowRef, toRefs, useTemplateRef, watch, nextTick } from 'vue'
39
39
 
40
40
  import type { ProgressNodesProps, ProgressNodesEmits } from '../../types'
@@ -49,6 +49,9 @@ const props = withDefaults(defineProps<ProgressNodesProps>(), {
49
49
 
50
50
  const emit = defineEmits<ProgressNodesEmits>()
51
51
 
52
+ const labelKey = computed(() => fieldKey(props.labelKey, 'label'))
53
+ const valueKey = computed(() => fieldKey(props.valueKey, 'value'))
54
+
52
55
  const { nodes } = toRefs(props)
53
56
 
54
57
  const cls = bem('progress-nodes')
@@ -121,16 +124,16 @@ function isChecked(node: Record<string, any>, index: number) {
121
124
  }
122
125
 
123
126
  function isActive(node: Record<string, any>) {
124
- const value = o(node).get(props.valueKey)
127
+ const value = o(node).get(valueKey.value)
125
128
  return props.modelValue === value
126
129
  }
127
130
 
128
131
  function getLabel(node: Record<string, any>) {
129
- return o(node).get(props.labelKey)
132
+ return o(node).get(labelKey.value)
130
133
  }
131
134
 
132
135
  function handleClick(node: Record<string, any>, index: number) {
133
- const value = o(node).get(props.valueKey)
136
+ const value = o(node).get(valueKey.value)
134
137
  emit('update:modelValue', value)
135
138
  emit('click', node, index)
136
139
  }
@@ -20,8 +20,9 @@
20
20
 
21
21
  <script lang="ts" setup>
22
22
  import { useFormFallbackProps } from '@veltra/compositions'
23
- import { bem, FORM_EMPTY_CONTENT } from '@veltra/utils'
23
+ import { bem, fieldKey, FORM_EMPTY_CONTENT } from '@veltra/utils'
24
24
  import { injectFormContext } from '@veltra/utils'
25
+ import { computed } from 'vue'
25
26
 
26
27
  import type { RadioGroupProps, RadioGroupEmits } from '../../types'
27
28
  import URadio from '../radio/radio.vue'
@@ -49,6 +50,9 @@ const { size, disabled, readonly } = useFormFallbackProps([formProps ?? {}, prop
49
50
 
50
51
  const cls = bem('radio-group')
51
52
 
53
+ const labelKey = computed(() => fieldKey(props.labelKey, 'label'))
54
+ const valueKey = computed(() => fieldKey(props.valueKey, 'value'))
55
+
52
56
  const handleUpdate = (value: any, item: Record<string, any>) => {
53
57
  model.value = value
54
58
  emit('change', item)
@@ -119,7 +119,7 @@ import { o } from '@cat-kit/core'
119
119
  import { useFormFallbackProps, useVirtualizer } from '@veltra/compositions'
120
120
  import { vFocus } from '@veltra/directives'
121
121
  import { ArrowDown, Search } from '@veltra/icons/normal'
122
- import { bem, FORM_EMPTY_CONTENT, scrollIntoContainerView, withUnit } from '@veltra/utils'
122
+ import { bem, fieldKey, FORM_EMPTY_CONTENT, scrollIntoContainerView, withUnit } from '@veltra/utils'
123
123
  import { injectFormContext } from '@veltra/utils'
124
124
  import { computed, nextTick, shallowRef, watch } from 'vue'
125
125
 
@@ -160,6 +160,9 @@ defineSlots<{
160
160
 
161
161
  const cls = bem('select')
162
162
 
163
+ const labelKey = computed(() => fieldKey(props.labelKey, 'label'))
164
+ const valueKey = computed(() => fieldKey(props.valueKey, 'value'))
165
+
163
166
  const optionClass = cls.e('option')
164
167
 
165
168
  const { formProps } = injectFormContext()
@@ -176,7 +179,7 @@ const selected = shallowRef<Record<string, any>>()
176
179
  const displayedValue = computed(() => {
177
180
  if (label.value) return label.value
178
181
 
179
- return selected.value ? o(selected.value).get(props.labelKey) : String(props.modelValue ?? '')
182
+ return selected.value ? o(selected.value).get(labelKey.value) : String(props.modelValue ?? '')
180
183
  })
181
184
 
182
185
  const dropdownRef = shallowRef<DropdownExposed>()
@@ -210,12 +213,12 @@ watch(
210
213
 
211
214
  if (!options?.length) return
212
215
 
213
- const { valueKey, labelKey } = props
214
-
215
216
  if (modelValue !== undefined && modelValue !== null && modelValue !== '') {
216
- currentIndex.value = options.findIndex((option) => o(option).get(valueKey) === modelValue)
217
+ currentIndex.value = options.findIndex(
218
+ (option) => o(option).get(valueKey.value) === modelValue
219
+ )
217
220
  selected.value = currentIndex.value >= 0 ? options[currentIndex.value] : undefined
218
- label.value = selected.value ? o(selected.value).get(labelKey) : undefined
221
+ label.value = selected.value ? o(selected.value).get(labelKey.value) : undefined
219
222
  } else {
220
223
  currentIndex.value = -1
221
224
  selected.value = undefined
@@ -238,14 +241,13 @@ const { virtualizer, items } = useVirtualizer({
238
241
 
239
242
  const virtualOptions = computed(() => {
240
243
  const _options = options.value
241
- const { labelKey, valueKey } = props
242
244
  return items.value.map((item) => {
243
245
  const option = _options[item.index]!
244
246
  return {
245
247
  option,
246
248
  index: item.index,
247
- label: o(option).get(labelKey),
248
- val: o(option).get(valueKey),
249
+ label: o(option).get(labelKey.value),
250
+ val: o(option).get(valueKey.value),
249
251
  key: item.index,
250
252
  offset: item.start
251
253
  }
@@ -292,8 +294,8 @@ const handleSelect = (option: Record<string, any>, index: number) => {
292
294
  lock()
293
295
  selected.value = option
294
296
 
295
- emit('update:modelValue', option?.[props.valueKey])
296
- label.value = option?.[props.labelKey]
297
+ emit('update:modelValue', option?.[valueKey.value])
298
+ label.value = option?.[labelKey.value]
297
299
  emit('change', option)
298
300
  if (option.__isTemp) {
299
301
  temOptionsToCreatedOptions()
@@ -1,4 +1,5 @@
1
1
  import { debounce, o as chainObj } from '@cat-kit/core'
2
+ import { fieldKey } from '@veltra/utils'
2
3
  import { computed, shallowRef, watch, type ShallowRef, type ComputedRef } from 'vue'
3
4
 
4
5
  import type { SelectProps } from '../../types'
@@ -53,14 +54,16 @@ export function useOptions(o: Options): UseOptionsReturned {
53
54
  tempOptions.value = []
54
55
  return
55
56
  }
57
+ const labelKey = fieldKey(props.labelKey, 'label')
58
+ const valueKey = fieldKey(props.valueKey, 'value')
56
59
  const exactMatch = options
57
60
  ? options.some((option) => {
58
- return chainObj(option).get(props.labelKey!) === qs
61
+ return chainObj(option).get(labelKey) === qs
59
62
  })
60
63
  : false
61
64
 
62
65
  if (!exactMatch) {
63
- tempOptions.value = [{ [props.labelKey!]: qs, [props.valueKey!]: qs, __isTemp: true }]
66
+ tempOptions.value = [{ [labelKey]: qs, [valueKey]: qs, __isTemp: true }]
64
67
  }
65
68
  }
66
69
 
@@ -95,7 +98,7 @@ export function useOptions(o: Options): UseOptionsReturned {
95
98
  setTempOption(qs, options)
96
99
  } else {
97
100
  // 当选项不是函数时,可以创建选项
98
- const { labelKey } = props
101
+ const labelKey = fieldKey(props.labelKey, 'label')
99
102
 
100
103
  if (!qs) {
101
104
  filteredOptions.value = propsOptions ?? []
@@ -105,7 +108,7 @@ export function useOptions(o: Options): UseOptionsReturned {
105
108
 
106
109
  const _filteredOptions =
107
110
  propsOptions?.filter((item) => {
108
- return chainObj(item).get(labelKey!)?.includes(qs) ?? false
111
+ return chainObj(item).get(labelKey)?.includes(qs) ?? false
109
112
  }) ?? []
110
113
 
111
114
  setTempOption(qs, _filteredOptions)
@@ -54,7 +54,7 @@
54
54
  import { n, o } from '@cat-kit/core'
55
55
  import { useFallbackProps } from '@veltra/compositions'
56
56
  import { Check } from '@veltra/icons/normal'
57
- import { bem } from '@veltra/utils'
57
+ import { bem, fieldKey } from '@veltra/utils'
58
58
  import { computed, shallowRef, useTemplateRef, watch } from 'vue'
59
59
 
60
60
  import type { StepsProps, StepsEmits, ComponentSize, StepsSlotScope } from '../../types'
@@ -80,6 +80,8 @@ const slots = defineSlots<{
80
80
 
81
81
  const cls = bem('steps')
82
82
 
83
+ const labelKey = computed(() => fieldKey(props.labelKey, 'label'))
84
+
83
85
  const { size } = useFallbackProps([props], { size: 'default' as ComponentSize })
84
86
 
85
87
  const className = computed(() => {