@meistrari/tela-build 1.42.1 → 1.42.2

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.
@@ -0,0 +1,39 @@
1
+ <script setup lang="ts">
2
+ defineOptions({
3
+ name: 'TelaComplexTableFallbackSkeleton',
4
+ })
5
+
6
+ const props = withDefaults(defineProps<{
7
+ rowHeight?: number
8
+ rows?: number
9
+ height?: number | string
10
+ }>(), {
11
+ rowHeight: 64,
12
+ rows: 1,
13
+ })
14
+
15
+ const skeletonStyle = computed(() => ({
16
+ '--complex-table-fallback-row-height': `${props.rowHeight}px`,
17
+ 'height': typeof props.height === 'number' ? `${props.height}px` : props.height ?? `${props.rows * props.rowHeight}px`,
18
+ }))
19
+ </script>
20
+
21
+ <template>
22
+ <div
23
+ aria-hidden="true"
24
+ class="complex-table-fallback-skeleton"
25
+ :style="skeletonStyle"
26
+ />
27
+ </template>
28
+
29
+ <style scoped>
30
+ .complex-table-fallback-skeleton {
31
+ width: 100%;
32
+ min-height: var(--complex-table-fallback-row-height);
33
+ background-color: white;
34
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1380' height='64' viewBox='0 0 1380 64'%3E%3Cg fill='%23EFF1F3'%3E%3Crect x='100' y='28' width='34' height='8' rx='4'/%3E%3Crect x='146' y='28' width='48' height='8' rx='4'/%3E%3Crect x='206' y='28' width='200' height='8' rx='4'/%3E%3Crect x='418' y='28' width='90' height='8' rx='4'/%3E%3Crect x='520' y='28' width='180' height='8' rx='4'/%3E%3Crect x='808' y='28' width='80' height='8' rx='4'/%3E%3Crect x='1012' y='28' width='80' height='8' rx='4'/%3E%3Crect x='1156' y='28' width='80' height='8' rx='4'/%3E%3Crect x='1300' y='28' width='80' height='8' rx='4'/%3E%3C/g%3E%3C/svg%3E");
35
+ background-position: -100px 0;
36
+ background-repeat: repeat-y;
37
+ background-size: 1380px var(--complex-table-fallback-row-height);
38
+ }
39
+ </style>
@@ -1,6 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  import type { Column, Row } from './types'
3
3
  import TelaTableHeaderCell from './complex-table-header-cell.vue'
4
+ import { isHorizontalSpacerColumn } from './composables/horizontal-virtual-columns'
4
5
 
