|
1 | 1 | <template>
|
2 | 2 | <div
|
3 |
| - class="multiselect" |
4 |
| - :class="[`is-${mode}`, { |
5 |
| - 'is-open': isOpen, |
6 |
| - 'is-searchable': searchable, |
7 |
| - 'is-disabled': disabled, |
8 |
| - 'no-caret': !caret, |
9 |
| - 'open-top': openDirection === 'top', |
10 |
| - }]" |
11 |
| - :id="id" |
12 |
| - @keydown.prevent.enter |
13 | 3 | ref="multiselect"
|
| 4 | + :tabindex="tabindex" |
| 5 | + :class="classList.container" |
| 6 | + :id="id" |
| 7 | + @focusin="open" |
| 8 | + @focusout="close" |
| 9 | + @keydown.self="searchable ? false : handleKeydown($event)" |
14 | 10 | >
|
15 |
| - <div |
16 |
| - class="multiselect-input" |
17 |
| - :tabindex="tabindex" |
18 |
| - @mousedown="handleInputMousedown" |
19 |
| - @focus="openDropdown" |
20 |
| - @blur="closeDropdown" |
21 |
| - @keyup.esc="handleEsc" |
22 |
| - @keyup.enter="selectPointer" |
23 |
| - @keydown.prevent.delete="handleBackspace" |
24 |
| - @keydown.prevent.up="openDirection === 'top' ? forwardPointer() : backwardPointer()" |
25 |
| - @keydown.prevent.down="openDirection === 'top' ? backwardPointer() : forwardPointer()" |
26 |
| - > |
27 |
| - <!-- Single label --> |
28 |
| - <template v-if="mode == 'single' && hasSelected && !search && iv"> |
29 |
| - <slot name="singlelabel" :value="iv"> |
30 |
| - <div class="multiselect-single-label"> |
31 |
| - {{ iv[label] }} |
32 |
| - </div> |
33 |
| - </slot> |
34 |
| - </template> |
35 |
| - |
36 |
| - <!-- Multiple label --> |
37 |
| - <template v-if="mode == 'multiple' && hasSelected && !search"> |
38 |
| - <slot name="multiplelabel" :values="iv"> |
39 |
| - <div class="multiselect-multiple-label"> |
40 |
| - {{ multipleLabelText }} |
41 |
| - </div> |
42 |
| - </slot> |
43 |
| - </template> |
44 |
| - |
45 |
| - <!-- Search --> |
46 |
| - <template v-if="mode !== 'tags' && searchable && !disabled"> |
47 |
| - <div class="multiselect-search"> |
48 |
| - <input |
49 |
| - :modelValue="search" |
50 |
| - :value="search" |
51 |
| - @focus.stop="openDropdown" |
52 |
| - @blur.stop="closeDropdown" |
53 |
| - @keyup.stop.esc="handleEsc" |
54 |
| - @keyup.stop.enter="selectPointer" |
55 |
| - @keydown.delete="handleSearchBackspace" |
56 |
| - @keydown.stop.up="openDirection === 'top' ? forwardPointer() : backwardPointer()" |
57 |
| - @keydown.stop.down="openDirection === 'top' ? backwardPointer() : forwardPointer()" |
58 |
| - @input="handleSearchInput" |
59 |
| - ref="input" |
60 |
| - /> |
61 |
| - </div> |
62 |
| - </template> |
63 |
| - |
64 |
| - <!-- Tags (with search) --> |
65 |
| - <template v-if="mode == 'tags'"> |
66 |
| - <div class="multiselect-tags"> |
67 |
| - |
68 |
| - <span v-for="(option, i, key) in iv" :key="key"> |
69 |
| - <slot name="tag" :option="option" :handleTagRemove="handleTagRemove" :disabled="disabled"> |
70 |
| - <div class="multiselect-tag"> |
71 |
| - {{ option[label] }} |
72 |
| - <i |
73 |
| - v-if="!disabled" |
74 |
| - @click.prevent |
75 |
| - @mousedown.prevent.stop="handleTagRemove(option, $event)" |
76 |
| - ></i> |
77 |
| - </div> |
78 |
| - </slot> |
79 |
| - </span> |
80 |
| - |
81 |
| - <div |
82 |
| - v-if="searchable && !disabled" |
83 |
| - class="multiselect-search" |
84 |
| - :style="{ width: tagsSearchWidth }" |
85 |
| - > |
86 |
| - <input |
87 |
| - :modelValue="search" |
88 |
| - :value="search" |
89 |
| - @focus.stop="openDropdown" |
90 |
| - @blur.stop="closeDropdown" |
91 |
| - @keyup.stop.esc="handleEsc" |
92 |
| - @keyup.stop.enter="handleAddTag" |
93 |
| - @keyup.stop.space="handleAddTag" |
94 |
| - @keydown.delete="handleSearchBackspace" |
95 |
| - @keydown.stop.up="openDirection === 'top' ? forwardPointer() : backwardPointer()" |
96 |
| - @keydown.stop.down="openDirection === 'top' ? backwardPointer() : forwardPointer()" |
97 |
| - @input="handleSearchInput" |
98 |
| - :style="{ width: tagsSearchWidth }" |
99 |
| - ref="input" |
100 |
| - /> |
101 |
| - </div> |
| 11 | + <!-- Single label --> |
| 12 | + <template v-if="mode == 'single' && hasSelected && !search && iv"> |
| 13 | + <slot name="singlelabel" :value="iv"> |
| 14 | + <div class="multiselect-single-label"> |
| 15 | + {{ iv[label] }} |
102 | 16 | </div>
|
103 |
| - </template> |
104 |
| - |
105 |
| - <!-- Placeholder --> |
106 |
| - <template v-if="placeholder && !hasSelected && !search"> |
107 |
| - <slot name="placeholder"> |
108 |
| - <div class="multiselect-placeholder"> |
109 |
| - {{ placeholder }} |
110 |
| - </div> |
111 |
| - </slot> |
112 |
| - </template> |
113 |
| - |
114 |
| - <slot v-if="!hasSelected && caret && !busy" name="caret"> |
115 |
| - <span class="multiselect-caret"></span> |
116 |
| - </slot> |
117 |
| - |
118 |
| - <slot v-if="hasSelected && !disabled && !busy && canDeselect" name="clear" :clear="clear"> |
119 |
| - <a class="multiselect-clear" @click.prevent="clear"></a> |
120 | 17 | </slot>
|
121 |
| - |
122 |
| - <transition name="multiselect-loading"> |
123 |
| - <span v-if="busy"> |
124 |
| - <slot name="spinner"> |
125 |
| - <span class="multiselect-spinner"></span> |
126 |
| - </slot> |
127 |
| - </span> |
128 |
| - </transition> |
129 |
| - </div> |
| 18 | + </template> |
| 19 | + |
| 20 | + <!-- Search --> |
| 21 | + <template v-if="mode !== 'tags' && searchable && !disabled"> |
| 22 | + <div class="multiselect-search"> |
| 23 | + <input |
| 24 | + :modelValue="search" |
| 25 | + :value="search" |
| 26 | + @keydown.self="handleKeydown" |
| 27 | + @input="handleSearchInput" |
| 28 | + ref="input" |
| 29 | + /> |
| 30 | + </div> |
| 31 | + </template> |
130 | 32 |
|
131 | 33 | <!-- Options -->
|
132 | 34 | <transition v-if="!resolving || !clearOnSearch" name="multiselect" @after-leave="clearSearch">
|
| 35 | + <!-- @mousedown.prevent: do not close when before/afterlist is clicked --> |
133 | 36 | <div
|
134 | 37 | v-show="isOpen && showOptions"
|
135 | 38 | class="multiselect-options"
|
136 | 39 | :style="{ maxHeight: contentMaxHeight }"
|
137 | 40 | >
|
138 | 41 | <slot name="beforelist"></slot>
|
139 |
| - |
140 |
| - <span |
141 |
| - v-for="(option, i, key) in fo" |
142 |
| - :tabindex="-1" |
143 |
| - class="multiselect-option" |
144 |
| - :class="{ |
145 |
| - 'is-pointed': isPointed(option), |
146 |
| - 'is-selected': isSelected(option), |
147 |
| - 'is-disabled': isDisabled(option), |
148 |
| - }" |
149 |
| - :key="key" |
150 |
| - @mousedown.prevent |
151 |
| - @mouseenter="setPointer(option)" |
152 |
| - @click.stop.prevent="handleOptionClick(option)" |
153 |
| - > |
154 |
| - <slot name="option" :option="option" :search="search"> |
155 |
| - <span>{{ option[label] }}</span> |
156 |
| - </slot> |
157 |
| - </span> |
158 |
| - |
159 |
| - <span v-show="noOptions"> |
160 |
| - <slot name="nooptions"> |
161 |
| - <div class="multiselect-no-options">{{ noOptionsText }}</div> |
162 |
| - </slot> |
163 |
| - </span> |
164 |
| - |
165 |
| - <span v-show="noResults"> |
166 |
| - <slot name="noresults"> |
167 |
| - <div class="multiselect-no-results">{{ noResultsText }}</div> |
168 |
| - </slot> |
169 |
| - </span> |
170 |
| - |
171 |
| - <slot name="afterlist"></slot> |
| 42 | + <ul> |
| 43 | + <li |
| 44 | + v-for="(option, i, key) in fo" |
| 45 | + :class="classList.option(option)" |
| 46 | + :key="key" |
| 47 | + @mouseenter="setPointer(option)" |
| 48 | + @click="handleOptionClick(option)" |
| 49 | + > |
| 50 | + <slot name="option" :option="option" :search="search"> |
| 51 | + <span>{{ option[label] }}</span> |
| 52 | + </slot> |
| 53 | + </li> |
| 54 | + </ul> |
172 | 55 | </div>
|
173 | 56 | </transition>
|
174 | 57 |
|
175 |
| - <!-- Hacky input element to show HTML5 required warning --> |
176 |
| - <input v-if="required" class="multiselect-fake-input" tabindex="-1" :value="textValue" required/> |
177 |
| - |
178 |
| - <template v-if="nativeSupport"> |
179 |
| - <input v-if="mode == 'single'" type="hidden" :name="name" :value="plainValue !== undefined ? plainValue : ''" /> |
180 |
| - <template v-else> |
181 |
| - <input v-for="(v, i) in plainValue" type="hidden" :name="`${name}[]`" :value="v" :key="i" /> |
182 |
| - </template> |
183 |
| - </template> |
184 | 58 | </div>
|
185 | 59 | </template>
|
186 | 60 |
|
|
194 | 68 | import useDropdown from './composables/useDropdown'
|
195 | 69 | import useMultiselect from './composables/useMultiselect'
|
196 | 70 | import useKeyboard from './composables/useKeyboard'
|
| 71 | + import useClasses from './composables/useClasses' |
197 | 72 |
|
198 | 73 | export default {
|
199 | 74 | name: 'Multiselect',
|
|
217 | 92 | id: {
|
218 | 93 | type: [String, Number],
|
219 | 94 | required: false,
|
220 |
| - default: 'multiselect', |
221 | 95 | },
|
222 | 96 | name: {
|
223 | 97 | type: [String, Number],
|
|
382 | 256 | setup(props, context)
|
383 | 257 | {
|
384 | 258 | const value = useValue(props, context)
|
385 |
| - const multiselect = useMultiselect(props, context) |
386 | 259 | const pointer = usePointer(props, context)
|
387 | 260 |
|
388 | 261 | const data = useData(props, context, {
|
|
393 | 266 | iv: value.iv,
|
394 | 267 | })
|
395 | 268 |
|
| 269 | + const multiselect = useMultiselect(props, context, { |
| 270 | + input: search.input, |
| 271 | + }) |
| 272 | +
|
396 | 273 | const dropdown = useDropdown(props, context, {
|
397 | 274 | multiselect: multiselect.multiselect,
|
398 | 275 | blurInput: multiselect.blurInput,
|
|
405 | 282 | ev: value.ev,
|
406 | 283 | iv: value.iv,
|
407 | 284 | search: search.search,
|
408 |
| - blurSearch: search.blurSearch, |
| 285 | + blur: multiselect.blur, |
409 | 286 | clearSearch: search.clearSearch,
|
410 | 287 | update: data.update,
|
411 | 288 | blurInput: multiselect.blurInput,
|
412 | 289 | pointer: pointer.pointer,
|
| 290 | + close: dropdown.close, |
413 | 291 | })
|
414 | 292 |
|
415 | 293 | const pointerAction = usePointerAction(props, context, {
|
416 | 294 | fo: options.fo,
|
417 | 295 | handleOptionClick: options.handleOptionClick,
|
418 | 296 | search: search.search,
|
419 | 297 | pointer: pointer.pointer,
|
| 298 | + multiselect: multiselect.multiselect, |
420 | 299 | })
|
421 | 300 |
|
422 | 301 | const keyboard = useKeyboard(props, context, {
|
|
426 | 305 | clearPointer: pointerAction.clearPointer,
|
427 | 306 | search: search.search,
|
428 | 307 | selectPointer: pointerAction.selectPointer,
|
| 308 | + forwardPointer: pointerAction.forwardPointer, |
| 309 | + backwardPointer: pointerAction.backwardPointer, |
| 310 | + blur: multiselect.blur, |
| 311 | + }) |
| 312 | +
|
| 313 | + const classes = useClasses(props, context, { |
| 314 | + isOpen: dropdown.isOpen, |
| 315 | + isPointed: pointerAction.isPointed, |
| 316 | + isSelected: options.isSelected, |
| 317 | + isDisabled: options.isDisabled, |
429 | 318 | })
|
430 | 319 |
|
431 | 320 | return {
|
|
438 | 327 | ...options,
|
439 | 328 | ...pointerAction,
|
440 | 329 | ...keyboard,
|
| 330 | + ...classes, |
441 | 331 | }
|
442 | 332 | }
|
443 | 333 | }
|
|
0 commit comments