@veltra/desktop 1.3.7 → 1.3.8
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/auto-complete/auto-complete.vue.d.ts +2 -2
- package/dist/components/cascade/cascade.vue.d.ts +5 -5
- package/dist/components/checkbox/checkbox-button.vue.d.ts +1 -1
- package/dist/components/checkbox-group/checkbox-group.vue.d.ts +2 -2
- package/dist/components/code-editor/code-editor.vue.d.ts +2 -2
- package/dist/components/date-panel/date-panel.vue.d.ts +1 -1
- package/dist/components/date-picker/date-picker.vue.d.ts +2 -2
- package/dist/components/date-range-picker/date-range-picker.vue.d.ts +2 -2
- package/dist/components/input/input.vue.d.ts +4 -4
- package/dist/components/multi-select/multi-select.vue.d.ts +3 -3
- package/dist/components/multi-tree-select/multi-tree-select.vue.d.ts +5 -5
- package/dist/components/number-input/number-input.vue.d.ts +4 -4
- package/dist/components/number-range-input/number-range-input.vue.d.ts +2 -2
- package/dist/components/password-input/password-input.vue.d.ts +1 -1
- package/dist/components/radio-group/radio-group.vue.d.ts +2 -2
- package/dist/components/rich-text-editor/rich-text-editor.vue.d.ts +1 -1
- package/dist/components/select/select.js +42 -51
- package/dist/components/select/select.js.map +1 -1
- package/dist/components/select/select.vue.d.ts +6 -14
- package/dist/components/steps/steps.vue.d.ts +3 -3
- package/dist/components/textarea/textarea.vue.d.ts +4 -4
- package/dist/components/tree-select/tree-select.js +40 -33
- package/dist/components/tree-select/tree-select.js.map +1 -1
- package/dist/components/tree-select/tree-select.vue.d.ts +5 -5
- package/dist/types/select.d.ts +5 -4
- package/dist/types/tree-select.d.ts +4 -2
- package/package.json +6 -6
- package/src/components/select/__test__/select.test.ts +77 -2
- package/src/components/select/select.vue +39 -47
- package/src/components/tree-select/__test__/tree-select.test.ts +133 -0
- package/src/components/tree-select/tree-select.vue +44 -38
- package/src/types/select.ts +5 -4
- package/src/types/tree-select.ts +4 -3
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { SelectProps } from "../../types/select.js";
|
|
2
2
|
import "../../types/index.js";
|
|
3
3
|
//#region src/components/select/select.vue.d.ts
|
|
4
|
-
type __VLS_Props = SelectProps;
|
|
5
4
|
type __VLS_Slots = {
|
|
6
5
|
/** 前缀插槽 */
|
|
7
6
|
prefix?: () => any;
|
|
@@ -11,28 +10,21 @@ type __VLS_Slots = {
|
|
|
11
10
|
index: number;
|
|
12
11
|
}) => any;
|
|
13
12
|
};
|
|
14
|
-
|
|
15
|
-
'text'?: any;
|
|
16
|
-
};
|
|
17
|
-
type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
|
|
18
|
-
declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
19
|
-
"update:text": (value: any) => any;
|
|
20
|
-
} & {
|
|
13
|
+
declare const __VLS_base: import("vue").DefineComponent<SelectProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
21
14
|
"update:modelValue": (modelValue?: any) => any;
|
|
22
15
|
change: (option?: Record<string, any> | undefined) => any;
|
|
23
|
-
"update:
|
|
24
|
-
}, string, import("vue").PublicProps, Readonly<
|
|
16
|
+
"update:text": (text?: string | undefined) => any;
|
|
17
|
+
}, string, import("vue").PublicProps, Readonly<SelectProps> & Readonly<{
|
|
25
18
|
"onUpdate:modelValue"?: ((modelValue?: any) => any) | undefined;
|
|
26
19
|
onChange?: ((option?: Record<string, any> | undefined) => any) | undefined;
|
|
27
|
-
"onUpdate:text"?: ((
|
|
28
|
-
"onUpdate:label"?: ((label?: string | undefined) => any) | undefined;
|
|
20
|
+
"onUpdate:text"?: ((text?: string | undefined) => any) | undefined;
|
|
29
21
|
}>, {
|
|
30
|
-
disabled: boolean;
|
|
31
|
-
readonly: boolean;
|
|
32
22
|
labelKey: string;
|
|
33
23
|
valueKey: string;
|
|
34
24
|
placeholder: string;
|
|
35
25
|
clearable: boolean;
|
|
26
|
+
disabled: boolean;
|
|
27
|
+
readonly: boolean;
|
|
36
28
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
37
29
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
38
30
|
declare const _default: typeof __VLS_export;
|
|
@@ -7,14 +7,14 @@ type __VLS_Slots = {
|
|
|
7
7
|
tip?: (scope: StepsSlotScope) => any;
|
|
8
8
|
};
|
|
9
9
|
declare const __VLS_base: import("vue").DefineComponent<StepsProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
10
|
-
"item-click": (item: Record<string, any>, index: number) => any;
|
|
11
10
|
"update:current": (value?: string | number | undefined) => any;
|
|
11
|
+
"item-click": (item: Record<string, any>, index: number) => any;
|
|
12
12
|
}, string, import("vue").PublicProps, Readonly<StepsProps> & Readonly<{
|
|
13
|
-
"onItem-click"?: ((item: Record<string, any>, index: number) => any) | undefined;
|
|
14
13
|
"onUpdate:current"?: ((value?: string | number | undefined) => any) | undefined;
|
|
14
|
+
"onItem-click"?: ((item: Record<string, any>, index: number) => any) | undefined;
|
|
15
15
|
}>, {
|
|
16
|
-
direction: "horizontal" | "vertical";
|
|
17
16
|
labelKey: string;
|
|
17
|
+
direction: "horizontal" | "vertical";
|
|
18
18
|
finishedStepType: import("@veltra/utils").ColorType;
|
|
19
19
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
20
20
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
@@ -10,21 +10,21 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
|
|
|
10
10
|
"update:modelValue": (value: string | undefined) => any;
|
|
11
11
|
} & {
|
|
12
12
|
"update:modelValue": (value: string) => any;
|
|
13
|
-
change: (value: string) => any;
|
|
14
13
|
clear: () => any;
|
|
14
|
+
change: (value: string) => any;
|
|
15
15
|
focus: () => any;
|
|
16
16
|
blur: () => any;
|
|
17
17
|
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
18
18
|
"onUpdate:modelValue"?: ((value: string) => any) | undefined;
|
|
19
|
-
onChange?: ((value: string) => any) | undefined;
|
|
20
19
|
onClear?: (() => any) | undefined;
|
|
20
|
+
onChange?: ((value: string) => any) | undefined;
|
|
21
21
|
onFocus?: (() => any) | undefined;
|
|
22
22
|
onBlur?: (() => any) | undefined;
|
|
23
23
|
}>, {
|
|
24
|
-
disabled: boolean;
|
|
25
|
-
readonly: boolean;
|
|
26
24
|
placeholder: string;
|
|
27
25
|
clearable: boolean;
|
|
26
|
+
disabled: boolean;
|
|
27
|
+
readonly: boolean;
|
|
28
28
|
resize: boolean;
|
|
29
29
|
autosize: boolean;
|
|
30
30
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
@@ -3,7 +3,7 @@ import _sfc_main$2 from "../dropdown/dropdown.js";
|
|
|
3
3
|
import input_default from "../input/input.js";
|
|
4
4
|
import _sfc_main$3 from "../tree/tree.js";
|
|
5
5
|
import { Fragment, Transition, computed, createBlock, createCommentVNode, createElementBlock, createSlots, createTextVNode, createVNode, defineComponent, mergeModels, mergeProps, nextTick, normalizeClass, openBlock, renderSlot, shallowRef, toDisplayString, unref, useModel, useSlots, useTemplateRef, watch, withCtx, withModifiers } from "vue";
|
|
6
|
-
import { useFormFallbackProps } from "@veltra/compositions";
|
|
6
|
+
import { useFormFallbackProps, useUserAction } from "@veltra/compositions";
|
|
7
7
|
import { FORM_EMPTY_CONTENT, bem, fieldKey, injectFormContext } from "@veltra/utils";
|
|
8
8
|
import { ArrowDown, Close } from "@veltra/icons/normal";
|
|
9
9
|
import { dfs, o } from "@cat-kit/core";
|
|
@@ -25,7 +25,6 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
25
25
|
},
|
|
26
26
|
minWidth: { default: "280px" },
|
|
27
27
|
width: {},
|
|
28
|
-
text: {},
|
|
29
28
|
contentStyle: {},
|
|
30
29
|
contentClass: {},
|
|
31
30
|
tips: {},
|
|
@@ -92,6 +91,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
92
91
|
treeRef.value?.filter(qs);
|
|
93
92
|
});
|
|
94
93
|
const model = useModel(__props, "modelValue");
|
|
94
|
+
/** 内部展示文案,仅由 data 推导;通过 update:text 单向通知父级 */
|
|
95
95
|
const label = shallowRef();
|
|
96
96
|
const dropdownVisible = shallowRef(false);
|
|
97
97
|
/**
|
|
@@ -101,14 +101,20 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
101
101
|
*/
|
|
102
102
|
const querying = shallowRef(false);
|
|
103
103
|
const inputRef = useTemplateRef("inputRef");
|
|
104
|
+
/** 更新内部文案;值变化时单向 emit update:text */
|
|
105
|
+
function setLabel(next) {
|
|
106
|
+
if (label.value === next) return;
|
|
107
|
+
label.value = next;
|
|
108
|
+
emit("update:text", next);
|
|
109
|
+
}
|
|
104
110
|
/** 触发输入框的值:查询态下承载查询串,否则展示选中标签 */
|
|
105
111
|
const inputValue = computed(() => {
|
|
106
112
|
if (props.filterable && querying.value) return qs.value;
|
|
107
|
-
return
|
|
113
|
+
return model.value || model.value === 0 ? label.value : void 0;
|
|
108
114
|
});
|
|
109
115
|
/** 查询态下已选标签降级为占位提示 */
|
|
110
116
|
const inputPlaceholder = computed(() => {
|
|
111
|
-
const display =
|
|
117
|
+
const display = model.value || model.value === 0 ? label.value : void 0;
|
|
112
118
|
if (props.filterable && querying.value && display) return display;
|
|
113
119
|
return props.placeholder;
|
|
114
120
|
});
|
|
@@ -120,7 +126,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
120
126
|
/** 悬停且存在选中值时展示清除按钮(替代下拉箭头) */
|
|
121
127
|
const showClear = computed(() => {
|
|
122
128
|
if (!props.clearable || disabled.value || !hovered.value) return false;
|
|
123
|
-
return !!(
|
|
129
|
+
return !!(model.value || model.value === 0 ? label.value : void 0);
|
|
124
130
|
});
|
|
125
131
|
/**
|
|
126
132
|
* 过滤模式下拦截输入区域的点击:
|
|
@@ -143,49 +149,46 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
143
149
|
const { size, disabled, readonly } = useFormFallbackProps([formProps ?? {}, props]);
|
|
144
150
|
const treeRef = shallowRef();
|
|
145
151
|
const dropdownRef = shallowRef();
|
|
146
|
-
|
|
147
|
-
|
|
152
|
+
const { userAction, isUserActive } = useUserAction();
|
|
153
|
+
/** 清空 */
|
|
154
|
+
const handleClear = userAction(() => {
|
|
148
155
|
model.value = "";
|
|
149
|
-
|
|
156
|
+
setLabel(void 0);
|
|
150
157
|
emit("clear");
|
|
151
158
|
emit("change", void 0);
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
if (
|
|
157
|
-
|
|
158
|
-
label.value = void 0;
|
|
159
|
-
model = void 0;
|
|
159
|
+
});
|
|
160
|
+
/** 外部回显:按 data 查找 label(O(n));用户动作期内跳过 */
|
|
161
|
+
watch([() => props.data, model], ([data, modelVal]) => {
|
|
162
|
+
if (isUserActive()) return;
|
|
163
|
+
if (!data?.length || modelVal === void 0 || modelVal === null || modelVal === "") {
|
|
164
|
+
setLabel(void 0);
|
|
160
165
|
return;
|
|
161
166
|
}
|
|
162
|
-
let
|
|
167
|
+
let nextLabel;
|
|
163
168
|
const childrenKey = props.childrenKey ?? "children";
|
|
164
169
|
data.some((item) => {
|
|
170
|
+
let founded = false;
|
|
165
171
|
dfs(item, (v) => {
|
|
166
|
-
if (v[valueKey.value] ===
|
|
167
|
-
|
|
172
|
+
if (v[valueKey.value] === modelVal) {
|
|
173
|
+
nextLabel = v[labelKey.value];
|
|
168
174
|
founded = true;
|
|
169
175
|
return true;
|
|
170
176
|
}
|
|
171
177
|
}, childrenKey);
|
|
172
178
|
return founded;
|
|
173
179
|
});
|
|
180
|
+
setLabel(nextLabel);
|
|
174
181
|
}, { immediate: true });
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
nextTick(() => {
|
|
178
|
-
changedByEvent = false;
|
|
179
|
-
});
|
|
182
|
+
/** 用户选择:O(1) 写入值与文案,跳过 data 回显查找 */
|
|
183
|
+
const handleSelect = userAction((selected, selectedData) => {
|
|
180
184
|
model.value = selected ?? "";
|
|
181
|
-
if (selectedData)
|
|
182
|
-
else
|
|
185
|
+
if (selectedData) setLabel(o(selectedData).get(labelKey.value));
|
|
186
|
+
else setLabel(void 0);
|
|
183
187
|
emit("change", selectedData);
|
|
184
|
-
emit("update:text", label.value);
|
|
185
188
|
querying.value = false;
|
|
186
189
|
qs.value = "";
|
|
187
190
|
dropdownRef.value?.close();
|
|
188
|
-
};
|
|
191
|
+
});
|
|
189
192
|
const handleDropdownVisible = (visible) => {
|
|
190
193
|
if (!visible) qs.value = "";
|
|
191
194
|
};
|
|
@@ -229,11 +232,11 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
229
232
|
class: normalizeClass(unref(cls).e("clear")),
|
|
230
233
|
title: "清除",
|
|
231
234
|
key: "clear",
|
|
232
|
-
onClick: withModifiers(handleClear, ["stop"])
|
|
235
|
+
onClick: withModifiers(unref(handleClear), ["stop"])
|
|
233
236
|
}, {
|
|
234
237
|
default: withCtx(() => [createVNode(unref(Close))]),
|
|
235
238
|
_: 1
|
|
236
|
-
}, 8, ["class"])) : (openBlock(), createBlock(unref(_sfc_main$1), {
|
|
239
|
+
}, 8, ["class", "onClick"])) : (openBlock(), createBlock(unref(_sfc_main$1), {
|
|
237
240
|
class: normalizeClass(unref(cls).e("arrow")),
|
|
238
241
|
key: "arrow"
|
|
239
242
|
}, {
|
|
@@ -256,14 +259,18 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
256
259
|
])]),
|
|
257
260
|
content: withCtx(() => [createCommentVNode(" 菜单列表 "), createVNode(unref(_sfc_main$3), mergeProps(treeProps.value, {
|
|
258
261
|
selected: model.value,
|
|
259
|
-
"onUpdate:selected": handleSelect,
|
|
262
|
+
"onUpdate:selected": unref(handleSelect),
|
|
260
263
|
ref_key: "treeRef",
|
|
261
264
|
ref: treeRef,
|
|
262
265
|
selectable: "",
|
|
263
266
|
class: unref(cls).e("content-tree"),
|
|
264
267
|
slots,
|
|
265
268
|
"scroll-to-view": ""
|
|
266
|
-
}), null, 16, [
|
|
269
|
+
}), null, 16, [
|
|
270
|
+
"selected",
|
|
271
|
+
"onUpdate:selected",
|
|
272
|
+
"class"
|
|
273
|
+
])]),
|
|
267
274
|
_: 3
|
|
268
275
|
}, 16, [
|
|
269
276
|
"class",
|
|
@@ -273,7 +280,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
273
280
|
"min-width",
|
|
274
281
|
"width",
|
|
275
282
|
"disabled"
|
|
276
|
-
])) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [createTextVNode(toDisplayString(
|
|
283
|
+
])) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [createTextVNode(toDisplayString(label.value || unref(FORM_EMPTY_CONTENT)), 1)], 64));
|
|
277
284
|
};
|
|
278
285
|
}
|
|
279
286
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tree-select.js","names":["$attrs","$slots"],"sources":["../../../src/components/tree-select/tree-select.vue"],"sourcesContent":["<template>\n <u-dropdown\n v-if=\"!readonly\"\n v-bind=\"$attrs\"\n :class=\"[cls.b, bem.is('disabled', disabled)]\"\n trigger=\"click\"\n :content-class=\"[cls.e('panel'), cls.em('panel', size), contentClass]\"\n :content-style=\"contentStyle\"\n ref=\"dropdownRef\"\n v-model:visible=\"dropdownVisible\"\n :min-width=\"minWidth\"\n :width=\"width\"\n :disabled=\"disabled\"\n @update:visible=\"handleDropdownVisible\"\n @mouseenter=\"hovered = true\"\n @mouseleave=\"hovered = false\"\n >\n <template #trigger>\n <u-input\n ref=\"inputRef\"\n :size=\"size\"\n :disabled=\"disabled\"\n :placeholder=\"inputPlaceholder\"\n :clearable=\"false\"\n :model-value=\"inputValue\"\n @update:model-value=\"handleQueryInput\"\n :native-readonly=\"!filterable || !querying\"\n @click.capture=\"handleTriggerClickCapture\"\n >\n <template #prefix v-if=\"$slots.prefix\">\n <slot name=\"prefix\" />\n </template>\n <template #suffix>\n <transition name=\"zoom-in\" mode=\"out-in\">\n <u-icon\n v-if=\"showClear\"\n :class=\"cls.e('clear')\"\n title=\"清除\"\n key=\"clear\"\n @click.stop=\"handleClear\"\n >\n <Close />\n </u-icon>\n <u-icon v-else :class=\"cls.e('arrow')\" key=\"arrow\"><ArrowDown /></u-icon>\n </transition>\n </template>\n </u-input>\n </template>\n <template #content>\n <!-- 菜单列表 -->\n\n <u-tree\n v-bind=\"treeProps\"\n :selected=\"model\"\n @update:selected=\"handleSelect\"\n ref=\"treeRef\"\n selectable\n :class=\"cls.e('content-tree')\"\n :slots=\"slots\"\n scroll-to-view\n />\n </template>\n </u-dropdown>\n\n <template v-else>\n {{ text || label || FORM_EMPTY_CONTENT }}\n </template>\n</template>\n\n<script lang=\"ts\" setup>\nimport { dfs, o } from '@cat-kit/core'\nimport { useFormFallbackProps } from '@veltra/compositions'\nimport { ArrowDown, Close } from '@veltra/icons/normal'\nimport { bem, fieldKey, FORM_EMPTY_CONTENT } from '@veltra/utils'\nimport { injectFormContext } from '@veltra/utils'\nimport { computed, nextTick, shallowRef, useTemplateRef, watch } from 'vue'\n\nimport type { TreeSelectProps, TreeSelectEmits, TreeExposed, InputExposed } from '../../types'\nimport { UDropdown } from '../dropdown'\nimport { UIcon } from '../icon'\nimport { UInput } from '../input'\nimport { UTree } from '../tree'\nimport type { TreeSlotsScope } from '../tree/di'\n\ndefineOptions({ name: 'UTreeSelect', inheritAttrs: false })\n\nconst props = withDefaults(defineProps<TreeSelectProps>(), {\n labelKey: 'label',\n valueKey: 'value',\n placeholder: '请选择',\n expandAll: false,\n clearable: true,\n disabled: undefined,\n readonly: undefined,\n filterable: false,\n minWidth: '280px'\n})\n\nconst emit = defineEmits<TreeSelectEmits>()\n\nconst slots = defineSlots<{\n /** 默认插槽 */\n default?: (props: TreeSlotsScope) => any\n /** 前缀插槽 */\n prefix?: () => any\n}>()\n\nconst treeProps = computed(() => {\n return o(props as Record<string, any>).omit([\n 'tips',\n 'field',\n 'placeholder',\n 'disabled',\n 'label',\n 'readonly',\n 'contentClass',\n 'contentStyle',\n 'minWidth',\n 'width'\n ])\n})\n\nconst cls = bem('tree-select')\n\nconst labelKey = computed(() => fieldKey(props.labelKey, 'label'))\nconst valueKey = computed(() => fieldKey(props.valueKey, 'value'))\n\n/**过滤 */\nconst qs = shallowRef('')\nwatch(qs, (qs) => {\n treeRef.value?.filter(qs)\n})\n\nconst model = defineModel<string | number>()\n\nconst label = shallowRef<string>()\n\nconst dropdownVisible = shallowRef(false)\n\n/**\n * 是否处于查询态:决定触发输入框显示查询串还是选中标签。\n * 与面板可见状态解耦 —— 选择后立即退出查询态,\n * 不等面板关闭动画结束(否则显示值会延迟一个动画时长才恢复)。\n */\nconst querying = shallowRef(false)\n\nconst inputRef = useTemplateRef<InputExposed>('inputRef')\n\n/** 触发输入框的值:查询态下承载查询串,否则展示选中标签 */\nconst inputValue = computed(() => {\n if (props.filterable && querying.value) return qs.value\n return props.text ?? (model.value ? label.value : undefined)\n})\n\n/** 查询态下已选标签降级为占位提示 */\nconst inputPlaceholder = computed(() => {\n const display = props.text ?? (model.value ? label.value : undefined)\n if (props.filterable && querying.value && display) {\n return display\n }\n return props.placeholder\n})\n\nfunction handleQueryInput(value: string) {\n if (!props.filterable) return\n qs.value = value\n}\n\nconst hovered = shallowRef(false)\n\n/** 悬停且存在选中值时展示清除按钮(替代下拉箭头) */\nconst showClear = computed(() => {\n if (!props.clearable || disabled.value || !hovered.value) return false\n return !!(props.text ?? (model.value ? label.value : undefined))\n})\n\n/**\n * 过滤模式下拦截输入区域的点击:\n * 阻止 dropdown 的 trigger 开合切换,保持面板展开以不中断输入。\n * 非输入区域(箭头、留白)放行,维持原开合行为。\n */\nfunction handleTriggerClickCapture(e: MouseEvent) {\n if (!props.filterable || disabled.value) return\n if (!(e.target instanceof HTMLInputElement)) return\n\n e.stopPropagation()\n if (!dropdownVisible.value) dropdownRef.value?.open()\n}\n\nwatch(dropdownVisible, (visible) => {\n if (visible && props.filterable) {\n // 面板展开后进入查询态并聚焦输入框,可以立即输入查询\n querying.value = true\n nextTick(() => inputRef.value?.el?.focus())\n } else if (!visible) {\n querying.value = false\n }\n})\n\nconst { formProps } = injectFormContext()\n\nconst { size, disabled, readonly } = useFormFallbackProps([formProps ?? {}, props])\n\nconst treeRef = shallowRef<TreeExposed>()\n\nconst dropdownRef = shallowRef<InstanceType<typeof UDropdown>>()\n\n/**清空 */\nconst handleClear = () => {\n model.value = ''\n label.value = undefined\n emit('clear')\n emit('change', undefined)\n emit('update:text', '')\n}\n\nlet changedByEvent = false\nwatch(\n [() => props.data, model],\n ([data, model]) => {\n if (changedByEvent) return\n if (!data?.length || model === undefined) {\n label.value = undefined\n model = undefined\n return\n }\n\n let founded = false\n const childrenKey = props.childrenKey ?? 'children'\n data.some((item) => {\n dfs(\n item,\n (v) => {\n if (v[valueKey.value] === model) {\n label.value = v[labelKey.value]\n founded = true\n return true\n }\n },\n childrenKey\n )\n\n return founded\n })\n },\n { immediate: true }\n)\n\nconst handleSelect = (selected?: string | number, selectedData?: Record<string, any>) => {\n changedByEvent = true\n nextTick(() => {\n changedByEvent = false\n })\n\n model.value = selected ?? ''\n\n if (selectedData) {\n label.value = o(selectedData).get(labelKey.value)\n } else {\n label.value = ''\n }\n\n emit('change', selectedData)\n emit('update:text', label.value)\n // 立即退出查询态,输入框同步恢复显示选中标签\n querying.value = false\n qs.value = ''\n dropdownRef.value?.close()\n}\n\nconst handleDropdownVisible = (visible: boolean) => {\n if (!visible) {\n qs.value = ''\n }\n}\n</script>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"tree-select.js","names":["$attrs","$slots"],"sources":["../../../src/components/tree-select/tree-select.vue"],"sourcesContent":["<template>\n <u-dropdown\n v-if=\"!readonly\"\n v-bind=\"$attrs\"\n :class=\"[cls.b, bem.is('disabled', disabled)]\"\n trigger=\"click\"\n :content-class=\"[cls.e('panel'), cls.em('panel', size), contentClass]\"\n :content-style=\"contentStyle\"\n ref=\"dropdownRef\"\n v-model:visible=\"dropdownVisible\"\n :min-width=\"minWidth\"\n :width=\"width\"\n :disabled=\"disabled\"\n @update:visible=\"handleDropdownVisible\"\n @mouseenter=\"hovered = true\"\n @mouseleave=\"hovered = false\"\n >\n <template #trigger>\n <u-input\n ref=\"inputRef\"\n :size=\"size\"\n :disabled=\"disabled\"\n :placeholder=\"inputPlaceholder\"\n :clearable=\"false\"\n :model-value=\"inputValue\"\n @update:model-value=\"handleQueryInput\"\n :native-readonly=\"!filterable || !querying\"\n @click.capture=\"handleTriggerClickCapture\"\n >\n <template #prefix v-if=\"$slots.prefix\">\n <slot name=\"prefix\" />\n </template>\n <template #suffix>\n <transition name=\"zoom-in\" mode=\"out-in\">\n <u-icon\n v-if=\"showClear\"\n :class=\"cls.e('clear')\"\n title=\"清除\"\n key=\"clear\"\n @click.stop=\"handleClear\"\n >\n <Close />\n </u-icon>\n <u-icon v-else :class=\"cls.e('arrow')\" key=\"arrow\"><ArrowDown /></u-icon>\n </transition>\n </template>\n </u-input>\n </template>\n <template #content>\n <!-- 菜单列表 -->\n\n <u-tree\n v-bind=\"treeProps\"\n :selected=\"model\"\n @update:selected=\"handleSelect\"\n ref=\"treeRef\"\n selectable\n :class=\"cls.e('content-tree')\"\n :slots=\"slots\"\n scroll-to-view\n />\n </template>\n </u-dropdown>\n\n <template v-else>\n {{ label || FORM_EMPTY_CONTENT }}\n </template>\n</template>\n\n<script lang=\"ts\" setup>\nimport { dfs, o } from '@cat-kit/core'\nimport { useFormFallbackProps, useUserAction } from '@veltra/compositions'\nimport { ArrowDown, Close } from '@veltra/icons/normal'\nimport { bem, fieldKey, FORM_EMPTY_CONTENT } from '@veltra/utils'\nimport { injectFormContext } from '@veltra/utils'\nimport { computed, nextTick, shallowRef, useTemplateRef, watch } from 'vue'\n\nimport type { TreeSelectProps, TreeSelectEmits, TreeExposed, InputExposed } from '../../types'\nimport { UDropdown } from '../dropdown'\nimport { UIcon } from '../icon'\nimport { UInput } from '../input'\nimport { UTree } from '../tree'\nimport type { TreeSlotsScope } from '../tree/di'\n\ndefineOptions({ name: 'UTreeSelect', inheritAttrs: false })\n\nconst props = withDefaults(defineProps<TreeSelectProps>(), {\n labelKey: 'label',\n valueKey: 'value',\n placeholder: '请选择',\n expandAll: false,\n clearable: true,\n disabled: undefined,\n readonly: undefined,\n filterable: false,\n minWidth: '280px'\n})\n\nconst emit = defineEmits<TreeSelectEmits>()\n\nconst slots = defineSlots<{\n /** 默认插槽 */\n default?: (props: TreeSlotsScope) => any\n /** 前缀插槽 */\n prefix?: () => any\n}>()\n\nconst treeProps = computed(() => {\n return o(props as Record<string, any>).omit([\n 'tips',\n 'field',\n 'placeholder',\n 'disabled',\n 'label',\n 'readonly',\n 'contentClass',\n 'contentStyle',\n 'minWidth',\n 'width'\n ])\n})\n\nconst cls = bem('tree-select')\n\nconst labelKey = computed(() => fieldKey(props.labelKey, 'label'))\nconst valueKey = computed(() => fieldKey(props.valueKey, 'value'))\n\n/**过滤 */\nconst qs = shallowRef('')\nwatch(qs, (qs) => {\n treeRef.value?.filter(qs)\n})\n\nconst model = defineModel<string | number>()\n\n/** 内部展示文案,仅由 data 推导;通过 update:text 单向通知父级 */\nconst label = shallowRef<string>()\n\nconst dropdownVisible = shallowRef(false)\n\n/**\n * 是否处于查询态:决定触发输入框显示查询串还是选中标签。\n * 与面板可见状态解耦 —— 选择后立即退出查询态,\n * 不等面板关闭动画结束(否则显示值会延迟一个动画时长才恢复)。\n */\nconst querying = shallowRef(false)\n\nconst inputRef = useTemplateRef<InputExposed>('inputRef')\n\n/** 更新内部文案;值变化时单向 emit update:text */\nfunction setLabel(next?: string) {\n if (label.value === next) return\n label.value = next\n emit('update:text', next)\n}\n\n/** 触发输入框的值:查询态下承载查询串,否则展示选中标签 */\nconst inputValue = computed(() => {\n if (props.filterable && querying.value) return qs.value\n return model.value || model.value === 0 ? label.value : undefined\n})\n\n/** 查询态下已选标签降级为占位提示 */\nconst inputPlaceholder = computed(() => {\n const display = model.value || model.value === 0 ? label.value : undefined\n if (props.filterable && querying.value && display) {\n return display\n }\n return props.placeholder\n})\n\nfunction handleQueryInput(value: string) {\n if (!props.filterable) return\n qs.value = value\n}\n\nconst hovered = shallowRef(false)\n\n/** 悬停且存在选中值时展示清除按钮(替代下拉箭头) */\nconst showClear = computed(() => {\n if (!props.clearable || disabled.value || !hovered.value) return false\n return !!(model.value || model.value === 0 ? label.value : undefined)\n})\n\n/**\n * 过滤模式下拦截输入区域的点击:\n * 阻止 dropdown 的 trigger 开合切换,保持面板展开以不中断输入。\n * 非输入区域(箭头、留白)放行,维持原开合行为。\n */\nfunction handleTriggerClickCapture(e: MouseEvent) {\n if (!props.filterable || disabled.value) return\n if (!(e.target instanceof HTMLInputElement)) return\n\n e.stopPropagation()\n if (!dropdownVisible.value) dropdownRef.value?.open()\n}\n\nwatch(dropdownVisible, (visible) => {\n if (visible && props.filterable) {\n // 面板展开后进入查询态并聚焦输入框,可以立即输入查询\n querying.value = true\n nextTick(() => inputRef.value?.el?.focus())\n } else if (!visible) {\n querying.value = false\n }\n})\n\nconst { formProps } = injectFormContext()\n\nconst { size, disabled, readonly } = useFormFallbackProps([formProps ?? {}, props])\n\nconst treeRef = shallowRef<TreeExposed>()\n\nconst dropdownRef = shallowRef<InstanceType<typeof UDropdown>>()\n\nconst { userAction, isUserActive } = useUserAction()\n\n/** 清空 */\nconst handleClear = userAction(() => {\n model.value = ''\n setLabel(undefined)\n emit('clear')\n emit('change', undefined)\n})\n\n/** 外部回显:按 data 查找 label(O(n));用户动作期内跳过 */\nwatch(\n [() => props.data, model],\n ([data, modelVal]) => {\n if (isUserActive()) return\n if (!data?.length || modelVal === undefined || modelVal === null || modelVal === '') {\n setLabel(undefined)\n return\n }\n\n let nextLabel: string | undefined\n const childrenKey = props.childrenKey ?? 'children'\n data.some((item) => {\n let founded = false\n dfs(\n item,\n (v) => {\n if (v[valueKey.value] === modelVal) {\n nextLabel = v[labelKey.value]\n founded = true\n return true\n }\n },\n childrenKey\n )\n\n return founded\n })\n setLabel(nextLabel)\n },\n { immediate: true }\n)\n\n/** 用户选择:O(1) 写入值与文案,跳过 data 回显查找 */\nconst handleSelect = userAction(\n (selected?: string | number, selectedData?: Record<string, any>) => {\n model.value = selected ?? ''\n\n if (selectedData) {\n setLabel(o(selectedData).get(labelKey.value))\n } else {\n setLabel(undefined)\n }\n\n emit('change', selectedData)\n // 立即退出查询态,输入框同步恢复显示选中标签\n querying.value = false\n qs.value = ''\n dropdownRef.value?.close()\n }\n)\nconst handleDropdownVisible = (visible: boolean) => {\n if (!visible) {\n qs.value = ''\n }\n}\n</script>\n"],"mappings":"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"}
|
|
@@ -17,24 +17,24 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {
|
|
|
17
17
|
"update:modelValue": (value: string | number | undefined) => any;
|
|
18
18
|
} & {
|
|
19
19
|
"update:modelValue": (value?: string | number | undefined) => any;
|
|
20
|
-
change: (selectedData?: Record<string, any> | undefined) => any;
|
|
21
20
|
clear: () => any;
|
|
21
|
+
change: (selectedData?: Record<string, any> | undefined) => any;
|
|
22
22
|
"update:text": (text?: string | undefined) => any;
|
|
23
23
|
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
24
24
|
"onUpdate:modelValue"?: ((value?: string | number | undefined) => any) | undefined;
|
|
25
|
-
onChange?: ((selectedData?: Record<string, any> | undefined) => any) | undefined;
|
|
26
25
|
onClear?: (() => any) | undefined;
|
|
26
|
+
onChange?: ((selectedData?: Record<string, any> | undefined) => any) | undefined;
|
|
27
27
|
"onUpdate:text"?: ((text?: string | undefined) => any) | undefined;
|
|
28
28
|
}>, {
|
|
29
|
-
disabled: boolean;
|
|
30
|
-
readonly: boolean;
|
|
31
|
-
minWidth: string;
|
|
32
29
|
expandAll: boolean;
|
|
33
30
|
labelKey: string;
|
|
34
31
|
valueKey: string;
|
|
35
32
|
placeholder: string;
|
|
36
33
|
clearable: boolean;
|
|
34
|
+
disabled: boolean;
|
|
35
|
+
readonly: boolean;
|
|
37
36
|
filterable: boolean;
|
|
37
|
+
minWidth: string;
|
|
38
38
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
39
39
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
40
40
|
declare const _default: typeof __VLS_export;
|
package/dist/types/select.d.ts
CHANGED
|
@@ -5,8 +5,6 @@ import { DeconstructValue, FormComponentProps } from "@veltra/utils";
|
|
|
5
5
|
interface SelectProps extends FormComponentProps {
|
|
6
6
|
/** 绑定值 */
|
|
7
7
|
modelValue?: any;
|
|
8
|
-
/** 文本内容 */
|
|
9
|
-
text?: string;
|
|
10
8
|
/**
|
|
11
9
|
* 列表选项
|
|
12
10
|
* @description 如果传入一个函数,那么filterable会被强制启用
|
|
@@ -54,8 +52,11 @@ interface SelectProps extends FormComponentProps {
|
|
|
54
52
|
};
|
|
55
53
|
}
|
|
56
54
|
interface SelectEmits {
|
|
57
|
-
/**
|
|
58
|
-
|
|
55
|
+
/**
|
|
56
|
+
* 选中项文案变化(单向通知,用于同步父级冗余字段)
|
|
57
|
+
* @description 展示始终由 options 推导,请用 `@update:text` 而非 `v-model:text`
|
|
58
|
+
*/
|
|
59
|
+
(e: 'update:text', text?: string): void;
|
|
59
60
|
(e: 'update:modelValue', modelValue?: any): void;
|
|
60
61
|
(e: 'change', option?: Record<string, any>): void;
|
|
61
62
|
}
|
|
@@ -25,8 +25,6 @@ interface TreeSelectProps extends FormComponentProps, Omit<TreeProps, 'selected'
|
|
|
25
25
|
* @default 跟随触发元素的宽度
|
|
26
26
|
*/
|
|
27
27
|
width?: string;
|
|
28
|
-
/** 显示文本 */
|
|
29
|
-
text?: string;
|
|
30
28
|
/** 内容容器样式 */
|
|
31
29
|
contentStyle?: CSSProperties | string;
|
|
32
30
|
/** 内容容器类名 */
|
|
@@ -37,6 +35,10 @@ interface TreeSelectEmits {
|
|
|
37
35
|
(e: 'clear'): void;
|
|
38
36
|
(e: 'update:modelValue', value?: string | number): void;
|
|
39
37
|
(e: 'change', selectedData?: Record<string, any>): void;
|
|
38
|
+
/**
|
|
39
|
+
* 选中项文案变化(单向通知,用于同步父级冗余字段)
|
|
40
|
+
* @description 展示始终由 data 推导,请用 `@update:text` 而非 `v-model:text`
|
|
41
|
+
*/
|
|
40
42
|
(e: 'update:text', text?: string): void;
|
|
41
43
|
}
|
|
42
44
|
/** 树形选择器组件暴露的属性和方法(组件内部使用) */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@veltra/desktop",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.8",
|
|
4
4
|
"files": [
|
|
5
5
|
"dist",
|
|
6
6
|
"src"
|
|
@@ -57,11 +57,11 @@
|
|
|
57
57
|
"@cat-kit/core": ">=1.1.8",
|
|
58
58
|
"@cat-kit/excel": ">=1.0.0",
|
|
59
59
|
"@cat-kit/fe": "^1.1.8",
|
|
60
|
-
"@veltra/compositions": "1.3.
|
|
61
|
-
"@veltra/directives": "1.3.
|
|
62
|
-
"@veltra/icons": "1.3.
|
|
63
|
-
"@veltra/styles": "1.3.
|
|
64
|
-
"@veltra/utils": "1.3.
|
|
60
|
+
"@veltra/compositions": "1.3.8",
|
|
61
|
+
"@veltra/directives": "1.3.8",
|
|
62
|
+
"@veltra/icons": "1.3.8",
|
|
63
|
+
"@veltra/styles": "1.3.8",
|
|
64
|
+
"@veltra/utils": "1.3.8",
|
|
65
65
|
"vue": ">=3.5.40"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
@@ -1,22 +1,29 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import { createApp, h, nextTick, ref } from 'vue'
|
|
2
|
+
import { createApp, h, nextTick, ref, type Ref } from 'vue'
|
|
3
3
|
|
|
4
4
|
import USelect from '../select.vue'
|
|
5
5
|
|
|
6
6
|
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
|
|
7
7
|
|
|
8
|
-
function mountSelect(props: Record<string, unknown>) {
|
|
8
|
+
function mountSelect(props: Record<string, unknown> = {}) {
|
|
9
9
|
const host = document.createElement('div')
|
|
10
10
|
document.body.appendChild(host)
|
|
11
11
|
|
|
12
12
|
const model = ref(props.modelValue)
|
|
13
|
+
const options = ref(props.options) as Ref<unknown>
|
|
14
|
+
const texts: (string | undefined)[] = []
|
|
15
|
+
|
|
13
16
|
const app = createApp({
|
|
14
17
|
render() {
|
|
15
18
|
return h(USelect, {
|
|
16
19
|
...props,
|
|
17
20
|
modelValue: model.value,
|
|
21
|
+
options: options.value as any,
|
|
18
22
|
'onUpdate:modelValue': (value: unknown) => {
|
|
19
23
|
model.value = value
|
|
24
|
+
},
|
|
25
|
+
'onUpdate:text': (text?: string) => {
|
|
26
|
+
texts.push(text)
|
|
20
27
|
}
|
|
21
28
|
})
|
|
22
29
|
}
|
|
@@ -27,6 +34,8 @@ function mountSelect(props: Record<string, unknown>) {
|
|
|
27
34
|
return {
|
|
28
35
|
host,
|
|
29
36
|
model,
|
|
37
|
+
options,
|
|
38
|
+
texts,
|
|
30
39
|
unmount() {
|
|
31
40
|
app.unmount()
|
|
32
41
|
host.remove()
|
|
@@ -107,4 +116,70 @@ describe('USelect', () => {
|
|
|
107
116
|
unmount()
|
|
108
117
|
}
|
|
109
118
|
})
|
|
119
|
+
|
|
120
|
+
it('emits update:text with option label when modelValue echoes against options', async () => {
|
|
121
|
+
const { host, texts, unmount } = mountSelect({
|
|
122
|
+
modelValue: 'beijing',
|
|
123
|
+
options: [
|
|
124
|
+
{ label: '北京(最新)', value: 'beijing' },
|
|
125
|
+
{ label: '上海(最新)', value: 'shanghai' }
|
|
126
|
+
]
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
await nextTick()
|
|
131
|
+
expect(texts.at(-1)).toBe('北京(最新)')
|
|
132
|
+
expect(host.querySelector('input')!.value).toBe('北京(最新)')
|
|
133
|
+
} finally {
|
|
134
|
+
unmount()
|
|
135
|
+
}
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
it('emits update:text on user select and clear', async () => {
|
|
139
|
+
const { host, model, texts, unmount } = mountSelect({
|
|
140
|
+
filterable: true,
|
|
141
|
+
options: [
|
|
142
|
+
{ label: '北京', value: 'beijing' },
|
|
143
|
+
{ label: '上海', value: 'shanghai' }
|
|
144
|
+
]
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
await openDropdown(host)
|
|
149
|
+
await clickOption(queryOptions().find((el) => el.textContent === '上海')!)
|
|
150
|
+
expect(model.value).toBe('shanghai')
|
|
151
|
+
expect(texts.at(-1)).toBe('上海')
|
|
152
|
+
|
|
153
|
+
// 清除:父级清空值时回显同步发出 update:text
|
|
154
|
+
model.value = undefined
|
|
155
|
+
await nextTick()
|
|
156
|
+
await nextTick()
|
|
157
|
+
expect(texts.at(-1)).toBeUndefined()
|
|
158
|
+
expect(host.querySelector('input')!.value).toBe('')
|
|
159
|
+
} finally {
|
|
160
|
+
unmount()
|
|
161
|
+
}
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
it('emits update:text after async options arrive for an existing modelValue', async () => {
|
|
165
|
+
const { host, options, texts, unmount } = mountSelect({ modelValue: 'shanghai', options: [] })
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
await nextTick()
|
|
169
|
+
expect(texts).toHaveLength(0)
|
|
170
|
+
expect(host.querySelector('input')!.value).toBe('shanghai')
|
|
171
|
+
|
|
172
|
+
options.value = [
|
|
173
|
+
{ label: '北京(最新)', value: 'beijing' },
|
|
174
|
+
{ label: '上海(最新)', value: 'shanghai' }
|
|
175
|
+
]
|
|
176
|
+
await nextTick()
|
|
177
|
+
await nextTick()
|
|
178
|
+
|
|
179
|
+
expect(texts.at(-1)).toBe('上海(最新)')
|
|
180
|
+
expect(host.querySelector('input')!.value).toBe('上海(最新)')
|
|
181
|
+
} finally {
|
|
182
|
+
unmount()
|
|
183
|
+
}
|
|
184
|
+
})
|
|
110
185
|
})
|