@teammodlr/ui-kit 2.1.27 → 2.1.28

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.
@@ -7,19 +7,29 @@
7
7
  {{ description }}
8
8
  </div>
9
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
- @focusin="focusSelect">
13
-
10
+ <div
11
+ :class="computedWrapperClasses"
12
+ v-on="inputListeners"
13
+ :id="id"
14
+ ref="container"
15
+ :aria-disabled="disabled ? 'true' : 'false'"
16
+ :aria-labelledby="`${id}-label`"
17
+ tabindex="0"
18
+ @focusin="focusSelect"
19
+ @click="onSelectClick">
14
20
  <div :class="selectComputedClasses">
15
- <template v-if="multiple && Array.isArray(selected) && selected.length > 0">
21
+ <template
22
+ v-if="
23
+ multiple &&
24
+ Array.isArray(selected) &&
25
+ selected.length > 0
26
+ ">
16
27
  <MTag
17
28
  v-for="value in selectedMax"
18
29
  :key="value"
19
30
  :closeable="true"
20
- @close="removeSelected(value)"
21
- :size="size"
22
- >
31
+ @close="removeSelected($event, value)"
32
+ :size="size">
23
33
  {{ getSelectedLabel(value) }}
24
34
  </MTag>
25
35
  <template v-if="selectedCountNotShown > 0">
@@ -41,46 +51,88 @@
41
51
  :enter-active-class="scale.enterActive"
42
52
  :leave-active-class="scale.leaveActive"
43
53
  :enter-from-class="scale.enterFrom"
44
- :leave-to-class="scale.leaveTo"
45
- >
46
- <div :class="style.menu" v-if="isOpen" ref="popper" :style="computedConsistentWidth"
47
- :aria-hidden="isOpen ? 'false' : 'true'">
48
- <div v-if="searchable || (multiple && selectAll)" :class="style.search">
49
- <div v-if="multiple && selectAll" :class="style.checkAll">
54
+ :leave-to-class="scale.leaveTo">
55
+ <div
56
+ :class="style.menu"
57
+ v-if="isOpen"
58
+ ref="popper"
59
+ :style="computedConsistentWidth"
60
+ :aria-hidden="isOpen ? 'false' : 'true'">
61
+ <div
62
+ v-if="searchable || (multiple && selectAll)"
63
+ :class="style.search">
64
+ <div
65
+ v-if="multiple && selectAll"
66
+ :class="style.checkAll">
50
67
  <button
51
68
  role="button"
52
69
  type="button"
53
70
  :class="{
54
71
  [style.checkAllButton]: true,
55
- [style.checkAllButtonActive]: computedOptions.length > 0 && computedOptions.length === selected.length
72
+ [style.checkAllButtonActive]:
73
+ computedOptions.length > 0 &&
74
+ computedOptions.length ===
75
+ selected.length
56
76
  }"
57
77
  @click.prevent="toggleAllItems"
58
- aria-description="Select all items"
59
- >
60
- <svg v-if="computedOptions.length > 0 && computedOptions.length === selected.length" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><path fill-rule="evenodd" 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" clip-rule="evenodd"></path></svg>
78
+ aria-description="Select all items">
79
+ <svg
80
+ v-if="
81
+ computedOptions.length > 0 &&
82
+ computedOptions.length ===
83
+ selected.length
84
+ "
85
+ xmlns="http://www.w3.org/2000/svg"
86
+ viewBox="0 0 20 20"
87
+ fill="currentColor"
88
+ aria-hidden="true">
89
+ <path
90
+ fill-rule="evenodd"
91
+ 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"
92
+ clip-rule="evenodd"></path>
93
+ </svg>
61
94
  </button>
62
95
  </div>
63
- <input v-if="searchable" type="text" ref="searchRef" v-model="search" placeholder="Search" :class="style.searchInput" />
96
+ <input
97
+ v-if="searchable"
98
+ type="text"
99
+ ref="searchRef"
100
+ v-model="search"
101
+ placeholder="Search"
102
+ :class="style.searchInput" />
64
103
  </div>
65
104
  <div :class="style.options">
