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.
- package/README.md +668 -0
- package/dist/index.d.ts +396 -0
- package/dist/shadcn.css +138 -0
- package/dist/style.css +1 -0
- package/dist/table-minimap.cjs +2 -0
- package/dist/table-minimap.cjs.map +1 -0
- package/dist/table-minimap.js +626 -0
- package/dist/table-minimap.js.map +1 -0
- package/package.json +63 -0
|
@@ -0,0 +1,626 @@
|
|
|
1
|
+
var D = Object.defineProperty;
|
|
2
|
+
var z = (C, t, e) => t in C ? D(C, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : C[t] = e;
|
|
3
|
+
var o = (C, t, e) => z(C, typeof t != "symbol" ? t + "" : t, e);
|
|
4
|
+
const T = {
|
|
5
|
+
mode: "columns",
|
|
6
|
+
height: 40,
|
|
7
|
+
position: "bottom",
|
|
8
|
+
fixedWidth: 300,
|
|
9
|
+
draggable: !0,
|
|
10
|
+
showViewport: !0,
|
|
11
|
+
zoomable: !1,
|
|
12
|
+
minZoom: 1,
|
|
13
|
+
maxZoom: 10,
|
|
14
|
+
zoomSpeed: 0.1
|
|
15
|
+
};
|
|
16
|
+
class A {
|
|
17
|
+
/**
|
|
18
|
+
* Creates a new TableMinimap instance
|
|
19
|
+
*
|
|
20
|
+
* @param selector - CSS selector string or HTMLTableElement
|
|
21
|
+
* @param options - Configuration options
|
|
22
|
+
* @throws Error if table element is not found or invalid
|
|
23
|
+
*/
|
|
24
|
+
constructor(t, e = {}) {
|
|
25
|
+
/** The target table element */
|
|
26
|
+
o(this, "table");
|
|
27
|
+
/** Configuration options */
|
|
28
|
+
o(this, "options");
|
|
29
|
+
/** The scrollable container (parent of table) */
|
|
30
|
+
o(this, "scrollContainer", null);
|
|
31
|
+
/** Main minimap container element */
|
|
32
|
+
o(this, "minimapEl", null);
|
|
33
|
+
/** Columns container for columns mode */
|
|
34
|
+
o(this, "columnsEl", null);
|
|
35
|
+
/** Canvas element for canvas mode */
|
|
36
|
+
o(this, "canvasEl", null);
|
|
37
|
+
/** Canvas 2D rendering context */
|
|
38
|
+
o(this, "canvasCtx", null);
|
|
39
|
+
/** Viewport indicator element */
|
|
40
|
+
o(this, "viewportEl", null);
|
|
41
|
+
/** Detected column information */
|
|
42
|
+
o(this, "columns", []);
|
|
43
|
+
/** Current scroll state */
|
|
44
|
+
o(this, "scrollState", {
|
|
45
|
+
scrollLeft: 0,
|
|
46
|
+
scrollWidth: 0,
|
|
47
|
+
clientWidth: 0,
|
|
48
|
+
viewportRatio: 1,
|
|
49
|
+
positionRatio: 0
|
|
50
|
+
});
|
|
51
|
+
/** Current zoom state */
|
|
52
|
+
o(this, "zoomState", {
|
|
53
|
+
level: 1,
|
|
54
|
+
panX: 0,
|
|
55
|
+
isMinZoom: !0,
|
|
56
|
+
isMaxZoom: !1
|
|
57
|
+
});
|
|
58
|
+
/** Is the viewport being dragged */
|
|
59
|
+
o(this, "isDragging", !1);
|
|
60
|
+
/** Is the canvas being panned (when zoomed) */
|
|
61
|
+
o(this, "isPanning", !1);
|
|
62
|
+
/** Was just panning (to prevent click after pan) */
|
|
63
|
+
o(this, "wasPanning", !1);
|
|
64
|
+
/** Pan start position */
|
|
65
|
+
o(this, "panStartX", 0);
|
|
66
|
+
/** Currently hovered column index (-1 = none) */
|
|
67
|
+
o(this, "hoveredColumn", -1);
|
|
68
|
+
/** Currently focused/clicked column index (-1 = none) */
|
|
69
|
+
o(this, "focusedColumn", -1);
|
|
70
|
+
/** Drag start position */
|
|
71
|
+
o(this, "dragStartX", 0);
|
|
72
|
+
/** Drag start scroll position */
|
|
73
|
+
o(this, "dragStartScrollLeft", 0);
|
|
74
|
+
/** ResizeObserver instance */
|
|
75
|
+
o(this, "resizeObserver", null);
|
|
76
|
+
/** MutationObserver instance */
|
|
77
|
+
o(this, "mutationObserver", null);
|
|
78
|
+
/** Bound event handlers for cleanup */
|
|
79
|
+
o(this, "boundHandlers");
|
|
80
|
+
/** Animation frame ID for throttling */
|
|
81
|
+
o(this, "rafId", null);
|
|
82
|
+
/** Whether the instance has been destroyed */
|
|
83
|
+
o(this, "isDestroyed", !1);
|
|
84
|
+
/** Whether we started a potential pan (waiting to see if it's a click or drag) */
|
|
85
|
+
o(this, "isPotentialPan", !1);
|
|
86
|
+
this.table = this.resolveTable(t), this.options = { ...T, ...e }, this.boundHandlers = {
|
|
87
|
+
onScroll: this.onScroll.bind(this),
|
|
88
|
+
onPointerDown: this.onPointerDown.bind(this),
|
|
89
|
+
onPointerMove: this.onPointerMove.bind(this),
|
|
90
|
+
onPointerUp: this.onPointerUp.bind(this),
|
|
91
|
+
onMinimapClick: this.onMinimapClick.bind(this),
|
|
92
|
+
onWheel: this.onWheel.bind(this),
|
|
93
|
+
onCanvasPointerDown: this.onCanvasPointerDown.bind(this),
|
|
94
|
+
onCanvasMouseMove: this.onCanvasMouseMove.bind(this),
|
|
95
|
+
onCanvasMouseLeave: this.onCanvasMouseLeave.bind(this)
|
|
96
|
+
}, this.init();
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Resolves the table element from a selector or element
|
|
100
|
+
*
|
|
101
|
+
* @param selector - CSS selector or HTMLTableElement
|
|
102
|
+
* @returns The resolved table element
|
|
103
|
+
* @throws Error if element is not found or not a table
|
|
104
|
+
*/
|
|
105
|
+
resolveTable(t) {
|
|
106
|
+
let e;
|
|
107
|
+
if (typeof t == "string") {
|
|
108
|
+
if (e = document.querySelector(t), !e)
|
|
109
|
+
throw new Error(
|
|
110
|
+
`TableMinimap: No element found for selector "${t}"`
|
|
111
|
+
);
|
|
112
|
+
} else if (t instanceof HTMLTableElement)
|
|
113
|
+
e = t;
|
|
114
|
+
else
|
|
115
|
+
throw new Error(
|
|
116
|
+
"TableMinimap: Selector must be a CSS selector string or an HTMLTableElement"
|
|
117
|
+
);
|
|
118
|
+
if (e.tagName !== "TABLE")
|
|
119
|
+
throw new Error(
|
|
120
|
+
`TableMinimap: Element must be a <table>, got <${e.tagName.toLowerCase()}>`
|
|
121
|
+
);
|
|
122
|
+
return e;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Initializes the minimap
|
|
126
|
+
*/
|
|
127
|
+
init() {
|
|
128
|
+
this.scrollContainer = this.findScrollContainer(), this.detectColumns(), this.createMinimapElement(), this.updateScrollState(), this.render(), this.attachEventListeners(), this.setupObservers();
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Finds the nearest scrollable parent container
|
|
132
|
+
*
|
|
133
|
+
* @returns The scrollable container or the table's parent
|
|
134
|
+
*/
|
|
135
|
+
findScrollContainer() {
|
|
136
|
+
let t = this.table.parentElement;
|
|
137
|
+
for (; t; ) {
|
|
138
|
+
const e = getComputedStyle(t), i = e.overflowX, s = e.overflow;
|
|
139
|
+
if (i === "auto" || i === "scroll" || s === "auto" || s === "scroll" || t.scrollWidth > t.clientWidth)
|
|
140
|
+
return t;
|
|
141
|
+
t = t.parentElement;
|
|
142
|
+
}
|
|
143
|
+
return this.table.parentElement || document.body;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Detects table columns from thead or first row
|
|
147
|
+
*/
|
|
148
|
+
detectColumns() {
|
|
149
|
+
this.columns = [];
|
|
150
|
+
const t = this.table.querySelectorAll(
|
|
151
|
+
"thead th, thead td"
|
|
152
|
+
);
|
|
153
|
+
let e;
|
|
154
|
+
if (t.length > 0)
|
|
155
|
+
e = t;
|
|
156
|
+
else {
|
|
157
|
+
const s = this.table.querySelector("tr");
|
|
158
|
+
s ? e = s.querySelectorAll("th, td") : e = [];
|
|
159
|
+
}
|
|
160
|
+
const i = this.table.offsetWidth || 1;
|
|
161
|
+
Array.from(e).forEach((s) => {
|
|
162
|
+
const a = s.colSpan || 1, n = s.offsetWidth;
|
|
163
|
+
for (let l = 0; l < a; l++) {
|
|
164
|
+
const r = n / a;
|
|
165
|
+
this.columns.push({
|
|
166
|
+
index: this.columns.length,
|
|
167
|
+
width: r,
|
|
168
|
+
widthPercent: r / i * 100
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}), this.columns.length === 0 && this.columns.push({
|
|
172
|
+
index: 0,
|
|
173
|
+
width: i,
|
|
174
|
+
widthPercent: 100
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Creates the minimap DOM element
|
|
179
|
+
*/
|
|
180
|
+
createMinimapElement() {
|
|
181
|
+
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();
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Creates columns-mode content
|
|
185
|
+
*/
|
|
186
|
+
createColumnsContent() {
|
|
187
|
+
this.minimapEl && (this.columnsEl = document.createElement("div"), this.columnsEl.className = "tm-columns", this.columns.forEach((t) => {
|
|
188
|
+
const e = document.createElement("div");
|
|
189
|
+
e.className = "tm-column", e.style.width = `${t.widthPercent}%`, this.columnsEl.appendChild(e);
|
|
190
|
+
}), this.minimapEl.appendChild(this.columnsEl));
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Creates canvas-mode content
|
|
194
|
+
*/
|
|
195
|
+
createCanvasContent() {
|
|
196
|
+
this.minimapEl && (this.canvasEl = document.createElement("canvas"), this.canvasEl.className = "tm-canvas", this.canvasCtx = this.canvasEl.getContext("2d"), this.minimapEl.appendChild(this.canvasEl));
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Creates the viewport indicator element
|
|
200
|
+
*/
|
|
201
|
+
createViewportIndicator() {
|
|
202
|
+
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));
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Inserts the minimap into the DOM
|
|
206
|
+
*/
|
|
207
|
+
insertMinimap() {
|
|
208
|
+
if (!this.minimapEl || !this.scrollContainer) return;
|
|
209
|
+
const t = this.scrollContainer.parentElement;
|
|
210
|
+
if (this.options.position === "fixed") {
|
|
211
|
+
if (t) {
|
|
212
|
+
getComputedStyle(t).position === "static" && (t.style.position = "relative");
|
|
213
|
+
const i = this.scrollContainer.nextSibling;
|
|
214
|
+
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";
|
|
215
|
+
}
|
|
216
|
+
} else if (this.options.position === "top")
|
|
217
|
+
t ? t.insertBefore(this.minimapEl, this.scrollContainer) : this.scrollContainer.insertBefore(this.minimapEl, this.scrollContainer.firstChild);
|
|
218
|
+
else if (t) {
|
|
219
|
+
const e = this.scrollContainer.nextSibling;
|
|
220
|
+
e ? t.insertBefore(this.minimapEl, e) : t.appendChild(this.minimapEl);
|
|
221
|
+
} else
|
|
222
|
+
this.scrollContainer.appendChild(this.minimapEl);
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Updates the scroll state from the container
|
|
226
|
+
*/
|
|
227
|
+
updateScrollState() {
|
|
228
|
+
if (!this.scrollContainer) return;
|
|
229
|
+
const { scrollLeft: t, scrollWidth: e, clientWidth: i } = this.scrollContainer;
|
|
230
|
+
this.scrollState = {
|
|
231
|
+
scrollLeft: t,
|
|
232
|
+
scrollWidth: e,
|
|
233
|
+
clientWidth: i,
|
|
234
|
+
viewportRatio: i / Math.max(e, 1),
|
|
235
|
+
positionRatio: t / Math.max(e - i, 1)
|
|
236
|
+
}, 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(
|
|
237
|
+
"aria-valuenow",
|
|
238
|
+
String(Math.round(this.scrollState.positionRatio * 100))
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Renders the minimap
|
|
243
|
+
*/
|
|
244
|
+
render() {
|
|
245
|
+
this.isDestroyed || (this.updateViewport(), this.options.mode === "canvas" && this.renderCanvas());
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Canvas metrics for calculations - cached values to avoid repeated computations
|
|
249
|
+
*/
|
|
250
|
+
getCanvasMetrics() {
|
|
251
|
+
var c;
|
|
252
|
+
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;
|
|
253
|
+
let l = 0;
|
|
254
|
+
if (e > 1 && this.scrollContainer) {
|
|
255
|
+
const { scrollLeft: d, scrollWidth: b, clientWidth: w } = this.scrollContainer, h = Math.max(b - w, 1);
|
|
256
|
+
l = d / h * (1 - s);
|
|
257
|
+
}
|
|
258
|
+
const r = l * i, u = Math.floor(r), p = Math.min(Math.ceil(r + a) + 1, i), v = -(r - u) * n;
|
|
259
|
+
return {
|
|
260
|
+
width: t,
|
|
261
|
+
zoom: e,
|
|
262
|
+
numCols: i,
|
|
263
|
+
visibleRatio: s,
|
|
264
|
+
visibleCols: a,
|
|
265
|
+
cellWidth: n,
|
|
266
|
+
panX: l,
|
|
267
|
+
startColFloat: r,
|
|
268
|
+
startCol: u,
|
|
269
|
+
endCol: p,
|
|
270
|
+
xOffset: v
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Calculates column index from mouse X position
|
|
275
|
+
*/
|
|
276
|
+
getColumnAtX(t) {
|
|
277
|
+
const { width: e, numCols: i, panX: s, visibleRatio: a } = this.getCanvasMetrics();
|
|
278
|
+
if (i === 0 || e === 0) return -1;
|
|
279
|
+
const n = t / e, l = s + n * a, r = Math.floor(l * i);
|
|
280
|
+
return Math.max(0, Math.min(i - 1, r));
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Updates the viewport indicator position and size
|
|
284
|
+
* In canvas mode, the viewport shows the focused (clicked) column
|
|
285
|
+
*/
|
|
286
|
+
updateViewport() {
|
|
287
|
+
if (!this.viewportEl || !this.minimapEl) return;
|
|
288
|
+
if (this.options.mode === "canvas") {
|
|
289
|
+
if (this.focusedColumn >= 0 && this.columns.length > 0) {
|
|
290
|
+
const { cellWidth: a, startColFloat: n } = this.getCanvasMetrics(), l = (this.focusedColumn - n) * a;
|
|
291
|
+
this.viewportEl.style.cssText = `width:${a}px;left:${l}px;display:block`;
|
|
292
|
+
} else
|
|
293
|
+
this.viewportEl.style.display = "none";
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
const t = this.minimapEl.offsetWidth, e = Math.max(t * this.scrollState.viewportRatio, 20), s = (t - e) * this.scrollState.positionRatio;
|
|
297
|
+
this.viewportEl.style.cssText = `width:${e}px;left:${s}px;display:block`;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Renders the canvas-mode visualization with table preview
|
|
301
|
+
*/
|
|
302
|
+
renderCanvas() {
|
|
303
|
+
if (!this.canvasEl || !this.canvasCtx || !this.minimapEl) return;
|
|
304
|
+
const t = this.getCanvasMetrics(), e = this.options.height, i = window.devicePixelRatio || 1;
|
|
305
|
+
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);
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Renders the visible portion of the table directly onto the canvas
|
|
309
|
+
*/
|
|
310
|
+
renderTableDirect(t, e) {
|
|
311
|
+
var P, y, L, W;
|
|
312
|
+
if (!this.canvasCtx) return;
|
|
313
|
+
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;
|
|
314
|
+
if (v === 0 || a === 0) return;
|
|
315
|
+
const c = Math.min(e * 0.15, 30), d = (e - c) / v, b = Math.min(d * 0.6, n * 0.15, 14), w = Math.min(c * 0.6, n * 0.15, 14), h = {
|
|
316
|
+
bg: "#ffffff",
|
|
317
|
+
headerBg: "#f1f5f9",
|
|
318
|
+
border: "#e2e8f0",
|
|
319
|
+
text: "#334155",
|
|
320
|
+
headerText: "#1e293b",
|
|
321
|
+
altRow: "#f8fafc",
|
|
322
|
+
hoverFill: "rgba(59, 130, 246, 0.08)",
|
|
323
|
+
hoverStroke: "rgba(59, 130, 246, 0.3)"
|
|
324
|
+
};
|
|
325
|
+
i.fillStyle = h.bg, i.fillRect(0, 0, s, e), i.fillStyle = h.headerBg, i.fillRect(0, 0, s, c);
|
|
326
|
+
const g = this.table.querySelector("thead tr") || p[0], x = g ? Array.from(g.querySelectorAll("th, td")) : [];
|
|
327
|
+
i.font = `bold ${w}px system-ui, sans-serif`, i.textBaseline = "middle";
|
|
328
|
+
for (let m = l; m < r; m++) {
|
|
329
|
+
const f = u + (m - l) * n;
|
|
330
|
+
if (f + n < 0 || f > s) continue;
|
|
331
|
+
i.strokeStyle = h.border, i.lineWidth = 1, i.strokeRect(f, 0, n, c);
|
|
332
|
+
const E = ((y = (P = x[m]) == null ? void 0 : P.textContent) == null ? void 0 : y.trim()) || `Col ${m + 1}`;
|
|
333
|
+
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();
|
|
334
|
+
}
|
|
335
|
+
i.font = `${b}px system-ui, sans-serif`;
|
|
336
|
+
for (let m = 0; m < p.length; m++) {
|
|
337
|
+
const f = p[m];
|
|
338
|
+
if (f.closest("thead")) continue;
|
|
339
|
+
const E = c + m * d;
|
|
340
|
+
if (E + d < 0 || E > e) continue;
|
|
341
|
+
m % 2 === 1 && (i.fillStyle = h.altRow, i.fillRect(0, E, s, d));
|
|
342
|
+
const X = Array.from(f.querySelectorAll("th, td"));
|
|
343
|
+
for (let M = l; M < r; M++) {
|
|
344
|
+
const S = u + (M - l) * n;
|
|
345
|
+
if (S + n < 0 || S > s) continue;
|
|
346
|
+
i.strokeStyle = h.border, i.lineWidth = 0.5, i.strokeRect(S, E, n, d);
|
|
347
|
+
const R = (W = (L = X[M]) == null ? void 0 : L.textContent) == null ? void 0 : W.trim();
|
|
348
|
+
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());
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
if (this.hoveredColumn >= l && this.hoveredColumn < r) {
|
|
352
|
+
const m = u + (this.hoveredColumn - l) * n;
|
|
353
|
+
i.fillStyle = h.hoverFill, i.fillRect(m, 0, n, e), i.strokeStyle = h.hoverStroke, i.lineWidth = 1, i.strokeRect(m, 0, n, e);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Attaches event listeners
|
|
358
|
+
*/
|
|
359
|
+
attachEventListeners() {
|
|
360
|
+
!this.scrollContainer || !this.minimapEl || (this.scrollContainer.addEventListener("scroll", this.boundHandlers.onScroll, {
|
|
361
|
+
passive: !0
|
|
362
|
+
}), 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));
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Handles scroll events on the container
|
|
366
|
+
*/
|
|
367
|
+
onScroll() {
|
|
368
|
+
this.isDragging || this.rafId === null && (this.rafId = requestAnimationFrame(() => {
|
|
369
|
+
this.updateScrollState(), this.updateViewport(), this.options.mode === "canvas" && this.render(), this.rafId = null;
|
|
370
|
+
}));
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Handles click on the minimap to jump to position
|
|
374
|
+
*
|
|
375
|
+
* @param e - Mouse event
|
|
376
|
+
*/
|
|
377
|
+
onMinimapClick(t) {
|
|
378
|
+
if (!this.minimapEl || !this.scrollContainer || this.isDragging || this.isPanning || this.wasPanning || t.target === this.viewportEl) return;
|
|
379
|
+
const { scrollWidth: e, clientWidth: i } = this.scrollContainer, s = this.columns.length;
|
|
380
|
+
if (this.hoveredColumn >= 0 && s > 0) {
|
|
381
|
+
this.focusedColumn = this.hoveredColumn;
|
|
382
|
+
const b = e / s, h = (this.hoveredColumn + 0.5) * b - i / 2, g = e - i, x = Math.max(0, Math.min(g, h));
|
|
383
|
+
this.scrollContainer.scrollTo({
|
|
384
|
+
left: x,
|
|
385
|
+
behavior: "smooth"
|
|
386
|
+
}), this.updateViewport();
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
const a = this.minimapEl.getBoundingClientRect(), l = (t.clientX - a.left) / a.width, r = this.zoomState.level;
|
|
390
|
+
let u;
|
|
391
|
+
if (r > 1) {
|
|
392
|
+
const w = this.scrollContainer.scrollLeft / Math.max(e - i, 1), h = 1 / r;
|
|
393
|
+
u = w * (1 - h) + l * h;
|
|
394
|
+
} else
|
|
395
|
+
u = l;
|
|
396
|
+
const v = u * e - i / 2, c = e - i, d = Math.max(0, Math.min(c, v));
|
|
397
|
+
this.scrollContainer.scrollTo({
|
|
398
|
+
left: d,
|
|
399
|
+
behavior: "smooth"
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Handles pointer down on viewport for drag start
|
|
404
|
+
*
|
|
405
|
+
* @param e - Pointer event
|
|
406
|
+
*/
|
|
407
|
+
onPointerDown(t) {
|
|
408
|
+
!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));
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Handles pointer move during drag
|
|
412
|
+
*
|
|
413
|
+
* @param e - Pointer event
|
|
414
|
+
*/
|
|
415
|
+
onPointerMove(t) {
|
|
416
|
+
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) {
|
|
417
|
+
t.preventDefault();
|
|
418
|
+
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;
|
|
419
|
+
this.scrollContainer.scrollLeft = Math.max(0, Math.min(v, d)), this.updateScrollState(), this.updateViewport(), this.render();
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
if (!this.isDragging || !this.minimapEl || !this.scrollContainer) return;
|
|
423
|
+
t.preventDefault();
|
|
424
|
+
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;
|
|
425
|
+
this.scrollContainer.scrollLeft = Math.max(
|
|
426
|
+
0,
|
|
427
|
+
Math.min(s, n)
|
|
428
|
+
), this.updateScrollState(), this.updateViewport();
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Handles pointer up to end drag
|
|
432
|
+
*
|
|
433
|
+
* @param e - Pointer event
|
|
434
|
+
*/
|
|
435
|
+
onPointerUp(t) {
|
|
436
|
+
if (this.isPotentialPan && !this.isPanning && this.canvasEl && this.minimapEl) {
|
|
437
|
+
this.isPotentialPan = !1, this.canvasEl.hasPointerCapture(t.pointerId) && this.canvasEl.releasePointerCapture(t.pointerId);
|
|
438
|
+
const e = new MouseEvent("click", {
|
|
439
|
+
clientX: t.clientX,
|
|
440
|
+
clientY: t.clientY,
|
|
441
|
+
bubbles: !0
|
|
442
|
+
});
|
|
443
|
+
this.minimapEl.dispatchEvent(e);
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
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(() => {
|
|
447
|
+
this.wasPanning = !1;
|
|
448
|
+
}, 100)), this.isDragging && (this.isDragging = !1, this.viewportEl && (this.viewportEl.classList.remove("tm-viewport--dragging"), this.viewportEl.releasePointerCapture(t.pointerId)));
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Handles wheel events for zoom
|
|
452
|
+
*
|
|
453
|
+
* @param e - Wheel event
|
|
454
|
+
*/
|
|
455
|
+
onWheel(t) {
|
|
456
|
+
if (!this.options.zoomable || this.options.mode !== "canvas") return;
|
|
457
|
+
t.preventDefault();
|
|
458
|
+
const e = -t.deltaY * this.options.zoomSpeed, i = Math.max(
|
|
459
|
+
this.options.minZoom,
|
|
460
|
+
Math.min(this.options.maxZoom, this.zoomState.level + e)
|
|
461
|
+
);
|
|
462
|
+
this.zoomState = {
|
|
463
|
+
level: i,
|
|
464
|
+
panX: 0,
|
|
465
|
+
// Not used anymore - derived from scroll position
|
|
466
|
+
isMinZoom: i <= this.options.minZoom,
|
|
467
|
+
isMaxZoom: i >= this.options.maxZoom
|
|
468
|
+
}, this.render();
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Handles pointer down on canvas for drag start (scrolls table when zoomed)
|
|
472
|
+
*
|
|
473
|
+
* @param e - Pointer event
|
|
474
|
+
*/
|
|
475
|
+
onCanvasPointerDown(t) {
|
|
476
|
+
!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)));
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Handles mouse move on canvas for column hover highlighting
|
|
480
|
+
*/
|
|
481
|
+
onCanvasMouseMove(t) {
|
|
482
|
+
if (!this.canvasEl || this.isPanning) return;
|
|
483
|
+
const e = this.canvasEl.getBoundingClientRect(), i = this.getColumnAtX(t.clientX - e.left);
|
|
484
|
+
i !== this.hoveredColumn && (this.hoveredColumn = i, this.canvasEl.style.cursor = i >= 0 ? "pointer" : this.zoomState.level > 1 ? "grab" : "default", this.render());
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Handles mouse leave on canvas to clear hover state
|
|
488
|
+
*/
|
|
489
|
+
onCanvasMouseLeave() {
|
|
490
|
+
this.hoveredColumn !== -1 && (this.hoveredColumn = -1, this.canvasEl && (this.canvasEl.style.cursor = this.zoomState.level > 1 ? "grab" : "default"), this.render());
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Sets up ResizeObserver and MutationObserver
|
|
494
|
+
*/
|
|
495
|
+
setupObservers() {
|
|
496
|
+
this.resizeObserver = new ResizeObserver(() => {
|
|
497
|
+
this.onResize();
|
|
498
|
+
}), this.scrollContainer && this.resizeObserver.observe(this.scrollContainer), this.resizeObserver.observe(this.table), this.mutationObserver = new MutationObserver((t) => {
|
|
499
|
+
t.some(
|
|
500
|
+
(i) => i.type === "childList" || i.attributeName === "colspan"
|
|
501
|
+
) && this.onTableMutation();
|
|
502
|
+
}), this.mutationObserver.observe(this.table, {
|
|
503
|
+
childList: !0,
|
|
504
|
+
subtree: !0,
|
|
505
|
+
attributes: !0,
|
|
506
|
+
attributeFilter: ["colspan"]
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Handles resize events
|
|
511
|
+
*/
|
|
512
|
+
onResize() {
|
|
513
|
+
this.isDestroyed || (this.rafId !== null && cancelAnimationFrame(this.rafId), this.rafId = requestAnimationFrame(() => {
|
|
514
|
+
this.detectColumns(), this.updateScrollState(), this.render(), this.options.mode === "columns" && this.columnsEl && this.minimapEl && (this.columnsEl.innerHTML = "", this.columns.forEach((t) => {
|
|
515
|
+
const e = document.createElement("div");
|
|
516
|
+
e.className = "tm-column", e.style.width = `${t.widthPercent}%`, this.columnsEl.appendChild(e);
|
|
517
|
+
})), this.rafId = null;
|
|
518
|
+
}));
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* Handles table mutation events
|
|
522
|
+
*/
|
|
523
|
+
onTableMutation() {
|
|
524
|
+
this.isDestroyed || (this.detectColumns(), this.updateScrollState(), this.render(), this.options.mode === "columns" && this.columnsEl && (this.columnsEl.innerHTML = "", this.columns.forEach((t) => {
|
|
525
|
+
const e = document.createElement("div");
|
|
526
|
+
e.className = "tm-column", e.style.width = `${t.widthPercent}%`, this.columnsEl.appendChild(e);
|
|
527
|
+
})));
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* Gets the current scroll state
|
|
531
|
+
*
|
|
532
|
+
* @returns Current scroll state
|
|
533
|
+
*/
|
|
534
|
+
getScrollState() {
|
|
535
|
+
return { ...this.scrollState };
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Gets the detected columns
|
|
539
|
+
*
|
|
540
|
+
* @returns Array of column information
|
|
541
|
+
*/
|
|
542
|
+
getColumns() {
|
|
543
|
+
return [...this.columns];
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Scrolls to a specific column
|
|
547
|
+
*
|
|
548
|
+
* @param columnIndex - Zero-based column index
|
|
549
|
+
* @param smooth - Use smooth scrolling
|
|
550
|
+
*/
|
|
551
|
+
scrollToColumn(t, e = !0) {
|
|
552
|
+
if (!this.scrollContainer || t < 0 || t >= this.columns.length)
|
|
553
|
+
return;
|
|
554
|
+
const s = this.columns.slice(0, t).reduce((a, n) => a + n.width, 0);
|
|
555
|
+
this.scrollContainer.scrollTo({
|
|
556
|
+
left: s,
|
|
557
|
+
behavior: e ? "smooth" : "auto"
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Forces a refresh of the minimap
|
|
562
|
+
*/
|
|
563
|
+
refresh() {
|
|
564
|
+
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) => {
|
|
565
|
+
const e = document.createElement("div");
|
|
566
|
+
e.className = "tm-column", e.style.width = `${t.widthPercent}%`, this.columnsEl.appendChild(e);
|
|
567
|
+
})));
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Gets the current zoom state (canvas mode only)
|
|
571
|
+
*
|
|
572
|
+
* @returns Current zoom state
|
|
573
|
+
*/
|
|
574
|
+
getZoomState() {
|
|
575
|
+
return { ...this.zoomState };
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Sets the zoom level programmatically (canvas mode only)
|
|
579
|
+
*
|
|
580
|
+
* @param level - Zoom level (1 = no zoom)
|
|
581
|
+
* @param panX - Optional pan position (0-1)
|
|
582
|
+
*/
|
|
583
|
+
setZoom(t, e) {
|
|
584
|
+
if (this.isDestroyed || this.options.mode !== "canvas") return;
|
|
585
|
+
const i = Math.max(
|
|
586
|
+
this.options.minZoom,
|
|
587
|
+
Math.min(this.options.maxZoom, t)
|
|
588
|
+
), a = 1 - 1 / i;
|
|
589
|
+
let n = e !== void 0 ? e : this.zoomState.panX;
|
|
590
|
+
n = Math.max(0, Math.min(a, n)), this.zoomState = {
|
|
591
|
+
level: i,
|
|
592
|
+
panX: i > 1 ? n : 0,
|
|
593
|
+
isMinZoom: i <= this.options.minZoom,
|
|
594
|
+
isMaxZoom: i >= this.options.maxZoom
|
|
595
|
+
}, this.render();
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Resets zoom to default (shows full table overview)
|
|
599
|
+
*/
|
|
600
|
+
resetZoom() {
|
|
601
|
+
this.setZoom(1, 0);
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* Zooms to a specific column range (canvas mode only)
|
|
605
|
+
*
|
|
606
|
+
* @param startCol - Start column index
|
|
607
|
+
* @param endCol - End column index
|
|
608
|
+
*/
|
|
609
|
+
zoomToColumns(t, e) {
|
|
610
|
+
if (this.isDestroyed || this.options.mode !== "canvas") return;
|
|
611
|
+
const i = this.columns.length;
|
|
612
|
+
if (i === 0) return;
|
|
613
|
+
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;
|
|
614
|
+
this.setZoom(l, r);
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Destroys the minimap instance and cleans up resources
|
|
618
|
+
*/
|
|
619
|
+
destroy() {
|
|
620
|
+
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 = []);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
export {
|
|
624
|
+
A as TableMinimap
|
|
625
|
+
};
|
|
626
|
+
//# sourceMappingURL=table-minimap.js.map
|