@bildvitta/quasar-ui-asteroid 3.0.0-beta.7 → 3.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 (106) hide show
  1. package/dist/api/QasAppBar.json +0 -4
  2. package/dist/api/QasBtn.json +2 -1
  3. package/dist/api/QasCard.json +13 -9
  4. package/dist/api/QasDateTimeInput.json +12 -12
  5. package/dist/api/QasDialog.json +6 -2
  6. package/dist/api/QasFilters.json +4 -4
  7. package/dist/api/QasFormGenerator.json +33 -2
  8. package/dist/api/QasFormView.json +43 -15
  9. package/dist/api/QasGridGenerator.json +5 -4
  10. package/dist/api/QasInput.json +1 -1
  11. package/dist/api/QasListItems.json +18 -17
  12. package/dist/api/QasListView.json +21 -7
  13. package/dist/api/QasNestedFields.json +13 -3
  14. package/dist/api/QasNumericInput.json +10 -10
  15. package/dist/api/QasPasswordInput.json +1 -1
  16. package/dist/api/QasSearchBox.json +85 -3
  17. package/dist/api/QasSelect.json +81 -14
  18. package/dist/api/QasSelectList.json +16 -14
  19. package/dist/api/QasSignaturePad.json +1 -1
  20. package/dist/api/QasSingleView.json +13 -4
  21. package/dist/api/QasTabsGenerator.json +5 -2
  22. package/dist/api/QasUploader.json +5 -0
  23. package/dist/asteroid.cjs.css +1 -1
  24. package/dist/asteroid.cjs.js +1522 -706
  25. package/dist/asteroid.cjs.min.js +2 -2
  26. package/dist/asteroid.esm.css +1 -1
  27. package/dist/asteroid.esm.js +1525 -709
  28. package/dist/asteroid.esm.min.js +2 -2
  29. package/dist/asteroid.umd.css +1 -1
  30. package/dist/asteroid.umd.js +1525 -710
  31. package/dist/asteroid.umd.min.js +2 -2
  32. package/dist/vetur/asteroid-attributes.json +176 -100
  33. package/dist/vetur/asteroid-tags.json +60 -41
  34. package/package.json +1 -1
  35. package/src/components/actions-menu/QasActionsMenu.vue +2 -8
  36. package/src/components/app-bar/QasAppBar.vue +16 -12
  37. package/src/components/app-bar/QasAppBar.yml +0 -4
  38. package/src/components/avatar/QasAvatar.vue +0 -4
  39. package/src/components/btn/QasBtn.vue +5 -8
  40. package/src/components/btn/QasBtn.yml +2 -1
  41. package/src/components/card/QasCard.vue +18 -9
  42. package/src/components/card/QasCard.yml +13 -9
  43. package/src/components/date-time-input/QasDateTimeInput.vue +39 -41
  44. package/src/components/date-time-input/QasDateTimeInput.yml +11 -12
  45. package/src/components/delete/QasDelete.vue +15 -1
  46. package/src/components/dialog/QasDialog.vue +26 -3
  47. package/src/components/dialog/QasDialog.yml +6 -3
  48. package/src/components/dialog-router/QasDialogRouter.vue +1 -1
  49. package/src/components/field/QasField.vue +15 -14
  50. package/src/components/filters/QasFilters.vue +27 -10
  51. package/src/components/filters/QasFilters.yml +4 -4
  52. package/src/components/form-generator/QasFormGenerator.vue +87 -12
  53. package/src/components/form-generator/QasFormGenerator.yml +16 -2
  54. package/src/components/form-view/QasFormView.vue +138 -56
  55. package/src/components/form-view/QasFormView.yml +39 -15
  56. package/src/components/grid-generator/QasGridGenerator.vue +23 -7
  57. package/src/components/grid-generator/QasGridGenerator.yml +5 -4
  58. package/src/components/input/QasInput.vue +37 -21
  59. package/src/components/input/QasInput.yml +1 -1
  60. package/src/components/layout/QasLayout.vue +4 -0
  61. package/src/components/list-items/QasListItems.vue +15 -23
  62. package/src/components/list-items/QasListItems.yml +14 -15
  63. package/src/components/list-view/QasListView.vue +45 -24
  64. package/src/components/list-view/QasListView.yml +19 -7
  65. package/src/components/map/QasMap.vue +5 -5
  66. package/src/components/nested-fields/QasNestedFields.vue +29 -21
  67. package/src/components/nested-fields/QasNestedFields.yml +9 -3
  68. package/src/components/numeric-input/QasNumericInput.vue +14 -14
  69. package/src/components/numeric-input/QasNumericInput.yml +10 -10
  70. package/src/components/page-header/QasPageHeader.vue +14 -11
  71. package/src/components/password-input/QasPasswordInput.vue +17 -16
  72. package/src/components/password-input/QasPasswordInput.yml +1 -1
  73. package/src/components/profile/QasProfile.vue +1 -1
  74. package/src/components/search-box/QasSearchBox.vue +138 -42
  75. package/src/components/search-box/QasSearchBox.yml +69 -2
  76. package/src/components/select/QasSelect.vue +63 -53
  77. package/src/components/select/QasSelect.yml +64 -13
  78. package/src/components/select-list/QasSelectList.vue +11 -27
  79. package/src/components/select-list/QasSelectList.yml +13 -14
  80. package/src/components/signature-pad/QasSignaturePad.yml +1 -1
  81. package/src/components/signature-uploader/QasSignatureUploader.vue +7 -5
  82. package/src/components/single-view/QasSingleView.vue +22 -6
  83. package/src/components/single-view/QasSingleView.yml +11 -4
  84. package/src/components/table-generator/QasTableGenerator.vue +13 -2
  85. package/src/components/tabs-generator/QasTabsGenerator.vue +2 -2
  86. package/src/components/tabs-generator/QasTabsGenerator.yml +2 -2
  87. package/src/components/text-truncate/QasTextTruncate.vue +1 -1
  88. package/src/components/uploader/QasUploader.vue +62 -15
  89. package/src/components/uploader/QasUploader.yml +5 -0
  90. package/src/helpers/camelize-fields-name.js +15 -0
  91. package/src/helpers/filters.js +2 -0
  92. package/src/helpers/get-normalized-options.js +20 -0
  93. package/src/helpers/handle-process.js +13 -0
  94. package/src/helpers/index.js +3 -0
  95. package/src/mixins/generator.js +10 -2
  96. package/src/mixins/index.js +2 -0
  97. package/src/mixins/search-filter.js +227 -0
  98. package/src/mixins/view.js +35 -13
  99. package/src/pages/Forbidden.vue +3 -1
  100. package/src/pages/NotFound.vue +3 -1
  101. package/src/pages/ServerError.vue +3 -1
  102. package/src/pages/Unauthorized.vue +28 -0
  103. package/src/plugins/index.js +4 -2
  104. package/src/plugins/logger/Logger.js +44 -0
  105. package/src/plugins/logger/Logger.yml +9 -0
  106. package/src/vue-plugin.js +6 -3
