@bildvitta/quasar-ui-asteroid 3.20.0-beta.2 → 3.20.0-beta.21
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.
- package/package.json +3 -3
- package/src/asteroid.js +8 -1
- package/src/components/actions/QasActions.vue +12 -2
- package/src/components/actions-menu/QasActionsMenu.vue +18 -5
- package/src/components/alert/QasAlert.vue +89 -64
- package/src/components/app-user/QasAppUser.vue +2 -1
- package/src/components/board-generator/QasBoardGenerator.vue +883 -162
- package/src/components/board-generator/QasBoardGenerator.yml +83 -2
- package/src/components/board-generator/private/PvBoardGeneratorCardsContainer.vue +25 -0
- package/src/components/box/QasBox.vue +16 -3
- package/src/components/box/QasBox.yml +10 -0
- package/src/components/btn/QasBtn.vue +27 -5
- package/src/components/btn/QasBtn.yml +10 -1
- package/src/components/btn-dropdown/QasBtnDropdown.vue +13 -1
- package/src/components/card/QasCard.vue +97 -25
- package/src/components/card/QasCard.yml +10 -0
- package/src/components/card-image/QasCardImage.vue +10 -1
- package/src/components/card-image/QasCardImage.yml +5 -0
- package/src/components/chart-view/QasChartView.vue +4 -3
- package/src/components/chart-view/QasChartView.yml +5 -0
- package/src/components/checkbox/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/src/components/copy/QasCopy.vue +6 -1
- package/src/components/copy/QasCopy.yml +5 -0
- package/src/components/date-time-input/QasDateTimeInput.vue +30 -6
- package/src/components/dialog/QasDialog.vue +308 -91
- package/src/components/dialog/QasDialog.yml +51 -23
- package/src/components/dialog/composables/use-cancel.js +1 -1
- package/src/components/dialog/composables/use-dynamic-components.js +2 -2
- package/src/components/dialog/composables/use-ok.js +1 -0
- package/src/components/dialog-router/QasDialogRouter.vue +1 -1
- package/src/components/drawer/QasDrawer.vue +76 -26
- package/src/components/drawer/QasDrawer.yml +10 -0
- package/src/components/expansion-item/QasExpansionItem.yml +5 -0
- package/src/components/filters/QasFilters.vue +2 -1
- package/src/components/filters/private/PvFiltersActions.vue +79 -13
- package/src/components/form-generator/QasFormGenerator.vue +8 -1
- package/src/components/form-generator/QasFormGenerator.yml +10 -0
- package/src/components/form-view/QasFormView.vue +20 -11
- package/src/components/form-view/QasFormView.yml +6 -0
- package/src/components/gallery/composables/use-delete.js +2 -3
- package/src/components/gallery/private/PvGalleryCarouselDialog.vue +8 -7
- package/src/components/grid-item/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/src/components/header/QasHeader.vue +66 -11
- package/src/components/header/QasHeader.yml +16 -1
- package/src/components/infinite-scroll/QasInfiniteScroll.vue +1 -1
- package/src/components/label/QasLabel.vue +3 -1
- package/src/components/layout/QasLayout.vue +16 -1
- package/src/components/layout/private/PvLayoutNotificationsDrawer.vue +2 -1
- package/src/components/layout/private/PvLayoutOverlayDrawer.vue +4 -2
- package/src/components/lazy-loading-components/QasLazyLoadingComponents.vue +262 -0
- package/src/components/lazy-loading-components/QasLazyLoadingComponents.yml +49 -0
- package/src/components/list-view/QasListView.vue +12 -4
- package/src/components/list-view/QasListView.yml +12 -0
- package/src/components/page-header/QasPageHeader.vue +49 -3
- package/src/components/page-header/QasPageHeader.yml +5 -0
- package/src/components/router-link/QasRouterLink.vue +72 -0
- package/src/components/router-link/QasRouterLink.yml +24 -0
- package/src/components/search-box/QasSearchBox.vue +1 -1
- package/src/components/select/QasSelect.vue +8 -1
- package/src/components/select-list-dialog/QasSelectListDialog.vue +40 -20
- package/src/components/select-list-dialog/QasSelectListDialog.yml +14 -2
- package/src/components/signature-uploader/QasSignatureUploader.vue +5 -18
- package/src/components/single-view/QasSingleView.vue +2 -2
- package/src/components/skeleton/QasSkeleton.vue +139 -0
- package/src/components/skeleton/QasSkeleton.yml +48 -0
- package/src/components/sortable/QasSortable.vue +1 -1
- package/src/components/stepper/QasStepper.vue +24 -2
- package/src/components/table-generator/QasTableGenerator.vue +186 -35
- package/src/components/table-generator/QasTableGenerator.yml +6 -1
- package/src/components/tabs-generator/QasTabsGenerator.vue +14 -3
- package/src/components/tabs-generator/QasTabsGenerator.yml +5 -1
- package/src/components/text-truncate/QasTextTruncate.vue +61 -12
- package/src/components/text-truncate/QasTextTruncate.yml +5 -0
- package/src/components/toggle-visibility/QasToggleVisibility.vue +2 -1
- package/src/components/tooltip/QasTooltip.vue +6 -1
- package/src/components/tree-generator/QasTreeGenerator.vue +4 -6
- package/src/components/uploader/QasUploader.vue +12 -2
- package/src/composables/private/use-view.js +1 -1
- package/src/composables/use-overlay-navigation.js +116 -10
- package/src/composables/use-screen.js +17 -1
- package/src/css/components/button.scss +82 -3
- package/src/css/components/item.scss +6 -0
- package/src/css/utils/background.scss +5 -0
- package/src/css/utils/border.scss +6 -0
- package/src/css/utils/container.scss +4 -3
- package/src/css/utils/text.scss +9 -0
- package/src/helpers/copy-to-clipboard.js +2 -1
- package/src/helpers/filters.js +1 -1
- package/src/helpers/promise-handler.js +2 -1
- package/src/helpers/set-scroll-gradient.js +31 -8
- package/src/helpers/set-scroll-on-grab.js +10 -3
- package/src/index.scss +1 -0
- package/src/mixins/search-filter.js +7 -1
- package/src/plugins/delete/Delete.js +7 -9
- package/src/plugins/delete/Delete.yml +1 -1
- package/src/plugins/dialog/Dialog.yml +1 -1
- package/src/plugins/notify-error/NotifyError.yml +1 -1
- package/src/plugins/notify-success/NotifySuccess.yml +1 -1
- package/src/plugins/screen/Screen.js +17 -1
- package/src/plugins/screen/Screen.yml +5 -1
- package/src/vue-plugin.js +5 -7
- package/src/plugins/index.js +0 -5
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<!--
|
|
3
|
+
Itera sobre todos os componentes extraídos do slot
|
|
4
|
+
Cada item pode estar em dois estados:
|
|
5
|
+
1. Visível: Renderiza o componente real
|
|
6
|
+
2. Não visível: Renderiza placeholder vazio (reserva espaço)
|
|
7
|
+
-->
|
|
8
|
+
<template
|
|
9
|
+
v-for="(item, index) in items"
|
|
10
|
+
:key="index"
|
|
11
|
+
>
|
|
12
|
+
<transition enter-active-class="animated fadeIn slow">
|
|
13
|
+
<!-- Componente real - renderizado quando visível -->
|
|
14
|
+
<component
|
|
15
|
+
:is="item"
|
|
16
|
+
v-if="visibleItems.has(index)"
|
|
17
|
+
/>
|
|
18
|
+
|
|
19
|
+
<!-- Placeholder - div vazia que reserva espaço no layout -->
|
|
20
|
+
<div
|
|
21
|
+
v-else
|
|
22
|
+
:ref="element => setPlaceholderRef(element, index)"
|
|
23
|
+
/>
|
|
24
|
+
</transition>
|
|
25
|
+
</template>
|
|
26
|
+
</template>
|
|
27
|
+
|
|
28
|
+
<script setup>
|
|
29
|
+
import { ref, onMounted, onBeforeUnmount, useSlots, nextTick, watch } from 'vue'
|
|
30
|
+
|
|
31
|
+
defineOptions({ name: 'QasLazyLoadingComponents' })
|
|
32
|
+
|
|
33
|
+
const props = defineProps({
|
|
34
|
+
threshold: {
|
|
35
|
+
type: Number,
|
|
36
|
+
default: 0.1 // 10% visível
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
rootMargin: {
|
|
40
|
+
type: String,
|
|
41
|
+
default: '0px'
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
direction: {
|
|
45
|
+
type: String,
|
|
46
|
+
default: 'vertical',
|
|
47
|
+
validator: value => ['vertical', 'horizontal'].includes(value)
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
placeholderHeight: {
|
|
51
|
+
type: String,
|
|
52
|
+
default: '500px'
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
placeholderWidth: {
|
|
56
|
+
type: String,
|
|
57
|
+
default: '300px'
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
// models
|
|
62
|
+
const visibleItemsModel = defineModel('visibleItems', { type: Array, default: () => [] })
|
|
63
|
+
|
|
64
|
+
// refs
|
|
65
|
+
/**
|
|
66
|
+
* Lista de VNodes extraídos do slot default
|
|
67
|
+
* Cada item é um VNode que representa um componente filho
|
|
68
|
+
*
|
|
69
|
+
* Exemplo:
|
|
70
|
+
* <qas-lazy-loading-components>
|
|
71
|
+
* <ComponenteA /> <- items[0]
|
|
72
|
+
* <ComponenteB /> <- items[1]
|
|
73
|
+
* </qas-lazy-loading-components>
|
|
74
|
+
*/
|
|
75
|
+
const items = ref([])
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Set de índices dos componentes que já foram renderizados
|
|
79
|
+
* Quando um placeholder entra no viewport, seu índice é adicionado aqui
|
|
80
|
+
* @type {Ref<Set<number>>}
|
|
81
|
+
*/
|
|
82
|
+
const visibleItems = ref(new Set())
|
|
83
|
+
|
|
84
|
+
// composables
|
|
85
|
+
const slots = useSlots()
|
|
86
|
+
|
|
87
|
+
// consts
|
|
88
|
+
/**
|
|
89
|
+
* Map que armazena referências aos elementos placeholder
|
|
90
|
+
* Chave: índice do componente
|
|
91
|
+
* Valor: elemento DOM do placeholder
|
|
92
|
+
* @type {Map<number, HTMLElement>}
|
|
93
|
+
*/
|
|
94
|
+
const placeholderRefs = new Map()
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Instância do IntersectionObserver
|
|
98
|
+
* Responsável por detectar quando placeholders entram no viewport
|
|
99
|
+
* @type {IntersectionObserver | null}
|
|
100
|
+
*/
|
|
101
|
+
let observer = null
|
|
102
|
+
|
|
103
|
+
// lifecycle hooks
|
|
104
|
+
onMounted(handleObserver)
|
|
105
|
+
|
|
106
|
+
// Observa mudanças no slot para atualizar items
|
|
107
|
+
watch(
|
|
108
|
+
() => slots.default?.(),
|
|
109
|
+
handleObserver,
|
|
110
|
+
{ flush: 'post' }
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
onBeforeUnmount(() => {
|
|
114
|
+
if (!observer) return
|
|
115
|
+
|
|
116
|
+
// Limpa o observer para evitar memory leaks
|
|
117
|
+
observer.disconnect()
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
// functions
|
|
121
|
+
/**
|
|
122
|
+
* Cria e configura o IntersectionObserver
|
|
123
|
+
* Observa todos os placeholders e renderiza componentes quando visíveis
|
|
124
|
+
*
|
|
125
|
+
* Fluxo:
|
|
126
|
+
* 1. Cria observer com threshold e rootMargin configurados
|
|
127
|
+
* 2. Quando placeholder entra no viewport:
|
|
128
|
+
* - Encontra o índice correspondente no Map
|
|
129
|
+
* - Adiciona índice ao visibleItems (renderiza componente)
|
|
130
|
+
* - Para de observar esse elemento
|
|
131
|
+
* 3. Observa todos os placeholders armazenados no Map
|
|
132
|
+
*/
|
|
133
|
+
function createObserver () {
|
|
134
|
+
observer = new IntersectionObserver(
|
|
135
|
+
entries => {
|
|
136
|
+
entries.forEach(entry => {
|
|
137
|
+
if (!entry.isIntersecting) return
|
|
138
|
+
|
|
139
|
+
// Converte Map em Array para facilitar busca
|
|
140
|
+
const list = Array.from(placeholderRefs.entries())
|
|
141
|
+
|
|
142
|
+
// Encontra o índice do placeholder no Map
|
|
143
|
+
const index = list.findIndex(([_, element]) => element === entry.target)
|
|
144
|
+
|
|
145
|
+
// Verifica se encontrou o índice (index !== -1)
|
|
146
|
+
if (!~index) return
|
|
147
|
+
|
|
148
|
+
// Adiciona ao Set para renderizar o componente real
|
|
149
|
+
visibleItems.value.add(index)
|
|
150
|
+
|
|
151
|
+
// Para de observar - componente já foi renderizado
|
|
152
|
+
observer.unobserve(entry.target)
|
|
153
|
+
|
|
154
|
+
// Emite os índices atualmente visíveis
|
|
155
|
+
visibleItemsModel.value = Array.from(visibleItems.value)
|
|
156
|
+
})
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
threshold: props.threshold,
|
|
160
|
+
rootMargin: props.rootMargin
|
|
161
|
+
}
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
// Observa todos os placeholders do Map
|
|
165
|
+
placeholderRefs.forEach(element => {
|
|
166
|
+
observer.observe(element)
|
|
167
|
+
})
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function handleObserver () {
|
|
171
|
+
setItems()
|
|
172
|
+
|
|
173
|
+
// Reconstrói o observer com os novos items
|
|
174
|
+
if (observer) {
|
|
175
|
+
observer.disconnect()
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
nextTick(() => requestAnimationFrame(createObserver))
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Extrai e achata os VNodes do slot default
|
|
183
|
+
* Suporta múltiplos cenários:
|
|
184
|
+
* - Componentes individuais: <ComponenteA /> <ComponenteB />
|
|
185
|
+
* - v-for: <Component v-for="..." />
|
|
186
|
+
* - template v-for: <template v-for="..."><Component /></template>
|
|
187
|
+
*
|
|
188
|
+
* @param {Array} vnodes - Array de VNodes
|
|
189
|
+
* @returns {Array} Array de VNodes achatado
|
|
190
|
+
*/
|
|
191
|
+
function flattenVNodes (vnodes) {
|
|
192
|
+
const flattened = []
|
|
193
|
+
|
|
194
|
+
vnodes.forEach(vnode => {
|
|
195
|
+
// Ignora VNodes inválidos (null, undefined, primitivos)
|
|
196
|
+
if (!vnode || typeof vnode !== 'object') return
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Se for um Fragment (v-for, template, v-if, etc), achata recursivamente os children
|
|
200
|
+
* Fragments são wrappers que o Vue cria para agrupar múltiplos elementos
|
|
201
|
+
*/
|
|
202
|
+
const isFragment = typeof vnode.type === 'symbol' && Array.isArray(vnode.children)
|
|
203
|
+
|
|
204
|
+
if (isFragment) {
|
|
205
|
+
flattened.push(...flattenVNodes(vnode.children))
|
|
206
|
+
} else {
|
|
207
|
+
// VNode normal (componente ou elemento HTML)
|
|
208
|
+
flattened.push(vnode)
|
|
209
|
+
}
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
return flattened
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Extrai os VNodes do slot default e armazena em items
|
|
217
|
+
* Achata automaticamente fragments e v-for
|
|
218
|
+
*
|
|
219
|
+
* Exemplo de uso:
|
|
220
|
+
* <qas-lazy-loading-components>
|
|
221
|
+
* <ComponenteA /> <- vnode 0
|
|
222
|
+
* <ComponenteB /> <- vnode 1
|
|
223
|
+
* <Component v-for="item in 10" /> <- vnode 2-11
|
|
224
|
+
* </qas-lazy-loading-components>
|
|
225
|
+
*/
|
|
226
|
+
function setItems () {
|
|
227
|
+
const slotContent = slots.default?.() || []
|
|
228
|
+
const flattenedItems = flattenVNodes(slotContent)
|
|
229
|
+
|
|
230
|
+
items.value = flattenedItems
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Armazena referência do elemento placeholder no Map
|
|
235
|
+
* Chamado automaticamente pelo Vue através do :ref no template
|
|
236
|
+
*
|
|
237
|
+
* @param {HTMLElement | null} element - Elemento DOM do placeholder
|
|
238
|
+
* @param {number} index - Índice do componente
|
|
239
|
+
*/
|
|
240
|
+
function setPlaceholderRef (element, index) {
|
|
241
|
+
if (!element) return
|
|
242
|
+
|
|
243
|
+
placeholderRefs.set(index, element)
|
|
244
|
+
|
|
245
|
+
const itemProps = items.value[index].props
|
|
246
|
+
|
|
247
|
+
if (props.direction === 'horizontal') {
|
|
248
|
+
/**
|
|
249
|
+
* Define a largura do placeholder com base no atributo "data-placeholder-width" caso queira customizar
|
|
250
|
+
* para o elemento específico, e não o definido na prop placeholderWidth.
|
|
251
|
+
*/
|
|
252
|
+
element.style.width = itemProps?.['data-placeholder-width'] || props.placeholderWidth
|
|
253
|
+
element.style.flexShrink = '0'
|
|
254
|
+
} else {
|
|
255
|
+
/**
|
|
256
|
+
* Define a altura do placeholder com base no atributo "data-placeholder-height" caso queira customizar
|
|
257
|
+
* para o elemento específico, e não o definido na prop placeholderHeight.
|
|
258
|
+
*/
|
|
259
|
+
element.style.height = itemProps?.['data-placeholder-height'] || props.placeholderHeight
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
</script>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
type: component
|
|
2
|
+
|
|
3
|
+
meta:
|
|
4
|
+
desc: Componente para carregar elementos do slot somente quando ficam visíveis na viewport, otimizando performance da página.
|
|
5
|
+
|
|
6
|
+
props:
|
|
7
|
+
direction:
|
|
8
|
+
desc: Direção do scroll e do lazy loading. Use `vertical` (padrão) para listas de rolagem vertical e `horizontal` para listas de rolagem horizontal.
|
|
9
|
+
type: String
|
|
10
|
+
default: vertical
|
|
11
|
+
values: vertical | horizontal
|
|
12
|
+
|
|
13
|
+
placeholder-height:
|
|
14
|
+
desc: Altura do placeholder exibido enquanto o elemento não está visível (usado quando `direction` é `vertical`).
|
|
15
|
+
default: '500px'
|
|
16
|
+
type: String
|
|
17
|
+
|
|
18
|
+
placeholder-width:
|
|
19
|
+
desc: Largura do placeholder exibido enquanto o elemento não está visível (usado quando `direction` é `horizontal`).
|
|
20
|
+
default: '300px'
|
|
21
|
+
type: String
|
|
22
|
+
|
|
23
|
+
root-margin:
|
|
24
|
+
desc: Margem extra ao redor do viewport para pré-carregamento
|
|
25
|
+
default: '0px'
|
|
26
|
+
type: String
|
|
27
|
+
|
|
28
|
+
threshold:
|
|
29
|
+
desc: Threshold do IntersectionObserver (0 = aparece um pouco, 1 = totalmente visível)
|
|
30
|
+
default: 0.1
|
|
31
|
+
type: Number
|
|
32
|
+
|
|
33
|
+
visible-items:
|
|
34
|
+
desc: Expõe os índices dos itens atualmente visíveis na viewport. Útil para otimizar renders e priorizar carregamento de dados.
|
|
35
|
+
type: Array
|
|
36
|
+
default: []
|
|
37
|
+
model: true
|
|
38
|
+
|
|
39
|
+
slots:
|
|
40
|
+
default:
|
|
41
|
+
desc: Elementos/componentes que serão carregados conforme ficam visíveis, onde cada elemento irmão dentro do slot será tratado individualmente para o lazy loading.
|
|
42
|
+
|
|
43
|
+
events:
|
|
44
|
+
'@update:visible-items -> function(value)':
|
|
45
|
+
desc: Dispara toda vez que os índices dos itens visíveis são atualizados, também utilizado para v-model:visible-items.
|
|
46
|
+
params:
|
|
47
|
+
value:
|
|
48
|
+
desc: Array de índices dos itens atualmente visíveis na viewport.
|
|
49
|
+
type: Array
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
<div v-if="showResults">
|
|
14
14
|
<slot />
|
|
15
15
|
|
|
16
|
-
<q-inner-loading :showing="mx_isFetching">
|
|
16
|
+
<q-inner-loading :showing="mx_isFetching && useLoading">
|
|
17
17
|
<q-spinner color="grey" size="3em" />
|
|
18
18
|
</q-inner-loading>
|
|
19
19
|
</div>
|
|
@@ -108,6 +108,11 @@ export default {
|
|
|
108
108
|
type: Boolean
|
|
109
109
|
},
|
|
110
110
|
|
|
111
|
+
useLoading: {
|
|
112
|
+
type: Boolean,
|
|
113
|
+
default: true
|
|
114
|
+
},
|
|
115
|
+
|
|
111
116
|
usePagination: {
|
|
112
117
|
default: true,
|
|
113
118
|
type: Boolean
|
|
@@ -129,6 +134,7 @@ export default {
|
|
|
129
134
|
},
|
|
130
135
|
|
|
131
136
|
emits: [
|
|
137
|
+
'fetch-start',
|
|
132
138
|
'fetch-success',
|
|
133
139
|
'fetch-error',
|
|
134
140
|
'update:errors',
|
|
@@ -158,7 +164,7 @@ export default {
|
|
|
158
164
|
* automaticamente.
|
|
159
165
|
*/
|
|
160
166
|
hasDeleteEventListener () {
|
|
161
|
-
return this.useAutoHandleOnDelete || this.useAutoRefetchOnDelete
|
|
167
|
+
return this.useAutoHandleOnDelete || this.useAutoRefetchOnDelete
|
|
162
168
|
},
|
|
163
169
|
|
|
164
170
|
hasHeaderSlot () {
|
|
@@ -189,7 +195,7 @@ export default {
|
|
|
189
195
|
},
|
|
190
196
|
|
|
191
197
|
showResults () {
|
|
192
|
-
return this.hasResults || this.useResultsAreaOnly
|
|
198
|
+
return this.hasResults || this.useResultsAreaOnly || !this.useLoading
|
|
193
199
|
},
|
|
194
200
|
|
|
195
201
|
paginationClasses () {
|
|
@@ -251,6 +257,8 @@ export default {
|
|
|
251
257
|
...externalPayload
|
|
252
258
|
}
|
|
253
259
|
|
|
260
|
+
this.$emit('fetch-start', payload)
|
|
261
|
+
|
|
254
262
|
const response = await this.handleFetchList(payload)
|
|
255
263
|
|
|
256
264
|
const { errors, fields, metadata, results, count } = response.data
|
|
@@ -374,7 +382,7 @@ export default {
|
|
|
374
382
|
},
|
|
375
383
|
|
|
376
384
|
onDeleteResult (event) {
|
|
377
|
-
if (this.useAutoRefetchOnDelete
|
|
385
|
+
if (this.useAutoRefetchOnDelete) {
|
|
378
386
|
this.mx_fetchHandler({ ...this.mx_context, url: this.url }, this.fetchList)
|
|
379
387
|
return
|
|
380
388
|
}
|
|
@@ -86,6 +86,11 @@ props:
|
|
|
86
86
|
default: true
|
|
87
87
|
type: Boolean
|
|
88
88
|
|
|
89
|
+
use-loading:
|
|
90
|
+
desc: Controla se o componente vai exibir um loading quando estiver fazendo fetch dos dados.
|
|
91
|
+
default: true
|
|
92
|
+
type: Boolean
|
|
93
|
+
|
|
89
94
|
use-pagination:
|
|
90
95
|
desc: Controla se vai ter ou não paginação.
|
|
91
96
|
default: true
|
|
@@ -125,6 +130,13 @@ slots:
|
|
|
125
130
|
desc: 'Slot para acessar o header.'
|
|
126
131
|
|
|
127
132
|
events:
|
|
133
|
+
'@fetch-start -> function(value)':
|
|
134
|
+
desc: Dispara logo antes da action "fetchList".
|
|
135
|
+
params:
|
|
136
|
+
value:
|
|
137
|
+
desc: Retorna o payload enviado para o fetchList, contendo paginação, filtros, search, etc.
|
|
138
|
+
type: Object
|
|
139
|
+
|
|
128
140
|
'@fetch-success -> function(value)':
|
|
129
141
|
desc: Dispara quando a action "fetchList" é executada com sucesso.
|
|
130
142
|
params:
|
|
@@ -3,13 +3,23 @@
|
|
|
3
3
|
<q-toolbar class="justify-between q-mb-md q-px-none qas-page-header">
|
|
4
4
|
<div class="ellipsis">
|
|
5
5
|
<q-toolbar-title v-if="props.title" class="text-h3">
|
|
6
|
-
|
|
6
|
+
<qas-skeleton v-if="props.skeleton" max-width="300px" type="text" use-contrast />
|
|
7
|
+
|
|
8
|
+
<template v-else>
|
|
9
|
+
{{ props.title }}
|
|
10
|
+
</template>
|
|
7
11
|
</q-toolbar-title>
|
|
8
12
|
|
|
9
13
|
<q-breadcrumbs v-if="hasBreadcrumbs" class="text-caption" gutter="xs" separator-color="grey-8">
|
|
10
14
|
<q-breadcrumbs-el v-if="props.useHomeIcon" class="qas-page-header__breadcrumbs-el text-grey-8" icon="sym_r_home" :to="homeRoute" />
|
|
11
15
|
|
|
12
|
-
<q-breadcrumbs-el v-for="(item, index) in normalizedBreadcrumbs" :key="index" class="ellipsis inline-block qas-page-header__breadcrumbs-el"
|
|
16
|
+
<q-breadcrumbs-el v-for="(item, index) in normalizedBreadcrumbs" :key="index" class="ellipsis inline-block qas-page-header__breadcrumbs-el" tag="div" :to="item.route">
|
|
17
|
+
<qas-skeleton v-if="props.skeleton" v-bind="getBreadcrumbSkeletonProps(item)" />
|
|
18
|
+
|
|
19
|
+
<template v-else>
|
|
20
|
+
{{ item.label }}
|
|
21
|
+
</template>
|
|
22
|
+
</q-breadcrumbs-el>
|
|
13
23
|
</q-breadcrumbs>
|
|
14
24
|
</div>
|
|
15
25
|
|
|
@@ -25,6 +35,7 @@
|
|
|
25
35
|
</template>
|
|
26
36
|
|
|
27
37
|
<script setup>
|
|
38
|
+
import QasSkeleton from '../skeleton/QasSkeleton.vue'
|
|
28
39
|
import QasHeader from '../header/QasHeader.vue'
|
|
29
40
|
|
|
30
41
|
import { useOverlayNavigation } from '../../composables'
|
|
@@ -52,6 +63,10 @@ const props = defineProps({
|
|
|
52
63
|
type: [Object, String]
|
|
53
64
|
},
|
|
54
65
|
|
|
66
|
+
skeleton: {
|
|
67
|
+
type: Boolean
|
|
68
|
+
},
|
|
69
|
+
|
|
55
70
|
title: {
|
|
56
71
|
default: '',
|
|
57
72
|
type: String
|
|
@@ -77,6 +92,7 @@ useMeta(() => ({ title: props.title }))
|
|
|
77
92
|
|
|
78
93
|
// computed
|
|
79
94
|
const hasBreadcrumbs = computed(() => props.useBreadcrumbs && !isOverlay)
|
|
95
|
+
|
|
80
96
|
const transformedBreadcrumbs = computed(() => {
|
|
81
97
|
const list = [...castArray(props.breadcrumbs || props.title)]
|
|
82
98
|
|
|
@@ -95,7 +111,7 @@ const transformedBreadcrumbs = computed(() => {
|
|
|
95
111
|
})
|
|
96
112
|
})
|
|
97
113
|
|
|
98
|
-
const
|
|
114
|
+
const truncatedBreadcrumbs = computed(() => {
|
|
99
115
|
const breadcrumbsSize = transformedBreadcrumbs.value.length
|
|
100
116
|
|
|
101
117
|
if (breadcrumbsSize < 5) return transformedBreadcrumbs.value
|
|
@@ -116,9 +132,39 @@ const normalizedBreadcrumbs = computed(() => {
|
|
|
116
132
|
]
|
|
117
133
|
})
|
|
118
134
|
|
|
135
|
+
/**
|
|
136
|
+
* Caso a prop skeleton esteja ativa, retorna um array com 3 elementos vazios para renderizar
|
|
137
|
+
* os esqueletos dos breadcrumbs. Caso contrário, retorna os breadcrumbs truncados.
|
|
138
|
+
*/
|
|
139
|
+
const normalizedBreadcrumbs = computed(() => {
|
|
140
|
+
if (props.skeleton) {
|
|
141
|
+
return Array.from({ length: 3 }).map(() => ({ label: '', route: null }))
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return truncatedBreadcrumbs.value
|
|
145
|
+
})
|
|
146
|
+
|
|
119
147
|
const hasHeader = computed(() => !!Object.keys(props.headerProps).length)
|
|
120
148
|
|
|
121
149
|
const homeRoute = computed(() => router.hasRoute('Root') ? { name: 'Root' } : '/')
|
|
150
|
+
|
|
151
|
+
// functions
|
|
152
|
+
/**
|
|
153
|
+
* Retorna propriedades para o componente QasSkeleton usado nos breadcrumbs
|
|
154
|
+
*/
|
|
155
|
+
function getBreadcrumbSkeletonProps () {
|
|
156
|
+
const min = 60
|
|
157
|
+
const max = 160
|
|
158
|
+
|
|
159
|
+
// Gera uma largura aleatória entre min e max entre 60 e 160 pixels
|
|
160
|
+
const width = Math.floor(Math.random() * (max - min + 1)) + min
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
type: 'text',
|
|
164
|
+
useContrast: true,
|
|
165
|
+
width: `${width}px`
|
|
166
|
+
}
|
|
167
|
+
}
|
|
122
168
|
</script>
|
|
123
169
|
|
|
124
170
|
<style lang="scss">
|
|
@@ -24,6 +24,11 @@ props:
|
|
|
24
24
|
desc: Rota raiz do breadcrumbs.
|
|
25
25
|
type: [Object, String]
|
|
26
26
|
|
|
27
|
+
skeleton:
|
|
28
|
+
desc: Exibe um esqueleto de carregamento no lugar do conteúdo.
|
|
29
|
+
default: false
|
|
30
|
+
type: Boolean
|
|
31
|
+
|
|
27
32
|
use-breadcrumbs:
|
|
28
33
|
desc: Habilita ou não o breadcrumbs.
|
|
29
34
|
default: true
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Tratamento feito para ter o comportamento de ao abrir em uma nova guia, direto na página, abre sem overlay.
|
|
3
|
+
Caso contrário, ao clicar normalmente, abrirá no overlay.
|
|
4
|
+
-->
|
|
5
|
+
<template>
|
|
6
|
+
<router-link
|
|
7
|
+
class="qas-router-link text-no-decoration"
|
|
8
|
+
v-bind="routerLinkProps"
|
|
9
|
+
>
|
|
10
|
+
<slot>
|
|
11
|
+
{{ props.label }}
|
|
12
|
+
</slot>
|
|
13
|
+
</router-link>
|
|
14
|
+
</template>
|
|
15
|
+
|
|
16
|
+
<script setup>
|
|
17
|
+
import { computed } from 'vue'
|
|
18
|
+
import { useRouter } from 'vue-router'
|
|
19
|
+
import { useOverlayNavigation } from 'asteroid'
|
|
20
|
+
|
|
21
|
+
defineOptions({ name: 'QasRouterLink' })
|
|
22
|
+
|
|
23
|
+
const props = defineProps({
|
|
24
|
+
label: {
|
|
25
|
+
type: String,
|
|
26
|
+
default: ''
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
route: {
|
|
30
|
+
type: Object,
|
|
31
|
+
required: true
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
useOverlayRoute: {
|
|
35
|
+
type: Boolean
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
// composables
|
|
40
|
+
const router = useRouter()
|
|
41
|
+
const { getOverlayRoute } = useOverlayNavigation()
|
|
42
|
+
|
|
43
|
+
// computeds
|
|
44
|
+
const routerLinkProps = computed(() => {
|
|
45
|
+
return {
|
|
46
|
+
to: props.route,
|
|
47
|
+
|
|
48
|
+
...(props.useOverlayRoute && {
|
|
49
|
+
onClick: event => {
|
|
50
|
+
/**
|
|
51
|
+
* @click.prevent pra evitar com que o router-link já trate o clique e tente navegar para a rota
|
|
52
|
+
* normalmente, o que não é o desejado quando queremos abrir um overlay
|
|
53
|
+
*/
|
|
54
|
+
event.preventDefault()
|
|
55
|
+
|
|
56
|
+
router.push(getOverlayRoute(props.route))
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
</script>
|
|
62
|
+
|
|
63
|
+
<style lang="scss">
|
|
64
|
+
.qas-router-link {
|
|
65
|
+
transition: color var(--qas-generic-transition);
|
|
66
|
+
color: inherit;
|
|
67
|
+
|
|
68
|
+
&:hover {
|
|
69
|
+
color: $primary;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
</style>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
type: component
|
|
2
|
+
|
|
3
|
+
meta:
|
|
4
|
+
desc: Componente de link baseado no router-link. Ao passar a prop use-overlay-route, o clique normal abre a rota no overlay. Ao abrir em uma nova guia, navega diretamente para a página, sem overlay.
|
|
5
|
+
|
|
6
|
+
props:
|
|
7
|
+
label:
|
|
8
|
+
desc: Texto exibido como conteúdo padrão do link, utilizado quando o slot default não é fornecido.
|
|
9
|
+
default: ''
|
|
10
|
+
type: String
|
|
11
|
+
|
|
12
|
+
route:
|
|
13
|
+
desc: Rota de destino do link, repassada diretamente ao `router-link`.
|
|
14
|
+
required: true
|
|
15
|
+
type: Object
|
|
16
|
+
|
|
17
|
+
use-overlay-route:
|
|
18
|
+
desc: Quando verdadeiro, o clique no link abre a rota no overlay em vez de navegar normalmente. Ao abrir em uma nova guia, o comportamento padrão do `router-link` é mantido.
|
|
19
|
+
default: false
|
|
20
|
+
type: Boolean
|
|
21
|
+
|
|
22
|
+
slots:
|
|
23
|
+
default:
|
|
24
|
+
desc: Conteúdo do link. Quando não fornecido, exibe o valor da prop `label`.
|
|
@@ -138,7 +138,7 @@ export default {
|
|
|
138
138
|
},
|
|
139
139
|
|
|
140
140
|
hasNoOptionsOnFirstFetch () {
|
|
141
|
-
return this.mx_fetchCount === 1 && !this.mx_hasFilteredOptions
|
|
141
|
+
return this.mx_fetchCount === 1 && !this.mx_hasFilteredOptions && !this.mx_hasNextPage
|
|
142
142
|
},
|
|
143
143
|
|
|
144
144
|
containerHeight () {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<q-select v-model="model" v-bind="attributes" class="qas-select" :class="componentClasses" no-error-icon>
|
|
2
|
+
<q-select ref="select" v-model="model" v-bind="attributes" class="qas-select" :class="componentClasses" no-error-icon>
|
|
3
3
|
<template v-if="hasIcon" #prepend>
|
|
4
4
|
<q-icon :name="defaultIcon" />
|
|
5
5
|
</template>
|
|
@@ -409,6 +409,13 @@ export default {
|
|
|
409
409
|
|
|
410
410
|
onPopupHide () {
|
|
411
411
|
this.isPopupContentOpen = false
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* necessário limpar o campo de input de pesquisa ao fechar o popup, para evitar que o valor da busca permaneça
|
|
415
|
+
* no campo quando abrir novamente e também o texto não sobreponha os itens selecionados ao apertar a tecla tab.
|
|
416
|
+
*/
|
|
417
|
+
if (this.isSearchable) this.$refs.select.updateInputValue('', true)
|
|
418
|
+
|
|
412
419
|
this.$emit('popup-hide')
|
|
413
420
|
},
|
|
414
421
|
|