goodteditor-ui 1.0.96 → 1.0.97

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "goodteditor-ui",
3
- "version": "1.0.96",
3
+ "version": "1.0.97",
4
4
  "main": "index.js",
5
5
  "homepage": "https://goodt-ui.netlify.app/",
6
6
  "scripts": {
@@ -0,0 +1,158 @@
1
+ <template>
2
+ <div class="form-control form-control-icon-both" :class="cssClass">
3
+ <i
4
+ class="icon mdi mdi-magnify"
5
+ :class="{
6
+ 'mdi-18px': size === '',
7
+ 'h-auto w-auto': size === 'small'
8
+ }"></i>
9
+ <input
10
+ class="input"
11
+ :class="inputCssClass"
12
+ type="text"
13
+ ref="input"
14
+ :value="value"
15
+ :disabled="disabled"
16
+ :placeholder="$tCap(placeholder)"
17
+ @input="onInput"
18
+ @change="onChange"
19
+ @keydown.esc="onKeydownEsc" />
20
+ <i
21
+ v-if="value.length"
22
+ class="icon mdi mdi-close cursor-pointer"
23
+ :class="{
24
+ 'mdi-18px': size === '',
25
+ 'h-auto w-auto': size === 'small'
26
+ }"
27
+ @click="onClickClear"></i>
28
+ </div>
29
+ </template>
30
+ <script>
31
+ const Size = {
32
+ NORMAL: { name: '', class: [] },
33
+ SMALL: { name: 'small', class: ['input-small'] }
34
+ };
35
+
36
+ export default {
37
+ props: {
38
+ /**
39
+ * @vmodel
40
+ */
41
+ value: {
42
+ type: String,
43
+ default: ''
44
+ },
45
+ /**
46
+ * @example Search something
47
+ */
48
+ placeholder: {
49
+ type: String,
50
+ default: ''
51
+ },
52
+ /**
53
+ * @default normal
54
+ * @values small,normal
55
+ */
56
+ size: {
57
+ type: String,
58
+ default: Size.NORMAL.name,
59
+ validator: (val) =>
60
+ Object.values(Size)
61
+ .map(({ name }) => name)
62
+ .includes(val)
63
+ },
64
+ disabled: {
65
+ type: Boolean,
66
+ default: false
67
+ },
68
+ embedded: {
69
+ type: Boolean,
70
+ default: false
71
+ }
72
+ },
73
+ computed: {
74
+ cssClass() {
75
+ const { embedded } = this;
76
+ if (embedded === false) {
77
+ return [];
78
+ }
79
+ return ['form-control--embedded'];
80
+ },
81
+ inputCssClass() {
82
+ const { size } = this;
83
+ return Object.values(Size).find(({ name }) => name === size)?.class;
84
+ }
85
+ },
86
+ methods: {
87
+ /**
88
+ * @param {string} val
89
+ */
90
+ emitInput(val = '') {
91
+ /**
92
+ * @property {string} value
93
+ */
94
+ this.$emit('input', val);
95
+ },
96
+ /**
97
+ * @param {string} val
98
+ */
99
+ emitChange(val = '') {
100
+ /**
101
+ * @property {string} value
102
+ */
103
+ this.$emit('change', val);
104
+ },
105
+ /**
106
+ * @public
107
+ * Clears the value
108
+ */
109
+ clear() {
110
+ this.emitInput();
111
+ this.emitChange();
112
+ },
113
+ /**
114
+ * @public
115
+ * Focuses the input
116
+ */
117
+ focus() {
118
+ this.$refs.input.focus();
119
+ },
120
+ /**
121
+ * @param {InputEvent} e
122
+ */
123
+ onInput({ target }) {
124
+ this.emitInput(target.value);
125
+ },
126
+ /**
127
+ * @param {InputEvent} e
128
+ */
129
+ onChange({ target }) {
130
+ this.emitChange(target.value);
131
+ },
132
+ /**
133
+ * @param {KeyboardEvent} e
134
+ */
135
+ onKeydownEsc(e) {
136
+ if (this.value.length > 0) {
137
+ e.stopPropagation();
138
+ this.clear();
139
+ }
140
+ },
141
+ /**
142
+ */
143
+ onClickClear() {
144
+ this.clear();
145
+ this.focus();
146
+ }
147
+ }
148
+ };
149
+ </script>
150
+ <style scoped lang="pcss">
151
+ .form-control--embedded .input {
152
+ border: none;
153
+ outline: none;
154
+ padding-top: 0;
155
+ padding-bottom: 0;
156
+ min-height: initial;
157
+ }
158
+ </style>
@@ -2,13 +2,26 @@
2
2
  <div
