its_ui_vite 0.0.2 → 0.0.4

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.
@@ -1,28 +1,51 @@
1
1
  <template>
2
- <div :style="`--transition: ${transition}ms`" :class="[classes.root, variant]">
3
- <CInput ref="input" :placeholder="activePlaceholder" v-model.trim="findValue" :class="classes.input">
2
+ <div
3
+ ref="root"
4
+ :style="`--transition: ${transition}ms; --width: ${width}; --list-indent-y: ${listIndentY}px`"
5
+ :class="[classes.root, variant, size, { disabled }]"
6
+ >
7
+ <CInput :placeholder="activePlaceholder" :disabled="disabled" :size="size" :width="width" v-model.trim="findValue" :class="[classes.input, {open: isOpen}]">
4
8
  <template #customIcon>
5
- <CIcons class="c-select__inp_icon" :iconId="isMultiple ? 'arrow' : 'lens'" />
9
+ <CIcons :class="['c-select__inp_icon']" :iconId="isMultiple ? 'arrow' : 'lens'" />
6
10
  </template>
7
11
  </CInput>
8
12
 
9
- <CScroll :scrollY="scrollY" :class="['c-select__list', {open: isOpen}]">
10
- <button
13
+ <CScroll :scrollY="scrollY" :class="[classes.list, listPosition, {open: isOpen}]">
14
+ <button
15
+ v-if="isSelectAll && foundOptions.length > 1"
11
16
  :class="classes.optionBtn"
17
+ @click="debounceEvent({}, $event)"
18
+ >
19
+ <CCheckbox
20
+ :class="classes.option"
21
+ size='sm'
22
+ :modelValue="options.length === activeOptions.size"
23
+ >
24
+ <span class="c-select__list_item-text">
25
+ {{ text.selectAll }}
26
+ </span>
27
+ </CCheckbox>
28
+ </button>
29
+ <button
12
30
  v-for="item in foundOptions"
13
- :key="item.id"
31
+ :key="item.value"
14
32
  @click="debounceEvent(item, $event)"
33
+ :class="classes.optionBtn"
15
34
  >
16
35
  <component
17
36
  :class="classes.option"
18
37
  :is="isMultiple ? 'CCheckbox' : 'div'"
19
- v-bind.props="{size: 'sm'}"
38
+ v-bind.props="getCheckboxProp(item)"
20
39
  >
21
- {{ item.text }}
40
+ <span class="c-select__list_item-text">
41
+ {{ item.text }}
42
+ </span>
22
43
  </component>
23
44
  </button>
24
45
  <div v-if="foundOptions.length < 1" :class="[classes.option, 'not-found']">
25
- ничего не найдено
46
+ <span class="c-select__list_item-text">
47
+ {{ text.notFound }}
48
+ </span>
26
49
  </div>
27
50
  </CScroll>
28
51
  </div>
@@ -41,23 +64,36 @@ export default {
41
64
  return {
42
65
  transition: 200,
43
66
  isMultiple: this.variant === 'multiple',
67
+ isSelectAll: this.selectAll && this.variant === 'multiple',
68
+ listIndentY: 12,
44
69
  scrollY: 0,
45
70
  activeOptions: new Set(),
71
+ oldActiveOptions: new Set(),
46
72
  findValue: '',
47
73
  activePlaceholder: this.placeholder,
74
+ listPosition: 'bottom',
48
75
 
49
76
  debounceEvent: () => {},
50
77
 
51
78
  isOpen: false,
79
+
52
80
  classes: {
53
81
  root: 'c-select',
54
82
  input: 'c-select__inp',
83
+ list: 'c-select__list',
55
84
  option: 'c-select__list_item',
56
85
  optionBtn: 'c-select__list_btn',
57
86
  },
58
87
  }
59
88
  },
60
89
 
90
+ emits: ['change'],
91
+
92
+ model: {
93
+ event: 'change',
94
+ prop: 'modelValue',
95
+ },
96
+
61
97
  props: {
62
98
  options: {
63
99
  type: Array,
@@ -72,55 +108,200 @@ export default {
72
108
  validator: getPropValidator('CSelect', 'variant', ['default', 'multiple']),
73
109
  },
74
110
 
111
+ size: {
112
+ type: String,
113
+
114
+ default: 'lg',
115
+ validator: getPropValidator('CSelect', 'size', ['lg', 'md', 'sm'])
116
+ },
117
+
118
+ selectAll: {
119
+ type: Boolean,
120
+
121
+ default: false,
122
+ },
123
+
124
+ autocomplete: {
125
+ type: Boolean,
126
+
127
+ default: false,
128
+ },
129
+
130
+ disabled: {
131
+ type: Boolean,
132
+
133
+ default: false,
134
+ },
135
+
136
+ transformVal: {
137
+ type: Boolean,
138
+
139
+ default: false,
140
+ },
141
+
142
+ locale: {
143
+ type: String,
144
+
145
+ default: 'rus',
146
+ validator: getPropValidator('CSelect', 'variant', ['rus', 'usa', 'tur', 'spa']),
147
+ },
148
+
75
149
  placeholder: {
76
150
  type: String,
77
151
 
78
- default: 'выберете вариант',
79
- }
152
+ default: 'выберите вариант',
153
+ },
154
+
155
+ width: {
156
+ type: String,
157
+
158
+ default: '100%'
159
+ },
160
+
161
+ modelValue: {
162
+ type: Array
163
+ },
80
164
  },