@@ -1,19 +1,17 @@
1
1
  <template>
2
- <div>
3
- <qas-input v-model="model" :bottom-slots="false" color="negative" v-bind="$attrs" remove-error-on-type :type="type">
4
- <template #append>
5
- <q-icon class="cursor-pointer" :color="iconColor" :name="icon" @click="toggle" />
6
- </template>
7
-
8
- <template v-for="(_, name) in $slots" #[name]="context">
9
- <slot :name="name" v-bind="context || {}" />
10
- </template>
11
-
12
- <template v-if="!hideStrengthChecker" #hint>
13
- <qas-password-strength-checker v-bind="strengthCheckerProps" :password="model" />
14
- </template>
15
- </qas-input>
16
- </div>
2
+ <qas-input v-model="model" :bottom-slots="false" v-bind="$attrs" :type="type" use-remove-error-on-type>
3
+ <template #append>
4
+ <q-icon class="cursor-pointer" :color="iconColor" :name="icon" @click="toggle" />
5
+ </template>
6
+
7
+ <template v-for="(_, name) in $slots" #[name]="context">
8
+ <slot :name="name" v-bind="context || {}" />
9
+ </template>
10
+
11
+ <template v-if="useStrengthChecker" #hint>
12
+ <qas-password-strength-checker v-bind="strengthCheckerProps" :password="model" />
13
+ </template>
14
+ </qas-input>
17
15
  </template>
