@bildvitta/quasar-ui-asteroid 3.20.0-beta.14-alpha.4 → 3.20.0-beta.14
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 +1 -1
- package/src/components/actions-menu/QasActionsMenu.vue +4 -4
- package/src/components/board-generator/QasBoardGenerator.vue +165 -99
- package/src/components/board-generator/QasBoardGenerator.yml +56 -2
- package/src/components/board-generator/private/PvBoardGeneratorCardsContainer.vue +0 -1
- package/src/components/box/QasBox.yml +5 -0
- package/src/components/card/QasCard.vue +1 -0
- package/src/components/date-time-input/QasDateTimeInput.vue +30 -6
- package/src/components/dialog/QasDialog.vue +1 -3
- package/src/components/dialog/QasDialog.yml +8 -0
- package/src/components/expansion-item/QasExpansionItem.yml +5 -0
- package/src/components/form-generator/QasFormGenerator.yml +5 -0
- package/src/components/header/QasHeader.yml +5 -0
- package/src/components/lazy-loading-components/QasLazyLoadingComponents.vue +0 -7
- package/src/components/lazy-loading-components/QasLazyLoadingComponents.yml +34 -9
- package/src/components/select-list-dialog/QasSelectListDialog.vue +1 -1
- package/src/components/stepper/QasStepper.vue +24 -2
- package/src/composables/use-screen.js +17 -1
- package/src/helpers/filters.js +1 -1
- package/src/helpers/set-scroll-gradient.js +5 -2
- package/src/mixins/search-filter.js +1 -1
- 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/package.json
CHANGED
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
<q-item v-bind="getItemProps(item)" :key="key" active-class="primary" clickable data-cy="actions-menu-list-item" @click="setClickHandler(item)">
|
|
7
7
|
<q-item-section avatar>
|
|
8
8
|
<q-spinner v-if="item.loading" size="sm" />
|
|
9
|
-
<q-icon v-else :class="
|
|
9
|
+
<q-icon v-else :class="getMagicAiClasses(item)" :name="item.icon" />
|
|
10
10
|
</q-item-section>
|
|
11
11
|
|
|
12
12
|
<q-item-section>
|
|
13
|
-
<q-item-label :class="
|
|
13
|
+
<q-item-label :class="getMagicAiClasses(item)">
|
|
14
14
|
{{ item.label }}
|
|
15
15
|
</q-item-label>
|
|
16
16
|
</q-item-section>
|
|
@@ -286,8 +286,8 @@ function getItemProps (item) {
|
|
|
286
286
|
}
|
|
287
287
|
}
|
|
288
288
|
|
|
289
|
-
function
|
|
290
|
-
return { 'text-magic-ai':
|
|
289
|
+
function getMagicAiClasses ({ useMagicAiColor }) {
|
|
290
|
+
return { 'text-magic-ai': useMagicAiColor }
|
|
291
291
|
}
|
|
292
292
|
|
|
293
293
|
function handleMenuModel (newValue, oldValue) {
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
<qas-grabbable class="qas-board-generator" v-bind="grabbableProps">
|
|
4
4
|
<div ref="columnsContainer" class="no-wrap q-gutter-md q-pb-xs q-px-lg row">
|
|
5
5
|
<qas-lazy-loading-components :key="lazyLoadingKey" v-model:visible-items="visibleItems" direction="horizontal" placeholder-width="350px" :threshold="0">
|
|
6
|
-
<qas-box v-for="(header, index) in normalizedHeaders" :key="index" :class="
|
|
7
|
-
<div class="ellipsis q-mb-md text-grey-10"
|
|
6
|
+
<qas-box v-for="(header, index) in normalizedHeaders" :key="index" :class="getColumnClasses(header)" :style="containerStyle">
|
|
7
|
+
<div class="ellipsis q-mb-md text-grey-10">
|
|
8
8
|
<qas-skeleton v-if="props.skeleton" type="text" use-contrast width="80%" />
|
|
9
9
|
|
|
10
10
|
<slot v-else :fields="getFieldsByHeader(header)" :header="header" :index="index" name="header-column" />
|
|
@@ -22,24 +22,19 @@
|
|
|
22
22
|
</div>
|
|
23
23
|
|
|
24
24
|
<div class="text-center">
|
|
25
|
-
<qas-btn class="q-mt-md" icon="sym_r_refresh" label="Tentar novamente" :loading="columnsLoading[getKeyByHeader(header)]" @click="
|
|
25
|
+
<qas-btn class="q-mt-md" icon="sym_r_refresh" label="Tentar novamente" :loading="columnsLoading[getKeyByHeader(header)]" @click="handleFetchColumnClick(header)" />
|
|
26
26
|
</div>
|
|
27
27
|
</div>
|
|
28
28
|
|
|
29
29
|
<template v-else>
|
|
30
30
|
<qas-lazy-loading-components :threshold="0">
|
|
31
31
|
<div v-for="(item) in getItemsByHeader(header)" :id="item[props.itemIdKey]" :key="item[props.itemIdKey]" class="qas-board-generator__item" :data-disable-drag="updatingPositionItemKeys.has(item[props.itemIdKey])">
|
|
32
|
-
<!-- <slot v-if="!props.skeleton" :column-index="index" :fields="getFieldsByHeader(header)" :header="header" :item="item" name="column-item" /> -->
|
|
33
|
-
<!-- <qas-card v-if="updatingPositionItemKey === item[props.itemIdKey]" v-bind="skeletonCards.at(0)" :key="item[props.itemIdKey]" class="q-mb-sm" :column-index="index">
|
|
34
|
-
<template #default />
|
|
35
|
-
</qas-card> -->
|
|
36
|
-
|
|
37
32
|
<slot v-if="!props.skeleton" :column-index="index" :fields="getFieldsByHeader(header)" :header="header" :is-updating-position="updatingPositionItemKeys.has(item[props.itemIdKey])" :item="item" name="column-item" />
|
|
38
33
|
</div>
|
|
39
34
|
</qas-lazy-loading-components>
|
|
40
35
|
|
|
41
36
|
<div class="full-width justify-center row">
|
|
42
|
-
<qas-btn v-if="hasSeeMore(header)" icon="sym_r_add" :label="props.seeMoreButtonLabel" :loading="columnsLoading[getKeyByHeader(header)]" :use-label-on-small-screen="false" variant="tertiary" @click="
|
|
37
|
+
<qas-btn v-if="hasSeeMore(header)" icon="sym_r_add" :label="props.seeMoreButtonLabel" :loading="columnsLoading[getKeyByHeader(header)]" :use-label-on-small-screen="false" variant="tertiary" @click="handleFetchColumnClick(header)" />
|
|
43
38
|
|
|
44
39
|
<template v-if="hasSkeletonByHeader(header)">
|
|
45
40
|
<div class="q-col-gutter-y-sm row">
|
|
@@ -117,11 +112,6 @@ const props = defineProps({
|
|
|
117
112
|
default: () => ({})
|
|
118
113
|
},
|
|
119
114
|
|
|
120
|
-
headerBoxProps: {
|
|
121
|
-
type: Object,
|
|
122
|
-
default: () => ({})
|
|
123
|
-
},
|
|
124
|
-
|
|
125
115
|
columnIdKey: {
|
|
126
116
|
type: String,
|
|
127
117
|
required: true
|
|
@@ -182,13 +172,7 @@ const props = defineProps({
|
|
|
182
172
|
},
|
|
183
173
|
|
|
184
174
|
skeleton: {
|
|
185
|
-
type: Boolean
|
|
186
|
-
default: true
|
|
187
|
-
},
|
|
188
|
-
|
|
189
|
-
useMarkRaw: {
|
|
190
|
-
type: Boolean,
|
|
191
|
-
default: true
|
|
175
|
+
type: Boolean
|
|
192
176
|
},
|
|
193
177
|
|
|
194
178
|
useDragAndDropX: {
|
|
@@ -247,8 +231,13 @@ const columnsLoading = ref({})
|
|
|
247
231
|
const columnsFieldsModel = ref({})
|
|
248
232
|
const showConfirmDialog = ref(false)
|
|
249
233
|
const isLoadingUpdatePosition = ref(false)
|
|
250
|
-
const
|
|
234
|
+
const hideSkeleton = ref(false)
|
|
251
235
|
const columnsWithError = ref({})
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Set de IDs dos itens com update de posição em andamento.
|
|
239
|
+
* Usamos Set para evitar duplicatas e ter O(1) no has().
|
|
240
|
+
*/
|
|
252
241
|
const updatingPositionItemKeys = ref(new Set())
|
|
253
242
|
|
|
254
243
|
/**
|
|
@@ -309,7 +298,10 @@ let currentFetchColumnsSession = 0
|
|
|
309
298
|
|
|
310
299
|
/**
|
|
311
300
|
* Estado do smooth scroll horizontal.
|
|
312
|
-
* Acumula o target e interpola suavemente via requestAnimationFrame.
|
|
301
|
+
* Acumula o target e interpola suavemente via requestAnimationFrame (rAF).
|
|
302
|
+
*
|
|
303
|
+
* rAF (requestAnimationFrame) sincroniza a animação com o refresh da tela (~60fps).
|
|
304
|
+
* Isso evita "saltos" de scroll, tornando o movimento suave e fluido.
|
|
313
305
|
*/
|
|
314
306
|
const SMOOTH_SCROLL_LERP = 0.2
|
|
315
307
|
|
|
@@ -352,13 +344,15 @@ const grabbableProps = {
|
|
|
352
344
|
* Gera cards de skeleton para exibir enquanto carrega os itens da coluna, o mesmo é usado para preencher a coluna
|
|
353
345
|
* quando a prop "skeleton" for true, indicando que é para simular o carregamento.
|
|
354
346
|
*/
|
|
355
|
-
const skeletonCards = Array.from({ length: 6 }).map(() =>
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
347
|
+
const skeletonCards = Array.from({ length: 6 }).map(() => {
|
|
348
|
+
return {
|
|
349
|
+
skeleton: true,
|
|
350
|
+
title: '-',
|
|
351
|
+
expansionProps: { label: '-' },
|
|
352
|
+
actionsMenuProps: { list: {} },
|
|
353
|
+
useSelection: true
|
|
354
|
+
}
|
|
355
|
+
})
|
|
362
356
|
|
|
363
357
|
// computeds
|
|
364
358
|
const columnContainerElements = computed(() => {
|
|
@@ -378,38 +372,6 @@ const normalizedHeaders = computed(() => {
|
|
|
378
372
|
return props.headers
|
|
379
373
|
})
|
|
380
374
|
|
|
381
|
-
// watchers
|
|
382
|
-
watch(
|
|
383
|
-
() => normalizedHeaders.value,
|
|
384
|
-
value => {
|
|
385
|
-
if (isLoadingUpdatePosition.value || !value?.length) return
|
|
386
|
-
|
|
387
|
-
fetchColumnsValues()
|
|
388
|
-
}
|
|
389
|
-
)
|
|
390
|
-
|
|
391
|
-
watch(() => columnContainerElements.value, () => {
|
|
392
|
-
setColumnHeightContainer()
|
|
393
|
-
handleElementsList()
|
|
394
|
-
})
|
|
395
|
-
|
|
396
|
-
// hooks
|
|
397
|
-
onMounted(() => {
|
|
398
|
-
if (normalizedHeaders.value.length) {
|
|
399
|
-
fetchColumnsValues()
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
window.addEventListener('resize', setColumnHeightContainer)
|
|
403
|
-
})
|
|
404
|
-
|
|
405
|
-
onBeforeUnmount(() => {
|
|
406
|
-
abortAllColumnRequests()
|
|
407
|
-
destroySortable()
|
|
408
|
-
|
|
409
|
-
window.removeEventListener('resize', setColumnHeightContainer)
|
|
410
|
-
})
|
|
411
|
-
|
|
412
|
-
// Computeds
|
|
413
375
|
const columnsResultsModel = computed({
|
|
414
376
|
get () {
|
|
415
377
|
return props.results
|
|
@@ -422,8 +384,6 @@ const columnsResultsModel = computed({
|
|
|
422
384
|
|
|
423
385
|
const isDragging = computed(() => draggingCount.value > 0)
|
|
424
386
|
|
|
425
|
-
provide('isDragging', isDragging)
|
|
426
|
-
|
|
427
387
|
const hasColumnsLength = computed(() => !!Object.keys(columnsResultsModel.value).length)
|
|
428
388
|
|
|
429
389
|
const containerStyle = computed(() => `width: ${props.columnWidth};`)
|
|
@@ -449,12 +409,54 @@ const defaultConfirmDialogProps = computed(() => {
|
|
|
449
409
|
}
|
|
450
410
|
})
|
|
451
411
|
|
|
412
|
+
// provide
|
|
413
|
+
provide('isDragging', isDragging)
|
|
414
|
+
|
|
415
|
+
// watchers
|
|
416
|
+
watch(
|
|
417
|
+
() => normalizedHeaders.value,
|
|
418
|
+
value => {
|
|
419
|
+
if (isLoadingUpdatePosition.value || !value?.length) return
|
|
420
|
+
|
|
421
|
+
fetchColumnsValues()
|
|
422
|
+
}
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
watch(() => columnContainerElements.value, () => {
|
|
426
|
+
setColumnHeightContainer()
|
|
427
|
+
handleElementsList()
|
|
428
|
+
})
|
|
429
|
+
|
|
430
|
+
// hooks
|
|
431
|
+
onMounted(() => {
|
|
432
|
+
if (normalizedHeaders.value.length) {
|
|
433
|
+
fetchColumnsValues()
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
window.addEventListener('resize', setColumnHeightContainer)
|
|
437
|
+
})
|
|
438
|
+
|
|
439
|
+
onBeforeUnmount(() => {
|
|
440
|
+
abortAllColumnRequests()
|
|
441
|
+
destroySortable()
|
|
442
|
+
|
|
443
|
+
window.removeEventListener('resize', setColumnHeightContainer)
|
|
444
|
+
})
|
|
445
|
+
|
|
452
446
|
// functions
|
|
453
447
|
/*
|
|
454
448
|
* Setar o tamanho do container do board, onde deverá ser a altura passada via prop, ou o default será ocupar o maximo
|
|
455
449
|
* de espaço que ele conseguir considerando a altura do container em relação ao topo.
|
|
456
450
|
*/
|
|
457
451
|
function setColumnHeightContainer () {
|
|
452
|
+
if (props.height) {
|
|
453
|
+
columnContainerElements.value.forEach(columnElement => {
|
|
454
|
+
columnElement.style.height = props.height
|
|
455
|
+
})
|
|
456
|
+
|
|
457
|
+
return
|
|
458
|
+
}
|
|
459
|
+
|
|
458
460
|
// Primeira etapa: calcula e aplica a altura inicial de cada coluna
|
|
459
461
|
columnContainerElements.value.forEach(columnElement => {
|
|
460
462
|
// Pega a posição atual da coluna em relação ao topo da viewport
|
|
@@ -514,7 +516,7 @@ async function fetchColumns () {
|
|
|
514
516
|
const mySession = ++currentFetchColumnsSession
|
|
515
517
|
|
|
516
518
|
// Mapeia os índices visíveis para os IDs de coluna correspondentes
|
|
517
|
-
const visibleKeys = new Set(visibleItems.value.map(
|
|
519
|
+
const visibleKeys = new Set(visibleItems.value.map(item => getKeyByHeader(normalizedHeaders.value[item])))
|
|
518
520
|
|
|
519
521
|
const visibleHeaders = visibleKeys.size
|
|
520
522
|
? normalizedHeaders.value.filter(header => visibleKeys.has(getKeyByHeader(header)))
|
|
@@ -548,6 +550,7 @@ async function fetchColumns () {
|
|
|
548
550
|
if (currentFetchColumnsSession !== mySession) return
|
|
549
551
|
|
|
550
552
|
const allPromises = [...visibleColumns, ...hiddenColumns]
|
|
553
|
+
console.log('🚀 ~ fetchColumns ~ allPromises:', allPromises)
|
|
551
554
|
|
|
552
555
|
const hasAllPromisesSucceeded = allPromises.every(promise => promise.status === 'fulfilled')
|
|
553
556
|
const hasAllPromisesFailed = allPromises.length > 0 && allPromises.every(promise => promise.status === 'rejected')
|
|
@@ -568,18 +571,36 @@ async function fetchColumns () {
|
|
|
568
571
|
}
|
|
569
572
|
|
|
570
573
|
/*
|
|
571
|
-
*
|
|
574
|
+
* Wrapper para chamadas de fetchColumn originadas de eventos de clique do usuário.
|
|
575
|
+
* fetchColumn relança o erro para que Promise.allSettled em fetchColumns consiga
|
|
576
|
+
* detectar colunas com falha; aqui o erro já foi tratado internamente, portanto
|
|
577
|
+
* é suprimido para evitar o warning "Unhandled error during execution of component
|
|
578
|
+
* event handler" do Vue.
|
|
572
579
|
*/
|
|
573
|
-
|
|
580
|
+
function handleFetchColumnClick (header) {
|
|
581
|
+
fetchColumn(header, true)
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* Busca a coluna com base no header recebido.
|
|
586
|
+
*
|
|
587
|
+
* @param header - payload do header da coluna a ser buscada, exemplo: { date: '2024-02-12', ... }
|
|
588
|
+
* @param shouldHideSkeleton - flag para controlar se o skeleton de carregamento deve ser ocultado durante a busca
|
|
589
|
+
*/
|
|
590
|
+
async function fetchColumn (header, shouldHideSkeleton) {
|
|
574
591
|
const headerKey = getKeyByHeader(header)
|
|
575
592
|
|
|
576
|
-
|
|
577
|
-
|
|
593
|
+
/**
|
|
594
|
+
* Cancela qualquer request anterior para esta mesma coluna antes de iniciar a nova.
|
|
595
|
+
* Garante que nunca haja duas requests paralelas para a mesma coluna.
|
|
596
|
+
*/
|
|
578
597
|
abortColumnRequest(headerKey)
|
|
579
598
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
599
|
+
/**
|
|
600
|
+
* Incrementa a geração desta coluna para invalidar callbacks onLoading da request cancelada,
|
|
601
|
+
* evitando que o onLoading(false) antigo sobrescreva o estado da nova request.
|
|
602
|
+
* Usa ?? 0 pois ++undefined retorna NaN, e NaN === NaN é sempre false, travando o skeleton.
|
|
603
|
+
*/
|
|
583
604
|
columnFetchGenerations[headerKey] = (columnFetchGenerations[headerKey] ?? 0) + 1
|
|
584
605
|
const generation = columnFetchGenerations[headerKey]
|
|
585
606
|
|
|
@@ -588,10 +609,10 @@ async function fetchColumn (header, fromSeeMore, setEr) {
|
|
|
588
609
|
|
|
589
610
|
const { limit, offset } = columnsPagination.value[headerKey] || {}
|
|
590
611
|
|
|
591
|
-
|
|
612
|
+
hideSkeleton.value = shouldHideSkeleton
|
|
592
613
|
|
|
593
614
|
const { data: response, error } = await promiseHandler(
|
|
594
|
-
axios.get(`${props.columnUrl}/${headerKey}
|
|
615
|
+
axios.get(`${props.columnUrl}/${headerKey}`, {
|
|
595
616
|
signal: abortController.signal,
|
|
596
617
|
params: {
|
|
597
618
|
...props.columnParams,
|
|
@@ -601,8 +622,10 @@ async function fetchColumn (header, fromSeeMore, setEr) {
|
|
|
601
622
|
}),
|
|
602
623
|
{
|
|
603
624
|
onLoading: value => {
|
|
604
|
-
|
|
605
|
-
|
|
625
|
+
/**
|
|
626
|
+
* Só atualiza o loading se ainda for a request atual desta coluna,
|
|
627
|
+
* evitando que o onLoading(false) de uma request cancelada sobrescreva o da nova.
|
|
628
|
+
*/
|
|
606
629
|
if (columnFetchGenerations[headerKey] === generation) {
|
|
607
630
|
columnsLoading.value[headerKey] = value
|
|
608
631
|
}
|
|
@@ -613,7 +636,7 @@ async function fetchColumn (header, fromSeeMore, setEr) {
|
|
|
613
636
|
|
|
614
637
|
delete columnAbortControllers[headerKey]
|
|
615
638
|
|
|
616
|
-
|
|
639
|
+
hideSkeleton.value = false
|
|
617
640
|
|
|
618
641
|
if (error) {
|
|
619
642
|
// Request cancelada intencionalmente (novo fetchColumnsValues chamado): ignora silenciosamente.
|
|
@@ -647,7 +670,7 @@ async function fetchColumn (header, fromSeeMore, setEr) {
|
|
|
647
670
|
* onde cada item do objeto é uma coluna no board. O mesmo vale para "columnsFieldsModel", "columnsLoading" e
|
|
648
671
|
* "columnPagination", organizando os fields, loadings e o controle de paginação por chave identificadora do header.
|
|
649
672
|
*/
|
|
650
|
-
columnsResultsModel.value[headerKey] =
|
|
673
|
+
columnsResultsModel.value[headerKey] = hasDragAndDrop ? newColumnValues : markRaw(newColumnValues)
|
|
651
674
|
|
|
652
675
|
/*
|
|
653
676
|
* Pode acontecer das options nos fields da segunda página serem diferentes da primeira página,
|
|
@@ -755,15 +778,19 @@ function setColumnsPagination () {
|
|
|
755
778
|
|
|
756
779
|
columnsPagination.value[headerKey] = { limit: props.limitPerColumn, offset: 0 }
|
|
757
780
|
|
|
758
|
-
|
|
759
|
-
|
|
781
|
+
/**
|
|
782
|
+
* Inicia como true para exibir skeleton imediatamente, evitando flash de tela vazia
|
|
783
|
+
* entre o reset e o início das novas requests.
|
|
784
|
+
*/
|
|
760
785
|
columnsLoading.value[headerKey] = true
|
|
761
786
|
})
|
|
762
787
|
}
|
|
763
788
|
|
|
764
789
|
function fetchColumnsValues () {
|
|
765
|
-
|
|
766
|
-
|
|
790
|
+
/**
|
|
791
|
+
* Cancela todas as requests em andamento antes de iniciar novas, evitando resposta de requests
|
|
792
|
+
* antigas sobrescrevendo dados da nova sessão.
|
|
793
|
+
*/
|
|
767
794
|
abortAllColumnRequests()
|
|
768
795
|
|
|
769
796
|
lazyLoadingKey.value++
|
|
@@ -777,10 +804,10 @@ function fetchColumnsValues () {
|
|
|
777
804
|
/**
|
|
778
805
|
* Descrição:
|
|
779
806
|
* Exibe o texto quando:
|
|
780
|
-
* -
|
|
781
|
-
* -
|
|
782
|
-
* -
|
|
783
|
-
* -
|
|
807
|
+
* - Não está carregando a coluna
|
|
808
|
+
* - Não tem itens na coluna
|
|
809
|
+
* - Não estou fazendo o drag and drop
|
|
810
|
+
* - Não está exibindo o dialog de confirmação
|
|
784
811
|
*
|
|
785
812
|
* @param {Object} header
|
|
786
813
|
*/
|
|
@@ -881,8 +908,6 @@ function setSortable (element, index) {
|
|
|
881
908
|
|
|
882
909
|
group: useOnlyDragAndDropY ? `column-${index}` : 'shared',
|
|
883
910
|
|
|
884
|
-
// direction: 'vertical',
|
|
885
|
-
|
|
886
911
|
/**
|
|
887
912
|
* invertSwap muda o algoritmo de swap: em vez de trocar quando o centro do item arrastado
|
|
888
913
|
* cruza o centro do alvo (instável, causa oscilação), troca apenas quando cruza a BORDA
|
|
@@ -944,7 +969,10 @@ function stopDragging () {
|
|
|
944
969
|
/**
|
|
945
970
|
* Intercepta cada chamada de scroll do SortableJS.
|
|
946
971
|
* - Scroll vertical (colunas): retorna 'continue' para manter o comportamento nativo.
|
|
947
|
-
* - Scroll horizontal (container do board): aplica smooth scroll via rAF.
|
|
972
|
+
* - Scroll horizontal (container do board): aplica smooth scroll interpolado via rAF.
|
|
973
|
+
*
|
|
974
|
+
* rAF (requestAnimationFrame) sincroniza com o refresh da tela (~60fps),
|
|
975
|
+
* tornando o scroll suave. Sem rAF, seria um "salto" abrupto.
|
|
948
976
|
*/
|
|
949
977
|
function handleSortableScroll (offsetX, offsetY, evt, _touchEvt, scrollEl) {
|
|
950
978
|
// Obtém o container com scroll horizontal (`.qas-grabbable__container`).
|
|
@@ -961,16 +989,25 @@ function handleSortableScroll (offsetX, offsetY, evt, _touchEvt, scrollEl) {
|
|
|
961
989
|
|
|
962
990
|
smoothScrollTarget = Math.max(0, Math.min(smoothScrollTarget + offsetX, maxScroll))
|
|
963
991
|
|
|
992
|
+
// Se não há animação em andamento, inicia uma.
|
|
993
|
+
// rAF (requestAnimationFrame) agenda a função smoothScrollStep para o próximo frame (~16ms a 60fps).
|
|
994
|
+
|
|
995
|
+
/**
|
|
996
|
+
* Se não há animação em andamento, inicia uma. rAF (requestAnimationFrame) agenda a função smoothScrollStep
|
|
997
|
+
* para o próximo frame (~16ms a 60fps).
|
|
998
|
+
*/
|
|
964
999
|
if (!smoothScrollRafId) {
|
|
965
1000
|
smoothScrollRafId = requestAnimationFrame(smoothScrollStep)
|
|
966
1001
|
}
|
|
1002
|
+
|
|
967
1003
|
return
|
|
968
1004
|
}
|
|
969
1005
|
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
1006
|
+
/**
|
|
1007
|
+
* SortableJS chama essa função com offsets de scroll.
|
|
1008
|
+
* Para scroll vertical: retorna 'continue' para deixar o comportamento nativo.
|
|
1009
|
+
* Para scroll horizontal: aplica smooth scroll customizado via rAF.
|
|
1010
|
+
*/
|
|
974
1011
|
if (offsetY) {
|
|
975
1012
|
const clientY = evt?.clientY ?? evt?.touches?.[0]?.clientY
|
|
976
1013
|
|
|
@@ -991,6 +1028,14 @@ function handleSortableScroll (offsetX, offsetY, evt, _touchEvt, scrollEl) {
|
|
|
991
1028
|
/**
|
|
992
1029
|
* Loop de animação que interpola o scrollLeft em direção ao target
|
|
993
1030
|
* usando fator de lerp (move 20% da distância restante por frame).
|
|
1031
|
+
*
|
|
1032
|
+
* Funciona assim:
|
|
1033
|
+
* 1. Calcula a diferença entre a posição atual e a posição desejada (target).
|
|
1034
|
+
* 2. Se ainda há diferença significativa, move 20% da distância restante.
|
|
1035
|
+
* 3. Agenda o próximo frame via rAF para continuar a interpolação.
|
|
1036
|
+
* 4. Quando chega perto o suficiente (~0.5px), para a animação.
|
|
1037
|
+
*
|
|
1038
|
+
* Resultado: scroll suave e progressivo, não um "salto" abrupto.
|
|
994
1039
|
*/
|
|
995
1040
|
function smoothScrollStep () {
|
|
996
1041
|
smoothScrollRafId = null
|
|
@@ -1000,16 +1045,21 @@ function smoothScrollStep () {
|
|
|
1000
1045
|
const current = smoothScrollEl.scrollLeft
|
|
1001
1046
|
const diff = smoothScrollTarget - current
|
|
1002
1047
|
|
|
1048
|
+
// Se chegou bem perto do target, para na posição exata.
|
|
1003
1049
|
if (Math.abs(diff) < 0.5) {
|
|
1004
1050
|
smoothScrollEl.scrollLeft = smoothScrollTarget
|
|
1005
1051
|
return
|
|
1006
1052
|
}
|
|
1007
1053
|
|
|
1054
|
+
// Move 20% da distância restante (interpolação suave).
|
|
1008
1055
|
smoothScrollEl.scrollLeft = current + diff * SMOOTH_SCROLL_LERP
|
|
1056
|
+
|
|
1057
|
+
// Agenda o próximo frame para continuar a animação (chamada recursiva via rAF).
|
|
1009
1058
|
smoothScrollRafId = requestAnimationFrame(smoothScrollStep)
|
|
1010
1059
|
}
|
|
1011
1060
|
|
|
1012
1061
|
function resetSmoothScroll () {
|
|
1062
|
+
// Cancela a animação agendada (rAF) para evitar que continue executando após o drag terminar.
|
|
1013
1063
|
if (smoothScrollRafId) {
|
|
1014
1064
|
cancelAnimationFrame(smoothScrollRafId)
|
|
1015
1065
|
smoothScrollRafId = null
|
|
@@ -1051,7 +1101,7 @@ function addItemToList ({ headerKey, item, index }) {
|
|
|
1051
1101
|
|
|
1052
1102
|
updatedList.splice(insertIndex, 0, item)
|
|
1053
1103
|
|
|
1054
|
-
columnsResultsModel.value[headerKey] =
|
|
1104
|
+
columnsResultsModel.value[headerKey] = updatedList
|
|
1055
1105
|
columnsPagination.value[headerKey].count += 1
|
|
1056
1106
|
}
|
|
1057
1107
|
|
|
@@ -1172,7 +1222,7 @@ function removeItemFromList ({ headerKey, itemId }) {
|
|
|
1172
1222
|
const updatedList = [...columnItemList]
|
|
1173
1223
|
updatedList.splice(itemIndex, 1)
|
|
1174
1224
|
|
|
1175
|
-
columnsResultsModel.value[headerKey] =
|
|
1225
|
+
columnsResultsModel.value[headerKey] = updatedList
|
|
1176
1226
|
|
|
1177
1227
|
/**
|
|
1178
1228
|
* Remove o item do count da coluna para não mostrar o botão de "Ver mais¨.
|
|
@@ -1180,13 +1230,29 @@ function removeItemFromList ({ headerKey, itemId }) {
|
|
|
1180
1230
|
columnsPagination.value[headerKey].count -= 1
|
|
1181
1231
|
}
|
|
1182
1232
|
|
|
1233
|
+
/**
|
|
1234
|
+
* Substitui um item na lista de uma coluna pelos dados atualizados do servidor.
|
|
1235
|
+
*
|
|
1236
|
+
* 1. Localiza a lista de itens da coluna
|
|
1237
|
+
* 2. Encontra o índice do item usando o itemIdKey como identificador
|
|
1238
|
+
* 3. Se encontrado, altera o item localmente (sem criar nova referência de array)
|
|
1239
|
+
*
|
|
1240
|
+
* @param {Object} params
|
|
1241
|
+
* @param {string} params.headerKey - ID da coluna
|
|
1242
|
+
* @param {string|number} params.itemId - ID do item a atualizar (deve corresponder a props.itemIdKey)
|
|
1243
|
+
* @param {Object} params.updatedItem - Novo objeto do item com dados atualizados
|
|
1244
|
+
*
|
|
1245
|
+
*/
|
|
1183
1246
|
function updateItemInList ({ headerKey, itemId, updatedItem }) {
|
|
1184
1247
|
const columnItemList = columnsResultsModel.value[headerKey]
|
|
1185
1248
|
|
|
1249
|
+
// Encontra o índice do item na lista usando o identificador (itemIdKey)
|
|
1186
1250
|
const itemIndex = columnItemList.findIndex(itemContent => itemContent[props.itemIdKey] === itemId)
|
|
1187
1251
|
|
|
1252
|
+
// Se não encontrar (itemIndex === -1), retorna sem fazer nada
|
|
1188
1253
|
if (!~itemIndex) return
|
|
1189
1254
|
|
|
1255
|
+
// Substitui o item antigo pelo item atualizado na mesma posição
|
|
1190
1256
|
columnItemList.splice(itemIndex, 1, updatedItem)
|
|
1191
1257
|
}
|
|
1192
1258
|
|
|
@@ -1209,7 +1275,7 @@ async function updatePosition ({ newHeaderKey, oldHeaderKey, itemId, event, opti
|
|
|
1209
1275
|
|
|
1210
1276
|
const isFnUpdatePositionUrl = typeof props.updatePositionUrl === 'function'
|
|
1211
1277
|
|
|
1212
|
-
|
|
1278
|
+
hideSkeleton.value = true
|
|
1213
1279
|
|
|
1214
1280
|
const url = isFnUpdatePositionUrl
|
|
1215
1281
|
? props.updatePositionUrl({ newHeaderKey, oldHeaderKey, itemId })
|
|
@@ -1314,7 +1380,7 @@ function isCancelledError (error) {
|
|
|
1314
1380
|
* @param {string|number} params.itemId - ID do item a ser movido (valor correspondente à prop `itemIdKey`).
|
|
1315
1381
|
* @param {string|number} params.fromColumnId - ID da coluna de origem (valor correspondente à prop `columnIdKey`).
|
|
1316
1382
|
* @param {string|number} params.toColumnId - ID da coluna de destino (valor correspondente à prop `columnIdKey`).
|
|
1317
|
-
* @param {Object}
|
|
1383
|
+
* @param {Object} [params.updatedItem] - Dados atualizados do item. Quando informado, sobrescreve o item original na coluna de destino.
|
|
1318
1384
|
* @returns {void}
|
|
1319
1385
|
*/
|
|
1320
1386
|
function transferItemToColumn ({ itemId, fromColumnId, toColumnId, updatedItem }) {
|
|
@@ -1334,10 +1400,10 @@ function transferItemToColumn ({ itemId, fromColumnId, toColumnId, updatedItem }
|
|
|
1334
1400
|
function hasSkeletonByHeader (header) {
|
|
1335
1401
|
const headerKey = getKeyByHeader(header)
|
|
1336
1402
|
|
|
1337
|
-
return (props.skeleton || columnsLoading.value[headerKey]) && !
|
|
1403
|
+
return (props.skeleton || columnsLoading.value[headerKey]) && !hideSkeleton.value
|
|
1338
1404
|
}
|
|
1339
1405
|
|
|
1340
|
-
function
|
|
1406
|
+
function getColumnClasses (header) {
|
|
1341
1407
|
const headerKey = getKeyByHeader(header)
|
|
1342
1408
|
|
|
1343
1409
|
return {
|
|
@@ -97,8 +97,8 @@ props:
|
|
|
97
97
|
type: Number
|
|
98
98
|
default: 12
|
|
99
99
|
|
|
100
|
-
|
|
101
|
-
desc:
|
|
100
|
+
skeleton:
|
|
101
|
+
desc: Exibe o estado de esqueleto de carregamento das colunas. Enquanto `true`, cada coluna renderiza cards fictícios no lugar dos itens reais.
|
|
102
102
|
type: Boolean
|
|
103
103
|
default: true
|
|
104
104
|
|
|
@@ -150,11 +150,26 @@ slots:
|
|
|
150
150
|
column-item:
|
|
151
151
|
desc: Slot para acessar o item da coluna.
|
|
152
152
|
scope:
|
|
153
|
+
column-index:
|
|
154
|
+
desc: Índice da coluna atual.
|
|
155
|
+
type: Number
|
|
156
|
+
default: 0
|
|
157
|
+
|
|
153
158
|
fields:
|
|
154
159
|
desc: Fields referente à coluna atual do template.
|
|
155
160
|
type: Object
|
|
156
161
|
default: {}
|
|
157
162
|
|
|
163
|
+
header:
|
|
164
|
+
desc: Informações do header da coluna atual.
|
|
165
|
+
type: Object
|
|
166
|
+
default: {}
|
|
167
|
+
|
|
168
|
+
is-updating-position:
|
|
169
|
+
desc: Indica se o item está sendo atualizado via drag-and-drop (aguardando retorno da API de update).
|
|
170
|
+
type: Boolean
|
|
171
|
+
default: false
|
|
172
|
+
|
|
158
173
|
item:
|
|
159
174
|
desc: Informações de cada item da coluna que foi buscado através da API.
|
|
160
175
|
type: Object
|
|
@@ -220,6 +235,45 @@ methods:
|
|
|
220
235
|
'fetchColumn: (header) => void':
|
|
221
236
|
desc: Busca uma coluna específica com base no header fornecido.
|
|
222
237
|
|
|
238
|
+
'refreshColumn: (header) => void':
|
|
239
|
+
desc: Reinicia a paginação da coluna e refaz a busca, substituindo completamente os itens existentes.
|
|
240
|
+
params:
|
|
241
|
+
header:
|
|
242
|
+
desc: Header da coluna a ser recarregada (mesmo formato da prop `headers`).
|
|
243
|
+
type: Object
|
|
244
|
+
required: true
|
|
245
|
+
|
|
246
|
+
'removeItemFromList: ({ headerKey, itemId }) => void':
|
|
247
|
+
desc: Remove um item da lista de uma coluna localmente, sem realizar nenhuma requisição. Também decrementa o contador de paginação.
|
|
248
|
+
params:
|
|
249
|
+
headerKey:
|
|
250
|
+
desc: Chave identificadora da coluna de origem (valor correspondente à prop `column-id-key`).
|
|
251
|
+
type: String | Number
|
|
252
|
+
required: true
|
|
253
|
+
itemId:
|
|
254
|
+
desc: ID do item a ser removido (valor correspondente à prop `item-id-key`).
|
|
255
|
+
type: String | Number
|
|
256
|
+
required: true
|
|
257
|
+
|
|
258
|
+
'updateItemInList: ({ headerKey, itemId, updatedItem }) => void':
|
|
259
|
+
desc: Substitui os dados de um item existente em uma coluna localmente, sem realizar nenhuma requisição.
|
|
260
|
+
params:
|
|
261
|
+
headerKey:
|
|
262
|
+
desc: Chave identificadora da coluna (valor correspondente à prop `column-id-key`).
|
|
263
|
+
type: String | Number
|
|
264
|
+
required: true
|
|
265
|
+
itemId:
|
|
266
|
+
desc: ID do item a ser atualizado (valor correspondente à prop `item-id-key`).
|
|
267
|
+
type: String | Number
|
|
268
|
+
required: true
|
|
269
|
+
updatedItem:
|
|
270
|
+
desc: Dados atualizados que substituirão o item original.
|
|
271
|
+
type: Object
|
|
272
|
+
required: true
|
|
273
|
+
|
|
274
|
+
'refetchColumns: () => void':
|
|
275
|
+
desc: Alias de `fetchColumns`. Busca novamente todas as colunas com base nos headers fornecidos.
|
|
276
|
+
|
|
223
277
|
'transferItemToColumn: ({ itemId, fromColumnId, toColumnId, updatedItem? }) => void':
|
|
224
278
|
desc: |
|
|
225
279
|
Transfere um item de uma coluna para outra localmente, sem realizar nenhuma requisição.
|
|
@@ -165,6 +165,12 @@ const hasDatePicker = computed(() => !props.useTimeOnly && !props.readonly)
|
|
|
165
165
|
const hasTimePicker = computed(() => !props.useDateOnly && !props.readonly)
|
|
166
166
|
|
|
167
167
|
watch(() => props.modelValue, (current, original) => {
|
|
168
|
+
/**
|
|
169
|
+
* No caso de ser adicionado uma data incompleta ou inválida, vamos limpar o model,
|
|
170
|
+
* mas não vamos limpar o "currentValue", para não limpar o campo, somente o model.
|
|
171
|
+
*/
|
|
172
|
+
if (!current && original && error.value) return
|
|
173
|
+
|
|
168
174
|
if (!current || props.useTimeOnly) {
|
|
169
175
|
currentValue.value = current
|
|
170
176
|
return
|
|
@@ -217,6 +223,9 @@ function updateModelValue (value) {
|
|
|
217
223
|
if (error.value) {
|
|
218
224
|
hasInvalidDate.value = true
|
|
219
225
|
errorMessage.value = 'Data inválida.'
|
|
226
|
+
|
|
227
|
+
emit('update:modelValue', '')
|
|
228
|
+
|
|
220
229
|
return
|
|
221
230
|
}
|
|
222
231
|
|
|
@@ -262,18 +271,33 @@ function validateDateAndTime (value) {
|
|
|
262
271
|
function validateDateTimeOnBlur () {
|
|
263
272
|
const valueLength = currentValue.value?.replace?.(/_/g, '')?.length
|
|
264
273
|
|
|
274
|
+
// Caso for datetime
|
|
275
|
+
if (!props.useDateOnly && !props.useTimeOnly) {
|
|
276
|
+
const [date, time] = (currentValue.value || '').split(' ') || []
|
|
277
|
+
const isValidDate = date?.replace?.(/_/g, '')?.length === props.dateMask.length
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Caso tenha preenchido a data, a data seja válida e não tenha preenchido o horário,
|
|
281
|
+
* preenche o horário com 00:00, ou de acordo com a mask.
|
|
282
|
+
*/
|
|
283
|
+
if (isValidDate && !validateDateOnly(date) && !time?.length) {
|
|
284
|
+
// Horario setado deve seguir a mascara.
|
|
285
|
+
const defaultTimeByMask = props.timeMask.replace(/[HhMmSs]/g, '0')
|
|
286
|
+
const newValue = `${date} ${defaultTimeByMask}`
|
|
287
|
+
|
|
288
|
+
currentValue.value = newValue
|
|
289
|
+
updateModelValue(newValue)
|
|
290
|
+
|
|
291
|
+
return
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
265
295
|
// valida se o tamanho digitado é o tamanho que a mascara espera receber
|
|
266
296
|
error.value = !!((valueLength < mask.value.length || error.value) && valueLength)
|
|
267
297
|
|
|
268
298
|
if (error.value && !hasInvalidDate.value) {
|
|
269
299
|
errorMessage.value = 'Data incompleta.'
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
if (hasInvalidDate.value) {
|
|
273
|
-
currentValue.value = ''
|
|
274
|
-
}
|
|
275
300
|
|
|
276
|
-
if (error.value || hasInvalidDate.value) {
|
|
277
301
|
emit('update:modelValue', '')
|
|
278
302
|
}
|
|
279
303
|
}
|
|
@@ -5,10 +5,8 @@
|
|
|
5
5
|
<slot name="header">
|
|
6
6
|
<div class="items-center justify-between row">
|
|
7
7
|
<qas-label data-cy="dialog-title" :label="props.card.title" margin="none">
|
|
8
|
-
<slot name="title"
|
|
8
|
+
<slot name="title" />
|
|
9
9
|
</qas-label>
|
|
10
|
-
<!-- <slot name="title">
|
|
11
|
-
</slot> -->
|
|
12
10
|
|
|
13
11
|
<qas-btn v-if="isInfoDialog" v-close-popup color="grey-10" data-cy="dialog-close-btn" icon="sym_r_close" variant="tertiary" />
|
|
14
12
|
</div>
|
|
@@ -69,6 +69,11 @@ slots:
|
|
|
69
69
|
header:
|
|
70
70
|
desc: Slot para o título.
|
|
71
71
|
|
|
72
|
+
provide:
|
|
73
|
+
is-dialog:
|
|
74
|
+
desc: Identificador booleano que indica quando um componente está dentro do contexto de um QasDialog.
|
|
75
|
+
type: Boolean
|
|
76
|
+
|
|
72
77
|
events:
|
|
73
78
|
'@update:model-value -> function (value)':
|
|
74
79
|
desc: Dispara toda vez que o model é atualizado, também utilizado para v-model.
|
|
@@ -111,6 +116,9 @@ selectors:
|
|
|
111
116
|
desc: Seletor do botão de confirmar do componente.
|
|
112
117
|
examples: ['data-cy="dialog-ok-btn"']
|
|
113
118
|
|
|
119
|
+
title:
|
|
120
|
+
desc: Slot para inserir conteúdo adicional ao lado do título do card do dialog.
|
|
121
|
+
|
|
114
122
|
dialog-title:
|
|
115
123
|
desc: Seletor do título do componente.
|
|
116
124
|
examples: ['data-cy="dialog-title"']
|
|
@@ -62,6 +62,11 @@ slots:
|
|
|
62
62
|
content:
|
|
63
63
|
desc: Slot para substituir o conteúdo principal do card.
|
|
64
64
|
|
|
65
|
+
provide:
|
|
66
|
+
is-expansion-item:
|
|
67
|
+
desc: Identificador booleano que indica quando um componente está dentro do contexto de um QasExpansionItem.
|
|
68
|
+
type: Boolean
|
|
69
|
+
|
|
65
70
|
events:
|
|
66
71
|
'@update:model-value -> function(value)':
|
|
67
72
|
desc: Dispara quando o model-value altera, também usado para v-model.
|
|
@@ -120,6 +120,11 @@ slots:
|
|
|
120
120
|
'legend-bottom[nome-da-chave-do-fieldset]-[nome-da-chave-do-subset]':
|
|
121
121
|
desc: Acessa o slot da seção abaixo do conteúdo do form de um subset específico.
|
|
122
122
|
|
|
123
|
+
provide:
|
|
124
|
+
is-form-generator:
|
|
125
|
+
desc: Identificador booleano que indica quando um componente está dentro do contexto de um QasFormGenerator.
|
|
126
|
+
type: Boolean
|
|
127
|
+
|
|
123
128
|
events:
|
|
124
129
|
'@update:model-value -> function(value)':
|
|
125
130
|
desc: Dispara quando o model-value altera, também usado para v-model.
|
|
@@ -54,6 +54,11 @@ props:
|
|
|
54
54
|
type: Boolean
|
|
55
55
|
default: false
|
|
56
56
|
|
|
57
|
+
provide:
|
|
58
|
+
is-header:
|
|
59
|
+
desc: Identificador booleano que indica quando um componente está dentro do contexto de um QasHeader.
|
|
60
|
+
type: Boolean
|
|
61
|
+
|
|
57
62
|
slots:
|
|
58
63
|
actions:
|
|
59
64
|
desc: slot para acessar seção da direita (ações).
|
|
@@ -30,35 +30,28 @@ import { ref, onMounted, onBeforeUnmount, useSlots, nextTick, watch } from 'vue'
|
|
|
30
30
|
|
|
31
31
|
defineOptions({ name: 'QasLazyLoadingComponents' })
|
|
32
32
|
|
|
33
|
-
// const emit = defineEmits(['update:visibleItems'])
|
|
34
|
-
|
|
35
33
|
const props = defineProps({
|
|
36
|
-
// Porcentagem de visibilidade necessária para ativar (0.0 a 1.0)
|
|
37
34
|
threshold: {
|
|
38
35
|
type: Number,
|
|
39
36
|
default: 0.1 // 10% visível
|
|
40
37
|
},
|
|
41
38
|
|
|
42
|
-
// Margem extra ao redor do viewport para pré-carregamento
|
|
43
39
|
rootMargin: {
|
|
44
40
|
type: String,
|
|
45
41
|
default: '0px'
|
|
46
42
|
},
|
|
47
43
|
|
|
48
|
-
// Direção do scroll: 'vertical' (padrão) ou 'horizontal'
|
|
49
44
|
direction: {
|
|
50
45
|
type: String,
|
|
51
46
|
default: 'vertical',
|
|
52
47
|
validator: value => ['vertical', 'horizontal'].includes(value)
|
|
53
48
|
},
|
|
54
49
|
|
|
55
|
-
// Altura dos placeholders antes de carregar (usado em direction='vertical')
|
|
56
50
|
placeholderHeight: {
|
|
57
51
|
type: String,
|
|
58
52
|
default: '500px'
|
|
59
53
|
},
|
|
60
54
|
|
|
61
|
-
// Largura dos placeholders antes de carregar (usado em direction='horizontal')
|
|
62
55
|
placeholderWidth: {
|
|
63
56
|
type: String,
|
|
64
57
|
default: '300px'
|
|
@@ -4,21 +4,46 @@ meta:
|
|
|
4
4
|
desc: Componente para carregar elementos do slot somente quando ficam visíveis na viewport, otimizando performance da página.
|
|
5
5
|
|
|
6
6
|
props:
|
|
7
|
-
|
|
8
|
-
desc:
|
|
9
|
-
default: 0.1
|
|
10
|
-
type: Number
|
|
11
|
-
|
|
12
|
-
root-margin:
|
|
13
|
-
desc: Margem extra ao redor do viewport para pré-carregamento
|
|
14
|
-
default: '0px'
|
|
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.
|
|
15
9
|
type: String
|
|
10
|
+
default: vertical
|
|
11
|
+
values: vertical | horizontal
|
|
16
12
|
|
|
17
13
|
placeholder-height:
|
|
18
|
-
desc: Altura do placeholder exibido enquanto o elemento não está visível
|
|
14
|
+
desc: Altura do placeholder exibido enquanto o elemento não está visível (usado quando `direction` é `vertical`).
|
|
19
15
|
default: '500px'
|
|
20
16
|
type: String
|
|
21
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
|
+
|
|
22
39
|
slots:
|
|
23
40
|
default:
|
|
24
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
|
|
@@ -229,7 +229,7 @@ function getDialogSlot (name) {
|
|
|
229
229
|
|
|
230
230
|
// ------------------------- composable functions ------------------------------
|
|
231
231
|
function useList () {
|
|
232
|
-
const filteredOptions = ref(props.options)
|
|
232
|
+
const filteredOptions = ref([...props.options])
|
|
233
233
|
|
|
234
234
|
const selectedOptions = computed(() => {
|
|
235
235
|
const options = []
|
|
@@ -206,6 +206,27 @@ function previous () {
|
|
|
206
206
|
border-radius: var(--qas-generic-border-radius);
|
|
207
207
|
}
|
|
208
208
|
|
|
209
|
+
.q-stepper__tab {
|
|
210
|
+
/*
|
|
211
|
+
* Por padrão o quasar sempre deixa as demais linhas com o mesmo tamanho, exceto a primeira e a última, que são
|
|
212
|
+
* maiores devido terem alinhamento inicial e final respectivamente, enquanto as demais possuem alinhamento
|
|
213
|
+
* central. Para equalizar visualmente os tamanhos, é necessário setar uma proporção de flex diferente para a
|
|
214
|
+
* primeira/última tab e para as tabs do meio.
|
|
215
|
+
*
|
|
216
|
+
* Proporção 2:3 para equalizar visualmente os tamanhos:
|
|
217
|
+
* - Primeira/última tab: flex 2 (possuem apenas meia linha)
|
|
218
|
+
* - Tabs do meio: flex 3 (possuem linha completa dos dois lados)
|
|
219
|
+
*/
|
|
220
|
+
&:first-child,
|
|
221
|
+
&:last-child {
|
|
222
|
+
flex: 2;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
&:not(:first-child):not(:last-child) {
|
|
226
|
+
flex: 3;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
209
230
|
.q-stepper {
|
|
210
231
|
&__tab {
|
|
211
232
|
&--done {
|
|
@@ -223,9 +244,10 @@ function previous () {
|
|
|
223
244
|
}
|
|
224
245
|
}
|
|
225
246
|
|
|
226
|
-
// Seta a cor do after da linha quando a step está finalizada, é a primeira step ou a próxima é ativa.
|
|
247
|
+
// Seta a cor do after da linha quando a step está finalizada, é a primeira step ou a próxima é ativa ou finalizada.
|
|
227
248
|
&:first-child,
|
|
228
|
-
&:has(+ .q-stepper__tab.q-stepper__tab--active)
|
|
249
|
+
&:has(+ .q-stepper__tab.q-stepper__tab--active),
|
|
250
|
+
&:has(+ .q-stepper__tab.q-stepper__tab--done) {
|
|
229
251
|
.q-stepper__line::after {
|
|
230
252
|
background-color: var(--q-primary);
|
|
231
253
|
}
|
|
@@ -7,8 +7,12 @@ import { computed, reactive } from 'vue'
|
|
|
7
7
|
* isSmall: boolean,
|
|
8
8
|
* isMedium: boolean,
|
|
9
9
|
* isLarge: boolean,
|
|
10
|
+
* isXLarge: boolean,
|
|
11
|
+
* is2XLarge: boolean,
|
|
10
12
|
* untilMedium: boolean,
|
|
11
13
|
* untilLarge: boolean,
|
|
14
|
+
* untilXLarge: boolean,
|
|
15
|
+
* until2XLarge: boolean,
|
|
12
16
|
* isMobile: boolean
|
|
13
17
|
* }}
|
|
14
18
|
*
|
|
@@ -25,15 +29,27 @@ export default function () {
|
|
|
25
29
|
// de 600 até 1023px
|
|
26
30
|
isMedium: computed(() => Screen.sm),
|
|
27
31
|
|
|
28
|
-
//
|
|
32
|
+
// Maior que 1023px
|
|
29
33
|
isLarge: computed(() => Screen.gt.sm),
|
|
30
34
|
|
|
35
|
+
// Maior que 1439px
|
|
36
|
+
isXLarge: computed(() => Screen.gt.md),
|
|
37
|
+
|
|
38
|
+
// Maior que 1919px
|
|
39
|
+
is2XLarge: computed(() => Screen.gt.lg),
|
|
40
|
+
|
|
31
41
|
// de 0 até 599px
|
|
32
42
|
untilMedium: computed(() => Screen.lt.sm),
|
|
33
43
|
|
|
34
44
|
// de 0 ate 1023px
|
|
35
45
|
untilLarge: computed(() => Screen.lt.md),
|
|
36
46
|
|
|
47
|
+
// de 0 até 1439px
|
|
48
|
+
untilXLarge: computed(() => Screen.lt.lg),
|
|
49
|
+
|
|
50
|
+
// de 0 até 1919px
|
|
51
|
+
until2XLarge: computed(() => Screen.lt.xl),
|
|
52
|
+
|
|
37
53
|
// Plataforma
|
|
38
54
|
isMobile: computed(() => Platform.is.mobile || false)
|
|
39
55
|
})
|
package/src/helpers/filters.js
CHANGED
|
@@ -160,7 +160,7 @@ function parseValue (value) {
|
|
|
160
160
|
try { return JSON.parse(value) } catch { return value }
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
-
function booleanLabel (value, trueLabel = '
|
|
163
|
+
function booleanLabel (value, trueLabel = 'Sim', falseLabel = 'Não') {
|
|
164
164
|
try { return JSON.parse(value) ? trueLabel : falseLabel } catch { return value }
|
|
165
165
|
}
|
|
166
166
|
|
|
@@ -174,13 +174,16 @@ export default function setScrollGradient (config = {}) {
|
|
|
174
174
|
const elementRect = element.getBoundingClientRect()
|
|
175
175
|
const parentRect = element.parentElement.getBoundingClientRect()
|
|
176
176
|
|
|
177
|
+
// pequena margem para garantir que o gradiente fique grudado ao elemento.
|
|
178
|
+
const safeArea = 1
|
|
179
|
+
|
|
177
180
|
/**
|
|
178
181
|
* diferença entre o bottom do pai e o bottom do filho, valor positivo significa
|
|
179
182
|
* que há espaço livre abaixo do filho.
|
|
180
183
|
*/
|
|
181
184
|
const distance = {
|
|
182
|
-
end: isVertical ? (parentRect.bottom - elementRect.bottom) : (parentRect.right - elementRect.right),
|
|
183
|
-
start: isVertical ? (elementRect.top - parentRect.top) : (elementRect.left - parentRect.left)
|
|
185
|
+
end: isVertical ? (parentRect.bottom - elementRect.bottom - safeArea) : (parentRect.right - elementRect.right - safeArea),
|
|
186
|
+
start: isVertical ? (elementRect.top - parentRect.top) - safeArea : (elementRect.left - parentRect.left) - safeArea
|
|
184
187
|
}
|
|
185
188
|
|
|
186
189
|
span.style[getDirection(direction)] = `${distance[direction]}px`
|
|
@@ -3,7 +3,7 @@ type: component
|
|
|
3
3
|
meta:
|
|
4
4
|
desc: Plugin que implementa a action `destroy` do `StoreModule` adicionando comportamento de confirmação antes de excluir, este mesmo plugin é utilizado no componente `QasDelete`.
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
provide:
|
|
7
7
|
'this.$qas.delete(config)':
|
|
8
8
|
params:
|
|
9
9
|
config:
|
|
@@ -4,15 +4,31 @@ export default () => {
|
|
|
4
4
|
const screensModel = {
|
|
5
5
|
// até 599px
|
|
6
6
|
isSmall: () => Screen.xs,
|
|
7
|
+
|
|
7
8
|
// de 600 até 1023px
|
|
8
9
|
isMedium: () => Screen.sm,
|
|
9
|
-
|
|
10
|
+
|
|
11
|
+
// Maior que 1023px
|
|
10
12
|
isLarge: () => Screen.gt.sm,
|
|
13
|
+
|
|
14
|
+
// Maior que 1439px
|
|
15
|
+
isXLarge: () => Screen.gt.md,
|
|
16
|
+
|
|
17
|
+
// Maior que 1919px
|
|
18
|
+
is2XLarge: () => Screen.gt.lg,
|
|
19
|
+
|
|
11
20
|
// de 0 até 599px
|
|
12
21
|
untilMedium: () => Screen.lt.sm,
|
|
22
|
+
|
|
13
23
|
// de 0 ate 1023px
|
|
14
24
|
untilLarge: () => Screen.lt.md,
|
|
15
25
|
|
|
26
|
+
// de 0 até 1439px
|
|
27
|
+
untilXLarge: () => Screen.lt.lg,
|
|
28
|
+
|
|
29
|
+
// de 0 até 1919px
|
|
30
|
+
until2XLarge: () => Screen.lt.xl,
|
|
31
|
+
|
|
16
32
|
// Plataforma
|
|
17
33
|
isMobile: () => Platform.is.mobile || false
|
|
18
34
|
}
|
|
@@ -3,7 +3,7 @@ type: component
|
|
|
3
3
|
meta:
|
|
4
4
|
desc: Plugin que implementa o "Screen" do quasar.
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
provide:
|
|
7
7
|
'this.$qas.screen':
|
|
8
8
|
type: Object
|
|
9
9
|
debugger: true
|
|
@@ -11,6 +11,10 @@ inject:
|
|
|
11
11
|
isSmall: false
|
|
12
12
|
isMedium: false
|
|
13
13
|
isLarge: false
|
|
14
|
+
isXLarge: false
|
|
15
|
+
is2XLarge: false
|
|
14
16
|
untilMedium: false
|
|
15
17
|
untilLarge: false
|
|
18
|
+
untilXLarge: false
|
|
19
|
+
until2XLarge: false
|
|
16
20
|
isMobile: false
|