@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.
- package/dist/components/action/action-group.vue.d.ts +2 -2
- package/dist/components/action/action.vue.d.ts +1 -1
- package/dist/components/batch-edit/batch-edit.vue.d.ts +2 -2
- package/dist/components/cascade/cascade.js +5 -3
- package/dist/components/cascade/cascade.js.map +1 -1
- package/dist/components/cascade/cascade.vue.d.ts +3 -3
- package/dist/components/cascade/use-data-map.js +2 -1
- package/dist/components/cascade/use-data-map.js.map +1 -1
- package/dist/components/checkbox/checkbox-button.vue.d.ts +1 -1
- package/dist/components/checkbox-group/checkbox-group.js +10 -10
- package/dist/components/checkbox-group/checkbox-group.js.map +1 -1
- package/dist/components/checkbox-group/checkbox-group.vue.d.ts +2 -2
- package/dist/components/code-editor/basic-setup.js +2 -1
- package/dist/components/code-editor/basic-setup.js.map +1 -1
- package/dist/components/code-editor/code-editor.js +136 -79
- package/dist/components/code-editor/code-editor.js.map +1 -1
- package/dist/components/code-editor/code-editor.vue.d.ts +10 -2
- package/dist/components/code-editor/shell-extension.js +139 -0
- package/dist/components/code-editor/shell-extension.js.map +1 -0
- package/dist/components/code-editor/style.js +1 -0
- package/dist/components/code-editor/style2.css +15 -11
- package/dist/components/date-panel/date-panel.vue.d.ts +1 -1
- package/dist/components/date-picker/date-picker.vue.d.ts +1 -1
- package/dist/components/date-range-picker/date-range-picker.vue.d.ts +1 -1
- package/dist/components/multi-select/multi-select.js +31 -36
- package/dist/components/multi-select/multi-select.js.map +1 -1
- package/dist/components/multi-select/multi-select.vue.d.ts +2 -2
- package/dist/components/multi-tree-select/multi-tree-select.js +8 -6
- package/dist/components/multi-tree-select/multi-tree-select.js.map +1 -1
- package/dist/components/multi-tree-select/multi-tree-select.vue.d.ts +2 -2
- package/dist/components/pop-confirm/pop-confirm.vue.d.ts +1 -1
- package/dist/components/progress-nodes/progress-nodes.js +6 -4
- package/dist/components/progress-nodes/progress-nodes.js.map +1 -1
- package/dist/components/radio-group/radio-group.js +8 -6
- package/dist/components/radio-group/radio-group.js.map +1 -1
- package/dist/components/radio-group/radio-group.vue.d.ts +2 -2
- package/dist/components/select/select.js +13 -13
- package/dist/components/select/select.js.map +1 -1
- package/dist/components/select/select.vue.d.ts +2 -2
- package/dist/components/select/use-options.js +7 -4
- package/dist/components/select/use-options.js.map +1 -1
- package/dist/components/steps/steps.js +3 -2
- package/dist/components/steps/steps.js.map +1 -1
- package/dist/components/table/allocate-column-widths.js +32 -0
- package/dist/components/table/allocate-column-widths.js.map +1 -0
- package/dist/components/table/use-col-resize.js +2 -10
- package/dist/components/table/use-col-resize.js.map +1 -1
- package/dist/components/tip/tip.vue.d.ts +1 -1
- package/dist/components/tree/use-check.js +2 -1
- package/dist/components/tree/use-check.js.map +1 -1
- package/dist/components/tree/use-select.js +3 -1
- package/dist/components/tree/use-select.js.map +1 -1
- package/dist/components/tree/use-tree-nodes.js +4 -1
- package/dist/components/tree/use-tree-nodes.js.map +1 -1
- package/dist/components/tree-select/tree-select.js +6 -4
- package/dist/components/tree-select/tree-select.js.map +1 -1
- package/dist/components/tree-select/tree-select.vue.d.ts +2 -2
- package/dist/components/watermark/watermark.vue.d.ts +1 -1
- package/dist/node_modules/.bun/@codemirror_commands@6.10.4/node_modules/@codemirror/commands/dist/index.js +12 -1
- package/dist/node_modules/.bun/@codemirror_commands@6.10.4/node_modules/@codemirror/commands/dist/index.js.map +1 -1
- package/dist/style.js +1 -1
- package/dist/types/code-editor.d.ts +13 -2
- package/package.json +6 -6
- package/src/components/cascade/cascade.vue +6 -3
- package/src/components/cascade/use-data-map.ts +3 -2
- package/src/components/checkbox-group/checkbox-group.vue +10 -8
- package/src/components/code-editor/basic-setup.ts +3 -1
- package/src/components/code-editor/code-editor.vue +154 -96
- package/src/components/code-editor/shell-extension.ts +199 -0
- package/src/components/code-editor/style.scss +20 -13
- package/src/components/code-editor/style.ts +1 -0
- package/src/components/multi-select/multi-select.vue +31 -30
- package/src/components/multi-tree-select/multi-tree-select.vue +5 -2
- package/src/components/progress-nodes/progress-nodes.vue +7 -4
- package/src/components/radio-group/radio-group.vue +5 -1
- package/src/components/select/select.vue +13 -11
- package/src/components/select/use-options.ts +7 -4
- package/src/components/steps/steps.vue +3 -1
- package/src/components/table/__test__/col-width.test.ts +77 -0
- package/src/components/table/allocate-column-widths.ts +38 -0
- package/src/components/table/use-col-resize.ts +2 -19
- package/src/components/tree/use-check.ts +2 -1
- package/src/components/tree/use-select.ts +3 -1
- package/src/components/tree/use-tree-nodes.ts +5 -9
- package/src/components/tree-select/tree-select.vue +7 -4
- 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
|
-
|
|
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) {
|
|
@@ -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
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
const
|
|
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(
|
|
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
|
|
399
|
-
|
|
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
|
|
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(
|
|
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[
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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?.[
|
|
296
|
-
label.value = option?.[
|
|
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(
|
|
61
|
+
return chainObj(option).get(labelKey) === qs
|
|
59
62
|
})
|
|
60
63
|
: false
|
|
61
64
|
|
|
62
65
|
if (!exactMatch) {
|
|
63
|
-
tempOptions.value = [{ [
|
|
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
|
|
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
|
|
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(() => {
|