table-minimap 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,396 @@
1
+ /**
2
+ * Table Minimap - A framework-agnostic minimap component for large HTML tables
3
+ *
4
+ * @packageDocumentation
5
+ */
6
+
7
+ /**
8
+ * Cell data for canvas rendering
9
+ */
10
+ export declare interface CellData {
11
+ /** Row index */
12
+ row: number;
13
+ /** Column index */
14
+ col: number;
15
+ /** Whether the cell has content */
16
+ hasContent: boolean;
17
+ /** Content density (0-1) for color intensity */
18
+ density: number;
19
+ }
20
+
21
+ /**
22
+ * Internal representation of a table column
23
+ */
24
+ export declare interface ColumnInfo {
25
+ /** Zero-based index of the column */
26
+ index: number;
27
+ /** Width of the column in pixels */
28
+ width: number;
29
+ /** Percentage width relative to total table width */
30
+ widthPercent: number;
31
+ }
32
+
33
+ /**
34
+ * Scroll state information
35
+ */
36
+ export declare interface ScrollState {
37
+ /** Current horizontal scroll position */
38
+ scrollLeft: number;
39
+ /** Maximum scrollable width */
40
+ scrollWidth: number;
41
+ /** Visible width of the container */
42
+ clientWidth: number;
43
+ /** Viewport width as a ratio (0-1) */
44
+ viewportRatio: number;
45
+ /** Viewport position as a ratio (0-1) */
46
+ positionRatio: number;
47
+ }
48
+
49
+ /**
50
+ * TableMinimap - A framework-agnostic minimap component for large HTML tables
51
+ *
52
+ * @example
53
+ * ```ts
54
+ * import { TableMinimap } from 'table-minimap';
55
+ * import 'table-minimap/style.css';
56
+ *
57
+ * const minimap = new TableMinimap('#my-table');
58
+ *
59
+ * // Or with options
60
+ * const minimap = new TableMinimap('#my-table', {
61
+ * mode: 'canvas',
62
+ * height: 50,
63
+ * position: 'top'
64
+ * });
65
+ *
66
+ * // Cleanup
67
+ * minimap.destroy();
68
+ * ```
69
+ */
70
+ export declare class TableMinimap {
71
+ /** The target table element */
72
+ private readonly table;
73
+ /** Configuration options */
74
+ private readonly options;
75
+ /** The scrollable container (parent of table) */
76
+ private scrollContainer;
77
+ /** Main minimap container element */
78
+ private minimapEl;
79
+ /** Columns container for columns mode */
80
+ private columnsEl;
81
+ /** Canvas element for canvas mode */
82
+ private canvasEl;
83
+ /** Canvas 2D rendering context */
84
+ private canvasCtx;
85
+ /** Viewport indicator element */
86
+ private viewportEl;
87
+ /** Detected column information */
88
+ private columns;
89
+ /** Current scroll state */
90
+ private scrollState;
91
+ /** Current zoom state */
92
+ private zoomState;
93
+ /** Is the viewport being dragged */
94
+ private isDragging;
95
+ /** Is the canvas being panned (when zoomed) */
96
+ private isPanning;
97
+ /** Was just panning (to prevent click after pan) */
98
+ private wasPanning;
99
+ /** Pan start position */
100
+ private panStartX;
101
+ /** Currently hovered column index (-1 = none) */
102
+ private hoveredColumn;
103
+ /** Currently focused/clicked column index (-1 = none) */
104
+ private focusedColumn;
105
+ /** Drag start position */
106
+ private dragStartX;
107
+ /** Drag start scroll position */
108
+ private dragStartScrollLeft;
109
+ /** ResizeObserver instance */
110
+ private resizeObserver;
111
+ /** MutationObserver instance */
112
+ private mutationObserver;
113
+ /** Bound event handlers for cleanup */
114
+ private boundHandlers;
115
+ /** Animation frame ID for throttling */
116
+ private rafId;
117
+ /** Whether the instance has been destroyed */
118
+ private isDestroyed;
119
+ /**
120
+ * Creates a new TableMinimap instance
121
+ *
122
+ * @param selector - CSS selector string or HTMLTableElement
123
+ * @param options - Configuration options
124
+ * @throws Error if table element is not found or invalid
125
+ */
126
+ constructor(selector: TableSelector, options?: TableMinimapOptions);
127
+ /**
128
+ * Resolves the table element from a selector or element
129
+ *
130
+ * @param selector - CSS selector or HTMLTableElement
131
+ * @returns The resolved table element
132
+ * @throws Error if element is not found or not a table
133
+ */
134
+ private resolveTable;
135
+ /**
136
+ * Initializes the minimap
137
+ */
138
+ private init;
139
+ /**
140
+ * Finds the nearest scrollable parent container
141
+ *
142
+ * @returns The scrollable container or the table's parent
143
+ */
144
+ private findScrollContainer;
145
+ /**
146
+ * Detects table columns from thead or first row
147
+ */
148
+ private detectColumns;
149
+ /**
150
+ * Creates the minimap DOM element
151
+ */
152
+ private createMinimapElement;
153
+ /**
154
+ * Creates columns-mode content
155
+ */
156
+ private createColumnsContent;
157
+ /**
158
+ * Creates canvas-mode content
159
+ */
160
+ private createCanvasContent;
161
+ /**
162
+ * Creates the viewport indicator element
163
+ */
164
+ private createViewportIndicator;
165
+ /**
166
+ * Inserts the minimap into the DOM
167
+ */
168
+ private insertMinimap;
169
+ /**
170
+ * Updates the scroll state from the container
171
+ */
172
+ private updateScrollState;
173
+ /**
174
+ * Renders the minimap
175
+ */
176
+ private render;
177
+ /**
178
+ * Canvas metrics for calculations - cached values to avoid repeated computations
179
+ */
180
+ private getCanvasMetrics;
181
+ /**
182
+ * Calculates column index from mouse X position
183
+ */
184
+ private getColumnAtX;
185
+ /**
186
+ * Updates the viewport indicator position and size
187
+ * In canvas mode, the viewport shows the focused (clicked) column
188
+ */
189
+ private updateViewport;
190
+ /**
191
+ * Renders the canvas-mode visualization with table preview
192
+ */
193
+ private renderCanvas;
194
+ /**
195
+ * Renders the visible portion of the table directly onto the canvas
196
+ */
197
+ private renderTableDirect;
198
+ /**
199
+ * Attaches event listeners
200
+ */
201
+ private attachEventListeners;
202
+ /**
203
+ * Handles scroll events on the container
204
+ */
205
+ private onScroll;
206
+ /**
207
+ * Handles click on the minimap to jump to position
208
+ *
209
+ * @param e - Mouse event
210
+ */
211
+ private onMinimapClick;
212
+ /**
213
+ * Handles pointer down on viewport for drag start
214
+ *
215
+ * @param e - Pointer event
216
+ */
217
+ private onPointerDown;
218
+ /**
219
+ * Handles pointer move during drag
220
+ *
221
+ * @param e - Pointer event
222
+ */
223
+ private onPointerMove;
224
+ /**
225
+ * Handles pointer up to end drag
226
+ *
227
+ * @param e - Pointer event
228
+ */
229
+ private onPointerUp;
230
+ /**
231
+ * Handles wheel events for zoom
232
+ *
233
+ * @param e - Wheel event
234
+ */
235
+ private onWheel;
236
+ /** Whether we started a potential pan (waiting to see if it's a click or drag) */
237
+ private isPotentialPan;
238
+ /**
239
+ * Handles pointer down on canvas for drag start (scrolls table when zoomed)
240
+ *
241
+ * @param e - Pointer event
242
+ */
243
+ private onCanvasPointerDown;
244
+ /**
245
+ * Handles mouse move on canvas for column hover highlighting
246
+ */
247
+ private onCanvasMouseMove;
248
+ /**
249
+ * Handles mouse leave on canvas to clear hover state
250
+ */
251
+ private onCanvasMouseLeave;
252
+ /**
253
+ * Sets up ResizeObserver and MutationObserver
254
+ */
255
+ private setupObservers;
256
+ /**
257
+ * Handles resize events
258
+ */
259
+ private onResize;
260
+ /**
261
+ * Handles table mutation events
262
+ */
263
+ private onTableMutation;
264
+ /**
265
+ * Gets the current scroll state
266
+ *
267
+ * @returns Current scroll state
268
+ */
269
+ getScrollState(): ScrollState;
270
+ /**
271
+ * Gets the detected columns
272
+ *
273
+ * @returns Array of column information
274
+ */
275
+ getColumns(): ColumnInfo[];
276
+ /**
277
+ * Scrolls to a specific column
278
+ *
279
+ * @param columnIndex - Zero-based column index
280
+ * @param smooth - Use smooth scrolling
281
+ */
282
+ scrollToColumn(columnIndex: number, smooth?: boolean): void;
283
+ /**
284
+ * Forces a refresh of the minimap
285
+ */
286
+ refresh(): void;
287
+ /**
288
+ * Gets the current zoom state (canvas mode only)
289
+ *
290
+ * @returns Current zoom state
291
+ */
292
+ getZoomState(): ZoomState;
293
+ /**
294
+ * Sets the zoom level programmatically (canvas mode only)
295
+ *
296
+ * @param level - Zoom level (1 = no zoom)
297
+ * @param panX - Optional pan position (0-1)
298
+ */
299
+ setZoom(level: number, panX?: number): void;
300
+ /**
301
+ * Resets zoom to default (shows full table overview)
302
+ */
303
+ resetZoom(): void;
304
+ /**
305
+ * Zooms to a specific column range (canvas mode only)
306
+ *
307
+ * @param startCol - Start column index
308
+ * @param endCol - End column index
309
+ */
310
+ zoomToColumns(startCol: number, endCol: number): void;
311
+ /**
312
+ * Destroys the minimap instance and cleans up resources
313
+ */
314
+ destroy(): void;
315
+ }
316
+
317
+ /**
318
+ * Configuration options for the TableMinimap component
319
+ */
320
+ export declare interface TableMinimapOptions {
321
+ /**
322
+ * Rendering mode for the minimap
323
+ * - "columns": Simple column-based visualization (default)
324
+ * - "canvas": VS Code-like compressed pixel preview
325
+ */
326
+ mode?: 'columns' | 'canvas';
327
+ /**
328
+ * Height of the minimap in pixels
329
+ * @default 40
330
+ */
331
+ height?: number;
332
+ /**
333
+ * Position of the minimap relative to the table
334
+ * - "top": Above the table
335
+ * - "bottom": Below the table
336
+ * - "fixed": Floating overlay at bottom-right corner of the table
337
+ * @default "bottom"
338
+ */
339
+ position?: 'top' | 'bottom' | 'fixed';
340
+ /**
341
+ * Width of the minimap when using position: 'fixed'
342
+ * @default 300
343
+ */
344
+ fixedWidth?: number;
345
+ /**
346
+ * Enable drag navigation on the viewport indicator
347
+ * @default true
348
+ */
349
+ draggable?: boolean;
350
+ /**
351
+ * Show the viewport indicator
352
+ * @default true
353
+ */
354
+ showViewport?: boolean;
355
+ /**
356
+ * Enable zoom functionality in canvas mode (scroll wheel to zoom)
357
+ * @default false
358
+ */
359
+ zoomable?: boolean;
360
+ /**
361
+ * Minimum zoom level (1 = no zoom, showing full table)
362
+ * @default 1
363
+ */
364
+ minZoom?: number;
365
+ /**
366
+ * Maximum zoom level
367
+ * @default 10
368
+ */
369
+ maxZoom?: number;
370
+ /**
371
+ * Zoom speed multiplier (higher = faster zoom)
372
+ * @default 0.1
373
+ */
374
+ zoomSpeed?: number;
375
+ }
376
+
377
+ /**
378
+ * Table element selector type
379
+ */
380
+ export declare type TableSelector = string | HTMLTableElement;
381
+
382
+ /**
383
+ * Zoom state information
384
+ */
385
+ export declare interface ZoomState {
386
+ /** Current zoom level (1 = no zoom) */
387
+ level: number;
388
+ /** Horizontal pan offset (0-1, representing position in table) */
389
+ panX: number;
390
+ /** Whether zoom is at minimum (showing full overview) */
391
+ isMinZoom: boolean;
392
+ /** Whether zoom is at maximum */
393
+ isMaxZoom: boolean;
394
+ }
395
+
396
+ export { }
@@ -0,0 +1,138 @@
1
+ /* ==========================================================================
2
+ Table Minimap - shadcn/ui Theme
3
+
4
+ This stylesheet uses shadcn/ui CSS variables for seamless integration.
5
+ Import this instead of the default style.css when using shadcn/ui.
6
+
7
+ Usage:
8
+ import 'table-minimap/shadcn.css';
9
+ ========================================================================== */
10
+
11
+ /* Map shadcn/ui variables to table-minimap */
12
+ .tm-minimap {
13
+ --tm-background: hsl(var(--muted));
14
+ --tm-border: hsl(var(--border));
15
+ --tm-viewport-color: hsl(var(--primary) / 0.2);
16
+ --tm-viewport-border: hsl(var(--primary));
17
+ --tm-height: 40px;
18
+ --tm-column-color: hsl(var(--muted-foreground) / 0.3);
19
+ --tm-column-gap: 1px;
20
+ --tm-border-radius: var(--radius);
21
+ --tm-canvas-empty: hsl(var(--muted));
22
+ --tm-canvas-filled: hsl(var(--foreground) / 0.7);
23
+ }
24
+
25
+ /* ==========================================================================
26
+ Main Container
27
+ ========================================================================== */
28
+
29
+ .tm-minimap {
30
+ position: relative;
31
+ width: 100%;
32
+ height: var(--tm-height);
33
+ background: var(--tm-background);
34
+ border: 1px solid var(--tm-border);
35
+ border-radius: var(--tm-border-radius);
36
+ box-sizing: border-box;
37
+ overflow: hidden;
38
+ user-select: none;
39
+ -webkit-user-select: none;
40
+ }
41
+
42
+ .tm-minimap--top {
43
+ margin-bottom: 8px;
44
+ }
45
+
46
+ .tm-minimap--bottom {
47
+ margin-top: 8px;
48
+ }
49
+
50
+ /* ==========================================================================
51
+ Columns Mode
52
+ ========================================================================== */
53
+
54
+ .tm-columns {
55
+ display: flex;
56
+ align-items: stretch;
57
+ height: 100%;
58
+ gap: var(--tm-column-gap);
59
+ padding: 4px;
60
+ box-sizing: border-box;
61
+ }
62
+
63
+ .tm-column {
64
+ flex-shrink: 0;
65
+ height: 100%;
66
+ background: var(--tm-column-color);
67
+ border-radius: calc(var(--tm-border-radius) / 2);
68
+ transition: background-color 0.15s ease;
69
+ }
70
+
71
+ .tm-column:hover {
72
+ background: hsl(var(--muted-foreground) / 0.5);
73
+ }
74
+
75
+ /* ==========================================================================
76
+ Canvas Mode
77
+ ========================================================================== */
78
+
79
+ .tm-canvas {
80
+ width: 100%;
81
+ height: 100%;
82
+ display: block;
83
+ }
84
+
85
+ /* ==========================================================================
86
+ Viewport Indicator
87
+ ========================================================================== */
88
+
89
+ .tm-viewport {
90
+ position: absolute;
91
+ top: 0;
92
+ height: 100%;
93
+ background: var(--tm-viewport-color);
94
+ border-left: 2px solid var(--tm-viewport-border);
95
+ border-right: 2px solid var(--tm-viewport-border);
96
+ box-sizing: border-box;
97
+ cursor: grab;
98
+ transition: background-color 0.15s ease;
99
+ z-index: 10;
100
+ }
101
+
102
+ .tm-viewport:hover {
103
+ background: hsl(var(--primary) / 0.3);
104
+ }
105
+
106
+ .tm-viewport--dragging {
107
+ cursor: grabbing;
108
+ background: hsl(var(--primary) / 0.4);
109
+ transition: none;
110
+ }
111
+
112
+ .tm-viewport--disabled {
113
+ cursor: default;
114
+ pointer-events: none;
115
+ }
116
+
117
+ /* ==========================================================================
118
+ Accessibility
119
+ ========================================================================== */
120
+
121
+ .tm-minimap:focus-visible {
122
+ outline: 2px solid hsl(var(--ring));
123
+ outline-offset: 2px;
124
+ }
125
+
126
+ /* Screen reader only content */
127
+ .tm-sr-only {
128
+ position: absolute;
129
+ width: 1px;
130
+ height: 1px;
131
+ padding: 0;
132
+ margin: -1px;
133
+ overflow: hidden;
134
+ clip: rect(0, 0, 0, 0);
135
+ white-space: nowrap;
136
+ border: 0;
137
+ }
138
+
package/dist/style.css ADDED
@@ -0,0 +1 @@
1
+ :root{--tm-background: #e3f2fd;--tm-border: #90caf9;--tm-viewport-color: rgba(25, 118, 210, .3);--tm-viewport-border: #1976d2;--tm-height: 40px;--tm-column-color: #64b5f6;--tm-column-gap: 1px;--tm-border-radius: 4px;--tm-canvas-empty: #bbdefb;--tm-canvas-filled: #1565c0}@media (prefers-color-scheme: dark){:root{--tm-background: #1a237e;--tm-border: #3949ab;--tm-viewport-color: rgba(100, 180, 255, .3);--tm-viewport-border: #64b5f6;--tm-column-color: #3f51b5;--tm-canvas-empty: #283593;--tm-canvas-filled: #90caf9}}.tm-minimap{position:relative;width:100%;height:var(--tm-height);background:var(--tm-background);border:1px solid var(--tm-border);border-radius:var(--tm-border-radius);box-sizing:border-box;overflow:hidden;user-select:none;-webkit-user-select:none;cursor:pointer}.tm-minimap--top{margin-bottom:8px}.tm-minimap--bottom{margin-top:8px}.tm-minimap--fixed{z-index:100;box-shadow:0 4px 12px #00000026;border-radius:8px}.tm-columns{display:flex;align-items:stretch;height:100%;gap:var(--tm-column-gap);padding:4px;box-sizing:border-box}.tm-column{flex-shrink:0;height:100%;background:var(--tm-column-color);border-radius:2px;transition:background-color .15s ease;cursor:pointer}.tm-column:hover{background:color-mix(in srgb,var(--tm-column-color) 80%,black)}.tm-canvas{width:100%;height:100%;display:block;cursor:pointer}.tm-viewport{position:absolute;top:0;height:100%;background:var(--tm-viewport-color);border-left:2px solid var(--tm-viewport-border);border-right:2px solid var(--tm-viewport-border);box-sizing:border-box;cursor:grab;transition:background-color .15s ease;z-index:10}.tm-viewport:hover{background:color-mix(in srgb,var(--tm-viewport-color) 100%,black 10%)}.tm-viewport--dragging{cursor:grabbing;background:color-mix(in srgb,var(--tm-viewport-color) 100%,black 20%);transition:none}.tm-viewport--disabled{cursor:default;pointer-events:none}.tm-minimap:focus-visible{outline:2px solid var(--tm-viewport-border);outline-offset:2px}.tm-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
@@ -0,0 +1,2 @@
1
+ "use strict";var D=Object.defineProperty;var T=(C,t,e)=>t in C?D(C,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):C[t]=e;var o=(C,t,e)=>T(C,typeof t!="symbol"?t+"":t,e);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const z={mode:"columns",height:40,position:"bottom",fixedWidth:300,draggable:!0,showViewport:!0,zoomable:!1,minZoom:1,maxZoom:10,zoomSpeed:.1};class H{constructor(t,e={}){o(this,"table");o(this,"options");o(this,"scrollContainer",null);o(this,"minimapEl",null);o(this,"columnsEl",null);o(this,"canvasEl",null);o(this,"canvasCtx",null);o(this,"viewportEl",null);o(this,"columns",[]);o(this,"scrollState",{scrollLeft:0,scrollWidth:0,clientWidth:0,viewportRatio:1,positionRatio:0});o(this,"zoomState",{level:1,panX:0,isMinZoom:!0,isMaxZoom:!1});o(this,"isDragging",!1);o(this,"isPanning",!1);o(this,"wasPanning",!1);o(this,"panStartX",0);o(this,"hoveredColumn",-1);o(this,"focusedColumn",-1);o(this,"dragStartX",0);o(this,"dragStartScrollLeft",0);o(this,"resizeObserver",null);o(this,"mutationObserver",null);o(this,"boundHandlers");o(this,"rafId",null);o(this,"isDestroyed",!1);o(this,"isPotentialPan",!1);this.table=this.resolveTable(t),this.options={...z,...e},this.boundHandlers={onScroll:this.onScroll.bind(this),onPointerDown:this.onPointerDown.bind(this),onPointerMove:this.onPointerMove.bind(this),onPointerUp:this.onPointerUp.bind(this),onMinimapClick:this.onMinimapClick.bind(this),onWheel:this.onWheel.bind(this),onCanvasPointerDown:this.onCanvasPointerDown.bind(this),onCanvasMouseMove:this.onCanvasMouseMove.bind(this),onCanvasMouseLeave:this.onCanvasMouseLeave.bind(this)},this.init()}resolveTable(t){let e;if(typeof t=="string"){if(e=document.querySelector(t),!e)throw new Error(`TableMinimap: No element found for selector "${t}"`)}else if(t instanceof HTMLTableElement)e=t;else throw new Error("TableMinimap: Selector must be a CSS selector string or an HTMLTableElement");if(e.tagName!=="TABLE")throw new Error(`TableMinimap: Element must be a <table>, got <${e.tagName.toLowerCase()}>`);return e}init(){this.scrollContainer=this.findScrollContainer(),this.detectColumns(),this.createMinimapElement(),this.updateScrollState(),this.render(),this.attachEventListeners(),this.setupObservers()}findScrollContainer(){let t=this.table.parentElement;for(;t;){const e=getComputedStyle(t),i=e.overflowX,s=e.overflow;if(i==="auto"||i==="scroll"||s==="auto"||s==="scroll"||t.scrollWidth>t.clientWidth)return t;t=t.parentElement}return this.table.parentElement||document.body}detectColumns(){this.columns=[];const t=this.table.querySelectorAll("thead th, thead td");let e;if(t.length>0)e=t;else{const s=this.table.querySelector("tr");s?e=s.querySelectorAll("th, td"):e=[]}const i=this.table.offsetWidth||1;Array.from(e).forEach(s=>{const a=s.colSpan||1,n=s.offsetWidth;for(let l=0;l<a;l++){const r=n/a;this.columns.push({index:this.columns.length,width:r,widthPercent:r/i*100})}}),this.columns.length===0&&this.columns.push({index:0,width:i,widthPercent:100})}createMinimapElement(){this.minimapEl=document.createElement("div"),this.minimapEl.className=`tm-minimap tm-minimap--${this.options.position}`,this.minimapEl.style.height=`${this.options.height}px`,this.options.position==="fixed"&&(this.minimapEl.style.width=`${this.options.fixedWidth}px`),this.minimapEl.setAttribute("role","slider"),this.minimapEl.setAttribute("aria-label","Table minimap navigation"),this.minimapEl.setAttribute("aria-valuemin","0"),this.minimapEl.setAttribute("aria-valuemax","100"),this.minimapEl.setAttribute("tabindex","0"),this.options.mode==="canvas"?this.createCanvasContent():this.createColumnsContent(),this.options.showViewport&&this.createViewportIndicator(),this.insertMinimap()}createColumnsContent(){this.minimapEl&&(this.columnsEl=document.createElement("div"),this.columnsEl.className="tm-columns",this.columns.forEach(t=>{const e=document.createElement("div");e.className="tm-column",e.style.width=`${t.widthPercent}%`,this.columnsEl.appendChild(e)}),this.minimapEl.appendChild(this.columnsEl))}createCanvasContent(){this.minimapEl&&(this.canvasEl=document.createElement("canvas"),this.canvasEl.className="tm-canvas",this.canvasCtx=this.canvasEl.getContext("2d"),this.minimapEl.appendChild(this.canvasEl))}createViewportIndicator(){this.minimapEl&&(this.viewportEl=document.createElement("div"),this.viewportEl.className="tm-viewport",this.options.draggable||this.viewportEl.classList.add("tm-viewport--disabled"),this.minimapEl.appendChild(this.viewportEl))}insertMinimap(){if(!this.minimapEl||!this.scrollContainer)return;const t=this.scrollContainer.parentElement;if(this.options.position==="fixed"){if(t){getComputedStyle(t).position==="static"&&(t.style.position="relative");const i=this.scrollContainer.nextSibling;i?t.insertBefore(this.minimapEl,i):t.appendChild(this.minimapEl),this.minimapEl.style.position="absolute",this.minimapEl.style.bottom="12px",this.minimapEl.style.right="12px",this.minimapEl.style.marginTop="0"}}else if(this.options.position==="top")t?t.insertBefore(this.minimapEl,this.scrollContainer):this.scrollContainer.insertBefore(this.minimapEl,this.scrollContainer.firstChild);else if(t){const e=this.scrollContainer.nextSibling;e?t.insertBefore(this.minimapEl,e):t.appendChild(this.minimapEl)}else this.scrollContainer.appendChild(this.minimapEl)}updateScrollState(){if(!this.scrollContainer)return;const{scrollLeft:t,scrollWidth:e,clientWidth:i}=this.scrollContainer;this.scrollState={scrollLeft:t,scrollWidth:e,clientWidth:i,viewportRatio:i/Math.max(e,1),positionRatio:t/Math.max(e-i,1)},this.scrollState.viewportRatio=Math.min(1,Math.max(0,this.scrollState.viewportRatio)),this.scrollState.positionRatio=Math.min(1,Math.max(0,this.scrollState.positionRatio)),this.minimapEl&&this.minimapEl.setAttribute("aria-valuenow",String(Math.round(this.scrollState.positionRatio*100)))}render(){this.isDestroyed||(this.updateViewport(),this.options.mode==="canvas"&&this.renderCanvas())}getCanvasMetrics(){var c;const t=((c=this.minimapEl)==null?void 0:c.offsetWidth)??0,e=this.zoomState.level,i=this.columns.length,s=1/e,a=i*s,n=a>0?t/a:0;let l=0;if(e>1&&this.scrollContainer){const{scrollLeft:d,scrollWidth:b,clientWidth:g}=this.scrollContainer,h=Math.max(b-g,1);l=d/h*(1-s)}const r=l*i,u=Math.floor(r),p=Math.min(Math.ceil(r+a)+1,i),v=-(r-u)*n;return{width:t,zoom:e,numCols:i,visibleRatio:s,visibleCols:a,cellWidth:n,panX:l,startColFloat:r,startCol:u,endCol:p,xOffset:v}}getColumnAtX(t){const{width:e,numCols:i,panX:s,visibleRatio:a}=this.getCanvasMetrics();if(i===0||e===0)return-1;const n=t/e,l=s+n*a,r=Math.floor(l*i);return Math.max(0,Math.min(i-1,r))}updateViewport(){if(!this.viewportEl||!this.minimapEl)return;if(this.options.mode==="canvas"){if(this.focusedColumn>=0&&this.columns.length>0){const{cellWidth:a,startColFloat:n}=this.getCanvasMetrics(),l=(this.focusedColumn-n)*a;this.viewportEl.style.cssText=`width:${a}px;left:${l}px;display:block`}else this.viewportEl.style.display="none";return}const t=this.minimapEl.offsetWidth,e=Math.max(t*this.scrollState.viewportRatio,20),s=(t-e)*this.scrollState.positionRatio;this.viewportEl.style.cssText=`width:${e}px;left:${s}px;display:block`}renderCanvas(){if(!this.canvasEl||!this.canvasCtx||!this.minimapEl)return;const t=this.getCanvasMetrics(),e=this.options.height,i=window.devicePixelRatio||1;this.canvasEl.width=t.width*i,this.canvasEl.height=e*i,this.canvasEl.style.width=`${t.width}px`,this.canvasEl.style.height=`${e}px`,this.canvasCtx.scale(i,i),this.renderTableDirect(t,e)}renderTableDirect(t,e){var P,y,L,W;if(!this.canvasCtx)return;const i=this.canvasCtx,{width:s,numCols:a,cellWidth:n,startCol:l,endCol:r,xOffset:u}=t,p=Array.from(this.table.querySelectorAll("tr")),v=p.length;if(v===0||a===0)return;const c=Math.min(e*.15,30),d=(e-c)/v,b=Math.min(d*.6,n*.15,14),g=Math.min(c*.6,n*.15,14),h={bg:"#ffffff",headerBg:"#f1f5f9",border:"#e2e8f0",text:"#334155",headerText:"#1e293b",altRow:"#f8fafc",hoverFill:"rgba(59, 130, 246, 0.08)",hoverStroke:"rgba(59, 130, 246, 0.3)"};i.fillStyle=h.bg,i.fillRect(0,0,s,e),i.fillStyle=h.headerBg,i.fillRect(0,0,s,c);const w=this.table.querySelector("thead tr")||p[0],x=w?Array.from(w.querySelectorAll("th, td")):[];i.font=`bold ${g}px system-ui, sans-serif`,i.textBaseline="middle";for(let m=l;m<r;m++){const f=u+(m-l)*n;if(f+n<0||f>s)continue;i.strokeStyle=h.border,i.lineWidth=1,i.strokeRect(f,0,n,c);const E=((y=(P=x[m])==null?void 0:P.textContent)==null?void 0:y.trim())||`Col ${m+1}`;i.fillStyle=h.headerText,i.save(),i.beginPath(),i.rect(f+2,0,n-4,c),i.clip(),i.fillText(E,f+4,c/2),i.restore()}i.font=`${b}px system-ui, sans-serif`;for(let m=0;m<p.length;m++){const f=p[m];if(f.closest("thead"))continue;const E=c+m*d;if(E+d<0||E>e)continue;m%2===1&&(i.fillStyle=h.altRow,i.fillRect(0,E,s,d));const X=Array.from(f.querySelectorAll("th, td"));for(let M=l;M<r;M++){const S=u+(M-l)*n;if(S+n<0||S>s)continue;i.strokeStyle=h.border,i.lineWidth=.5,i.strokeRect(S,E,n,d);const R=(W=(L=X[M])==null?void 0:L.textContent)==null?void 0:W.trim();R&&(i.fillStyle=h.text,i.save(),i.beginPath(),i.rect(S+2,E,n-4,d),i.clip(),i.fillText(R,S+4,E+d/2),i.restore())}}if(this.hoveredColumn>=l&&this.hoveredColumn<r){const m=u+(this.hoveredColumn-l)*n;i.fillStyle=h.hoverFill,i.fillRect(m,0,n,e),i.strokeStyle=h.hoverStroke,i.lineWidth=1,i.strokeRect(m,0,n,e)}}attachEventListeners(){!this.scrollContainer||!this.minimapEl||(this.scrollContainer.addEventListener("scroll",this.boundHandlers.onScroll,{passive:!0}),this.minimapEl.addEventListener("click",this.boundHandlers.onMinimapClick),this.options.draggable&&this.viewportEl&&this.viewportEl.addEventListener("pointerdown",this.boundHandlers.onPointerDown),this.options.zoomable&&this.options.mode==="canvas"&&this.canvasEl&&(this.canvasEl.addEventListener("wheel",this.boundHandlers.onWheel,{passive:!1}),this.canvasEl.addEventListener("pointerdown",this.boundHandlers.onCanvasPointerDown),this.canvasEl.style.cursor=this.zoomState.level>1?"grab":"pointer",this.viewportEl&&this.viewportEl.addEventListener("wheel",this.boundHandlers.onWheel,{passive:!1})),this.options.mode==="canvas"&&this.canvasEl&&(this.canvasEl.addEventListener("mousemove",this.boundHandlers.onCanvasMouseMove),this.canvasEl.addEventListener("mouseleave",this.boundHandlers.onCanvasMouseLeave)),document.addEventListener("pointermove",this.boundHandlers.onPointerMove),document.addEventListener("pointerup",this.boundHandlers.onPointerUp))}onScroll(){this.isDragging||this.rafId===null&&(this.rafId=requestAnimationFrame(()=>{this.updateScrollState(),this.updateViewport(),this.options.mode==="canvas"&&this.render(),this.rafId=null}))}onMinimapClick(t){if(!this.minimapEl||!this.scrollContainer||this.isDragging||this.isPanning||this.wasPanning||t.target===this.viewportEl)return;const{scrollWidth:e,clientWidth:i}=this.scrollContainer,s=this.columns.length;if(this.hoveredColumn>=0&&s>0){this.focusedColumn=this.hoveredColumn;const b=e/s,h=(this.hoveredColumn+.5)*b-i/2,w=e-i,x=Math.max(0,Math.min(w,h));this.scrollContainer.scrollTo({left:x,behavior:"smooth"}),this.updateViewport();return}const a=this.minimapEl.getBoundingClientRect(),l=(t.clientX-a.left)/a.width,r=this.zoomState.level;let u;if(r>1){const g=this.scrollContainer.scrollLeft/Math.max(e-i,1),h=1/r;u=g*(1-h)+l*h}else u=l;const v=u*e-i/2,c=e-i,d=Math.max(0,Math.min(c,v));this.scrollContainer.scrollTo({left:d,behavior:"smooth"})}onPointerDown(t){!this.viewportEl||!this.scrollContainer||(t.preventDefault(),t.stopPropagation(),this.isDragging=!0,this.dragStartX=t.clientX,this.dragStartScrollLeft=this.scrollContainer.scrollLeft,this.viewportEl.classList.add("tm-viewport--dragging"),this.viewportEl.setPointerCapture(t.pointerId))}onPointerMove(t){if(this.isPotentialPan&&!this.isPanning&&this.canvasEl&&this.zoomState.level>1&&Math.abs(t.clientX-this.panStartX)>3&&(this.isPanning=!0,this.canvasEl.style.cursor="grabbing"),this.isPanning&&this.canvasEl&&this.minimapEl&&this.scrollContainer){t.preventDefault();const l=t.clientX-this.panStartX,r=this.minimapEl.offsetWidth,{scrollWidth:u,clientWidth:p}=this.scrollContainer,v=u-p,c=l/r*v*this.zoomState.level,d=this.dragStartScrollLeft+c;this.scrollContainer.scrollLeft=Math.max(0,Math.min(v,d)),this.updateScrollState(),this.updateViewport(),this.render();return}if(!this.isDragging||!this.minimapEl||!this.scrollContainer)return;t.preventDefault();const e=t.clientX-this.dragStartX,i=this.minimapEl.offsetWidth,s=this.scrollContainer.scrollWidth-this.scrollContainer.clientWidth,a=e/i*s,n=this.dragStartScrollLeft+a;this.scrollContainer.scrollLeft=Math.max(0,Math.min(s,n)),this.updateScrollState(),this.updateViewport()}onPointerUp(t){if(this.isPotentialPan&&!this.isPanning&&this.canvasEl&&this.minimapEl){this.isPotentialPan=!1,this.canvasEl.hasPointerCapture(t.pointerId)&&this.canvasEl.releasePointerCapture(t.pointerId);const e=new MouseEvent("click",{clientX:t.clientX,clientY:t.clientY,bubbles:!0});this.minimapEl.dispatchEvent(e);return}this.isPotentialPan=!1,this.isPanning&&this.canvasEl&&(this.isPanning=!1,this.wasPanning=!0,this.canvasEl.hasPointerCapture(t.pointerId)&&this.canvasEl.releasePointerCapture(t.pointerId),this.canvasEl.style.cursor=this.zoomState.level>1?"grab":"pointer",setTimeout(()=>{this.wasPanning=!1},100)),this.isDragging&&(this.isDragging=!1,this.viewportEl&&(this.viewportEl.classList.remove("tm-viewport--dragging"),this.viewportEl.releasePointerCapture(t.pointerId)))}onWheel(t){if(!this.options.zoomable||this.options.mode!=="canvas")return;t.preventDefault();const e=-t.deltaY*this.options.zoomSpeed,i=Math.max(this.options.minZoom,Math.min(this.options.maxZoom,this.zoomState.level+e));this.zoomState={level:i,panX:0,isMinZoom:i<=this.options.minZoom,isMaxZoom:i>=this.options.maxZoom},this.render()}onCanvasPointerDown(t){!this.canvasEl||!this.scrollContainer||(this.isPotentialPan=!0,this.panStartX=t.clientX,this.dragStartScrollLeft=this.scrollContainer.scrollLeft,this.zoomState.level>1&&(t.preventDefault(),this.canvasEl.setPointerCapture(t.pointerId)))}onCanvasMouseMove(t){if(!this.canvasEl||this.isPanning)return;const e=this.canvasEl.getBoundingClientRect(),i=this.getColumnAtX(t.clientX-e.left);i!==this.hoveredColumn&&(this.hoveredColumn=i,this.canvasEl.style.cursor=i>=0?"pointer":this.zoomState.level>1?"grab":"default",this.render())}onCanvasMouseLeave(){this.hoveredColumn!==-1&&(this.hoveredColumn=-1,this.canvasEl&&(this.canvasEl.style.cursor=this.zoomState.level>1?"grab":"default"),this.render())}setupObservers(){this.resizeObserver=new ResizeObserver(()=>{this.onResize()}),this.scrollContainer&&this.resizeObserver.observe(this.scrollContainer),this.resizeObserver.observe(this.table),this.mutationObserver=new MutationObserver(t=>{t.some(i=>i.type==="childList"||i.attributeName==="colspan")&&this.onTableMutation()}),this.mutationObserver.observe(this.table,{childList:!0,subtree:!0,attributes:!0,attributeFilter:["colspan"]})}onResize(){this.isDestroyed||(this.rafId!==null&&cancelAnimationFrame(this.rafId),this.rafId=requestAnimationFrame(()=>{this.detectColumns(),this.updateScrollState(),this.render(),this.options.mode==="columns"&&this.columnsEl&&this.minimapEl&&(this.columnsEl.innerHTML="",this.columns.forEach(t=>{const e=document.createElement("div");e.className="tm-column",e.style.width=`${t.widthPercent}%`,this.columnsEl.appendChild(e)})),this.rafId=null}))}onTableMutation(){this.isDestroyed||(this.detectColumns(),this.updateScrollState(),this.render(),this.options.mode==="columns"&&this.columnsEl&&(this.columnsEl.innerHTML="",this.columns.forEach(t=>{const e=document.createElement("div");e.className="tm-column",e.style.width=`${t.widthPercent}%`,this.columnsEl.appendChild(e)})))}getScrollState(){return{...this.scrollState}}getColumns(){return[...this.columns]}scrollToColumn(t,e=!0){if(!this.scrollContainer||t<0||t>=this.columns.length)return;const s=this.columns.slice(0,t).reduce((a,n)=>a+n.width,0);this.scrollContainer.scrollTo({left:s,behavior:e?"smooth":"auto"})}refresh(){this.isDestroyed||(this.scrollContainer=this.findScrollContainer(),this.detectColumns(),this.updateScrollState(),this.render(),this.options.mode==="columns"&&this.columnsEl&&(this.columnsEl.innerHTML="",this.columns.forEach(t=>{const e=document.createElement("div");e.className="tm-column",e.style.width=`${t.widthPercent}%`,this.columnsEl.appendChild(e)})))}getZoomState(){return{...this.zoomState}}setZoom(t,e){if(this.isDestroyed||this.options.mode!=="canvas")return;const i=Math.max(this.options.minZoom,Math.min(this.options.maxZoom,t)),a=1-1/i;let n=e!==void 0?e:this.zoomState.panX;n=Math.max(0,Math.min(a,n)),this.zoomState={level:i,panX:i>1?n:0,isMinZoom:i<=this.options.minZoom,isMaxZoom:i>=this.options.maxZoom},this.render()}resetZoom(){this.setZoom(1,0)}zoomToColumns(t,e){if(this.isDestroyed||this.options.mode!=="canvas")return;const i=this.columns.length;if(i===0)return;const s=Math.max(0,Math.min(i-1,t)),n=Math.max(s+1,Math.min(i,e))-s,l=i/n,r=s/i;this.setZoom(l,r)}destroy(){this.isDestroyed||(this.isDestroyed=!0,this.rafId!==null&&(cancelAnimationFrame(this.rafId),this.rafId=null),this.scrollContainer&&this.scrollContainer.removeEventListener("scroll",this.boundHandlers.onScroll),this.minimapEl&&this.minimapEl.removeEventListener("click",this.boundHandlers.onMinimapClick),this.viewportEl&&(this.viewportEl.removeEventListener("pointerdown",this.boundHandlers.onPointerDown),this.viewportEl.removeEventListener("wheel",this.boundHandlers.onWheel)),this.canvasEl&&(this.canvasEl.removeEventListener("wheel",this.boundHandlers.onWheel),this.canvasEl.removeEventListener("pointerdown",this.boundHandlers.onCanvasPointerDown),this.canvasEl.removeEventListener("mousemove",this.boundHandlers.onCanvasMouseMove),this.canvasEl.removeEventListener("mouseleave",this.boundHandlers.onCanvasMouseLeave)),document.removeEventListener("pointermove",this.boundHandlers.onPointerMove),document.removeEventListener("pointerup",this.boundHandlers.onPointerUp),this.resizeObserver&&(this.resizeObserver.disconnect(),this.resizeObserver=null),this.mutationObserver&&(this.mutationObserver.disconnect(),this.mutationObserver=null),this.minimapEl&&this.minimapEl.parentNode&&this.minimapEl.parentNode.removeChild(this.minimapEl),this.minimapEl=null,this.columnsEl=null,this.canvasEl=null,this.canvasCtx=null,this.viewportEl=null,this.scrollContainer=null,this.columns=[])}}exports.TableMinimap=H;
2
+ //# sourceMappingURL=table-minimap.cjs.map