@asteby/metacore-runtime-react 38.1.0 → 39.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # @asteby/metacore-runtime-react
2
2
 
3
+ ## 39.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 6549688: DynamicTable virtualizes desktop rows and mobile cards once the visible row model reaches 40+ rows (override with `virtualizeRows`). Cuts DOM cost for infinite scroll and large page sizes.
8
+
9
+ ## 39.0.0
10
+
11
+ ### Patch Changes
12
+
13
+ - Updated dependencies [5b64c23]
14
+ - @asteby/metacore-sdk@3.8.0
15
+
3
16
  ## 38.1.0
4
17
 
5
18
  ### Minor Changes
@@ -83,6 +83,13 @@ export interface DynamicTableProps {
83
83
  * mode exactly as before.
84
84
  */
85
85
  infiniteScroll?: boolean;
86
+ /**
87
+ * Row virtualization. When the visible row model has at least N rows,
88
+ * only viewport rows are mounted (helps infinite scroll and large page
89
+ * sizes). `true` / omitted → default threshold (40). `false` → always
90
+ * render every row. A positive number overrides the threshold.
91
+ */
92
+ virtualizeRows?: boolean | number;
86
93
  }
87
- export declare function DynamicTable({ model, endpoint, mutationEndpoint, enableUrlSync, hideImport, hideExport, hiddenColumns, allowedActionKeys, onAction, onRowClick, refreshTrigger, defaultFilters, extraColumns, getDynamicColumns, timeZone, currency, pagination: paginationMode, infiniteScroll: infiniteScrollProp, }: DynamicTableProps): import("react").JSX.Element;
94
+ export declare function DynamicTable({ model, endpoint, mutationEndpoint, enableUrlSync, hideImport, hideExport, hiddenColumns, allowedActionKeys, onAction, onRowClick, refreshTrigger, defaultFilters, extraColumns, getDynamicColumns, timeZone, currency, pagination: paginationMode, infiniteScroll: infiniteScrollProp, virtualizeRows, }: DynamicTableProps): import("react").JSX.Element;
88
95
  //# sourceMappingURL=dynamic-table.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"dynamic-table.d.ts","sourceRoot":"","sources":["../src/dynamic-table.tsx"],"names":[],"mappings":"AAgBA,OAAO,EAKH,KAAK,SAAS,EAajB,MAAM,uBAAuB,CAAA;AAgC9B,OAAO,KAAK,EAAsB,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAkGnF,MAAM,WAAW,iBAAiB;IAC9B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,6DAA6D;IAC7D,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB;;;;;;;;;OASG;IACH,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC5B,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,IAAI,CAAA;IAC7C;;;;;OAKG;IACH,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,CAAA;IAC/B,cAAc,CAAC,EAAE,GAAG,CAAA;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACpC,YAAY,CAAC,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE,CAAA;IAC/B;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,iBAAiB,CAAA;IACrC;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;;;;;;;;;;OAaG;IACH,UAAU,CAAC,EAAE,OAAO,GAAG,UAAU,CAAA;IACjC;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;CAC3B;AAED,wBAAgB,YAAY,CAAC,EACzB,KAAK,EACL,QAAQ,EACR,gBAAgB,EAChB,aAAoB,EACpB,UAAU,EACV,UAAU,EACV,aAAkB,EAClB,iBAAiB,EACjB,QAAQ,EACR,UAAU,EACV,cAAc,EACd,cAAc,EACd,YAAiB,EACjB,iBAA4C,EAC5C,QAAQ,EACR,QAAQ,EACR,UAAU,EAAE,cAAc,EAC1B,cAAc,EAAE,kBAA0B,GAC7C,EAAE,iBAAiB,+BA62CnB"}
1
+ {"version":3,"file":"dynamic-table.d.ts","sourceRoot":"","sources":["../src/dynamic-table.tsx"],"names":[],"mappings":"AAiBA,OAAO,EAKH,KAAK,SAAS,EAajB,MAAM,uBAAuB,CAAA;AAgC9B,OAAO,KAAK,EAAsB,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAuGnF,MAAM,WAAW,iBAAiB;IAC9B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,6DAA6D;IAC7D,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB;;;;;;;;;OASG;IACH,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC5B,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,IAAI,CAAA;IAC7C;;;;;OAKG;IACH,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,CAAA;IAC/B,cAAc,CAAC,EAAE,GAAG,CAAA;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACpC,YAAY,CAAC,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE,CAAA;IAC/B;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,iBAAiB,CAAA;IACrC;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;;;;;;;;;;OAaG;IACH,UAAU,CAAC,EAAE,OAAO,GAAG,UAAU,CAAA;IACjC;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,OAAO,GAAG,MAAM,CAAA;CACpC;AAED,wBAAgB,YAAY,CAAC,EACzB,KAAK,EACL,QAAQ,EACR,gBAAgB,EAChB,aAAoB,EACpB,UAAU,EACV,UAAU,EACV,aAAkB,EAClB,iBAAiB,EACjB,QAAQ,EACR,UAAU,EACV,cAAc,EACd,cAAc,EACd,YAAiB,EACjB,iBAA4C,EAC5C,QAAQ,EACR,QAAQ,EACR,UAAU,EAAE,cAAc,EAC1B,cAAc,EAAE,kBAA0B,EAC1C,cAAc,GACjB,EAAE,iBAAiB,+BAy/CnB"}
@@ -14,6 +14,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
14
14
  import { useEffect, useState, useMemo, useCallback, useRef } from 'react';
15
15
  import { useTranslation } from 'react-i18next';
16
16
  import { format } from 'date-fns';
17
+ import { useVirtualizer } from '@tanstack/react-virtual';
17
18
  import { flexRender, getCoreRowModel, getFacetedRowModel, getFacetedUniqueValues, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, useReactTable, } from '@tanstack/react-table';
18
19
  import { cn } from '@asteby/metacore-ui/lib';
