@webitel/ui-sdk 2.0.32 → 2.1.0-1

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.
@@ -3,6 +3,7 @@
3
3
  :class="{
4
4
  'wt-select--disabled': disabled,
5
5
  'wt-select--invalid': invalid,
6
+ 'wt-select--multiple': multiple,
6
7
  'wt-select--clearable': clearable,
7
8
  'wt-select--loading': isLoading,
8
9
  }"
@@ -25,16 +26,21 @@
25
26
  :internal-search="!searchMethod"
26
27
  :label="trackBy ? optionLabel : null"
27
28
  :limit="1"
28
- :limit-text="(count) => `+${count}`"
29
29
  :loading="false"
30
+ :multiple="multiple"
30
31
  :options="selectOptions"
31
32
  :placeholder="placeholder || label"
32
33
  :track-by="trackBy"
33
34
  :value="selectValue"
34
35
  class="wt-select__select"
35
36
  v-bind="$attrs"
37
+ @close="isOpened = false"
38
+ @open="isOpened = true"
36
39
  v-on="listeners"
37
40
  >
41
+ <template v-if="!isOpened" v-slot:limit>
42
+ <wt-chip class="multiselect__limit">+{{ value.length - 1 }}</wt-chip>
43
+ </template>
38
44
 
39
45
  <!-- Slot that is used for all selected options (tags)-->
40
46
  <template slot="tag" slot-scope="{ option }">
@@ -55,18 +61,16 @@
55
61
  <!-- Slot for custom option template -->
56
62
  <template slot="option" slot-scope="{ option }">
57
63
  <slot name="option" v-bind="{ option, optionLabel }">
58
- <span>
59
- {{ getOptionLabel({ option, optionLabel }) }}
60
- </span>
64
+ {{ getOptionLabel({ option, optionLabel }) }}
61
65
  </slot>
62
66
  </template>
63
67
 
64
68
  <!-- Element for opening and closing the dropdown -->
65
- <template slot="caret" slot-scope="{ toggle }">
69
+ <template v-slot:caret="{ toggle }">
66
70
  <!-- @mousedown.native.prevent.stop="toggle": https://github.com/shentao/vue-multiselect/issues/1204#issuecomment-615114727 -->
67
71
  <wt-icon-btn
68
72
  :disabled="disabled"
69
- class="wt-select__arrow-caret"
73
+ class="multiselect__select"
70
74
  icon="arrow-down"
71
75
  @mousedown.native.prevent.stop="toggle"
72
76
  ></wt-icon-btn>
@@ -78,14 +82,14 @@
78
82
  v-if="clearable"
79
83
  :class="{ 'hidden': !isValue }"
80
84
  :disabled="disabled"
81
- class="wt-select__clear"
85
+ class="multiselect__clear"
82
86
  icon="close"
83
87
  @click="clearValue"
84
88
  ></wt-icon-btn>
85
89
  </template>
86
90
 
87
- <template slot="beforeList">
88
- <div v-show="isLoading" class="wt-select__loading-wrapper">
91
+ <template v-slot:beforeList>
92
+ <div v-show="isLoading" class="multiselect__loading-wrapper">
89
93
  <wt-loader size="sm"></wt-loader>
90
94
  </div>
91
95
  </template>
@@ -103,51 +107,15 @@
103
107
  </template>
104
108
 
105
109
  <script>
106
- import VueMultiselect from 'vue-multiselect';
107
- import { ObserveVisibility } from 'vue-observe-visibility';
108
- import validationMixin from '../../../mixins/validationMixin/validationMixin';
109
- import debounce from '../../../scripts/debounce';
110
- import isEmpty from '../../../scripts/isEmpty';
110
+ import multiselectMixin from './mixins/multiselectMixin';
111
111
 
