@veltra/desktop 1.3.5 → 1.3.6
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/action/action.vue.d.ts +1 -1
- package/dist/components/cascade/cascade.js +8 -2
- package/dist/components/cascade/cascade.js.map +1 -1
- package/dist/components/cascade/style2.css +4 -0
- package/dist/components/date-range-picker/date-range-picker.js +5 -2
- package/dist/components/date-range-picker/date-range-picker.js.map +1 -1
- package/dist/components/date-range-picker/style2.css +4 -0
- package/dist/components/empty/empty.vue.d.ts +1 -1
- package/dist/components/form-item/form-item.js.map +1 -1
- package/dist/components/form-item/form-item.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/components/form-item/form-item.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/form-item/style2.css +2 -1
- package/dist/components/grid-input/style2.css +3 -0
- package/dist/components/multi-select/multi-select.js +67 -40
- package/dist/components/multi-select/multi-select.js.map +1 -1
- package/dist/components/multi-select/multi-select.vue.d.ts +4 -4
- package/dist/components/multi-select/style2.css +23 -10
- package/dist/components/multi-tree-select/multi-tree-select.js +52 -35
- package/dist/components/multi-tree-select/multi-tree-select.js.map +1 -1
- package/dist/components/multi-tree-select/multi-tree-select.vue.d.ts +1 -1
- package/dist/components/multi-tree-select/style2.css +26 -16
- package/dist/components/progress-nodes/progress-nodes.vue.d.ts +1 -1
- package/dist/components/select/select.js +159 -95
- package/dist/components/select/select.js.map +1 -1
- package/dist/components/select/style2.css +4 -17
- package/dist/components/select/use-options.js +67 -26
- package/dist/components/select/use-options.js.map +1 -1
- package/dist/components/steps/steps.vue.d.ts +2 -2
- package/dist/components/tabs/tabs-horizontal.vue.d.ts +2 -2
- package/dist/components/tabs/tabs-vertical.vue.d.ts +2 -2
- package/dist/components/tabs/tabs.vue.d.ts +2 -2
- package/dist/components/textarea/style2.css +1 -0
- package/dist/components/textarea/textarea.vue.d.ts +1 -1
- package/dist/components/tree/tree.vue.d.ts +1 -1
- package/dist/components/tree-select/style2.css +6 -15
- package/dist/components/tree-select/tree-select.js +95 -41
- package/dist/components/tree-select/tree-select.js.map +1 -1
- package/dist/components/tree-select/tree-select.vue.d.ts +1 -1
- package/package.json +6 -6
- package/src/components/cascade/cascade.vue +15 -2
- package/src/components/cascade/style.scss +5 -0
- package/src/components/date-range-picker/date-range-picker.vue +9 -1
- package/src/components/date-range-picker/style.scss +5 -0
- package/src/components/form-item/form-item.vue +2 -2
- package/src/components/form-item/style.scss +2 -1
- package/src/components/grid-input/style.scss +1 -0
- package/src/components/multi-select/multi-select.vue +50 -19
- package/src/components/multi-select/style.scss +27 -4
- package/src/components/multi-tree-select/multi-tree-select.vue +34 -14
- package/src/components/multi-tree-select/style.scss +28 -9
- package/src/components/select/__test__/select.test.ts +110 -0
- package/src/components/select/select.vue +130 -46
- package/src/components/select/style.scss +5 -14
- package/src/components/select/use-options.ts +83 -39
- package/src/components/textarea/style.scss +1 -0
- package/src/components/tree-select/style.scss +4 -8
- package/src/components/tree-select/tree-select.vue +90 -17
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
v-bind="$attrs"
|
|
5
5
|
trigger="click"
|
|
6
6
|
ref="dropdownRef"
|
|
7
|
-
:class="[cls.b, cls.m(size), bem.is('disabled', disabled)]"
|
|
7
|
+
:class="[cls.b, cls.m(size), bem.is('disabled', disabled), bem.is('focus', dropdownVisible)]"
|
|
8
8
|
:content-class="[cls.e('panel'), cls.em('panel', size), contentClass]"
|
|
9
9
|
:content-style="contentStyle"
|
|
10
10
|
:min-width="minWidth"
|
|
@@ -16,11 +16,11 @@
|
|
|
16
16
|
>
|
|
17
17
|
<!-- 触发 -->
|
|
18
18
|
<template #trigger>
|
|
19
|
-
<span :class="cls.e('placeholder')" v-
|
|
19
|
+
<span :class="cls.e('placeholder')" v-if="!filterable && !model?.length">
|
|
20
20
|
{{ placeholder }}
|
|
21
21
|
</span>
|
|
22
22
|
|
|
23
|
-
<div v-if="model?.length" :class="cls.e('tags')">
|
|
23
|
+
<div v-if="model?.length || filterable" :class="cls.e('tags')">
|
|
24
24
|
<u-tag
|
|
25
25
|
v-for="option of tags"
|
|
26
26
|
:key="option[valueKey]"
|
|
@@ -31,6 +31,19 @@
|
|
|
31
31
|
{{ option[labelKey] }}
|
|
32
32
|
</u-tag>
|
|
33
33
|
<u-tag v-if="restTag" size="small"> {{ restTag }}+ </u-tag>
|
|
34
|
+
|
|
35
|
+
<!-- 内嵌查询输入框:承载过滤与回车创建 -->
|
|
36
|
+
<input
|
|
37
|
+
v-if="filterable"
|
|
38
|
+
ref="inputRef"
|
|
39
|
+
:class="cls.e('input')"
|
|
40
|
+
v-model="queryString"
|
|
41
|
+
:placeholder="model?.length ? '' : placeholder"
|
|
42
|
+
:disabled="disabled"
|
|
43
|
+
@click.stop="handleInputClick"
|
|
44
|
+
@focus="handleInputFocus"
|
|
45
|
+
@keydown.enter.prevent="handleCreateByEnter"
|
|
46
|
+
/>
|
|
34
47
|
</div>
|
|
35
48
|
|
|
36
49
|
<transition name="zoom-in" mode="out-in">
|
|
@@ -62,19 +75,6 @@
|
|
|
62
75
|
<span> 已选 {{ model?.length }}/{{ max ?? options?.length ?? 0 }} </span>
|
|
63
76
|
</div>
|
|
64
77
|
|
|
65
|
-
<!-- 过滤器 -->
|
|
66
|
-
<div v-if="filterable" :class="cls.e('content-filter')">
|
|
67
|
-
<u-input
|
|
68
|
-
placeholder="输入关键字进行过滤"
|
|
69
|
-
v-model="queryString"
|
|
70
|
-
@keydown.enter.prevent="handleCreateByEnter"
|
|
71
|
-
>
|
|
72
|
-
<template #suffix>
|
|
73
|
-
<u-icon><Search /></u-icon>
|
|
74
|
-
</template>
|
|
75
|
-
</u-input>
|
|
76
|
-
</div>
|
|
77
|
-
|
|
78
78
|
<!-- 多选列表 -->
|
|
79
79
|
<!--
|
|
80
80
|
虚拟化启用时内容容器 height 由 useVirtualizer 命令式写入;
|
|
@@ -143,10 +143,18 @@
|
|
|
143
143
|
<script lang="ts" setup>
|
|
144
144
|
import { o as chainObj } from '@cat-kit/core'
|
|
145
145
|
import { useFormFallbackProps, useVirtualizer } from '@veltra/compositions'
|
|
146
|
-
import { ArrowDown, Close
|
|
146
|
+
import { ArrowDown, Close } from '@veltra/icons/normal'
|
|
147
147
|
import { bem, fieldKey, FORM_EMPTY_CONTENT } from '@veltra/utils'
|
|
148
148
|
import { injectFormContext } from '@veltra/utils'
|
|
149
|
-
import {
|
|
149
|
+
import {
|
|
150
|
+
computed,
|
|
151
|
+
nextTick,
|
|
152
|
+
shallowRef,
|
|
153
|
+
shallowReactive,
|
|
154
|
+
watch,
|
|
155
|
+
provide,
|
|
156
|
+
useTemplateRef
|
|
157
|
+
} from 'vue'
|
|
150
158
|
|
|
151
159
|
import type {
|
|
152
160
|
MultiSelectEmits,
|
|
@@ -158,7 +166,6 @@ import { UCheckbox } from '../checkbox'
|
|
|
158
166
|
import { UDropdown } from '../dropdown'
|
|
159
167
|
import { UEmpty } from '../empty'
|
|
160
168
|
import { UIcon } from '../icon'
|
|
161
|
-
import { UInput } from '../input'
|
|
162
169
|
import { UScroll } from '../scroll'
|
|
163
170
|
import { useOptions } from '../select/use-options'
|
|
164
171
|
import { UTag } from '../tag'
|
|
@@ -319,12 +326,35 @@ const restTag = computed(() => {
|
|
|
319
326
|
return (model.value?.length ?? 0) - tags.value.length
|
|
320
327
|
})
|
|
321
328
|
|
|
329
|
+
const dropdownVisible = shallowRef(false)
|
|
330
|
+
|
|
331
|
+
const inputRef = useTemplateRef<HTMLInputElement>('inputRef')
|
|
332
|
+
|
|
322
333
|
const handleDropdownVisible = (visible: boolean) => {
|
|
334
|
+
dropdownVisible.value = visible
|
|
323
335
|
if (!visible) {
|
|
324
336
|
queryString.value = ''
|
|
325
337
|
}
|
|
326
338
|
}
|
|
327
339
|
|
|
340
|
+
/**
|
|
341
|
+
* 拦截输入区域的点击(@click.stop):阻止 dropdown 的 trigger 开合切换,
|
|
342
|
+
* 保持面板展开以不中断输入。
|
|
343
|
+
*/
|
|
344
|
+
const handleInputClick = () => {
|
|
345
|
+
if (!dropdownVisible.value) dropdownRef.value?.open()
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const handleInputFocus = () => {
|
|
349
|
+
if (!dropdownVisible.value) dropdownRef.value?.open()
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/** 勾选后把焦点还给输入框,便于连续输入过滤 */
|
|
353
|
+
const refocusInput = () => {
|
|
354
|
+
if (!filterable.value) return
|
|
355
|
+
nextTick(() => inputRef.value?.focus())
|
|
356
|
+
}
|
|
357
|
+
|
|
328
358
|
const handleCheck = (option: Record<string, any>, checked: boolean) => {
|
|
329
359
|
if (checked) {
|
|
330
360
|
if (option.__isTemp && props.creatable) {
|
|
@@ -352,6 +382,7 @@ const handleCheck = (option: Record<string, any>, checked: boolean) => {
|
|
|
352
382
|
|
|
353
383
|
emitChange()
|
|
354
384
|
dropdownRef.value?.updateDropdown()
|
|
385
|
+
refocusInput()
|
|
355
386
|
}
|
|
356
387
|
|
|
357
388
|
/** 全选 */
|
|
@@ -36,13 +36,18 @@ $root-name: multi-select;
|
|
|
36
36
|
border-color: fn.use-var(color, primary);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
@include m.is(focus) {
|
|
40
|
+
border-color: fn.use-var(color, primary);
|
|
41
|
+
box-shadow: fn.use-var(focus-ring);
|
|
42
|
+
}
|
|
43
|
+
|
|
39
44
|
@include m.e(ripple) {
|
|
40
45
|
background-color: fn.use-var(color, primary, light-7);
|
|
41
46
|
opacity: 0.5;
|
|
42
47
|
}
|
|
43
48
|
|
|
44
49
|
@include m.e(options) {
|
|
45
|
-
max-height:
|
|
50
|
+
max-height: 240px;
|
|
46
51
|
}
|
|
47
52
|
|
|
48
53
|
@include m.e(options-wrap) {
|
|
@@ -79,6 +84,27 @@ $root-name: multi-select;
|
|
|
79
84
|
@include m.flex($justify: flex-start, $align: center, $wrap: wrap);
|
|
80
85
|
}
|
|
81
86
|
|
|
87
|
+
@include m.e(input) {
|
|
88
|
+
flex: 1;
|
|
89
|
+
min-width: 60px;
|
|
90
|
+
padding: 0;
|
|
91
|
+
margin: 0;
|
|
92
|
+
border: none;
|
|
93
|
+
outline: none;
|
|
94
|
+
background: transparent;
|
|
95
|
+
font: inherit;
|
|
96
|
+
color: fn.use-var(text-color, main);
|
|
97
|
+
cursor: text;
|
|
98
|
+
|
|
99
|
+
&::placeholder {
|
|
100
|
+
color: fn.use-var(text-color, placeholder);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
&:disabled {
|
|
104
|
+
cursor: not-allowed;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
82
108
|
@include m.e(readonly-tags) {
|
|
83
109
|
padding-left: 0;
|
|
84
110
|
padding-right: 0;
|
|
@@ -104,9 +130,6 @@ $root-name: multi-select;
|
|
|
104
130
|
|
|
105
131
|
@include m.e(panel) {
|
|
106
132
|
@include m.size using ($size) {
|
|
107
|
-
@include m.bem($root-name, content-filter) {
|
|
108
|
-
padding: calc(fn.use-var(gap, $size) / 2) fn.use-var(gap, $size);
|
|
109
|
-
}
|
|
110
133
|
@include m.bem($root-name, content-header) {
|
|
111
134
|
padding: calc(fn.use-var(gap, $size) / 2) calc(fn.use-var(gap, $size) * 2);
|
|
112
135
|
border-bottom: fn.use-var(border);
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<u-dropdown
|
|
3
3
|
v-if="!readonly"
|
|
4
4
|
v-bind="$attrs"
|
|
5
|
-
:class="[cls.b, cls.m(size), bem.is('disabled', disabled)]"
|
|
5
|
+
:class="[cls.b, cls.m(size), bem.is('disabled', disabled), bem.is('focus', dropdownVisible)]"
|
|
6
6
|
trigger="click"
|
|
7
7
|
:content-class="[cls.e('panel'), cls.em('panel', size), contentClass]"
|
|
8
8
|
:content-style="contentStyle"
|
|
@@ -16,12 +16,12 @@
|
|
|
16
16
|
>
|
|
17
17
|
<template #trigger>
|
|
18
18
|
<!-- 默认展示 -->
|
|
19
|
-
<span :class="cls.e('placeholder')" v-
|
|
19
|
+
<span :class="cls.e('placeholder')" v-if="!filterable && !tags.length">
|
|
20
20
|
{{ placeholder }}
|
|
21
21
|
</span>
|
|
22
22
|
|
|
23
23
|
<!-- 选择的数据项 -->
|
|
24
|
-
<div v-if="tags.length" :class="cls.e('tags')">
|
|
24
|
+
<div v-if="tags.length || filterable" :class="cls.e('tags')">
|
|
25
25
|
<u-tag
|
|
26
26
|
v-for="(tag, index) in visibleTags"
|
|
27
27
|
:key="tag[valueKey]"
|
|
@@ -31,6 +31,18 @@
|
|
|
31
31
|
{{ tag[labelKey] }}
|
|
32
32
|
</u-tag>
|
|
33
33
|
<u-tag v-if="hiddenCount > 0">+{{ hiddenCount }}</u-tag>
|
|
34
|
+
|
|
35
|
+
<!-- 内嵌查询输入框:承载过滤 -->
|
|
36
|
+
<input
|
|
37
|
+
v-if="filterable"
|
|
38
|
+
ref="inputRef"
|
|
39
|
+
:class="cls.e('input')"
|
|
40
|
+
v-model="qs"
|
|
41
|
+
:placeholder="tags.length ? '' : placeholder"
|
|
42
|
+
:disabled="disabled"
|
|
43
|
+
@click.stop="handleInputClick"
|
|
44
|
+
@focus="handleInputFocus"
|
|
45
|
+
/>
|
|
34
46
|
</div>
|
|
35
47
|
<!-- 清空 icon -->
|
|
36
48
|
<transition name="zoom-in" mode="out-in">
|
|
@@ -59,14 +71,6 @@
|
|
|
59
71
|
{{ allExpanded ? '收起全部' : '展开全部' }}
|
|
60
72
|
</u-button>
|
|
61
73
|
</div>
|
|
62
|
-
<!-- 过滤器 -->
|
|
63
|
-
<div v-if="filterable" :class="[cls.e('content-filter'), cls.m(size)]">
|
|
64
|
-
<u-input placeholder="输入关键字进行过滤" v-model="qs">
|
|
65
|
-
<template #suffix>
|
|
66
|
-
<u-icon><Search /></u-icon>
|
|
67
|
-
</template>
|
|
68
|
-
</u-input>
|
|
69
|
-
</div>
|
|
70
74
|
<!-- 菜单列表 -->
|
|
71
75
|
<u-tree
|
|
72
76
|
v-bind="treeProps"
|
|
@@ -98,10 +102,10 @@
|
|
|
98
102
|
<script lang="ts" setup>
|
|
99
103
|
import { dfs, o } from '@cat-kit/core'
|
|
100
104
|
import { useFormFallbackProps } from '@veltra/compositions'
|
|
101
|
-
import { ArrowDown, Close
|
|
105
|
+
import { ArrowDown, Close } from '@veltra/icons/normal'
|
|
102
106
|
import { bem, fieldKey, FORM_EMPTY_CONTENT } from '@veltra/utils'
|
|
103
107
|
import { injectFormContext } from '@veltra/utils'
|
|
104
|
-
import { computed, nextTick, shallowRef, watch } from 'vue'
|
|
108
|
+
import { computed, nextTick, shallowRef, useTemplateRef, watch } from 'vue'
|
|
105
109
|
|
|
106
110
|
import type { MultiTreeSelectProps, MultiTreeSelectEmits, TreeExposed } from '../../types'
|
|
107
111
|
import type { DropdownExposed } from '../../types'
|
|
@@ -109,7 +113,6 @@ import { UButton } from '../button'
|
|
|
109
113
|
import { UCheckbox } from '../checkbox'
|
|
110
114
|
import { UDropdown } from '../dropdown'
|
|
111
115
|
import { UIcon } from '../icon'
|
|
112
|
-
import { UInput } from '../input'
|
|
113
116
|
import { UTag } from '../tag'
|
|
114
117
|
import { UTree } from '../tree'
|
|
115
118
|
import type { TreeSlotsScope } from '../tree/di'
|
|
@@ -306,9 +309,26 @@ const hiddenCount = computed(() => {
|
|
|
306
309
|
return 0
|
|
307
310
|
})
|
|
308
311
|
|
|
312
|
+
const dropdownVisible = shallowRef(false)
|
|
313
|
+
|
|
314
|
+
const inputRef = useTemplateRef<HTMLInputElement>('inputRef')
|
|
315
|
+
|
|
309
316
|
const handleDropdownVisible = (visible: boolean) => {
|
|
317
|
+
dropdownVisible.value = visible
|
|
310
318
|
if (!visible) {
|
|
311
319
|
qs.value = ''
|
|
312
320
|
}
|
|
313
321
|
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* 拦截输入区域的点击(@click.stop):阻止 dropdown 的 trigger 开合切换,
|
|
325
|
+
* 保持面板展开以不中断输入。
|
|
326
|
+
*/
|
|
327
|
+
const handleInputClick = () => {
|
|
328
|
+
if (!dropdownVisible.value) dropdownRef.value?.open()
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const handleInputFocus = () => {
|
|
332
|
+
if (!dropdownVisible.value) dropdownRef.value?.open()
|
|
333
|
+
}
|
|
314
334
|
</script>
|
|
@@ -22,6 +22,11 @@ $root-name: multi-tree-select;
|
|
|
22
22
|
border-color: fn.use-var(color, primary);
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
@include m.is(focus) {
|
|
26
|
+
border-color: fn.use-var(color, primary);
|
|
27
|
+
box-shadow: fn.use-var(focus-ring);
|
|
28
|
+
}
|
|
29
|
+
|
|
25
30
|
@include m.size using ($size) {
|
|
26
31
|
min-height: fn.use-var(form-component-height, $size);
|
|
27
32
|
border-radius: fn.use-var(radius, $size);
|
|
@@ -58,16 +63,33 @@ $root-name: multi-tree-select;
|
|
|
58
63
|
color: fn.use-var(text-color, placeholder);
|
|
59
64
|
}
|
|
60
65
|
|
|
61
|
-
@include m.e(filter) {
|
|
62
|
-
margin-bottom: 4px;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
66
|
@include m.e(tags) {
|
|
66
67
|
flex-grow: 1;
|
|
67
68
|
overflow: hidden;
|
|
68
69
|
@include m.flex($justify: flex-start, $align: center, $wrap: wrap);
|
|
69
70
|
}
|
|
70
71
|
|
|
72
|
+
@include m.e(input) {
|
|
73
|
+
flex: 1;
|
|
74
|
+
min-width: 60px;
|
|
75
|
+
padding: 0;
|
|
76
|
+
margin: 0;
|
|
77
|
+
border: none;
|
|
78
|
+
outline: none;
|
|
79
|
+
background: transparent;
|
|
80
|
+
font: inherit;
|
|
81
|
+
color: fn.use-var(text-color, main);
|
|
82
|
+
cursor: text;
|
|
83
|
+
|
|
84
|
+
&::placeholder {
|
|
85
|
+
color: fn.use-var(text-color, placeholder);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
&:disabled {
|
|
89
|
+
cursor: not-allowed;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
71
93
|
@include m.e(readonly-tags) {
|
|
72
94
|
padding-left: 0;
|
|
73
95
|
padding-right: 0;
|
|
@@ -90,21 +112,18 @@ $root-name: multi-tree-select;
|
|
|
90
112
|
|
|
91
113
|
@include m.e(panel) {
|
|
92
114
|
@include m.size using ($size) {
|
|
93
|
-
@include m.bem($root-name, content-filter) {
|
|
94
|
-
padding: calc(fn.use-var(gap, $size) / 2) fn.use-var(gap, $size);
|
|
95
|
-
}
|
|
96
115
|
@include m.bem($root-name, content-header) {
|
|
97
116
|
padding: calc(fn.use-var(gap, $size) / 2) fn.use-var(gap, $size);
|
|
98
117
|
border-bottom: fn.use-var(border);
|
|
99
118
|
}
|
|
100
119
|
|
|
101
120
|
@include m.bem($root-name, content-tree) {
|
|
102
|
-
padding:
|
|
121
|
+
padding: fn.use-var(gap, $size);
|
|
103
122
|
}
|
|
104
123
|
}
|
|
105
124
|
}
|
|
106
125
|
|
|
107
126
|
@include m.e(content-tree) {
|
|
108
|
-
max-height:
|
|
127
|
+
max-height: 240px;
|
|
109
128
|
}
|
|
110
129
|
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { createApp, h, nextTick, ref } from 'vue'
|
|
3
|
+
|
|
4
|
+
import USelect from '../select.vue'
|
|
5
|
+
|
|
6
|
+
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
|
|
7
|
+
|
|
8
|
+
function mountSelect(props: Record<string, unknown>) {
|
|
9
|
+
const host = document.createElement('div')
|
|
10
|
+
document.body.appendChild(host)
|
|
11
|
+
|
|
12
|
+
const model = ref(props.modelValue)
|
|
13
|
+
const app = createApp({
|
|
14
|
+
render() {
|
|
15
|
+
return h(USelect, {
|
|
16
|
+
...props,
|
|
17
|
+
modelValue: model.value,
|
|
18
|
+
'onUpdate:modelValue': (value: unknown) => {
|
|
19
|
+
model.value = value
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
}
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
app.mount(host)
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
host,
|
|
29
|
+
model,
|
|
30
|
+
unmount() {
|
|
31
|
+
app.unmount()
|
|
32
|
+
host.remove()
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function queryOptions() {
|
|
38
|
+
return [...document.body.querySelectorAll<HTMLElement>('.u-select__option')]
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function openDropdown(host: HTMLElement) {
|
|
42
|
+
const input = host.querySelector('input')!
|
|
43
|
+
// 与组件 capture 拦截一致:点击原生 input 打开面板并进入查询态
|
|
44
|
+
input.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
|
|
45
|
+
await nextTick()
|
|
46
|
+
await nextTick()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function typeQuery(host: HTMLElement, text: string) {
|
|
50
|
+
const input = host.querySelector('input')!
|
|
51
|
+
input.focus()
|
|
52
|
+
// 清空查询串(查询态下 model 绑定 queryString)
|
|
53
|
+
input.value = ''
|
|
54
|
+
input.dispatchEvent(
|
|
55
|
+
new InputEvent('input', { bubbles: true, data: '', inputType: 'deleteContentBackward' })
|
|
56
|
+
)
|
|
57
|
+
await nextTick()
|
|
58
|
+
|
|
59
|
+
input.value = text
|
|
60
|
+
input.dispatchEvent(
|
|
61
|
+
new InputEvent('input', { bubbles: true, data: text, inputType: 'insertText' })
|
|
62
|
+
)
|
|
63
|
+
await nextTick()
|
|
64
|
+
// 本地过滤防抖 200ms
|
|
65
|
+
await sleep(250)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function clickOption(option: HTMLElement) {
|
|
69
|
+
option.dispatchEvent(new MouseEvent('click', { bubbles: true }))
|
|
70
|
+
await nextTick()
|
|
71
|
+
await sleep(50)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
describe('USelect', () => {
|
|
75
|
+
it('highlights the latest created option after consecutive creations', async () => {
|
|
76
|
+
const { host, model, unmount } = mountSelect({
|
|
77
|
+
filterable: true,
|
|
78
|
+
creatable: true,
|
|
79
|
+
options: [
|
|
80
|
+
{ label: '选项0', value: '0' },
|
|
81
|
+
{ label: '选项1', value: '1' }
|
|
82
|
+
]
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
// 创建并选择第一个选项
|
|
87
|
+
await openDropdown(host)
|
|
88
|
+
await typeQuery(host, '新A')
|
|
89
|
+
expect(queryOptions().some((el) => el.textContent === '新A')).toBe(true)
|
|
90
|
+
await clickOption(queryOptions().find((el) => el.textContent === '新A')!)
|
|
91
|
+
expect(model.value).toBe('新A')
|
|
92
|
+
|
|
93
|
+
// 创建并选择第二个选项
|
|
94
|
+
await openDropdown(host)
|
|
95
|
+
await typeQuery(host, '新B')
|
|
96
|
+
expect(queryOptions().some((el) => el.textContent === '新B')).toBe(true)
|
|
97
|
+
await clickOption(queryOptions().find((el) => el.textContent === '新B')!)
|
|
98
|
+
expect(model.value).toBe('新B')
|
|
99
|
+
expect(host.querySelector('input')!.value).toBe('新B')
|
|
100
|
+
|
|
101
|
+
// 重新展开,高亮应落在后创建的“新B”上,而非停留在“新A”
|
|
102
|
+
await openDropdown(host)
|
|
103
|
+
const selected = queryOptions().filter((el) => el.classList.contains('is-selected'))
|
|
104
|
+
expect(selected).toHaveLength(1)
|
|
105
|
+
expect(selected[0]!.textContent).toBe('新B')
|
|
106
|
+
} finally {
|
|
107
|
+
unmount()
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
})
|