@bildvitta/quasar-ui-asteroid 3.9.0 → 3.10.0-beta.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.
@@ -1,13 +1,9 @@
1
1
  <template>
2
- <qas-box>
3
- <qas-input v-bind="attributes" ref="search" v-model="mx_search">
4
- <template #prepend>
5
- <q-icon color="grey-8" name="sym_r_search" />
6
- </template>
7
- </qas-input>
8
-
9
- <div ref="scrollContainer" class="overflow-auto q-mt-xs relative-position" :style="containerStyle">
10
- <component :is="component.is" v-bind="component.props">
2
+ <div>
3
+ <qas-search-input v-bind="attributes" v-model="mx_search" />
4
+
5
+ <div ref="scrollContainer" class="overflow-auto q-mt-md relative-position" :style="containerStyle">
6
+ <component :is="component.is" v-bind="component.props" class="q-mr-sm">
11
7
  <slot v-if="mx_hasFilteredOptions" />
12
8
  </component>
13
9
 
@@ -28,7 +24,7 @@
28
24
  <q-spinner color="grey" size="3em" />
29
25
  </q-inner-loading>
30
26
  </div>
31
- </qas-box>
27
+ </div>
32
28
  </template>
33
29
 
34
30
  <script>
@@ -110,12 +106,10 @@ export default {
110
106
  computed: {
111
107
  attributes () {
112
108
  return {
113
- clearable: true,
109
+ ref: 'search',
114
110
  disable: this.isDisabled,
115
- debounce: this.useLazyLoading ? 1200 : 0,
116
- outlined: true,
111
+ useDebounce: this.useLazyLoading,
117
112
  placeholder: this.placeholder,
118
- hideBottomSpace: true,
119
113
  error: this.mx_hasFetchError,
120
114
  loading: this.mx_isFetching
121
115
  }
@@ -195,7 +189,7 @@ export default {
195
189
  await this.mx_filterOptionsByStore(value)
196
190
 
197
191
  this.$refs.infiniteScrollRef.resume()
198
- this.$refs.search.focus()
192
+ this.$refs.search.input.focus()
199
193
 
200
194
  return
201
195
  }
@@ -222,12 +216,7 @@ export default {
222
216
  },
223
217
 
224
218
  created () {
225
- if (this.useLazyLoading) return
226
-
227
- this.mx_filteredOptions = this.list
228
- this.fuse = new Fuse(this.list, this.defaultFuseOptions)
229
-
230
- this.setListWatcher()
219
+ this.setSearchMethod()
231
220
  },
232
221
 
233
222
  methods: {
@@ -258,6 +247,23 @@ export default {
258
247
 
259
248
  this.filterOptionsByFuse(this.mx_search)
260
249
  }, { deep: true })
250
+ },
251
+
252
+ setSearchMethod () {
253
+ this.useLazyLoading ? this.setLazyLoading() : this.setFuse()
254
+ },
255
+
256
+ setFuse () {
257
+ const list = [...this.list]
258
+
259
+ this.mx_filteredOptions = list
260
+ this.fuse = new Fuse(list, this.defaultFuseOptions)
261
+
262
+ this.setListWatcher()
263
+ },
264
+
265
+ setLazyLoading () {
266
+ this.mx_setCachedOptions('list')
261
267
  }
262
268
  }
263
269
  }