19
20
  import { Table, TableBody, TableCell, TableFooter, TableHead, TableHeader, TableRow, Button, Skeleton, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from '@asteby/metacore-ui/primitives';
@@ -27,6 +28,7 @@ import { defaultGetDynamicColumns, DATE_CELL_TYPES, aggregateOf, formatAggregate
27
28
  import { useFacetLoaders, isLongTextColumn } from './use-facet-loaders';
28
29
  import { translateOptionLabels } from './filter-chips';
29
30
  import { dedupeById, useInfiniteScrollSentinel } from './use-infinite-scroll';
31
+ import { DYNAMIC_TABLE_CARD_ESTIMATE_PX, DYNAMIC_TABLE_ROW_ESTIMATE_PX, resolveVirtualizeThreshold, } from './table-virtualization';
30
32
  import { OptionsContext } from './options-context';
31
33
  import { getSearchableColumnKeys } from './column-visibility';
32
34
  import { useDebouncedValue } from './use-debounced-value';
@@ -105,7 +107,7 @@ function writeStoredPageSize(model, size) {
105
107
  // quota / private mode — persistence is a nicety, never fatal
106
108
  }
107
109
  }
108
- export function DynamicTable({ model, endpoint, mutationEndpoint, enableUrlSync = true, hideImport, hideExport, hiddenColumns = [], allowedActionKeys, onAction, onRowClick, refreshTrigger, defaultFilters, extraColumns = [], getDynamicColumns = defaultGetDynamicColumns, timeZone, currency, pagination: paginationMode, infiniteScroll: infiniteScrollProp = false, }) {
110
+ export function DynamicTable({ model, endpoint, mutationEndpoint, enableUrlSync = true, hideImport, hideExport, hiddenColumns = [], allowedActionKeys, onAction, onRowClick, refreshTrigger, defaultFilters, extraColumns = [], getDynamicColumns = defaultGetDynamicColumns, timeZone, currency, pagination: paginationMode, infiniteScroll: infiniteScrollProp = false, virtualizeRows, }) {
109
111
  // The explicit `pagination` prop wins; the legacy `infiniteScroll` boolean
110
112
  // still selects the mode when `pagination` is absent (back-compat).
111
113
  const infiniteScroll = paginationMode ? paginationMode === 'infinite' : infiniteScrollProp;
@@ -1171,6 +1173,24 @@ export function DynamicTable({ model, endpoint, mutationEndpoint, enableUrlSync
1171
1173
  getFacetedRowModel: getFacetedRowModel(),
1172
1174
  getFacetedUniqueValues: getFacetedUniqueValues(),
1173
1175
  });
1176
+ const tableRows = table.getRowModel().rows;
1177
+ const virtualizeThreshold = resolveVirtualizeThreshold(virtualizeRows);
1178
+ const shouldVirtualize = virtualizeThreshold !== false && tableRows.length >= virtualizeThreshold;
1179
+ const colSpan = Math.max(columns.length, 1);
1180
+ const desktopVirtualizer = useVirtualizer({
1181
+ count: shouldVirtualize ? tableRows.length : 0,
1182
+ getScrollElement: () => infDesktopRoot.current,
1183
+ estimateSize: () => DYNAMIC_TABLE_ROW_ESTIMATE_PX,
1184
+ overscan: 8,
1185
+ enabled: shouldVirtualize,
1186
+ });
1187
+ const mobileVirtualizer = useVirtualizer({
1188
+ count: shouldVirtualize ? tableRows.length : 0,
1189
+ getScrollElement: () => infMobileRoot.current,
1190
+ estimateSize: () => DYNAMIC_TABLE_CARD_ESTIMATE_PX,
1191
+ overscan: 6,
1192
+ enabled: shouldVirtualize,
1193
+ });
1174
1194
  const TableSkeleton = () => (_jsx(_Fragment, { children: Array.from({ length: 5 }).map((_, i) => (_jsxs(TableRow, { className: "hover:bg-transparent", children: [_jsx(TableCell, { className: "py-3 w-10", children: _jsx(Skeleton, { className: "h-4 w-4" }) }), _jsx(TableCell, { className: "py-3", children: _jsx(Skeleton, { className: "h-4 w-[70%]" }) }), _jsx(TableCell, { className: "py-3", children: _jsx(Skeleton, { className: "h-4 w-[50%]" }) }), _jsx(TableCell, { className: "py-3", children: _jsx(Skeleton, { className: "h-4 w-[60%]" }) }), _jsx(TableCell, { className: "py-3 w-16", children: _jsx(Skeleton, { className: "h-6 w-6" }) })] }, `skeleton-${i}`))) }));
1175
1195
  if (loading) {
1176
1196
  return (_jsxs("div", { className: 'flex flex-col h-full min-h-0 w-full', children: [_jsx("div", { className: 'pb-4 shrink-0', children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsx(Skeleton, { className: "h-9 w-[280px]" }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Skeleton, { className: "h-9 w-9" }), _jsx(Skeleton, { className: "h-9 w-[70px]" })] })] }) }), _jsx("div", { className: 'flex-1 min-h-0 overflow-auto border rounded-md bg-card', children: _jsxs(Table, { noWrapper: true, className: "min-w-max w-full", children: [_jsx(TableHeader, { className: 'sticky top-0 z-10', children: _jsxs(TableRow, { className: 'border-b-0 hover:bg-transparent', children: [_jsx(TableHead, { className: 'bg-card border-b h-10 w-10', children: _jsx(Skeleton, { className: "h-4 w-4" }) }), _jsx(TableHead, { className: 'bg-card border-b h-10', children: _jsx(Skeleton, { className: "h-4 w-16" }) }), _jsx(TableHead, { className: 'bg-card border-b h-10', children: _jsx(Skeleton, { className: "h-4 w-14" }) }), _jsx(TableHead, { className: 'bg-card border-b h-10', children: _jsx(Skeleton, { className: "h-4 w-20" }) }), _jsx(TableHead, { className: 'bg-card border-b h-10 w-16', children: _jsx(Skeleton, { className: "h-4 w-12" }) })] }) }), _jsx(TableBody, { children: _jsx(TableSkeleton, {}) })] }) })] }));