3
3
  class="ui-select form-elem"
4
4
  :class="cssClassExt"
5
- @keydown.prevent="onKeyDown"
5
+ @keydown="onKeyDown"
6
6
  @click="onClick"
7
7
  tabindex="0"
8
8
  :data-popover="popoverTargetId">
9
9
  <div class="ui-select-label u-select-none">
10
- <template v-if="multiple">
11
- <template v-if="optionsSelected.length">
10
+ <!--
11
+ @slot search slot
12
+ -->
13
+ <slot v-if="search && popoverShow" name="label-search">
14
+ <ui-searchbar
15
+ tabindex="1"
16
+ v-model="searchQuery"
17
+ :size="size"
18
+ embedded
19
+ class="ui-select-searchbar"
20
+ ref="searchbar"
21
+ @click.native.stop></ui-searchbar>
22
+ </slot>
23
+ <template v-else-if="multiple">
24
+ <template v-if="optionsSelected.length > 0">
12
25
  <!--
13
26
  @slot Label slot for multiple mode
14
27
  @binding {Object} option option
@@ -17,24 +30,32 @@
17
30
  @binding {Function} deselectOption deselects option function(option:Object)
18
31
  -->
19
32
  <slot
20
- name="label-multiple"
21
- v-for="(option, index) in optionsSelected"
33
+ name="label"
22
34
  v-bind="{
23
- option,
24
- value: getOptionValue(option),
25
- label: getOptionLabel(option),
26
- deselectOption
35
+ selected: optionsSelected,
36
+ deselectOption,
37
+ clear: clearOptions
27
38
  }">
28
- <ui-badge
29
- class="mar-none mar-right-2"
30
- :theme="selectedBadgeTheme"
31
- size="small"
32
- :key="index"
33
- :removable="isSelectedRemovable"
34
- @click.native.stop
35
- @remove="deselectOption(option)">
36
- <span>{{ getOptionLabel(option) }}</span>
37
- </ui-badge>
39
+ <slot
40
+ name="label-multiple"
41
+ v-for="(option, index) in optionsSelected"
42
+ v-bind="{
43
+ option,
44
+ value: getOptionValue(option),
45
+ label: getOptionLabel(option),
46
+ deselectOption
47
+ }">
48
+ <ui-badge
49
+ class="mar-none mar-right-2"
50
+ :theme="selectedBadgeTheme"
51
+ size="small"
52
+ :key="index"
53
+ :removable="isSelectedRemovable"
54
+ @click.native.stop
55
+ @remove="deselectOption(option)">
56
+ <span>{{ getOptionLabel(option) }}</span>
57
+ </ui-badge>
58
+ </slot>
38
59
  </slot>
39
60
  </template>
40
61
  <div class="ui-select-placeholder" v-else>
@@ -55,14 +76,15 @@
55
76
  @binding {String} label option's label
56
77
  -->
57
78
  <slot
79
+ v-if="optionsSelected.length > 0"
58
80
  name="label"
59
81
  v-bind="{
60
82
  option: optionsSelected[0],
61
83
  value: getOptionValue(optionsSelected[0]),
62
84
  label: getOptionLabel(optionsSelected[0]),
63
- index: getOptionIndex(optionsSelected[0])
64
- }"
65
- v-if="optionsSelected.length">
85
+ index: getOptionIndex(optionsSelected[0]),
86
+ clear: clearOptions
87
+ }">
66
88
  {{ getOptionLabel(optionsSelected[0]) }}
67
89
  </slot>
68
90
  <div class="ui-select-placeholder" v-else>
@@ -76,6 +98,21 @@
76
98
  </template>
77
99
  </div>
78
100
  <!--
