goodteditor-ui 1.0.95 → 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.95",
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
  <!--
@@ -120,6 +168,7 @@
120
168
  @binding {String} label option's label
121
169
  @binding {Number} index option's index
122
170
  @binding {Boolean} isSelected option selection status
171
+ @binding {Boolean} isDisabled option availability status
123
172
  @binding {Number} cursorIndex current cursor index
124
173
  @binding {Function} selectOption function that selects option
125
174
  @binding {Function} deselectOption function that deselects option
@@ -134,6 +183,7 @@
134
183
  index,
135
184
  cursorIndex,
136
185
  isSelected: isOptionSelected(option),
186
+ isDisabled: isOptionDisabled(option),
137
187
  selectOption,
138
188
  deselectOption,
139
189
  toggleOption
@@ -145,6 +195,7 @@
145
195
  @binding {String} label option's label
146
196
  @binding {Number} index option's index
147
197
  @binding {Boolean} isSelected option selection status
198
+ @binding {Boolean} isDisabled option availability status
148
199
  @binding {Number} cursorIndex current cursor index
149
200
  @binding {Function} selectOption function that selects option
150
201
  @binding {Function} deselectOption function that deselects option
@@ -160,6 +211,7 @@
160
211
  label: getOptionLabel(option),
161
212
  index,
162
213
  isSelected: isOptionSelected(option),
214
+ isDisabled: isOptionDisabled(option),
163
215
  cursorIndex,
164
216
  selectOption,
165
217
  deselectOption,
@@ -171,7 +223,8 @@
171
223
  <li
172
224
  :class="{
173
225
  active: isOptionSelected(option),
174
- 'bg-grey-lighter': index == cursorIndex
226
+ 'bg-grey-lighter': index == cursorIndex,
227
+ 'events-none': isOptionDisabled(option)
175
228
  }"
176
229
  :key="index"
177
230
  :title="getOptionLabel(option)"
@@ -187,6 +240,7 @@
187
240
  label: getOptionLabel(option),
188
241
  index,
189
242
  isSelected: isOptionSelected(option),
243
+ isDisabled: isOptionDisabled(option),
190
244
  cursorIndex
191
245
  }">
192
246
  {{ getOptionLabel(option) }}
@@ -223,6 +277,7 @@
223
277
  @binding {String} label option's label
224
278
  @binding {Number} index option's index
225
279
  @binding {Boolean} isSelected option selection status
280
+ @binding {Boolean} isDisabled option availability status
226
281
  @binding {Number} cursorIndex current cursor index
227
282
  @binding {Function} selectOption function that selects option
228
283
  @binding {Function} deselectOption function that deselects option
@@ -236,6 +291,7 @@
236
291
  label: getOptionLabel(option),
237
292
  index,
238
293
  isSelected: isOptionSelected(option),
294
+ isDisabled: isOptionDisabled(option),
239
295
  cursorIndex,
240
296
  selectOption,
241
297
  deselectOption,
@@ -264,6 +320,10 @@
264
320
  flex: 1 0 0;
265
321
  }
266
322
 
323
+ .icon-clear {
324
+ cursor: pointer;
325
+ }
326
+
267
327
  &-placeholder {
268
328
  &-input {
269
329
  font-size: inherit;
@@ -275,6 +335,29 @@
275
335
  color: inherit;
276
336
  }
277
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;
278
361
  }
279
362
  </style>
280
363
  <script>
@@ -283,16 +366,71 @@ import UiDatalist from './Datalist.vue';
283
366
  import UiPopover from './Popover.vue';
284
367
  import FormComponent from './utils/FormComponent';
285
368
  import WithPopover from './utils/WithPopover';
369
+ import UiSearchbar from './Searchbar.vue';
286
370
  import { Key } from './utils/Helpers';