66
- <div v-for="(option, index) in computedOptions" :key="index" ref="optionsRef"
67
- :class="{
68
- [style.option]: true,
69
- [style.optionSelected]: option.active,
70
- [style.optionFocused]: selectedIndex === index
71
- }"
72
- @click.prevent="option.onClick"
73
- @hover="selectedIndex = index"
74
- >
105
+ <div
106
+ v-for="(option, index) in computedOptions"
107
+ :key="index"
108
+ ref="optionsRef"
109
+ :class="{
110
+ [style.option]: true,
111
+ [style.optionSelected]: option.active,
112
+ [style.optionFocused]:
113
+ selectedIndex === index
114
+ }"
115
+ @click.prevent="option.onClick"
116
+ @hover="selectedIndex = index">
75
117
  <span :class="style.optionIcon">
76
- <svg v-if="option.active" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><path fill-rule="evenodd" 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" clip-rule="evenodd"></path></svg>
118
+ <svg
119
+ v-if="option.active"
120
+ xmlns="http://www.w3.org/2000/svg"
121
+ viewBox="0 0 20 20"
122
+ fill="currentColor"
123
+ aria-hidden="true">
124
+ <path
125
+ fill-rule="evenodd"
126
+ 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"
127
+ clip-rule="evenodd"></path>
128
+ </svg>
77
129
  </span>
78
- <span :class="style.optionLabel">{{ option.label }}</span>
130
+ <span :class="style.optionLabel">{{
131
+ option.label
132
+ }}</span>
79
133
  </div>
80
134
  <template v-if="computedOptions.length === 0">
81
- <div :class="style.noResults">
82
- No results
83
- </div>
135
+ <div :class="style.noResults">No results</div>
84
136
  </template>
85
137
  </div>
86
138
  </div>
@@ -94,10 +146,10 @@
94
146
  </template>
95
147
 
96
148
  <script lang="ts">
97
- import {useInput} from "../../composables/useInput";
98
- import {usePopper, type Placement} from "../../composables/usePopper";
99
- import type {Size} from "../../types/props";
100
- import {onClickOutside, useEventListener} from '@vueuse/core';
149
+ import { useInput } from '../../composables/useInput';
150
+ import { usePopper, type Placement } from '../../composables/usePopper';
151
+ import type { Size } from '../../types/props';
152
+ import { onClickOutside, useEventListener } from '@vueuse/core';
101
153
  import {
102
154
  computed,
103
155
  defineComponent,
@@ -106,11 +158,11 @@ import {
106
158
  watch,
107
159
  watchEffect,
108
160
  type PropType
109
- } from "vue";
110
- import MTag from "../tag/Tag.vue";
111
- import * as style from "./style";
112
- import { scale } from "../../styles/transitions.css";
113
- import {useInteractive} from "../../composables/useInteractive";
161
+ } from 'vue';
162
+ import MTag from '../tag/Tag.vue';
163
+ import * as style from './style';
164
+ import { scale } from '../../styles/transitions.css';
165
+ import { useInteractive } from '../../composables/useInteractive';
114
166
 
