@teammodlr/ui-kit 2.1.49 → 2.1.52

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 (104) hide show
  1. package/lib/components/avatar/Avatar.vue.d.ts +1 -1
  2. package/lib/components/badge/style/Badge.css.d.ts +2 -2
  3. package/lib/components/button/Button.vue.d.ts +5 -5
  4. package/lib/components/button/style/Button.css.d.ts +3 -3
  5. package/lib/components/checkbox/Checkbox.vue.d.ts +4 -4
  6. package/lib/components/collapse/Collapse.vue.d.ts +82 -0
  7. package/lib/components/collapse/CollapseItem.vue.d.ts +43 -0
  8. package/lib/components/collapse/CollapseItemContent.vue.d.ts +29 -0
  9. package/lib/components/collapse/interface.d.ts +9 -0
  10. package/lib/components/collapse/style/Collapse.css.d.ts +8 -0
  11. package/lib/components/collapse/style/index.d.ts +1 -0
  12. package/lib/components/configprovider/ConfigProvider.vue.d.ts +11 -11
  13. package/lib/components/confirm/ConfirmProvider.vue.d.ts +10 -10
  14. package/lib/components/dialog/Dialog.vue.d.ts +1 -1
  15. package/lib/components/dropdown/Dropdown.vue.d.ts +2 -2
  16. package/lib/components/dropdown/DropdownItem.vue.d.ts +3 -3
  17. package/lib/components/dropdown/style/Dropdown.css.d.ts +1 -1
  18. package/lib/components/index.d.ts +3 -0
  19. package/lib/components/input/Input.vue.d.ts +7 -7
  20. package/lib/components/link/Link.vue.d.ts +2 -2
  21. package/lib/components/messageprovider/Message.vue.d.ts +2 -2
  22. package/lib/components/messageprovider/MessageProvider.vue.d.ts +5 -5
  23. package/lib/components/messageprovider/style/Message.css.d.ts +1 -1
  24. package/lib/components/modal/Modal.vue.d.ts +2 -2
  25. package/lib/components/radio/Radio.vue.d.ts +7 -7
  26. package/lib/components/radiobutton/RadioButton.vue.d.ts +6 -6
  27. package/lib/components/renderer/renderer.d.ts +1 -1
  28. package/lib/components/select/Select.vue.d.ts +21 -20
  29. package/lib/components/switch/Switch.vue.d.ts +4 -4
  30. package/lib/components/tab/Tab.vue.d.ts +4 -6
  31. package/lib/components/tab/TabGroup.vue.d.ts +17 -3
  32. package/lib/components/tab/style/Tab.css.d.ts +7 -0
  33. package/lib/components/tab/style/index.d.ts +1 -0
  34. package/lib/components/table/Table.vue.d.ts +2 -2
  35. package/lib/components/textarea/Textarea.vue.d.ts +6 -6
  36. package/lib/components/tree/Tree.vue.d.ts +1 -1
  37. package/lib/composables/useInput.d.ts +1 -1
  38. package/lib/index.cjs.js +10 -13
  39. package/lib/index.es.js +3151 -2955
  40. package/lib/install.d.ts +1 -1
  41. package/lib/style.css +1 -1
  42. package/lib/styles/theme.css.d.ts +1 -1
  43. package/package.json +76 -89
  44. package/src/call.ts +34 -34
  45. package/src/components/avatar/examples/index.stories.ts +25 -25
  46. package/src/components/avatargroup/examples/index.stories.ts +19 -19
  47. package/src/components/badge/examples/index.stories.ts +19 -19
  48. package/src/components/breadcrumb/examples/index.stories.ts +16 -16
  49. package/src/components/button/examples/index.stories.ts +37 -37
  50. package/src/components/buttongroup/examples/index.stories.ts +17 -17
  51. package/src/components/checkbox/examples/index.stories.ts +16 -16
  52. package/src/components/collapse/Collapse.vue +143 -0
  53. package/src/components/collapse/CollapseItem.vue +123 -0
  54. package/src/components/collapse/CollapseItemContent.vue +58 -0
  55. package/src/components/collapse/examples/basic.vue +13 -0
  56. package/src/components/collapse/examples/index.stories.ts +18 -0
  57. package/src/components/collapse/examples/index.ts +1 -0
  58. package/src/components/collapse/interface.ts +38 -0
  59. package/src/components/collapse/style/Collapse.css.ts +87 -0
  60. package/src/components/collapse/style/index.ts +1 -0
  61. package/src/components/colorpicker/examples/index.stories.ts +12 -12
  62. package/src/components/configprovider/ConfigProvider.vue +38 -38
  63. package/src/components/confirm/ConfirmProvider.vue +82 -82
  64. package/src/components/confirm/examples/index.stories.ts +26 -26
  65. package/src/components/dialog/examples/index.stories.ts +14 -14
  66. package/src/components/dropdown/examples/index.stories.ts +26 -26
  67. package/src/components/form/examples/index.stories.ts +23 -23
  68. package/src/components/image/examples/index.stories.ts +10 -10
  69. package/src/components/index.ts +51 -48
  70. package/src/components/input/examples/index.stories.ts +26 -26
  71. package/src/components/link/examples/index.stories.ts +15 -15
  72. package/src/components/messageprovider/examples/index.stories.ts +26 -26
  73. package/src/components/modal/examples/index.stories.ts +16 -16
  74. package/src/components/pagination/examples/index.stories.ts +11 -11
  75. package/src/components/radio/examples/index.stories.ts +16 -16
  76. package/src/components/radiobuttongroup/examples/index.stories.ts +18 -18
  77. package/src/components/renderer/examlpes/index.stories.ts +14 -14
  78. package/src/components/select/Select.vue +755 -755
  79. package/src/components/select/examples/basic.vue +26 -26
  80. package/src/components/select/examples/index.stories.ts +16 -16
  81. package/src/components/select/types.ts +27 -27
  82. package/src/components/skeleton/examples/index.stories.ts +13 -13
  83. package/src/components/switch/examples/index.stories.ts +15 -15
  84. package/src/components/switch/style/Switch.css.ts +135 -135
  85. package/src/components/tab/Tab.vue +85 -98
  86. package/src/components/tab/TabGroup.vue +96 -90
  87. package/src/components/tab/examples/index.stories.ts +21 -10
  88. package/src/components/tab/style/Tab.css.ts +137 -0
  89. package/src/components/tab/style/index.ts +1 -0
  90. package/src/components/table/examples/index.stories.ts +17 -17
  91. package/src/components/tag/examples/index.stories.ts +16 -16
  92. package/src/components/textarea/examples/index.stories.ts +14 -14
  93. package/src/components/tooltip/examples/index.stories.ts +17 -17
  94. package/src/components/transfer/examples/index.stories.ts +12 -12
  95. package/src/components/tree/examples/index.stories.ts +12 -12
  96. package/src/components/virtualscroll/examples/index.stories.ts +11 -11
  97. package/src/composables/useCachedUid.ts +15 -15
  98. package/src/composables/useInput.ts +151 -151
  99. package/src/examples/cards/cards.stories.ts +9 -9
  100. package/src/examples/modal/modal.stories.ts +9 -9
  101. package/src/types/props.ts +21 -21
  102. package/src/utils/call.ts +34 -34
  103. package/src/utils/injection.ts +45 -45
  104. package/src/components/tab/Tab.css +0 -113