101
+ @slot Reset state icon slot
102
+ -->
103
+ <slot
104
+ v-if="clear && optionsSelected.length > 0 && (search === false || popoverShow === false)"
105
+ name="icon-clear"
106
+ v-bind="{ clear: clearOptions }">
107
+ <i
108
+ class="icon icon-clear mdi mdi-close"
109
+ :class="{
110
+ 'mdi-18px': size === '',
111
+ 'h-auto w-auto': size === 'small'
112
+ }"
113
+ @click.stop="clearOptions"></i>
114
+ </slot>
115
+ <!--
79
116
  @slot Open state icon slot
80
117
  -->
81
118
  <slot name="icon-open" v-if="popoverShow">
@@ -100,10 +137,11 @@
100
137
 
101
138
  <ui-popover :show.sync="popoverShow" v-bind="popoverOptions">
102
139
  <ui-datalist
103
- class="w-100 pull-left"
140
+ class="ui-datalist w-100 pull-left"
141
+ :class="{ multiple }"
104
142
  @click.native.stop
105
143
  @select-option="onDatalistSelectOption"
106
- v-bind="{ size, options, class: datalistCssClass, maxHeight, maxItems, itemHeight }"
144
+ v-bind="{ options: optionsInternal, ...dropdownSettings }"
107
145
  :cursorIndex.sync="dataListCursorIndex"
108
146
  ref="datalist">
109
147
  <template #header>
@@ -111,6 +149,16 @@
111
149
  @slot Dropdown header slot
112
150
  -->
113
151
  <slot name="dropdown-header"></slot>
152
+ <slot name="search-empty" v-if="searchQuery.length > 0 && optionsInternal.length === 0">
153
+ <div class="text-center pad-h-l1 pad-v-l1">
154
+ <i
155
+ class="icon mdi mdi-information-off-outline color-grey"
156
+ :class="{
157
+ 'mdi-24px': size === '',
158
+ 'mdi-18px': size === 'small'
159
+ }"></i>
160
+ </div>
161
+ </slot>
114
162
  </template>
115
163
  <template #option="{ option, index, cursorIndex }">
116
164
  <!--
@@ -272,6 +320,10 @@
272
320
  flex: 1 0 0;
273
321
  }
274
322
 
323
+ .icon-clear {
324
+ cursor: pointer;
325
+ }
326
+
275
327
  &-placeholder {
276
328
  &-input {
277
329
  font-size: inherit;
@@ -283,6 +335,29 @@
283
335
  color: inherit;
284
336
  }
285
337
  }
338
+
339
+ &-searchbar {
340
+ margin-left: calc(-1 * var(--spacer3));
341
+ width: calc(100% + var(--spacer5));
342
+ }
343
+ }
344
+
345
+ .ui-datalist {
346
+ .active:hover {
347
+ background-color: var(--color-primary-hover);
348
+ }
349
+ &.multiple {
350
+ li + li {
351
+ border-top: 1px solid transparent;
352
+ }
353
+ .active + .active {
354
+ border-color: var(--color-primary-hover);
355
+ }
356
+ }
357
+ }
358
+
359
+ .popover-badges {
360
+ margin-top: -1px;
286
361
  }
287
362
  </style>
288
363
  <script>
@@ -291,16 +366,71 @@ import UiDatalist from './Datalist.vue';
291
366
  import UiPopover from './Popover.vue';
292
367
  import FormComponent from './utils/FormComponent';
293
368
  import WithPopover from './utils/WithPopover';
369
+ import UiSearchbar from './Searchbar.vue';
294
370
  import { Key } from './utils/Helpers';
295
371
 