@@ -0,0 +1,122 @@
1
+ <template>
2
+ <div class="qas-filter-input">
3
+ <qas-input ref="input" v-model="model" class="bg-white q-px-sm rounded-borders-sm shadow-2" v-bind="$attrs" data-cy="search-input" :debounce="debounce" dense hide-bottom-space input-class="ellipsis text-grey-8" :outlined="false" type="search">
4
+ <template #prepend>
5
+ <q-icon v-if="useSearchOnType" color="grey-8" name="sym_r_search" />
6
+ <qas-btn v-else color="grey-9" icon="sym_r_search" variant="tertiary" @click="$emit('filter')" />
7
+ </template>
8
+
9
+ <template #append>
10
+ <qas-btn v-if="hasSearch" color="grey-9" icon="sym_r_clear" variant="tertiary" @click="clear" />
11
+
12
+ <slot name="after-clear" />
13
+ </template>
14
+ </qas-input>
15
+ </div>
16
+ </template>
17
+
18
+ <script>
19
+ export default {
20
+ name: 'QasSearchInput',
21
+
22
+ inheritAttrs: false,
23
+
24
+ props: {
25
+ modelValue: {
26
+ default: '',
27
+ type: String
28
+ },
29
+
30
+ useDebounce: {
31
+ default: true,
32
+ type: Boolean
33
+ },
34
+
35
+ useSearchOnType: {
36
+ default: true,
37
+ type: Boolean
38
+ }
39
+ },
40
+
41
+ emits: [
42
+ 'clear',
43
+ 'filter',
44
+ 'update:modelValue'
45
+ ],
46
+
47
+ computed: {
48
+ debounce () {
49
+ return this.useDebounce ? '1200' : ''
50
+ },
51
+
52
+ hasSearch () {
53
+ return !!this.model.length
54
+ },
55
+
56
+ input () {
57
+ return this.$refs.input
58
+ },
59
+
60
+ model: {
61
+ get () {
62
+ return this.modelValue
63
+ },
64
+
65
+ set (value) {
66
+ this.$emit('update:modelValue', value)
67
+ }
68
+ }
69
+ },
70
+
71
+ methods: {
72
+ clear () {
73
+ this.$emit('clear', this.modelValue)
74
+ this.$emit('update:modelValue', '')
75
+ }
76
+ }
77
+ }
78
+ </script>
79
+
80
+ <style lang="scss">
81
+ .qas-filter-input {
82
+ position: relative;
83
+
84
+ .q-field {
85
+ &::before {
86
+ border: 2px solid transparent;
87
+ border-radius: var(--qas-generic-border-radius);
88
+ bottom: 0;
89
+ content: '';
90
+ left: 0;
91
+ pointer-events: none;
92
+ position: absolute;
93
+ right: 0;
94
+ top: 0;
95
+ transition: border-color var(--qas-generic-transition);
96
+ }
97
+
98
+ &--dense .q-field__prepend {
99
+ padding-right: var(--qas-spacing-xs);
100
+ }
101
+
102
+ &--dense .q-field__append {
103
+ padding-left: var(--qas-spacing-sm);
104
+ }
105
+
106
+ &--focused::before {
107
+ border-color: var(--q-primary);
108
+ color: var(--q-primary);
109
+ }
110
+
111
+ &__control::after,
112
+ &__control::before {
113
+ display: none !important;
114
+ }
115
+
116
+ &__native {
117
+ padding-bottom: var(--qas-spacing-sm);
118
+ padding-top: var(--qas-spacing-sm);
119
+ }
120
+ }
121
+ }
122
+ </style>
@@ -0,0 +1,43 @@
1
+ type: component
2
+
3
+ meta:
4
+ desc: Componente de input para pesquisa / filtros.
5
+
6
+ props:
7
+ model-value:
8
+ desc: Model do componente, utilizado para v-model.
9
+ default: ''
10
+ type: String
11
+ model: true
12
+
13
+ use-debounce:
14
+ desc: Controla debounce.
15
+ default: true
16
+ type: Boolean
17
+
18
+ use-search-on-type:
19
+ desc: Controla se vai habilitar o botão de pesquisa ao clicar.
20
+ default: true
21
+ type: Boolean
22
+
23
+ slots:
24
+ after-clear:
25
+ desc: Slot para acessar área após o botão de limpar pesquisa.
26
+
27
+ events:
28
+ '@update:clear -> function(oldValue)':
29
+ desc: Dispara quando é clicado no botão de limpar pesquisa.
30
+ params:
31
+ oldValue:
32
+ desc: Valor do model antes de ser limpo.
33
+ type: String
34
+
35
+ '@update:filter -> function()':
36
+ desc: Dispara quando a prop "useSearchOnType" for "false" e é clicado no botão de pesquisar.
37
+
38
+ '@update:model-value -> function(value)':
39
+ desc: Dispara quando o model-value altera, também usado para v-model.
40
+ params:
41
+ value:
42
+ desc: Novo valor do model.
43
+ type: String
@@ -43,11 +43,6 @@ export default {
43
43
  type: Object
44
44
  },