@@ -1181,11 +1201,31 @@ export function DynamicTable({ model, endpoint, mutationEndpoint, enableUrlSync
1181
1201
  return (_jsxs(OptionsContext.Provider, { value: { optionsMap }, children: [_jsxs("div", { className: 'flex flex-col h-full min-h-0 w-full', children: [_jsx("div", { className: 'pb-4 shrink-0', children: _jsx(DataTableToolbar, { table: table, searchPlaceholder: metadata.searchPlaceholder || 'Buscar...', filters: filters, activeFilters: dynamicFilters, onDynamicFilterChange: handleDynamicFilterChange, dateFilter: { value: dateRange, onChange: setDateRange, placeholder: 'Filtrar por fecha' }, perPageOptions: metadata.perPageOptions, onRefresh: handleRefresh, isLoading: loadingData, selectedCount: Object.keys(rowSelection).length, onBulkDelete: () => setShowBulkDeleteConfirm(true), extraActions: _jsxs(_Fragment, { children: [exportEnabled && (_jsxs(Button, { variant: "outline", size: "sm", className: "h-8", onClick: () => setExportOpen(true), children: [_jsx(Download, { className: "h-4 w-4 mr-1" }), " Exportar"] })), importEnabled && (_jsxs(Button, { variant: "outline", size: "sm", className: "h-8", onClick: () => setImportOpen(true), children: [_jsx(Upload, { className: "h-4 w-4 mr-1" }), " Importar"] }))] }) }) }), _jsxs("div", { ref: infDesktopRoot, className: 'hidden sm:block flex-1 min-h-0 overflow-auto border rounded-md bg-card', children: [_jsxs(Table, { noWrapper: true, className: cn('min-w-max w-full', aggregateColumns.length > 0 && Object.keys(footerTotals).length > 0 && 'h-full'), children: [_jsx(TableHeader, { className: 'sticky top-0 z-10', children: table.getHeaderGroups().map((headerGroup) => (_jsx(TableRow, { className: 'border-b-0 hover:bg-transparent', children: headerGroup.headers.map((header) => {
1182
1202
  const isActionsColumn = header.id === 'actions';
1183
1203
  return (_jsx(TableHead, { colSpan: header.colSpan, style: header.column.columnDef.size ? { width: header.column.columnDef.size } : undefined, className: cn('bg-card border-b h-10', isActionsColumn && 'sticky right-0 z-20 bg-card shadow-[-2px_0_5px_-2px_rgba(0,0,0,0.1)]'), children: header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext()) }, header.id));
1184
- }) }, headerGroup.id))) }), _jsx(TableBody, { children: loadingData && data.length === 0 ? (_jsx(TableSkeleton, {})) : table.getRowModel().rows?.length ? (_jsxs(_Fragment, { children: [table.getRowModel().rows.map((row) => (_jsx(TableRow, { "data-state": row.getIsSelected() && 'selected', className: cn(onRowClick && 'cursor-pointer'), onClick: onRowClick ? () => onRowClick(row.original) : undefined, children: row.getVisibleCells().map((cell) => {
1204
+ }) }, headerGroup.id))) }), _jsx(TableBody, { children: loadingData && data.length === 0 ? (_jsx(TableSkeleton, {})) : tableRows.length ? (_jsxs(_Fragment, { children: [shouldVirtualize ? (_jsxs(_Fragment, { children: [desktopVirtualizer.getVirtualItems().length > 0 && (_jsx(TableRow, { className: 'border-0 hover:bg-transparent', "aria-hidden": true, children: _jsx(TableCell, { colSpan: colSpan, className: 'p-0', style: {
1205
+ height: desktopVirtualizer.getVirtualItems()[0]?.start ?? 0,
1206
+ } }) })), desktopVirtualizer.getVirtualItems().map((virtualRow) => {
1207
+ const row = tableRows[virtualRow.index];
1208
+ if (!row)
1209
+ return null;
1210
+ return (_jsx(TableRow, { "data-index": virtualRow.index, ref: desktopVirtualizer.measureElement, "data-state": row.getIsSelected() && 'selected', className: cn(onRowClick && 'cursor-pointer'), onClick: onRowClick ? () => onRowClick(row.original) : undefined, children: row.getVisibleCells().map((cell) => {
1211
+ const isActionsColumn = cell.column.id === 'actions';
1212
+ const isSelectColumn = cell.column.id === 'select';
1213
+ return (_jsx(TableCell, { style: cell.column.columnDef.size ? { width: cell.column.columnDef.size } : undefined, className: cn('py-2', isActionsColumn && 'sticky right-0 bg-card shadow-[-2px_0_5px_-2px_rgba(0,0,0,0.1)]'), onClick: (isActionsColumn || isSelectColumn) ? (e) => e.stopPropagation() : undefined, children: flexRender(cell.column.columnDef.cell, cell.getContext()) }, cell.id));
1214
+ }) }, row.id));
1215
+ }), (() => {
1216
+ const items = desktopVirtualizer.getVirtualItems();
1217
+ const last = items[items.length - 1];
1218
+ const pad = last != null
1219
+ ? desktopVirtualizer.getTotalSize() - last.end
1220
+ : 0;
1221
+ if (pad <= 0)
1222
+ return null;
1223
+ return (_jsx(TableRow, { className: 'border-0 hover:bg-transparent', "aria-hidden": true, children: _jsx(TableCell, { colSpan: colSpan, className: 'p-0', style: { height: pad } }) }));
1224
+ })()] })) : (tableRows.map((row) => (_jsx(TableRow, { "data-state": row.getIsSelected() && 'selected', className: cn(onRowClick && 'cursor-pointer'), onClick: onRowClick ? () => onRowClick(row.original) : undefined, children: row.getVisibleCells().map((cell) => {
1185
1225
  const isActionsColumn = cell.column.id === 'actions';
1186
1226
  const isSelectColumn = cell.column.id === 'select';
1187
1227
  return (_jsx(TableCell, { style: cell.column.columnDef.size ? { width: cell.column.columnDef.size } : undefined, className: cn('py-2', isActionsColumn && 'sticky right-0 bg-card shadow-[-2px_0_5px_-2px_rgba(0,0,0,0.1)]'), onClick: (isActionsColumn || isSelectColumn) ? (e) => e.stopPropagation() : undefined, children: flexRender(cell.column.columnDef.cell, cell.getContext()) }, cell.id));
1188
- }) }, row.id))), aggregateColumns.length > 0 && Object.keys(footerTotals).length > 0 && (_jsx(TableRow, { className: 'border-0 hover:bg-transparent', children: _jsx(TableCell, { colSpan: columns.length, className: 'h-full p-0' }) }))] })) : (_jsx(TableRow, { className: 'border-b-0 hover:bg-transparent', children: _jsx(TableCell, { colSpan: columns.length, className: 'h-full p-0', children: _jsxs("div", { className: "flex h-full py-12 flex-col items-center justify-center gap-2 text-muted-foreground", children: [_jsx("div", { className: "flex h-20 w-20 items-center justify-center rounded-full bg-muted/50", children: _jsx(Inbox, { className: "h-10 w-10" }) }), _jsxs("div", { className: "flex flex-col items-center gap-1", children: [_jsx("h3", { className: "text-lg font-semibold text-foreground", children: "No se encontraron resultados" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "No hay datos para mostrar en este momento." })] })] }) }) })) }), aggregateColumns.length > 0 && Object.keys(footerTotals).length > 0 && (_jsx(TableFooter, { className: "bg-transparent", children: _jsx(TableRow, { className: "hover:bg-transparent", children: table.getVisibleLeafColumns().map((leaf, idx) => {
1228
+ }) }, row.id)))), aggregateColumns.length > 0 && Object.keys(footerTotals).length > 0 && (_jsx(TableRow, { className: 'border-0 hover:bg-transparent', children: _jsx(TableCell, { colSpan: columns.length, className: 'h-full p-0' }) }))] })) : (_jsx(TableRow, { className: 'border-b-0 hover:bg-transparent', children: _jsx(TableCell, { colSpan: columns.length, className: 'h-full p-0', children: _jsxs("div", { className: "flex h-full py-12 flex-col items-center justify-center gap-2 text-muted-foreground", children: [_jsx("div", { className: "flex h-20 w-20 items-center justify-center rounded-full bg-muted/50", children: _jsx(Inbox, { className: "h-10 w-10" }) }), _jsxs("div", { className: "flex flex-col items-center gap-1", children: [_jsx("h3", { className: "text-lg font-semibold text-foreground", children: "No se encontraron resultados" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "No hay datos para mostrar en este momento." })] })] }) }) })) }), aggregateColumns.length > 0 && Object.keys(footerTotals).length > 0 && (_jsx(TableFooter, { className: "bg-transparent", children: _jsx(TableRow, { className: "hover:bg-transparent", children: table.getVisibleLeafColumns().map((leaf, idx) => {
1189
1229
  const col = (metadata?.columns ?? []).find((c) => c.key === leaf.id);
1190
1230
  const isFirst = idx === 0;
1191
1231
  const stickyBase = 'sticky bottom-0 z-10 border-t bg-background py-2 font-semibold';
@@ -1195,7 +1235,21 @@ export function DynamicTable({ model, endpoint, mutationEndpoint, enableUrlSync
1195
1235
  }
1196
1236
  // First non-aggregate column carries the "Total" label.
1197
1237
  return (_jsx(TableCell, { className: stickyBase, children: isFirst ? t('common.total', 'Total') : '' }, leaf.id));
1198
- }) }) }))] }), infiniteScroll && (_jsxs(_Fragment, { children: [loadingMore && (_jsx("div", { className: 'p-2', children: _jsx(Skeleton, { className: 'h-8 w-full', "data-testid": 'table-loading-more' }) })), _jsx("div", { ref: infDesktopSentinel, className: 'h-1 w-full', "aria-hidden": true })] }))] }), _jsxs("div", { ref: infMobileRoot, className: 'flex flex-1 min-h-0 flex-col gap-2 overflow-y-auto sm:hidden', children: [loadingData && data.length === 0 ? (Array.from({ length: 5 }).map((_, i) => (_jsxs("div", { className: 'rounded-lg border bg-card p-3', children: [_jsx(Skeleton, { className: 'h-4 w-24' }), _jsx(Skeleton, { className: 'mt-2 h-4 w-40' }), _jsx(Skeleton, { className: 'mt-2 h-4 w-32' })] }, i)))) : table.getRowModel().rows?.length ? (table.getRowModel().rows.map((row) => {
1238
+ }) }) }))] }), infiniteScroll && (_jsxs(_Fragment, { children: [loadingMore && (_jsx("div", { className: 'p-2', children: _jsx(Skeleton, { className: 'h-8 w-full', "data-testid": 'table-loading-more' }) })), _jsx("div", { ref: infDesktopSentinel, className: 'h-1 w-full', "aria-hidden": true })] }))] }), _jsxs("div", { ref: infMobileRoot, className: 'flex flex-1 min-h-0 flex-col gap-2 overflow-y-auto sm:hidden', children: [loadingData && data.length === 0 ? (Array.from({ length: 5 }).map((_, i) => (_jsxs("div", { className: 'rounded-lg border bg-card p-3', children: [_jsx(Skeleton, { className: 'h-4 w-24' }), _jsx(Skeleton, { className: 'mt-2 h-4 w-40' }), _jsx(Skeleton, { className: 'mt-2 h-4 w-32' })] }, i)))) : tableRows.length ? (shouldVirtualize ? (_jsx("div", { className: 'relative w-full', style: { height: mobileVirtualizer.getTotalSize() }, children: mobileVirtualizer.getVirtualItems().map((virtualRow) => {
1239
+ const row = tableRows[virtualRow.index];
1240
+ if (!row)
1241
+ return null;
1242
+ const cells = row.getVisibleCells();
1243
+ const actionsCell = cells.find((c) => c.column.id === 'actions');
1244
+ const dataCells = cells.filter((c) => c.column.id !== 'actions' && c.column.id !== 'select');
1245
+ return (_jsxs("div", { "data-index": virtualRow.index, ref: mobileVirtualizer.measureElement, "data-state": row.getIsSelected() && 'selected', className: cn('absolute left-0 right-0 flex flex-col gap-1.5 rounded-lg border bg-card p-3 data-[state=selected]:border-primary/40', onRowClick && 'cursor-pointer'), style: { transform: `translateY(${virtualRow.start}px)` }, onClick: onRowClick ? () => onRowClick(row.original) : undefined, children: [dataCells.map((cell) => {
1246
+ const cellMeta = cell.column.columnDef.meta;
1247
+ const header = cell.column.columnDef.header;
1248
+ const label = cellMeta?.label ??
1249
+ (typeof header === 'string' ? header : cell.column.id);
1250
+ return (_jsxs("div", { className: 'flex items-start justify-between gap-3 text-sm', children: [_jsx("span", { className: 'shrink-0 text-muted-foreground', children: label }), _jsx("span", { className: 'min-w-0 overflow-hidden break-words text-right font-medium [&_[data-slot=badge]]:max-w-full [&_[data-slot=badge]]:whitespace-normal [&_[data-slot=badge]]:text-start', children: flexRender(cell.column.columnDef.cell, cell.getContext()) })] }, cell.id));
1251
+ }), actionsCell && (_jsx("div", { className: 'flex justify-end border-t pt-2', onClick: onRowClick ? (e) => e.stopPropagation() : undefined, children: flexRender(actionsCell.column.columnDef.cell, actionsCell.getContext()) }))] }, row.id));
1252
+ }) })) : (tableRows.map((row) => {
1199
1253
  const cells = row.getVisibleCells();
1200
1254
  const actionsCell = cells.find((c) => c.column.id === 'actions');
1201
1255
  const dataCells = cells.filter((c) => c.column.id !== 'actions' && c.column.id !== 'select');
@@ -1211,7 +1265,7 @@ export function DynamicTable({ model, endpoint, mutationEndpoint, enableUrlSync
1211
1265
  (typeof header === 'string' ? header : cell.column.id);
1212
1266
  return (_jsxs("div", { className: 'flex items-start justify-between gap-3 text-sm', children: [_jsx("span", { className: 'shrink-0 text-muted-foreground', children: label }), _jsx("span", { className: 'min-w-0 overflow-hidden break-words text-right font-medium [&_[data-slot=badge]]:max-w-full [&_[data-slot=badge]]:whitespace-normal [&_[data-slot=badge]]:text-start', children: flexRender(cell.column.columnDef.cell, cell.getContext()) })] }, cell.id));
1213
1267
  }), actionsCell && (_jsx("div", { className: 'flex justify-end border-t pt-2', onClick: onRowClick ? (e) => e.stopPropagation() : undefined, children: flexRender(actionsCell.column.columnDef.cell, actionsCell.getContext()) }))] }, row.id));
1214
- })) : (_jsxs("div", { className: 'flex flex-col items-center justify-center gap-2 rounded-lg border bg-card py-12 text-muted-foreground', children: [_jsx("div", { className: 'flex h-16 w-16 items-center justify-center rounded-full bg-muted/50', children: _jsx(Inbox, { className: 'h-8 w-8' }) }), _jsx("h3", { className: 'text-base font-semibold text-foreground', children: "No se encontraron resultados" }), _jsx("p", { className: 'text-sm text-muted-foreground', children: "No hay datos para mostrar en este momento." })] })), infiniteScroll && (_jsxs(_Fragment, { children: [loadingMore && (_jsx(Skeleton, { className: 'h-16 w-full shrink-0', "data-testid": 'table-loading-more-mobile' })), _jsx("div", { ref: infMobileSentinel, className: 'h-1 w-full shrink-0', "aria-hidden": true })] }))] }), _jsx("div", { className: 'shrink-0 pt-4', children: infiniteScroll ? (data.length > 0 && (_jsx("p", { className: 'text-center text-xs text-muted-foreground tabular-nums', children: t('common.showingCount', {
1268
+ }))) : (_jsxs("div", { className: 'flex flex-col items-center justify-center gap-2 rounded-lg border bg-card py-12 text-muted-foreground', children: [_jsx("div", { className: 'flex h-16 w-16 items-center justify-center rounded-full bg-muted/50', children: _jsx(Inbox, { className: 'h-8 w-8' }) }), _jsx("h3", { className: 'text-base font-semibold text-foreground', children: "No se encontraron resultados" }), _jsx("p", { className: 'text-sm text-muted-foreground', children: "No hay datos para mostrar en este momento." })] })), infiniteScroll && (_jsxs(_Fragment, { children: [loadingMore && (_jsx(Skeleton, { className: 'h-16 w-full shrink-0', "data-testid": 'table-loading-more-mobile' })), _jsx("div", { ref: infMobileSentinel, className: 'h-1 w-full shrink-0', "aria-hidden": true })] }))] }), _jsx("div", { className: 'shrink-0 pt-4', children: infiniteScroll ? (data.length > 0 && (_jsx("p", { className: 'text-center text-xs text-muted-foreground tabular-nums', children: t('common.showingCount', {
1215
1269
  defaultValue: '{{count}} de {{total}}',
1216
1270
  count: data.length,
1217
1271
  total: rowCount,
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from './types';
2
+ export * from './table-virtualization';
2
3
  export { LicenseGate, LicenseExpiryBanner, LicenseStatusBadge, isLicenseOperable, isLicenseBlocking, isPresetEntitled, isTrialExpired, type LicenseGateProps, type LicenseExpiryBannerProps, type LicenseStatusBadgeProps, type LicenseState, type LicenseStatus, type LicenseBranding, } from './license';
3
4
  export * from './options-context';
4
5
  export { extractServerError, extractFieldErrors, localizeFieldIssue, localizeFieldErrorMap, toastServerError, toastServerSuccess, type ExtractedError, type Translate, type FieldIssue, } from './server-error';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,cAAc,SAAS,CAAA;AACvB,OAAO,EACH,WAAW,EACX,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,EAChB,cAAc,EACd,KAAK,gBAAgB,EACrB,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,EAC5B,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,eAAe,GACvB,MAAM,WAAW,CAAA;AAClB,cAAc,mBAAmB,CAAA;AACjC,OAAO,EACH,kBAAkB,EAClB,kBAAkB,EAClB,kBAAkB,EAClB,qBAAqB,EACrB,gBAAgB,EAChB,kBAAkB,EAClB,KAAK,cAAc,EACnB,KAAK,SAAS,EACd,KAAK,UAAU,GAClB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EACH,eAAe,EACf,iBAAiB,EACjB,UAAU,EACV,cAAc,EACd,YAAY,EACZ,KAAK,cAAc,GACtB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;AACnG,OAAO,EACH,sBAAsB,EACtB,yBAAyB,EACzB,4BAA4B,EAC5B,mBAAmB,EACnB,iBAAiB,GACpB,MAAM,uBAAuB,CAAA;AAC9B,cAAc,iBAAiB,CAAA;AAC/B,OAAO,EACH,aAAa,EACb,KAAK,kBAAkB,EACvB,YAAY,EACZ,YAAY,EACZ,mBAAmB,EACnB,mBAAmB,EACnB,iBAAiB,EACjB,eAAe,GAClB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EACH,mBAAmB,EACnB,sBAAsB,EACtB,WAAW,EACX,sBAAsB,EACtB,uBAAuB,EACvB,qBAAqB,EACrB,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,KAAK,yBAAyB,EAC9B,KAAK,kBAAkB,EACvB,KAAK,yBAAyB,EAC9B,KAAK,2BAA2B,GACnC,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACH,eAAe,EACf,iBAAiB,EACjB,eAAe,EACf,iBAAiB,EACjB,eAAe,EACf,uBAAuB,EACvB,uBAAuB,EACvB,uBAAuB,EACvB,eAAe,EACf,sBAAsB,EACtB,cAAc,EACd,mBAAmB,EACnB,iBAAiB,EACjB,uBAAuB,EACvB,qBAAqB,EACrB,iBAAiB,EACjB,mBAAmB,EACnB,SAAS,EACT,mBAAmB,EACnB,uBAAuB,EACvB,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,0BAA0B,EAC/B,KAAK,sBAAsB,GAC9B,MAAM,iBAAiB,CAAA;AACxB,OAAO,EACH,cAAc,EACd,KAAK,WAAW,EAChB,KAAK,oBAAoB,GAC5B,MAAM,gBAAgB,CAAA;AACvB,OAAO,EACH,iBAAiB,EACjB,KAAK,kBAAkB,EACvB,KAAK,uBAAuB,GAC/B,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EACH,WAAW,EACX,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,EACjB,KAAK,gBAAgB,GACxB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EACH,iBAAiB,EACjB,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,GAC/B,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AAC7E,OAAO,EACH,WAAW,EACX,WAAW,EACX,KAAK,aAAa,EAClB,KAAK,aAAa,GACrB,MAAM,gBAAgB,CAAA;AACvB,cAAc,gBAAgB,CAAA;AAC9B,OAAO,EACH,cAAc,EACd,KAAK,mBAAmB,GAC3B,MAAM,oBAAoB,CAAA;AAC3B,cAAc,mBAAmB,CAAA;AACjC,cAAc,eAAe,CAAA;AAC7B,cAAc,kBAAkB,CAAA;AAChC,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAC/D,OAAO,EACH,qBAAqB,EACrB,KAAK,gBAAgB,GACxB,MAAM,2BAA2B,CAAA;AAClC,OAAO,EACH,kBAAkB,EAClB,eAAe,EACf,KAAK,uBAAuB,EAC5B,KAAK,eAAe,GACvB,MAAM,wBAAwB,CAAA;AAC/B,cAAc,gBAAgB,CAAA;AAC9B,OAAO,EACH,mBAAmB,EACnB,uBAAuB,EACvB,uBAAuB,EACvB,oBAAoB,EACpB,kBAAkB,EAClB,UAAU,EACV,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACxB,KAAK,cAAc,GACtB,MAAM,eAAe,CAAA;AACtB,OAAO,EACH,mBAAmB,EACnB,cAAc,EACd,qBAAqB,EACrB,qBAAqB,EACrB,KAAK,WAAW,EAChB,KAAK,wBAAwB,GAChC,MAAM,wBAAwB,CAAA;AAC/B,cAAc,QAAQ,CAAA;AACtB,cAAc,mBAAmB,CAAA;AACjC,OAAO,EACH,mBAAmB,EACnB,MAAM,EACN,oBAAoB,EACpB,OAAO,EACP,sBAAsB,EACtB,eAAe,EACf,iBAAiB,EACjB,iBAAiB,EACjB,KAAK,KAAK,EACV,KAAK,wBAAwB,GAChC,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACH,oBAAoB,EACpB,KAAK,0BAA0B,EAC/B,KAAK,iBAAiB,GACzB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AACzF,OAAO,EACH,kBAAkB,EAClB,sBAAsB,EACtB,kBAAkB,EAClB,qBAAqB,EACrB,mBAAmB,EACnB,iBAAiB,EACjB,eAAe,EACf,sBAAsB,EACtB,aAAa,EACb,kBAAkB,EAClB,kBAAkB,EAClB,KAAK,uBAAuB,EAC5B,KAAK,kBAAkB,EACvB,KAAK,yBAAyB,EAC9B,KAAK,sBAAsB,EAC3B,KAAK,WAAW,EAChB,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,OAAO,EACZ,KAAK,SAAS,GACjB,MAAM,uBAAuB,CAAA;AAC9B,cAAc,uBAAuB,CAAA;AACrC,cAAc,wBAAwB,CAAA;AACtC,cAAc,sBAAsB,CAAA;AACpC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,eAAe,CAAA;AAC7B,cAAc,sBAAsB,CAAA;AACpC,cAAc,mBAAmB,CAAA;AACjC,cAAc,sBAAsB,CAAA;AACpC,cAAc,yBAAyB,CAAA;AACvC,cAAc,8BAA8B,CAAA;AAC5C,cAAc,4BAA4B,CAAA;AAC1C,cAAc,kBAAkB,CAAA;AAChC,OAAO,EACH,gBAAgB,EAChB,iBAAiB,EACjB,kBAAkB,EAClB,WAAW,EACX,iBAAiB,EACjB,qBAAqB,EACrB,eAAe,EACf,oBAAoB,EACpB,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,kBAAkB,EACvB,KAAK,4BAA4B,EACjC,KAAK,sBAAsB,GAC9B,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACH,2BAA2B,EAC3B,uBAAuB,EACvB,4BAA4B,EAC5B,KAAK,2BAA2B,EAChC,KAAK,qBAAqB,EAC1B,KAAK,8BAA8B,GACtC,MAAM,+BAA+B,CAAA;AACtC,OAAO,EACH,gBAAgB,EAChB,kBAAkB,EAClB,gBAAgB,EAChB,wBAAwB,EACxB,WAAW,EACX,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,GAC9B,MAAM,yBAAyB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,YAAY,EACR,kBAAkB,EAClB,YAAY,IAAI,yBAAyB,EACzC,iBAAiB,EACjB,oBAAoB,GACvB,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EACH,wBAAwB,EACxB,4BAA4B,EAC5B,cAAc,EACd,gBAAgB,EAChB,wBAAwB,EACxB,uBAAuB,EACvB,oBAAoB,EACpB,KAAK,qBAAqB,GAC7B,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAA;AACjF,YAAY,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAA;AACnE,OAAO,EACH,OAAO,EACP,QAAQ,EACR,cAAc,EACd,UAAU,EACV,QAAQ,EACR,WAAW,EACX,WAAW,EACX,UAAU,EACV,SAAS,EACT,UAAU,EACV,aAAa,EACb,eAAe,EACf,kBAAkB,EAClB,KAAK,OAAO,EACZ,KAAK,cAAc,GACtB,MAAM,YAAY,CAAA;AACnB,OAAO,EACH,UAAU,EACV,WAAW,EACX,cAAc,EACd,cAAc,GACjB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EACH,cAAc,EACd,YAAY,EACZ,WAAW,EACX,UAAU,EACV,KAAK,mBAAmB,EACxB,KAAK,SAAS,IAAI,uBAAuB,GAC5C,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAClE,OAAO,EAAE,mBAAmB,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAA;AACzE,OAAO,EAAE,2BAA2B,EAAE,MAAM,4BAA4B,CAAA;AACxE,YAAY,EAAE,wBAAwB,EAAE,QAAQ,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AAC5G,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAA;AACnE,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAC9C,YAAY,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AAC5E,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAA;AAC/D,YAAY,EACR,QAAQ,EACR,WAAW,EACX,YAAY,EACZ,iBAAiB,EACjB,uBAAuB,EACvB,qBAAqB,GACxB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EACH,eAAe,EACf,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,sBAAsB,GAC9B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACH,eAAe,EACf,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,EACxB,yBAAyB,EACzB,kBAAkB,EAClB,wBAAwB,EACxB,cAAc,GACjB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACH,gBAAgB,EAChB,eAAe,EACf,oBAAoB,EACpB,UAAU,EACV,KAAK,qBAAqB,GAC7B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACH,sBAAsB,EACtB,iBAAiB,EACjB,oBAAoB,EACpB,KAAK,cAAc,EACnB,KAAK,mBAAmB,GAC3B,MAAM,4BAA4B,CAAA;AACnC,OAAO,EACH,sBAAsB,EACtB,sBAAsB,EACtB,6BAA6B,EAC7B,0BAA0B,EAC1B,uBAAuB,GAC1B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACH,kBAAkB,EAClB,aAAa,EACb,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,sBAAsB,EAC3B,KAAK,wBAAwB,GAChC,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EACH,kBAAkB,EAClB,kBAAkB,EAClB,qBAAqB,EACrB,KAAK,eAAe,GACvB,MAAM,yBAAyB,CAAA;AAChC,OAAO,EACH,iBAAiB,EACjB,YAAY,EACZ,mBAAmB,EACnB,gBAAgB,EAChB,oBAAoB,EACpB,cAAc,EACd,mBAAmB,EACnB,yBAAyB,EACzB,oBAAoB,EACpB,+BAA+B,EAC/B,wBAAwB,EACxB,qBAAqB,EACrB,QAAQ,GACX,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACH,wBAAwB,EACxB,mBAAmB,EACnB,KAAK,mBAAmB,GAC3B,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EACH,qBAAqB,EACrB,KAAK,0BAA0B,GAClC,MAAM,2BAA2B,CAAA;AAClC,OAAO,EACH,YAAY,EACZ,KAAK,aAAa,EAClB,KAAK,iBAAiB,GACzB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EACH,aAAa,EACb,KAAK,kBAAkB,GAC1B,MAAM,kBAAkB,CAAA;AACzB,OAAO,EACH,gBAAgB,EAChB,KAAK,qBAAqB,GAC7B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACH,aAAa,EACb,eAAe,GAClB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAA;AAC/D,YAAY,EACR,UAAU,EACV,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,aAAa,EACb,oBAAoB,EACpB,sBAAsB,EACtB,mBAAmB,EACnB,iBAAiB,EACjB,UAAU,EACV,oBAAoB,EACpB,cAAc,EACd,kBAAkB,EAClB,oBAAoB,GACvB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EACH,UAAU,EACV,SAAS,EACT,UAAU,EACV,UAAU,EACV,SAAS,EACT,WAAW,EACX,UAAU,EACV,cAAc,EACd,KAAK,iBAAiB,GACzB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACH,cAAc,EACd,cAAc,EACd,SAAS,EACT,UAAU,EACV,KAAK,mBAAmB,GAC3B,MAAM,2BAA2B,CAAA;AAClC,OAAO,EACH,UAAU,EACV,SAAS,EACT,WAAW,EACX,WAAW,EACX,KAAK,eAAe,GACvB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACH,iBAAiB,EACjB,cAAc,EACd,WAAW,EACX,aAAa,EACb,YAAY,EACZ,aAAa,EACb,KAAK,aAAa,EAClB,KAAK,eAAe,GACvB,MAAM,yBAAyB,CAAA;AAChC,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACpE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,cAAc,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -4,6 +4,7 @@
4
4
  // duplicate-symbol conflict; consumers who want the canonical SDK type
5
5
  // should import from `@asteby/metacore-sdk` directly.
6
6
  export * from './types';
7
+ export * from './table-virtualization';
7
8
  export { LicenseGate, LicenseExpiryBanner, LicenseStatusBadge, isLicenseOperable, isLicenseBlocking, isPresetEntitled, isTrialExpired, } from './license';
8
9
  export * from './options-context';
9
10
  export { extractServerError, extractFieldErrors, localizeFieldIssue, localizeFieldErrorMap, toastServerError, toastServerSuccess, } from './server-error';
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Row virtualization defaults for DynamicTable.
3
+ *
4
+ * When the visible row model grows past the threshold (common in
5
+ * `pagination="infinite"` as pages accumulate, or when the user picks a large
6
+ * page size), we only mount the rows in the viewport via
7
+ * `@tanstack/react-virtual`. Below the threshold we keep the simple map so
8
+ * short tables stay trivial to debug.
9
+ */
10
+ export declare const DYNAMIC_TABLE_VIRTUALIZE_THRESHOLD = 40;
11
+ /** Estimated desktop table row height (py-2 + content). */
12
+ export declare const DYNAMIC_TABLE_ROW_ESTIMATE_PX = 44;
13
+ /** Estimated mobile card height (padding + a few label/value rows). */
14
+ export declare const DYNAMIC_TABLE_CARD_ESTIMATE_PX = 120;
15
+ export declare function resolveVirtualizeThreshold(virtualizeRows?: boolean | number): number | false;
16
+ //# sourceMappingURL=table-virtualization.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"table-virtualization.d.ts","sourceRoot":"","sources":["../src/table-virtualization.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,eAAO,MAAM,kCAAkC,KAAK,CAAA;AAEpD,2DAA2D;AAC3D,eAAO,MAAM,6BAA6B,KAAK,CAAA;AAE/C,uEAAuE;AACvE,eAAO,MAAM,8BAA8B,MAAM,CAAA;AAEjD,wBAAgB,0BAA0B,CACxC,cAAc,CAAC,EAAE,OAAO,GAAG,MAAM,GAChC,MAAM,GAAG,KAAK,CAOhB"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Row virtualization defaults for DynamicTable.
3
+ *
4
+ * When the visible row model grows past the threshold (common in
5
+ * `pagination="infinite"` as pages accumulate, or when the user picks a large
6
+ * page size), we only mount the rows in the viewport via
7
+ * `@tanstack/react-virtual`. Below the threshold we keep the simple map so
8
+ * short tables stay trivial to debug.
9
+ */
10
+ export const DYNAMIC_TABLE_VIRTUALIZE_THRESHOLD = 40;
11
+ /** Estimated desktop table row height (py-2 + content). */
12
+ export const DYNAMIC_TABLE_ROW_ESTIMATE_PX = 44;
13
+ /** Estimated mobile card height (padding + a few label/value rows). */
14
+ export const DYNAMIC_TABLE_CARD_ESTIMATE_PX = 120;
15
+ export function resolveVirtualizeThreshold(virtualizeRows) {
16
+ if (virtualizeRows === false)
17
+ return false;
18
+ if (typeof virtualizeRows === 'number') {
19
+ return virtualizeRows > 0 ? virtualizeRows : false;
20
+ }
21
+ // true or undefined → default threshold
22
+ return DYNAMIC_TABLE_VIRTUALIZE_THRESHOLD;
23
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asteby/metacore-runtime-react",
3
- "version": "38.1.0",
3
+ "version": "39.1.0",
4
4
  "description": "React runtime for metacore hosts — renders addon contributions dynamically",
5
5
  "repository": {
6
6
  "type": "git",
@@ -21,6 +21,7 @@
21
21
  "@dnd-kit/sortable": "^10.0.0",
22
22
  "@dnd-kit/utilities": "^3.2.2",
23
23
  "@module-federation/runtime": "^2.5.0",
24
+ "@tanstack/react-virtual": "^3.14.13",
24
25
  "recharts": "^2.15.0",
25
26
  "zod": "^4.3.0"
26
27
  },
@@ -37,8 +38,8 @@
37
38
  "react-i18next": ">=13",
38
39
  "sonner": ">=1.7",
39
40
  "zustand": ">=5",
40
- "@asteby/metacore-sdk": "^3.7.0",
41
- "@asteby/metacore-ui": "^2.18.0"
41
+ "@asteby/metacore-sdk": "^3.8.0",
42
+ "@asteby/metacore-ui": "^2.18.1"
42
43
  },
43
44
  "peerDependenciesMeta": {
44
45
  "@tanstack/react-router": {
@@ -67,8 +68,8 @@
67
68
  "typescript": "^6.0.0",
68
69
  "vitest": "^4.0.0",
69
70
  "zustand": "^5.0.0",
70
- "@asteby/metacore-sdk": "3.7.0",
71
- "@asteby/metacore-ui": "2.18.0"
71
+ "@asteby/metacore-sdk": "3.8.0",
72
+ "@asteby/metacore-ui": "2.18.1"
72
73
  },
73
74
  "scripts": {
74
75
  "build": "tsc -p tsconfig.json",
@@ -0,0 +1,25 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import {
3
+ DYNAMIC_TABLE_VIRTUALIZE_THRESHOLD,
4
+ resolveVirtualizeThreshold,
5
+ } from '../table-virtualization'
6
+
7
+ describe('resolveVirtualizeThreshold', () => {
8
+ it('defaults to the shared threshold', () => {
9
+ expect(resolveVirtualizeThreshold()).toBe(DYNAMIC_TABLE_VIRTUALIZE_THRESHOLD)
10
+ expect(resolveVirtualizeThreshold(true)).toBe(DYNAMIC_TABLE_VIRTUALIZE_THRESHOLD)
11
+ })
12
+
13
+ it('can be disabled', () => {
14
+ expect(resolveVirtualizeThreshold(false)).toBe(false)
15
+ })
16
+
17
+ it('accepts a custom positive threshold', () => {
18
+ expect(resolveVirtualizeThreshold(80)).toBe(80)
19
+ })
20
+
21
+ it('treats non-positive numbers as disabled', () => {
22
+ expect(resolveVirtualizeThreshold(0)).toBe(false)
23
+ expect(resolveVirtualizeThreshold(-1)).toBe(false)
24
+ })
25
+ })
@@ -10,10 +10,11 @@
10
10
  // * `@/components/dynamic/dynamic-columns` → host-injected via the
11
11
  // `getDynamicColumns` prop (hosts retain ownership because the rendered
12
12
  // column cells are tightly coupled to their design system).
13
- import { useEffect, useState, useMemo, useCallback, useRef } from 'react'
13
+ import { useEffect, useState, useMemo, useCallback, useRef, type MouseEvent } from 'react'
14
14
  import { useTranslation } from 'react-i18next'
15
15
  import { format } from 'date-fns'
16
16
  import type { DateRange } from 'react-day-picker'
17
+ import { useVirtualizer } from '@tanstack/react-virtual'
17
18
  import {
18
19
  type SortingState,
19
20
  type VisibilityState,
@@ -69,6 +70,11 @@ import { defaultGetDynamicColumns, DATE_CELL_TYPES, aggregateOf, formatAggregate
69
70
  import { useFacetLoaders, isLongTextColumn } from './use-facet-loaders'
70
71
  import { translateOptionLabels } from './filter-chips'
71
72
  import { dedupeById, useInfiniteScrollSentinel } from './use-infinite-scroll'
73
+ import {
74
+ DYNAMIC_TABLE_CARD_ESTIMATE_PX,
75
+ DYNAMIC_TABLE_ROW_ESTIMATE_PX,
76
+ resolveVirtualizeThreshold,
77
+ } from './table-virtualization'
72
78
  import { OptionsContext } from './options-context'
73
79
  import type { TableMetadata, ApiResponse, ColumnDefinition } from './types'
74
80
  import { getSearchableColumnKeys } from './column-visibility'
@@ -245,6 +251,13 @@ export interface DynamicTableProps {
245
251
  * mode exactly as before.
246
252
  */
247
253
  infiniteScroll?: boolean
254
+ /**
255
+ * Row virtualization. When the visible row model has at least N rows,
256
+ * only viewport rows are mounted (helps infinite scroll and large page
257
+ * sizes). `true` / omitted → default threshold (40). `false` → always
258
+ * render every row. A positive number overrides the threshold.
259
+ */
260
+ virtualizeRows?: boolean | number
248
261
  }
249
262
 
250
263
  export function DynamicTable({
@@ -266,6 +279,7 @@ export function DynamicTable({
266
279
  currency,
267
280
  pagination: paginationMode,
268
281
  infiniteScroll: infiniteScrollProp = false,
282
+ virtualizeRows,
269
283
  }: DynamicTableProps) {
270
284
  // The explicit `pagination` prop wins; the legacy `infiniteScroll` boolean
271
285
  // still selects the mode when `pagination` is absent (back-compat).
@@ -1300,6 +1314,27 @@ export function DynamicTable({
1300
1314
  getFacetedUniqueValues: getFacetedUniqueValues(),
1301
1315
  })
1302
1316
 
1317
+ const tableRows = table.getRowModel().rows
1318
+ const virtualizeThreshold = resolveVirtualizeThreshold(virtualizeRows)
1319
+ const shouldVirtualize =
1320
+ virtualizeThreshold !== false && tableRows.length >= virtualizeThreshold
1321
+ const colSpan = Math.max(columns.length, 1)
1322
+
1323
+ const desktopVirtualizer = useVirtualizer({
1324
+ count: shouldVirtualize ? tableRows.length : 0,
1325
+ getScrollElement: () => infDesktopRoot.current,
1326
+ estimateSize: () => DYNAMIC_TABLE_ROW_ESTIMATE_PX,
1327
+ overscan: 8,
1328
+ enabled: shouldVirtualize,
1329
+ })
1330
+ const mobileVirtualizer = useVirtualizer({
1331
+ count: shouldVirtualize ? tableRows.length : 0,
1332
+ getScrollElement: () => infMobileRoot.current,
1333
+ estimateSize: () => DYNAMIC_TABLE_CARD_ESTIMATE_PX,
1334
+ overscan: 6,
1335
+ enabled: shouldVirtualize,
1336
+ })
1337
+
1303
1338
  const TableSkeleton = () => (
1304
1339
  <>
1305
1340
  {Array.from({ length: 5 }).map((_, i) => (
@@ -1407,9 +1442,70 @@ export function DynamicTable({
1407
1442
  <TableBody>
1408
1443
  {loadingData && data.length === 0 ? (
1409
1444
  <TableSkeleton />
1410
- ) : table.getRowModel().rows?.length ? (
1445
+ ) : tableRows.length ? (
1411
1446
  <>
1412
- {table.getRowModel().rows.map((row: Row<any>) => (
1447
+ {shouldVirtualize ? (
1448
+ <>
1449
+ {desktopVirtualizer.getVirtualItems().length > 0 && (
1450
+ <TableRow
1451
+ className='border-0 hover:bg-transparent'
1452
+ aria-hidden
1453
+ >
1454
+ <TableCell
1455
+ colSpan={colSpan}
1456
+ className='p-0'
1457
+ style={{
1458
+ height: desktopVirtualizer.getVirtualItems()[0]?.start ?? 0,
1459
+ }}
1460
+ />
1461
+ </TableRow>
1462
+ )}
1463
+ {desktopVirtualizer.getVirtualItems().map((virtualRow) => {
1464
+ const row = tableRows[virtualRow.index]
1465
+ if (!row) return null
1466
+ return (
1467
+ <TableRow
1468
+ key={row.id}
1469
+ data-index={virtualRow.index}
1470
+ ref={desktopVirtualizer.measureElement}
1471
+ data-state={row.getIsSelected() && 'selected'}
1472
+ className={cn(onRowClick && 'cursor-pointer')}
1473
+ onClick={onRowClick ? () => onRowClick(row.original) : undefined}
1474
+ >
1475
+ {row.getVisibleCells().map((cell: Cell<any, unknown>) => {
1476
+ const isActionsColumn = cell.column.id === 'actions'
1477
+ const isSelectColumn = cell.column.id === 'select'
1478
+ return (
1479
+ <TableCell
1480
+ key={cell.id}
1481
+ style={cell.column.columnDef.size ? { width: cell.column.columnDef.size } : undefined}
1482
+ className={cn('py-2', isActionsColumn && 'sticky right-0 bg-card shadow-[-2px_0_5px_-2px_rgba(0,0,0,0.1)]')}
1483
+ onClick={(isActionsColumn || isSelectColumn) ? (e: MouseEvent) => e.stopPropagation() : undefined}
1484
+ >
1485
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
1486
+ </TableCell>
1487
+ )
1488
+ })}
1489
+ </TableRow>
1490
+ )
1491
+ })}
1492
+ {(() => {
1493
+ const items = desktopVirtualizer.getVirtualItems()
1494
+ const last = items[items.length - 1]
1495
+ const pad =
1496
+ last != null
1497
+ ? desktopVirtualizer.getTotalSize() - last.end
1498
+ : 0
1499
+ if (pad <= 0) return null
1500
+ return (
1501
+ <TableRow className='border-0 hover:bg-transparent' aria-hidden>
1502
+ <TableCell colSpan={colSpan} className='p-0' style={{ height: pad }} />
1503
+ </TableRow>
1504
+ )
1505
+ })()}
1506
+ </>
1507
+ ) : (
1508
+ tableRows.map((row: Row<any>) => (
1413
1509
  <TableRow
1414
1510
  key={row.id}
1415
1511
  data-state={row.getIsSelected() && 'selected'}
@@ -1424,14 +1520,15 @@ export function DynamicTable({
1424
1520
  key={cell.id}
1425
1521
  style={cell.column.columnDef.size ? { width: cell.column.columnDef.size } : undefined}
1426
1522
  className={cn('py-2', isActionsColumn && 'sticky right-0 bg-card shadow-[-2px_0_5px_-2px_rgba(0,0,0,0.1)]')}
1427
- onClick={(isActionsColumn || isSelectColumn) ? (e: React.MouseEvent) => e.stopPropagation() : undefined}
1523
+ onClick={(isActionsColumn || isSelectColumn) ? (e: MouseEvent) => e.stopPropagation() : undefined}
1428
1524
  >
1429
1525
  {flexRender(cell.column.columnDef.cell, cell.getContext())}
1430
1526
  </TableCell>
1431
1527
  )
1432
1528
  })}
1433
1529
  </TableRow>
1434
- ))}
1530
+ ))
1531
+ )}
1435
1532
  {/* Spacer row: absorbs the table's leftover height (table is
1436
1533
  h-full when a footer shows) so the totals footer is pinned to
1437
1534
  the bottom of the box even with only a few rows. */}
@@ -1526,8 +1623,64 @@ export function DynamicTable({
1526
1623
  <Skeleton className='mt-2 h-4 w-32' />
1527
1624
  </div>
1528
1625
  ))
1529
- ) : table.getRowModel().rows?.length ? (
1530
- table.getRowModel().rows.map((row: Row<any>) => {
1626
+ ) : tableRows.length ? (
1627
+ shouldVirtualize ? (
1628
+ <div
1629
+ className='relative w-full'
1630
+ style={{ height: mobileVirtualizer.getTotalSize() }}
1631
+ >
1632
+ {mobileVirtualizer.getVirtualItems().map((virtualRow) => {
1633
+ const row = tableRows[virtualRow.index]
1634
+ if (!row) return null
1635
+ const cells = row.getVisibleCells()
1636
+ const actionsCell = cells.find((c: Cell<any, unknown>) => c.column.id === 'actions')
1637
+ const dataCells = cells.filter(
1638
+ (c: Cell<any, unknown>) => c.column.id !== 'actions' && c.column.id !== 'select',
1639
+ )
1640
+ return (
1641
+ <div
1642
+ key={row.id}
1643
+ data-index={virtualRow.index}
1644
+ ref={mobileVirtualizer.measureElement}
1645
+ data-state={row.getIsSelected() && 'selected'}
1646
+ className={cn(
1647
+ 'absolute left-0 right-0 flex flex-col gap-1.5 rounded-lg border bg-card p-3 data-[state=selected]:border-primary/40',
1648
+ onRowClick && 'cursor-pointer',
1649
+ )}
1650
+ style={{ transform: `translateY(${virtualRow.start}px)` }}
1651
+ onClick={onRowClick ? () => onRowClick(row.original) : undefined}
1652
+ >
1653
+ {dataCells.map((cell: Cell<any, unknown>) => {
1654
+ const cellMeta = cell.column.columnDef.meta as
1655
+ | { label?: string }
1656
+ | undefined
1657
+ const header = cell.column.columnDef.header
1658
+ const label =
1659
+ cellMeta?.label ??
1660
+ (typeof header === 'string' ? header : cell.column.id)
1661
+ return (
1662
+ <div key={cell.id} className='flex items-start justify-between gap-3 text-sm'>
1663
+ <span className='shrink-0 text-muted-foreground'>{label}</span>
1664
+ <span className='min-w-0 overflow-hidden break-words text-right font-medium [&_[data-slot=badge]]:max-w-full [&_[data-slot=badge]]:whitespace-normal [&_[data-slot=badge]]:text-start'>
1665
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
1666
+ </span>
1667
+ </div>
1668
+ )
1669
+ })}
1670
+ {actionsCell && (
1671
+ <div
1672
+ className='flex justify-end border-t pt-2'
1673
+ onClick={onRowClick ? (e: MouseEvent) => e.stopPropagation() : undefined}
1674
+ >
1675
+ {flexRender(actionsCell.column.columnDef.cell, actionsCell.getContext())}
1676
+ </div>
1677
+ )}
1678
+ </div>
1679
+ )
1680
+ })}
1681
+ </div>
1682
+ ) : (
1683
+ tableRows.map((row: Row<any>) => {
1531
1684
  const cells = row.getVisibleCells()
1532
1685
  const actionsCell = cells.find((c: Cell<any, unknown>) => c.column.id === 'actions')
1533
1686
  const dataCells = cells.filter(
@@ -1569,7 +1722,7 @@ export function DynamicTable({
1569
1722
  {actionsCell && (
1570
1723
  <div
1571
1724
  className='flex justify-end border-t pt-2'
1572
- onClick={onRowClick ? (e: React.MouseEvent) => e.stopPropagation() : undefined}
1725
+ onClick={onRowClick ? (e: MouseEvent) => e.stopPropagation() : undefined}
1573
1726
  >
1574
1727
  {flexRender(actionsCell.column.columnDef.cell, actionsCell.getContext())}
1575
1728
  </div>
@@ -1577,6 +1730,7 @@ export function DynamicTable({
1577
1730
  </div>
1578
1731
  )
1579
1732
  })
1733
+ )
1580
1734
  ) : (
1581
1735
  <div className='flex flex-col items-center justify-center gap-2 rounded-lg border bg-card py-12 text-muted-foreground'>
1582
1736
  <div className='flex h-16 w-16 items-center justify-center rounded-full bg-muted/50'>
@@ -1636,7 +1790,7 @@ export function DynamicTable({
1636
1790
  {!isBulkDeleting && (
1637
1791
  <AlertDialogFooter>
1638
1792
  <AlertDialogCancel>{t('common.cancel')}</AlertDialogCancel>
1639
- <AlertDialogAction onClick={(e: React.MouseEvent) => { e.preventDefault(); confirmBulkDelete() }} className="bg-red-600 hover:bg-red-700">Eliminar todos</AlertDialogAction>
1793
+ <AlertDialogAction onClick={(e: MouseEvent) => { e.preventDefault(); confirmBulkDelete() }} className="bg-red-600 hover:bg-red-700">Eliminar todos</AlertDialogAction>
1640
1794
  </AlertDialogFooter>
1641
1795
  )}
1642
1796
  </AlertDialogContent>
package/src/index.ts CHANGED
@@ -4,6 +4,7 @@
4
4
  // duplicate-symbol conflict; consumers who want the canonical SDK type
5
5
  // should import from `@asteby/metacore-sdk` directly.
6
6
  export * from './types'
7
+ export * from './table-virtualization'
7
8
  export {
8
9
  LicenseGate,
9
10
  LicenseExpiryBanner,
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Row virtualization defaults for DynamicTable.
3
+ *
4
+ * When the visible row model grows past the threshold (common in
5
+ * `pagination="infinite"` as pages accumulate, or when the user picks a large
6
+ * page size), we only mount the rows in the viewport via
7
+ * `@tanstack/react-virtual`. Below the threshold we keep the simple map so
8
+ * short tables stay trivial to debug.
9
+ */
10
+ export const DYNAMIC_TABLE_VIRTUALIZE_THRESHOLD = 40
11
+
12
+ /** Estimated desktop table row height (py-2 + content). */
13
+ export const DYNAMIC_TABLE_ROW_ESTIMATE_PX = 44
14
+
15
+ /** Estimated mobile card height (padding + a few label/value rows). */
16
+ export const DYNAMIC_TABLE_CARD_ESTIMATE_PX = 120
17
+
18
+ export function resolveVirtualizeThreshold(
19
+ virtualizeRows?: boolean | number,
20
+ ): number | false {
21
+ if (virtualizeRows === false) return false
22
+ if (typeof virtualizeRows === 'number') {
23
+ return virtualizeRows > 0 ? virtualizeRows : false
24
+ }
25
+ // true or undefined → default threshold
26
+ return DYNAMIC_TABLE_VIRTUALIZE_THRESHOLD
27
+ }