5
6
  defineOptions({
6
7
  name: 'TelaComplexTableHeader',
@@ -43,7 +44,8 @@ const emit = defineEmits(['selectAll'])
43
44
  :class="headerClass"
44
45
  :style="column.style"
45
46
  >
46
- <slot v-if="column.key && $slots[`header-${column.key}`] !== undefined" :name="`header-${column.key}`" :column="column" />
47
+ <div v-if="isHorizontalSpacerColumn(column)" h-56px bg-white border-b-0.5px border-gray-200 />
48
+ <slot v-else-if="column.key && $slots[`header-${column.key}`] !== undefined" :name="`header-${column.key}`" :column="column" />
47
49
  <TelaTableHeaderCell v-else :column="column" type="default" />
48
50
  </th>
49
51
  <th v-for="(_, idx) in (rows?.length ? 0 : 3)" :key="idx" sticky top-0px p-0px />
@@ -2,6 +2,7 @@
2
2
  import type { Column, Row, HoverMechanism } from './types'
3
3
  import { HoverMechanismTypes } from './types'
4
4
  import TelaTableCell from './complex-table-cell.vue'
5
+ import { isHorizontalSpacerColumn } from './composables/horizontal-virtual-columns'
5
6
  import { isLoading, hasContent, hasError, getRowTitle, readRowPath } from './utils'
6
7
 
7
8
  const props = defineProps<{
@@ -15,7 +16,6 @@ const props = defineProps<{
15
16
  loading?: boolean
16
17
  disableRowClick?: boolean
17
18
  hoverMechanism?: HoverMechanism
18
- hoveredRowIndex?: number | null
19
19
  rowClass?: string | ((row: Row) => string)
20
20
  rowIndexClass?: string
21
21
  rowTitlePath?: string
@@ -26,12 +26,13 @@ const props = defineProps<{
26
26
  totalRows?: number
27
27
  hideError?: boolean
28
28
  selectClass?: string
29
+ showBorder?: boolean
30
+ borderMode?: 'default' | 'visible'
29
31
  }>()
30
32
 
31
33
  const emit = defineEmits<{
32
34
  select: [id: string]
33
35
  open: [id: string]
34
- hover: [index: number | null]
35
36
  }>()
36
37
 
37
38
  const slots = useSlots()
@@ -42,23 +43,27 @@ const computedRowClass = computed(() => {
42
43
  // Base classes
43
44
  classes.push('bg-white')
44
45
 
45
- // Border classes
46
- if (props.isFirstRow && props.totalRows && props.totalRows > 1) {
47
- classes.push('b-b-0.5px b-t-0px b-gray-200!')
46
+ if (props.borderMode === 'visible') {
47
+ classes.push('b-b-0.5px! b-t-0px!', props.showBorder === false ? 'b-transparent!' : 'b-gray-200!')
48
48
  }
49
- if (props.rowIndex % 2 !== 0 && props.rowIndex > 0) {
50
- classes.push('b-y-0.5px b-gray-200!')
51
- }
52
- if (props.isLastRow) {
53
- classes.push('b-b-0px!')
49
+ else {
50
+ if (props.showBorder !== false) {
51
+ // Border classes
52
+ if (props.isFirstRow && props.totalRows && props.totalRows > 1) {
53
+ classes.push('b-b-0.5px b-t-0px b-gray-200!')
54
+ }
55
+ if (props.rowIndex % 2 !== 0 && props.rowIndex > 0) {
56
+ classes.push('b-y-0.5px b-gray-200!')
57
+ }
58
+ if (props.isLastRow) {
59
+ classes.push('b-b-0px!')
60
+ }
61
+ }
54
62
  }
55
63
 
56
64
  // Hover classes
57
65
  if (props.hoverMechanism === HoverMechanismTypes.Row) {
58
- classes.push('cursor-pointer')
59
- if (props.hoveredRowIndex === props.rowIndex) {
60
- classes.push('bg-gray-50!')
61
- }
66
+ classes.push('cursor-pointer', 'hover:bg-gray-50!')
62
67
  }
63
68
 
64
69
  // Custom row class
@@ -93,15 +98,21 @@ function handleCheckboxChange() {
93
98
  <tr
94
99
  h-64px
95
100
  :class="computedRowClass"
96
- @mouseenter="emit('hover', rowIndex)"
97
- @mouseleave="emit('hover', null)"
98
101
  @click.stop="handleRowClick"
99
102
  >
100
103
  <td v-if="allowSelect" class="sticky left-0 bg-white z-9" :class="[selectClass]" @click.stop>
101
104
  <div
102
105
  flex="~" h-px items-center justify-center w-32px relative z-0
103
106
  >
107
+ <TelaSkeleton
108
+ v-if="isLoading(row, loading || false) && !row.id"
109
+ h-16px
110
+ rounded="2px"
111
+ w-16px
112
+ bg="#E9ECEF"
113
+ />
104
114
  <TelaCheckbox
115
+ v-else
105
116
  :model-value="isSelected || false"
106
117
  @update:model-value="handleCheckboxChange"
107
118
  />
@@ -148,7 +159,8 @@ function handleCheckboxChange() {
148
159
  :key="columnIdx"
149
160
  :style="column.style"
150
161
  >
151
- <div v-if="isLoading(row, loading || false)" flex="~ col" gap-4px pl-12px h-64px justify-center :class="rowClass">
162
+ <div v-if="isHorizontalSpacerColumn(column)" h-64px :class="rowClass" />
163
+ <div v-else-if="isLoading(row, loading || false)" flex="~ col" gap-4px pl-12px h-64px justify-center :class="rowClass">
152
164
  <TelaSkeleton w="70%" h-8px />
153
165
  </div>
154
166
  <template v-else>
@@ -1,8 +1,16 @@
1
1
  <script setup lang="ts">
2
- import type { Column, Row, ComplexTableType } from './types'
2
+ import type { Column, ComplexTableType } from './types'
3
3
  import { HoverMechanismTypes } from './types'
4
4
  import TelaTableHeader from './complex-table-header.vue'
5
5
  import TelaTableRow from './complex-table-row.vue'
6
+ import TelaComplexTableFallbackSkeleton from './complex-table-fallback-skeleton.vue'
7
+ import {
8
+ createHorizontalSpacerColumn,
9
+ getColumnVirtualWidth,
10
+ getHorizontalVirtualColumns,
11
+ HORIZONTAL_LEFT_SPACER_COLUMN_KEY,
12
+ HORIZONTAL_RIGHT_SPACER_COLUMN_KEY,
13
+ } from './composables/horizontal-virtual-columns'
6
14
  import { useTableSelection } from './composables/table-selection'
7
15
  import { useTableCommon } from './composables/table-common'
8
16
  import { useVirtualScroll } from './composables/virtual-scroll'
@@ -36,13 +44,11 @@ const {
36
44
  const selectedRows = computed(() => props.selectedRows ?? internalSelectedRows.value)
37
45
 
38
46
  const {
39
- hoveredRowIndex,
40
47
  hasHorizontalScroll,
41
48
  handleRowSelect,
42
49
  handleSelectAllRows,
43
50
  handleScroll,
44
51
  updateScrollButtonsState,
45
- handleRowHover,
46
52
  } = useTableCommon({
47
53
  props,
48
54
  emit,
@@ -54,8 +60,44 @@ const {
54
60
  })
55
61
 
56
62
  const hoverMechanism = computed(() => props.hoverMechanism || HoverMechanismTypes.Cell)
57
- const columnsWithHeaders: ComputedRef<Column[]> = computed(() => props.columns.filter((column: Column) => slots[`header-${column.key}`] !== undefined))
63
+ const horizontalScrollLeft = ref(0)
64
+ const horizontalViewportWidth = ref(0)
65
+ const defaultFixedColumnsWidth = computed(() => {
66
+ const selectWidth = props.allowSelect && props.rows.length ? 32 : 0
67
+ const rowIndexWidth = props.allowRowIndex ? 48 : 0
68
+ return selectWidth + rowIndexWidth
69
+ })
70
+ const fixedColumnsWidth = computed(() => props.virtualizedFixedColumnsWidth ?? defaultFixedColumnsWidth.value)
71
+ const horizontalVirtualColumns = computed(() => getHorizontalVirtualColumns({
72
+ columns: props.columns,
73
+ getColumnWidth: column => getColumnVirtualWidth(column, props.virtualizedDefaultColumnWidth ?? 200),
74
+ scrollLeft: horizontalScrollLeft.value,
75
+ viewportWidth: horizontalViewportWidth.value,
76
+ fixedWidth: fixedColumnsWidth.value,
77
+ overscanPx: props.virtualizedColumnOverscan ?? 480,
78
+ }))
79
+ const renderedColumns = computed<Column[]>(() => {
80
+ if (!props.virtualizedColumns) {
81
+ return props.columns
82
+ }
83
+
84
+ const result: Column[] = []
85
+
86
+ if (horizontalVirtualColumns.value.leftSpacerWidth > 0) {
87
+ result.push(createHorizontalSpacerColumn(HORIZONTAL_LEFT_SPACER_COLUMN_KEY, horizontalVirtualColumns.value.leftSpacerWidth))
88
+ }
89
+
90
+ result.push(...horizontalVirtualColumns.value.visibleColumns)
91
+
92
+ if (horizontalVirtualColumns.value.rightSpacerWidth > 0) {
93
+ result.push(createHorizontalSpacerColumn(HORIZONTAL_RIGHT_SPACER_COLUMN_KEY, horizontalVirtualColumns.value.rightSpacerWidth))
94
+ }
95
+
96
+ return result
97
+ })
98
+ const columnsWithHeaders: ComputedRef<Column[]> = computed(() => renderedColumns.value.filter((column: Column) => slots[`header-${column.key}`] !== undefined))
58
99
  const shouldHideScrollbar = computed(() => props.hideScrollbar || (!hasHorizontalScroll.value && !props.showVerticalScrollbar))
100
+ const rowHeight = computed(() => props.virtualizedRowHeight ?? 64)
59
101
 
60
102
  // Track if we're currently loading more data to prevent duplicate requests
61
103
  const isLoadingMore = ref(false)
@@ -65,35 +107,56 @@ const virtualScroll = props.useVirtualization !== false
65
107
  ? useVirtualScroll({
66
108
  count: computed(() => props.rows.length),
67
109
  getScrollElement: () => mainTableEl.value || null,
68
- estimateSize: props.virtualizedRowHeight ?? 64,
110
+ estimateSize: rowHeight.value,
69
111
  overscan: props.virtualizedOverscan ?? 5,
112
+ maxScrollItemsPerEvent: props.virtualizedMaxScrollRowsPerEvent,
70
113
  initialBatchSize: props.virtualizedInitialBatchSize ?? 30,
71
114
  })
72
115
  : null
73
116
 
117
+ const spacerColspan = computed(() => renderedColumns.value.length + (props.allowSelect ? 1 : 0) + (props.allowRowIndex ? 1 : 0) + 2)
118
+ const shouldShowVirtualFallbackBackground = computed(() => Boolean(virtualScroll && props.virtualizedShowBlankStateLoading && props.rows.length))
119
+ const isVirtualFallbackBackgroundVisible = computed(() => shouldShowVirtualFallbackBackground.value && Boolean(virtualScroll?.isScrollingFast.value))
120
+ const virtualTableContainerStyle = computed(() => ({
121
+ maxHeight: `${props.maxHeight || 462.5}px`,
122
+ }))
123
+ const visibleRange = computed(() => virtualScroll?.visibleRange.value)
124
+
125
+ function isRowVisible(index: number) {
126
+ const range = visibleRange.value
127
+ return !range || (index >= range.startIndex && index <= range.endIndex)
128
+ }
129
+
74
130
  const displayRows = computed(() => {
75
131
  if (!virtualScroll) {
76
- return props.rows.map((row, index) => ({ row, index }))
132
+ return props.rows.map((row, index) => ({ row, index, isVisible: true }))
77
133
  }
78
134
 
79
- return virtualScroll.virtualItems.value.map(virtualItem => ({
80
- row: props.rows[virtualItem.index] || {} as Row,
81
- index: virtualItem.index,
82
- }))
135
+ return virtualScroll.virtualItems.value.flatMap((virtualItem) => {
136
+ const row = props.rows[virtualItem.index]
137
+
138
+ return row
139
+ ? [{
140
+ row,
141
+ index: virtualItem.index,
142
+ isVisible: isRowVisible(virtualItem.index),
143
+ }]
144
+ : []
145
+ })
83
146
  })
84
147
 
85
148
  const topSpacerHeight = computed(() => {
86
149
  if (!virtualScroll)
87
150
  return 0
88
151
  const startIndex = virtualScroll.range.value.startIndex
89
- return startIndex * (props.virtualizedRowHeight ?? 64)
152
+ return startIndex * rowHeight.value
90
153
  })
91
154
 
92
155
  const bottomSpacerHeight = computed(() => {
93
156
  if (!virtualScroll)
94
157
  return 0
95
158
  const endIndex = virtualScroll.range.value.endIndex
96
- return (props.rows.length - endIndex - 1) * (props.virtualizedRowHeight ?? 64)
159
+ return (props.rows.length - endIndex - 1) * rowHeight.value
97
160
  })
98
161
 
99
162
  const shouldShowTopSpacer = computed(() => {
@@ -104,11 +167,41 @@ const shouldShowBottomSpacer = computed(() => {
104
167
  return virtualScroll && bottomSpacerHeight.value > 0
105
168
  })
106
169
 
107
- // Infinite scroll with virtualization support
170
+ function updateTableMeasurements() {
171
+ updateScrollButtonsState()
172
+ updateHorizontalScrollState(mainTableEl.value)
173
+ }
174
+
175
+ function updateHorizontalScrollState(el?: HTMLElement) {
176
+ if (!el)
177
+ return
178
+
179
+ if (horizontalScrollLeft.value !== el.scrollLeft) {
180
+ horizontalScrollLeft.value = el.scrollLeft
181
+ }
182
+
183
+ if (horizontalViewportWidth.value !== el.clientWidth) {
184
+ horizontalViewportWidth.value = el.clientWidth
185
+ }
186
+ }
187
+
188
+ function handleTableScroll(event: Event) {
189
+ updateHorizontalScrollState(event.target as HTMLElement)
190
+ handleScroll(event)
191
+ }
192
+
108
193
  let lastLoadMoreTriggeredAt = 0
194
+ // Infinite scroll with virtualization support
109
195
  watch(() => virtualScroll?.range.value, async (newRange) => {
110
- if (!newRange || !virtualScroll || isLoadingMore.value)
196
+ if (!newRange || !virtualScroll)
197
+ return
198
+
199
+ if (!mainTableEl.value?.clientHeight)
200
+ return
201
+
202
+ if (isLoadingMore.value)
111
203
  return
204
+
112
205
  // Check if we're near the end and haven't recently triggered a load
113
206
  const now = Date.now()
114
207
  if (newRange.endIndex >= props.rows.length - 10 && now - lastLoadMoreTriggeredAt > 500) {
@@ -127,7 +220,7 @@ watch(() => virtualScroll?.range.value, async (newRange) => {
127
220
  emit('error', error instanceof Error ? error : new Error(String(error)))
128
221
  }
129
222
  }
130
- }, { deep: true })
223
+ })
131
224
 
132
225
  // Fallback for non-virtualized mode
133
226
  if (!virtualScroll) {
@@ -142,20 +235,27 @@ if (!virtualScroll) {
142
235
  }
143
236
 
144
237
  onMounted(() => {
145
- updateScrollButtonsState()
146
- window.addEventListener('resize', updateScrollButtonsState)
238
+ updateTableMeasurements()
239
+ window.addEventListener('resize', updateTableMeasurements)
147
240
  virtualScroll?.init()
148
241
  })
149
242
 
150
243
  onUnmounted(() => {
151
- window.removeEventListener('resize', updateScrollButtonsState)
244
+ window.removeEventListener('resize', updateTableMeasurements)
152
245
  virtualScroll?.destroy()
153
246
  })
154
247
 
155
248
  watch(() => props.columns, () => {
156
- nextTick(updateScrollButtonsState)
249
+ nextTick(updateTableMeasurements)
157
250
  }, { deep: true })
158
251
 
252
+ useResizeObserver(mainTableEl, (entries) => {
253
+ const width = entries[0]?.contentRect.width
254
+ if (width && horizontalViewportWidth.value !== width) {
255
+ horizontalViewportWidth.value = width
256
+ }
257
+ })
258
+
159
259
  const hasRowIndex = computed(() => props.allowRowIndex && props.rows.length)
160
260
  const hasSelect = computed(() => props.allowSelect && props.rows.length)
161
261
 
@@ -180,6 +280,7 @@ watch([hasRowIndex, hasSelect, mainTableEl], () => {
180
280
 
181
281
  <template>
182
282
  <div
283
+ class="relative"
183
284
  rounded-12px flex="~"
184
285
  b="0.5px gray-300"
185
286
  overflow-hidden
@@ -188,18 +289,28 @@ watch([hasRowIndex, hasSelect, mainTableEl], () => {
188
289
  boxShadow: '0px 2px 5px 0px #677F940D, 0px 3px 16px 0px #677F940A',
189
290
  }"
190
291
  >
292
+ <TelaComplexTableFallbackSkeleton
293
+ v-if="shouldShowVirtualFallbackBackground"
294
+ v-show="isVirtualFallbackBackgroundVisible"
295
+ class="virtual-scroll-fallback-skeleton"
296
+ :row-height="rowHeight"
297
+ height="100%"
298
+ />
191
299
  <div
192
300
  ref="mainTableEl"
193
301
  w-full
194
302
  class="main-table-container overflow-auto"
195
303
  relative
196
- :class="{ 'hide-scrollbar': shouldHideScrollbar, 'show-vertical-scrollbar': props.showVerticalScrollbar }"
197
- :style="{ maxHeight: `${props.maxHeight || 462.5}px` }"
198
- @scroll="handleScroll"
304
+ :class="{
305
+ 'hide-scrollbar': shouldHideScrollbar,
306
+ 'show-vertical-scrollbar': props.showVerticalScrollbar,
307
+ }"
308
+ :style="virtualTableContainerStyle"
309
+ @scroll.passive="handleTableScroll"
199
310
  >
200
311
  <table z-2 class="relative w-full min-w-full">
201
312
  <TelaTableHeader
202
- :columns="columns"
313
+ :columns="renderedColumns"
203
314
  :rows="rows"
204
315
  :selected-rows="selectedRows"
205
316
  :allow-select="allowSelect"
@@ -223,15 +334,15 @@ watch([hasRowIndex, hasSelect, mainTableEl], () => {
223
334
 
224
335
  <tbody v-if="rows.length">
225
336
  <tr v-if="shouldShowTopSpacer" :style="{ height: `${topSpacerHeight}px` }">
226
- <td :colspan="columns.length + (allowSelect ? 1 : 0) + (allowRowIndex ? 1 : 0) + 2" />
337
+ <td :colspan="spacerColspan" />
227
338
  </tr>
228
339
 
229
340
  <TelaTableRow
230
- v-for="{ row: rowData, index: idx } in displayRows"
341
+ v-for="{ row: rowData, index: idx, isVisible } in displayRows"
231
342
  :key="rowData.id || idx"
232
343
  :row="rowData"
233
344
  :row-index="idx"
234
- :columns="columns"
345
+ :columns="renderedColumns"
235
346
  :allow-select="allowSelect"
236
347
  :allow-row-index="allowRowIndex"
237
348
  :allow-row-action="$slots['row-action'] !== undefined"
@@ -239,7 +350,6 @@ watch([hasRowIndex, hasSelect, mainTableEl], () => {
239
350
  :loading="props.loading"
240
351
  :disable-row-click="props.disableRowClick?.(rowData)"
241
352
  :hover-mechanism="hoverMechanism"
242
- :hovered-row-index="hoveredRowIndex"
243
353
  :row-class="props.rowClass"
244
354
  :row-index-class="props.rowIndexClass"
245
355
  :row-title-path="props.rowTitlePath"
@@ -249,15 +359,16 @@ watch([hasRowIndex, hasSelect, mainTableEl], () => {
249
359
  :is-last-row="idx === rows.length - 1"
250
360
  :hide-error="props.hideError"
251
361
  :total-rows="rows.length"
362
+ :show-border="isVisible"
363
+ :border-mode="virtualScroll ? 'visible' : 'default'"
252
364
  @select="handleRowSelect"
253
365
  @open="(id: string) => emit('open', id)"
254
- @hover="handleRowHover"
255
366
  >
256
367
  <template #leading="slotProps">
257
368
  <slot name="leading" v-bind="slotProps" />
258
369
  </template>
259
370
 
260
- <template v-for="column in columns" :key="column.key" #[column.key!]="slotProps">
371
+ <template v-for="column in renderedColumns" :key="column.key" #[column.key!]="slotProps">
261
372
  <slot v-if="column.key" :name="column.key" v-bind="slotProps" />
262
373
  </template>
263
374
 
@@ -267,11 +378,11 @@ watch([hasRowIndex, hasSelect, mainTableEl], () => {
267
378
  </TelaTableRow>
268
379
 
269
380
  <tr v-if="shouldShowBottomSpacer" :style="{ height: `${bottomSpacerHeight}px` }">
270
- <td :colspan="columns.length + (allowSelect ? 1 : 0) + (allowRowIndex ? 1 : 0) + 2" />
381
+ <td :colspan="spacerColspan" />
271
382
  </tr>
272
383
 
273
384
  <tr v-if="$slots.footer && !virtualScroll" b="b-0.5px gray-200">
274
- <td :colspan="columns.length + 2">
385
+ <td :colspan="renderedColumns.length + 2">
275
386
  <div flex items-center h-64px b="t-0.5px gray-200" :class="props.rowClass">
276
387
  <slot name="footer" />
277
388
  </div>
@@ -319,4 +430,11 @@ thead th[style*="left"] {
319
430
  thead th.bg-white {
320
431
  background-color: white !important;
321
432
  }
433
+
434
+ .virtual-scroll-fallback-skeleton {
435
+ position: absolute;
436
+ inset: 0;
437
+ z-index: 0;
438
+ pointer-events: none;
439
+ }
322
440
  </style>
@@ -361,6 +361,7 @@ const handleError = (error) => {
361
361
  interface Column {
362
362
  key?: string
363
363
  title: string
364
+ width?: number
364
365
  isDefault?: boolean
365
366
  icon?: {
366
367
  name: string
@@ -375,6 +376,7 @@ interface Column {
375
376
  interface Row {
376
377
  id?: string
377
378
  index?: number
379
+ renderKey?: string | number
378
380
  status: string
379
381
  name: string
380
382
  errorMessage?: string
@@ -414,6 +416,12 @@ type ComplexTableProps = {
414
416
  virtualizedOverscan?: number
415
417
  virtualizedRowHeight?: number
416
418
  virtualizedInitialBatchSize?: number
419
+ virtualizedMaxScrollRowsPerEvent?: number
420
+ virtualizedShowBlankStateLoading?: boolean
421
+ virtualizedColumns?: boolean
422
+ virtualizedColumnOverscan?: number
423
+ virtualizedFixedColumnsWidth?: number
424
+ virtualizedDefaultColumnWidth?: number
417
425
  }
418
426
  ```
419
427
 
@@ -436,6 +444,7 @@ The ComplexTable system consists of these sub-components:
436
444
  - `TelaComplexTableRow` - Individual row component
437
445
  - `TelaComplexTableCell` - Default cell component
438
446
  - `TelaComplexTableHeaderCell` - Header cell component
447
+ - `TelaComplexTableFallbackSkeleton` - Table-shaped skeleton fallback for virtual blank states and loading empty states
439
448
 
440
449
  ## Features
441
450
 
@@ -446,6 +455,7 @@ The ComplexTable system consists of these sub-components:
446
455
  - **Status Indicators**: Built-in support for loading, success, and error states
447
456
  - **Custom Rendering**: Slot-based customization for headers, cells, and rows
448
457
  - **Virtualization**: Automatic virtualization for large datasets with configurable options
458
+ - **Column Virtualization**: Optional horizontal virtualization for tables with many columns
449
459
  - **Infinite Scroll**: Built-in infinite scroll support for lazy loading
450
460
  - **Error Handling**: Display error messages for failed rows
451
461
  - **Empty States**: Customizable empty state when no data is available
@@ -479,6 +489,12 @@ When `useVirtualization` is enabled, the table automatically switches to a virtu
479
489
  - `virtualizedRowHeight` - Height of each row in pixels (default: 64)
480
490
  - `virtualizedOverscan` - Number of rows to render outside the visible area (default: 5)
481
491
  - `virtualizedInitialBatchSize` - Initial number of rows to render (default: 30)
492
+ - `virtualizedMaxScrollRowsPerEvent` - Maximum estimated rows a single scroll event can move before the offset is clamped (default: unlimited)
493
+ - `virtualizedShowBlankStateLoading` - Shows a table-shaped skeleton background in blank virtualized areas while rows are being repainted during fast scrolling (default: false)
494
+ - `virtualizedColumns` - Enables horizontal column virtualization for the `columns` array (default: false)
495
+ - `virtualizedColumnOverscan` - Horizontal overscan in pixels for keeping nearby columns mounted (default: 480)
496
+ - `virtualizedFixedColumnsWidth` - Total width before the virtualized columns start, including select/index/leading columns when those areas should remain mounted (default: select + index width)
497
+ - `virtualizedDefaultColumnWidth` - Width used when a column does not provide `width` or a pixel `style` width (default: 200)
482
498
 
483
499
  ## Accessibility
484
500
 
@@ -487,4 +503,3 @@ When `useVirtualization` is enabled, the table automatically switches to a virtu
487
503
  - ARIA attributes for screen readers
488
504
  - Focus management
489
505
  - Proper table headers and relationships
490
-
@@ -150,15 +150,11 @@ export const WithRowHover: Story = {
150
150
  render: args => ({
151
151
  components: { TelaComplexTable },
152
152
  setup() {
153
- return {
154
- args,
155
- onHover: (rowData: any) => console.log('Hovering row:', rowData),
156
- }
153
+ return { args }
157
154
  },
158
155
  template: `
159
156
  <TelaComplexTable
160
157
  v-bind="args"
161
- @hover="onHover"
162
158
  />
163
159
  `,
164
160
  }),