81
165
 
82
166
  methods: {
83
167
  handleOption(option, evt) {
84
- const input = evt.target.closest(`.${this.classes.optionBtn}`).querySelector('input');
168
+ if (this.isSelectAll) this.setAllOption(evt)
169
+ this.setOption(option, evt);
170
+
171
+ this.setActivePlaceholder()
172
+ if (!this.isMultiple) this.dispatchEmit()
173
+ },
174
+
175
+ setOption(option, evt) {
176
+ if (!option?.value) return;
177
+
178
+ const input = evt?.target?.closest(`.${this.classes.optionBtn}`)?.querySelector('input');
85
179
  const isAdd = !this.isMultiple || input?.checked
86
180
 
87
181
  if (!this.isMultiple) this.activeOptions.clear()
88
182
 
89
183
  this.activeOptions[isAdd ? 'add' : 'delete'](option)
184
+ },
90
185
 
91
- this.setActivePlaceholder()
92
- this.$emit('change', [...this.activeOptions])
186
+
187
+ getCheckboxProp(option) {
188
+ const isChecked = !!([...this.activeOptions].find(({ value }) => option.value === value));
189
+
190
+ if (!this.isMultiple) return {}
191
+ return {
192
+ size: 'sm',
193
+ modelValue: isChecked,
194
+ }
195
+ },
196
+
197
+ setAllOption(evt) {
198
+ const list = evt.target.closest(`.${this.classes.list}`);
199
+ const inputs = list.querySelectorAll(`.${this.classes.optionBtn} input`);
200
+ const inputsChecked = list.querySelectorAll(`.${this.classes.optionBtn}:not(:first-child) input:checked`);
201
+ const selectAllInput = inputs[0];
202
+
203
+ const isSelectAllInput = evt.target.closest(`.${this.classes.option}`).querySelector(`.${this.classes.option} input`) === selectAllInput;
204
+ const isOneNotChecked = inputs.length - 1 === inputsChecked.length;
205
+
206
+ if (isSelectAllInput) {
207
+ inputs.forEach((inp) => {
208
+ inp.checked = selectAllInput.checked;
209
+ });
210
+
211
+ this.options.forEach((option) => {
212
+ this.activeOptions[selectAllInput.checked ? 'add' : 'delete'](option);
213
+ });
214
+ } else {
215
+ selectAllInput.checked = isOneNotChecked;
216
+ }
93
217
  },
94
218
 
95
219
  setActivePlaceholder() {
96
220
  this.findValue = ''
97
221
 
98
222
  const placeholderText = [...this.activeOptions].map(({text}) => text).join('')
99
- const newPlaceholder = this.activeOptions.size > 1 ? 'несколько' : placeholderText
100
-
101
- console.log([...this.activeOptions], 'placeholderText');
223
+ const newPlaceholder = this.activeOptions.size > 1 ? this.text.multiple : placeholderText
102
224
 
103
225
  this.activePlaceholder = newPlaceholder || this.placeholder
104
226
  },
105
- },
106
227
 
