@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.
Files changed (35) hide show
  1. package/dist/components/action/action-group.vue.d.ts +2 -2
  2. package/dist/components/action/action.vue.d.ts +1 -1
  3. package/dist/components/auto-complete/auto-complete.vue.d.ts +2 -2
  4. package/dist/components/cascade/cascade.vue.d.ts +5 -5
  5. package/dist/components/checkbox/checkbox-button.vue.d.ts +1 -1
  6. package/dist/components/checkbox-group/checkbox-group.vue.d.ts +2 -2
  7. package/dist/components/code-editor/code-editor.vue.d.ts +2 -2
  8. package/dist/components/date-panel/date-panel.vue.d.ts +1 -1
  9. package/dist/components/date-picker/date-picker.vue.d.ts +2 -2
  10. package/dist/components/date-range-picker/date-range-picker.vue.d.ts +2 -2
  11. package/dist/components/input/input.vue.d.ts +4 -4
  12. package/dist/components/multi-select/multi-select.vue.d.ts +3 -3
  13. package/dist/components/multi-tree-select/multi-tree-select.vue.d.ts +5 -5
  14. package/dist/components/number-input/number-input.vue.d.ts +4 -4
  15. package/dist/components/number-range-input/number-range-input.vue.d.ts +2 -2
  16. package/dist/components/password-input/password-input.vue.d.ts +1 -1
  17. package/dist/components/radio-group/radio-group.vue.d.ts +2 -2
  18. package/dist/components/rich-text-editor/rich-text-editor.vue.d.ts +1 -1
  19. package/dist/components/select/select.js +42 -51
  20. package/dist/components/select/select.js.map +1 -1
  21. package/dist/components/select/select.vue.d.ts +6 -14
  22. package/dist/components/steps/steps.vue.d.ts +3 -3
  23. package/dist/components/textarea/textarea.vue.d.ts +4 -4
  24. package/dist/components/tree-select/tree-select.js +40 -33
  25. package/dist/components/tree-select/tree-select.js.map +1 -1
  26. package/dist/components/tree-select/tree-select.vue.d.ts +5 -5
  27. package/dist/types/select.d.ts +5 -4
  28. package/dist/types/tree-select.d.ts +4 -2
  29. package/package.json +6 -6
  30. package/src/components/select/__test__/select.test.ts +77 -2
  31. package/src/components/select/select.vue +39 -47
  32. package/src/components/tree-select/__test__/tree-select.test.ts +133 -0
  33. package/src/components/tree-select/tree-select.vue +44 -38
  34. package/src/types/select.ts +5 -4
  35. 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
- type __VLS_ModelProps = {
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:label": (label?: string | undefined) => any;
24
- }, string, import("vue").PublicProps, Readonly<__VLS_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"?: ((value: any) => any) | undefined;
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 props.text ?? (model.value ? label.value : void 0);
113
+ return model.value || model.value === 0 ? label.value : void 0;
108
114
  });
109
115
  /** 查询态下已选标签降级为占位提示 */
110
116
  const inputPlaceholder = computed(() => {
111
- const display = props.text ?? (model.value ? label.value : void 0);
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 !!(props.text ?? (model.value ? label.value : void 0));
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
- const handleClear = () => {
152
+ const { userAction, isUserActive } = useUserAction();
153
+ /** 清空 */
154
+ const handleClear = userAction(() => {
148
155
  model.value = "";
149
- label.value = void 0;
156
+ setLabel(void 0);
150
157
  emit("clear");
151
158
  emit("change", void 0);
152
- emit("update:text", "");
153
- };
154
- let changedByEvent = false;
155
- watch([() => props.data, model], ([data, model]) => {
156
- if (changedByEvent) return;
157
- if (!data?.length || model === void 0) {
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 founded = false;
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] === model) {
167
- label.value = v[labelKey.value];
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
- const handleSelect = (selected, selectedData) => {
176
- changedByEvent = true;
177
- nextTick(() => {
178
- changedByEvent = false;
179
- });
182
+ /** 用户选择:O(1) 写入值与文案,跳过 data 回显查找 */
183
+ const handleSelect = userAction((selected, selectedData) => {
180
184
  model.value = selected ?? "";
181
- if (selectedData) label.value = o(selectedData).get(labelKey.value);
182
- else label.value = "";
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, ["selected", "class"])]),
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(__props.text || label.value || unref(FORM_EMPTY_CONTENT)), 1)], 64));
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;
@@ -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
- /** 触发更新label事件 */
58
- (e: 'update:label', label?: string): void;
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.7",
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.7",
61
- "@veltra/directives": "1.3.7",
62
- "@veltra/icons": "1.3.7",
63
- "@veltra/styles": "1.3.7",
64
- "@veltra/utils": "1.3.7",
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
  })