18
16
 
19
17
  <script>
@@ -29,8 +27,11 @@ export default {
29
27
 
30
28
  mixins: [passwordMixin],
31
29
 
30
+ inheritAttrs: false,
31
+
32
32
  props: {
33
- hideStrengthChecker: {
33
+ useStrengthChecker: {
34
+ default: true,
34
35
  type: Boolean
35
36
  },
36
37
 
@@ -70,7 +70,7 @@ props:
70
70
  type: String
71
71
 
72
72
  use-strength-checker:
73
- desc: Controla exibição do componente `QasStrenghtChecker`.
73
+ desc: Controla exibição do componente `QasStrengthChecker`.
74
74
  default: true
75
75
  type: Boolean
76
76
 
@@ -13,7 +13,7 @@
13
13
  </div>
14
14
  </div>
15
15
  <slot name="grid">
16
- <qas-grid-generator class="col-lg-7 col-xs-12 items-center" :columns="columns" :fields="filterObject(fields, list)" hide-empty-result :result="result">
16
+ <qas-grid-generator class="col-lg-7 col-xs-12 items-center" :columns="columns" :fields="filterObject(fields, list)" :result="result" :use-empty-result="false">
17
17
  <template v-for="(_, name) in $slots" #[name]="context">
18
18
  <slot :name="name" v-bind="context || {}" />
19
19
  </template>
@@ -1,42 +1,64 @@
1
1
  <template>
2
2
  <qas-box>
3
- <q-input v-model="search" clearable :disable="!list.length" outlined :placeholder="placeholder">
3
+ <qas-input v-bind="attributes" ref="search" v-model="mx_search">
4
4
  <template #append>
5
5
  <q-icon color="primary" name="o_search" />
6
6
  </template>
7
- </q-input>
7
+ </qas-input>
8
8
 
9
- <div class="overflow-auto q-mt-xs relative-position" :style="contentStyle">
10
- <slot v-if="hasResults" />
9
+ <div ref="scrollContainer" class="overflow-auto q-mt-xs relative-position" :style="containerStyle">
10
+ <component :is="component.is" v-bind="component.props">
11
+ <slot v-if="mx_hasFilteredOptions" />
12
+ </component>
11
13
 
12
- <slot v-else-if="useEmptySlot" name="empty-result">
14
+ <slot v-if="showSpinnerDots" name="loading">
15
+ <div class="flex justify-center q-pb-sm">
16
+ <q-spinner-dots color="primary" size="20px" />
17
+ </div>
18
+ </slot>
19
+
20
+ <slot v-if="showEmptyResult" name="empty-result">
13
21
  <div class="absolute-center text-center">
14
22
  <q-icon class="q-mb-sm text-center" color="primary" name="o_search" size="38px" />
15
- <div>Não resultados disponíveis.</div>
23
+ <div>{{ emptyResultText }}</div>
16
24
  </div>
17
25
  </slot>
26
+
27
+ <q-inner-loading :showing="showInnerLoading">
28
+ <q-spinner color="grey" size="3em" />
29
+ </q-inner-loading>
18
30
  </div>
19
31
  </qas-box>
20
32
  </template>
21
33
 
22
34
  <script>
35
+ import { QInfiniteScroll } from 'quasar'
23
36
  import Fuse from 'fuse.js'
24
37
 
25
38
  import QasBox from '../box/QasBox.vue'
39
+ import { searchFilterMixin } from '../../mixins'
26
40
 
27
41
  export default {
28
42
  name: 'QasSearchBox',
29
43
 
30
44
  components: {
31
- QasBox
45
+ QasBox,
46
+ QInfiniteScroll
32
47
  },
33
48
 
49
+ mixins: [searchFilterMixin],
50
+
34
51
  props: {
35
52
  emptyListHeight: {
36
53
  default: '100px',
37
54
  type: String
38
55
  },
39
56
 
57
+ emptyResultText: {
58
+ default: 'Não há resultados disponíveis.',
59
+ type: String
60
+ },
61
+
40
62
  fuseOptions: {
41
63
  default: () => ({}),
42
64
  type: Object
@@ -81,88 +103,162 @@ export default {
81
103
 
82
104
  data () {
83
105
  return {
84
- fuse: null,
85
- searchResults: this.list,
86
- search: ''
106
+ fuse: null
87
107
  }
88
108
  },
89
109
 
90
110
  computed: {
91
- contentStyle () {
92
- return { height: this.list.length ? this.height : this.emptyListHeight }
111
+ attributes () {
112
+ return {
113
+ clearable: true,
114
+ disable: this.isDisabled,
115
+ debounce: this.useLazyLoading ? 500 : 0,
116
+ outlined: true,
117
+ placeholder: this.placeholder,
118
+ hideBottomSpace: true,
119
+ error: this.mx_hasFetchError,
120
+ loading: this.mx_isFetching
121
+ }
122
+ },
123
+
124
+ containerStyle () {
125
+ return { height: this.containerHeight }
126
+ },
127
+
128
+ hasNoOptionsOnFirstFetch () {
129
+ return this.mx_fetchCount === 1 && !this.mx_hasFilteredOptions
130
+ },
131
+
132
+ containerHeight () {
133
+ const hasEmptyList = (!this.list.length && !this.useLazyLoading) || this.hasNoOptionsOnFirstFetch
134
+
135
+ return hasEmptyList ? this.emptyListHeight : this.height
136
+ },
137
+
138
+ component () {
139
+ const infiniteScrollProps = {
140
+ offset: 100,
141
+ scrollTarget: this.$refs.scrollContainer,
142
+ ref: 'infiniteScrollRef'
143
+ }
144
+
145
+ return {
146
+ is: this.useLazyLoading ? 'q-infinite-scroll' : 'div',
147
+ props: {
148
+ ...(this.useLazyLoading && infiniteScrollProps),
149
+ ...(this.useLazyLoading && { onLoad: this.onInfiniteScroll })
150
+ }
151
+ }
93
152
  },
94
153
 
95
154
  defaultFuseOptions () {
96
155
  return {
97
- distance: 100,
98
- location: 0,
99
- maxPatternLength: 32,
100
- minMatchCharLength: 1,
101
- shouldSort: true,
102
156
  threshold: 0.1,
103
- tokenize: true,
157
+ ignoreLocation: true,
104
158
 
105
159
  ...this.fuseOptions
106
160
  }
107
161
  },
108
162
 
109
- hasResults () {
110
- return !!this.searchResults.length
163
+ isDisabled () {
164
+ return (!this.useLazyLoading && !this.list.length) || this.mx_isFetching || this.hasNoOptionsOnFirstFetch
165
+ },
166
+
167
+ showEmptyResult () {
168
+ return this.useEmptySlot && !this.mx_hasFilteredOptions && !this.mx_isFetching
169
+ },
170
+
171
+ showSpinnerDots () {
172
+ return this.mx_hasFilteredOptions && this.mx_isFetching
173
+ },
174
+
175
+ showInnerLoading () {
176
+ return !this.mx_hasFilteredOptions && this.mx_isFetching
111
177
  }
112
178
  },
113
179
 
114
180
  watch: {
115
181
  defaultFuseOptions (value) {
182
+ if (this.useLazyLoading) return
183
+
116
184
  this.fuse.options = { ...this.fuse.options, ...value }
117
185
  },
118
186
 
119
- hasResults (value) {
187
+ mx_hasFilteredOptions (value) {
120
188
  !value && this.$emit('empty-result')
121
189
  },
122
190
 
123
- list: {
124
- handler (value) {
125
- this.fuse = new Fuse(value, this.defaultFuseOptions)
191
+ mx_search: {
192
+ async handler (value) {
193
+ this.$emit('update:modelValue', value)
126
194
 
127
- this.setResults(this.search)
128
- this.updateResultsModel(value)
129
- },
195
+ if (this.useLazyLoading) {
196
+ await this.mx_filterOptionsByStore(value)
197
+
198
+ this.$refs.infiniteScrollRef.resume()
199
+ this.$refs.search.focus()
200
+
201
+ return
202
+ }
130
203
 
131
- deep: true
204
+ this.filterOptionsByFuse(value)
205
+ }
132
206
  },
133
207
 
134
- search: {
208
+ modelValue: {
135
209
  handler (value) {
136
- this.setResults(value)
137
- this.$emit('update:modelValue', value)
210
+ this.mx_search = value
138
211
  },
139
212
 
140
213
  immediate: true
141
214
  },
142
215
 
143
- searchResults: {
144
- handler (value) {
145
- this.updateResultsModel(value)
216
+ mx_filteredOptions: {
217
+ handler (options) {
218
+ this.$emit('update:results', options)
146
219
  },
220
+
147
221
  immediate: true
148
222
  }
149
223
  },
150
224
 
151
225
  created () {
152
- this.search = this.modelValue
226
+ if (this.useLazyLoading) return
227
+
228
+ this.mx_filteredOptions = this.list
153
229
  this.fuse = new Fuse(this.list, this.defaultFuseOptions)
154
- this.setResults()
230
+
231
+ this.setListWatcher()
155
232
  },
156
233
 
157
234
  methods: {
158
- setResults (value) {
159
- this.searchResults = value
160
- ? this.fuse.search(value)
161
- : this.list
235
+ filterOptionsByFuse (value) {
236
+ this.mx_filteredOptions = value ? this.mx_getNormalizedFuseResults(this.fuse.search(value)) : this.list
162
237
  },
163
238
 
164
- updateResultsModel (value) {
165
- this.$emit('update:results', value.map(result => result.item || result))
239
+ async onInfiniteScroll (_, done) {
240
+ // Se tiver erro no primeiro fetch, retorna o "done" na proxima.
241
+ if (((this.mx_hasFetchError && !this.mx_hasFilteredOptions) || this.hasNoOptionsOnFirstFetch)) return done()
242
+
243
+ if (!this.mx_hasFilteredOptions && !this.mx_search) {
244
+ await this.mx_setFetchOptions()
245
+ return done()
246
+ }
247
+
248
+ if (this.mx_canFetchOptions()) {
249
+ await this.mx_loadMoreOptions()
250
+ return done()
251
+ }
252
+
253
+ done(true)
254
+ },
255
+
256
+ setListWatcher () {
257
+ this.$watch('list', value => {
258
+ this.fuse = new Fuse(value, this.defaultFuseOptions)
259
+
260
+ this.filterOptionsByFuse(this.mx_search)
261
+ }, { deep: true })
166
262
  }
167
263
  }
168
264
  }
@@ -9,20 +9,50 @@ props:
9
9
  default: 100px
10
10
  type: String
11
11
 
12
+ empty-result-text:
13
+ desc: Define o texto dentro do box quando a lista está vazia.
14
+ default: Não há resultados disponíveis.
15
+ type: String
16
+
17
+ entity:
18
+ desc: Entidade enviada para a action "fetchFieldOptions" (usar somente quando "useLazyLoading" estiver habilitada).
19
+ default: ''
20
+ type: String
21
+ examples: [users]
22
+
23
+ fetching:
24
+ desc: Usado para saber quando o componente está fazendo fetching (usar somente quando "useLazyLoading" estiver habilitada).
25
+ default: false
26
+ type: Boolean
27
+ model: true
28
+ examples: [v-model:fetching="isFetching"]
29
+
12
30
  fuse-options:
13
31
  desc: Opções do Fuse.js (https://fusejs.io/api/options.html).
14
- default: "{ distance: 100, location: 0, maxPatternLength: 32, minMatchCharLength: 1, shouldSort: true, threshold: 0.1, tokenize: true }"
15
32
  debugger: true
16
33
  type: Object
17
34
  examples: ["{ keys: ['label'] }"]
35
+ default:
36
+ ignoreLocation: true
37
+ threshold: 0.1
18
38
 
19
39
  height:
20
40
  desc: Define altura do box quando a lista não está vazia.
21
41
  default: 300px
22
42
  type: String
23
43
 
44
+ lazy-loading-props:
45
+ desc: Propriedades para o lazy loading (usar somente quando "useLazyLoading" estiver habilitada).
46
+ type: Object
47
+ debugger: true
48
+ default:
49
+ decamelizeFieldName: true
50
+ url: ''
51
+ params:
52
+ limit: 48
53
+
24
54
  list:
25
- desc: Lista onde ocorrerá a busca (array de objetos).
55
+ desc: Lista onde ocorrerá a busca sendo array de objetos (usar somente quando "useLazyLoading" **NÃO** estiver habilitada).
26
56
  default: []
27
57
  type: String
28
58
 
@@ -32,6 +62,11 @@ props:
32
62
  examples: [v-model="search"]
33
63
  model: true
34
64
 
65
+ name:
66
+ desc: Nome do campo a ser enviado para a action "fetchFieldOptions" (usar somente quando "useLazyLoading" estiver habilitada).
67
+ type: String
68
+ examples: [cities]
69
+
35
70
  placeholder:
36
71
  desc: Placeholder do campo de pesquisa.
37
72
  default: Pesquisar
@@ -49,6 +84,11 @@ props:
49
84
  default: true
50
85
  type: Boolean
51
86
 
87
+ use-lazy-loading:
88
+ desc: Controla a busca pela store "fetchFieldOptions".
89
+ default: false
90
+ type: Boolean
91
+
52
92
  slots:
53
93
  default:
54
94
  desc: Acesso ao conteúdo principal, só existe caso a busca retorne algum resultado.
@@ -56,6 +96,9 @@ slots:
56
96
  empty-result:
57
97
  desc: Acesso ao conteúdo quando a busca não retorne resultado e a prop "useEmptySlot" seja "true".
58
98
 
99
+ loading:
100
+ desc: Acesso ao conteúdo do loading que contém o "<q-spinner-dots />"..
101
+
59
102
  events:
60
103
  '@empty-result -> function ()':
61
104
  desc: Dispara toda vez que a pesquisa não retorna resultado.
@@ -74,3 +117,27 @@ events:
74
117
  desc: Novo valor do v-model:results
75
118
  default: []
76
119
  type: Array
120
+
121
+ '@update:fetching -> function (value)':
122
+ desc: Dispara toda vez que o campo de busca faz o fetch do lazy loading
123
+ params:
124
+ value:
125
+ desc: Novo valor do v-model:fetching
126
+ default: false
127
+ type: Boolean
128
+
129
+ '@fetch-options-success -> function (value)':
130
+ desc: Dispara toda vez que o campo de busca faz o fetch do lazy loading com sucesso.
131
+ params:
132
+ value:
133
+ desc: Valor retornado pela action "fetchFieldOptions"
134
+ default: {}
135
+ type: Object
136
+
137
+ '@fetch-options-error -> function (value)':
138
+ desc: Dispara toda vez que o campo de busca faz o fetch do lazy loading e cai em uma exceção.
139
+ params:
140
+ value:
141
+ desc: Valor retornado pela action "fetchFieldOptions"
142
+ default: {}
143
+ type: Object
@@ -1,13 +1,13 @@
1
1
  <template>
2
- <q-select v-model="model" v-bind="attributes" @filter="filterOptions">
2
+ <q-select v-model="model" v-bind="attributes">
3
3
  <template #append>
4
4
  <slot name="append">
5
- <q-icon v-if="searchable" name="o_search" />
5
+ <q-icon v-if="isSearchable" name="o_search" />
6
6
  </slot>
7
7
  </template>
8
8
 
9
9
  <template #no-option>
10
- <slot name="no-option">
10
+ <slot v-if="!mx_isFetching" name="no-option">
11
11
  <q-item>
12
12
  <q-item-section class="text-grey">
13
13
  {{ noOptionLabel }}
@@ -16,6 +16,14 @@
16
16
  </slot>
17
17
  </template>
18
18
 
19
+ <template #after-options>
20
+ <slot v-if="mx_isFetching" name="after-options">
21
+ <div class="flex justify-center q-pb-sm">
22
+ <q-spinner-dots color="primary" size="20px" />
23
+ </div>
24
+ </slot>
25
+ </template>
26
+
19
27
  <template v-for="(_, name) in $slots" #[name]="context">
20
28
  <slot :name="name" v-bind="context || {}" />
21
29
  </template>
@@ -23,11 +31,14 @@
23
31
  </template>
24
32
 
25
33
  <script>
34
+ import { searchFilterMixin } from '../../mixins'
26
35
  import Fuse from 'fuse.js'
27
36
 
28
37
  export default {
29
38
  name: 'QasSelect',
30
39
 
40
+ mixins: [searchFilterMixin],
41
+
31
42
  props: {
32
43
  fuseOptions: {
33
44
  default: () => ({}),
@@ -41,7 +52,7 @@ export default {
41
52
 
42
53
  modelValue: {
43
54
  default: () => [],
44
- type: [Array, Object, String, Number]
55
+ type: [Array, Object, String, Number, Boolean]
45
56
  },
46
57
 
47
58
  noOptionLabel: {
@@ -54,13 +65,13 @@ export default {
54
65
  type: Array
55
66
  },
56
67
 
57
- searchable: {
58
- type: Boolean
59
- },
60
-
61
68
  valueKey: {
62
69
  default: '',
63
70
  type: String
71
+ },
72
+
73
+ useSearch: {
74
+ type: Boolean
64
75
  }
65
76
  },
66
77
 
@@ -68,7 +79,6 @@ export default {
68
79
 
69
80
  data () {
70
81
  return {
71
- filteredOptions: [],
72
82
  fuse: null
73
83
  }
74
84
  },
@@ -76,40 +86,45 @@ export default {
76
86
  computed: {
77
87
  attributes () {
78
88
  return {
79
- clearable: this.searchable,
89
+ clearable: this.isSearchable,
80
90
  emitValue: true,
81
91
  mapOptions: true,
82
92
  outlined: true,
83
-
84
93
  ...this.$attrs,
85
94
 
86
- options: this.filteredOptions,
87
- useInput: this.searchable
95
+ options: this.mx_filteredOptions,
96
+ useInput: this.isSearchable,
97
+ error: this.hasError,
98
+ loading: this.hasLoading,
99
+ ...(this.useLazyLoading && { onVirtualScroll: this.mx_onVirtualScroll }),
100
+ ...(this.isSearchable && { onFilter: this.onFilter })
88
101
  }
89
102
  },
90
103
 
91
104
  defaultFuseOptions () {
92
105
  return {
93
- distance: 100,
94
- includeScore: true,
106
+ ignoreLocation: true,
95
107
  keys: ['label', 'value'],
96
- location: 0,
97
- maxPatternLength: 32,
98
- minMatchCharLength: 1,
99
- shouldSort: true,
100
108
  threshold: 0.1,
101
- tokenize: true,
102
109
 
103
110
  ...this.fuseOptions
104
111
  }
105
112
  },
106
113
 
107
- formattedResult () {
108
- if (!this.labelKey && !this.valueKey) {
109
- return this.options
110
- }
114
+ isSearchable () {
115
+ return this.useSearch || this.useLazyLoading
116
+ },
117
+
118
+ defaultOptions () {
119
+ return this.mx_handleOptions(this.options)
120
+ },
111
121
 
112
- return this.options.map(item => this.renameKey(item))
122
+ hasError () {
123
+ return this.mx_hasFetchError || this.$attrs.error
124
+ },
125
+
126
+ hasLoading () {
127
+ return this.mx_isFetching || this.$attrs.loading
113
128
  },
114
129
 
115
130
  model: {
@@ -130,11 +145,11 @@ export default {
130
145
 
131
146
  options: {
132
147
  handler () {
133
- if (this.fuse) {
134
- this.fuse.list = this.formattedResult
135
- }
148
+ if (this.useLazyLoading && this.mx_hasFilteredOptions) return
149
+
150
+ if (this.fuse) this.setFuse()
136
151
 
137
- this.filteredOptions = this.formattedResult
152
+ this.mx_filteredOptions = this.defaultOptions
138
153
  },
139
154
 
140
155
  immediate: true
@@ -143,42 +158,37 @@ export default {
143
158
 
144
159
  created () {
145
160
  this.setFuse()
161
+ this.useLazyLoading && this.mx_setFetchOptions('')
146
162
  },
147
163
 
148
164
  methods: {
149
- filterOptions (value, update) {
150
- update(() => {
151
- if (!this.searchable) return
152
-
153
- if (value === '') {
154
- this.filteredOptions = this.formattedResult
155
- } else {
156
- const results = this.fuse.search(value)
157
- this.filteredOptions = results.map(item => item.item)
158
- }
159
- })
165
+ setFuse () {
166
+ if (this.useSearch) {
167
+ this.fuse = new Fuse(this.defaultOptions, this.defaultFuseOptions)
168
+ }
160
169
  },
161
170
 
162
- renameKey (item) {
163
- const mapKeys = {
164
- label: this.labelKey,
165
- value: this.valueKey
171
+ async onFilter (value, update) {
172
+ if (this.useLazyLoading && value !== this.mx_search) {
173
+ await this.mx_filterOptionsByStore(value)
166
174
  }
167
175
 
168
- for (const newKey in mapKeys) {
169
- if (!item.hasOwnProperty.call(newKey)) {
170
- item[newKey] = item[mapKeys[newKey]]
171
- delete item[mapKeys[newKey]]
172
- }
176
+ if (!this.useLazyLoading && this.useSearch) {
177
+ this.filterOptionsByFuse(value)
173
178
  }
174
179
 
175
- return item
180
+ update()
176
181
  },
177
182
 
178
- setFuse () {
179
- if (this.searchable) {
180
- this.fuse = new Fuse(this.options, this.defaultFuseOptions)
183
+ filterOptionsByFuse (value) {
184
+ if (value === '') {
185
+ this.mx_filteredOptions = this.defaultOptions
186
+ return
181
187
  }
188
+
189
+ const results = this.fuse.search(value)
190
+
191
+ this.mx_filteredOptions = this.mx_getNormalizedFuseResults(results)
182
192
  }
183
193
  }
184
194
  }