v-sistec-features 1.9.3 → 1.10.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "v-sistec-features",
3
3
  "private": false,
4
- "version": "1.9.3",
4
+ "version": "1.10.1",
5
5
  "author": "Márlon Bento Azevedo (https://github.com/marlon-bento)",
6
6
  "repository": {
7
7
  "type": "git",
@@ -78,6 +78,4 @@ export interface ExposedFunctions {
78
78
  set_filter: (newFilter: string) => void;
79
79
  set_page: (newPage: number) => void;
80
80
  reSearch: () => void;
81
-
82
-
83
81
  }
@@ -7,7 +7,7 @@ import { inject, onMounted, useSlots, defineSlots } from 'vue';
7
7
  import { dataTableApiKey } from '../keys';
8
8
 
9
9
  defineSlots<{
10
- body?: () => any
10
+ body?: () => any,
11
11
  }>();
12
12
  interface VColumnProps {
13
13
  field?: string | null;
@@ -21,9 +21,8 @@
21
21
  </slot>
22
22
 
23
23
  <Search v-model:search="pagination.search" v-model:filter="pagination.filter" :list_filter="props.list_filter"
24
- :item_use="item_use" @search="reSearch"
25
- :deactivate_search_on_clear="props.deactivate_search_on_clear"
26
- :placeholder_search="props.placeholder_search"/>
24
+ :item_use="item_use" @search="reSearch" :deactivate_search_on_clear="props.deactivate_search_on_clear"
25
+ :placeholder_search="props.placeholder_search" />
27
26
  </div>
28
27
  <slot name="item-selected-info" :selected_items="selected_items" :clearSelection="() => selected_items = []">
29
28
  <div v-if="(props.use_checkbox && selected_items.length > 0) && !props.deactivate_selected_info"
@@ -165,6 +164,7 @@
165
164
  <draggable v-model="draggableColumns" tag="tr" item-key="header" :animation="400"
166
165
  ghost-class="ghost-item" drag-class="dragging-item">
167
166
  <template #header>
167
+ <th v-if="props.use_expandable_items"></th>
168
168
  <th v-if="props.use_checkbox" class="w-1">
169
169
  <input class="form-check-input m-0" type="checkbox" ref="selectAllCheckbox"
170
170
  @change="toggleSelectAll" aria-label="Selecionar todos os itens na página" />
@@ -282,60 +282,109 @@
282
282
 
283
283
  </thead>
284
284
  <tbody>
285
- <TransitionGroup tag="tr" v-for="item in items" :key="item[props.item_key]" name="column-move">
286
- <td v-if="props.use_checkbox" class="w-1">
287
- <input class="form-check-input m-0" type="checkbox" :checked="isSelected(item)"
288
- @change="toggleItemSelection(item)" aria-label="Selecionar este item" />
289
- </td>
290
- <td v-for="col in renderedColumns" :key="col.field || col.header" :class="col.class_row">
291
- <component v-if="col.bodySlot" :is="col.bodySlot" :item="item" :is-selected="isSelected(item)" />
292
- <span @click="col.click ? col.click(item) : null"
293
- :class="col.class_item + (col.click ? ' cursor-pointer' : '')" v-else-if="col.type === 'text'">
294
- {{
295
- limiteText(getSubItem(col.field, item, col.transform_function), col.limite_text ?? null)
296
- }}</span>
297
-
298
- <span @click="col.click ? col.click(item) : null" v-else-if="col.type === 'date'"
299
- :class="col.class_item + (col.click ? ' cursor-pointer' : '')">
300
- <span v-if="col.format === 'complete'">{{ new Date(getSubItem(col.field, item)).toLocaleString()
301
- }}</span>
302
- <span v-if="col.format === 'simple'"> {{ new Date(getSubItem(col.field,
303
- item)).toLocaleDateString()
285
+ <template v-for="item in items" :key="item[props.item_key]">
286
+ <TransitionGroup tag="tr" name="column-move">
287
+ <td v-if="props.use_expandable_items" class="w-1">
288
+ <slot name="expand-button" :item="item" :is-expanded="is_item_expanded(item)"
289
+ :expand_item_toggle="expand_item_toggle">
290
+ <button type="button" class="btn-clean btn-icon-anim"
291
+ :class="{ 'is-expanded': is_item_expanded(item) }" @click="expand_item_toggle(item)">
292
+
293
+ <template v-if="props.type_button_expand === 'arrow'">
294
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
295
+ fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
296
+ stroke-linejoin="round"
297
+ class="icon icon-tabler icons-tabler-outline icon-tabler-chevron-right icon-transition-arrow">
298
+ <path stroke="none" d="M0 0h24v24H0z" fill="none" />
299
+ <path d="M9 6l6 6l-6 6" />
300
+ </svg>
301
+
302
+ </template>
303
+
304
+ <template v-else>
305
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24"
306
+ fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
307
+ stroke-linejoin="round" class="icon icon-transition-plus">
308
+ <path stroke="none" d="M0 0h24v24H0z" fill="none" />
309
+ <path d="M12 5l0 14" class="vertical-line" />
310
+ <path d="M5 12l14 0" />
311
+ </svg>
312
+ </template>
313
+
314
+ </button>
315
+ </slot>
316
+ </td>
317
+ <td v-if="props.use_checkbox" class="w-1">
318
+ <input class="form-check-input m-0" type="checkbox" :checked="isSelected(item)"
319
+ @change="toggleItemSelection(item)" aria-label="Selecionar este item" />
320
+ </td>
321
+
322
+ <td v-for="col in renderedColumns" :key="col.field || col.header" :class="col.class_row">
323
+ <component v-if="col.bodySlot" :is="col.bodySlot" :item="item" :is-selected="isSelected(item)" />
324
+ <span @click="col.click ? col.click(item) : null"
325
+ :class="col.class_item + (col.click ? ' cursor-pointer' : '')" v-else-if="col.type === 'text'">
326
+ {{
327
+ limiteText(getSubItem(col.field, item, col.transform_function), col.limite_text ?? null)
328
+ }}</span>
329
+
330
+ <span @click="col.click ? col.click(item) : null" v-else-if="col.type === 'date'"
331
+ :class="col.class_item + (col.click ? ' cursor-pointer' : '')">
332
+ <span v-if="col.format === 'complete'">{{ new Date(getSubItem(col.field, item)).toLocaleString()
333
+ }}</span>
334
+ <span v-if="col.format === 'simple'"> {{ new Date(getSubItem(col.field,
335
+ item)).toLocaleDateString()
304
336
  }} </span>
305
- </span>
306
- <div @click="col.click ? col.click(item) : null"
307
- :class="col.class_item + (col.click ? ' cursor-pointer' : '')" v-else-if="col.type === 'html'"
308
- v-html="getSubItem(col.field, item)">
309
- </div>
310
-
311
- <div @click="col.click ? col.click(item) : null"
312
- :class="col.class_item + (col.click ? ' cursor-pointer' : '')" v-else-if="col.type === 'img'">
313
-
314
- <div v-if="getSubItem(col.field, item)" v-bind="col.deactivate_img_preview ? {
315
- class: 'container-img'
316
- } :
317
- {
318
- onMouseover: (event) => handleMouseOver(event, getSubItem(col.field, item)),
319
- onMousemove: handleMouseMove,
320
- onMouseleave: handleMouseLeave,
321
- class: 'container-img container-img-preview'
322
- }">
323
-
324
- <img class="img-tamanho" :src="getSubItem(col.field, item)" />
325
- <img class="img-tamanho-cover" :src="getSubItem(col.field, item)" />
326
- <div class="bg-img"></div>
337
+ </span>
338
+ <div @click="col.click ? col.click(item) : null"
339
+ :class="col.class_item + (col.click ? ' cursor-pointer' : '')" v-else-if="col.type === 'html'"
340
+ v-html="getSubItem(col.field, item)">
341
+ </div>
342
+
343
+ <div @click="col.click ? col.click(item) : null"
344
+ :class="col.class_item + (col.click ? ' cursor-pointer' : '')" v-else-if="col.type === 'img'">
345
+
346
+ <div v-if="getSubItem(col.field, item)" v-bind="col.deactivate_img_preview ? {
347
+ class: 'container-img'
348
+ } :
349
+ {
350
+ onMouseover: (event) => handleMouseOver(event, getSubItem(col.field, item)),
351
+ onMousemove: handleMouseMove,
352
+ onMouseleave: handleMouseLeave,
353
+ class: 'container-img container-img-preview'
354
+ }">
355
+
356
+ <img class="img-tamanho" :src="getSubItem(col.field, item)" />
357
+ <img class="img-tamanho-cover" :src="getSubItem(col.field, item)" />
358
+ <div class="bg-img"></div>
359
+ </div>
360
+
327
361
  </div>
362
+ <span class="text-danger erro-custom-container" v-else>tipo <span
363
+ class="badge bg-orange text-white erro-custom-text">{{ col.type }}</span> não suportado</span>
364
+ </td>
365
+ </TransitionGroup>
366
+ <Transition :name="'expand-item-' + props.type_animation_expand"
367
+ :css="!props.deactivate_animation_expand">
368
+ <!-- mostra uma linha após cada item -->
369
+ <tr v-if="is_item_expanded(item)" class="">
370
+ <!-- se estiver usando checkbox existe uma coluna a mais -->
371
+ <td :colspan="colspanExpandItems()">
372
+ <slot name="after-row" :item="item">
373
+
374
+ </slot>
375
+
376
+ </td>
377
+ </tr>
378
+ </Transition>
379
+
380
+
381
+ </template>
328
382
 
329
- </div>
330
- <span class="text-danger erro-custom-container" v-else>tipo <span
331
- class="badge bg-orange text-white erro-custom-text">{{ col.type }}</span> não suportado</span>
332
- </td>
333
- </TransitionGroup>
334
383
  </tbody>
335
384
  </table>
336
385
  </div>
337
- <div v-else-if="first_fetch === false" >
338
-
386
+ <div v-else-if="first_fetch === false">
387
+
339
388
  </div>
340
389
  <div v-else class="text-center p-4 text-secondary">
341
390
  <p class="m-0">Nenhum item encontrado.</p>
@@ -413,6 +462,10 @@ const props = withDefaults(defineProps<VDataTableProps>(), {
413
462
  immediate: true,
414
463
  placeholder_search: "Buscar...",
415
464
  deactivate_search_on_clear: false,
465
+ use_expandable_items: false,
466
+ type_animation_expand: 'expand',
467
+ deactivate_animation_expand: false,
468
+ type_button_expand: 'arrow'
416
469
  });
417
470
 
418
471
 
@@ -427,6 +480,7 @@ const columns = ref<ColumnConfiguration[]>([]);
427
480
  const items = ref<T[]>([]) as Ref<T[]>;
428
481
  const totalItems = ref<number>(0);
429
482
  const selected_items = ref<T[]>([]) as Ref<T[]>;
483
+ const expanded_items = ref<any[]>([]);
430
484
  const selectAllCheckbox = ref<HTMLInputElement | null>(null);
431
485
  const isDelaying = ref<boolean>(false);
432
486
  const delayTimer = ref<ReturnType<typeof setTimeout> | null>(null);
@@ -749,6 +803,28 @@ function set_page(newPage: number): void {
749
803
  console.warn("Número de página inválido.");
750
804
  }
751
805
  }
806
+ function expand_item_toggle(item: any): void {
807
+ const identifier_item = item[props.item_key];
808
+ const index = expanded_items.value.findIndex(expandedItem => expandedItem === identifier_item);
809
+ if (index > -1) {
810
+ expanded_items.value.splice(index, 1); // Remove se já existe
811
+ } else {
812
+ expanded_items.value.push(identifier_item); // Adiciona se não existe
813
+ }
814
+ }
815
+ function is_item_expanded(item: any): boolean {
816
+ const identifier_item = item[props.item_key];
817
+ return expanded_items.value.some(expandedItem => expandedItem === identifier_item);
818
+ }
819
+ function close_all_expanded_items(): void {
820
+ expanded_items.value = [];
821
+ }
822
+ function colspanExpandItems(): number {
823
+ let colspan = columns.value.length;
824
+ if (props.use_checkbox) colspan += 1;
825
+ if (props.use_expandable_items) colspan += 1;
826
+ return colspan;
827
+ }
752
828
 
753
829
  defineExpose<ExposedFunctions<T>>({
754
830
  execute: fetchDataWithDelay,
@@ -761,6 +837,8 @@ defineExpose<ExposedFunctions<T>>({
761
837
  default_params,
762
838
  selected_items,
763
839
  atLeastOneSelected,
840
+ expand_item_toggle,
841
+ close_all_expanded_items
764
842
  });
765
843
  const on_mounted_called = ref<boolean>(false);
766
844
  watch(
@@ -954,4 +1032,106 @@ $max-width-preview: 250px;
954
1032
  display: flex;
955
1033
  justify-content: space-between;
956
1034
  }
1035
+
1036
+
1037
+
1038
+
1039
+
1040
+
1041
+
1042
+
1043
+
1044
+
1045
+ /* 1. Animação de entrada para novos itens */
1046
+ .expand-item-expand-enter-active {
1047
+ transition: all 0.5s ease;
1048
+ }
1049
+
1050
+ .expand-item-expand-enter-from {
1051
+ opacity: 0;
1052
+ transform: scaleY(0.3) translateY(-30px);
1053
+ /* Começa em cima e desce */
1054
+ }
1055
+
1056
+ .expand-item-expand-enter-to {
1057
+ opacity: 1;
1058
+ transform: scaleY(1);
1059
+ transform: translateY(0);
1060
+ }
1061
+
1062
+ /* 2. Animação de saída para itens removidos*/
1063
+ .expand-item-expand-leave-active {
1064
+ transition: all 0.4s ease;
1065
+ }
1066
+
1067
+ .expand-item-expand-leave-to {
1068
+ opacity: 0;
1069
+ transform: scaleY(0.3) translateY(-30px);
1070
+ /* Desliza para cima e desaparece */
1071
+ }
1072
+
1073
+
1074
+
1075
+ .expand-item-fade-enter-active,
1076
+ .expand-item-fade-leave-active {
1077
+ transition: opacity 0.5s ease;
1078
+ }
1079
+
1080
+ .expand-item-fade-enter-from,
1081
+ .expand-item-fade-leave-to {
1082
+ opacity: 0;
1083
+ }
1084
+
1085
+
1086
+
1087
+
1088
+
1089
+ .btn-clean {
1090
+ display: inline-flex;
1091
+ align-items: center;
1092
+ justify-content: center;
1093
+ padding: 4px;
1094
+ border-radius: 4px;
1095
+ transition: background-color 0.2s ease;
1096
+ background: transparent;
1097
+ border: none;
1098
+ outline: none;
1099
+ cursor: pointer;
1100
+
1101
+
1102
+ &:hover {
1103
+ background-color: rgba(0, 0, 0, 0.05);
1104
+ }
1105
+ }
1106
+
1107
+
1108
+ .icon-transition-arrow {
1109
+ transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), color 0.2s ease;
1110
+ color: var(--tblr-primary, #206bc4);
1111
+ }
1112
+
1113
+
1114
+ .is-expanded .icon-transition-arrow {
1115
+ transform: rotate(90deg);
1116
+ }
1117
+
1118
+
1119
+ .icon-transition-plus {
1120
+ transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), color 0.2s ease;
1121
+ color: var(--tblr-primary, #206bc4);
1122
+ }
1123
+
1124
+ .icon-transition-plus .vertical-line {
1125
+ transform-origin: center;
1126
+ transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.2s ease;
1127
+ }
1128
+
1129
+ .is-expanded .icon-transition-plus {
1130
+ transform: rotate(180deg);
1131
+ }
1132
+
1133
+ .is-expanded .icon-transition-plus .vertical-line {
1134
+ transform: scaleY(0);
1135
+ opacity: 0;
1136
+ }
957
1137
  </style>
@@ -54,6 +54,11 @@ export interface VDataTableProps {
54
54
 
55
55
  // Ativa a funcionalidade de seleção com checkboxes
56
56
  use_checkbox?: boolean;
57
+ use_expandable_items?: boolean;
58
+ type_animation_expand?: 'fade' | 'expand' | 'none';
59
+ deactivate_animation_expand?: boolean;
60
+ type_button_expand?: 'arrow' | 'plus';
61
+
57
62
  // Define qual propriedade do item será usada como chave única para a seleção.
58
63
  item_key?: string;
59
64
 
@@ -76,4 +81,6 @@ export interface ExposedFunctions<T extends Record<string, any>> {
76
81
  set_search: (newSearch: string) => void;
77
82
  set_filter: (newFilter: string) => void;
78
83
  set_page: (newPage: number) => void;
84
+ expand_item_toggle: (item: any) => void;
85
+ close_all_expanded_items: () => void;
79
86
  }