115
167
  export default defineComponent({
116
168
  name: 'MSelect',
@@ -139,7 +191,7 @@ export default defineComponent({
139
191
  },
140
192
  size: {
141
193
  type: String as PropType<Size>,
142
- default: 'md',
194
+ default: 'md'
143
195
  },
144
196
  description: {
145
197
  type: String,
@@ -170,7 +222,7 @@ export default defineComponent({
170
222
  default: false
171
223
  },
172
224
  error: {
173
- type: String,
225
+ type: String
174
226
  },
175
227
  allowBlank: {
176
228
  type: Boolean,
@@ -188,7 +240,7 @@ export default defineComponent({
188
240
  components: {
189
241
  MTag
190
242
  },
191
- setup(props, {emit, expose}) {
243
+ setup(props, { emit, expose }) {
192
244
  const container = ref<HTMLElement | null>(null);
193
245
  const wrapper = ref<HTMLElement>();
194
246
  const optionsRef = ref<HTMLElement[]>();
@@ -198,12 +250,12 @@ export default defineComponent({
198
250
  const open = ref<boolean>(false);
199
251
  const focused = ref<boolean>(false);
200
252
  const search = ref<string>('');
201
- const placement = ref<Placement>("bottom-start");
253
+ const placement = ref<Placement>('bottom-start');
202
254
  const transfer = ref(false);
203
255
  const width = ref<number>(0);
204
256
  const keyboardNavigation = ref<boolean>(false);
205
257
 
206
- const {focus, blur} = useInteractive(container);
258
+ const { focus, blur } = useInteractive(container);
207
259
  // TODO: Add debounce on keyboard movement (hover flicks it back up)
208
260
  const {
209
261
  id,
@@ -212,9 +264,9 @@ export default defineComponent({
212
264
  formError,
213
265
  formDisabled,
214
266
  formLoading
215
- } = useInput(props, {focus, emit, withListeners: false});
267
+ } = useInput(props, { focus, emit, withListeners: false });
216
268
 
217
- const {popper, updatePopper} = usePopper({
269
+ const { popper, updatePopper } = usePopper({
218
270
  placement,
219
271
  transfer,
220
272
  reference: container,
@@ -251,7 +303,11 @@ export default defineComponent({
251
303
  return selected.value;
252
304
  }
253
305
 
254
- if (props.multiple && Array.isArray(selected.value) && selected.value.length > props.max) {
306
+ if (
307
+ props.multiple &&
308
+ Array.isArray(selected.value) &&
309
+ selected.value.length > props.max
310
+ ) {
255
311
  return selected.value.slice(0, props.max);
256
312
  }
257
313
 
@@ -259,8 +315,11 @@ export default defineComponent({
259
315
  });
260
316
 
261
317
  const selectedCountNotShown = computed(() => {
262
-
263
- if (props.multiple && Array.isArray(selected.value) && props.max > 0) {
318
+ if (
319
+ props.multiple &&
320
+ Array.isArray(selected.value) &&
321
+ props.max > 0
322
+ ) {
264
323
  return selected.value.length - props.max;
265
324
  }
266
325
 
@@ -271,7 +330,7 @@ export default defineComponent({
271
330
  if (props.consistentWidth) {
272
331
  return {
273
332
  width: width.value + 'px'
274
- }
333
+ };
275
334
  }
276
335
 
277
336
  return {};
@@ -282,35 +341,38 @@ export default defineComponent({
282
341
  return [];
283
342
  }
284
343
 
285
-
286
344
  // Filter options by search
287
345
  if (props.searchable && search.value) {
288
- let options = props.options.filter((option) => {
289
- return option[props.optionLabel].toLowerCase().includes(search.value.toLowerCase());
290
- }).map((option) => {
291
- if (option[props.optionValue] === undefined) {
292
- throw new Error('Option value is undefined');
293
- }
346
+ let options = props.options
347
+ .filter(option => {
348
+ return option[props.optionLabel]
349
+ .toLowerCase()
350
+ .includes(search.value.toLowerCase());
351
+ })
352
+ .map(option => {
353
+ if (option[props.optionValue] === undefined) {
354
+ throw new Error('Option value is undefined');
355
+ }
294
356
 
295
- if (option[props.optionLabel] === undefined) {
296
- throw new Error('Option label is undefined');
297
- }
357
+ if (option[props.optionLabel] === undefined) {
358
+ throw new Error('Option label is undefined');
359
+ }
298
360
 
299
- let value = option[props.optionValue];
300
- let active: boolean;
301
- if (props.multiple && Array.isArray(selected.value)) {
302
- active = selected.value.includes(value);
303
- } else {
304
- active = selected.value === value;
305
- }
361
+ let value = option[props.optionValue];
362
+ let active: boolean;
363
+ if (props.multiple && Array.isArray(selected.value)) {
364
+ active = selected.value.includes(value);
365
+ } else {
366
+ active = selected.value === value;
367
+ }
306
368
 
307
- return {
308
- value: value,
309
- label: option[props.optionLabel],
310
- active: active,
311
- onClick: () => handleOptionClick(value)
312
- }
313
- });
369
+ return {
370
+ value: value,
371
+ label: option[props.optionLabel],
372
+ active: active,
373
+ onClick: () => handleOptionClick(value)
374
+ };
375
+ });
314
376
 
315
377
  if (props.allowBlank) {
316
378
  options.unshift({
@@ -324,7 +386,7 @@ export default defineComponent({
324
386
  return options;
325
387
  }
326
388
 
327
- let options = props.options.map((option) => {
389
+ let options = props.options.map(option => {
328
390
  if (option[props.optionValue] === undefined) {
329
391
  throw new Error('Option value is undefined');
330
392
  }
@@ -346,7 +408,7 @@ export default defineComponent({
346
408
  label: option[props.optionLabel],
347
409
  active: active,
348
410
  onClick: () => handleOptionClick(value)
349
- }
411
+ };
350
412
  });
351
413
 
352
414
  if (props.allowBlank) {
@@ -374,7 +436,9 @@ export default defineComponent({
374
436
  if (selected.value.length === computedOptions.value.length) {
375
437
  selected.value = [];
376
438
  } else {
377
- selected.value = computedOptions.value.map((option) => option.value);
439
+ selected.value = computedOptions.value.map(
440
+ option => option.value
441
+ );
378
442
  }
379
443
  } else {
380
444
  selected.value = [];
@@ -386,10 +450,18 @@ export default defineComponent({
386
450
  return;
387
451
  }
388
452
 
453
+ if (open.value) {
454
+ open.value = false;
455
+ return;
456
+ }
457
+
389
458
  open.value = true;
390
459
  }
391
460
 
392
- function getSelectedIndex(start: number | undefined, direction = 'down') {
461
+ function getSelectedIndex(
462
+ start: number | undefined,
463
+ direction = 'down'
464
+ ) {
393
465
  if (typeof start === 'undefined') {
394
466
  start = direction === 'down' ? -1 : 1;
395
467
  }
@@ -414,7 +486,9 @@ export default defineComponent({
414
486
  }
415
487
 
416
488
  function handleOptionClick(value: string | number) {
417
- const option = computedOptions.value.find((item) => item.value === value);
489
+ const option = computedOptions.value.find(
490
+ item => item.value === value
491
+ );
418
492
  if (!option) {
419
493
  return;
420
494
  }
@@ -440,7 +514,9 @@ export default defineComponent({
440
514
  }
441
515
  }
442
516
 
443
- function removeSelected(value: string | number) {
517
+ function removeSelected(event: MouseEvent, value: string | number) {
518
+ event.stopPropagation();
519
+ event.preventDefault();
444
520
  if (!props.multiple) {
445
521
  return;
446
522
  }
@@ -506,7 +582,9 @@ export default defineComponent({
506
582
  }
507
583
 
508
584
  if (typeof selectedIndex.value !== 'undefined') {
509
- handleOptionClick(computedOptions.value[selectedIndex.value].value);
585
+ handleOptionClick(
586
+ computedOptions.value[selectedIndex.value].value
587
+ );
510
588
  if (!props.multiple) {
511
589
  open.value = false;
512
590
  }
@@ -535,23 +613,26 @@ export default defineComponent({
535
613
  {
536
614
  'm-form__input': insideForm
537
615
  }
538
- ]
616
+ ];
539
617
  });
540
618
 
541
619
  const computedWrapperClasses = computed(() => {
542
620
  return [
543
621
  style.wrapper,
544
622
  {
545
- [style.focused]: focused.value,
623
+ [style.focused]: focused.value
546
624
  }
547
- ]
625
+ ];
548
626
  });
549
627
 
550
628
  const selectComputedClasses = computed(() => {
551
629
  return [
552
630
  style.base,
553
631
  {
554
- [style.selectedTags]: props.multiple && Array.isArray(selected.value) && selected.value.length > 0,
632
+ [style.selectedTags]:
633
+ props.multiple &&
634
+ Array.isArray(selected.value) &&
635
+ selected.value.length > 0,
555
636
  [style.focused]: open.value,
556
637
  // [`m-select--${props.size}`]: props.size,
557
638
  'm-select--disabled': disabled.value || loading.value,
@@ -559,11 +640,13 @@ export default defineComponent({
559
640
  'm-select--multiple': props.multiple,
560
641
  'm-select--error': formError.value
561
642
  }
562
- ]
643
+ ];
563
644
  });
564
645
 
565
646
  function getSelectedLabel(value: string | number | []) {
566
- const option = props.options.find((i) => i[props.optionValue] === value)
647
+ const option = props.options.find(
648
+ i => i[props.optionValue] === value
649
+ );
567
650
  if (!option) {
568
651
  return null;
569
652
  }
@@ -571,12 +654,16 @@ export default defineComponent({
571
654
  return option[props.optionLabel];
572
655
  }
573
656
 
574
- useEventListener(selectRef, 'keydown', handleKeyDown)
575
- useEventListener(searchRef, 'keydown', handleKeyDown)
657
+ useEventListener(selectRef, 'keydown', handleKeyDown);
658
+ useEventListener(searchRef, 'keydown', handleKeyDown);
576
659
 
577
- onClickOutside(container, () => {
578
- open.value = false;
579
- }, {ignore: [searchRef, popper]});
660
+ onClickOutside(
661
+ container,
662
+ () => {
663
+ open.value = false;
664
+ },
665
+ { ignore: [searchRef, popper] }
666
+ );
580
667
 
581
668
  watchEffect(() => {
582
669
  if (container.value && open.value) {
@@ -589,23 +676,30 @@ export default defineComponent({
589
676
  return;
590
677
  }
591
678
  if (typeof index !== 'undefined' && optionsRef.value) {
592
- optionsRef.value[index].scrollIntoView({block: 'nearest', inline: 'nearest'});
679
+ optionsRef.value[index].scrollIntoView({
680
+ block: 'nearest',
681
+ inline: 'nearest'
682
+ });
593
683
  }
594
- })
684
+ });
595
685
 
596
686
  watch(open, (value: boolean) => {
597
687
  nextTick(() => {
598
688
  if (!value) {
599
- search.value = "";
689
+ search.value = '';
600
690
  }
601
691
 
602
692
  if (props.searchable && searchRef.value) {
603
693
  searchRef.value.focus();
604
694
  }
605
695
  if (value && optionsRef.value) {
606
- const index = computedOptions.value.findIndex((i) => i.value === selected.value);
696
+ const index = computedOptions.value.findIndex(
697
+ i => i.value === selected.value
698
+ );
607
699
  if (index !== -1) {
608
- optionsRef.value[index].scrollIntoView({block: 'nearest'});
700
+ optionsRef.value[index].scrollIntoView({
701
+ block: 'nearest'
702
+ });
609
703
  selectedIndex.value = index;
610
704
  }
611
705
  }
@@ -619,22 +713,37 @@ export default defineComponent({
619
713
  });
620
714
 
621
715
  const isOpen = computed(() => {
622
- if (props.multiple && computedOptions.value.length == 0 && !search.value) {
716
+ if (
717
+ props.multiple &&
718
+ computedOptions.value.length == 0 &&
719
+ !search.value
720
+ ) {
623
721
  return false;
624
722
  }
625
723
  return open.value;
626
724
  });
627
725
 
628
- const focusSelect = () => {
629
- if (selectRef.value) {
630
- openSelect();
726
+ const focusSelect = (event: FocusEvent) => {
727
+ event.preventDefault();
728
+ event.stopPropagation();
729
+ if (event.relatedTarget != null && !isOpen.value) {
730
+ if (selectRef.value) {
731
+ openSelect();
732
+ }
631
733
  }
632
- }
734
+ };
735
+
736
+ const onSelectClick = (event: MouseEvent) => {
737
+ event.preventDefault();
738
+ event.stopPropagation();
739
+
740
+ openSelect();
741
+ };
633
742
 
634
743
  expose({
635
744
  focus,
636
745
  blur
637
- })
746
+ });
638
747
 
639
748
  return {
640
749
  container,
@@ -643,6 +752,7 @@ export default defineComponent({
643
752
  selectRef,
644
753
  popper,
645
754
  focusSelect,
755
+ onSelectClick,
646
756
 
647
757
  id,
648
758
  isOpen,
@@ -669,7 +779,7 @@ export default defineComponent({
669
779
  focused,
670
780
  scale,
671
781
  toggleAllItems
672
- }
782
+ };
673
783
  }
674
784
  });
675
785
  </script>
@@ -325,7 +325,8 @@ globalStyle(`${checkAllButtonActive} > svg`, {
325
325
  top: '1px',
326
326
  width: themeVars.space[4],
327
327
  height: themeVars.space[4],
328
- color: 'white'
328
+ color: 'white',
329
+ verticalAlign: 'top'
329
330
  });
330
331
 
331
332
  export const optionIcon = style({