@veltra/desktop 1.3.7 → 1.3.9
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 +1 -1
- package/dist/components/batch-edit/batch-edit.vue.d.ts +8 -8
- package/dist/components/cascade/cascade.vue.d.ts +1 -1
- package/dist/components/multi-select/multi-select.vue.d.ts +1 -1
- package/dist/components/multi-tree-select/multi-tree-select.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 +4 -12
- package/dist/components/steps/steps.vue.d.ts +1 -1
- 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 +1 -1
- package/dist/components/watermark/watermark.vue.d.ts +1 -1
- 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
|
@@ -9,8 +9,8 @@ declare const __VLS_base: import("vue").DefineComponent<ActionGroupProps, _Actio
|
|
|
9
9
|
size: "small" | "default" | "large";
|
|
10
10
|
type: import("@veltra/utils").ColorType;
|
|
11
11
|
text: boolean;
|
|
12
|
-
max: number;
|
|
13
12
|
circle: boolean;
|
|
13
|
+
max: number;
|
|
14
14
|
loading: boolean;
|
|
15
15
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
16
16
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
@@ -8,28 +8,28 @@ declare const __VLS_base: import("vue").DefineComponent<BatchEditProps, {}, {},
|
|
|
8
8
|
"update:selected": (value: Record<string, any> | undefined) => any;
|
|
9
9
|
"update:checked": (value: Record<string, any>[]) => any;
|
|
10
10
|
"update:current": (row?: TableRow | undefined) => any;
|
|
11
|
+
"update:rows": (rows: TableRow[]) => any;
|
|
12
|
+
"update:forest": (rows?: import("@cat-kit/core").Forest<Record<string, unknown>, any> | undefined) => any;
|
|
13
|
+
"cell-click": (row: TableRow, column: TableColumn, ev: MouseEvent) => any;
|
|
14
|
+
"row-click": (row: TableRow, ev: MouseEvent) => any;
|
|
11
15
|
create: () => any;
|
|
12
16
|
"update:data": (value: Record<string, any>[]) => any;
|
|
13
17
|
"create-prev": (row: TableRow) => any;
|
|
14
18
|
"create-next": (row: TableRow) => any;
|
|
15
19
|
"create-child": (row: TableRow) => any;
|
|
16
|
-
"update:rows": (rows: TableRow[]) => any;
|
|
17
|
-
"update:forest": (rows?: import("@cat-kit/core").Forest<Record<string, unknown>, any> | undefined) => any;
|
|
18
|
-
"cell-click": (row: TableRow, column: TableColumn, ev: MouseEvent) => any;
|
|
19
|
-
"row-click": (row: TableRow, ev: MouseEvent) => any;
|
|
20
20
|
}, string, import("vue").PublicProps, Readonly<BatchEditProps> & Readonly<{
|
|
21
21
|
"onUpdate:selected"?: ((value: Record<string, any> | undefined) => any) | undefined;
|
|
22
22
|
"onUpdate:checked"?: ((value: Record<string, any>[]) => any) | undefined;
|
|
23
23
|
"onUpdate:current"?: ((row?: TableRow | undefined) => any) | undefined;
|
|
24
|
+
"onUpdate:rows"?: ((rows: TableRow[]) => any) | undefined;
|
|
25
|
+
"onUpdate:forest"?: ((rows?: import("@cat-kit/core").Forest<Record<string, unknown>, any> | undefined) => any) | undefined;
|
|
26
|
+
"onCell-click"?: ((row: TableRow, column: TableColumn, ev: MouseEvent) => any) | undefined;
|
|
27
|
+
"onRow-click"?: ((row: TableRow, ev: MouseEvent) => any) | undefined;
|
|
24
28
|
onCreate?: (() => any) | undefined;
|
|
25
29
|
"onUpdate:data"?: ((value: Record<string, any>[]) => any) | undefined;
|
|
26
30
|
"onCreate-prev"?: ((row: TableRow) => any) | undefined;
|
|
27
31
|
"onCreate-next"?: ((row: TableRow) => any) | undefined;
|
|
28
32
|
"onCreate-child"?: ((row: TableRow) => any) | undefined;
|
|
29
|
-
"onUpdate:rows"?: ((rows: TableRow[]) => any) | undefined;
|
|
30
|
-
"onUpdate:forest"?: ((rows?: import("@cat-kit/core").Forest<Record<string, unknown>, any> | undefined) => any) | undefined;
|
|
31
|
-
"onCell-click"?: ((row: TableRow, column: TableColumn, ev: MouseEvent) => any) | undefined;
|
|
32
|
-
"onRow-click"?: ((row: TableRow, ev: MouseEvent) => any) | undefined;
|
|
33
33
|
}>, {
|
|
34
34
|
cols: string | [string, string];
|
|
35
35
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
@@ -21,8 +21,8 @@ declare const __VLS_export: import("vue").DefineComponent<CascadeProps, {}, {},
|
|
|
21
21
|
placeholder: string;
|
|
22
22
|
clearable: boolean;
|
|
23
23
|
filterable: boolean;
|
|
24
|
-
separator: string;
|
|
25
24
|
visibilityLimit: number;
|
|
25
|
+
separator: string;
|
|
26
26
|
showFullPath: boolean;
|
|
27
27
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
28
28
|
declare const _default: typeof __VLS_export;
|
|
@@ -29,11 +29,11 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {
|
|
|
29
29
|
}>, {
|
|
30
30
|
disabled: boolean;
|
|
31
31
|
readonly: boolean;
|
|
32
|
-
minWidth: string;
|
|
33
32
|
labelKey: string;
|
|
34
33
|
valueKey: string;
|
|
35
34
|
placeholder: string;
|
|
36
35
|
clearable: boolean;
|
|
36
|
+
minWidth: string;
|
|
37
37
|
visibilityLimit: number;
|
|
38
38
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
39
39
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
@@ -23,13 +23,13 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {
|
|
|
23
23
|
}>, {
|
|
24
24
|
disabled: boolean;
|
|
25
25
|
readonly: boolean;
|
|
26
|
-
minWidth: string;
|
|
27
26
|
expandAll: boolean;
|
|
28
27
|
labelKey: string;
|
|
29
28
|
valueKey: string;
|
|
30
29
|
placeholder: string;
|
|
31
30
|
clearable: boolean;
|
|
32
31
|
filterable: boolean;
|
|
32
|
+
minWidth: string;
|
|
33
33
|
visibilityLimit: number;
|
|
34
34
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
35
35
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
@@ -5,8 +5,8 @@ import _sfc_main$4 from "../dropdown/dropdown.js";
|
|
|
5
5
|
import input_default from "../input/input.js";
|
|
6
6
|
import { useKeyboard } from "./use-keyboard.js";
|
|
7
7
|
import { useOptions } from "./use-options.js";
|
|
8
|
-
import { Fragment, Transition, computed, createBlock, createCommentVNode, createElementBlock, createSlots, createTextVNode, createVNode, defineComponent,
|
|
9
|
-
import { useFormFallbackProps, useVirtualizer } from "@veltra/compositions";
|
|
8
|
+
import { Fragment, Transition, computed, createBlock, createCommentVNode, createElementBlock, createSlots, createTextVNode, createVNode, defineComponent, mergeProps, nextTick, normalizeClass, normalizeStyle, openBlock, renderList, renderSlot, shallowRef, toDisplayString, unref, useTemplateRef, watch, withCtx, withModifiers } from "vue";
|
|
9
|
+
import { useFormFallbackProps, useUserAction, useVirtualizer } from "@veltra/compositions";
|
|
10
10
|
import { FORM_EMPTY_CONTENT, bem, fieldKey, injectFormContext, scrollIntoContainerView, withUnit } from "@veltra/utils";
|
|
11
11
|
import { ArrowDown, Close } from "@veltra/icons/normal";
|
|
12
12
|
import { o } from "@cat-kit/core";
|
|
@@ -21,9 +21,8 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
21
21
|
name: "USelect",
|
|
22
22
|
inheritAttrs: false,
|
|
23
23
|
__name: "select",
|
|
24
|
-
props:
|
|
24
|
+
props: {
|
|
25
25
|
modelValue: {},
|
|
26
|
-
text: {},
|
|
27
26
|
options: {},
|
|
28
27
|
valueKey: { default: "value" },
|
|
29
28
|
labelKey: { default: "label" },
|
|
@@ -53,15 +52,12 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
53
52
|
},
|
|
54
53
|
rules: {},
|
|
55
54
|
size: {}
|
|
56
|
-
},
|
|
57
|
-
|
|
58
|
-
"
|
|
59
|
-
}),
|
|
60
|
-
emits: /*@__PURE__*/ mergeModels([
|
|
61
|
-
"update:label",
|
|
55
|
+
},
|
|
56
|
+
emits: [
|
|
57
|
+
"update:text",
|
|
62
58
|
"update:modelValue",
|
|
63
59
|
"change"
|
|
64
|
-
],
|
|
60
|
+
],
|
|
65
61
|
setup(__props, { emit: __emit }) {
|
|
66
62
|
const props = __props;
|
|
67
63
|
const emit = __emit;
|
|
@@ -76,7 +72,8 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
76
72
|
readonly: false
|
|
77
73
|
});
|
|
78
74
|
const currentIndex = shallowRef(-1);
|
|
79
|
-
|
|
75
|
+
/** 内部展示文案,仅由选项推导;通过 update:text 单向通知父级 */
|
|
76
|
+
const label = shallowRef();
|
|
80
77
|
const selected = shallowRef();
|
|
81
78
|
const displayedValue = computed(() => {
|
|
82
79
|
if (label.value) return label.value;
|
|
@@ -88,7 +85,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
88
85
|
const filterable = computed(() => {
|
|
89
86
|
return props.filterable || typeof props.options === "function";
|
|
90
87
|
});
|
|
91
|
-
const { queryString, options, temOptionsToCreatedOptions, clearCreatedOptions } = useOptions({ props });
|
|
88
|
+
const { queryString, options, allOptions, temOptionsToCreatedOptions, clearCreatedOptions } = useOptions({ props });
|
|
92
89
|
const dropdownVisible = shallowRef(false);
|
|
93
90
|
/**
|
|
94
91
|
* 是否处于查询态:决定触发输入框显示查询串还是选中标签。
|
|
@@ -126,40 +123,38 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
126
123
|
e.stopPropagation();
|
|
127
124
|
if (!dropdownVisible.value) dropdownRef.value?.open();
|
|
128
125
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
126
|
+
const { userAction, isUserActive } = useUserAction();
|
|
127
|
+
/** 更新内部文案;值变化时单向 emit update:text */
|
|
128
|
+
function setLabel(next) {
|
|
129
|
+
if (label.value === next) return;
|
|
130
|
+
label.value = next;
|
|
131
|
+
emit("update:text", next);
|
|
132
132
|
}
|
|
133
|
-
/**
|
|
134
|
-
|
|
135
|
-
*/
|
|
136
|
-
function unlock(preferredValue) {
|
|
137
|
-
nextTick(() => {
|
|
138
|
-
userSelecting = false;
|
|
139
|
-
syncSelected(preferredValue !== void 0 ? preferredValue : props.modelValue, options.value);
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
/** 按 modelValue 与选项列表同步高亮索引、选中项与显示标签 */
|
|
143
|
-
function syncSelected(modelValue, options) {
|
|
133
|
+
/** 按 modelValue 与完整选项列表同步高亮索引、选中项与显示标签(外部回显用,O(n)) */
|
|
134
|
+
function syncSelected(modelValue, sourceOptions) {
|
|
144
135
|
if (queryString.value) {
|
|
145
|
-
currentIndex.value = options
|
|
136
|
+
currentIndex.value = options.value.length ? 0 : -1;
|
|
146
137
|
return;
|
|
147
138
|
}
|
|
148
|
-
if (!
|
|
139
|
+
if (!sourceOptions?.length) return;
|
|
149
140
|
if (modelValue !== void 0 && modelValue !== null && modelValue !== "") {
|
|
150
|
-
currentIndex.value =
|
|
151
|
-
selected.value = currentIndex.value >= 0 ?
|
|
152
|
-
|
|
141
|
+
currentIndex.value = sourceOptions.findIndex((option) => o(option).get(valueKey.value) === modelValue);
|
|
142
|
+
selected.value = currentIndex.value >= 0 ? sourceOptions[currentIndex.value] : void 0;
|
|
143
|
+
setLabel(selected.value ? o(selected.value).get(labelKey.value) : void 0);
|
|
153
144
|
} else {
|
|
154
145
|
currentIndex.value = -1;
|
|
155
146
|
selected.value = void 0;
|
|
156
|
-
|
|
147
|
+
setLabel(void 0);
|
|
157
148
|
}
|
|
158
149
|
}
|
|
159
|
-
watch([() => props.modelValue,
|
|
160
|
-
if (
|
|
161
|
-
syncSelected(modelValue,
|
|
150
|
+
watch([() => props.modelValue, allOptions], ([modelValue]) => {
|
|
151
|
+
if (isUserActive()) return;
|
|
152
|
+
syncSelected(modelValue, allOptions.value);
|
|
162
153
|
}, { immediate: true });
|
|
154
|
+
watch([queryString, options], () => {
|
|
155
|
+
if (!queryString.value) return;
|
|
156
|
+
currentIndex.value = options.value.length ? 0 : -1;
|
|
157
|
+
});
|
|
163
158
|
const baseVirtualEnabled = computed(() => options.value.length > 80);
|
|
164
159
|
const virtualEnabled = computed(() => baseVirtualEnabled.value && !props.grid);
|
|
165
160
|
const { virtualizer, items } = useVirtualizer({
|
|
@@ -212,13 +207,12 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
212
207
|
const handleDropdownVisible = (visible) => {
|
|
213
208
|
if (!visible) queryString.value = "";
|
|
214
209
|
};
|
|
215
|
-
/**
|
|
216
|
-
const handleSelect = (option, _index) => {
|
|
217
|
-
lock();
|
|
210
|
+
/** 单选:用户动作内 O(1) 写入值与文案,跳过 modelValue 回显的 O(n) 查找 */
|
|
211
|
+
const handleSelect = userAction((option, _index) => {
|
|
218
212
|
const value = option?.[valueKey.value];
|
|
219
213
|
selected.value = option;
|
|
220
214
|
emit("update:modelValue", value);
|
|
221
|
-
|
|
215
|
+
setLabel(option?.[labelKey.value]);
|
|
222
216
|
emit("change", option);
|
|
223
217
|
if (option.__isTemp) temOptionsToCreatedOptions();
|
|
224
218
|
querying.value = false;
|
|
@@ -226,19 +220,16 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
226
220
|
currentIndex.value = options.value.findIndex((item) => o(item).get(valueKey.value) === value);
|
|
227
221
|
if (currentIndex.value >= 0) selected.value = options.value[currentIndex.value];
|
|
228
222
|
dropdownRef.value?.close();
|
|
229
|
-
|
|
230
|
-
};
|
|
223
|
+
});
|
|
231
224
|
/** 清除选项 */
|
|
232
|
-
const handleClear = () => {
|
|
233
|
-
lock();
|
|
225
|
+
const handleClear = userAction(() => {
|
|
234
226
|
selected.value = void 0;
|
|
235
227
|
currentIndex.value = -1;
|
|
236
|
-
|
|
228
|
+
setLabel(void 0);
|
|
237
229
|
clearCreatedOptions();
|
|
238
230
|
emit("update:modelValue", void 0);
|
|
239
231
|
emit("change", void 0);
|
|
240
|
-
|
|
241
|
-
};
|
|
232
|
+
});
|
|
242
233
|
function getCurrentEl() {
|
|
243
234
|
return scrollRef.value?.contentRef?.querySelector("li.is-selected");
|
|
244
235
|
}
|
|
@@ -290,11 +281,11 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
290
281
|
class: normalizeClass(unref(cls).e("clear")),
|
|
291
282
|
title: "清除",
|
|
292
283
|
key: "clear",
|
|
293
|
-
onClick: withModifiers(handleClear, ["stop"])
|
|
284
|
+
onClick: withModifiers(unref(handleClear), ["stop"])
|
|
294
285
|
}, {
|
|
295
286
|
default: withCtx(() => [createVNode(unref(Close))]),
|
|
296
287
|
_: 1
|
|
297
|
-
}, 8, ["class"])) : (openBlock(), createBlock(unref(_sfc_main$1), {
|
|
288
|
+
}, 8, ["class", "onClick"])) : (openBlock(), createBlock(unref(_sfc_main$1), {
|
|
298
289
|
class: normalizeClass(unref(cls).e("arrow")),
|
|
299
290
|
key: "arrow"
|
|
300
291
|
}, {
|
|
@@ -335,7 +326,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
335
326
|
default: withCtx(() => [virtualEnabled.value ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [createCommentVNode(" @vue-ignore "), (openBlock(true), createElementBlock(Fragment, null, renderList(virtualOptions.value, ({ option, index, val, label, key, offset }) => {
|
|
336
327
|
return openBlock(), createElementBlock("li", {
|
|
337
328
|
class: normalizeClass([unref(optionClass), unref(bem).is("selected", index === currentIndex.value)]),
|
|
338
|
-
onClick: ($event) => handleSelect(option, index),
|
|
329
|
+
onClick: ($event) => unref(handleSelect)(option, index),
|
|
339
330
|
key,
|
|
340
331
|
style: normalizeStyle({ transform: `translateY(${offset}px)` }),
|
|
341
332
|
"data-index": index,
|
|
@@ -348,7 +339,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
348
339
|
}), 128))], 64)) : (openBlock(true), createElementBlock(Fragment, { key: 1 }, renderList(unref(options), (option, index) => {
|
|
349
340
|
return openBlock(), createElementBlock("li", {
|
|
350
341
|
class: normalizeClass([unref(optionClass), unref(bem).is("selected", index === currentIndex.value)]),
|
|
351
|
-
onClick: ($event) => handleSelect(option, index),
|
|
342
|
+
onClick: ($event) => unref(handleSelect)(option, index),
|
|
352
343
|
"data-index": index,
|
|
353
344
|
title: unref(o)(option).get(labelKey.value),
|
|
354
345
|
key: unref(o)(option).get(valueKey.value)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"select.js","names":["$attrs","$slots"],"sources":["../../../src/components/select/select.vue"],"sourcesContent":["<template>\n <u-dropdown\n v-if=\"!readonly\"\n v-bind=\"$attrs\"\n trigger=\"click\"\n :class=\"[cls.b, bem.is('disabled', disabled)]\"\n ref=\"dropdownRef\"\n v-model:visible=\"dropdownVisible\"\n :content-class=\"[cls.e('panel'), cls.em('panel', size), contentClass]\"\n :content-style=\"contentStyle\"\n :disabled=\"disabled\"\n :min-width=\"minWidth\"\n :width=\"width\"\n @keydown=\"handleKeydown\"\n @update:visible=\"handleDropdownVisible\"\n @mouseenter=\"hovered = true\"\n @mouseleave=\"hovered = false\"\n >\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 @keydown=\"handleKeydown\"\n :native-readonly=\"!filterable || !querying\"\n @click.capture=\"handleTriggerClickCapture\"\n >\n <template #prefix v-if=\"$slots.prefix\">\n <slot name=\"prefix\" />\n </template>\n\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\">\n <ArrowDown />\n </u-icon>\n </transition>\n </template>\n </u-input>\n </template>\n\n <!-- 下拉内容 -->\n <template #content>\n <!--\n 单选列表。虚拟化启用时,内容容器 height 由 useVirtualizer 命令式写入;\n 此处 content-style 仅承担 grid 相关样式,避免 height 变化触发模板重渲染。\n -->\n <u-scroll\n v-if=\"options.length\"\n tag=\"ul\"\n :class=\"cls.e('options')\"\n ref=\"scrollRef\"\n :content-class=\"[\n cls.e('options-wrap'),\n bem.is('virtual', virtualEnabled),\n bem.is('grid', !!grid)\n ]\"\n :content-style=\"{\n gridTemplateColumns: grid ? `repeat(${grid.cols}, minmax(0px, 1fr))` : undefined,\n gridGap: grid ? withUnit(grid.gap, 'px') : undefined\n }\"\n >\n <template v-if=\"virtualEnabled\">\n <!-- @vue-ignore -->\n <li\n v-for=\"{ option, index, val, label, key, offset } of virtualOptions\"\n :class=\"[optionClass, bem.is('selected', index === currentIndex)]\"\n @click=\"handleSelect(option, index)\"\n :key=\"key\"\n :style=\"{ transform: `translateY(${offset}px)` }\"\n :data-index=\"index\"\n :ref=\"(el) => measureElement(index, el as Element | null)\"\n >\n <slot v-bind=\"{ option, index }\">\n {{ label }}\n </slot>\n </li>\n </template>\n\n <template v-else>\n <li\n v-for=\"(option, index) of options\"\n :class=\"[optionClass, bem.is('selected', index === currentIndex)]\"\n @click=\"handleSelect(option, index)\"\n :data-index=\"index\"\n :title=\"o(option).get(labelKey)\"\n :key=\"o(option).get(valueKey)\"\n >\n <slot v-bind=\"{ option, index }\">\n {{ o(option).get(labelKey) }}\n </slot>\n </li>\n </template>\n </u-scroll>\n\n <div v-else :class=\"cls.e('empty')\">\n <UEmpty />\n </div>\n </template>\n </u-dropdown>\n\n <template v-else>\n {{ displayedValue || FORM_EMPTY_CONTENT }}\n </template>\n</template>\n\n<script lang=\"ts\" setup>\nimport { o } from '@cat-kit/core'\nimport { useFormFallbackProps, useVirtualizer } from '@veltra/compositions'\nimport { ArrowDown, Close } from '@veltra/icons/normal'\nimport { bem, fieldKey, FORM_EMPTY_CONTENT, scrollIntoContainerView, withUnit } from '@veltra/utils'\nimport { injectFormContext } from '@veltra/utils'\nimport { computed, nextTick, shallowRef, useTemplateRef, watch } from 'vue'\n\nimport type {\n SelectEmits,\n SelectProps,\n _SelectExposed,\n DropdownExposed,\n InputExposed,\n ScrollExposed\n} from '../../types'\nimport { UDropdown } from '../dropdown'\nimport { UEmpty } from '../empty'\nimport { UIcon } from '../icon'\nimport { UInput } from '../input'\nimport { UScroll } from '../scroll'\nimport { useKeyboard } from './use-keyboard'\nimport { useOptions } from './use-options'\n\ndefineOptions({ name: 'USelect', inheritAttrs: false })\n\nconst props = withDefaults(defineProps<SelectProps>(), {\n labelKey: 'label',\n valueKey: 'value',\n placeholder: '请选择',\n clearable: true,\n disabled: undefined,\n readonly: undefined\n})\n\nconst emit = defineEmits<SelectEmits>()\n\ndefineSlots<{\n /** 前缀插槽 */\n prefix?: () => any\n /** 默认插槽 */\n default?: (scope: { option: Record<string, any>; index: number }) => any\n}>()\n\nconst cls = bem('select')\n\nconst labelKey = computed(() => fieldKey(props.labelKey, 'label'))\nconst valueKey = computed(() => fieldKey(props.valueKey, 'value'))\n\nconst optionClass = cls.e('option')\n\nconst { formProps } = injectFormContext()\nconst { size, disabled, readonly } = useFormFallbackProps([formProps ?? {}, props], {\n size: 'default',\n disabled: false,\n readonly: false\n})\n\nconst currentIndex = shallowRef(-1)\nconst label = defineModel('text')\nconst selected = shallowRef<Record<string, any>>()\n\nconst displayedValue = computed(() => {\n if (label.value) return label.value\n\n return selected.value ? o(selected.value).get(labelKey.value) : String(props.modelValue ?? '')\n})\n\nconst dropdownRef = shallowRef<DropdownExposed>()\nconst scrollRef = shallowRef<ScrollExposed>()\nconst inputRef = useTemplateRef<InputExposed>('inputRef')\n\nconst filterable = computed(() => {\n return props.filterable || typeof props.options === 'function'\n})\n\nconst { queryString, options, temOptionsToCreatedOptions, clearCreatedOptions } = useOptions({\n props\n})\n\nconst dropdownVisible = shallowRef(false)\n\n/**\n * 是否处于查询态:决定触发输入框显示查询串还是选中标签。\n * 与面板可见状态解耦 —— 选择后立即退出查询态,\n * 不等面板关闭动画结束(否则显示值会延迟一个动画时长才恢复)。\n */\nconst querying = shallowRef(false)\n\n/** 触发输入框的值:查询态下承载查询串,否则展示选中标签 */\nconst inputValue = computed(() => {\n if (filterable.value && querying.value) return queryString.value\n return displayedValue.value\n})\n\n/** 查询态下已选标签降级为占位提示 */\nconst inputPlaceholder = computed(() => {\n if (filterable.value && querying.value && displayedValue.value) {\n return displayedValue.value\n }\n return props.placeholder\n})\n\nfunction handleQueryInput(value: string) {\n if (!filterable.value) return\n queryString.value = value\n}\n\nconst hovered = shallowRef(false)\n\n/** 悬停且存在选中值时展示清除按钮(替代下拉箭头) */\nconst showClear = computed(() => {\n return props.clearable && !disabled.value && hovered.value && !!displayedValue.value\n})\n\n/**\n * 过滤模式下拦截输入区域的点击:\n * 阻止 dropdown 的 trigger 开合切换,保持面板展开以不中断输入。\n * 非输入区域(箭头、留白)放行,维持原开合行为。\n */\nfunction handleTriggerClickCapture(e: MouseEvent) {\n if (!filterable.value || disabled.value) return\n if (!(e.target instanceof HTMLInputElement)) return\n\n e.stopPropagation()\n if (!dropdownVisible.value) dropdownRef.value?.open()\n}\n\n// TODO: 优化\nlet userSelecting = false\nfunction lock() {\n userSelecting = true\n}\n\n/**\n * @param preferredValue 选择时传入刚提交的值,避免 nextTick 时 props.modelValue 尚未回传而用旧值覆盖高亮/标签\n */\nfunction unlock(preferredValue?: any) {\n nextTick(() => {\n userSelecting = false\n // 选择/清除会同步重建选项列表(临时项转正、查询串清空),\n // 重建触发的回显在锁定期间被跳过,解锁后按最新列表重同步一次,\n // 避免高亮停留在选择前的旧索引上\n const value = preferredValue !== undefined ? preferredValue : props.modelValue\n syncSelected(value, options.value)\n })\n}\n\n/** 按 modelValue 与选项列表同步高亮索引、选中项与显示标签 */\nfunction syncSelected(modelValue: any, options: Record<string, any>[] | undefined) {\n // 查询输入期间高亮第一项(创建模式下通常是临时项,便于回车创建)\n if (queryString.value) {\n currentIndex.value = options?.length ? 0 : -1\n return\n }\n\n if (!options?.length) return\n\n if (modelValue !== undefined && modelValue !== null && modelValue !== '') {\n currentIndex.value = options.findIndex((option) => o(option).get(valueKey.value) === modelValue)\n selected.value = currentIndex.value >= 0 ? options[currentIndex.value] : undefined\n label.value = selected.value ? o(selected.value).get(labelKey.value) : undefined\n } else {\n currentIndex.value = -1\n selected.value = undefined\n label.value = undefined\n }\n}\n\n// 回显\nwatch(\n [() => props.modelValue, options],\n ([modelValue, options]) => {\n if (userSelecting) return\n syncSelected(modelValue, options)\n },\n { immediate: true }\n)\n\nconst baseVirtualEnabled = computed(() => options.value.length > 80)\nconst virtualEnabled = computed(() => baseVirtualEnabled.value && !props.grid)\n\nconst { virtualizer, items } = useVirtualizer({\n count: computed(() => options.value.length),\n scrollEl: () => scrollRef.value?.containerRef ?? null,\n contentEl: () => (virtualEnabled.value ? (scrollRef.value?.contentRef ?? null) : null),\n gap: 4,\n estimateSize: () => 40\n})\n\nconst virtualOptions = computed(() => {\n const _options = options.value\n return items.value.map((item) => {\n const option = _options[item.index]!\n return {\n option,\n index: item.index,\n label: o(option).get(labelKey.value),\n val: o(option).get(valueKey.value),\n key: item.index,\n offset: item.start\n }\n })\n})\n\nconst measureElement: (index: number, el: Element | null) => void = (index, el) =>\n virtualizer.measureElement(index, el)\n\nfunction scrollTo(index: number): void {\n virtualizer.scrollToIndex(index, { align: 'center' })\n}\n\nwatch([scrollRef, virtualEnabled], ([scroll, virtualEnabled]) => {\n if (!scroll || !props.modelValue) return\n\n if (virtualEnabled) {\n const index = options.value.findIndex((option) => option === selected.value)\n index !== -1 && dropdownVisible.value && nextTick(() => scrollTo(index))\n } else {\n const selectedEl = scroll?.contentRef?.getElementsByClassName('is-selected')[0]\n if (selectedEl) {\n scrollIntoContainerView(selectedEl as HTMLElement, scroll.containerRef ?? null)\n }\n }\n})\n\nwatch(dropdownVisible, (visible) => {\n if (visible) {\n // 面板展开后进入查询态并聚焦输入框,可以立即输入查询\n if (filterable.value) {\n queryString.value = ''\n querying.value = true\n nextTick(() => inputRef.value?.el?.focus())\n }\n } else {\n querying.value = false\n queryString.value = ''\n }\n})\n\nconst handleDropdownVisible = (visible: boolean) => {\n if (!visible) {\n queryString.value = ''\n }\n}\n\n/** 单选 */\nconst handleSelect = (option: Record<string, any>, _index: number) => {\n lock()\n const value = option?.[valueKey.value]\n selected.value = option\n\n emit('update:modelValue', value)\n label.value = option?.[labelKey.value]\n emit('change', option)\n if (option.__isTemp) {\n // 转正后列表同步重建为完整源(临时项置顶索引失效),必须按 value 重定位\n temOptionsToCreatedOptions()\n }\n // 立即退出查询态,输入框同步恢复显示选中标签\n querying.value = false\n queryString.value = ''\n // 按当前列表定位高亮,避免创建项转正后仍停留在点击时的临时索引(通常为 0)\n currentIndex.value = options.value.findIndex((item) => o(item).get(valueKey.value) === value)\n if (currentIndex.value >= 0) {\n selected.value = options.value[currentIndex.value]\n }\n dropdownRef.value?.close()\n\n unlock(value)\n}\n\n/** 清除选项 */\nconst handleClear = () => {\n lock()\n selected.value = undefined\n currentIndex.value = -1\n label.value = undefined\n clearCreatedOptions()\n emit('update:modelValue', undefined)\n emit('change', undefined)\n unlock(null)\n}\n\nfunction getCurrentEl() {\n return scrollRef.value?.contentRef?.querySelector('li.is-selected') as HTMLElement | undefined\n}\n\nconst { handleKeydown } = useKeyboard({\n options,\n currentIndex,\n selectOption: handleSelect,\n getCurrentEl\n})\n</script>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"select.js","names":["$attrs","$slots"],"sources":["../../../src/components/select/select.vue"],"sourcesContent":["<template>\n <u-dropdown\n v-if=\"!readonly\"\n v-bind=\"$attrs\"\n trigger=\"click\"\n :class=\"[cls.b, bem.is('disabled', disabled)]\"\n ref=\"dropdownRef\"\n v-model:visible=\"dropdownVisible\"\n :content-class=\"[cls.e('panel'), cls.em('panel', size), contentClass]\"\n :content-style=\"contentStyle\"\n :disabled=\"disabled\"\n :min-width=\"minWidth\"\n :width=\"width\"\n @keydown=\"handleKeydown\"\n @update:visible=\"handleDropdownVisible\"\n @mouseenter=\"hovered = true\"\n @mouseleave=\"hovered = false\"\n >\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 @keydown=\"handleKeydown\"\n :native-readonly=\"!filterable || !querying\"\n @click.capture=\"handleTriggerClickCapture\"\n >\n <template #prefix v-if=\"$slots.prefix\">\n <slot name=\"prefix\" />\n </template>\n\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\">\n <ArrowDown />\n </u-icon>\n </transition>\n </template>\n </u-input>\n </template>\n\n <!-- 下拉内容 -->\n <template #content>\n <!--\n 单选列表。虚拟化启用时,内容容器 height 由 useVirtualizer 命令式写入;\n 此处 content-style 仅承担 grid 相关样式,避免 height 变化触发模板重渲染。\n -->\n <u-scroll\n v-if=\"options.length\"\n tag=\"ul\"\n :class=\"cls.e('options')\"\n ref=\"scrollRef\"\n :content-class=\"[\n cls.e('options-wrap'),\n bem.is('virtual', virtualEnabled),\n bem.is('grid', !!grid)\n ]\"\n :content-style=\"{\n gridTemplateColumns: grid ? `repeat(${grid.cols}, minmax(0px, 1fr))` : undefined,\n gridGap: grid ? withUnit(grid.gap, 'px') : undefined\n }\"\n >\n <template v-if=\"virtualEnabled\">\n <!-- @vue-ignore -->\n <li\n v-for=\"{ option, index, val, label, key, offset } of virtualOptions\"\n :class=\"[optionClass, bem.is('selected', index === currentIndex)]\"\n @click=\"handleSelect(option, index)\"\n :key=\"key\"\n :style=\"{ transform: `translateY(${offset}px)` }\"\n :data-index=\"index\"\n :ref=\"(el) => measureElement(index, el as Element | null)\"\n >\n <slot v-bind=\"{ option, index }\">\n {{ label }}\n </slot>\n </li>\n </template>\n\n <template v-else>\n <li\n v-for=\"(option, index) of options\"\n :class=\"[optionClass, bem.is('selected', index === currentIndex)]\"\n @click=\"handleSelect(option, index)\"\n :data-index=\"index\"\n :title=\"o(option).get(labelKey)\"\n :key=\"o(option).get(valueKey)\"\n >\n <slot v-bind=\"{ option, index }\">\n {{ o(option).get(labelKey) }}\n </slot>\n </li>\n </template>\n </u-scroll>\n\n <div v-else :class=\"cls.e('empty')\">\n <UEmpty />\n </div>\n </template>\n </u-dropdown>\n\n <template v-else>\n {{ displayedValue || FORM_EMPTY_CONTENT }}\n </template>\n</template>\n\n<script lang=\"ts\" setup>\nimport { o } from '@cat-kit/core'\nimport { useFormFallbackProps, useUserAction, useVirtualizer } from '@veltra/compositions'\nimport { ArrowDown, Close } from '@veltra/icons/normal'\nimport { bem, fieldKey, FORM_EMPTY_CONTENT, scrollIntoContainerView, withUnit } from '@veltra/utils'\nimport { injectFormContext } from '@veltra/utils'\nimport { computed, nextTick, shallowRef, useTemplateRef, watch } from 'vue'\n\nimport type {\n SelectEmits,\n SelectProps,\n _SelectExposed,\n DropdownExposed,\n InputExposed,\n ScrollExposed\n} from '../../types'\nimport { UDropdown } from '../dropdown'\nimport { UEmpty } from '../empty'\nimport { UIcon } from '../icon'\nimport { UInput } from '../input'\nimport { UScroll } from '../scroll'\nimport { useKeyboard } from './use-keyboard'\nimport { useOptions } from './use-options'\n\ndefineOptions({ name: 'USelect', inheritAttrs: false })\n\nconst props = withDefaults(defineProps<SelectProps>(), {\n labelKey: 'label',\n valueKey: 'value',\n placeholder: '请选择',\n clearable: true,\n disabled: undefined,\n readonly: undefined\n})\n\nconst emit = defineEmits<SelectEmits>()\n\ndefineSlots<{\n /** 前缀插槽 */\n prefix?: () => any\n /** 默认插槽 */\n default?: (scope: { option: Record<string, any>; index: number }) => any\n}>()\n\nconst cls = bem('select')\n\nconst labelKey = computed(() => fieldKey(props.labelKey, 'label'))\nconst valueKey = computed(() => fieldKey(props.valueKey, 'value'))\n\nconst optionClass = cls.e('option')\n\nconst { formProps } = injectFormContext()\nconst { size, disabled, readonly } = useFormFallbackProps([formProps ?? {}, props], {\n size: 'default',\n disabled: false,\n readonly: false\n})\n\nconst currentIndex = shallowRef(-1)\n/** 内部展示文案,仅由选项推导;通过 update:text 单向通知父级 */\nconst label = shallowRef<string>()\nconst selected = shallowRef<Record<string, any>>()\n\nconst displayedValue = computed(() => {\n if (label.value) return label.value\n\n return selected.value ? o(selected.value).get(labelKey.value) : String(props.modelValue ?? '')\n})\n\nconst dropdownRef = shallowRef<DropdownExposed>()\nconst scrollRef = shallowRef<ScrollExposed>()\nconst inputRef = useTemplateRef<InputExposed>('inputRef')\n\nconst filterable = computed(() => {\n return props.filterable || typeof props.options === 'function'\n})\n\nconst { queryString, options, allOptions, temOptionsToCreatedOptions, clearCreatedOptions } =\n useOptions({ props })\n\nconst dropdownVisible = shallowRef(false)\n\n/**\n * 是否处于查询态:决定触发输入框显示查询串还是选中标签。\n * 与面板可见状态解耦 —— 选择后立即退出查询态,\n * 不等面板关闭动画结束(否则显示值会延迟一个动画时长才恢复)。\n */\nconst querying = shallowRef(false)\n\n/** 触发输入框的值:查询态下承载查询串,否则展示选中标签 */\nconst inputValue = computed(() => {\n if (filterable.value && querying.value) return queryString.value\n return displayedValue.value\n})\n\n/** 查询态下已选标签降级为占位提示 */\nconst inputPlaceholder = computed(() => {\n if (filterable.value && querying.value && displayedValue.value) {\n return displayedValue.value\n }\n return props.placeholder\n})\n\nfunction handleQueryInput(value: string) {\n if (!filterable.value) return\n queryString.value = value\n}\n\nconst hovered = shallowRef(false)\n\n/** 悬停且存在选中值时展示清除按钮(替代下拉箭头) */\nconst showClear = computed(() => {\n return props.clearable && !disabled.value && hovered.value && !!displayedValue.value\n})\n\n/**\n * 过滤模式下拦截输入区域的点击:\n * 阻止 dropdown 的 trigger 开合切换,保持面板展开以不中断输入。\n * 非输入区域(箭头、留白)放行,维持原开合行为。\n */\nfunction handleTriggerClickCapture(e: MouseEvent) {\n if (!filterable.value || disabled.value) return\n if (!(e.target instanceof HTMLInputElement)) return\n\n e.stopPropagation()\n if (!dropdownVisible.value) dropdownRef.value?.open()\n}\n\nconst { userAction, isUserActive } = useUserAction()\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/** 按 modelValue 与完整选项列表同步高亮索引、选中项与显示标签(外部回显用,O(n)) */\nfunction syncSelected(modelValue: any, sourceOptions: Record<string, any>[] | undefined) {\n // 查询输入期间高亮第一项(创建模式下通常是临时项,便于回车创建)\n if (queryString.value) {\n currentIndex.value = options.value.length ? 0 : -1\n return\n }\n\n if (!sourceOptions?.length) return\n\n if (modelValue !== undefined && modelValue !== null && modelValue !== '') {\n currentIndex.value = sourceOptions.findIndex(\n (option) => o(option).get(valueKey.value) === modelValue\n )\n selected.value = currentIndex.value >= 0 ? sourceOptions[currentIndex.value] : undefined\n setLabel(selected.value ? o(selected.value).get(labelKey.value) : undefined)\n } else {\n currentIndex.value = -1\n selected.value = undefined\n setLabel(undefined)\n }\n}\n\n// 回显:以 allOptions 匹配,避免过滤列表缺项时清掉已选文案;用户动作期内跳过\nwatch(\n [() => props.modelValue, allOptions],\n ([modelValue]) => {\n if (isUserActive()) return\n syncSelected(modelValue, allOptions.value)\n },\n { immediate: true }\n)\n\n// 过滤时单独同步高亮(allOptions 不变,不会触发上方回显 watch)\nwatch([queryString, options], () => {\n if (!queryString.value) return\n currentIndex.value = options.value.length ? 0 : -1\n})\n\nconst baseVirtualEnabled = computed(() => options.value.length > 80)\nconst virtualEnabled = computed(() => baseVirtualEnabled.value && !props.grid)\n\nconst { virtualizer, items } = useVirtualizer({\n count: computed(() => options.value.length),\n scrollEl: () => scrollRef.value?.containerRef ?? null,\n contentEl: () => (virtualEnabled.value ? (scrollRef.value?.contentRef ?? null) : null),\n gap: 4,\n estimateSize: () => 40\n})\n\nconst virtualOptions = computed(() => {\n const _options = options.value\n return items.value.map((item) => {\n const option = _options[item.index]!\n return {\n option,\n index: item.index,\n label: o(option).get(labelKey.value),\n val: o(option).get(valueKey.value),\n key: item.index,\n offset: item.start\n }\n })\n})\n\nconst measureElement: (index: number, el: Element | null) => void = (index, el) =>\n virtualizer.measureElement(index, el)\n\nfunction scrollTo(index: number): void {\n virtualizer.scrollToIndex(index, { align: 'center' })\n}\n\nwatch([scrollRef, virtualEnabled], ([scroll, virtualEnabled]) => {\n if (!scroll || !props.modelValue) return\n\n if (virtualEnabled) {\n const index = options.value.findIndex((option) => option === selected.value)\n index !== -1 && dropdownVisible.value && nextTick(() => scrollTo(index))\n } else {\n const selectedEl = scroll?.contentRef?.getElementsByClassName('is-selected')[0]\n if (selectedEl) {\n scrollIntoContainerView(selectedEl as HTMLElement, scroll.containerRef ?? null)\n }\n }\n})\n\nwatch(dropdownVisible, (visible) => {\n if (visible) {\n // 面板展开后进入查询态并聚焦输入框,可以立即输入查询\n if (filterable.value) {\n queryString.value = ''\n querying.value = true\n nextTick(() => inputRef.value?.el?.focus())\n }\n } else {\n querying.value = false\n queryString.value = ''\n }\n})\n\nconst handleDropdownVisible = (visible: boolean) => {\n if (!visible) {\n queryString.value = ''\n }\n}\n\n/** 单选:用户动作内 O(1) 写入值与文案,跳过 modelValue 回显的 O(n) 查找 */\nconst handleSelect = userAction((option: Record<string, any>, _index: number) => {\n const value = option?.[valueKey.value]\n selected.value = option\n\n emit('update:modelValue', value)\n setLabel(option?.[labelKey.value])\n emit('change', option)\n if (option.__isTemp) {\n // 转正后列表同步重建为完整源(临时项置顶索引失效),必须按 value 重定位\n temOptionsToCreatedOptions()\n }\n // 立即退出查询态,输入框同步恢复显示选中标签\n querying.value = false\n queryString.value = ''\n // 按当前列表定位高亮,避免创建项转正后仍停留在点击时的临时索引(通常为 0)\n currentIndex.value = options.value.findIndex((item) => o(item).get(valueKey.value) === value)\n if (currentIndex.value >= 0) {\n selected.value = options.value[currentIndex.value]\n }\n dropdownRef.value?.close()\n})\n\n/** 清除选项 */\nconst handleClear = userAction(() => {\n selected.value = undefined\n currentIndex.value = -1\n setLabel(undefined)\n clearCreatedOptions()\n emit('update:modelValue', undefined)\n emit('change', undefined)\n})\n\nfunction getCurrentEl() {\n return scrollRef.value?.contentRef?.querySelector('li.is-selected') as HTMLElement | undefined\n}\n\nconst { handleKeydown } = useKeyboard({\n options,\n currentIndex,\n selectOption: handleSelect,\n getCurrentEl\n})\n</script>\n"],"mappings":"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"}
|
|
@@ -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,21 +10,14 @@ 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
22
|
disabled: boolean;
|
|
31
23
|
readonly: boolean;
|
|
@@ -13,8 +13,8 @@ declare const __VLS_base: import("vue").DefineComponent<StepsProps, {}, {}, {},
|
|
|
13
13
|
"onItem-click"?: ((item: Record<string, any>, index: number) => any) | undefined;
|
|
14
14
|
"onUpdate:current"?: ((value?: string | number | undefined) => 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>;
|
|
@@ -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
|
});
|