112
112
  export default {
113
113
  name: 'wt-select',
114
- mixins: [validationMixin],
115
- directives: { ObserveVisibility },
116
- components: {
117
- VueMultiselect,
118
- },
114
+ mixins: [multiselectMixin],
119
115
  props: {
120
116
  value: {},
121
117
 
122
- options: {
123
- type: Array,
124
- default: () => [],
125
- },
126
-
127
- label: {
128
- type: String,
129
- default: '',
130
- },
131
-
132
- placeholder: {
133
- type: String,
134
- default: '',
135
- },
136
-
137
- trackBy: {
138
- type: String,
139
- default: 'id',
140
- },
141
-
142
- optionLabel: {
143
- type: String,
144
- },
145
-
146
- searchMethod: {
147
- type: Function,
148
- },
149
-
150
- disabled: {
118
+ multiple: {
151
119
  type: Boolean,
152
120
  default: false,
153
121
  },
@@ -156,108 +124,11 @@ export default {
156
124
  type: Boolean,
157
125
  default: true,
158
126
  },
159
-
160
- required: {
161
- type: Boolean,
162
- default: false,
163
- },
164
-
165
- allowEmpty: {
166
- type: Boolean,
167
- default: false,
168
- },
169
-
170
- labelProps: {
171
- type: Object,
172
- description: 'Object with props, passed down to wt-label as props',
173
- },
174
127
  },
175
-
176
128
  data: () => ({
177
- apiOptions: [],
178
- isLoading: false,
179
-
180
- searchParams: {
181
- search: '',
182
- page: 1,
183
- },
184
- searchHasNext: true,
129
+ isOpened: false,
185
130
  }),
186
- computed: {
187
- isApiMode() {
188
- return !!this.searchMethod;
189
- },
190
- showIntersectionObserver() {
191
- return this.isApiMode && !this.isLoading && this.apiOptions.length;
192
- },
193
- selectValue() {
194
- // vue-multiselect doesn't show placeholder if value is empty object
195
- return this.isValue ? this.value : '';
196
- },
197
-
198
- hasLabel() {
199
- return !!(this.label || this.$slots.label);
200
- },
201
-
202
- requiredLabel() {
203
- return this.required ? `${this.label}*` : this.label;
204
- },
205
-
206
- selectOptions() {
207
- return this.isApiMode ? this.apiOptions : this.options;
208
- },
209
-
210
- isValue() {
211
- return !isEmpty(this.value);
212
- },
213
- listeners() {
214
- return {
215
- ...this.$listeners,
216
- input: this.input,
217
- close: this.close,
218
- 'search-change': (search) => {
219
- this.$emit('search-change', search);
220
- this.handleSearchChange(search);
221
- },
222
- };
223
- },
224
- },
225
-
226
131
  methods: {
227
- getOptionLabel({ option, optionLabel }) {
228
- const defaultOptionLabel = 'name';
229
-
230
- if (optionLabel) return option[optionLabel];
231
- if (option.locale) {
232
- if (Array.isArray(option.locale)) return this.$tc(...option.locale);
233
- return this.$t(option.locale);
234
- }
235
- return option[defaultOptionLabel] || option;
236
- },
237
- handleSearchChange(search) {
238
- if (!this.isApiMode) return;
239
- this.isLoading = true;
240
- this.searchParams.page = 1;
241
- this.searchParams.search = search;
242
- this.fetchOptions();
243
- },
244
-
245
- handleAfterListIntersect(isVisible) {
246
- if (isVisible && this.searchHasNext) {
247
- this.isLoading = true;
248
- this.searchParams.page += 1;
249
- this.fetchOptions();
250
- }
251
- },
252
-
253
- async fetchOptions({ search, page } = this.searchParams) {
254
- if (!this.isApiMode) return;
255
- const { items, next } = await this.searchMethod({ search, page });
256
- this.apiOptions = this.searchParams.page === 1 ? items : this.apiOptions.concat(items);
257
- this.searchHasNext = next;
258
- this.isLoading = false;
259
- },
260
-
261
132
  clearValue() {
262
133
  let value = '';
263
134
  if (Array.isArray(this.value)) value = [];
@@ -265,26 +136,12 @@ export default {
265
136
  this.input(value);
266
137
  this.$emit('reset', value);
267
138
  },
268
-
269
- input(value) {
270
- this.$emit('input', value);
271
- },
272
-
273
- close() {
274
- this.$emit('closed');
275
- },
276
- },
277
-
278
- created() {
279
- this.fetchOptions();
280
- this.fetchOptions = debounce(this.fetchOptions, 500);
281
139
  },
282
140
  };
283
141
  </script>
284
142
 
285
143
  <style lang="scss" scoped>
286
- @import '~vue-multiselect/dist/vue-multiselect.min.css';
287
- @import '../../../css/components/atoms/wt-chip/wt-chip';
144
+ @import '../../../css/components/molecules/wt-select/multiselect';
288
145
 
289
146
  .wt-select {
290
147
  width: 100%;
@@ -293,267 +150,124 @@ export default {
293
150
 
294
151
  .wt-label {
295
152
  .wt-select:hover &,
296
- .wt-select:focus-within & {
153
+ .wt-select:focus-within &, {
297
154
  color: var(--form-label--hover-color);
298
155
  }
299
156
  }
300
157
 
301
- .multiselect {
302
- cursor: pointer;
303
-
304
- .wt-select__arrow-caret {
305
- position: absolute;
306
- z-index: var(--select-caret-z-index);
307
- top: var(--select-caret-top-pos);
308
- right: var(--select-caret-right-pos);
309
- transition: var(--transition);
310
- transform: rotate(0);
158
+ .wt-select--invalid,
159
+ .wt-select--invalid:hover {
160
+ .wt-label {
161
+ color: var(--false-color);
311
162
  }
163
+ }
312
164
 
313
- ::v-deep .wt-select__clear {
314
- position: absolute;
315
- z-index: var(--select-clear-z-index);
316
- top: var(--select-clear-top-pos);
317
- right: var(--select-clear-right-pos);
318
- transition: var(--transition);
319
- transform: rotate(0);
165
+ .multiselect ::v-deep {
166
+ .multiselect__custom-tag,
167
+ .multiselect__single-label {
168
+ // text overflow 3 dots
169
+ display: block;
170
+ overflow: hidden;
171
+ max-width: 100%;
172
+ white-space: nowrap;
173
+ text-overflow: ellipsis;
320
174
  }
175
+ }
321
176
 
322
- ::v-deep {
323
- .multiselect__tags {
324
- display: flex;
325
- align-items: center;
326
- box-sizing: border-box;
327
- min-height: var(--select-tags-min-height); // reset original 20px style
328
- padding: var(--select-tags-padding);
329
- color: var(--form-input-color);
330
- border: var(--select-tags-border);
331
- border-color: var(--form-border-color);
332
- border-radius: var(--border-radius);
333
-
334
- .multiselect__input,
335
- .multiselect__single {
336
- @extend %typo-body-1;
337
- @include wt-placeholder;
338
- min-height: var(--select-input-min-height); // reset original 20px style
339
- margin: 0;
340
- padding: 0;
341
- background: transparent;
342
- }
343
-
344
- .multiselect__input:focus {
345
- @include wt-placeholder('focus');
346
- }
347
-
348
- .multiselect__strong {
349
- @extend %typo-body-1;
350
- @extend .wt-chip;
351
- position: absolute;
352
- z-index: var(--select-chip-z-index);
353
- right: var(--select-chip-right-pos);
354
- min-height: var(--select-input-min-height); // reset original 20px style
355
- margin: 0;
356
- font-weight: normal;
357
-
358
- /* these before-after elements are fixing issue when chip is visually overflown by
359
- selected option name (yep, chip has z-index, but its background has 0.3 opacity)*/
360
- &:before {
361
- position: absolute;
362
- z-index: -2; // below chip itself
363
- top: 0;
364
- right: 0;
365
- bottom: 0;
366
- left: 0;
367
- content: '';
368
- background: var(--main-color);
369
- }
370
-
371
- &:after {
372
- @extend .wt-chip;
373
- position: absolute;
374
- z-index: -1; // below chip itself
375
- top: 0;
376
- right: 0;
377
- bottom: 0;
378
- left: 0;
379
- content: '';
380
-
381
- }
382
- }
383
-
384
- .multiselect__tags-wrap {
385
- width: 100%;
386
- }
387
-
388
- .multiselect__custom-tag, .multiselect__single-label {
389
- @extend %typo-body-1;
390
- display: block;
391
-
392
- // text overflow 3 dots
393
- overflow: hidden;
394
- white-space: nowrap;
395
- text-overflow: ellipsis;
396
- color: var(--form-input-color);
397
- }
398
-
399
- .multiselect__placeholder {
400
- @extend %wt-placeholder;
401
- overflow: hidden;
402
- margin: 0;
403
-
404
- // text overflow 3 dots
405
- padding: 0;
406
- white-space: nowrap;
407
- text-overflow: ellipsis;
408
- }
409
- }
410
-
411
- .multiselect__content-wrapper {
412
- @extend %wt-distant-scrollbar;
413
- transition: var(--transition);
414
- border: var(--select-tags-border);
415
- border-color: var(--form-border-color);
416
- border-radius: var(--border-radius);
417
-
418
- .multiselect__option {
419
- @extend %typo-body-1;
420
- padding: var(--select-options-padding);
421
-
422
- & > span {
423
- @extend %wt-placeholder;
424
- }
425
-
426
- &:after {
427
- display: none;
428
- }
429
-
430
- &--highlight {
431
- background: var(--т, еselect-option--hover-color);
432
-
433
- & > span {
434
- color: var(--form-input-color);
435
- }
436
- }
437
-
438
- &--selected {
439
- font-weight: normal;
440
- background: var(--select-option--active-color)
441
- }
442
- }
443
- }
444
-
445
- .wt-select__loading-wrapper {
446
- position: sticky;
447
- z-index: 1;
448
- top: 0;
449
- left: 0;
450
- display: flex;
451
- align-items: center;
452
- justify-content: center;
453
- width: 100%;
454
- height: 300px; // max dropdown panel height
455
- backdrop-filter: blur(4px);
456
- }
177
+ .multiselect--active ::v-deep {
178
+ .multiselect__tags-wrap,
179
+ .multiselect__strong {
180
+ display: none;
457
181
  }
182
+ }
458
183
 
459
- .wt-select:hover & {
460
- .wt-select__arrow-caret ::v-deep .wt-icon__icon {
461
- fill: var(--icon-color--hover);
462
- }
463
-
464
- ::v-deep .wt-select__clear .wt-icon__icon {
465
- fill: var(--icon-color--hover);
466
- }
184
+ // right padding setup
467
185
 
468
- ::v-deep {
469
- .multiselect__tags {
470
- border-color: var(--form-border--hover-color);
471
- }
186
+ // default case
187
+ .wt-select {
188
+ // all is fine
189
+ .multiselect ::v-deep {
190
+ .multiselect__tags {
191
+ padding: var(--input-padding) calc(
192
+ var(--input-padding)
193
+ + var(--icon-md-size)
194
+ + var(--select-caret-right-pos)
195
+ ) var(--input-padding) var(--input-padding);
472
196
  }
473
197
  }
198
+ }
474
199
 
475
- &--active {
476
- .wt-select__arrow-caret {
477
- transform: rotate(180deg);
478
- }
479
-
480
- .wt-select__arrow-caret ::v-deep .wt-icon__icon {
481
- fill: var(--icon-color--hover);
200
+ // only chip
201
+ .wt-select.wt-select--multiple:not(.wt-select--clearable) {
202
+ .multiselect ::v-deep {
203
+ $multiselect-limit-right-pos: calc(
204
+ var(--select-caret-right-pos) // caret offet from border
205
+ + var(--icon-md-size) // caret size
206
+ + var(--input-padding) // caret-to-chip offset
207
+ );
208
+ .multiselect__tags {
209
+ padding-right: calc(
210
+ $multiselect-limit-right-pos
211
+ + 50px // chip
212
+ + var(--input-padding) // chip-to-content offset
213
+ );
482
214
  }
483
-
484
- ::v-deep {
485
- .multiselect__tags {
486
- border-color: var(--form-border--active-color);
487
-
488
- .multiselect__tags-wrap,
489
- .multiselect__strong {
490
- display: none;
491
- }
492
-
493
- .multiselect__placeholder {
494
- @extend %wt-placeholder--focus;
495
- }
496
- }
215
+ .multiselect__limit {
216
+ right: $multiselect-limit-right-pos;
497
217
  }
498
218
  }
499
219
  }
500
220
 
501
- .wt-select--loading ::v-deep .multiselect__content-wrapper {
502
- overflow-y: hidden;
503
- }
504
-
505
- .wt-select--invalid,
506
- .wt-select--invalid:hover {
507
- .wt-label {
508
- color: var(--false-color);
509
- }
221
+ // only clearable
222
+ .wt-select.wt-select--clearable:not(.wt-select--multiple) {
223
+ .multiselect ::v-deep {
224
+ $multiselect-clear-right-pos: calc(
225
+ var(--select-caret-right-pos) // caret offet from border
226
+ + var(--icon-md-size) // caret size
227
+ + var(--input-padding) // caret-to-chip offset
228
+ );
229
+ .multiselect__tags {
230
+ padding-right: calc(
231
+ $multiselect-clear-right-pos
232
+ + var(--icon-md-size) // clear
233
+ + var(--input-padding) // clear-to-content offset
234
+ );
235
+ }
510
236
 
511
- .multiselect {
512
- ::v-deep {
513
- .multiselect__tags {
514
- border-color: var(--false-color);
515
- outline: none; // prevent outline overlapping false color
516
- }
237
+ .multiselect__clear {
238
+ right: $multiselect-clear-right-pos;
517
239
  }
518
240
  }
519
241
  }
520
242
 
521
- .wt-select--disabled {
522
- pointer-events: none;
523
-
524
- .multiselect--disabled {
525
- opacity: 1;
526
- background: none;
527
- }
528
-
529
- .wt-select__arrow-caret ::v-deep .wt-icon__icon,
530
- .wt-select__clear ::v-deep .wt-icon__icon {
531
- fill: var(--icon-color-disabled);
532
- }
243
+ .wt-select.wt-select--multiple.wt-select--clearable {
244
+ .multiselect ::v-deep {
245
+ $multiselect-clear-right-pos: calc(
246
+ var(--select-caret-right-pos)// caret offet from border
247
+ + var(--icon-md-size)// caret size
248
+ + var(--input-padding) // caret-to-chip offset
249
+ );
533
250
 
534
- .multiselect {
535
- ::v-deep {
536
- .multiselect__tags {
537
- border-color: var(--form-border--disabled-color);
538
- background: var(--form-border--disabled-color);
251
+ $multiselect-limit-right-pos: calc(
252
+ $multiselect-clear-right-pos// clear offet from border
253
+ + var(--icon-md-size)// clear size
254
+ + var(--input-padding) // cleat-to-chip offset
255
+ );
539
256
 
540
- .multiselect__placeholder {
541
- @extend %wt-placeholder--disabled;
542
- //color: var(--form-placeholder--disabled-color);
543
- }
544
- }
257
+ .multiselect__tags {
258
+ padding-right: calc(
259
+ $multiselect-limit-right-pos
260
+ + 50px// chip
261
+ + var(--input-padding) // chip-to-content offset
262
+ );
545
263
  }
546
- }
547
- }
548
264
 
549
- .wt-select--clearable {
550
- .multiselect::v-deep {
551
- .multiselect__tags {
552
- padding: var(--select-tags-padding--clearable);
265
+ .multiselect__clear {
266
+ right: $multiselect-clear-right-pos;
553
267
  }
554
268
 
555
- .multiselect__strong {
556
- right: var(--select-chip-right-pos--clearable);
269
+ .multiselect__limit {
270
+ right: $multiselect-limit-right-pos;
557
271
  }
558
272
  }
559
273
  }
@@ -17,29 +17,26 @@ describe('WtTagsInput', () => {
17
17
  expect(wrapper.find('.wt-label').text()).toBe(label);
18
18
  });
19
19
 
20
- it('emits "input" event at native "tags-changed" event', () => {
21
- const tags = [{ text: '123' }];
22
- const wrapper = shallowMount(WtTagsInput);
23
- wrapper.findComponent({ name: 'vue-tags-input' }).vm.$emit('tags-changed', tags);
24
- expect(wrapper.emitted().input[0][0]).toEqual(tags);
25
- });
26
-
27
- it('correctly computes autocomplete options', () => {
28
- const autocompleteItems = [{ name: '123' }, { name: '321' }];
29
- const autocompleteOptions = [{ name: '123', text: '123' }, { name: '321', text: '321' }];
20
+ it('by default emits "input" event at native "tag" event', () => {
21
+ const tag = '123';
30
22
  const wrapper = shallowMount(WtTagsInput, {
31
- propsData: { autocompleteItems },
23
+ propsData: {
24
+ value: [],
25
+ },
32
26
  });
33
- expect(wrapper.vm.autocompleteOptions).toEqual(autocompleteOptions);
27
+ wrapper.findComponent({ name: 'vue-multiselect' }).vm.$emit('tag', tag);
28
+ expect(wrapper.emitted().input[0][0]).toEqual([tag]);
34
29
  });
35
30
 
36
- it('correctly filters autocomplete options with search', () => {
37
- const autocompleteItems = [{ name: '123' }, { name: '456' }];
38
- const filteredAutocompleteOptions = [{ name: '123', text: '123' }];
31
+ it('in manual mode doesnt emit "input" event at native "tag" event', () => {
32
+ const tag = '123';
39
33
  const wrapper = shallowMount(WtTagsInput, {
40
- data: () => ({ input: '1' }),
41
- propsData: { autocompleteItems },
34
+ propsData: {
35
+ value: [],
36
+ manualTagging: true,
37
+ },
42
38
  });
43
- expect(wrapper.vm.autocompleteOptions).toEqual(filteredAutocompleteOptions);
39
+ wrapper.findComponent({ name: 'vue-multiselect' }).vm.$emit('tag', tag);
40
+ expect(wrapper.emitted().input).toBeFalsy();
44
41
  });
45
42
  });