@tagplus/components 4.7.12 → 5.0.0

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.
Files changed (88) hide show
  1. package/dist/tp.common.js +2 -1
  2. package/dist/tp.common.js.LICENSE.txt +9 -0
  3. package/dist/tp.common.js.map +1 -1
  4. package/dist/tp.common.lang-en-js.js +2 -0
  5. package/dist/tp.common.lang-en-js.js.map +1 -0
  6. package/dist/tp.css +11 -167
  7. package/dist/tp.umd.js +2 -1
  8. package/dist/tp.umd.js.LICENSE.txt +9 -0
  9. package/dist/tp.umd.js.map +1 -1
  10. package/dist/tp.umd.lang-en-js.js +2 -0
  11. package/dist/tp.umd.lang-en-js.js.map +1 -0
  12. package/dist/tp.umd.min.js +2 -1
  13. package/dist/tp.umd.min.js.LICENSE.txt +9 -0
  14. package/dist/tp.umd.min.js.map +1 -1
  15. package/dist/tp.umd.min.lang-en-js.js +2 -0
  16. package/dist/tp.umd.min.lang-en-js.js.map +1 -0
  17. package/package.json +51 -50
  18. package/src/assets/scss/_fonts.scss +24 -23
  19. package/src/assets/scss/_helpers.scss +4 -0
  20. package/src/assets/scss/_mixins.scss +2 -2
  21. package/src/assets/scss/_overrides.scss +5 -52
  22. package/src/assets/scss/_resass.scss +21 -12
  23. package/src/assets/scss/_variables.scss +0 -1
  24. package/src/assets/scss/index.scss +1 -4
  25. package/src/components/Autosuggest/Autosuggest.vue +340 -767
  26. package/src/components/Autosuggest/Multisuggest.vue +22 -0
  27. package/src/components/Autosuggest/autosuggest-props.js +210 -0
  28. package/src/components/Autosuggest/autosuggest-style.scss +127 -0
  29. package/src/components/Autosuggest/core.js +63 -0
  30. package/src/components/Autosuggest/index.js +2 -0
  31. package/src/components/Autosuggest/multisuggest-props.js +9 -0
  32. package/src/components/Autosuggest/option.vue +136 -0
  33. package/src/components/Autosuggest/select-dropdown.vue +64 -0
  34. package/src/components/Autosuggest/useOption.js +120 -0
  35. package/src/components/Autosuggest/useSelect.js +1133 -0
  36. package/src/components/AutosuggestTest.vue +56 -0
  37. package/src/components/CardExemplo.vue +49 -0
  38. package/src/components/CodeSample.vue +78 -0
  39. package/src/components/Inline/Inline.vue +24 -32
  40. package/src/components/InputNumber/InputNumber.vue +329 -378
  41. package/src/components/InputNumber/input-number.js +135 -0
  42. package/src/components/Loader/Loader.vue +42 -53
  43. package/src/components/Loader/animations.scss +13 -0
  44. package/src/components/Money/Money.vue +11 -20
  45. package/src/components/Multisuggest/index.js +2 -3
  46. package/src/components/MultisuggestTest.vue +56 -0
  47. package/src/components/OptionsList/OptionsList.vue +7 -6
  48. package/src/components/OptionsListItem/OptionsListItem.vue +46 -42
  49. package/src/components/Percent/Percent.vue +8 -14
  50. package/src/components/Skeleton/Skeleton.vue +16 -11
  51. package/src/components/Step/Step.vue +42 -35
  52. package/src/components/Steps/Steps.vue +4 -7
  53. package/src/components/TesteToCurrency.vue +171 -0
  54. package/src/components/Tip/Tip.vue +45 -30
  55. package/src/components/ValueSelector.vue +60 -0
  56. package/src/components/autosuggestMixin.js +301 -0
  57. package/src/components/index.js +4 -1
  58. package/src/locale/i18n.js +114 -0
  59. package/src/locale/lang/en.js +3 -2
  60. package/src/locale/lang/pt-br.js +3 -2
  61. package/src/main.js +9 -14
  62. package/src/mixins/floatFormatter.js +12 -16
  63. package/src/plugins/currency.js +100 -0
  64. package/src/utils/browser.js +6 -0
  65. package/src/utils/constants.js +3 -0
  66. package/src/utils/error.js +22 -0
  67. package/src/utils/filters.js +1 -14
  68. package/src/utils/helpers.js +41 -0
  69. package/src/utils/i18n.js +2 -0
  70. package/src/utils/icon.js +35 -0
  71. package/src/utils/index.js +20 -0
  72. package/src/utils/objects.js +17 -0
  73. package/src/utils/runtime.js +86 -0
  74. package/src/utils/scroll.js +100 -0
  75. package/src/utils/strings.js +17 -0
  76. package/src/utils/style.js +80 -0
  77. package/src/utils/types.js +39 -0
  78. package/src/utils/use-derived-namespace.js +112 -0
  79. package/src/utils/use-form-common-props.js +41 -0
  80. package/src/utils/use-form-item.js +80 -0
  81. package/src/utils/use-id.js +40 -0
  82. package/src/utils/use-input.js +33 -0
  83. package/src/components/Dialog/Dialog.vue +0 -253
  84. package/src/components/Dialog/index.js +0 -3
  85. package/src/components/Multisuggest/Multisuggest.vue +0 -858
  86. package/src/locale/index.js +0 -78
  87. package/src/mixins/locale.js +0 -9
  88. package/src/utils/currency.js +0 -180
