tablero 1.0.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.
@@ -0,0 +1,117 @@
1
+ import { ColumnDef, TableStateHandler, UncontrolledTableState, TableState, Column } from './core.js';
2
+
3
+ /**
4
+ * React hook for data table functionality
5
+ *
6
+ * Bridges core table logic to React components.
7
+ * Supports both controlled and uncontrolled state patterns.
8
+ */
9
+
10
+ /**
11
+ * Hook configuration options
12
+ */
13
+ interface UseDataTableOptions<TData> {
14
+ /** Data array to display */
15
+ data: TData[];
16
+ /** Column definitions */
17
+ columns: readonly ColumnDef<TData>[];
18
+ /** Initial page size (default: 10) */
19
+ pageSize?: number;
20
+ /** Controlled or uncontrolled state configuration */
21
+ state?: TableStateHandler | UncontrolledTableState;
22
+ }
23
+ /**
24
+ * Table instance API returned by useDataTable
25
+ */
26
+ interface TableInstance<TData> {
27
+ /** Current table state */
28
+ state: TableState;
29
+ /** Runtime columns (with normalized accessors) */
30
+ columns: Column<TData>[];
31
+ /** Visible columns in current order */
32
+ visibleColumns: Column<TData>[];
33
+ /** Original data array */
34
+ data: TData[];
35
+ /** Filtered data (after applying filters) */
36
+ filteredData: TData[];
37
+ /** Sorted data (after applying sorting) */
38
+ sortedData: TData[];
39
+ /** Paginated data (final data to display) */
40
+ paginatedData: TData[];
41
+ /** Total number of items after filtering */
42
+ filteredRowCount: number;
43
+ /** Total number of pages */
44
+ pageCount: number;
45
+ /** Current page index */
46
+ pageIndex: number;
47
+ /** Current page size */
48
+ pageSize: number;
49
+ /** Whether there is a next page */
50
+ hasNextPage: boolean;
51
+ /** Whether there is a previous page */
52
+ hasPreviousPage: boolean;
53
+ /** Sorting handlers */
54
+ sorting: {
55
+ /** Current sort state */
56
+ state: TableState["sorting"];
57
+ /** Toggle sort for a column */
58
+ toggle: (columnId: string) => void;
59
+ /** Set sort explicitly */
60
+ set: (columnId: string | null, direction: "asc" | "desc" | null) => void;
61
+ /** Clear sort */
62
+ clear: () => void;
63
+ };
64
+ /** Pagination handlers */
65
+ pagination: {
66
+ /** Current pagination state */
67
+ state: TableState["pagination"];
68
+ /** Go to next page */
69
+ nextPage: () => void;
70
+ /** Go to previous page */
71
+ previousPage: () => void;
72
+ /** Go to specific page */
73
+ goToPage: (pageIndex: number) => void;
74
+ /** Set page size */
75
+ setPageSize: (pageSize: number) => void;
76
+ };
77
+ /** Filtering handlers */
78
+ filtering: {
79
+ /** Current filter state */
80
+ state: TableState["filtering"];
81
+ /** Set global filter */
82
+ setGlobalFilter: (filter: string) => void;
83
+ /** Set column filter */
84
+ setColumnFilter: (columnId: string, filter: string) => void;
85
+ /** Clear column filter */
86
+ clearColumnFilter: (columnId: string) => void;
87
+ /** Clear all filters */
88
+ clearAllFilters: () => void;
89
+ };
90
+ /** Column management handlers */
91
+ columnManagement: {
92
+ /** Toggle column visibility */
93
+ toggleVisibility: (columnId: string) => void;
94
+ /** Set column visibility */
95
+ setVisibility: (columnId: string, visible: boolean) => void;
96
+ /** Reorder columns */
97
+ reorder: (columnOrder: string[]) => void;
98
+ };
99
+ }
100
+ /**
101
+ * useDataTable hook
102
+ *
103
+ * Provides a complete table instance with state management,
104
+ * data transformations, and event handlers.
105
+ *
106
+ * @example
107
+ * ```tsx
108
+ * const table = useDataTable({
109
+ * data: users,
110
+ * columns: userColumns,
111
+ * pageSize: 10,
112
+ * });
113
+ * ```
114
+ */
115
+ declare function useDataTable<TData>(options: UseDataTableOptions<TData>): TableInstance<TData>;
116
+
117
+ export { type TableInstance, type UseDataTableOptions, useDataTable };
package/dist/react.js ADDED
@@ -0,0 +1,543 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+
5
+ // packages/react/useDataTable.ts
6
+
7
+ // packages/core/tableState.ts
8
+ function createInitialTableState(columnIds) {
9
+ return {
10
+ sorting: {
11
+ columnId: null,
12
+ direction: null
13
+ },
14
+ pagination: {
15
+ pageIndex: 0,
16
+ pageSize: 10
17
+ },
18
+ filtering: {
19
+ globalFilter: "",
20
+ columnFilters: {}
21
+ },
22
+ columnVisibility: columnIds.reduce((acc, id) => {
23
+ acc[id] = true;
24
+ return acc;
25
+ }, {}),
26
+ columnOrder: [...columnIds]
27
+ };
28
+ }
29
+ function updateTableState(state, updates) {
30
+ return {
31
+ ...state,
32
+ ...updates
33
+ };
34
+ }
35
+ function updateSorting(state, sorting) {
36
+ return updateTableState(state, { sorting });
37
+ }
38
+ function updatePagination(state, pagination) {
39
+ return updateTableState(state, {
40
+ pagination: {
41
+ ...state.pagination,
42
+ ...pagination
43
+ }
44
+ });
45
+ }
46
+ function updateFiltering(state, filtering) {
47
+ return updateTableState(state, {
48
+ filtering: {
49
+ ...state.filtering,
50
+ ...filtering
51
+ }
52
+ });
53
+ }
54
+ function updateColumnVisibility(state, columnId, visible) {
55
+ return updateTableState(state, {
56
+ columnVisibility: {
57
+ ...state.columnVisibility,
58
+ [columnId]: visible
59
+ }
60
+ });
61
+ }
62
+ function updateColumnOrder(state, columnOrder) {
63
+ return updateTableState(state, { columnOrder });
64
+ }
65
+ function isControlledState(handler) {
66
+ return "state" in handler && "setState" in handler;
67
+ }
68
+
69
+ // packages/core/columns.ts
70
+ function normalizeAccessor(accessor) {
71
+ if (typeof accessor === "function") {
72
+ return accessor;
73
+ }
74
+ return (data) => {
75
+ return data[String(accessor)];
76
+ };
77
+ }
78
+ function createColumn(def) {
79
+ const accessor = def.accessor ? normalizeAccessor(def.accessor) : () => void 0;
80
+ return {
81
+ def,
82
+ id: def.id,
83
+ header: def.header ?? def.id,
84
+ sortable: def.sortable ?? false,
85
+ filter: def.filter ?? "none",
86
+ width: def.width,
87
+ align: def.align ?? "left",
88
+ accessor
89
+ };
90
+ }
91
+ function createColumns(definitions) {
92
+ return definitions.map(createColumn);
93
+ }
94
+ function getVisibleColumns(columns, visibility) {
95
+ return columns.filter((col) => visibility[col.id] !== false);
96
+ }
97
+ function getOrderedColumns(columns, order) {
98
+ const columnMap = new Map(columns.map((col) => [col.id, col]));
99
+ const ordered = [];
100
+ for (const id of order) {
101
+ const col = columnMap.get(id);
102
+ if (col) {
103
+ ordered.push(col);
104
+ columnMap.delete(id);
105
+ }
106
+ }
107
+ ordered.push(...columnMap.values());
108
+ return ordered;
109
+ }
110
+ function getColumnIds(definitions) {
111
+ return definitions.map((def) => def.id);
112
+ }
113
+
114
+ // packages/core/sorting.ts
115
+ function createInitialSortState() {
116
+ return {
117
+ columnId: null,
118
+ direction: null
119
+ };
120
+ }
121
+ function toggleSort(currentState, columnId) {
122
+ if (currentState.columnId === columnId) {
123
+ if (currentState.direction === "asc") {
124
+ return { columnId, direction: "desc" };
125
+ }
126
+ if (currentState.direction === "desc") {
127
+ return { columnId: null, direction: null };
128
+ }
129
+ }
130
+ return { columnId, direction: "asc" };
131
+ }
132
+ function setSort(columnId, direction) {
133
+ return {
134
+ columnId,
135
+ direction
136
+ };
137
+ }
138
+ function clearSort() {
139
+ return createInitialSortState();
140
+ }
141
+ function isSortActive(state) {
142
+ return state.columnId !== null && state.direction !== null;
143
+ }
144
+ function defaultCompare(a, b) {
145
+ if (a === b) return 0;
146
+ if (a === null || a === void 0) return 1;
147
+ if (b === null || b === void 0) return -1;
148
+ if (typeof a === "number" && typeof b === "number") {
149
+ return a - b;
150
+ }
151
+ if (typeof a === "string" && typeof b === "string") {
152
+ return a.localeCompare(b);
153
+ }
154
+ if (a instanceof Date && b instanceof Date) {
155
+ return a.getTime() - b.getTime();
156
+ }
157
+ return String(a).localeCompare(String(b));
158
+ }
159
+ function createComparator(direction, compareFn = defaultCompare) {
160
+ return (a, b) => {
161
+ const result = compareFn(a, b);
162
+ return direction === "asc" ? result : -result;
163
+ };
164
+ }
165
+ function applySort(data, sortState, getValue, compareFn) {
166
+ if (!isSortActive(sortState) || !sortState.columnId) {
167
+ return [...data];
168
+ }
169
+ const comparator = createComparator(
170
+ sortState.direction,
171
+ defaultCompare
172
+ );
173
+ return [...data].sort((a, b) => {
174
+ const valueA = getValue(a, sortState.columnId);
175
+ const valueB = getValue(b, sortState.columnId);
176
+ return comparator(valueA, valueB);
177
+ });
178
+ }
179
+
180
+ // packages/core/filtering.ts
181
+ function createInitialFilterState() {
182
+ return {
183
+ globalFilter: "",
184
+ columnFilters: {}
185
+ };
186
+ }
187
+ function setGlobalFilter(filter) {
188
+ return {
189
+ globalFilter: filter,
190
+ columnFilters: {}
191
+ };
192
+ }
193
+ function setColumnFilter(state, columnId, filter) {
194
+ return {
195
+ ...state,
196
+ columnFilters: {
197
+ ...state.columnFilters,
198
+ [columnId]: filter
199
+ }
200
+ };
201
+ }
202
+ function clearColumnFilter(state, columnId) {
203
+ const { [columnId]: _, ...restFilters } = state.columnFilters;
204
+ return {
205
+ ...state,
206
+ columnFilters: restFilters
207
+ };
208
+ }
209
+ function clearAllFilters() {
210
+ return createInitialFilterState();
211
+ }
212
+ function isFilterActive(state) {
213
+ return state.globalFilter.trim() !== "" || Object.values(state.columnFilters).some((filter) => filter.trim() !== "");
214
+ }
215
+ function defaultMatch(value, filter) {
216
+ if (!filter.trim()) return true;
217
+ const normalizedFilter = filter.toLowerCase().trim();
218
+ const normalizedValue = String(value ?? "").toLowerCase();
219
+ return normalizedValue.includes(normalizedFilter);
220
+ }
221
+ function applyFilters(data, filterState, getValue, matchFn = defaultMatch) {
222
+ if (!isFilterActive(filterState)) {
223
+ return [...data];
224
+ }
225
+ return data.filter((item) => {
226
+ if (filterState.globalFilter.trim()) {
227
+ const matchesGlobal = Object.keys(filterState.columnFilters).length === 0 ? (
228
+ // If no column filters, check all columns for global filter
229
+ Object.values(item).some(
230
+ (value) => matchFn(value, filterState.globalFilter)
231
+ )
232
+ ) : (
233
+ // If column filters exist, global filter is ignored (TODO: make configurable)
234
+ true
235
+ );
236
+ if (!matchesGlobal) return false;
237
+ }
238
+ for (const [columnId, filterValue] of Object.entries(filterState.columnFilters)) {
239
+ if (filterValue.trim()) {
240
+ const value = getValue(item, columnId);
241
+ if (!matchFn(value, filterValue)) {
242
+ return false;
243
+ }
244
+ }
245
+ }
246
+ return true;
247
+ });
248
+ }
249
+
250
+ // packages/core/pagination.ts
251
+ function getPageCount(totalItems, pageSize) {
252
+ if (pageSize <= 0) return 0;
253
+ return Math.ceil(totalItems / pageSize);
254
+ }
255
+ function getPageStartIndex(pageIndex, pageSize) {
256
+ return pageIndex * pageSize;
257
+ }
258
+ function getPageEndIndex(pageIndex, pageSize, totalItems) {
259
+ return Math.min(getPageStartIndex(pageIndex, pageSize) + pageSize, totalItems);
260
+ }
261
+ function clampPageIndex(pageIndex, totalPages) {
262
+ if (totalPages === 0) return 0;
263
+ return Math.max(0, Math.min(pageIndex, totalPages - 1));
264
+ }
265
+ function goToNextPage(state, totalPages) {
266
+ const nextIndex = clampPageIndex(state.pageIndex + 1, totalPages);
267
+ return {
268
+ ...state,
269
+ pageIndex: nextIndex
270
+ };
271
+ }
272
+ function goToPreviousPage(state, totalPages) {
273
+ const prevIndex = clampPageIndex(state.pageIndex - 1, totalPages);
274
+ return {
275
+ ...state,
276
+ pageIndex: prevIndex
277
+ };
278
+ }
279
+ function goToPage(state, pageIndex, totalPages) {
280
+ return {
281
+ ...state,
282
+ pageIndex: clampPageIndex(pageIndex, totalPages)
283
+ };
284
+ }
285
+ function setPageSize(state, pageSize, totalItems) {
286
+ const totalPages = getPageCount(totalItems, pageSize);
287
+ const clampedPageIndex = clampPageIndex(state.pageIndex, totalPages);
288
+ return {
289
+ pageIndex: clampedPageIndex,
290
+ pageSize
291
+ };
292
+ }
293
+ function getPaginatedData(data, pagination) {
294
+ const start = getPageStartIndex(pagination.pageIndex, pagination.pageSize);
295
+ const end = getPageEndIndex(
296
+ pagination.pageIndex,
297
+ pagination.pageSize,
298
+ data.length
299
+ );
300
+ return data.slice(start, end);
301
+ }
302
+ function hasNextPage(pageIndex, totalPages) {
303
+ return pageIndex < totalPages - 1;
304
+ }
305
+ function hasPreviousPage(pageIndex) {
306
+ return pageIndex > 0;
307
+ }
308
+
309
+ // packages/react/useDataTable.ts
310
+ function useDataTable(options) {
311
+ const { data, columns: columnDefs, pageSize: initialPageSize = 10, state } = options;
312
+ const isControlled = state && isControlledState(state);
313
+ const [internalState, setInternalState] = react.useState(() => {
314
+ const columnIds = getColumnIds(columnDefs);
315
+ const baseState = createInitialTableState(columnIds);
316
+ if (isControlled) {
317
+ return state.state;
318
+ }
319
+ const uncontrolledState = state;
320
+ const initialState = uncontrolledState?.initialState;
321
+ return {
322
+ ...baseState,
323
+ pagination: {
324
+ ...baseState.pagination,
325
+ pageSize: initialPageSize,
326
+ ...initialState?.pagination
327
+ },
328
+ ...initialState
329
+ };
330
+ });
331
+ const currentState = isControlled ? state.state : internalState;
332
+ const updateState = react.useCallback(
333
+ (updater) => {
334
+ const newState = typeof updater === "function" ? updater(currentState) : updater;
335
+ if (isControlled) {
336
+ state.setState(newState);
337
+ } else {
338
+ setInternalState(newState);
339
+ const uncontrolledState = state;
340
+ uncontrolledState?.onStateChange?.(newState);
341
+ }
342
+ },
343
+ [currentState, isControlled, state]
344
+ );
345
+ const columns = react.useMemo(() => {
346
+ return createColumns(columnDefs);
347
+ }, [columnDefs]);
348
+ const visibleColumns = react.useMemo(() => {
349
+ const visible = getVisibleColumns(columns, currentState.columnVisibility);
350
+ return getOrderedColumns(visible, currentState.columnOrder);
351
+ }, [columns, currentState.columnVisibility, currentState.columnOrder]);
352
+ const getValue = react.useCallback(
353
+ (item, columnId) => {
354
+ const column = columns.find((col) => col.id === columnId);
355
+ if (!column) return void 0;
356
+ return column.accessor(item);
357
+ },
358
+ [columns]
359
+ );
360
+ const filteredData = react.useMemo(() => {
361
+ return applyFilters(data, currentState.filtering, getValue);
362
+ }, [data, currentState.filtering, getValue]);
363
+ const sortedData = react.useMemo(() => {
364
+ if (!currentState.sorting.columnId) {
365
+ return filteredData;
366
+ }
367
+ return applySort(filteredData, currentState.sorting, getValue);
368
+ }, [filteredData, currentState.sorting, getValue]);
369
+ const paginatedData = react.useMemo(() => {
370
+ return getPaginatedData(sortedData, currentState.pagination);
371
+ }, [sortedData, currentState.pagination]);
372
+ const filteredRowCount = filteredData.length;
373
+ const pageCount = react.useMemo(() => {
374
+ return getPageCount(filteredRowCount, currentState.pagination.pageSize);
375
+ }, [filteredRowCount, currentState.pagination.pageSize]);
376
+ const hasNext = react.useMemo(() => {
377
+ return hasNextPage(currentState.pagination.pageIndex, pageCount);
378
+ }, [currentState.pagination.pageIndex, pageCount]);
379
+ const hasPrev = react.useMemo(() => {
380
+ return hasPreviousPage(currentState.pagination.pageIndex);
381
+ }, [currentState.pagination.pageIndex]);
382
+ const handleToggleSort = react.useCallback(
383
+ (columnId) => {
384
+ const newSorting = toggleSort(currentState.sorting, columnId);
385
+ updateState((prev) => updateSorting(prev, newSorting));
386
+ },
387
+ [currentState.sorting, updateState]
388
+ );
389
+ const handleSetSort = react.useCallback(
390
+ (columnId, direction) => {
391
+ const newSorting = setSort(columnId, direction);
392
+ updateState((prev) => updateSorting(prev, newSorting));
393
+ },
394
+ [updateState]
395
+ );
396
+ const handleClearSort = react.useCallback(() => {
397
+ const newSorting = clearSort();
398
+ updateState((prev) => updateSorting(prev, newSorting));
399
+ }, [updateState]);
400
+ const handleNextPage = react.useCallback(() => {
401
+ const newPagination = goToNextPage(currentState.pagination, pageCount);
402
+ updateState((prev) => updatePagination(prev, newPagination));
403
+ }, [currentState.pagination, pageCount, updateState]);
404
+ const handlePreviousPage = react.useCallback(() => {
405
+ const newPagination = goToPreviousPage(currentState.pagination, pageCount);
406
+ updateState((prev) => updatePagination(prev, newPagination));
407
+ }, [currentState.pagination, pageCount, updateState]);
408
+ const handleGoToPage = react.useCallback(
409
+ (pageIndex) => {
410
+ const newPagination = goToPage(currentState.pagination, pageIndex, pageCount);
411
+ updateState((prev) => updatePagination(prev, newPagination));
412
+ },
413
+ [currentState.pagination, pageCount, updateState]
414
+ );
415
+ const handleSetPageSize = react.useCallback(
416
+ (pageSize) => {
417
+ const newPagination = setPageSize(
418
+ currentState.pagination,
419
+ pageSize,
420
+ filteredRowCount
421
+ );
422
+ updateState((prev) => updatePagination(prev, newPagination));
423
+ },
424
+ [currentState.pagination, filteredRowCount, updateState]
425
+ );
426
+ const handleSetGlobalFilter = react.useCallback(
427
+ (filter) => {
428
+ const newFiltering = setGlobalFilter(filter);
429
+ updateState((prev) => updateFiltering(prev, newFiltering));
430
+ },
431
+ [updateState]
432
+ );
433
+ const handleSetColumnFilter = react.useCallback(
434
+ (columnId, filter) => {
435
+ const newFiltering = setColumnFilter(currentState.filtering, columnId, filter);
436
+ updateState((prev) => updateFiltering(prev, newFiltering));
437
+ },
438
+ [currentState.filtering, updateState]
439
+ );
440
+ const handleClearColumnFilter = react.useCallback(
441
+ (columnId) => {
442
+ const newFiltering = clearColumnFilter(currentState.filtering, columnId);
443
+ updateState((prev) => updateFiltering(prev, newFiltering));
444
+ },
445
+ [currentState.filtering, updateState]
446
+ );
447
+ const handleClearAllFilters = react.useCallback(() => {
448
+ const newFiltering = clearAllFilters();
449
+ updateState((prev) => updateFiltering(prev, newFiltering));
450
+ }, [updateState]);
451
+ const toggleColumnVisibility = react.useCallback(
452
+ (columnId) => {
453
+ const currentVisibility = currentState.columnVisibility[columnId] ?? true;
454
+ updateState((prev) => updateColumnVisibility(prev, columnId, !currentVisibility));
455
+ },
456
+ [currentState.columnVisibility, updateState]
457
+ );
458
+ const setColumnVisibility = react.useCallback(
459
+ (columnId, visible) => {
460
+ updateState((prev) => updateColumnVisibility(prev, columnId, visible));
461
+ },
462
+ [updateState]
463
+ );
464
+ const reorderColumns = react.useCallback(
465
+ (columnOrder) => {
466
+ updateState((prev) => updateColumnOrder(prev, columnOrder));
467
+ },
468
+ [updateState]
469
+ );
470
+ return react.useMemo(
471
+ () => ({
472
+ state: currentState,
473
+ columns,
474
+ visibleColumns,
475
+ data,
476
+ filteredData,
477
+ sortedData,
478
+ paginatedData,
479
+ filteredRowCount,
480
+ pageCount,
481
+ pageIndex: currentState.pagination.pageIndex,
482
+ pageSize: currentState.pagination.pageSize,
483
+ hasNextPage: hasNext,
484
+ hasPreviousPage: hasPrev,
485
+ sorting: {
486
+ state: currentState.sorting,
487
+ toggle: handleToggleSort,
488
+ set: handleSetSort,
489
+ clear: handleClearSort
490
+ },
491
+ pagination: {
492
+ state: currentState.pagination,
493
+ nextPage: handleNextPage,
494
+ previousPage: handlePreviousPage,
495
+ goToPage: handleGoToPage,
496
+ setPageSize: handleSetPageSize
497
+ },
498
+ filtering: {
499
+ state: currentState.filtering,
500
+ setGlobalFilter: handleSetGlobalFilter,
501
+ setColumnFilter: handleSetColumnFilter,
502
+ clearColumnFilter: handleClearColumnFilter,
503
+ clearAllFilters: handleClearAllFilters
504
+ },
505
+ columnManagement: {
506
+ toggleVisibility: toggleColumnVisibility,
507
+ setVisibility: setColumnVisibility,
508
+ reorder: reorderColumns
509
+ }
510
+ }),
511
+ [
512
+ currentState,
513
+ columns,
514
+ visibleColumns,
515
+ data,
516
+ filteredData,
517
+ sortedData,
518
+ paginatedData,
519
+ filteredRowCount,
520
+ pageCount,
521
+ hasNext,
522
+ hasPrev,
523
+ handleToggleSort,
524
+ handleSetSort,
525
+ handleClearSort,
526
+ handleNextPage,
527
+ handlePreviousPage,
528
+ handleGoToPage,
529
+ handleSetPageSize,
530
+ handleSetGlobalFilter,
531
+ handleSetColumnFilter,
532
+ handleClearColumnFilter,
533
+ handleClearAllFilters,
534
+ toggleColumnVisibility,
535
+ setColumnVisibility,
536
+ reorderColumns
537
+ ]
538
+ );
539
+ }
540
+
541
+ exports.useDataTable = useDataTable;
542
+ //# sourceMappingURL=react.js.map
543
+ //# sourceMappingURL=react.js.map