107
- mounted() {
108
- this.debounceEvent = fixDblEvent(this.handleOption)
109
- console.log(this.debounceEvent);
228
+ isChange() {
229
+ let biggerSet = this.activeOptions
230
+ let smallerSet = this.oldActiveOptions
231
+ let isChange = false
232
+
233
+ if (biggerSet.size < smallerSet.size) [biggerSet, smallerSet] = [smallerSet, biggerSet]
234
+
235
+ biggerSet.forEach((item) => {
236
+ if (!isChange) isChange = !smallerSet.has(item)
237
+ })
110
238
 
111
- document.body.addEventListener('click', (evt) => {
112
- const input = evt.target.closest(`.${this.classes.input}`)
239
+ return isChange
240
+ },
113
241
 
114
- if (input === this.$refs.input?.$el) return this.isOpen = true
242
+ setOldOptions() {
243
+ this.oldActiveOptions.clear()
244
+ this.activeOptions.forEach((item) => this.oldActiveOptions.add(item))
245
+ },
246
+
247
+ setListPosition() {
248
+ const windowHeight = window.innerHeight;
249
+ const inputHeight = this.$refs.root.clientHeight;
250
+ const listPosition = this.$refs.root.querySelector(`.${this.classes.list}`).getBoundingClientRect()
251
+ const isTop = this.listPosition === 'top'
115
252
 
116
- this.isOpen = false
253
+ const indentTop = (listPosition.height + inputHeight) * (isTop ? 1 : 0) + this.listIndentY
254
+ const indent = windowHeight - listPosition.y - indentTop
255
+
256
+ this.listPosition = indent > listPosition.height ? 'bottom' : 'top'
257
+ },
258
+
259
+ handleClick(evt) {
260
+ const classes = `.${this.classes.input},.c-input__custom-icon${this.isMultiple ? ',.c-input__custom-icon,.c-select__list_btn' : ''}`
261
+ const root = evt.target.closest(`.${this.classes.root}`)
262
+ const domElements = evt.target.closest(classes)
263
+
264
+ const isRoot = root === this.$refs.root
265
+ const isToggle = evt.target.closest('.c-input__custom-icon')
266
+
267
+ if (isRoot && domElements) {
268
+ this.setListPosition()
269
+ this.isOpen = isToggle ? !this.isOpen : true
270
+
271
+ return
272
+ }
273
+
274
+ if (this.isOpen) {
275
+ this.isOpen = false
276
+
277
+ if (this.isMultiple) this.dispatchEmit()
278
+ }
279
+ },
280
+
281
+ dispatchEmit() {
282
+ const options = [...this.activeOptions].map((option) => this.transformVal ? option.value : option)
283
+ if (this.isChange()) this.$emit('change', options)
117
284
 
118
285
  setTimeout(() => {
119
286
  this.findValue = ''
287
+
288
+ this.setOldOptions()
120
289
  }, this.transition);
121
- })
290
+ },
122
291
  },
123
292
 