45
45
 
46
- labelKey: {
47
- default: '',
48
- type: String
49
- },
50
-
51
46
  modelValue: {
52
47
  default: () => [],
53
48
  type: [Array, Object, String, Number, Boolean]
@@ -63,9 +58,9 @@ export default {
63
58
  type: Array
64
59
  },
65
60
 
66
- valueKey: {
67
- default: '',
68
- type: String
61
+ useFetchOptionsOnCreate: {
62
+ default: true,
63
+ type: Boolean
69
64
  },
70
65
 
71
66
  useSearch: {
@@ -114,10 +109,6 @@ export default {
114
109
  return this.hasFuse || this.useLazyLoading
115
110
  },
116
111
 
117
- defaultOptions () {
118
- return this.mx_handleOptions(this.options)
119
- },
120
-
121
112
  hasError () {
122
113
  return this.mx_hasFetchError || this.$attrs.error
123
114
  },
@@ -153,7 +144,7 @@ export default {
153
144
 
154
145
  watch: {
155
146
  defaultFuseOptions () {
156
- this.setFuse()
147
+ if (this.hasFuse) this.setFuse()
157
148
  },
158
149
 
159
150
  options: {
@@ -162,7 +153,7 @@ export default {
162
153
 
163
154
  if (this.fuse || this.hasFuse) this.setFuse()
164
155
 
165
- this.mx_filteredOptions = this.defaultOptions
156
+ this.mx_filteredOptions = this.options
166
157
  },
167
158
 
168
159
  immediate: true
@@ -170,15 +161,12 @@ export default {
170
161
  },
171
162
 
172
163
  created () {
173
- this.setFuse()
174
- this.useLazyLoading && this.mx_setFetchOptions('')
164
+ this.setSearchMethod()
175
165
  },
176
166
 
177
167
  methods: {
178
168
  setFuse () {
179
- if (this.hasFuse) {
180
- this.fuse = new Fuse(this.defaultOptions, this.defaultFuseOptions)
181
- }
169
+ this.fuse = new Fuse(this.options, this.defaultFuseOptions)
182
170
  },
183
171
 
184
172
  async onFilter (value, update) {
@@ -195,13 +183,25 @@ export default {
195
183
 
196
184
  filterOptionsByFuse (value) {
197
185
  if (value === '') {
198
- this.mx_filteredOptions = this.defaultOptions
186
+ this.mx_filteredOptions = this.options
199
187
  return
200
188
  }
201
189
 
202
190
  const results = this.fuse.search(value)
203
191
 
204
192
  this.mx_filteredOptions = this.mx_getNormalizedFuseResults(results)
193
+ },
194
+
195
+ setLazyLoading () {
196
+ this.mx_setCachedOptions('options')
197
+
198
+ if (this.useFetchOptionsOnCreate) this.mx_setFetchOptions('')
199
+ },
200
+
201
+ setSearchMethod () {
202
+ if (this.useLazyLoading) return this.setLazyLoading()
203
+
204
+ if (this.hasFuse) this.setFuse()
205
205
  }
206
206
  }
207
207
  }
@@ -71,6 +71,11 @@ props:
71
71
  type: String
72
72
  examples: [value-key="uuid"]
73
73
 
74
+ use-fetch-options-on-create:
75
+ desc: Controla se o componente vai fazer um fetch das opções assim que o mesmo é criado (caso tenha lazy loading ativado).
76
+ default: true
77
+ type: Boolean
78
+
74
79
  use-search:
75
80
  desc: Controla se vai ou não ter campo de busca no select.
76
81
  default: undefined
@@ -1,18 +1,20 @@
1
1
  <template>
2
- <qas-search-box v-model:results="results" class="q-pa-md" :fuse-options="fuseOptions" :list="sortedList">
2
+ <qas-search-box v-model:results="results" v-bind="defaultSearchBoxProps" :list="sortedList">
3
3
  <template #default>
4
4
  <q-list separator>
5
- <q-item v-for="result in results" :key="result.value">
5
+ <q-item v-for="result in results" :key="result.value" class="q-px-none">
6
6
  <slot v-bind="slotData" :item="result" name="item">
7
7
  <slot name="item-section" :result="result">
8
- <q-item-section class="items-start text-bold">
9
- <div :class="labelClass" @click="onClickLabel({ item: result, index })">{{ result.label }}</div>
8
+ <q-item-section>
9
+ <div :class="labelClass" @click="onClickLabel({ item: result, index })">
10
+ {{ result.label }}
11
+ </div>
10
12
  </q-item-section>
11
13
  </slot>
12
14
 
13
15
  <q-item-section avatar>
14
16
  <slot :item="result" name="item-action" v-bind="slotData">
15
- <qas-btn v-bind="getButtonProps(result)" @click="handleClick(result)" />
17
+ <qas-btn :disable="readonly" :use-label-on-small-screen="false" v-bind="getButtonProps(result)" @click="handleClick(result)" />
16
18
  </slot>
17
19
  </q-item-section>
18
20
  </slot>
@@ -36,19 +38,21 @@ export default {
36
38
  QasSearchBox
37
39
  },
38
40
 
41
+ inheritAttrs: false,
42
+
39
43
  props: {
40
- deleteOnly: {
41
- type: Boolean
44
+ addLabel: {
45
+ type: String,
46
+ default: 'Adicionar'
42
47
  },
43
48
 
44
- fuseOptions: {
45
- default: () => ({ keys: ['label'] }),
46
- type: Object
49
+ deleteLabel: {
50
+ type: String,
51
+ default: 'Excluir'
47
52
  },
48
53
 
49
- list: {
50
- default: () => [],
51
- type: Array
54
+ deleteOnly: {
55
+ type: Boolean
52
56
  },
53
57
 
54
58
  modelValue: {
@@ -56,6 +60,15 @@ export default {
56
60
  default: () => []
57
61
  },
58
62
 
63
+ readonly: {
64
+ type: Boolean
65
+ },
66
+
67
+ searchBoxProps: {
68
+ type: Object,
69
+ default: () => ({})
70
+ },
71
+
59
72
  useClickableLabel: {
60
73
  type: Boolean
61
74
  }
@@ -77,10 +90,26 @@ export default {
77
90
  },
78
91
 
79
92
  computed: {
93
+ defaultSearchBoxProps () {
94
+ return {
95
+ fuseOptions: { keys: ['label'] },
96
+
97
+ ...this.searchBoxProps
98
+ }
99
+ },
100
+
101
+ hasLazyLoading () {
102
+ return this.defaultSearchBoxProps.useLazyLoading
103
+ },
104
+
80
105
  labelClass () {
81
106
  return this.useClickableLabel && 'cursor-pointer'
82
107
  },
83
108
 
109
+ list () {
110
+ return this.defaultSearchBoxProps.list || []
111
+ },
112
+
84
113
  slotData () {
85
114
  return {
86
115
  add: this.add,
@@ -94,9 +123,9 @@ export default {
94
123
  watch: {
95
124
  list: {
96
125
  handler (value) {
97
- if (!this.sortedList.length) {
98
- this.sortedList = value
99
- }
126
+ if (!this.hasLazyLoading) return this.sortList()
127
+
128
+ this.sortedList = [...value]
100
129
  },
101
130
 
102
131
  immediate: true
@@ -113,7 +142,7 @@ export default {
113
142
  },
114
143
 
115
144
  created () {
116
- this.handleList()
145
+ if (!this.hasLazyLoading) this.handleList()
117
146
  },
118
147
 
119
148
  methods: {
@@ -128,9 +157,10 @@ export default {
128
157
  const isSelected = this.values.includes(value)
129
158
 
130
159
  return {
160
+ label: isSelected ? this.deleteLabel : this.addLabel,
131
161
  variant: 'tertiary',
132
162
  color: isSelected ? 'grey-9' : 'primary',
133
- icon: isSelected ? 'sym_r_remove' : 'sym_r_add'
163
+ icon: isSelected ? 'sym_r_delete' : 'sym_r_add'
134
164
  }
135
165
  },
136
166
 
@@ -7,20 +7,20 @@ meta:
7
7
  desc: Componente para selecionar dentro de uma lista com pesquisa utilizando o `QasSearchBox`.
8
8
 
9
9
  props:
10
+ add-label:
11
+ desc: Label do botão de adicionar.
12
+ default: Adicionar
13
+ type: String
14
+
15
+ delete-label:
16
+ desc: Label do botão de deletar.
17
+ default: Excluir
18
+ type: String
19
+
10
20
  delete-only:
11
21
  desc: Caso o "modelValue" tenha valor, ele remove tudo na lista que não esteja dentro do modelValue.
12
22
  type: Boolean
13
23
 
14
- fuse-options:
15
- desc: Propriedade que será repassada para o "QasSearchBox".
16
- default: "{ keys: ['label'] }"
17
- type: Object
18
-
19
- list:
20
- desc: Lista que será feita a seleção e pesquisa.
21
- default: []
22
- type: Array
23
-
24
24
  model-value:
25
25
  desc: Model do componente, que será controlado tudo que foi selecionado.
26
26
  default: []
@@ -28,6 +28,16 @@ props:
28
28
  examples: [v-model="value"]
29
29
  model: true
30
30
 
31
+ readonly:
32
+ desc: Habilita modo visualização.
33
+ default: false
34
+ type: Boolean
35
+
36
+ search-box-props:
37
+ desc: Repassa props para o componente "QasSearchBox"
38
+ default: {}
39
+ type: Object
40
+
31
41
  use-clickable-label:
32
42
  desc: Habilita "cursor-pointer" no label e evento "click-label".
33
43
  type: Boolean
@@ -1,6 +1,5 @@
1
1
  import { decamelize } from 'humps'
2
2
  import { isEqual } from 'lodash'
3
- import { getNormalizedOptions } from '../helpers'
4
3
  import { getAction } from '@bildvitta/store-adapter'
5
4
 
6
5
  export default {
@@ -20,11 +19,11 @@ export default {
20
19
  type: String
21
20
  },
22
21
 
23
- useLazyLoading: {
22
+ fetching: {
24
23
  type: Boolean
25
24
  },
26
25
 
27
- fetching: {
26
+ useLazyLoading: {
28
27
  type: Boolean
29
28
  }
30
29
  },
@@ -38,12 +37,15 @@ export default {
38
37
 
39
38
  data () {
40
39
  return {
40
+ mx_cachedOptions: [],
41
+ mx_fetchCount: 0,
41
42
  mx_filteredOptions: [],
43
+ mx_foundDuplicatedOptions: [],
44
+ mx_fromSearch: false,
42
45
  mx_hasFetchError: false,
43
46
  mx_isFetching: false,
44
47
  mx_isScrolling: false,
45
48
  mx_search: '',
46
- mx_fetchCount: 0,
47
49
  mx_pagination: {
48
50
  page: 1,
49
51
  lastPage: null,
@@ -77,10 +79,6 @@ export default {
77
79
  return !!this.mx_filteredOptions.length
78
80
  },
79
81
 
80
- mx_isFilterByFuse () {
81
- return !this.useLazyLoading
82
- },
83
-
84
82
  mx_isMultiple () {
85
83
  return this.$attrs.multiple || this.$attrs.multiple === ''
86
84
  }
@@ -101,11 +99,17 @@ export default {
101
99
  methods: {
102
100
  async mx_filterOptionsByStore (search) {
103
101
  this.mx_resetFilter(search)
102
+
103
+ this.mx_fromSearch = !!search
104
+
104
105
  await this.mx_setFetchOptions()
106
+
107
+ if (this.mx_cachedOptions.length && !search) this.mx_setInitialCachedOptions()
105
108
  },
106
109
 
107
110
  mx_resetFilter (search) {
108
111
  this.mx_filteredOptions = []
112
+ this.mx_foundDuplicatedOptions = []
109
113
  this.mx_search = search
110
114
  this.mx_pagination = {
111
115
  page: 1,
@@ -180,14 +184,8 @@ export default {
180
184
 
181
185
  this.$emit('fetch-options-success', data)
182
186
 
183
- return this.mx_handleOptions(results)
187
+ return this.mx_getNonDuplicatedOptions(results)
184
188
  } catch (error) {
185
- this.$qas.logger.group(
186
- `Mixin: searchFilterMixin - mx_fetchOptions -> exceção da action ${this.entity}/fetchFieldOptions`,
187
- [error],
188
- { error: true }
189
- )
190
-
191
189
  this.mx_hasFetchError = true
192
190
  this.$emit('fetch-options-error', error)
193
191
 
@@ -199,7 +197,48 @@ export default {
199
197
  },
200
198
 
201
199
  async mx_setFetchOptions () {
202
- this.mx_filteredOptions = await this.mx_fetchOptions()
200
+ const options = await this.mx_fetchOptions()
201
+
202
+ this.mx_filteredOptions.push(...options)
203
+ },
204
+
205
+ mx_setInitialCachedOptions () {
206
+ this.mx_cachedOptions.forEach(cachedOption => {
207
+ const hasOption = this.mx_filteredOptions.find(filteredOption => {
208
+ return filteredOption.value === cachedOption.value
209
+ })
210
+
211
+ if (!hasOption && this.mx_filteredOptions.length) {
212
+ this.mx_filteredOptions.unshift(cachedOption)
213
+ }
214
+ })
215
+ },
216
+
217
+ mx_getNonDuplicatedOptions (options = []) {
218
+ if (this.mx_fromSearch) {
219
+ this.mx_fromSearch = false
220
+
221
+ return options
222
+ }
223
+
224
+ if (!options.length) return []
225
+
226
+ const nonDuplicatedOptions = [...options]
227
+
228
+ this.mx_cachedOptions.forEach(cachedOption => {
229
+ if (this.mx_foundDuplicatedOptions.includes(cachedOption.value)) return
230
+
231
+ const duplicatedOptionIndex = nonDuplicatedOptions.findIndex(option => {
232
+ return option.value === cachedOption.value
233
+ })
234
+
235
+ if (~duplicatedOptionIndex) {
236
+ this.mx_foundDuplicatedOptions.push(cachedOption.value)
237
+ nonDuplicatedOptions.splice(duplicatedOptionIndex, 1)
238
+ }
239
+ })
240
+
241
+ return nonDuplicatedOptions
203
242
  },
204
243
 
205
244
  mx_canFetchOptions () {
@@ -210,18 +249,6 @@ export default {
210
249
  return hasMorePages && !this.mx_isFetching && !this.mx_isScrolling && this.useLazyLoading
211
250
  },
212
251
 
213
- mx_handleOptions (options) {
214
- if (this.labelKey && this.valueKey) {
215
- return getNormalizedOptions({
216
- options,
217
- label: this.labelKey,
218
- value: this.valueKey
219
- })
220
- }
221
-
222
- return options
223
- },
224
-
225
252
  mx_getMissingPropsMessage (prop) {
226
253
  return `A propriedade "${prop}" é obrigatória quando a propriedade "useLazyLoading" está ativa.`
227
254
  },
@@ -232,6 +259,15 @@ export default {
232
259
 
233
260
  mx_getNormalizedFuseResults (results = []) {
234
261
  return results.map(({ item }) => item)
262
+ },
263
+
264
+ mx_setCachedOptions (model) {
265
+ this.$watch(model, options => {
266
+ if (!options?.length) return
267
+
268
+ this.mx_filteredOptions = [...options]
269
+ this.mx_cachedOptions = [...options]
270
+ }, { immediate: true })
235
271
  }
236
272
  }
237
273
  }