@@ -0,0 +1,1133 @@
1
+ import {
2
+ computed,
3
+ nextTick,
4
+ onMounted,
5
+ reactive,
6
+ ref,
7
+ watch,
8
+ watchEffect,
9
+ getCurrentInstance,
10
+ } from 'vue'
11
+ // eslint-disable-next-line vue/prefer-import-from-vue
12
+ import { isArray, isObject, toRawType } from '@vue/shared'
13
+ import {
14
+ findLastIndex,
15
+ get,
16
+ isEqual,
17
+ debounce as lodashDebounce,
18
+ } from 'lodash-unified'
19
+ import { useResizeObserver } from '@vueuse/core'
20
+ const UPDATE_MODEL_EVENT = 'update:modelValue'
21
+ const CHANGE_EVENT = 'change'
22
+ const EVENT_CODE = {
23
+ tab: 'Tab',
24
+ enter: 'Enter',
25
+ space: 'Space',
26
+ left: 'ArrowLeft', // 37
27
+ up: 'ArrowUp', // 38
28
+ right: 'ArrowRight', // 39
29
+ down: 'ArrowDown', // 40
30
+ esc: 'Escape',
31
+ delete: 'Delete',
32
+ backspace: 'Backspace',
33
+ numpadEnter: 'NumpadEnter',
34
+ pageUp: 'PageUp',
35
+ pageDown: 'PageDown',
36
+ home: 'Home',
37
+ end: 'End',
38
+ }
39
+ import {
40
+ ValidateComponentsMap,
41
+ debugWarn,
42
+ isClient,
43
+ isFunction,
44
+ isNumber,
45
+ isString,
46
+ scrollIntoView,
47
+ useInput,
48
+ useFormItem,
49
+ useFormItemInputId,
50
+ useFormSize,
51
+ } from '@/utils'
52
+ import {
53
+ useFocusController,
54
+ useId,
55
+ useLocale,
56
+ useNamespace,
57
+ } from 'element-plus/es/hooks/index'
58
+
59
+ const MINIMUM_INPUT_WIDTH = 11
60
+
61
+ export const useSelect = (props, { emit }) => {
62
+ const { t } = useLocale()
63
+ const contentId = useId()
64
+ const nsSelect = useNamespace('select')
65
+ const nsInput = useNamespace('input')
66
+
67
+ const states = reactive({
68
+ inputValue: '',
69
+ options: new Map(),
70
+ cachedOptions: new Map(),
71
+ disabledOptions: new Map(),
72
+ optionValues: [], // sorted value of options
73
+ selected: props.multiple ? [] : ({}),
74
+ selectionWidth: 0,
75
+ calculatorWidth: 0,
76
+ collapseItemWidth: 0,
77
+ selectedLabel: '',
78
+ hoveringIndex: -1,
79
+ previousQuery: null,
80
+ inputHovering: false,
81
+ menuVisibleOnFocus: false,
82
+ isBeforeHide: false,
83
+ })
84
+
85
+ // template refs
86
+ const selectRef = ref(null)
87
+ const selectionRef = ref(null)
88
+ const tooltipRef = ref(null)
89
+ const inputRef = ref(null)
90
+ const calculatorRef = ref(null)
91
+ const suffixRef = ref(null)
92
+ const menuRef = ref(null)
93
+ const collapseItemRef = ref(null)
94
+ const scrollbarRef = ref(null)
95
+
96
+ const { wrapperRef, isFocused, handleFocus, handleBlur } = useFocusController(
97
+ inputRef,
98
+ {
99
+ afterFocus () {
100
+ if (props.automaticDropdown && !expanded.value) {
101
+ expanded.value = true
102
+ states.menuVisibleOnFocus = true
103
+ }
104
+ },
105
+ beforeBlur (event) {
106
+ return (
107
+ tooltipRef.value?.isFocusInsideContent(event)
108
+ )
109
+ },
110
+ afterBlur () {
111
+ expanded.value = false
112
+ states.menuVisibleOnFocus = false
113
+ },
114
+ }
115
+ )
116
+
117
+ // the controller of the expanded popup
118
+ const expanded = ref(false)
119
+ const hoverOption = ref()
120
+
121
+ const { form, formItem } = useFormItem()
122
+ const { inputId } = useFormItemInputId(props, {
123
+ formItemContext: formItem,
124
+ })
125
+
126
+ const selectDisabled = computed(() => props.disabled || form?.disabled)
127
+
128
+ const hasModelValue = computed(() => {
129
+ return props.multiple
130
+ ? isArray(props.modelValue) && props.modelValue.length > 0
131
+ : props.modelValue !== undefined &&
132
+ props.modelValue !== null &&
133
+ props.modelValue !== ''
134
+ })
135
+
136
+ /**
137
+ * @tagplus
138
+ * Sobrescrito para mostrar o botão sem hover, para funcionar no mobile
139
+ */
140
+ const showClose = computed(() => {
141
+ const criteria =
142
+ props.clearable &&
143
+ !selectDisabled.value &&
144
+ hasModelValue.value
145
+ return criteria
146
+ })
147
+
148
+ /**
149
+ * @tagplus
150
+ * Sobrescrito para remover parâmetro suffixTransition
151
+ */
152
+ const iconReverse = computed(() => {
153
+ return expanded.value ? 'is-reverse' : ''
154
+ })
155
+
156
+ /**
157
+ * @tagplus
158
+ * Trata o caso que o autosuggest recebe um objeto com o valueKey e labelKey
159
+ */
160
+ const formattedValue = computed(() => {
161
+ let newVal = ''
162
+ if (typeof props.modelValue === 'boolean') {
163
+ newVal = ''
164
+ } else if (Array.isArray(props.modelValue)) {
165
+ newVal = props.modelValue
166
+ } else if (props.modelValue && typeof props.modelValue === 'object') {
167
+ if (!Object.keys(newVal).length) { return {} }
168
+ if (!props.modelValue[props.valueKey] && props.modelValue[props.valueKey] !== '') {
169
+ if (process.env.DEBUG === 'true') {
170
+ const instance = getCurrentInstance()
171
+ console.error(`Componente '${instance.type.componentName}' option doesn't have a valueKey '${props.valueKey}' key` )
172
+ }
173
+ } else {
174
+ newVal = props.modelValue[props.valueKey]
175
+ }
176
+ }
177
+
178
+ return newVal
179
+ })
180
+
181
+ const validateState = computed(() => formItem?.validateState || '')
182
+ const validateIcon = computed(
183
+ () => ValidateComponentsMap[validateState.value]
184
+ )
185
+
186
+ /**
187
+ * @tagplus
188
+ * Sobrescrito porque é sempre remote
189
+ */
190
+ const debounce = 300
191
+
192
+ /**
193
+ * @tagplus
194
+ * Sobrescrito para remover parâmetro filterable e remote
195
+ * Altera a lang padrão de "Sem dados" para não ter que preencher a prop em todas instâncias do projeto 2.0
196
+ */
197
+ const emptyText = computed(() => {
198
+ if (props.loading) {
199
+ return props.loadingText || t('el.select.loading')
200
+ } else {
201
+ if (!states.inputValue && states.options.size === 0)
202
+ return false
203
+ if (
204
+ states.inputValue &&
205
+ states.options.size > 0 &&
206
+ filteredOptionsCount.value === 0
207
+ ) {
208
+ return props.noMatchText || t('el.select.noMatch')
209
+ }
210
+ if (states.options.size === 0) {
211
+ // @TODO Verificar se mostrará no projeto nuxt
212
+ return props.noDataText || t('autosuggests.sem_dados')
213
+ }
214
+ }
215
+ return null
216
+ })
217
+
218
+ const filteredOptionsCount = computed(
219
+ () => optionsArray.value.filter((option) => option.visible).length
220
+ )
221
+
222
+ const optionsArray = computed(() => {
223
+ const list = Array.from(states.options.values())
224
+ const newList = []
225
+ states.optionValues.forEach((item) => {
226
+ const index = list.findIndex((i) => i.value === item)
227
+ if (index > -1) {
228
+ newList.push(list[index])
229
+ }
230
+ })
231
+ return newList.length >= list.length ? newList : list
232
+ })
233
+
234
+ const cachedOptionsArray = computed(() =>
235
+ Array.from(states.cachedOptions.values())
236
+ )
237
+
238
+ /**
239
+ * @tagplus
240
+ * Sobrescrito para remover parâmetro filterable
241
+ */
242
+ const showNewOption = computed(() => {
243
+ const hasExistingOption = optionsArray.value
244
+ .filter((option) => {
245
+ return !option.created
246
+ })
247
+ .some((option) => {
248
+ return option.currentLabel === states.inputValue
249
+ })
250
+ return (
251
+ props.allowCreate &&
252
+ states.inputValue !== '' &&
253
+ !hasExistingOption
254
+ )
255
+ })
256
+
257
+ /**
258
+ * @tagplus
259
+ * Sobrescrito porque será sempre filterable com remoteMethod
260
+ */
261
+ const updateOptions = () => {
262
+ return
263
+ }
264
+
265
+ const selectSize = useFormSize()
266
+
267
+ const collapseTagSize = computed(() =>
268
+ ['small'].includes(selectSize.value) ? 'small' : 'default'
269
+ )
270
+
271
+ /**
272
+ * @tagplus
273
+ * Sobrescrito para alterar o doRequest
274
+ */
275
+ const dropdownMenuVisible = computed({
276
+ get () {
277
+ return expanded.value && emptyText.value !== false
278
+ },
279
+ set (val) {
280
+ states.doRequest = true
281
+ expanded.value = val
282
+ },
283
+ })
284
+
285
+ /**
286
+ * @tagplus
287
+ * Sobrescrito porque não tem mais filterable
288
+ */
289
+ const shouldShowPlaceholder = computed(() => {
290
+ if (isArray(props.modelValue)) {
291
+ return props.modelValue.length === 0 && !states.inputValue
292
+ }
293
+ return !states.inputValue
294
+ })
295
+
296
+ const currentPlaceholder = computed(() => {
297
+ const _placeholder = props.placeholder ?? t('el.select.placeholder')
298
+ return props.multiple || !hasModelValue.value
299
+ ? _placeholder
300
+ : states.selectedLabel
301
+ })
302
+
303
+ /**
304
+ * @tagplus
305
+ * Criado para montar a lista com ou sem "Cadastrar Novo Item"
306
+ */
307
+ const suggestionsList = computed(() => {
308
+ if (props.loading) return []
309
+
310
+ // transformando em Array
311
+ const list =
312
+ typeof props.suggestions === 'object'
313
+ ? Object.values(props.suggestions)
314
+ : props.suggestions
315
+
316
+ if (props.allowCreate) {
317
+ const createdSuggestion = { created: true }
318
+
319
+ createdSuggestion[props.labelKey] = states.inputValue !== '' ? states.inputValue : null
320
+ createdSuggestion[props.valueKey] = createdSuggestion[props.labelKey]
321
+ list.push(createdSuggestion)
322
+ }
323
+
324
+ return list
325
+ })
326
+
327
+ /**
328
+ * @tagplus
329
+ * Criado para formatar o newItem
330
+ */
331
+ const newItem = computed(() => {
332
+ const instance = getCurrentInstance()
333
+ // @todo rever se vai funcionar no nuxt
334
+ return states.inputValue
335
+ ? t('autosuggests.cadastrar', { nameItem: states.inputValue })
336
+ : t(`autosuggests.newItem.${instance.type.componentName}`)
337
+ })
338
+
339
+ /**
340
+ * @tagplus
341
+ * Criado para conseguir ter mais de um componente na mesma página com ids diferentes
342
+ */
343
+ const myId = computed(() => {
344
+ const instance = getCurrentInstance()
345
+ return props.id || instance.type.componentName
346
+ })
347
+
348
+ /**
349
+ * @tagplus
350
+ * Criado para formatar corretamente a option visível quando recebe number e carregou a lista
351
+ */
352
+ const firstValueStopHandle = watch(
353
+ () => props.suggestions,
354
+ (val, oldVal) => {
355
+ if (!isNumber(props.modelValue)) {
356
+ firstValueStopHandle()
357
+ }
358
+
359
+ if (val.length){
360
+ setSelected()
361
+ }
362
+
363
+ firstValueStopHandle()
364
+ }
365
+ )
366
+
367
+ /**
368
+ * @tagplus
369
+ * Sobrescrito para remover prop filterable
370
+ */
371
+ watch(
372
+ () => props.modelValue,
373
+ (val, oldVal) => {
374
+ if (props.multiple) {
375
+ if (!props.reserveKeyword) {
376
+ states.inputValue = ''
377
+ handleQueryChange('')
378
+ }
379
+ }
380
+ setSelected()
381
+ if (!isEqual(val, oldVal) && props.validateEvent) {
382
+ formItem?.validate('change').catch((err) => debugWarn(err))
383
+ }
384
+ },
385
+ {
386
+ flush: 'post',
387
+ deep: true,
388
+ }
389
+ )
390
+
391
+ watch(
392
+ () => expanded.value,
393
+ (val) => {
394
+ if (val) {
395
+ handleQueryChange(states.inputValue)
396
+ } else {
397
+ states.inputValue = ''
398
+ states.previousQuery = null
399
+ states.isBeforeHide = true
400
+ }
401
+ emit('visible-change', val)
402
+ }
403
+ )
404
+
405
+ /**
406
+ * @tagplus
407
+ * Sobrescrito para remover prop filterable e remote
408
+ */
409
+ watch(
410
+ // fix `Array.prototype.push/splice/..` cannot trigger non-deep watcher
411
+ // https://github.com/vuejs/vue-next/issues/2116
412
+ () => states.options.entries(),
413
+ () => {
414
+ if (!isClient) return
415
+
416
+ if (props.defaultFirstOption && filteredOptionsCount.value ) {
417
+ checkDefaultFirstOption()
418
+ }
419
+ },
420
+ {
421
+ flush: 'post',
422
+ }
423
+ )
424
+
425
+ watch(
426
+ () => states.hoveringIndex,
427
+ (val) => {
428
+ if (isNumber(val) && val > -1) {
429
+ hoverOption.value = optionsArray.value[val] || {}
430
+ } else {
431
+ hoverOption.value = {}
432
+ }
433
+ optionsArray.value.forEach((option) => {
434
+ option.hover = hoverOption.value === option
435
+ })
436
+ }
437
+ )
438
+
439
+ watchEffect(() => {
440
+ // Anything could cause options changed, then update options
441
+ // If you want to control it by condition, write here
442
+ if (states.isBeforeHide) return
443
+ updateOptions()
444
+ })
445
+
446
+ /**
447
+ * @tagplus
448
+ * Sobrescrito para remover parâmetro filterable e remote
449
+ * @param {String} val
450
+ */
451
+ const handleQueryChange = (val) => {
452
+ // Correção aqui para forçar primeeira request com createOnLoad = false
453
+ if (!states.doRequest && states.previousQuery === val) { return }
454
+ if (states.previousQuery === val) { return }
455
+
456
+ states.previousQuery = val
457
+ if (isFunction(props.remoteMethod)) {
458
+ props.remoteMethod(val)
459
+ }
460
+ if (props.defaultFirstOption) {
461
+ nextTick(checkDefaultFirstOption)
462
+ } else {
463
+ nextTick(updateHoveringIndex)
464
+ }
465
+ }
466
+
467
+ /**
468
+ * find and highlight first option as default selected
469
+ * @remark
470
+ * - if the first option in dropdown list is user-created,
471
+ * it would be at the end of the optionsArray
472
+ * so find it and set hover.
473
+ * (NOTE: there must be only one user-created option in dropdown list with query)
474
+ * - if there's no user-created option in list, just find the first one as usual
475
+ * (NOTE: exclude options that are disabled or in disabled-group)
476
+ */
477
+ const checkDefaultFirstOption = () => {
478
+ const optionsInDropdown = optionsArray.value.filter(
479
+ (n) => n.visible && !n.disabled && !n.states.groupDisabled
480
+ )
481
+ const userCreatedOption = optionsInDropdown.find((n) => n.created)
482
+ const firstOriginOption = optionsInDropdown[0]
483
+ states.hoveringIndex = getValueIndex(
484
+ optionsArray.value,
485
+ userCreatedOption || firstOriginOption
486
+ )
487
+ }
488
+
489
+ const setSelected = () => {
490
+ if (!props.multiple) {
491
+ const option = getOption(props.modelValue)
492
+ states.selectedLabel = option.currentLabel
493
+ states.selected = option
494
+ return
495
+ } else {
496
+ states.selectedLabel = ''
497
+ }
498
+ const result = []
499
+ if (isArray(props.modelValue)) {
500
+ props.modelValue.forEach((value) => {
501
+ result.push(getOption(value))
502
+ })
503
+ }
504
+ states.selected = result
505
+ }
506
+
507
+ /**
508
+ * @tagplus
509
+ * Criado para encontrar o objeto referente à value nas suggestions,
510
+ * comparando o value com o valor de item[valueKey]
511
+ * @param {String|Number} value
512
+ * @return {Object}
513
+ */
514
+ const getObjectFromSuggestions = (value) => {
515
+ // Tenta transformar modelVal Number em Object com valueKey
516
+ for (let i = 0; i < props.suggestions.length; i++) {
517
+ const item = props.suggestions[i]
518
+ if (item[props.valueKey] === value){
519
+ return {
520
+ ...item,
521
+ [props.valueKey]: JSON.parse(JSON.stringify(value)),
522
+ [props.labelKey]: item[props.labelKey]
523
+ }
524
+ }
525
+ }
526
+
527
+ return {
528
+ [props.valueKey]: value,
529
+ [props.labelKey]: value
530
+ }
531
+ }
532
+
533
+ /**
534
+ * @tagplus
535
+ * Criado para extrair dados da modelValue dos suggestions para criar a opção clicável e mostrar a label corretamente
536
+ * @param {Boolean, Object, Array, Number} modelValue
537
+ */
538
+ const getValueLabelForOption = (modelValue) => {
539
+ let value
540
+ let initialLabel = modelValue ?? false
541
+ if (typeof modelValue === 'boolean') {
542
+ value = ''
543
+ } else if (Array.isArray(modelValue)) {
544
+ value = modelValue
545
+ } else if (typeof modelValue === 'number' && props.suggestions.length && typeof props.suggestions[0] === 'object') {
546
+ const suggestionsValue = getObjectFromSuggestions(modelValue)
547
+ value = suggestionsValue[props.valueKey]
548
+ initialLabel = suggestionsValue[props.labelKey]
549
+ } else if (modelValue && typeof modelValue === 'object') {
550
+ if (!Object.keys(modelValue).length) { return {} }
551
+ if (!modelValue[props.valueKey] && modelValue[props.valueKey] !== '') {
552
+ const instance = getCurrentInstance()
553
+ console.error(`Componente '${instance.type.componentName}' option doesn't have a valueKey '${props.valueKey}' key`)
554
+ } else {
555
+ // Se mandou a label no objeto modelValue
556
+ if (modelValue[props.labelKey]) {
557
+ initialLabel = modelValue[props.labelKey]
558
+ }
559
+
560
+ value = modelValue[props.valueKey]
561
+ }
562
+ } else {
563
+ value = modelValue
564
+ }
565
+
566
+ return { value, initialLabel }
567
+ }
568
+
569
+ /**
570
+ * @tagplus
571
+ * Sobrescrito para permitir model com objeto, além de mostrar a label corretamente
572
+ * @param {*} val
573
+ */
574
+ const getOption = (val) => {
575
+ const { value, initialLabel } = getValueLabelForOption(val)
576
+
577
+ let option
578
+ if (value && initialLabel) {
579
+ option = {
580
+ value,
581
+ currentLabel: initialLabel
582
+ }
583
+ }
584
+
585
+ const isObjectValue = toRawType(value).toLowerCase() === 'object'
586
+ const isNull = toRawType(value).toLowerCase() === 'null'
587
+ const isUndefined = toRawType(value).toLowerCase() === 'undefined'
588
+ for (let i = states.cachedOptions.size - 1; i >= 0; i--) {
589
+ const cachedOption = cachedOptionsArray.value[i]
590
+ const isEqualValue = isObjectValue
591
+ ? get(cachedOption.value, props.valueKey) === get(value, props.valueKey)
592
+ : cachedOption.value === value
593
+ if (isEqualValue) {
594
+ option = {
595
+ value,
596
+ currentLabel: cachedOption.currentLabel,
597
+ isDisabled: cachedOption.isDisabled,
598
+ }
599
+ break
600
+ }
601
+ }
602
+ if (option) return option
603
+ const label = isObjectValue ? value.label : !isNull && !isUndefined ? value : ''
604
+
605
+ const newOption = {
606
+ value,
607
+ currentLabel: initialLabel || label,
608
+ }
609
+ return newOption
610
+ }
611
+
612
+ const updateHoveringIndex = () => {
613
+ if (!props.multiple) {
614
+ states.hoveringIndex = optionsArray.value.findIndex((item) => {
615
+ return getValueKey(item) === getValueKey(states.selected)
616
+ })
617
+ } else {
618
+ if (states.selected.length > 0) {
619
+ states.hoveringIndex = Math.min(
620
+ ...states.selected.map((selected) => {
621
+ return optionsArray.value.findIndex((item) => {
622
+ return getValueKey(item) === getValueKey(selected)
623
+ })
624
+ })
625
+ )
626
+ } else {
627
+ states.hoveringIndex = -1
628
+ }
629
+ }
630
+ }
631
+
632
+ const resetSelectionWidth = () => {
633
+ states.selectionWidth = selectionRef.value.getBoundingClientRect().width
634
+ }
635
+
636
+ const resetCalculatorWidth = () => {
637
+ states.calculatorWidth = calculatorRef.value.getBoundingClientRect().width
638
+ }
639
+
640
+ const resetCollapseItemWidth = () => {
641
+ states.collapseItemWidth =
642
+ collapseItemRef.value.getBoundingClientRect().width
643
+ }
644
+
645
+ const updateTooltip = () => {
646
+ tooltipRef.value?.updatePopper?.()
647
+ }
648
+
649
+ const onInputChange = () => {
650
+ if (states.inputValue.length > 0 && !expanded.value) {
651
+ expanded.value = true
652
+ }
653
+ handleQueryChange(states.inputValue)
654
+ }
655
+
656
+ /**
657
+ * @tagplus
658
+ * Sobrescrito porque é sempre remote
659
+ * @param {*} value
660
+ */
661
+ const onInput = (event) => {
662
+ states.inputValue = event.target.value
663
+ debouncedOnInputChange()
664
+ }
665
+
666
+ const debouncedOnInputChange = lodashDebounce(() => {
667
+ onInputChange()
668
+ // eslint-disable-next-line vue/no-ref-object-destructure
669
+ }, debounce.value)
670
+
671
+ /**
672
+ * @tagplus
673
+ * Sobrescrito para funcionar com model objeto e melhoria caso lista não tenha campos necessários
674
+ * @param {*} val
675
+ */
676
+ const emitChange = (val) => {
677
+ if (!isEqual(formattedValue, val)) {
678
+ emit(CHANGE_EVENT, val)
679
+ }
680
+ }
681
+
682
+ const getLastNotDisabledIndex = (value) =>
683
+ findLastIndex(value, (it) => !states.disabledOptions.has(it))
684
+
685
+ const deletePrevTag = (e) => {
686
+ if (!props.multiple) return
687
+ if (e.code === EVENT_CODE.delete) return
688
+ if (e.target.value.length <= 0) {
689
+ const value = props.modelValue.slice()
690
+ const lastNotDisabledIndex = getLastNotDisabledIndex(value)
691
+ if (lastNotDisabledIndex < 0) return
692
+ value.splice(lastNotDisabledIndex, 1)
693
+ emit(UPDATE_MODEL_EVENT, value)
694
+ emitChange(value)
695
+ }
696
+ }
697
+
698
+ const deleteTag = (event, tag) => {
699
+ const index = states.selected.indexOf(tag)
700
+ if (index > -1 && !selectDisabled.value) {
701
+ const value = props.modelValue.slice()
702
+ value.splice(index, 1)
703
+ emit(UPDATE_MODEL_EVENT, value)
704
+ emitChange(value)
705
+ emit('remove-tag', tag.value)
706
+ }
707
+ event.stopPropagation()
708
+ focus()
709
+ }
710
+
711
+ const deleteSelected = (event) => {
712
+ event.stopPropagation()
713
+ const value = props.multiple ? [] : ''
714
+ if (!isString(value)) {
715
+ for (const item of states.selected) {
716
+ if (item.isDisabled) value.push(item.value)
717
+ }
718
+ }
719
+ emit(UPDATE_MODEL_EVENT, value)
720
+ emitChange(value)
721
+ states.hoveringIndex = -1
722
+ expanded.value = false
723
+ emit('clear')
724
+ focus()
725
+ }
726
+
727
+ /**
728
+ * @tagplus
729
+ * Criado para emitir valores de acordo com a configuração da prop legacyModel,
730
+ * se recebeu um objeto ou array de objetos e legacyModel é true retornará o valor de valueKey ou array com valueKeys
731
+ * se recebeu um number/string ele será usado como valueKey para encontrar o objeto em suggestions ou retornar um objeto novo
732
+ * @param {String|Number|Object} value
733
+ * @returns {Object}
734
+ */
735
+ const normalizarValor = (value) => {
736
+ if (isObject(value) && props.legacyModel){
737
+ return value[props.valueKey]
738
+ } else if (!isObject(value) && !props.legacyModel){
739
+ return getObjectFromSuggestions(value)
740
+ }
741
+
742
+ return value
743
+ }
744
+
745
+ /**
746
+ * @tagplus
747
+ * Criado para emitir valores de acordo com a configuração da prop legacyModel
748
+ * @see normalizarValor
749
+ * @param {String|Number|Object} value
750
+ * @returns {Array|String|Number|Object}
751
+ */
752
+ const normalizarTipos = (value) => {
753
+ if (props.multiple){
754
+ return value.map(item => {
755
+ return normalizarValor(item)
756
+ })
757
+ }
758
+
759
+ return normalizarValor(value)
760
+ }
761
+
762
+ /**
763
+ * @tagplus
764
+ * Sobrescrito para corrigir a label no click
765
+ * @param {Object} option
766
+ */
767
+ const handleOptionSelect = (option) => {
768
+ if (props.multiple) {
769
+ const value = (props.modelValue || []).slice()
770
+ const optionIndex = getValueIndex(value, option.value)
771
+ if (optionIndex > -1) {
772
+ value.splice(optionIndex, 1)
773
+ } else if (props.multipleLimit <= 0 || value.length < props.multipleLimit) {
774
+ const emitValue = props.legacyModel ? option.value : option.item
775
+ value.push(emitValue)
776
+ }
777
+ // Configura o retorno do emit para objeto ou idValue
778
+ const formattedValue = normalizarTipos(value)
779
+ emit(UPDATE_MODEL_EVENT, formattedValue)
780
+ emitChange(formattedValue)
781
+ if (option.created) {
782
+ handleQueryChange('')
783
+ }
784
+ if (!props.reserveKeyword) {
785
+ states.inputValue = ''
786
+ }
787
+ } else {
788
+ // Configura o retorno do emit para objeto ou idValue
789
+ const emitValue = props.legacyModel ? option.value : option.item
790
+ const formattedValue = normalizarTipos(emitValue)
791
+ emit(UPDATE_MODEL_EVENT, formattedValue)
792
+ emitChange(formattedValue)
793
+ expanded.value = false
794
+ }
795
+ focus()
796
+ if (expanded.value) return
797
+ nextTick(() => {
798
+ scrollToOption(option)
799
+ })
800
+ }
801
+
802
+ /**
803
+ * @tagplus
804
+ * Sobrescrito para conseguir comparar itens tipo objeto dentro do array
805
+ * @param {Array} arr
806
+ * @param {*} value
807
+ * @return {Number}
808
+ */
809
+ const getValueIndex = (arr = [], value) => {
810
+ const compareValue = isObject(value) ? value[props.valueKey]: value
811
+ for (let i = 0; i < arr.length; i++) {
812
+ const el = arr[i]
813
+ const elCompareValue = isObject(el) ? el[props.valueKey] : el
814
+ if (compareValue === elCompareValue) {
815
+ return i
816
+ }
817
+ }
818
+
819
+ return -1
820
+ }
821
+
822
+ const scrollToOption = (option) => {
823
+ const targetOption = isArray(option) ? option[0] : option
824
+ let target = null
825
+
826
+ if (targetOption?.value) {
827
+ const options = optionsArray.value.filter(
828
+ (item) => item.value === targetOption.value
829
+ )
830
+ if (options.length > 0) {
831
+ target = options[0].$el
832
+ }
833
+ }
834
+
835
+ if (tooltipRef.value && target) {
836
+ const menu = tooltipRef.value?.popperRef?.contentRef?.querySelector?.(
837
+ `.${nsSelect.be('dropdown', 'wrap')}`
838
+ )
839
+ if (menu) {
840
+ scrollIntoView(menu, target)
841
+ }
842
+ }
843
+ scrollbarRef.value?.handleScroll()
844
+ }
845
+
846
+ const onOptionCreate = (vm) => {
847
+ states.options.set(vm.value, vm)
848
+ states.cachedOptions.set(vm.value, vm)
849
+ vm.disabled && states.disabledOptions.set(vm.value, vm)
850
+ }
851
+
852
+ const onOptionDestroy = (key, vm) => {
853
+ if (states.options.get(key) === vm) {
854
+ states.options.delete(key)
855
+ }
856
+ }
857
+
858
+ const {
859
+ handleCompositionStart,
860
+ handleCompositionUpdate,
861
+ handleCompositionEnd,
862
+ } = useInput((e) => onInput(e))
863
+
864
+ const popperRef = computed(() => {
865
+ return tooltipRef.value?.popperRef?.contentRef
866
+ })
867
+
868
+ const handleMenuEnter = () => {
869
+ nextTick(() => scrollToOption(states.selected))
870
+ }
871
+
872
+ const focus = () => {
873
+ inputRef.value?.focus()
874
+ }
875
+
876
+ const blur = () => {
877
+ handleClickOutside()
878
+ }
879
+
880
+ const handleClearClick = (event) => {
881
+ deleteSelected(event)
882
+ }
883
+
884
+ const handleClickOutside = (event) => {
885
+ expanded.value = false
886
+
887
+ if (isFocused.value) {
888
+ const _event = new FocusEvent('focus', event)
889
+ nextTick(() => handleBlur(_event))
890
+ }
891
+ }
892
+
893
+ const handleEsc = () => {
894
+ if (states.inputValue.length > 0) {
895
+ states.inputValue = ''
896
+ } else {
897
+ expanded.value = false
898
+ }
899
+ }
900
+
901
+ const toggleMenu = () => {
902
+ if (selectDisabled.value) return
903
+
904
+ if (states.menuVisibleOnFocus) {
905
+ // controlled by automaticDropdown
906
+ states.menuVisibleOnFocus = false
907
+ } else {
908
+ expanded.value = !expanded.value
909
+ }
910
+ }
911
+
912
+ const selectOption = () => {
913
+ if (!expanded.value) {
914
+ toggleMenu()
915
+ } else {
916
+ if (optionsArray.value[states.hoveringIndex]) {
917
+ handleOptionSelect(optionsArray.value[states.hoveringIndex])
918
+ }
919
+ }
920
+ }
921
+
922
+ const getValueKey = (item) => {
923
+ return isObject(item.value) ? get(item.value, props.valueKey) : item.value
924
+ }
925
+
926
+ const optionsAllDisabled = computed(() =>
927
+ optionsArray.value
928
+ .filter((option) => option.visible)
929
+ .every((option) => option.disabled)
930
+ )
931
+
932
+ const showTagList = computed(() => {
933
+ if (!props.multiple) {
934
+ return []
935
+ }
936
+ return props.collapseTags
937
+ ? states.selected.slice(0, props.maxCollapseTags)
938
+ : states.selected
939
+ })
940
+
941
+ const collapseTagList = computed(() => {
942
+ if (!props.multiple) {
943
+ return []
944
+ }
945
+ return props.collapseTags
946
+ ? states.selected.slice(props.maxCollapseTags)
947
+ : []
948
+ })
949
+
950
+ const navigateOptions = (direction) => {
951
+ if (!expanded.value) {
952
+ expanded.value = true
953
+ return
954
+ }
955
+ if (states.options.size === 0 || filteredOptionsCount.value === 0) return
956
+
957
+ if (!optionsAllDisabled.value) {
958
+ if (direction === 'next') {
959
+ states.hoveringIndex++
960
+ if (states.hoveringIndex === states.options.size) {
961
+ states.hoveringIndex = 0
962
+ }
963
+ } else if (direction === 'prev') {
964
+ states.hoveringIndex--
965
+ if (states.hoveringIndex < 0) {
966
+ states.hoveringIndex = states.options.size - 1
967
+ }
968
+ }
969
+ const option = optionsArray.value[states.hoveringIndex]
970
+ if (
971
+ option.disabled === true ||
972
+ option.states.groupDisabled === true ||
973
+ !option.visible
974
+ ) {
975
+ navigateOptions(direction)
976
+ }
977
+ nextTick(() => scrollToOption(hoverOption.value))
978
+ }
979
+ }
980
+
981
+ const getGapWidth = () => {
982
+ if (!selectionRef.value) return 0
983
+ const style = window.getComputedStyle(selectionRef.value)
984
+ return Number.parseFloat(style.gap || '6px')
985
+ }
986
+
987
+ // computed style
988
+ const tagStyle = computed(() => {
989
+ const gapWidth = getGapWidth()
990
+ const maxWidth =
991
+ collapseItemRef.value && props.maxCollapseTags === 1
992
+ ? states.selectionWidth - states.collapseItemWidth - gapWidth
993
+ : states.selectionWidth
994
+ return { maxWidth: `${maxWidth}px` }
995
+ })
996
+
997
+ const collapseTagStyle = computed(() => {
998
+ return { maxWidth: `${states.selectionWidth}px` }
999
+ })
1000
+
1001
+ const inputStyle = computed(() => ({
1002
+ width: `${Math.max(states.calculatorWidth, MINIMUM_INPUT_WIDTH)}px`,
1003
+ }))
1004
+
1005
+ if (props.multiple && !isArray(props.modelValue)) {
1006
+ emit(UPDATE_MODEL_EVENT, [])
1007
+ }
1008
+ if (!props.multiple && isArray(props.modelValue)) {
1009
+ emit(UPDATE_MODEL_EVENT, '')
1010
+ }
1011
+
1012
+ /**
1013
+ * @tagplus
1014
+ * Criado para apagar os itens de forma simplificada
1015
+ */
1016
+ const clearTags = () => {
1017
+ states.selectedLabel = ''
1018
+ // Faz uma requisição limpa
1019
+ states.doRequest = true
1020
+ states.previousQuery = false
1021
+ handleQueryChange('')
1022
+
1023
+ // Eventos ao limpar
1024
+ emit(UPDATE_MODEL_EVENT, null)
1025
+
1026
+ if (typeof props.modelValue === 'undefined') {
1027
+ emit('change', null, null)
1028
+ } else {
1029
+ emit('change', null, props.modelValue)
1030
+ }
1031
+ }
1032
+
1033
+ useResizeObserver(selectionRef, resetSelectionWidth)
1034
+ useResizeObserver(calculatorRef, resetCalculatorWidth)
1035
+ useResizeObserver(menuRef, updateTooltip)
1036
+ useResizeObserver(wrapperRef, updateTooltip)
1037
+ useResizeObserver(collapseItemRef, resetCollapseItemWidth)
1038
+
1039
+ onMounted(() => {
1040
+ // INICIO @tagplus
1041
+ states.doRequest = false
1042
+
1043
+ if (props.loadOnCreate) {
1044
+ states.previousQuery = false
1045
+ // Chama função do element-ui select que faz o remote method
1046
+ handleQueryChange('')
1047
+ } else {
1048
+ // Marca para fazer a requisição no primeiro clique
1049
+ states.doRequest = true
1050
+ }
1051
+ // FIM @tagplus
1052
+
1053
+ setSelected()
1054
+ })
1055
+
1056
+ return {
1057
+ inputId,
1058
+ contentId,
1059
+ nsSelect,
1060
+ nsInput,
1061
+ states,
1062
+ isFocused,
1063
+ expanded,
1064
+ optionsArray,
1065
+ hoverOption,
1066
+ selectSize,
1067
+ filteredOptionsCount,
1068
+ resetCalculatorWidth,
1069
+ updateTooltip,
1070
+ debouncedOnInputChange,
1071
+ onInput,
1072
+ deletePrevTag,
1073
+ deleteTag,
1074
+ deleteSelected,
1075
+ handleOptionSelect,
1076
+ scrollToOption,
1077
+ hasModelValue,
1078
+ shouldShowPlaceholder,
1079
+ currentPlaceholder,
1080
+ showClose,
1081
+ iconReverse,
1082
+ validateState,
1083
+ validateIcon,
1084
+ showNewOption,
1085
+ updateOptions,
1086
+ collapseTagSize,
1087
+ setSelected,
1088
+ selectDisabled,
1089
+ emptyText,
1090
+ handleCompositionStart,
1091
+ handleCompositionUpdate,
1092
+ handleCompositionEnd,
1093
+ onOptionCreate,
1094
+ onOptionDestroy,
1095
+ handleMenuEnter,
1096
+ handleFocus,
1097
+ focus,
1098
+ blur,
1099
+ handleBlur,
1100
+ handleClearClick,
1101
+ handleClickOutside,
1102
+ handleEsc,
1103
+ toggleMenu,
1104
+ selectOption,
1105
+ getValueKey,
1106
+ navigateOptions,
1107
+ dropdownMenuVisible,
1108
+ showTagList,
1109
+ collapseTagList,
1110
+ clearTags,
1111
+ suggestionsList,
1112
+ newItem,
1113
+ myId,
1114
+
1115
+ // computed style
1116
+ tagStyle,
1117
+ collapseTagStyle,
1118
+ inputStyle,
1119
+
1120
+ // DOM ref
1121
+ popperRef,
1122
+ inputRef,
1123
+ tooltipRef,
1124
+ calculatorRef,
1125
+ suffixRef,
1126
+ selectRef,
1127
+ wrapperRef,
1128
+ selectionRef,
1129
+ scrollbarRef,
1130
+ menuRef,
1131
+ collapseItemRef,
1132
+ }
1133
+ }