@teammodlr/ui-kit 2.1.27 → 2.1.29

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
31
  @close="removeSelected(value)"
21
- :size="size"
22
- >
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
  }
@@ -506,7 +580,9 @@ export default defineComponent({
506
580
  }
507
581
 
508
582
  if (typeof selectedIndex.value !== 'undefined') {
509
- handleOptionClick(computedOptions.value[selectedIndex.value].value);
583
+ handleOptionClick(
584
+ computedOptions.value[selectedIndex.value].value
585
+ );
510
586
  if (!props.multiple) {
511
587
  open.value = false;
512
588
  }
@@ -535,23 +611,26 @@ export default defineComponent({
535
611
  {
536
612
  'm-form__input': insideForm
537
613
  }
538
- ]
614
+ ];
539
615
  });
540
616
 
541
617
  const computedWrapperClasses = computed(() => {
542
618
  return [
543
619
  style.wrapper,
544
620
  {
545
- [style.focused]: focused.value,
621
+ [style.focused]: focused.value
546
622
  }
547
- ]
623
+ ];
548
624
  });
549
625
 
550
626
  const selectComputedClasses = computed(() => {
551
627
  return [
552
628
  style.base,
553
629
  {
554
- [style.selectedTags]: props.multiple && Array.isArray(selected.value) && selected.value.length > 0,
630
+ [style.selectedTags]:
631
+ props.multiple &&
632
+ Array.isArray(selected.value) &&
633
+ selected.value.length > 0,
555
634
  [style.focused]: open.value,
556
635
  // [`m-select--${props.size}`]: props.size,
557
636
  'm-select--disabled': disabled.value || loading.value,
@@ -559,11 +638,13 @@ export default defineComponent({
559
638
  'm-select--multiple': props.multiple,
560
639
  'm-select--error': formError.value
561
640
  }
562
- ]
641
+ ];
563
642
  });
564
643
 
565
644
  function getSelectedLabel(value: string | number | []) {
566
- const option = props.options.find((i) => i[props.optionValue] === value)
645
+ const option = props.options.find(
646
+ i => i[props.optionValue] === value
647
+ );
567
648
  if (!option) {
568
649
  return null;
569
650
  }
@@ -571,12 +652,16 @@ export default defineComponent({
571
652
  return option[props.optionLabel];
572
653
  }
573
654
 
574
- useEventListener(selectRef, 'keydown', handleKeyDown)
575
- useEventListener(searchRef, 'keydown', handleKeyDown)
655
+ useEventListener(selectRef, 'keydown', handleKeyDown);
656
+ useEventListener(searchRef, 'keydown', handleKeyDown);
576
657
 
577
- onClickOutside(container, () => {
578
- open.value = false;
579
- }, {ignore: [searchRef, popper]});
658
+ onClickOutside(
659
+ container,
660
+ () => {
661
+ open.value = false;
662
+ },
663
+ { ignore: [searchRef, popper] }
664
+ );
580
665
 
581
666
  watchEffect(() => {
582
667
  if (container.value && open.value) {
@@ -589,23 +674,30 @@ export default defineComponent({
589
674
  return;
590
675
  }
591
676
  if (typeof index !== 'undefined' && optionsRef.value) {
592
- optionsRef.value[index].scrollIntoView({block: 'nearest', inline: 'nearest'});
677
+ optionsRef.value[index].scrollIntoView({
678
+ block: 'nearest',
679
+ inline: 'nearest'
680
+ });
593
681
  }
594
- })
682
+ });
595
683
 
596
684
  watch(open, (value: boolean) => {
597
685
  nextTick(() => {
598
686
  if (!value) {
599
- search.value = "";
687
+ search.value = '';
600
688
  }
601
689
 
602
690
  if (props.searchable && searchRef.value) {
603
691
  searchRef.value.focus();
604
692
  }
605
693
  if (value && optionsRef.value) {
606
- const index = computedOptions.value.findIndex((i) => i.value === selected.value);
694
+ const index = computedOptions.value.findIndex(
695
+ i => i.value === selected.value
696
+ );
607
697
  if (index !== -1) {
608
- optionsRef.value[index].scrollIntoView({block: 'nearest'});
698
+ optionsRef.value[index].scrollIntoView({
699
+ block: 'nearest'
700
+ });
609
701
  selectedIndex.value = index;
610
702
  }
611
703
  }
@@ -619,22 +711,37 @@ export default defineComponent({
619
711
  });
620
712
 
621
713
  const isOpen = computed(() => {
622
- if (props.multiple && computedOptions.value.length == 0 && !search.value) {
714
+ if (
715
+ props.multiple &&
716
+ computedOptions.value.length == 0 &&
717
+ !search.value
718
+ ) {
623
719
  return false;
624
720
  }
625
721
  return open.value;
626
722
  });
627
723
 
628
- const focusSelect = () => {
629
- if (selectRef.value) {
630
- openSelect();
724
+ const focusSelect = (event: FocusEvent) => {
725
+ event.preventDefault();
726
+ event.stopPropagation();
727
+ if (event.relatedTarget != null && !isOpen.value) {
728
+ if (selectRef.value) {
729
+ openSelect();
730
+ }
631
731
  }
632
- }
732
+ };
733
+
734
+ const onSelectClick = (event: MouseEvent) => {
735
+ event.preventDefault();
736
+ event.stopPropagation();
737
+
738
+ openSelect();
739
+ };
633
740
 
634
741
  expose({
635
742
  focus,
636
743
  blur
637
- })
744
+ });
638
745
 
639
746
  return {
640
747
  container,
@@ -643,6 +750,7 @@ export default defineComponent({
643
750
  selectRef,
644
751
  popper,
645
752
  focusSelect,
753
+ onSelectClick,
646
754
 
647
755
  id,
648
756
  isOpen,
@@ -669,7 +777,7 @@ export default defineComponent({
669
777
  focused,
670
778
  scale,
671
779
  toggleAllItems
672
- }
780
+ };
673
781
  }
674
782
  });
675
783
  </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({