@superdangerous/app-framework 4.16.35 → 4.16.36

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.
@@ -1,282 +0,0 @@
1
- import { useState, useCallback, useEffect, useRef } from 'react';
2
-
3
- interface ColumnConfig {
4
- key: string;
5
- minWidth?: number;
6
- maxWidth?: number;
7
- defaultWidth?: number;
8
- }
9
-
10
- interface UseResizableColumnsOptions {
11
- tableId: string;
12
- columns: ColumnConfig[];
13
- storageKey?: string;
14
- }
15
-
16
- interface ResizableColumnState {
17
- widths: Record<string, number>;
18
- isResizing: boolean;
19
- resizingColumn: string | null;
20
- }
21
-
22
- export interface ResizableColumnResult {
23
- widths: Record<string, number>;
24
- isResizing: boolean;
25
- totalWidth: number;
26
- getResizeHandleProps: (columnKey: string) => {
27
- onPointerDown: (e: React.PointerEvent) => void;
28
- onMouseDown: (e: React.MouseEvent) => void;
29
- draggable: boolean;
30
- onDragStart: (e: React.DragEvent) => void;
31
- className: string;
32
- 'data-resizing': boolean;
33
- };
34
- getColumnStyle: (columnKey: string) => React.CSSProperties;
35
- getTableStyle: () => React.CSSProperties;
36
- resetToDefaults: () => void;
37
- }
38
-
39
- const DEFAULT_MIN_WIDTH = 50;
40
- const DEFAULT_WIDTH = 150;
41
- const DRAG_THRESHOLD = 3; // Minimum pixels before resize activates
42
-
43
- export function useResizableColumns({
44
- tableId,
45
- columns,
46
- storageKey,
47
- }: UseResizableColumnsOptions): ResizableColumnResult {
48
- const effectiveStorageKey = storageKey || `table-columns-${tableId}`;
49
-
50
- // Get default widths from column config
51
- const getDefaultWidths = useCallback(() => {
52
- return columns.reduce((acc, col) => {
53
- acc[col.key] = col.defaultWidth || DEFAULT_WIDTH;
54
- return acc;
55
- }, {} as Record<string, number>);
56
- }, [columns]);
57
-
58
- // Initialize widths from localStorage or defaults
59
- const [state, setState] = useState<ResizableColumnState>(() => {
60
- try {
61
- const stored = localStorage.getItem(effectiveStorageKey);
62
- if (stored) {
63
- const parsed = JSON.parse(stored);
64
- // Merge with defaults in case new columns were added
65
- const defaults = getDefaultWidths();
66
- return {
67
- widths: { ...defaults, ...parsed },
68
- isResizing: false,
69
- resizingColumn: null,
70
- };
71
- }
72
- } catch {
73
- // Ignore localStorage errors
74
- }
75
- return {
76
- widths: getDefaultWidths(),
77
- isResizing: false,
78
- resizingColumn: null,
79
- };
80
- });
81
-
82
- // Refs for resize handling - these are stable across renders
83
- const startXRef = useRef<number>(0);
84
- const startWidthRef = useRef<number>(0);
85
- const activeColumnRef = useRef<string | null>(null);
86
- const hasDraggedRef = useRef<boolean>(false);
87
- const columnsRef = useRef(columns);
88
-
89
- // Keep columnsRef up to date
90
- useEffect(() => {
91
- columnsRef.current = columns;
92
- }, [columns]);
93
-
94
- // Get column config by key - uses ref for stable reference
95
- const getColumnConfig = useCallback(
96
- (key: string) => columnsRef.current.find((c) => c.key === key),
97
- []
98
- );
99
-
100
- // Persist widths to localStorage
101
- useEffect(() => {
102
- try {
103
- localStorage.setItem(effectiveStorageKey, JSON.stringify(state.widths));
104
- } catch {
105
- // Ignore localStorage errors
106
- }
107
- }, [state.widths, effectiveStorageKey]);
108
-
109
- // Stable event handlers using refs - these don't change between renders
110
- const handlersRef = useRef<{
111
- onPointerMove: (e: PointerEvent) => void;
112
- onPointerUp: (e: PointerEvent) => void;
113
- } | null>(null);
114
-
115
- // Initialize handlers once
116
- if (!handlersRef.current) {
117
- handlersRef.current = {
118
- onPointerMove: (e: PointerEvent) => {
119
- if (!activeColumnRef.current) return;
120
-
121
- const diff = e.clientX - startXRef.current;
122
-
123
- // Check if we've exceeded the drag threshold
124
- if (!hasDraggedRef.current) {
125
- if (Math.abs(diff) < DRAG_THRESHOLD) {
126
- return; // Not yet dragging
127
- }
128
- // Now we're actually dragging - set the resizing state
129
- hasDraggedRef.current = true;
130
- setState((prev) => ({
131
- ...prev,
132
- isResizing: true,
133
- resizingColumn: activeColumnRef.current,
134
- }));
135
- document.body.style.cursor = 'col-resize';
136
- document.body.style.userSelect = 'none';
137
- }
138
-
139
- const config = getColumnConfig(activeColumnRef.current);
140
- const minWidth = config?.minWidth || DEFAULT_MIN_WIDTH;
141
- const maxWidth = config?.maxWidth;
142
-
143
- let newWidth = Math.max(minWidth, startWidthRef.current + diff);
144
- if (maxWidth) {
145
- newWidth = Math.min(maxWidth, newWidth);
146
- }
147
-
148
- setState((prev) => ({
149
- ...prev,
150
- widths: {
151
- ...prev.widths,
152
- [activeColumnRef.current!]: newWidth,
153
- },
154
- }));
155
- },
156
-
157
- onPointerUp: () => {
158
- activeColumnRef.current = null;
159
- hasDraggedRef.current = false;
160
- setState((prev) => ({
161
- ...prev,
162
- isResizing: false,
163
- resizingColumn: null,
164
- }));
165
-
166
- // Remove listeners using the same stable references
167
- document.removeEventListener('pointermove', handlersRef.current!.onPointerMove);
168
- document.removeEventListener('pointerup', handlersRef.current!.onPointerUp);
169
- document.removeEventListener('pointercancel', handlersRef.current!.onPointerUp);
170
-
171
- // Remove cursor override
172
- document.body.style.cursor = '';
173
- document.body.style.userSelect = '';
174
- },
175
- };
176
- }
177
-
178
- // Cleanup on unmount
179
- useEffect(() => {
180
- return () => {
181
- if (handlersRef.current) {
182
- document.removeEventListener('pointermove', handlersRef.current.onPointerMove);
183
- document.removeEventListener('pointerup', handlersRef.current.onPointerUp);
184
- document.removeEventListener('pointercancel', handlersRef.current.onPointerUp);
185
- }
186
- document.body.style.cursor = '';
187
- document.body.style.userSelect = '';
188
- };
189
- }, []);
190
-
191
- // Ref to access current widths without causing re-renders
192
- const widthsRef = useRef(state.widths);
193
- useEffect(() => {
194
- widthsRef.current = state.widths;
195
- }, [state.widths]);
196
-
197
- // Start resize - uses stable handler refs
198
- const startResize = useCallback(
199
- (columnKey: string, clientX: number) => {
200
- startXRef.current = clientX;
201
- startWidthRef.current = widthsRef.current[columnKey] || DEFAULT_WIDTH;
202
- activeColumnRef.current = columnKey;
203
- hasDraggedRef.current = false;
204
-
205
- // Add listeners using stable references - use pointer events consistently
206
- document.addEventListener('pointermove', handlersRef.current!.onPointerMove);
207
- document.addEventListener('pointerup', handlersRef.current!.onPointerUp);
208
- document.addEventListener('pointercancel', handlersRef.current!.onPointerUp);
209
- },
210
- []
211
- );
212
-
213
- // Get resize handle props for a column
214
- const getResizeHandleProps = useCallback(
215
- (columnKey: string) => ({
216
- onPointerDown: (e: React.PointerEvent) => {
217
- e.preventDefault();
218
- e.stopPropagation();
219
- startResize(columnKey, e.clientX);
220
- },
221
- // Prevent other events from interfering
222
- onMouseDown: (e: React.MouseEvent) => {
223
- e.preventDefault();
224
- e.stopPropagation();
225
- },
226
- draggable: false,
227
- onDragStart: (e: React.DragEvent) => {
228
- e.preventDefault();
229
- e.stopPropagation();
230
- },
231
- className: 'resize-handle',
232
- 'data-resizing': state.resizingColumn === columnKey,
233
- }),
234
- [startResize, state.resizingColumn]
235
- );
236
-
237
- // Get column style with width
238
- const getColumnStyle = useCallback(
239
- (columnKey: string): React.CSSProperties => {
240
- const currentWidth = state.widths[columnKey] || DEFAULT_WIDTH;
241
- const config = getColumnConfig(columnKey);
242
- const minWidth = config?.minWidth || DEFAULT_MIN_WIDTH;
243
- return {
244
- width: currentWidth,
245
- minWidth: minWidth, // Use configured min width, not current width
246
- position: 'relative',
247
- };
248
- },
249
- [state.widths, getColumnConfig]
250
- );
251
-
252
- // Calculate total table width from all columns
253
- const totalWidth = columns.reduce((sum, col) => {
254
- return sum + (state.widths[col.key] || col.defaultWidth || DEFAULT_WIDTH);
255
- }, 0);
256
-
257
- // Get table style
258
- const getTableStyle = useCallback(
259
- (): React.CSSProperties => ({
260
- minWidth: totalWidth,
261
- }),
262
- [totalWidth]
263
- );
264
-
265
- // Reset all columns to default widths
266
- const resetToDefaults = useCallback(() => {
267
- setState((prev) => ({
268
- ...prev,
269
- widths: getDefaultWidths(),
270
- }));
271
- }, [getDefaultWidths]);
272
-
273
- return {
274
- widths: state.widths,
275
- isResizing: state.isResizing,
276
- totalWidth,
277
- getResizeHandleProps,
278
- getColumnStyle,
279
- getTableStyle,
280
- resetToDefaults,
281
- };
282
- }
@@ -1,87 +0,0 @@
1
- /**
2
- * @superdangerous/app-framework/data-table
3
- *
4
- * A comprehensive data table module providing hooks and components
5
- * for building enterprise-grade data tables with:
6
- *
7
- * - Pagination with localStorage persistence
8
- * - Column visibility toggling with locked columns
9
- * - Column resizing with drag handles
10
- * - Column reordering with drag-and-drop
11
- * - Batch selection and actions
12
- * - Search and filter controls
13
- *
14
- * @example
15
- * ```tsx
16
- * import {
17
- * usePagination,
18
- * useColumnVisibility,
19
- * DataTablePage,
20
- * BatchActionsBar
21
- * } from '@superdangerous/app-framework/data-table';
22
- *
23
- * function MyTable() {
24
- * const pagination = usePagination({ data: items, storageKey: 'my-table' });
25
- * const columnVisibility = useColumnVisibility({ columns, storageKey: 'my-table-cols' });
26
- *
27
- * return (
28
- * <DataTablePage
29
- * title="Items"
30
- * pagination={pagination}
31
- * // ...
32
- * >
33
- * <table>...</table>
34
- * </DataTablePage>
35
- * );
36
- * }
37
- * ```
38
- */
39
-
40
- // Hooks
41
- export {
42
- usePagination,
43
- useColumnVisibility,
44
- useResizableColumns,
45
- useColumnOrder,
46
- useColumnDragDrop,
47
- } from './hooks';
48
-
49
- export type {
50
- ColumnConfig,
51
- ColumnVisibilityState,
52
- ResizableColumnResult,
53
- ColumnOrderConfig,
54
- DragState,
55
- } from './hooks';
56
-
57
- // Components
58
- export {
59
- DataTable,
60
- DataTablePage,
61
- PaginationControls,
62
- Pagination,
63
- BatchActionsBar,
64
- ColumnVisibility,
65
- TableFilters,
66
- MultiSelectFilter,
67
- } from './components';
68
-
69
- export type {
70
- DataTableProps,
71
- ColumnDef,
72
- ColumnWidth,
73
- ColumnVisibilityConfig,
74
- HeaderCellProps,
75
- CellProps,
76
- ExternalPaginationState,
77
- ColumnConfigCompat,
78
- ColumnSizeConfig,
79
- DataTablePageProps,
80
- FilterOption,
81
- PaginationControlsProps,
82
- BatchActionsBarProps,
83
- TableFiltersProps,
84
- TableFilterOption,
85
- MultiSelectFilterProps,
86
- MultiSelectOption,
87
- } from './components';