372
+ /**
373
+ * @typedef {{
374
+ * valueField?: string,
375
+ * labelField?: string,
376
+ * disabledField?: string,
377
+ * valueObjects?: boolean,
378
+ * valueOfOptionChecker?: (option: { label: string, value: any } | Record<string, any>) => boolean
379
+ * }} OptionSettings
380
+ *
381
+ * @typedef {{
382
+ * class?: string|string[],
383
+ * maxHeight?: null|string,
384
+ * maxItems?: number,
385
+ * itemHeight?: null|string
386
+ * }} DropdownSettings
387
+ *
388
+ * @typedef {{
389
+ * pin?: boolean
390
+ * }} MultipleSettings
391
+ */
392
+
393
+ /**
394
+ * @type {DropdownSettings}
395
+ */
396
+ const DropdownSettingsDefault = {
397
+ class: '',
398
+ maxHeight: null,
399
+ maxItems: 5,
400
+ itemHeight: null
401
+ };
402
+
403
+ /**
404
+ * @type {OptionSettings}
405
+ */
406
+ const OptionSettingsDefault = {
407
+ valueField: 'value',
408
+ labelField: 'label',
409
+ disabledField: 'disabled',
410
+ valueObjects: false,
411
+ valueOfOptionChecker: null
412
+ };
413
+
414
+ /**
415
+ * @type {MultipleSettings}
416
+ */
417
+ const MultipleSettingsDefault = {
418
+ pin: false
419
+ };
420
+
296
421
  export default {
297
422
  components: {
298
423
  UiBadge,
299
424
  UiDatalist,
300
- UiPopover
425
+ UiPopover,
426
+ UiSearchbar
301
427
  },
302
428
  mixins: [FormComponent, WithPopover],
303
429
  props: {
430
+ /**
431
+ */
432
+ ...FormComponent.props,
433
+ ...WithPopover.props,
304
434
  /**
305
435
  * @model
306
436
  */
@@ -320,14 +450,51 @@ export default {
320
450
  },
321
451
  /**
322
452
  * Allow multiple selection
453
+ * @type {import('vue').PropOptions<boolean | MultipleSettings>}
323
454
  */
324
455
  multiple: {
456
+ type: [Boolean, Object],
457
+ default: false
458
+ },
459
+ /**
460
+ * Allow clear with cross (close) icon
461
+ */
462
+ clear: {
325
463
  type: Boolean,
326
464
  default: false
327
465
  },
466
+ /**
467
+ * Allow embedded search
468
+ * @type {import('vue').PropOptions<boolean | {
469
+ * clear?: boolean,
470
+ * }>}
471
+ */
472
+ search: {
473
+ type: [Boolean, Object],
474
+ default: false
475
+ },
476
+ /**
477
+ * Allow multiple selection
478
+ * @type {import('vue').PropOptions<OptionSettings>}
479
+ */
480
+ option: {
481
+ type: Object,
482
+ default() {
483
+ return {
484
+ /*
485
+ valueField: 'value',
486
+ labelField: 'label',
487
+ disabledField: '',
488
+ valueObjects: false,
489
+ valueOfOptionChecker: null
490
+ */
491
+ };
492
+ }
493
+ },
328
494
  /**
329
495
  * Defines whether 'value' is the option value field or option object
330
496
  * @see options
497
+ * @deprecated
331
498
  */
332
499
  valueObjects: {
333
500
  type: Boolean,
@@ -335,6 +502,7 @@ export default {
335
502
  },
336
503
  /**
337
504
  * Defines the 'value' field of the option Object
505
+ * @deprecated
338
506
  */
339
507
  valueField: {
340
508
  type: [String, Symbol],
@@ -342,6 +510,7 @@ export default {
342
510
  },
343
511
  /**
344
512
  * Defines the 'label' field of the option Object
513
+ * @deprecated
345
514
  */
346
515
  labelField: {
347
516
  type: [String, Symbol],
@@ -349,56 +518,67 @@ export default {
349
518
  },
350
519
  /**
351
520
  * Defines the 'disabled' field of the option Object
521
+ * @deprecated
352
522
  */
353
523
  disabledField: {
354
524
  type: [String, Symbol],
355
525
  default: 'disabled'
356
526
  },
357
- autoWidth: {
358
- default: true
359
- },
360
527
  /**
361
528
  * Alternative function to detect if option Object selected
362
529
  * by compare model value with option Object valueField value
530
+ * @deprecated
363
531
  */
364
532
  valueOfOptionChecker: {
365
533
  type: Function,
366
534
  default: null
367
535
  },
368
536
  // DATALIST OPTIONS
537
+ /**
538
+ * Dropdown extra config options
539
+ * @type {import('vue').PropOptions<DropdownSettings>}
540
+ */
541
+ datalist: {
542
+ /*
543
+ class: '',
544
+ maxHeight: null,
545
+ maxItems: 5,
546
+ itemHeight: null
547
+ */
548
+ },
369
549
  /**
370
550
  * Datalist css classes (optional)
551
+ * @deprecated
371
552
  */
372
553
  datalistCssClass: {
373
554
  type: [String, Array],
374
555
  default: ''
375
556
  },
557
+ // Popover Options
376
558
  /**
377
- * Defines the max-height of the dropdown list (value may be any css unit/expression)
559
+ * @type {import('vue').PropOptions<boolean>}
560
+ * @deprecated
378
561
  */
379
- maxHeight: {
380
- type: String,
381
- default: null
382
- },
383
- /**
384
- * Max visible items count
385
- */
386
- maxItems: {
387
- type: Number,
388
- default: 5
562
+ autoWidth: {
563
+ default: true
389
564
  },
390
565
  /**
391
- * Item height count
566
+ * @type {import('vue').PropOptions<import('./utils/WithPopover').PopoverOptions>}
392
567
  */
393
- itemHeight: {
394
- type: String,
395
- default: null
568
+ popover: {
569
+ type: Object,
570
+ default() {
571
+ return {
572
+ autoWidth: true
573
+ };
574
+ }
396
575
  }
397
576
  },
398
577
  data() {
399
578
  return {
400
579
  optionsSelected: [],
401
- dataListCursorIndex: -1
580
+ dataListCursorIndex: -1,
581
+ searchQuery: ''
402
582
  };
403
583
  },
404
584
  computed: {
@@ -417,6 +597,54 @@ export default {
417
597
  },
418
598
  isSelectedRemovable() {
419
599
  return this.readonly === false && this.disabled === false;
600
+ },
601
+ optionsInternal() {
602
+ let {
603
+ searchQuery,
604
+ options,
605
+ getOptionsOrdered,
606
+ getOptionLabel,
607
+ multiple: { pin = false }
608
+ } = this;
609
+ if (pin === true) {
610
+ options = getOptionsOrdered();
611
+ }
612
+
613
+ if (searchQuery === '') {
614
+ return options;
615
+ }
616
+ searchQuery = searchQuery.toLowerCase();
617
+ return options.filter((option) => getOptionLabel(option).toLowerCase().includes(searchQuery));
618
+ },
619
+ /**
620
+ * @return {OptionSettings}
621
+ */
622
+ optionSettings() {
623
+ const { labelField, labelValue, disabledField, labelObjects, valueOfOptionChecker, option } = this;
624
+ return {
625
+ ...OptionSettingsDefault,
626
+ labelField,
627
+ labelValue,
628
+ disabledField,
629
+ labelObjects,
630
+ valueOfOptionChecker,
631
+ ...option
632
+ };
633
+ },
634
+ /**
635
+ * @return {DropdownSettings & { size: string }}
636
+ */
637
+ dropdownSettings() {
638
+ const { datalistCssClass, maxItems, itemHeight, maxHeight, datalist, size } = this;
639
+ return {
640
+ ...DropdownSettingsDefault,
641
+ class: datalistCssClass,
642
+ maxItems,
643
+ itemHeight,
644
+ maxHeight,
645
+ size,
646
+ ...datalist
647
+ };
420
648
  }
421
649
  },
422
650
  watch: {
@@ -431,11 +659,15 @@ export default {
431
659
  },
432
660
  popoverShow(isPopoverShown) {
433
661
  this.$emit('options-toggle', isPopoverShown);
662
+ if (this.search !== false) {
663
+ this.handleSearchbar(isPopoverShown);
664
+ }
434
665
  }
435
666
  },
436
667
  methods: {
437
668
  isValueOfOptionDefault(option, modelItem) {
438
- const modelValue = this.valueObjects ? this.getOptionValue(modelItem) : modelItem;
669
+ const { valueObjects } = this.optionSettings;
670
+ const modelValue = valueObjects ? this.getOptionValue(modelItem) : modelItem;
439
671
  const optionValue = this.getOptionValue(option);
440
672
  return modelValue === optionValue;
441
673
  },
@@ -445,7 +677,10 @@ export default {
445
677
  * @return {boolean}
446
678
  */
447
679
  isValueOfOption(option, modelItem) {
448
- const { valueOfOptionChecker, isValueOfOptionDefault } = this;
680
+ const {
681
+ optionSettings: { valueOfOptionChecker },
682
+ isValueOfOptionDefault
683
+ } = this;
449
684
  const isTrue = valueOfOptionChecker ?? isValueOfOptionDefault;
450
685
  return isTrue(option, modelItem);
451
686
  },
@@ -467,10 +702,28 @@ export default {
467
702
  }
468
703
  },
469
704
  exportModel() {
470
- const model = this.optionsSelected.map((option) =>
471
- this.valueObjects ? option : this.getOptionValue(option)
472
- );
473
- return this.multiple ? model : model[0] ?? null;
705
+ const {
706
+ optionsSelected,
707
+ multiple,
708
+ optionSettings: { valueObjects }
709
+ } = this;
710
+ const model = optionsSelected.map((option) => (valueObjects ? option : this.getOptionValue(option)));
711
+ return multiple ? model : model[0] ?? null;
712
+ },
713
+ /**
714
+ * @return {Option[]}
715
+ */
716
+ getOptionsOrdered() {
717
+ let { options, optionsSelected, getOptionValue } = this;
718
+ if (optionsSelected.length === 0) {
719
+ return options;
720
+ }
721
+ const selectedValues = optionsSelected.map((option) => getOptionValue(option));
722
+ const restOptions = options.filter((option) => {
723
+ const value = this.getOptionValue(option);
724
+ return selectedValues.every((selectedValue) => selectedValue !== value);
725
+ });
726
+ return [...optionsSelected, ...restOptions];
474
727
  },
475
728
  /**
476
729
  *
@@ -490,23 +743,52 @@ export default {
490
743
  */
491
744
  this.$emit('change', value, { cancel: rollback });
492
745
  },
746
+ /**
747
+ * @param {Option} option
748
+ * @return {string}
749
+ */
493
750
  getOptionLabel(option) {
494
- let label = option ? option[this.labelField] : null;
751
+ const {
752
+ optionSettings: { labelField }
753
+ } = this;
754
+ let label = option ? option[labelField] : null;
495
755
  return label == null ? option : label;
496
756
  },
757
+ /**
758
+ * @param {Option} option
759
+ * @return {any}
760
+ */
497
761
  getOptionValue(option) {
498
- let value = option ? option[this.valueField] : null;
762
+ const {
763
+ optionSettings: { valueField }
764
+ } = this;
765
+ let value = option ? option[valueField] : null;
499
766
  // @NOTE option.value might be 'null'
500
767
  return value === undefined ? option : value;
501
768
  },
769
+ /**
770
+ * @param {Option} option
771
+ * @return {number}
772
+ */
502
773
  getOptionIndex(option) {
503
- return this.options.indexOf(option);
774
+ return this.optionsInternal.indexOf(option);
504
775
  },
776
+ /**
777
+ * @param {Option} option
778
+ * @return {boolean}
779
+ */
505
780
  isOptionSelected(option) {
506
781
  return this.optionsSelected.includes(option);
507
782
  },
783
+ /**
784
+ * @param {Option} option
785
+ * @return {boolean}
786
+ */
508
787
  isOptionDisabled(option) {
509
- let disabled = option ? option[this.disabledField] : null;
788
+ const {
789
+ optionSettings: { disabledField }
790
+ } = this;
791
+ let disabled = option ? option[disabledField] : null;
510
792
  return Boolean(disabled);
511
793
  },
512
794
  createOptionRollback() {
@@ -517,6 +799,9 @@ export default {
517
799
  this.triggerModelChange(rollback);
518
800
  };
519
801
  },
802
+ /**
803
+ * @param {Option} option
804
+ */
520
805
  selectOption(option) {
521
806
  if (this.readonly) {
522
807
  return;
@@ -535,34 +820,57 @@ export default {
535
820
 
536
821
  this.triggerModelChange(rollback);
537
822
  },
823
+ /**
824
+ * @param {Option} option
825
+ */
538
826
  deselectOption(option) {
539
- if (this.readonly) {
827
+ const { multiple, readonly, optionsSelected } = this;
828
+ if (readonly) {
540
829
  return;
541
830
  }
542
- if (!this.multiple) {
831
+ if (!multiple) {
543
832
  this.popoverShow = false;
544
833
  return;
545
834
  }
546
835
  const rollback = this.createOptionRollback();
547
- this.optionsSelected.splice(this.optionsSelected.indexOf(option), 1);
836
+ optionsSelected.splice(optionsSelected.indexOf(option), 1);
548
837
  this.triggerModelChange(rollback);
549
838
  },
839
+ /**
840
+ * @param {Option} option
841
+ */
550
842
  toggleOption(option) {
551
- if (!this.isOptionSelected(option)) {
552
- this.selectOption(option);
843
+ const { isOptionSelected, selectOption, deselectOption } = this;
844
+ if (!isOptionSelected(option)) {
845
+ selectOption(option);
553
846
  } else {
554
- this.deselectOption(option);
847
+ deselectOption(option);
555
848
  }
556
849
  },
850
+ clearOptions() {
851
+ const rollback = this.createOptionRollback();
852
+ this.optionsSelected = [];
853
+ this.triggerModelChange(rollback);
854
+ },
557
855
  getDatalistRef() {
558
856
  return this.$refs.datalist;
559
857
  },
560
- onClick(e) {
858
+ handleSearchbar(isPopoverShown) {
859
+ if (!isPopoverShown) {
860
+ this.searchQuery = '';
861
+ return;
862
+ }
863
+ this.$nextTick(() => {
864
+ this.$refs.searchbar.focus();
865
+ });
866
+ },
867
+ onClick() {
868
+ const { popoverShow, optionsSelected } = this;
561
869
  this.togglePopover();
562
870
  this.rootHasFocus = true;
563
871
  this.$el.focus();
564
- if (this.popoverShow && this.optionsSelected.length) {
565
- this.dataListCursorIndex = this.getOptionIndex(this.optionsSelected[0]);
872
+ if (popoverShow && optionsSelected.length > 0) {
873
+ this.dataListCursorIndex = this.getOptionIndex(optionsSelected[0]);
566
874
  }
567
875
  },
568
876
  onDatalistSelectOption({ option }) {
@@ -572,11 +880,13 @@ export default {
572
880
  let list = this.getDatalistRef();
573
881
  if (e.key === Key.ESC) {
574
882
  if (this.popoverShow) {
883
+ e.preventDefault();
575
884
  e.stopPropagation();
576
885
  this.popoverShow = false;
577
886
  return;
578
887
  }
579
888
  if (this.rootHasFocus) {
889
+ e.preventDefault();
580
890
  e.stopPropagation();
581
891
  this.rootHasFocus = false;
582
892
  this.$el.blur();
@@ -1,5 +1,23 @@
1
1
  import { nextId, Position } from './Helpers';
2
2
 
3
+ /**
4
+ * @typedef {{
5
+ * zIndex?: number,
6
+ * appendToBody?: boolean,
7
+ * position?: string,
8
+ * positionOffset?: [number, number],
9
+ * autoWidth?: boolean
10
+ * }} PopoverOptions
11
+ */
12
+
13
+ const PopoverOptionsDefault = {
14
+ zIndex: 1010,
15
+ appendToBody: true,
16
+ position: `${Position.BOTTOM}-${Position.START}`,
17
+ positionOffset: [1, 1],
18
+ autoWidth: false
19
+ };
20
+
3
21
  export default {
4
22
  props: {
5
23
  /**
@@ -42,6 +60,18 @@ export default {
42
60
  autoWidth: {
43
61
  type: Boolean,
44
62
  default: false,
63
+ },
64
+ popover: {
65
+ type: Object,
66
+ default: () => ({
67
+ /*
68
+ zIndex: -1,
69
+ appendToBody: true,
70
+ position: `${Position.BOTTOM}-${Position.START}`,
71
+ positionOffset: [1, 1],
72
+ autoWidth: false
73
+ */
74
+ }),
45
75
  }
46
76
  },
47
77
  data() {
@@ -55,21 +85,24 @@ export default {
55
85
  return `[data-popover=${this.popoverTargetId}]`;
56
86
  },
57
87
  popoverOptions() {
58
- let {
88
+ const {
59
89
  zIndex,
60
90
  appendToBody,
61
91
  position,
62
92
  positionOffset,
63
93
  autoWidth,
64
- popoverTarget: target
94
+ popoverTarget: target,
95
+ popover
65
96
  } = this;
66
97
  return {
98
+ ...PopoverOptionsDefault,
67
99
  zIndex,
68
100
  appendToBody,
69
101
  position,
70
102
  positionOffset,
71
103
  autoWidth,
72
- target
104
+ target,
105
+ ...popover
73
106
  };
74
107
  },
75
108
  },