293
+ mounted() {
294
+ this.debounceEvent = fixDblEvent(this.handleOption)
295
+
296
+ document.body.addEventListener('click', this.handleClick)
297
+ if (!this.isMultiple && this.autocomplete) this.debounceEvent(this.options[0], {});
298
+ },
299
+
300
+ unmounted() {
301
+ document.body.removeEventListener('click', this.handleClick)
302
+ },
303
+
304
+
124
305
  computed: {
125
306
  foundOptions() {
126
307
  const reg = new RegExp(this.findValue.replace(/\W/g, '\\$&'), 'i')
@@ -128,6 +309,68 @@ export default {
128
309
 
129
310
  return this.options.filter(({text}) => reg.test(text))
130
311
  },
312
+
313
+ text() {
314
+ const lang = {
315
+ rus: {
316
+ multiple: "несколько",
317
+ notFound: "Ничего не найдено",
318
+ selectAll: "Выбрать все варианты",
319
+ },
320
+
321
+ usa: {
322
+ multiple: "Multiple",
323
+ notFound: "There are no matches",
324
+ selectAll: "Choose all options",
325
+ },
326
+
327
+ tur: {
328
+ multiple: "Multiple",
329
+ notFound: "There are no matches",
330
+ selectAll: "Choose all options",
331
+ },
332
+
333
+ spa: {
334
+ multiple: "Multiple",
335
+ notFound: "No se encontraron coincidencias",
336
+ selectAll: "Seleccionar todas las opciones",
337
+ },
338
+ };
339
+
340
+ return lang[this.locale]
341
+ },
342
+ },
343
+
344
+ watch: {
345
+ modelValue: {
346
+ handler(options) {
347
+ if (!(options?.length || options?.size)) return;
348
+ this.activeOptions.clear();
349
+ this.oldActiveOptions.clear();
350
+
351
+ options = this.isMultiple ? options : options.slice(0, 1);
352
+ [...options].forEach((item) => {
353
+
354
+ let option = this.options.find(({ value }) => value === (item?.value || item))
355
+
356
+ if (!item?.value) {
357
+ option = this.options.find(({ value }) => value === item);
358
+ } else {
359
+
360
+ }
361
+
362
+ if (undefined === option) return;
363
+
364
+ this.activeOptions.add(option);
365
+ this.oldActiveOptions.add(option);
366
+ })
367
+
368
+ this.setActivePlaceholder()
369
+ },
370
+
371
+ deep: true,
372
+ immediate: true,
373
+ }
131
374
  },
132
375
 
133
376
  components: {
@@ -141,50 +384,104 @@ export default {
141
384
 
142
385
  <style lang="scss">
143
386
  .c-select {
387
+ --icon-indent: 50px;
388
+ --icon-position: 15px;
389
+ --option-height: 48px;
390
+ --font-size: 16px;
391
+
144
392
  position: relative;
145
393
 
394
+ width: var(--width, 100%);
395
+
396
+ button,
397
+ input,
398
+ div,
399
+ a {
400
+ font-family: inherit;
401
+ font-weight: inherit;
402
+ }
403
+
146
404
  // prop.variant
147
405
  &.default {
148
406
  .c-select__inp .c-input {
149
- padding-left: 50px;
407
+ padding-left: var(--icon-indent);
150
408
  }
151
409
 
152
410
  .c-input__custom-icon {
153
- left: 15px;
411
+ left: var(--icon-position);
154
412
  right: auto;
155
413
  }
156
414
  }
157
415
 
158
416
  &.multiple {
159
- .c-input:focus {
160
- &~ .c-input__custom-icon svg {
161
- transform: rotate(0deg);
417
+ .c-select__inp {
418
+ &.open {
419
+ .c-input__custom-icon svg {
420
+ transform: rotate(0deg);
421
+ }
162
422
  }
163
- }
164
423
 
165
- .c-input__custom-icon svg {
166
- transform: rotate(180deg);
424
+ .c-input {
425
+ padding-right: var(--icon-indent);
426
+ }
427
+
428
+ .c-input__custom-icon {
429
+ right: var(--icon-position);
430
+
431
+ pointer-events: all;
432
+
433
+ svg {
434
+ transform: rotate(180deg);
435
+ }
436
+ }
167
437
  }
168
438
  }
169
439
  // ./prop.variant
170
440
 
441
+ // prop.size
442
+ &.sm {
443
+ --icon-indent: 40px;
444
+ --icon-position: 10px;
445
+ --option-height: 44px;
446
+ --font-size: 14px;
447
+ }
448
+ // ./prop.size
449
+
450
+ // prop.disabled
451
+ &.disabled {
452
+ opacity: 0.6;
453
+ pointer-events: none;
454
+ }
455
+ // ./prop.disabled
456
+
457
+
171
458
  &__inp {
459
+ &.open {
460
+ .c-input {
461
+ border-color: var(--green-light);
462
+ }
463
+
464
+ .c-input__custom-icon svg {
465
+ stroke: var(--green-light);
466
+ }
467
+ }
468
+
172
469
  .c-input__custom-icon {
470
+ cursor: pointer;
471
+
173
472
  opacity: 1;
174
- }
175
473
 
176
- .c-input:focus {
177
- &~ .c-input__custom-icon svg {
178
- stroke: var(--green-light);
474
+ svg {
475
+ stroke: var(--white);
179
476
  }
180
477
  }
181
478
  }
182
479
 
183
480
  &__list {
184
481
  position: absolute;
185
- top: 100%;
186
482
  left: 0;
187
483
  transform: translate(0px, 0px);
484
+ z-index: 20;
188
485
 
189
486
  width: 100%;
190
487
  max-height: 240px;
@@ -200,8 +497,20 @@ export default {
200
497
  opacity: 0;
201
498
  pointer-events: none;
202
499
 
500
+ &.bottom {
501
+ --indent-y: var(--list-indent-y);
502
+
503
+ top: 100%;
504
+ }
505
+
506
+ &.top {
507
+ --indent-y: calc(var(--list-indent-y) * -1);
508
+
509
+ bottom: 100%;
510
+ }
511
+
203
512
  &.open {
204
- transform: translate(0, 12px);
513
+ transform: translate(0, var(--indent-y));
205
514
 
206
515
  opacity: 1;
207
516
  pointer-events: all;
@@ -235,13 +544,16 @@ export default {
235
544
  }
236
545
  }
237
546
 
547
+ &-text {
548
+ font-size: var(--font-size);
549
+ }
550
+
238
551
  .c-checkbox__text {
239
552
  color: inherit;
240
553
  }
241
554
  }
242
555
 
243
556
  &_btn {
244
- height: 48px;
245
557
  width: 100%;
246
558
 
247
559
  flex-shrink: 0;
@@ -249,15 +561,26 @@ export default {
249
561
  align-items: center;
250
562
 
251
563
  border: none;
564
+ padding: 0;
252
565
 
253
- font-size: 16px;
566
+ text-align: left;
567
+ font-size: var(--font-size);
254
568
 
255
569
  background: transparent;
256
570
 
571
+ cursor: pointer;
572
+
257
573
  &:hover {
258
574
  background: var(--green-dark);
259
575
  }
576
+
577
+ .c-checkbox__wrap {
578
+ min-height: var(--option-height);
579
+
580
+ padding-top: 5px;
581
+ padding-bottom: 5px;
582
+ }
260
583
  }
261
584
  }
262
585
  }
263
- </style>
586
+ </style>