287
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
+
288
421
  export default {
289
422
  components: {
290
423
  UiBadge,
291
424
  UiDatalist,
292
- UiPopover
425
+ UiPopover,
426
+ UiSearchbar
293
427
  },
294
428
  mixins: [FormComponent, WithPopover],
295
429
  props: {
430
+ /**
431
+ */
432
+ ...FormComponent.props,
433
+ ...WithPopover.props,
296
434
  /**
297
435
  * @model
298
436
  */
@@ -312,14 +450,51 @@ export default {
312
450
  },
313
451
  /**
314
452
  * Allow multiple selection
453
+ * @type {import('vue').PropOptions<boolean | MultipleSettings>}
315
454
  */
316
455
  multiple: {
456
+ type: [Boolean, Object],
457
+ default: false
458
+ },
459
+ /**
460
+ * Allow clear with cross (close) icon
461
+ */
462
+ clear: {
317
463
  type: Boolean,
318
464
  default: false
319
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
+ },
320
494
  /**
321
495
  * Defines whether 'value' is the option value field or option object
322
496
  * @see options
497
+ * @deprecated
323
498
  */
324
499
  valueObjects: {
325
500
  type: Boolean,
@@ -327,6 +502,7 @@ export default {
327
502
  },
328
503
  /**
329
504
  * Defines the 'value' field of the option Object
505
+ * @deprecated
330
506
  */
331
507
  valueField: {
332
508
  type: [String, Symbol],
@@ -334,56 +510,75 @@ export default {
334
510
  },
335
511
  /**
336
512
  * Defines the 'label' field of the option Object
513
+ * @deprecated
337
514
  */
338
515
  labelField: {
339
516
  type: [String, Symbol],
340
517
  default: 'label'
341
518
  },
342
- autoWidth: {
343
- default: true
519
+ /**
520
+ * Defines the 'disabled' field of the option Object
521
+ * @deprecated
522
+ */
523
+ disabledField: {
524
+ type: [String, Symbol],
525
+ default: 'disabled'
344
526
  },
345
527
  /**
346
528
  * Alternative function to detect if option Object selected
347
529
  * by compare model value with option Object valueField value
530
+ * @deprecated
348
531
  */
349
532
  valueOfOptionChecker: {
350
533
  type: Function,
351
534
  default: null
352
535
  },
353
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
+ },
354
549
  /**
355
550
  * Datalist css classes (optional)
551
+ * @deprecated
356
552
  */
357
553
  datalistCssClass: {
358
554
  type: [String, Array],
359
555
  default: ''
360
556
  },
557
+ // Popover Options
361
558
  /**
362
- * Defines the max-height of the dropdown list (value may be any css unit/expression)
559
+ * @type {import('vue').PropOptions<boolean>}
560
+ * @deprecated
363
561
  */
364
- maxHeight: {
365
- type: String,
366
- default: null
367
- },
368
- /**
369
- * Max visible items count
370
- */
371
- maxItems: {
372
- type: Number,
373
- default: 5
562
+ autoWidth: {
563
+ default: true
374
564
  },
375
565
  /**
376
- * Item height count
566
+ * @type {import('vue').PropOptions<import('./utils/WithPopover').PopoverOptions>}
377
567
  */
378
- itemHeight: {
379
- type: String,
380
- default: null
568
+ popover: {
569
+ type: Object,
570
+ default() {
571
+ return {
572
+ autoWidth: true
573
+ };
574
+ }
381
575
  }
382
576
  },
383
577
  data() {
384
578
  return {
385
579
  optionsSelected: [],
386
- dataListCursorIndex: -1
580
+ dataListCursorIndex: -1,
581
+ searchQuery: ''
387
582
  };
388
583
  },
389
584
  computed: {
@@ -402,6 +597,54 @@ export default {
402
597
  },
403
598
  isSelectedRemovable() {
404
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
+ };
405
648
  }
406
649
  },
407
650
  watch: {
@@ -416,11 +659,15 @@ export default {
416
659
  },
417
660
  popoverShow(isPopoverShown) {
418
661
  this.$emit('options-toggle', isPopoverShown);
662
+ if (this.search !== false) {
663
+ this.handleSearchbar(isPopoverShown);
664
+ }
419
665
  }
420
666
  },
421
667
  methods: {
422
668
  isValueOfOptionDefault(option, modelItem) {
423
- const modelValue = this.valueObjects ? this.getOptionValue(modelItem) : modelItem;
669
+ const { valueObjects } = this.optionSettings;
670
+ const modelValue = valueObjects ? this.getOptionValue(modelItem) : modelItem;
424
671
  const optionValue = this.getOptionValue(option);
425
672
  return modelValue === optionValue;
426
673
  },
@@ -430,7 +677,10 @@ export default {
430
677
  * @return {boolean}
431
678
  */
432
679
  isValueOfOption(option, modelItem) {
433
- const { valueOfOptionChecker, isValueOfOptionDefault } = this;
680
+ const {
681
+ optionSettings: { valueOfOptionChecker },
682
+ isValueOfOptionDefault
683
+ } = this;
434
684
  const isTrue = valueOfOptionChecker ?? isValueOfOptionDefault;
435
685
  return isTrue(option, modelItem);
436
686
  },
@@ -452,10 +702,28 @@ export default {
452
702
  }
453
703
  },
454
704
  exportModel() {
455
- const model = this.optionsSelected.map((option) =>
456
- this.valueObjects ? option : this.getOptionValue(option)
457
- );
458
- 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];
459
727
  },
460
728
  /**
461
729
  *
@@ -475,21 +743,54 @@ export default {
475
743
  */
476
744
  this.$emit('change', value, { cancel: rollback });
477
745
  },
746
+ /**
747
+ * @param {Option} option
748
+ * @return {string}
749
+ */
478
750
  getOptionLabel(option) {
479
- let label = option ? option[this.labelField] : null;
751
+ const {
752
+ optionSettings: { labelField }
753
+ } = this;
754
+ let label = option ? option[labelField] : null;
480
755
  return label == null ? option : label;
481
756
  },
757
+ /**
758
+ * @param {Option} option
759
+ * @return {any}
760
+ */
482
761
  getOptionValue(option) {
483
- let value = option ? option[this.valueField] : null;
762
+ const {
763
+ optionSettings: { valueField }
764
+ } = this;
765
+ let value = option ? option[valueField] : null;
484
766
  // @NOTE option.value might be 'null'
485
767
  return value === undefined ? option : value;
486
768
  },
769
+ /**
770
+ * @param {Option} option
771
+ * @return {number}
772
+ */
487
773
  getOptionIndex(option) {
488
- return this.options.indexOf(option);
774
+ return this.optionsInternal.indexOf(option);
489
775
  },
776
+ /**
777
+ * @param {Option} option
778
+ * @return {boolean}
779
+ */
490
780
  isOptionSelected(option) {
491
781
  return this.optionsSelected.includes(option);
492
782
  },
783
+ /**
784
+ * @param {Option} option
785
+ * @return {boolean}
786
+ */
787
+ isOptionDisabled(option) {
788
+ const {
789
+ optionSettings: { disabledField }
790
+ } = this;
791
+ let disabled = option ? option[disabledField] : null;
792
+ return Boolean(disabled);
793
+ },
493
794
  createOptionRollback() {
494
795
  const optionsSelected = [...this.optionsSelected];
495
796
  return () => {
@@ -498,6 +799,9 @@ export default {
498
799
  this.triggerModelChange(rollback);
499
800
  };
500
801
  },
802
+ /**
803
+ * @param {Option} option
804
+ */
501
805
  selectOption(option) {
502
806
  if (this.readonly) {
503
807
  return;
@@ -516,34 +820,57 @@ export default {
516
820
 
517
821
  this.triggerModelChange(rollback);
518
822
  },
823
+ /**
824
+ * @param {Option} option
825
+ */
519
826
  deselectOption(option) {
520
- if (this.readonly) {
827
+ const { multiple, readonly, optionsSelected } = this;
828
+ if (readonly) {
521
829
  return;
522
830
  }
523
- if (!this.multiple) {
831
+ if (!multiple) {
524
832
  this.popoverShow = false;
525
833
  return;
526
834
  }
527
835
  const rollback = this.createOptionRollback();
528
- this.optionsSelected.splice(this.optionsSelected.indexOf(option), 1);
836
+ optionsSelected.splice(optionsSelected.indexOf(option), 1);
529
837
  this.triggerModelChange(rollback);
530
838
  },
839
+ /**
840
+ * @param {Option} option
841
+ */
531
842
  toggleOption(option) {
532
- if (!this.isOptionSelected(option)) {
533
- this.selectOption(option);
843
+ const { isOptionSelected, selectOption, deselectOption } = this;
844
+ if (!isOptionSelected(option)) {
845
+ selectOption(option);
534
846
  } else {
535
- this.deselectOption(option);
847
+ deselectOption(option);
536
848
  }
537
849
  },
850
+ clearOptions() {
851
+ const rollback = this.createOptionRollback();
852
+ this.optionsSelected = [];
853
+ this.triggerModelChange(rollback);
854
+ },
538
855
  getDatalistRef() {
539
856
  return this.$refs.datalist;
540
857
  },
541
- 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;
542
869
  this.togglePopover();
543
870
  this.rootHasFocus = true;
544
871
  this.$el.focus();
545
- if (this.popoverShow && this.optionsSelected.length) {
546
- this.dataListCursorIndex = this.getOptionIndex(this.optionsSelected[0]);
872
+ if (popoverShow && optionsSelected.length > 0) {
873
+ this.dataListCursorIndex = this.getOptionIndex(optionsSelected[0]);
547
874
  }
548
875
  },
549
876
  onDatalistSelectOption({ option }) {
@@ -553,11 +880,13 @@ export default {
553
880
  let list = this.getDatalistRef();
554
881
  if (e.key === Key.ESC) {
555
882
  if (this.popoverShow) {
883
+ e.preventDefault();
556
884
  e.stopPropagation();
557
885
  this.popoverShow = false;
558
886
  return;
559
887
  }
560
888
  if (this.rootHasFocus) {
889
+ e.preventDefault();
561
890
  e.stopPropagation();
562
891
  this.rootHasFocus = false;
563
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
  },