@@ -1,755 +1,755 @@
1
- <template>
2
- <div ref="selectRef" tabindex="-1">
3
- <label v-if="label" :for="id" :class="style.label" :id="`${id}-label`">
4
- {{ label }}
5
- </label>
6
- <div v-if="description" :class="style.description">
7
- {{ description }}
8
- </div>
9
-
10
- <div :class="computedWrapperClasses" v-on="inputListeners" :id="id" ref="container"
11
- :aria-disabled="disabled ? 'true' : 'false'" :aria-labelledby="`${id}-label`" tabindex="0"
12
- @mousedown.prevent="onSelectClick" @focusin.stop="focusSelect">
13
- <div :class="selectComputedClasses">
14
- <template v-if="
15
- multiple &&
16
- Array.isArray(selected) &&
17
- selected.length > 0
18
- ">
19
- <MTag v-for="value in selectedMax" :key="value" :closeable="true"
20
- @close="event => removeSelected(event, value)" :size="size">
21
- {{ getSelectedLabel(value) }}
22
- </MTag>
23
- <template v-if="selectedCountNotShown > 0">
24
- <MTag :size="size">
25
- +{{ selectedCountNotShown }} more
26
- </MTag>
27
- </template>
28
- </template>
29
- <template v-else-if="!multiple && selected">
30
- {{ getSelectedLabel(selected) }}
31
- </template>
32
- <template v-else>
33
- <div :class="style.placeholder">{{ placeholder }}</div>
34
- </template>
35
- </div>
36
-
37
- <Teleport :to="'body'">
38
- <Transition :enter-active-class="scale.enterActive" :leave-active-class="scale.leaveActive"
39
- :enter-from-class="scale.enterFrom" :leave-to-class="scale.leaveTo">
40
- <div :class="style.menu" v-if="isOpen" ref="popper" :style="computedConsistentWidth"
41
- :aria-hidden="isOpen ? 'false' : 'true'">
42
- <div v-if="searchable || (multiple && selectAll)" :class="style.search">
43
- <div v-if="multiple && selectAll" :class="style.checkAll">
44
- <button role="button" type="button" :class="{
45
- [style.checkAllButton]: true,
46
- [style.checkAllButtonActive]:
47
- computedOptions.length > 0 &&
48
- computedOptions.length ===
49
- selected.length
50
- }" @click.prevent="toggleAllItems" aria-description="Select all items">
51
- <svg v-if="
52
- computedOptions.length > 0 &&
53
- computedOptions.length ===
54
- selected.length
55
- " xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor"
56
- aria-hidden="true">
57
- <path fill-rule="evenodd"
58
- d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
59
- clip-rule="evenodd"></path>
60
- </svg>
61
- </button>
62
- </div>
63
- <input v-if="searchable" type="text" ref="searchRef" v-model="search" placeholder="Search"
64
- :class="style.searchInput" />
65
- </div>
66
- <div :class="style.options">
67
- <div v-for="(option, index) in computedOptions" :key="index" ref="optionsRef" :class="{
68
- [style.option]: true,
69
- [style.optionSelected]: option.active,
70
- [style.optionFocused]:
71
- selectedIndex === index
72
- }" @click.prevent="option.onClick" @hover="selectedIndex = index">
73
- <span :class="style.optionIcon">
74
- <svg v-if="option.active" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
75
- fill="currentColor" aria-hidden="true">
76
- <path fill-rule="evenodd"
77
- d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
78
- clip-rule="evenodd"></path>
79
- </svg>
80
- </span>
81
- <span :class="style.optionLabel">{{
82
- option.label
83
- }}</span>
84
- </div>
85
- <template v-if="computedOptions.length === 0">
86
- <div :class="style.noResults">No results</div>
87
- </template>
88
- </div>
89
- </div>
90
- </Transition>
91
- </Teleport>
92
- </div>
93
- <div v-if="inputError" :class="style.errorMessage">
94
- {{ inputError }}
95
- </div>
96
- </div>
97
- </template>
98
-
99
- <script lang="ts">
100
- import { useInput } from '../../composables/useInput';
101
- import { usePopper, type Placement } from '../../composables/usePopper';
102
- import type { MaybeArray, Size } from '../../types/props';
103
- import { onClickOutside, useEventListener } from '@vueuse/core';
104
- import {
105
- computed,
106
- defineComponent,
107
- nextTick,
108
- ref,
109
- watch,
110
- watchEffect,
111
- type PropType
112
- } from 'vue';
113
- import MTag from '../tag/Tag.vue';
114
- import * as style from './style';
115
- import { scale } from '../../styles/transitions.css';
116
- import { useInteractive } from '../../composables/useInteractive';
117
- import type { OnUpdateValue, OnUpdateValueImpl } from './types';
118
- import { call } from "../../utils/call";
119
-
120
- export default defineComponent({
121
- name: 'MSelect',
122
- emits: {
123
- ...useInput.emits()
124
- },
125
- props: {
126
- ...useInput.props(),
127
- ...useInteractive.props(),
128
- label: {
129
- type: String
130
- },
131
- placeholder: {
132
- type: String,
133
- default: 'Select an option'
134
- },
135
- options: {
136
- type: Array as PropType<Array<any>>,
137
- required: true
138
- },
139
- searchable: {
140
- type: Boolean,
141
- default: false
142
- },
143
- multiple: {
144
- type: Boolean,
145
- default: false
146
- },
147
- size: {
148
- type: String as PropType<Size>,
149
- default: 'md'
150
- },
151
- description: {
152
- type: String,
153
- default: ''
154
- },
155
- optionValue: {
156
- type: String,
157
- default: 'value'
158
- },
159
- optionLabel: {
160
- type: String,
161
- default: 'label'
162
- },
163
- transitionName: {
164
- type: String,
165
- default: 'fade'
166
- },
167
- consistentWidth: {
168
- type: Boolean,
169
- default: true
170
- },
171
- disabled: {
172
- type: Boolean,
173
- default: false
174
- },
175
- loading: {
176
- type: Boolean,
177
- default: false
178
- },
179
- error: {
180
- type: String
181
- },
182
- allowBlank: {
183
- type: Boolean,
184
- default: false
185
- },
186
- selectAll: {
187
- type: Boolean,
188
- default: false
189
- },
190
- max: {
191
- type: Number,
192
- default: 5
193
- },
194
- 'onUpdate:value': [Function, Array] as PropType<MaybeArray<OnUpdateValue> | undefined>,
195
- onUpdateValue: [Function, Array] as PropType<MaybeArray<OnUpdateValue> | undefined>,
196
- },
197
- components: {
198
- MTag
199
- },
200
- setup(props, { emit, expose }) {
201
- const container = ref<HTMLElement | null>(null);
202
- const wrapper = ref<HTMLElement>();
203
- const optionsRef = ref<HTMLElement[]>();
204
- const searchRef = ref<HTMLElement>();
205
- const selectRef = ref<HTMLElement>();
206
- const selectedIndex = ref<number | undefined>(0);
207
- const open = ref<boolean>(false);
208
- const focused = ref<boolean>(false);
209
- const search = ref<string>('');
210
- const placement = ref<Placement>('bottom-start');
211
- const transfer = ref(false);
212
- const width = ref<number>(0);
213
- const keyboardNavigation = ref<boolean>(false);
214
-
215
- function doUpdateValue(
216
- value: string | number | Array<string | number> | null,
217
- option: object | null
218
- ): void {
219
- const {
220
- 'onUpdate:value': _onUpdateValue,
221
- onUpdateValue
222
- } = props;
223
-
224
- if (onUpdateValue) call(onUpdateValue as OnUpdateValueImpl, value, option)
225
- if (_onUpdateValue) {
226
- call(_onUpdateValue as OnUpdateValueImpl, value, option)
227
- }
228
- }
229
-
230
-
231
- const { focus, blur } = useInteractive(container);
232
- // TODO: Add debounce on keyboard movement (hover flicks it back up)
233
- const {
234
- id,
235
- insideForm,
236
- inputListeners,
237
- formError,
238
- formDisabled,
239
- formLoading
240
- } = useInput(props, { focus, emit, withListeners: false });
241
-
242
- const { popper, updatePopper } = usePopper({
243
- placement,
244
- transfer,
245
- reference: container,
246
- wrapper,
247
- offset: [0, 5]
248
- });
249
-
250
- const disabled = computed(() => formDisabled || props.disabled);
251
- const loading = computed(() => formLoading || props.loading);
252
-
253
- const selected = computed<any | any[]>({
254
- get() {
255
- if (props.multiple) {
256
- if (!props.modelValue) {
257
- return [];
258
- }
259
-
260
- if (Array.isArray(props.modelValue)) {
261
- return props.modelValue;
262
- }
263
-
264
- return [props.modelValue];
265
- }
266
-
267
- return props.modelValue || null;
268
- },
269
- set(value: string | number | []) {
270
- emit('update:modelValue', value);
271
- doUpdateValue(value, null);
272
- }
273
- });
274
-
275
- const selectedMax = computed(() => {
276
- if (props.max <= 0) {
277
- return selected.value;
278
- }
279
-
280
- if (
281
- props.multiple &&
282
- Array.isArray(selected.value) &&
283
- selected.value.length > props.max
284
- ) {
285
- return selected.value.slice(0, props.max);
286
- }
287
-
288
- return selected.value;
289
- });
290
-
291
- const selectedCountNotShown = computed(() => {
292
- if (
293
- props.multiple &&
294
- Array.isArray(selected.value) &&
295
- props.max > 0
296
- ) {
297
- return selected.value.length - props.max;
298
- }
299
-
300
- return 0;
301
- });
302
-
303
- const computedConsistentWidth = computed(() => {
304
- if (props.consistentWidth) {
305
- return {
306
- width: width.value + 'px'
307
- };
308
- }
309
-
310
- return {};
311
- });
312
-
313
- const computedOptions = computed(() => {
314
- if (!props.options || props.options.length == 0) {
315
- return [];
316
- }
317
-
318
- // Filter options by search
319
- if (props.searchable && search.value) {
320
- let options = props.options
321
- .filter(option => {
322
- return option[props.optionLabel]
323
- .toLowerCase()
324
- .includes(search.value.toLowerCase());
325
- })
326
- .map(option => {
327
- if (option[props.optionValue] === undefined) {
328
- throw new Error('Option value is undefined');
329
- }
330
-
331
- if (option[props.optionLabel] === undefined) {
332
- throw new Error('Option label is undefined');
333
- }
334
-
335
- let value = option[props.optionValue];
336
- let active: boolean;
337
- if (props.multiple && Array.isArray(selected.value)) {
338
- active = selected.value.includes(value);
339
- } else {
340
- active = selected.value === value;
341
- }
342
-
343
- return {
344
- value: value,
345
- label: option[props.optionLabel],
346
- active: active,
347
- onClick: () => handleOptionClick(value)
348
- };
349
- });
350
-
351
- if (props.allowBlank) {
352
- options.unshift({
353
- value: '',
354
- label: '',
355
- active: false,
356
- onClick: () => handleOptionClick('')
357
- });
358
- }
359
-
360
- return options;
361
- }
362
-
363
- let options = props.options.map(option => {
364
- if (option[props.optionValue] === undefined) {
365
- throw new Error('Option value is undefined');
366
- }
367
-
368
- if (option[props.optionLabel] === undefined) {
369
- throw new Error('Option label is undefined');
370
- }
371
-
372
- let value = option[props.optionValue];
373
- let active: boolean;
374
- if (props.multiple && Array.isArray(selected.value)) {
375
- active = selected.value.includes(value);
376
- } else {
377
- active = selected.value === value;
378
- }
379
-
380
- return {
381
- value: value,
382
- label: option[props.optionLabel],
383
- active: active,
384
- onClick: () => handleOptionClick(value)
385
- };
386
- });
387
-
388
- if (props.allowBlank) {
389
- options.unshift({
390
- value: '',
391
- label: '',
392
- active: false,
393
- onClick: () => handleOptionClick('')
394
- });
395
- }
396
-
397
- return options;
398
- });
399
-
400
- function toggleAllItems() {
401
- if (!props.multiple) {
402
- return;
403
- }
404
-
405
- if (computedOptions.value.length === 0) {
406
- return;
407
- }
408
-
409
- if (Array.isArray(selected.value)) {
410
- if (selected.value.length === computedOptions.value.length) {
411
- selected.value = [];
412
- } else {
413
- selected.value = computedOptions.value.map(
414
- option => option.value
415
- );
416
- }
417
- } else {
418
- selected.value = [];
419
- }
420
- }
421
-
422
- function openSelect() {
423
- if (disabled.value || loading.value) {
424
- return;
425
- }
426
-
427
- if (open.value) {
428
- open.value = false;
429
- return;
430
- }
431
- open.value = true;
432
- }
433
-
434
- function getSelectedIndex(
435
- start: number | undefined,
436
- direction = 'down'
437
- ) {
438
- if (typeof start === 'undefined') {
439
- start = direction === 'down' ? -1 : 1;
440
- }
441
-
442
- if (direction === 'down') {
443
- let next = start + 1;
444
-
445
- if (next > computedOptions.value.length - 1) {
446
- next = 0;
447
- }
448
-
449
- selectedIndex.value = next;
450
- } else {
451
- let next = start - 1;
452
-
453
- if (next < 0) {
454
- next = computedOptions.value.length - 1;
455
- }
456
-
457
- selectedIndex.value = next;
458
- }
459
- }
460
-
461
- function handleOptionClick(value: string | number) {
462
- const option = computedOptions.value.find(
463
- item => item.value === value
464
- );
465
- if (!option) {
466
- return;
467
- }
468
-
469
- if (props.multiple) {
470
- if (Array.isArray(selected.value)) {
471
- const index = selected.value.indexOf(value);
472
-
473
- if (index !== -1) {
474
- selected.value.splice(index, 1);
475
- } else {
476
- selected.value.push(value);
477
- emit('update:modelValue', selected.value);
478
- }
479
- } else {
480
- selected.value = [value];
481
- }
482
-
483
- updatePopper();
484
- } else {
485
- selected.value = value;
486
- open.value = false;
487
- }
488
- }
489
-
490
- function removeSelected(event: MouseEvent, value: string | number) {
491
- event.preventDefault();
492
- event.stopPropagation();
493
- if (!props.multiple) {
494
- return;
495
- }
496
-
497
- if (Array.isArray(selected.value)) {
498
- const index = selected.value.indexOf(value);
499
-
500
- if (index !== -1) {
501
- selected.value.splice(index, 1);
502
- }
503
-
504
- updatePopper();
505
- }
506
- }
507
-
508
- useEventListener('resize', () => {
509
- open.value = false;
510
- });
511
-
512
- function handleKeyDown(e: Event) {
513
- if (computedOptions.value.length === 0) {
514
- return;
515
- }
516
-
517
- if (props.disabled) {
518
- return;
519
- }
520
-
521
- keyboardNavigation.value = true;
522
-
523
- const event: KeyboardEvent = e as KeyboardEvent;
524
-
525
- switch (event.code) {
526
- case 'Tab':
527
- if (open.value) {
528
- open.value = false;
529
- }
530
- break;
531
- case 'ArrowDown':
532
- e.preventDefault();
533
- if (!open.value) {
534
- open.value = true;
535
- return;
536
- }
537
-
538
- getSelectedIndex(selectedIndex.value, 'down');
539
- break;
540
- case 'ArrowUp':
541
- e.preventDefault();
542
- if (!open.value) {
543
- return;
544
- }
545
-
546
- getSelectedIndex(selectedIndex.value, 'up');
547
- break;
548
- case 'Enter':
549
- e.preventDefault();
550
- e.stopPropagation();
551
-
552
- if (!open.value) {
553
- open.value = true;
554
- return;
555
- }
556
-
557
- if (typeof selectedIndex.value !== 'undefined') {
558
- handleOptionClick(
559
- computedOptions.value[selectedIndex.value].value
560
- );
561
- if (!props.multiple) {
562
- open.value = false;
563
- }
564
- }
565
- break;
566
- case 'Escape':
567
- e.preventDefault();
568
- e.stopPropagation();
569
-
570
- if (!open.value) {
571
- return;
572
- }
573
-
574
- open.value = false;
575
- break;
576
- }
577
- }
578
-
579
- const transitionName = computed(() => {
580
- return `m-transition__${props.transitionName}`;
581
- });
582
-
583
- const containerComputedClasses = computed(() => {
584
- return [
585
- 'm-select__container',
586
- {
587
- 'm-form__input': insideForm
588
- }
589
- ];
590
- });
591
-
592
- const computedWrapperClasses = computed(() => {
593
- return [
594
- style.wrapper,
595
- {
596
- [style.focused]: focused.value
597
- }
598
- ];
599
- });
600
-
601
- const selectComputedClasses = computed(() => {
602
- return [
603
- style.base,
604
- {
605
- [style.selectedTags]:
606
- props.multiple &&
607
- Array.isArray(selected.value) &&
608
- selected.value.length > 0,
609
- [style.focused]: open.value,
610
- // [`m-select--${props.size}`]: props.size,
611
- 'm-select--disabled': disabled.value || loading.value,
612
- 'm-select--focused': open.value,
613
- 'm-select--multiple': props.multiple,
614
- 'm-select--error': formError.value
615
- }
616
- ];
617
- });
618
-
619
- function getSelectedLabel(value: string | number | []) {
620
- const option = props.options.find(
621
- i => i[props.optionValue] === value
622
- );
623
- if (!option) {
624
- return null;
625
- }
626
-
627
- return option[props.optionLabel];
628
- }
629
-
630
- useEventListener(selectRef, 'keydown', handleKeyDown);
631
- useEventListener(searchRef, 'keydown', handleKeyDown);
632
-
633
- onClickOutside(
634
- container,
635
- () => {
636
- open.value = false;
637
- },
638
- { ignore: [searchRef, popper] }
639
- );
640
-
641
- watchEffect(() => {
642
- if (container.value && open.value) {
643
- width.value = container.value.offsetWidth;
644
- }
645
- });
646
-
647
- watch(selectedIndex, (index: number | undefined) => {
648
- if (!keyboardNavigation.value) {
649
- return;
650
- }
651
- if (typeof index !== 'undefined' && optionsRef.value) {
652
- optionsRef.value[index].scrollIntoView({
653
- block: 'nearest',
654
- inline: 'nearest'
655
- });
656
- }
657
- });
658
-
659
- watch(open, (value: boolean) => {
660
- nextTick(() => {
661
- if (!value) {
662
- search.value = '';
663
- }
664
-
665
- if (props.searchable && searchRef.value) {
666
- searchRef.value.focus();
667
- }
668
- if (value && optionsRef.value) {
669
- const index = computedOptions.value.findIndex(
670
- i => i.value === selected.value
671
- );
672
- if (index !== -1) {
673
- optionsRef.value[index].scrollIntoView({
674
- block: 'nearest'
675
- });
676
- selectedIndex.value = index;
677
- }
678
- }
679
-
680
- keyboardNavigation.value = false;
681
- });
682
- });
683
-
684
- const inputError = computed(() => {
685
- return formError.value || props.error;
686
- });
687
-
688
- const isOpen = computed(() => {
689
- if (
690
- props.multiple &&
691
- computedOptions.value.length == 0 &&
692
- !search.value
693
- ) {
694
- return false;
695
- }
696
- return open.value;
697
- });
698
-
699
- const focusSelect = (event: FocusEvent) => {
700
- setTimeout(() => {
701
- if (selectRef.value && !open.value) {
702
- openSelect();
703
- }
704
- }, 10);
705
- };
706
-
707
- const onSelectClick = (event: MouseEvent) => {
708
- event.preventDefault();
709
- event.stopPropagation();
710
- openSelect();
711
- };
712
-
713
- expose({
714
- focus,
715
- blur
716
- });
717
-
718
- return {
719
- container,
720
- optionsRef,
721
- searchRef,
722
- selectRef,
723
- popper,
724
- focusSelect,
725
- onSelectClick,
726
-
727
- id,
728
- isOpen,
729
- open,
730
- selected,
731
- selectedMax,
732
- selectedCountNotShown,
733
- computedOptions,
734
- selectedIndex,
735
- search,
736
-
737
- containerComputedClasses,
738
- selectComputedClasses,
739
- computedWrapperClasses,
740
- computedConsistentWidth,
741
-
742
- getSelectedLabel,
743
- openSelect,
744
- removeSelected,
745
- inputError,
746
- transitionName,
747
- inputListeners,
748
- style,
749
- focused,
750
- scale,
751
- toggleAllItems
752
- };
753
- }
754
- });
755
- </script>
1
+ <template>
2
+ <div ref="selectRef" tabindex="-1">
3
+ <label v-if="label" :for="id" :class="style.label" :id="`${id}-label`">
4
+ {{ label }}
5
+ </label>
6
+ <div v-if="description" :class="style.description">
7
+ {{ description }}
8
+ </div>
9
+
10
+ <div :class="computedWrapperClasses" v-on="inputListeners" :id="id" ref="container"
11
+ :aria-disabled="disabled ? 'true' : 'false'" :aria-labelledby="`${id}-label`" tabindex="0"
12
+ @mousedown.prevent="onSelectClick" @focusin.stop="focusSelect">
13
+ <div :class="selectComputedClasses">
14
+ <template v-if="
15
+ multiple &&
16
+ Array.isArray(selected) &&
17
+ selected.length > 0
18
+ ">
19
+ <MTag v-for="value in selectedMax" :key="value" :closeable="true"
20
+ @close="event => removeSelected(event, value)" :size="size">
21
+ {{ getSelectedLabel(value) }}
22
+ </MTag>
23
+ <template v-if="selectedCountNotShown > 0">
24
+ <MTag :size="size">
25
+ +{{ selectedCountNotShown }} more
26
+ </MTag>
27
+ </template>
28
+ </template>
29
+ <template v-else-if="!multiple && selected">
30
+ {{ getSelectedLabel(selected) }}
31
+ </template>
32
+ <template v-else>
33
+ <div :class="style.placeholder">{{ placeholder }}</div>
34
+ </template>
35
+ </div>
36
+
37
+ <Teleport :to="'body'">
38
+ <Transition :enter-active-class="scale.enterActive" :leave-active-class="scale.leaveActive"
39
+ :enter-from-class="scale.enterFrom" :leave-to-class="scale.leaveTo">
40
+ <div :class="style.menu" v-if="isOpen" ref="popper" :style="computedConsistentWidth"
41
+ :aria-hidden="isOpen ? 'false' : 'true'">
42
+ <div v-if="searchable || (multiple && selectAll)" :class="style.search">
43
+ <div v-if="multiple && selectAll" :class="style.checkAll">
44
+ <button role="button" type="button" :class="{
45
+ [style.checkAllButton]: true,
46
+ [style.checkAllButtonActive]:
47
+ computedOptions.length > 0 &&
48
+ computedOptions.length ===
49
+ selected.length
50
+ }" @click.prevent="toggleAllItems" aria-description="Select all items">
51
+ <svg v-if="
52
+ computedOptions.length > 0 &&
53
+ computedOptions.length ===
54
+ selected.length
55
+ " xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor"
56
+ aria-hidden="true">
57
+ <path fill-rule="evenodd"
58
+ d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
59
+ clip-rule="evenodd"></path>
60
+ </svg>
61
+ </button>
62
+ </div>
63
+ <input v-if="searchable" type="text" ref="searchRef" v-model="search" placeholder="Search"
64
+ :class="style.searchInput" />
65
+ </div>
66
+ <div :class="style.options">
67
+ <div v-for="(option, index) in computedOptions" :key="index" ref="optionsRef" :class="{
68
+ [style.option]: true,
69
+ [style.optionSelected]: option.active,
70
+ [style.optionFocused]:
71
+ selectedIndex === index
72
+ }" @click.prevent="option.onClick" @hover="selectedIndex = index">
73
+ <span :class="style.optionIcon">
74
+ <svg v-if="option.active" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
75
+ fill="currentColor" aria-hidden="true">
76
+ <path fill-rule="evenodd"
77
+ d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
78
+ clip-rule="evenodd"></path>
79
+ </svg>
80
+ </span>
81
+ <span :class="style.optionLabel">{{
82
+ option.label
83
+ }}</span>
84
+ </div>
85
+ <template v-if="computedOptions.length === 0">
86
+ <div :class="style.noResults">No results</div>
87
+ </template>
88
+ </div>
89
+ </div>
90
+ </Transition>
91
+ </Teleport>
92
+ </div>
93
+ <div v-if="inputError" :class="style.errorMessage">
94
+ {{ inputError }}
95
+ </div>
96
+ </div>
97
+ </template>
98
+
99
+ <script lang="ts">
100
+ import { useInput } from '../../composables/useInput';
101
+ import { usePopper, type Placement } from '../../composables/usePopper';
102
+ import type { MaybeArray, Size } from '../../types/props';
103
+ import { onClickOutside, useEventListener } from '@vueuse/core';
104
+ import {
105
+ computed,
106
+ defineComponent,
107
+ nextTick,
108
+ ref,
109
+ watch,
110
+ watchEffect,
111
+ type PropType
112
+ } from 'vue';
113
+ import MTag from '../tag/Tag.vue';
114
+ import * as style from './style';
115
+ import { scale } from '../../styles/transitions.css';
116
+ import { useInteractive } from '../../composables/useInteractive';
117
+ import type { OnUpdateValue, OnUpdateValueImpl } from './types';
118
+ import { call } from "../../utils/call";
119
+
120
+ export default defineComponent({
121
+ name: 'MSelect',
122
+ emits: {
123
+ ...useInput.emits()
124
+ },
125
+ props: {
126
+ ...useInput.props(),
127
+ ...useInteractive.props(),
128
+ label: {
129
+ type: String
130
+ },
131
+ placeholder: {
132
+ type: String,
133
+ default: 'Select an option'
134
+ },
135
+ options: {
136
+ type: Array as PropType<Array<any>>,
137
+ required: true
138
+ },
139
+ searchable: {
140
+ type: Boolean,
141
+ default: false
142
+ },
143
+ multiple: {
144
+ type: Boolean,
145
+ default: false
146
+ },
147
+ size: {
148
+ type: String as PropType<Size>,
149
+ default: 'md'
150
+ },
151
+ description: {
152
+ type: String,
153
+ default: ''
154
+ },
155
+ optionValue: {
156
+ type: String,
157
+ default: 'value'
158
+ },
159
+ optionLabel: {
160
+ type: String,
161
+ default: 'label'
162
+ },
163
+ transitionName: {
164
+ type: String,
165
+ default: 'fade'
166
+ },
167
+ consistentWidth: {
168
+ type: Boolean,
169
+ default: true
170
+ },
171
+ disabled: {
172
+ type: Boolean,
173
+ default: false
174
+ },
175
+ loading: {
176
+ type: Boolean,
177
+ default: false
178
+ },
179
+ error: {
180
+ type: String
181
+ },
182
+ allowBlank: {
183
+ type: Boolean,
184
+ default: false
185
+ },
186
+ selectAll: {
187
+ type: Boolean,
188
+ default: false
189
+ },
190
+ max: {
191
+ type: Number,
192
+ default: 5
193
+ },
194
+ 'onUpdate:value': [Function, Array] as PropType<MaybeArray<OnUpdateValue> | undefined>,
195
+ onUpdateValue: [Function, Array] as PropType<MaybeArray<OnUpdateValue> | undefined>,
196
+ },
197
+ components: {
198
+ MTag
199
+ },
200
+ setup(props, { emit, expose }) {
201
+ const container = ref<HTMLElement | null>(null);
202
+ const wrapper = ref<HTMLElement>();
203
+ const optionsRef = ref<HTMLElement[]>();
204
+ const searchRef = ref<HTMLElement>();
205
+ const selectRef = ref<HTMLElement>();
206
+ const selectedIndex = ref<number | undefined>(0);
207
+ const open = ref<boolean>(false);
208
+ const focused = ref<boolean>(false);
209
+ const search = ref<string>('');
210
+ const placement = ref<Placement>('bottom-start');
211
+ const transfer = ref(false);
212
+ const width = ref<number>(0);
213
+ const keyboardNavigation = ref<boolean>(false);
214
+
215
+ function doUpdateValue(
216
+ value: string | number | Array<string | number> | null,
217
+ option: object | null
218
+ ): void {
219
+ const {
220
+ 'onUpdate:value': _onUpdateValue,
221
+ onUpdateValue
222
+ } = props;
223
+
224
+ if (onUpdateValue) call(onUpdateValue as OnUpdateValueImpl, value, option)
225
+ if (_onUpdateValue) {
226
+ call(_onUpdateValue as OnUpdateValueImpl, value, option)
227
+ }
228
+ }
229
+
230
+
231
+ const { focus, blur } = useInteractive(container);
232
+ // TODO: Add debounce on keyboard movement (hover flicks it back up)
233
+ const {
234
+ id,
235
+ insideForm,
236
+ inputListeners,
237
+ formError,
238
+ formDisabled,
239
+ formLoading
240
+ } = useInput(props, { focus, emit, withListeners: false });
241
+
242
+ const { popper, updatePopper } = usePopper({
243
+ placement,
244
+ transfer,
245
+ reference: container,
246
+ wrapper,
247
+ offset: [0, 5]
248
+ });
249
+
250
+ const disabled = computed(() => formDisabled || props.disabled);
251
+ const loading = computed(() => formLoading || props.loading);
252
+
253
+ const selected = computed<any | any[]>({
254
+ get() {
255
+ if (props.multiple) {
256
+ if (!props.modelValue) {
257
+ return [];
258
+ }
259
+
260
+ if (Array.isArray(props.modelValue)) {
261
+ return props.modelValue;
262
+ }
263
+
264
+ return [props.modelValue];
265
+ }
266
+
267
+ return props.modelValue || null;
268
+ },
269
+ set(value: string | number | []) {
270
+ emit('update:modelValue', value);
271
+ doUpdateValue(value, null);
272
+ }
273
+ });
274
+
275
+ const selectedMax = computed(() => {
276
+ if (props.max <= 0) {
277
+ return selected.value;
278
+ }
279
+
280
+ if (
281
+ props.multiple &&
282
+ Array.isArray(selected.value) &&
283
+ selected.value.length > props.max
284
+ ) {
285
+ return selected.value.slice(0, props.max);
286
+ }
287
+
288
+ return selected.value;
289
+ });
290
+
291
+ const selectedCountNotShown = computed(() => {
292
+ if (
293
+ props.multiple &&
294
+ Array.isArray(selected.value) &&
295
+ props.max > 0
296
+ ) {
297
+ return selected.value.length - props.max;
298
+ }
299
+
300
+ return 0;
301
+ });
302
+
303
+ const computedConsistentWidth = computed(() => {
304
+ if (props.consistentWidth) {
305
+ return {
306
+ width: width.value + 'px'
307
+ };
308
+ }
309
+
310
+ return {};
311
+ });
312
+
313
+ const computedOptions = computed(() => {
314
+ if (!props.options || props.options.length == 0) {
315
+ return [];
316
+ }
317
+
318
+ // Filter options by search
319
+ if (props.searchable && search.value) {
320
+ let options = props.options
321
+ .filter(option => {
322
+ return option[props.optionLabel]
323
+ .toLowerCase()
324
+ .includes(search.value.toLowerCase());
325
+ })
326
+ .map(option => {
327
+ if (option[props.optionValue] === undefined) {
328
+ throw new Error('Option value is undefined');
329
+ }
330
+
331
+ if (option[props.optionLabel] === undefined) {
332
+ throw new Error('Option label is undefined');
333
+ }
334
+
335
+ let value = option[props.optionValue];
336
+ let active: boolean;
337
+ if (props.multiple && Array.isArray(selected.value)) {
338
+ active = selected.value.includes(value);
339
+ } else {
340
+ active = selected.value === value;
341
+ }
342
+
343
+ return {
344
+ value: value,
345
+ label: option[props.optionLabel],
346
+ active: active,
347
+ onClick: () => handleOptionClick(value)
348
+ };
349
+ });
350
+
351
+ if (props.allowBlank) {
352
+ options.unshift({
353
+ value: '',
354
+ label: '',
355
+ active: false,
356
+ onClick: () => handleOptionClick('')
357
+ });
358
+ }
359
+
360
+ return options;
361
+ }
362
+
363
+ let options = props.options.map(option => {
364
+ if (option[props.optionValue] === undefined) {
365
+ throw new Error('Option value is undefined');
366
+ }
367
+
368
+ if (option[props.optionLabel] === undefined) {
369
+ throw new Error('Option label is undefined');
370
+ }
371
+
372
+ let value = option[props.optionValue];
373
+ let active: boolean;
374
+ if (props.multiple && Array.isArray(selected.value)) {
375
+ active = selected.value.includes(value);
376
+ } else {
377
+ active = selected.value === value;
378
+ }
379
+
380
+ return {
381
+ value: value,
382
+ label: option[props.optionLabel],
383
+ active: active,
384
+ onClick: () => handleOptionClick(value)
385
+ };
386
+ });
387
+
388
+ if (props.allowBlank) {
389
+ options.unshift({
390
+ value: '',
391
+ label: '',
392
+ active: false,
393
+ onClick: () => handleOptionClick('')
394
+ });
395
+ }
396
+
397
+ return options;
398
+ });
399
+
400
+ function toggleAllItems() {
401
+ if (!props.multiple) {
402
+ return;
403
+ }
404
+
405
+ if (computedOptions.value.length === 0) {
406
+ return;
407
+ }
408
+
409
+ if (Array.isArray(selected.value)) {
410
+ if (selected.value.length === computedOptions.value.length) {
411
+ selected.value = [];
412
+ } else {
413
+ selected.value = computedOptions.value.map(
414
+ option => option.value
415
+ );
416
+ }
417
+ } else {
418
+ selected.value = [];
419
+ }
420
+ }
421
+
422
+ function openSelect() {
423
+ if (disabled.value || loading.value) {
424
+ return;
425
+ }
426
+
427
+ if (open.value) {
428
+ open.value = false;
429
+ return;
430
+ }
431
+ open.value = true;
432
+ }
433
+
434
+ function getSelectedIndex(
435
+ start: number | undefined,
436
+ direction = 'down'
437
+ ) {
438
+ if (typeof start === 'undefined') {
439
+ start = direction === 'down' ? -1 : 1;
440
+ }
441
+
442
+ if (direction === 'down') {
443
+ let next = start + 1;
444
+
445
+ if (next > computedOptions.value.length - 1) {
446
+ next = 0;
447
+ }
448
+
449
+ selectedIndex.value = next;
450
+ } else {
451
+ let next = start - 1;
452
+
453
+ if (next < 0) {
454
+ next = computedOptions.value.length - 1;
455
+ }
456
+
457
+ selectedIndex.value = next;
458
+ }
459
+ }
460
+
461
+ function handleOptionClick(value: string | number) {
462
+ const option = computedOptions.value.find(
463
+ item => item.value === value
464
+ );
465
+ if (!option) {
466
+ return;
467
+ }
468
+
469
+ if (props.multiple) {
470
+ if (Array.isArray(selected.value)) {
471
+ const index = selected.value.indexOf(value);
472
+
473
+ if (index !== -1) {
474
+ selected.value.splice(index, 1);
475
+ } else {
476
+ selected.value.push(value);
477
+ emit('update:modelValue', selected.value);
478
+ }
479
+ } else {
480
+ selected.value = [value];
481
+ }
482
+
483
+ updatePopper();
484
+ } else {
485
+ selected.value = value;
486
+ open.value = false;
487
+ }
488
+ }
489
+
490
+ function removeSelected(event: MouseEvent, value: string | number) {
491
+ event.preventDefault();
492
+ event.stopPropagation();
493
+ if (!props.multiple) {
494
+ return;
495
+ }
496
+
497
+ if (Array.isArray(selected.value)) {
498
+ const index = selected.value.indexOf(value);
499
+
500
+ if (index !== -1) {
501
+ selected.value.splice(index, 1);
502
+ }
503
+
504
+ updatePopper();
505
+ }
506
+ }
507
+
508
+ useEventListener('resize', () => {
509
+ open.value = false;
510
+ });
511
+
512
+ function handleKeyDown(e: Event) {
513
+ if (computedOptions.value.length === 0) {
514
+ return;
515
+ }
516
+
517
+ if (props.disabled) {
518
+ return;
519
+ }
520
+
521
+ keyboardNavigation.value = true;
522
+
523
+ const event: KeyboardEvent = e as KeyboardEvent;
524
+
525
+ switch (event.code) {
526
+ case 'Tab':
527
+ if (open.value) {
528
+ open.value = false;
529
+ }
530
+ break;
531
+ case 'ArrowDown':
532
+ e.preventDefault();
533
+ if (!open.value) {
534
+ open.value = true;
535
+ return;
536
+ }
537
+
538
+ getSelectedIndex(selectedIndex.value, 'down');
539
+ break;
540
+ case 'ArrowUp':
541
+ e.preventDefault();
542
+ if (!open.value) {
543
+ return;
544
+ }
545
+
546
+ getSelectedIndex(selectedIndex.value, 'up');
547
+ break;
548
+ case 'Enter':
549
+ e.preventDefault();
550
+ e.stopPropagation();
551
+
552
+ if (!open.value) {
553
+ open.value = true;
554
+ return;
555
+ }
556
+
557
+ if (typeof selectedIndex.value !== 'undefined') {
558
+ handleOptionClick(
559
+ computedOptions.value[selectedIndex.value].value
560
+ );
561
+ if (!props.multiple) {
562
+ open.value = false;
563
+ }
564
+ }
565
+ break;
566
+ case 'Escape':
567
+ e.preventDefault();
568
+ e.stopPropagation();
569
+
570
+ if (!open.value) {
571
+ return;
572
+ }
573
+
574
+ open.value = false;
575
+ break;
576
+ }
577
+ }
578
+
579
+ const transitionName = computed(() => {
580
+ return `m-transition__${props.transitionName}`;
581
+ });
582
+
583
+ const containerComputedClasses = computed(() => {
584
+ return [
585
+ 'm-select__container',
586
+ {
587
+ 'm-form__input': insideForm
588
+ }
589
+ ];
590
+ });
591
+
592
+ const computedWrapperClasses = computed(() => {
593
+ return [
594
+ style.wrapper,
595
+ {
596
+ [style.focused]: focused.value
597
+ }
598
+ ];
599
+ });
600
+
601
+ const selectComputedClasses = computed(() => {
602
+ return [
603
+ style.base,
604
+ {
605
+ [style.selectedTags]:
606
+ props.multiple &&
607
+ Array.isArray(selected.value) &&
608
+ selected.value.length > 0,
609
+ [style.focused]: open.value,
610
+ // [`m-select--${props.size}`]: props.size,
611
+ 'm-select--disabled': disabled.value || loading.value,
612
+ 'm-select--focused': open.value,
613
+ 'm-select--multiple': props.multiple,
614
+ 'm-select--error': formError.value
615
+ }
616
+ ];
617
+ });
618
+
619
+ function getSelectedLabel(value: string | number | []) {
620
+ const option = props.options.find(
621
+ i => i[props.optionValue] === value
622
+ );
623
+ if (!option) {
624
+ return null;
625
+ }
626
+
627
+ return option[props.optionLabel];
628
+ }
629
+
630
+ useEventListener(selectRef, 'keydown', handleKeyDown);
631
+ useEventListener(searchRef, 'keydown', handleKeyDown);
632
+
633
+ onClickOutside(
634
+ container,
635
+ () => {
636
+ open.value = false;
637
+ },
638
+ { ignore: [searchRef, popper] }
639
+ );
640
+
641
+ watchEffect(() => {
642
+ if (container.value && open.value) {
643
+ width.value = container.value.offsetWidth;
644
+ }
645
+ });
646
+
647
+ watch(selectedIndex, (index: number | undefined) => {
648
+ if (!keyboardNavigation.value) {
649
+ return;
650
+ }
651
+ if (typeof index !== 'undefined' && optionsRef.value) {
652
+ optionsRef.value[index].scrollIntoView({
653
+ block: 'nearest',
654
+ inline: 'nearest'
655
+ });
656
+ }
657
+ });
658
+
659
+ watch(open, (value: boolean) => {
660
+ nextTick(() => {
661
+ if (!value) {
662
+ search.value = '';
663
+ }
664
+
665
+ if (props.searchable && searchRef.value) {
666
+ searchRef.value.focus();
667
+ }
668
+ if (value && optionsRef.value) {
669
+ const index = computedOptions.value.findIndex(
670
+ i => i.value === selected.value
671
+ );
672
+ if (index !== -1) {
673
+ optionsRef.value[index].scrollIntoView({
674
+ block: 'nearest'
675
+ });
676
+ selectedIndex.value = index;
677
+ }
678
+ }
679
+
680
+ keyboardNavigation.value = false;
681
+ });
682
+ });
683
+
684
+ const inputError = computed(() => {
685
+ return formError.value || props.error;
686
+ });
687
+
688
+ const isOpen = computed(() => {
689
+ if (
690
+ props.multiple &&
691
+ computedOptions.value.length == 0 &&
692
+ !search.value
693
+ ) {
694
+ return false;
695
+ }
696
+ return open.value;
697
+ });
698
+
699
+ const focusSelect = (event: FocusEvent) => {
700
+ setTimeout(() => {
701
+ if (selectRef.value && !open.value) {
702
+ openSelect();
703
+ }
704
+ }, 10);
705
+ };
706
+
707
+ const onSelectClick = (event: MouseEvent) => {
708
+ event.preventDefault();
709
+ event.stopPropagation();
710
+ openSelect();
711
+ };
712
+
713
+ expose({
714
+ focus,
715
+ blur
716
+ });
717
+
718
+ return {
719
+ container,
720
+ optionsRef,
721
+ searchRef,
722
+ selectRef,
723
+ popper,
724
+ focusSelect,
725
+ onSelectClick,
726
+
727
+ id,
728
+ isOpen,
729
+ open,
730
+ selected,
731
+ selectedMax,
732
+ selectedCountNotShown,
733
+ computedOptions,
734
+ selectedIndex,
735
+ search,
736
+
737
+ containerComputedClasses,
738
+ selectComputedClasses,
739
+ computedWrapperClasses,
740
+ computedConsistentWidth,
741
+
742
+ getSelectedLabel,
743
+ openSelect,
744
+ removeSelected,
745
+ inputError,
746
+ transitionName,
747
+ inputListeners,
748
+ style,
749
+ focused,
750
+ scale,
751
+ toggleAllItems
752
+ };
753
+ }
754
+ });
755
+ </script>