flexdesk 0.4.4 → 0.4.6
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 +10 -0
- package/css/base.css +102 -12
- package/css/flexdesk.css +120 -18
- package/css/overrides.css +15 -5
- package/dist/charts.js +2 -2
- package/dist/{chunk-P4AALM3A.js → chunk-CYTWMP2Y.js} +609 -55
- package/dist/chunk-CYTWMP2Y.js.map +7 -0
- package/dist/{chunk-4RGQSRMC.js → chunk-HWCX5I4R.js} +2 -2
- package/dist/{chunk-QNQHQ24V.js → chunk-NLTOY57M.js} +72 -1
- package/dist/chunk-NLTOY57M.js.map +7 -0
- package/dist/editor.js +1 -1
- package/dist/flexdesk.css +120 -18
- package/dist/tiles.js +2 -2
- package/dist/widgets.js +230 -1
- package/dist/widgets.js.map +3 -3
- package/dist/wm.js +13 -2
- package/dist/wm.js.map +2 -2
- package/package.json +1 -1
- package/src/editor/notebook_tab_bar.js +89 -0
- package/src/tiling/tile_tree.js +5 -0
- package/src/tiling/wm.js +14 -1
- package/src/ui/components/data_table.js +688 -63
- package/src/ui/utils/auto_scrollbars.js +270 -0
- package/dist/chunk-P4AALM3A.js.map +0 -7
- package/dist/chunk-QNQHQ24V.js.map +0 -7
- /package/dist/{chunk-4RGQSRMC.js.map → chunk-HWCX5I4R.js.map} +0 -0
|
@@ -16,6 +16,7 @@ function createRafResizeObserver(callback) {
|
|
|
16
16
|
// src/ui/components/data_table.js
|
|
17
17
|
var DEFAULT_PAGE_SIZE = 100;
|
|
18
18
|
var AUTOSIZE_MAX_PX = 520;
|
|
19
|
+
var VIRTUAL_FALLBACK_VIEWPORT_PX = 1e3;
|
|
19
20
|
var DataTable = class {
|
|
20
21
|
/**
|
|
21
22
|
* Create a DataTable instance
|
|
@@ -71,8 +72,14 @@ var DataTable = class {
|
|
|
71
72
|
// promise that adding the key changed no existing layout by a
|
|
72
73
|
// pixel. Only a consumer that asks for 'content' sees anything new.
|
|
73
74
|
columnFit: "container",
|
|
75
|
+
// C10. Off unless asked for — see the typedef.
|
|
76
|
+
virtualize: false,
|
|
77
|
+
virtualRowHeight: null,
|
|
78
|
+
onRowsRendered: null,
|
|
74
79
|
...config
|
|
75
80
|
};
|
|
81
|
+
this._virt = null;
|
|
82
|
+
this._virtBase = null;
|
|
76
83
|
if (this.config.persistKey && !this.config.stateStore) {
|
|
77
84
|
throw new Error(`DataTable: persistKey "${this.config.persistKey}" requires { stateStore }`);
|
|
78
85
|
}
|
|
@@ -302,8 +309,13 @@ var DataTable = class {
|
|
|
302
309
|
*/
|
|
303
310
|
render() {
|
|
304
311
|
const activeFilterColIdx = this._getActiveFilterColIdx();
|
|
312
|
+
const doc = this.container.ownerDocument;
|
|
313
|
+
const hadTableFocus = !!this._tableEl && doc?.activeElement === this._tableEl;
|
|
314
|
+
const prevWrap = this._tableWrapEl;
|
|
315
|
+
const prevScroll = prevWrap?.isConnected && this._renderedOffset === this._state.offset ? { top: prevWrap.scrollTop, left: prevWrap.scrollLeft } : null;
|
|
305
316
|
this._cleanup();
|
|
306
317
|
this.container.innerHTML = "";
|
|
318
|
+
this._virt = null;
|
|
307
319
|
const { rows, pagination, emptyMessage } = this.config;
|
|
308
320
|
const colSig = this._colSig();
|
|
309
321
|
if (this._colWidthsSig !== colSig) {
|
|
@@ -390,6 +402,18 @@ var DataTable = class {
|
|
|
390
402
|
}
|
|
391
403
|
this._tableWrapEl = tableWrap;
|
|
392
404
|
this.container.appendChild(this._wrapperEl);
|
|
405
|
+
if (prevScroll && this._state.offset === this._renderedOffset) {
|
|
406
|
+
tableWrap.scrollTop = prevScroll.top;
|
|
407
|
+
tableWrap.scrollLeft = prevScroll.left;
|
|
408
|
+
}
|
|
409
|
+
this._renderedOffset = this._state.offset;
|
|
410
|
+
if (this._virt) {
|
|
411
|
+
const onScroll = () => this._virtUpdate();
|
|
412
|
+
tableWrap.addEventListener("scroll", onScroll, { passive: true });
|
|
413
|
+
this._disposers.push(() => tableWrap.removeEventListener("scroll", onScroll));
|
|
414
|
+
this._virtMeasure();
|
|
415
|
+
this._virtUpdate({ force: true, notify: false });
|
|
416
|
+
}
|
|
393
417
|
if (this._headerTableEl && this._tableEl) {
|
|
394
418
|
requestAnimationFrame(() => this._syncHeaderWidths());
|
|
395
419
|
if (this._resizeObserver) {
|
|
@@ -399,9 +423,10 @@ var DataTable = class {
|
|
|
399
423
|
}
|
|
400
424
|
}
|
|
401
425
|
if (typeof ResizeObserver !== "undefined") {
|
|
402
|
-
this._resizeObserver = createRafResizeObserver(
|
|
403
|
-
|
|
404
|
-
|
|
426
|
+
this._resizeObserver = createRafResizeObserver(() => {
|
|
427
|
+
this._syncHeaderWidths();
|
|
428
|
+
this._virtUpdate();
|
|
429
|
+
});
|
|
405
430
|
this._resizeObserver.observe(this._wrapperEl);
|
|
406
431
|
}
|
|
407
432
|
this._installHeaderScrollSync();
|
|
@@ -412,6 +437,11 @@ var DataTable = class {
|
|
|
412
437
|
}
|
|
413
438
|
if (activeFilterColIdx !== null) {
|
|
414
439
|
this._restoreFilterFocus(activeFilterColIdx);
|
|
440
|
+
} else if (hadTableFocus && this._tableEl) {
|
|
441
|
+
try {
|
|
442
|
+
this._tableEl.focus({ preventScroll: true });
|
|
443
|
+
} catch (_) {
|
|
444
|
+
}
|
|
415
445
|
}
|
|
416
446
|
}
|
|
417
447
|
/**
|
|
@@ -527,6 +557,8 @@ var DataTable = class {
|
|
|
527
557
|
this._tableWrapEl = null;
|
|
528
558
|
this._processedRows = null;
|
|
529
559
|
this._processedIndexMap = null;
|
|
560
|
+
this._virt = null;
|
|
561
|
+
this._colgroupEl = null;
|
|
530
562
|
}
|
|
531
563
|
// ─────────────────────────────────────────────────────────────────
|
|
532
564
|
// Processed rows pipeline (filter → sort → cache)
|
|
@@ -1129,7 +1161,7 @@ var DataTable = class {
|
|
|
1129
1161
|
}
|
|
1130
1162
|
_createTable() {
|
|
1131
1163
|
const { headers, rows, pageSize, sortable, filterable, showRowNumbers, readonly, getHeaderIcon, onPageChange, offset: configOffset } = this.config;
|
|
1132
|
-
const {
|
|
1164
|
+
const { sortColumn, sortAscending } = this._state;
|
|
1133
1165
|
const isServerSide = typeof onPageChange === "function";
|
|
1134
1166
|
const processedRows = isServerSide ? rows : this._getProcessedRows();
|
|
1135
1167
|
const offset = isServerSide ? configOffset || 0 : this._state.offset;
|
|
@@ -1185,33 +1217,455 @@ var DataTable = class {
|
|
|
1185
1217
|
const filterRow = this._createFilterRow(headers);
|
|
1186
1218
|
thead.appendChild(filterRow);
|
|
1187
1219
|
}
|
|
1220
|
+
const virtual = this._isVirtual();
|
|
1221
|
+
if (virtual) {
|
|
1222
|
+
table.classList.add("twm-dt--virtual");
|
|
1223
|
+
table.appendChild(this._createColgroup());
|
|
1224
|
+
}
|
|
1188
1225
|
table.appendChild(thead);
|
|
1189
|
-
const
|
|
1190
|
-
pageRows.forEach((row, localIdx) => {
|
|
1226
|
+
const globalOf = (localIdx) => {
|
|
1191
1227
|
const processedIdx = offset + localIdx;
|
|
1192
|
-
|
|
1228
|
+
return isServerSide ? (configOffset || 0) + localIdx : this._processedIndexMap?.[processedIdx] ?? processedIdx;
|
|
1229
|
+
};
|
|
1230
|
+
const tbody = document.createElement("tbody");
|
|
1231
|
+
table.appendChild(tbody);
|
|
1232
|
+
this._tbodyEl = tbody;
|
|
1233
|
+
if (virtual) {
|
|
1234
|
+
this._virtInit(tbody, pageRows, globalOf, {
|
|
1235
|
+
serverSide: isServerSide,
|
|
1236
|
+
offset,
|
|
1237
|
+
configOffset: configOffset || 0
|
|
1238
|
+
});
|
|
1239
|
+
} else {
|
|
1240
|
+
pageRows.forEach((row, localIdx) => {
|
|
1241
|
+
tbody.appendChild(this._buildBodyRow(row, globalOf(localIdx), localIdx));
|
|
1242
|
+
});
|
|
1243
|
+
}
|
|
1244
|
+
return table;
|
|
1245
|
+
}
|
|
1246
|
+
/** One body row, fully built. The ONE place a `<tr>` for a row is made, so
|
|
1247
|
+
* the full render and the virtual window cannot drift apart: a row drawn
|
|
1248
|
+
* on scroll is the same row a render would have drawn. */
|
|
1249
|
+
_buildBodyRow(row, globalIdx, localIdx) {
|
|
1250
|
+
const { showRowNumbers } = this.config;
|
|
1251
|
+
const tr = document.createElement("tr");
|
|
1252
|
+
tr.__rowIndex = globalIdx;
|
|
1253
|
+
tr.className = "data-preview-row";
|
|
1254
|
+
if (this._state.selected.has(globalIdx)) {
|
|
1255
|
+
tr.classList.add("selected");
|
|
1256
|
+
}
|
|
1257
|
+
if (this._virt) tr.classList.toggle("twm-dt-row--alt", localIdx % 2 === 1);
|
|
1258
|
+
this._fillRowCells(tr, row, globalIdx, showRowNumbers);
|
|
1259
|
+
if (this.config.onRowClick) {
|
|
1260
|
+
tr.addEventListener("click", (ev) => {
|
|
1261
|
+
this.config.onRowClick(globalIdx, row, ev);
|
|
1262
|
+
});
|
|
1263
|
+
}
|
|
1264
|
+
if (this.config.onRowContextMenu) {
|
|
1265
|
+
tr.addEventListener("contextmenu", (ev) => {
|
|
1266
|
+
this.config.onRowContextMenu(globalIdx, row, ev);
|
|
1267
|
+
});
|
|
1268
|
+
}
|
|
1269
|
+
return tr;
|
|
1270
|
+
}
|
|
1271
|
+
// ─────────────────────────────────────────────────────────────────
|
|
1272
|
+
// C10 — the virtual window
|
|
1273
|
+
// ─────────────────────────────────────────────────────────────────
|
|
1274
|
+
//
|
|
1275
|
+
// ══ WHAT IT IS ═══════════════════════════════════════════════════
|
|
1276
|
+
//
|
|
1277
|
+
// Only the rows near the viewport are in the DOM: a TOP SPACER row whose
|
|
1278
|
+
// height is every row above the window, the window's rows, and a BOTTOM
|
|
1279
|
+
// SPACER for everything below it. The scrollbar is therefore sized to the
|
|
1280
|
+
// whole table and every row is reachable, while the DOM stays bounded by
|
|
1281
|
+
// the viewport — a thousand-row page (or fifty of them) costs the same
|
|
1282
|
+
// few dozen `<tr>`s as one screenful.
|
|
1283
|
+
//
|
|
1284
|
+
// ══ WHAT IT PROMISES ═════════════════════════════════════════════
|
|
1285
|
+
//
|
|
1286
|
+
// - A row that stays in the window is NEVER MOVED OR REBUILT when the
|
|
1287
|
+
// window slides. Rows leave from the edges and arrive at the edges; the
|
|
1288
|
+
// ones in the middle are the same nodes. An open editor, a focused
|
|
1289
|
+
// cell, a hover — anything living in a row that is still on screen —
|
|
1290
|
+
// survives a scroll. (Moving a node that holds focus blurs it, which in
|
|
1291
|
+
// an editable grid is a commit nobody asked for.)
|
|
1292
|
+
// - Column widths come from a `<colgroup>`, so the first row changing
|
|
1293
|
+
// moves nothing (see `_createTable`).
|
|
1294
|
+
// - A spacer is in the DOM only while it has a height. A table that fits
|
|
1295
|
+
// in its window has exactly the DOM it had without `virtualize`.
|
|
1296
|
+
// - `onRowsRendered` hears about every row added after `render()`, and
|
|
1297
|
+
// every row taken out.
|
|
1298
|
+
//
|
|
1299
|
+
// ══ WHAT IT ASSUMES ══════════════════════════════════════════════
|
|
1300
|
+
//
|
|
1301
|
+
// That a row's height is knowable without drawing it: `baseHeight`,
|
|
1302
|
+
// measured from a real row, or what `virtualRowHeight` says. A row that
|
|
1303
|
+
// wraps to two lines is drawn correctly and is simply mis-counted in the
|
|
1304
|
+
// spacer arithmetic by the difference — the overscan absorbs that.
|
|
1305
|
+
_isVirtual() {
|
|
1306
|
+
return !!this.config.virtualize;
|
|
1307
|
+
}
|
|
1308
|
+
_virtOptions() {
|
|
1309
|
+
const v = this.config.virtualize;
|
|
1310
|
+
const o = v && typeof v === "object" ? v : {};
|
|
1311
|
+
return {
|
|
1312
|
+
overscan: Number.isFinite(o.overscan) && o.overscan >= 0 ? Math.floor(o.overscan) : 20,
|
|
1313
|
+
rowHeight: Number.isFinite(o.rowHeight) && o.rowHeight > 0 ? o.rowHeight : 24
|
|
1314
|
+
};
|
|
1315
|
+
}
|
|
1316
|
+
/** One `<col>` per DOM column (the row-number column included), carrying
|
|
1317
|
+
* any width already pinned for it, so a re-render starts at the widths it
|
|
1318
|
+
* had rather than at a content-sized frame that then snaps. */
|
|
1319
|
+
_createColgroup() {
|
|
1320
|
+
const n = (this.config.headers?.length || 0) + (this.config.showRowNumbers ? 1 : 0);
|
|
1321
|
+
const colgroup = document.createElement("colgroup");
|
|
1322
|
+
for (let i = 0; i < n; i++) {
|
|
1323
|
+
const col = document.createElement("col");
|
|
1324
|
+
const w = this._colWidths?.[i];
|
|
1325
|
+
if (w != null && this._colWidthsSig === this._colSig()) col.style.width = `${w}px`;
|
|
1326
|
+
colgroup.appendChild(col);
|
|
1327
|
+
}
|
|
1328
|
+
this._colgroupEl = colgroup;
|
|
1329
|
+
return colgroup;
|
|
1330
|
+
}
|
|
1331
|
+
/** The `<col>`s, when this table has them. */
|
|
1332
|
+
_cols() {
|
|
1333
|
+
return this._virt && this._colgroupEl ? this._colgroupEl.children : null;
|
|
1334
|
+
}
|
|
1335
|
+
_virtInit(tbody, rows, globalOf, { serverSide, offset, configOffset }) {
|
|
1336
|
+
const { overscan, rowHeight } = this._virtOptions();
|
|
1337
|
+
const spacer = (edge) => {
|
|
1193
1338
|
const tr = document.createElement("tr");
|
|
1194
|
-
tr.
|
|
1195
|
-
tr.
|
|
1196
|
-
|
|
1197
|
-
|
|
1339
|
+
tr.className = `twm-dt-spacer twm-dt-spacer--${edge}`;
|
|
1340
|
+
tr.setAttribute("aria-hidden", "true");
|
|
1341
|
+
const td = document.createElement("td");
|
|
1342
|
+
td.colSpan = Math.max(1, (this.config.headers?.length || 0) + (this.config.showRowNumbers ? 1 : 0));
|
|
1343
|
+
td.style.cssText = "padding:0;border:0;height:0;min-width:0;max-width:none;";
|
|
1344
|
+
tr.appendChild(td);
|
|
1345
|
+
return tr;
|
|
1346
|
+
};
|
|
1347
|
+
const filler = document.createElement("tr");
|
|
1348
|
+
filler.className = "twm-dt-spacer twm-dt-spacer--parity";
|
|
1349
|
+
filler.setAttribute("aria-hidden", "true");
|
|
1350
|
+
filler.hidden = true;
|
|
1351
|
+
this._virt = {
|
|
1352
|
+
tbody,
|
|
1353
|
+
rows,
|
|
1354
|
+
globalOf,
|
|
1355
|
+
serverSide,
|
|
1356
|
+
offset,
|
|
1357
|
+
configOffset,
|
|
1358
|
+
overscan,
|
|
1359
|
+
base: this._virtBase || rowHeight,
|
|
1360
|
+
heights: null,
|
|
1361
|
+
offsets: null,
|
|
1362
|
+
start: 0,
|
|
1363
|
+
end: 0,
|
|
1364
|
+
map: /* @__PURE__ */ new Map(),
|
|
1365
|
+
top: spacer("top"),
|
|
1366
|
+
bottom: spacer("bottom"),
|
|
1367
|
+
filler
|
|
1368
|
+
};
|
|
1369
|
+
this._virtComputeOffsets();
|
|
1370
|
+
const guess = this.container?.clientHeight || 0;
|
|
1371
|
+
this._virtUpdate({ force: true, notify: false, viewport: guess || void 0, top: 0 });
|
|
1372
|
+
}
|
|
1373
|
+
/** Heights and their running sum. `offsets[i]` is where row `i` starts;
|
|
1374
|
+
* `offsets[n]` is the whole table. O(n), and rerun only when a height can
|
|
1375
|
+
* have changed — a new measurement, or `refreshVirtualLayout()`. */
|
|
1376
|
+
_virtComputeOffsets() {
|
|
1377
|
+
const v = this._virt;
|
|
1378
|
+
if (!v) return;
|
|
1379
|
+
const n = v.rows.length;
|
|
1380
|
+
const heights = new Float64Array(n);
|
|
1381
|
+
const offsets = new Float64Array(n + 1);
|
|
1382
|
+
const fn = typeof this.config.virtualRowHeight === "function" ? this.config.virtualRowHeight : null;
|
|
1383
|
+
let y = 0;
|
|
1384
|
+
for (let i = 0; i < n; i++) {
|
|
1385
|
+
let h = v.base;
|
|
1386
|
+
if (fn) {
|
|
1387
|
+
const asked = fn(v.globalOf(i), v.base);
|
|
1388
|
+
h = Number.isFinite(asked) && asked > 0 ? asked : 0;
|
|
1198
1389
|
}
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1390
|
+
heights[i] = h;
|
|
1391
|
+
offsets[i] = y;
|
|
1392
|
+
y += h;
|
|
1393
|
+
}
|
|
1394
|
+
offsets[n] = y;
|
|
1395
|
+
v.heights = heights;
|
|
1396
|
+
v.offsets = offsets;
|
|
1397
|
+
}
|
|
1398
|
+
/** The row whose span contains `y`: the LAST `i` with `offsets[i] <= y`,
|
|
1399
|
+
* which steps over rows of zero height at the same offset. */
|
|
1400
|
+
_virtIndexAt(y) {
|
|
1401
|
+
const v = this._virt;
|
|
1402
|
+
const n = v.rows.length;
|
|
1403
|
+
if (n === 0) return 0;
|
|
1404
|
+
let lo = 0;
|
|
1405
|
+
let hi = n - 1;
|
|
1406
|
+
while (lo < hi) {
|
|
1407
|
+
const mid = lo + hi + 1 >> 1;
|
|
1408
|
+
if (v.offsets[mid] <= y) lo = mid;
|
|
1409
|
+
else hi = mid - 1;
|
|
1410
|
+
}
|
|
1411
|
+
return lo;
|
|
1412
|
+
}
|
|
1413
|
+
/** Measure one real row. Client pixels, so the zoom is divided back out —
|
|
1414
|
+
* `_measureNaturalWidths` explains why — and NOT `offsetHeight`, which is
|
|
1415
|
+
* rounded: 21 for a 21.19px row is a 1,100px error over 6,000 rows. */
|
|
1416
|
+
_virtMeasure() {
|
|
1417
|
+
const v = this._virt;
|
|
1418
|
+
if (!v) return false;
|
|
1419
|
+
for (const tr of v.map.values()) {
|
|
1420
|
+
if (!tr.isConnected || tr.hidden) continue;
|
|
1421
|
+
const zoom = this._tableEl?.currentCSSZoom || 1;
|
|
1422
|
+
const h = tr.getBoundingClientRect().height / zoom;
|
|
1423
|
+
if (!(h > 0)) return false;
|
|
1424
|
+
if (Math.abs(h - v.base) > 0.01) {
|
|
1425
|
+
v.base = h;
|
|
1426
|
+
this._virtBase = h;
|
|
1427
|
+
this._virtComputeOffsets();
|
|
1428
|
+
return true;
|
|
1204
1429
|
}
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1430
|
+
return false;
|
|
1431
|
+
}
|
|
1432
|
+
return false;
|
|
1433
|
+
}
|
|
1434
|
+
/**
|
|
1435
|
+
* Slide the window to wherever the scroll box now is.
|
|
1436
|
+
*
|
|
1437
|
+
* HYSTERESIS, so a scroll of one pixel does not rebuild anything: the
|
|
1438
|
+
* window is redrawn only when the visible rows come within a quarter of the
|
|
1439
|
+
* overscan of either end of what is drawn, and it is then redrawn with the
|
|
1440
|
+
* full overscan on both sides.
|
|
1441
|
+
*
|
|
1442
|
+
* @param {object} [o]
|
|
1443
|
+
* @param {boolean} [o.force] redraw even inside the hysteresis band.
|
|
1444
|
+
* @param {boolean} [o.notify] report added and removed rows to `onRowsRendered`.
|
|
1445
|
+
* @param {number} [o.top] the scroll offset to draw for, instead of
|
|
1446
|
+
* reading one back — `scrollToRow` passes the offset it has just set,
|
|
1447
|
+
* which a DOM with no layout would read back as 0.
|
|
1448
|
+
* @param {number} [o.viewport] the viewport height, likewise.
|
|
1449
|
+
* @returns {HTMLElement[]} the rows added.
|
|
1450
|
+
*/
|
|
1451
|
+
_virtUpdate({ force = false, notify = true, top = null, viewport = null } = {}) {
|
|
1452
|
+
const v = this._virt;
|
|
1453
|
+
if (!v || !v.offsets) return [];
|
|
1454
|
+
const n = v.rows.length;
|
|
1455
|
+
const wrap = this._tableWrapEl;
|
|
1456
|
+
const view = viewport || wrap?.clientHeight || VIRTUAL_FALLBACK_VIEWPORT_PX;
|
|
1457
|
+
const scrollTop = top ?? (wrap?.scrollTop || 0);
|
|
1458
|
+
const vs = this._virtIndexAt(scrollTop);
|
|
1459
|
+
const ve = Math.min(n, this._virtIndexAt(scrollTop + view) + 1);
|
|
1460
|
+
if (!force && v.end > v.start) {
|
|
1461
|
+
const margin = Math.max(1, Math.floor(v.overscan / 4));
|
|
1462
|
+
const lowOk = v.start === 0 || vs >= v.start + margin;
|
|
1463
|
+
const highOk = v.end === n || ve <= v.end - margin;
|
|
1464
|
+
if (lowOk && highOk) return [];
|
|
1465
|
+
}
|
|
1466
|
+
const start = Math.max(0, vs - v.overscan);
|
|
1467
|
+
const end = Math.min(n, ve + v.overscan);
|
|
1468
|
+
const removed = [];
|
|
1469
|
+
const added = this._virtRender(start, end, removed);
|
|
1470
|
+
if (added.length || removed.length) {
|
|
1471
|
+
if (added[0]?.isConnected && this._tableEl?.style.tableLayout === "fixed") {
|
|
1472
|
+
this._updateCellTooltips(added);
|
|
1209
1473
|
}
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1474
|
+
if (notify) {
|
|
1475
|
+
try {
|
|
1476
|
+
this.config.onRowsRendered?.({ rows: added, removed });
|
|
1477
|
+
} catch (err) {
|
|
1478
|
+
console.error("[DataTable] onRowsRendered threw", err);
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
return added;
|
|
1483
|
+
}
|
|
1484
|
+
/** Make the DOM hold exactly the drawable rows of `[start, end)`, without
|
|
1485
|
+
* moving a row that is already there. See the promises above. */
|
|
1486
|
+
_virtRender(start, end, removed = null) {
|
|
1487
|
+
const v = this._virt;
|
|
1488
|
+
const tbody = v.tbody;
|
|
1489
|
+
const want = [];
|
|
1490
|
+
for (let i = start; i < end; i++) if (v.heights[i] > 0) want.push(i);
|
|
1491
|
+
const wanted = new Set(want);
|
|
1492
|
+
for (const [i, tr] of v.map) {
|
|
1493
|
+
if (!wanted.has(i)) {
|
|
1494
|
+
tr.remove();
|
|
1495
|
+
v.map.delete(i);
|
|
1496
|
+
removed?.push(tr);
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
v.start = start;
|
|
1500
|
+
v.end = end;
|
|
1501
|
+
this._virtSpacers();
|
|
1502
|
+
const added = [];
|
|
1503
|
+
let ref = v.bottom.isConnected ? v.bottom : null;
|
|
1504
|
+
for (let k = want.length - 1; k >= 0; k--) {
|
|
1505
|
+
const i = want[k];
|
|
1506
|
+
const have = v.map.get(i);
|
|
1507
|
+
if (have) {
|
|
1508
|
+
ref = have;
|
|
1509
|
+
continue;
|
|
1510
|
+
}
|
|
1511
|
+
const tr = this._buildBodyRow(v.rows[i], v.globalOf(i), i);
|
|
1512
|
+
v.map.set(i, tr);
|
|
1513
|
+
tbody.insertBefore(tr, ref);
|
|
1514
|
+
ref = tr;
|
|
1515
|
+
added.push(tr);
|
|
1516
|
+
}
|
|
1517
|
+
added.reverse();
|
|
1518
|
+
return added;
|
|
1519
|
+
}
|
|
1520
|
+
/**
|
|
1521
|
+
* Size the two spacers, and put each in the DOM only while it has a height.
|
|
1522
|
+
*
|
|
1523
|
+
* AND KEEP THE STRIPE. `tr:nth-child(even)` stripes by POSITION, and in a
|
|
1524
|
+
* full render row `i` is child `i + 1`. Here the first drawn row is preceded
|
|
1525
|
+
* by the top spacer, so its position is off by one — and by a different one
|
|
1526
|
+
* each time the window's start changes parity, which strobes every stripe
|
|
1527
|
+
* on the screen as you scroll. A hidden filler row after the spacer, present
|
|
1528
|
+
* exactly when `start` is even, puts the parity back: a `display: none` row
|
|
1529
|
+
* is still a child, and so still counted by `nth-child`.
|
|
1530
|
+
*/
|
|
1531
|
+
_virtSpacers() {
|
|
1532
|
+
const v = this._virt;
|
|
1533
|
+
const n = v.rows.length;
|
|
1534
|
+
const above = v.offsets[v.start];
|
|
1535
|
+
const below = v.offsets[n] - v.offsets[v.end];
|
|
1536
|
+
const tbody = v.tbody;
|
|
1537
|
+
if (above > 0) {
|
|
1538
|
+
v.top.firstChild.style.height = `${above}px`;
|
|
1539
|
+
if (tbody.firstChild !== v.top) tbody.insertBefore(v.top, tbody.firstChild);
|
|
1540
|
+
const needFiller = v.start % 2 === 0;
|
|
1541
|
+
if (needFiller && v.top.nextSibling !== v.filler) v.top.after(v.filler);
|
|
1542
|
+
else if (!needFiller) v.filler.remove();
|
|
1543
|
+
} else {
|
|
1544
|
+
v.top.remove();
|
|
1545
|
+
v.filler.remove();
|
|
1546
|
+
}
|
|
1547
|
+
if (below > 0) {
|
|
1548
|
+
v.bottom.firstChild.style.height = `${below}px`;
|
|
1549
|
+
if (tbody.lastChild !== v.bottom) tbody.appendChild(v.bottom);
|
|
1550
|
+
} else {
|
|
1551
|
+
v.bottom.remove();
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
/**
|
|
1555
|
+
* Recompute every row's height and redraw the window. For an embedder whose
|
|
1556
|
+
* `virtualRowHeight` answer has changed — a group folded, a header added.
|
|
1557
|
+
* A no-op on a table that is not virtual.
|
|
1558
|
+
*/
|
|
1559
|
+
refreshVirtualLayout() {
|
|
1560
|
+
if (!this._virt) return;
|
|
1561
|
+
this._virtMeasure();
|
|
1562
|
+
this._virtComputeOffsets();
|
|
1563
|
+
this._virtUpdate({ force: true });
|
|
1564
|
+
}
|
|
1565
|
+
/** The element that scrolls the body. Read-only; the table owns it. */
|
|
1566
|
+
get scrollElement() {
|
|
1567
|
+
return this._tableWrapEl || null;
|
|
1568
|
+
}
|
|
1569
|
+
/** The `<tr>` drawing row `rowIndex` (the `__rowIndex` space), or null when
|
|
1570
|
+
* it is not in the DOM — outside the window, on another page, or hidden. */
|
|
1571
|
+
getRowElement(rowIndex) {
|
|
1572
|
+
const v = this._virt;
|
|
1573
|
+
if (v) {
|
|
1574
|
+
const local = this._virtLocalOf(rowIndex);
|
|
1575
|
+
return local < 0 ? null : v.map.get(local) || null;
|
|
1576
|
+
}
|
|
1577
|
+
const tbody = this._tbodyEl;
|
|
1578
|
+
if (!tbody) return null;
|
|
1579
|
+
for (const tr of tbody.children) if (tr.__rowIndex === rowIndex) return tr;
|
|
1580
|
+
return null;
|
|
1581
|
+
}
|
|
1582
|
+
/** Keep the window's row list and `config.rows` in step for one row. They
|
|
1583
|
+
* are the same array in server-side mode; in client mode the window reads
|
|
1584
|
+
* the filtered-and-sorted copy, which holds its own reference. */
|
|
1585
|
+
_virtSetRow(rowIndex, row) {
|
|
1586
|
+
const v = this._virt;
|
|
1587
|
+
if (!v) return;
|
|
1588
|
+
if (Array.isArray(this.config.rows) && rowIndex >= 0 && rowIndex < this.config.rows.length) {
|
|
1589
|
+
this.config.rows[rowIndex] = row;
|
|
1590
|
+
}
|
|
1591
|
+
const local = this._virtLocalOf(rowIndex);
|
|
1592
|
+
if (local >= 0) v.rows[local] = row;
|
|
1593
|
+
}
|
|
1594
|
+
/** `__rowIndex` → position in the window's row list, or -1. */
|
|
1595
|
+
_virtLocalOf(rowIndex) {
|
|
1596
|
+
const v = this._virt;
|
|
1597
|
+
if (!v) return -1;
|
|
1598
|
+
let local;
|
|
1599
|
+
if (v.serverSide) local = rowIndex - v.configOffset;
|
|
1600
|
+
else {
|
|
1601
|
+
const map = this._processedIndexMap;
|
|
1602
|
+
const processed = map ? map.indexOf(rowIndex) : rowIndex;
|
|
1603
|
+
local = processed < 0 ? -1 : processed - v.offset;
|
|
1604
|
+
}
|
|
1605
|
+
return local >= 0 && local < v.rows.length ? local : -1;
|
|
1606
|
+
}
|
|
1607
|
+
/**
|
|
1608
|
+
* Scroll the body so row `rowIndex` is on screen, drawing it if it was not,
|
|
1609
|
+
* and return its `<tr>` (null when it cannot be shown: another page, or a
|
|
1610
|
+
* row the embedder hides). Works with or without `virtualize`.
|
|
1611
|
+
*
|
|
1612
|
+
* @param {number} rowIndex in the `__rowIndex` space.
|
|
1613
|
+
* @param {{block?: 'nearest'|'center'|'start'}} [opts]
|
|
1614
|
+
*/
|
|
1615
|
+
scrollToRow(rowIndex, { block = "nearest" } = {}) {
|
|
1616
|
+
const wrap = this._tableWrapEl;
|
|
1617
|
+
const v = this._virt;
|
|
1618
|
+
if (!v) {
|
|
1619
|
+
const tr = this.getRowElement(rowIndex);
|
|
1620
|
+
if (!tr || !wrap) return tr;
|
|
1621
|
+
const zoom = this._tableEl?.currentCSSZoom || 1;
|
|
1622
|
+
const box = wrap.getBoundingClientRect();
|
|
1623
|
+
const r = tr.getBoundingClientRect();
|
|
1624
|
+
const rowTop = (r.top - box.top) / zoom + wrap.scrollTop;
|
|
1625
|
+
const rowH = r.height / zoom;
|
|
1626
|
+
const target2 = this._scrollTarget(
|
|
1627
|
+
rowTop,
|
|
1628
|
+
rowH,
|
|
1629
|
+
wrap.scrollTop,
|
|
1630
|
+
wrap.clientHeight,
|
|
1631
|
+
block
|
|
1632
|
+
);
|
|
1633
|
+
if (target2 !== wrap.scrollTop) wrap.scrollTop = target2;
|
|
1634
|
+
return tr;
|
|
1635
|
+
}
|
|
1636
|
+
const local = this._virtLocalOf(rowIndex);
|
|
1637
|
+
if (local < 0 || !(v.heights[local] > 0)) return null;
|
|
1638
|
+
const view = wrap?.clientHeight || VIRTUAL_FALLBACK_VIEWPORT_PX;
|
|
1639
|
+
const current = wrap?.scrollTop || 0;
|
|
1640
|
+
let target = this._scrollTarget(
|
|
1641
|
+
v.offsets[local],
|
|
1642
|
+
v.heights[local],
|
|
1643
|
+
current,
|
|
1644
|
+
view,
|
|
1645
|
+
block
|
|
1646
|
+
);
|
|
1647
|
+
target = Math.max(0, Math.min(target, Math.max(0, v.offsets[v.rows.length] - view)));
|
|
1648
|
+
if (wrap && Math.abs(target - current) >= 1) wrap.scrollTop = target;
|
|
1649
|
+
this._virtUpdate({ top: Math.abs(target - current) >= 1 ? target : current });
|
|
1650
|
+
return v.map.get(local) || null;
|
|
1651
|
+
}
|
|
1652
|
+
_scrollTarget(rowTop, rowH, current, view, block) {
|
|
1653
|
+
if (block === "start") return rowTop;
|
|
1654
|
+
if (block === "center") return rowTop - Math.max(0, (view - rowH) / 2);
|
|
1655
|
+
if (rowTop < current) return rowTop;
|
|
1656
|
+
if (rowTop + rowH > current + view) return rowTop + rowH - view;
|
|
1657
|
+
return current;
|
|
1658
|
+
}
|
|
1659
|
+
/** The first body row that draws a row of data — never a spacer, and never
|
|
1660
|
+
* a row an embedder injected (a group header spans every column with ONE
|
|
1661
|
+
* cell, so measuring it would report one column). */
|
|
1662
|
+
_firstBodyRow() {
|
|
1663
|
+
const tbody = this._tableEl?.querySelector?.("tbody");
|
|
1664
|
+
if (!tbody) return null;
|
|
1665
|
+
for (const tr of tbody.children) {
|
|
1666
|
+
if (tr.__rowIndex !== void 0) return tr;
|
|
1667
|
+
}
|
|
1668
|
+
return null;
|
|
1215
1669
|
}
|
|
1216
1670
|
/** Build (or rebuild) one row's cells in place.
|
|
1217
1671
|
*
|
|
@@ -1291,10 +1745,14 @@ var DataTable = class {
|
|
|
1291
1745
|
break;
|
|
1292
1746
|
}
|
|
1293
1747
|
}
|
|
1294
|
-
if (!tr)
|
|
1748
|
+
if (!tr) {
|
|
1749
|
+
if (this._virt) this._virtSetRow(index, row);
|
|
1750
|
+
return false;
|
|
1751
|
+
}
|
|
1295
1752
|
if (Array.isArray(this.config.rows) && this.config.rows[index]) {
|
|
1296
1753
|
this.config.rows[index] = row;
|
|
1297
1754
|
}
|
|
1755
|
+
if (this._virt) this._virtSetRow(index, row);
|
|
1298
1756
|
const active = document.activeElement;
|
|
1299
1757
|
let focusedCol = -1;
|
|
1300
1758
|
if (active && tr.contains(active)) {
|
|
@@ -1343,14 +1801,23 @@ var DataTable = class {
|
|
|
1343
1801
|
* never visible. Clearing inline widths first stops the last sync's forced
|
|
1344
1802
|
* widths from constraining the measure.
|
|
1345
1803
|
*
|
|
1804
|
+
* C10 — UNDER `virtualize` ONLY THE WINDOW IS IN THE DOM, so `max-content`
|
|
1805
|
+
* spans the eighty-odd rows drawn rather than every row loaded. The fit
|
|
1806
|
+
* pass that runs on every render is content with that (it is fitting what
|
|
1807
|
+
* is on screen). An EXPLICIT auto-size is not: it is a promise to fit the
|
|
1808
|
+
* column's content, and the longest value in a 6,000-row table is usually
|
|
1809
|
+
* not in the window. So `sample` adds, for the length of this one reflow,
|
|
1810
|
+
* the row holding each column's longest value — see `_virtSampleRows`.
|
|
1811
|
+
*
|
|
1812
|
+
* @param {{sample?: boolean}} [o]
|
|
1346
1813
|
* @returns {number[]|null} width per DOM column index, or null when there
|
|
1347
1814
|
* is nothing laid out to measure.
|
|
1348
1815
|
*/
|
|
1349
|
-
_measureNaturalWidths() {
|
|
1816
|
+
_measureNaturalWidths({ sample = false } = {}) {
|
|
1350
1817
|
const headerTable = this._headerTableEl;
|
|
1351
1818
|
const bodyTable = this._tableEl;
|
|
1352
1819
|
if (!headerTable || !bodyTable) return null;
|
|
1353
|
-
const firstRow =
|
|
1820
|
+
const firstRow = this._firstBodyRow();
|
|
1354
1821
|
if (!firstRow) return null;
|
|
1355
1822
|
const bodyCells = firstRow.children;
|
|
1356
1823
|
if (!bodyCells.length) return null;
|
|
@@ -1358,10 +1825,13 @@ var DataTable = class {
|
|
|
1358
1825
|
for (const th of tr.children) this._clearCellWidth(th);
|
|
1359
1826
|
});
|
|
1360
1827
|
for (const td of bodyCells) this._clearCellWidth(td);
|
|
1828
|
+
for (const col of this._cols() || []) col.style.width = "";
|
|
1361
1829
|
headerTable.style.width = "";
|
|
1362
1830
|
bodyTable.style.width = "";
|
|
1363
1831
|
headerTable.classList.add("twm-dt-measuring");
|
|
1364
1832
|
bodyTable.classList.add("twm-dt-measuring");
|
|
1833
|
+
const sampled = sample ? this._virtSampleRows() : [];
|
|
1834
|
+
for (const tr of sampled) this._virt.tbody.appendChild(tr);
|
|
1365
1835
|
bodyTable.offsetWidth;
|
|
1366
1836
|
const labelRow = headerTable.querySelector("thead > tr");
|
|
1367
1837
|
const cols = bodyCells.length;
|
|
@@ -1372,12 +1842,61 @@ var DataTable = class {
|
|
|
1372
1842
|
const head = labelRow && labelRow.children[i] ? labelRow.children[i].getBoundingClientRect().width / zoom : 0;
|
|
1373
1843
|
natural[i] = Math.max(body, head);
|
|
1374
1844
|
}
|
|
1845
|
+
for (const tr of sampled) tr.remove();
|
|
1375
1846
|
headerTable.classList.remove("twm-dt-measuring");
|
|
1376
1847
|
bodyTable.classList.remove("twm-dt-measuring");
|
|
1377
1848
|
return natural;
|
|
1378
1849
|
}
|
|
1379
1850
|
/**
|
|
1380
|
-
*
|
|
1851
|
+
* The rows an explicit auto-size must measure that the window has not
|
|
1852
|
+
* drawn: for each column, the drawable row whose value is LONGEST as a
|
|
1853
|
+
* string, plus the last row (the widest row number). Built with
|
|
1854
|
+
* `_buildBodyRow` — so a consumer's `renderCell` draws them exactly as it
|
|
1855
|
+
* would on screen — and handed back detached, for `_measureNaturalWidths`
|
|
1856
|
+
* to put in and take out around its one reflow.
|
|
1857
|
+
*
|
|
1858
|
+
* The string length is a proxy, not a measurement: in a proportional font
|
|
1859
|
+
* forty `i`s are narrower than thirty `W`s. It is the proxy a spreadsheet's
|
|
1860
|
+
* own autofit falls short by, and it is right about the case that matters
|
|
1861
|
+
* here — a value five times longer than anything in the window. One pass
|
|
1862
|
+
* over the loaded rows, on a click and never on a scroll or a resize.
|
|
1863
|
+
*
|
|
1864
|
+
* @returns {HTMLTableRowElement[]}
|
|
1865
|
+
*/
|
|
1866
|
+
_virtSampleRows() {
|
|
1867
|
+
const v = this._virt;
|
|
1868
|
+
if (!v || !v.rows.length) return [];
|
|
1869
|
+
const longest = [];
|
|
1870
|
+
const at = [];
|
|
1871
|
+
let last = -1;
|
|
1872
|
+
for (let i = 0; i < v.rows.length; i++) {
|
|
1873
|
+
if (!(v.heights[i] > 0)) continue;
|
|
1874
|
+
last = i;
|
|
1875
|
+
const row = v.rows[i];
|
|
1876
|
+
if (!Array.isArray(row)) continue;
|
|
1877
|
+
for (let c = 0; c < row.length; c++) {
|
|
1878
|
+
const value = row[c];
|
|
1879
|
+
const len = value == null ? 0 : String(value).length;
|
|
1880
|
+
if (len > (longest[c] ?? -1)) {
|
|
1881
|
+
longest[c] = len;
|
|
1882
|
+
at[c] = i;
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1886
|
+
const want = new Set(at.filter((i) => i !== void 0));
|
|
1887
|
+
if (last >= 0) want.add(last);
|
|
1888
|
+
const rows = [];
|
|
1889
|
+
for (const i of want) {
|
|
1890
|
+
if (v.map.has(i)) continue;
|
|
1891
|
+
const tr = this._buildBodyRow(v.rows[i], v.globalOf(i), i);
|
|
1892
|
+
tr.setAttribute("aria-hidden", "true");
|
|
1893
|
+
rows.push(tr);
|
|
1894
|
+
}
|
|
1895
|
+
return rows;
|
|
1896
|
+
}
|
|
1897
|
+
/**
|
|
1898
|
+
* Fit one column to its widest value and pin it there — every loaded
|
|
1899
|
+
* row's, under `virtualize` (see `_virtSampleRows`), not only the window's.
|
|
1381
1900
|
*
|
|
1382
1901
|
* The gesture is a double-click on the column's resize grip, which is where
|
|
1383
1902
|
* every spreadsheet has put it — and the grip is this component's, which is
|
|
@@ -1392,7 +1911,7 @@ var DataTable = class {
|
|
|
1392
1911
|
* @returns {boolean} whether it had a layout to measure.
|
|
1393
1912
|
*/
|
|
1394
1913
|
autoSizeColumn(domIdx, opts = {}) {
|
|
1395
|
-
const natural = this._measureNaturalWidths();
|
|
1914
|
+
const natural = this._measureNaturalWidths({ sample: true });
|
|
1396
1915
|
if (!natural || domIdx < 0 || domIdx >= natural.length) return false;
|
|
1397
1916
|
this._colWidths[domIdx] = this._autoWidth(natural[domIdx], opts);
|
|
1398
1917
|
this._colWidthsSig = this._colSig();
|
|
@@ -1416,7 +1935,7 @@ var DataTable = class {
|
|
|
1416
1935
|
* already, so a button that did that would be a button that does nothing.
|
|
1417
1936
|
*/
|
|
1418
1937
|
autoSizeColumns(opts = {}) {
|
|
1419
|
-
const natural = this._measureNaturalWidths();
|
|
1938
|
+
const natural = this._measureNaturalWidths({ sample: true });
|
|
1420
1939
|
if (!natural) return false;
|
|
1421
1940
|
this._colWidths = {};
|
|
1422
1941
|
for (let i = 0; i < natural.length; i++) {
|
|
@@ -1465,17 +1984,18 @@ var DataTable = class {
|
|
|
1465
1984
|
const headerTable = this._headerTableEl;
|
|
1466
1985
|
const bodyTable = this._tableEl;
|
|
1467
1986
|
if (!headerTable || !bodyTable) return;
|
|
1468
|
-
const
|
|
1469
|
-
|
|
1470
|
-
const bodyCells = firstRow
|
|
1471
|
-
|
|
1987
|
+
const cols = this._cols();
|
|
1988
|
+
const firstRow = this._firstBodyRow();
|
|
1989
|
+
const bodyCells = cols ? [] : firstRow?.children;
|
|
1990
|
+
const n = cols ? cols.length : bodyCells?.length || 0;
|
|
1991
|
+
if (!n) return;
|
|
1472
1992
|
const headerRows = headerTable.querySelectorAll("thead > tr");
|
|
1473
|
-
const natural = this._allColumnsPinned(
|
|
1993
|
+
const natural = this._allColumnsPinned(n) ? new Array(n).fill(0) : this._measureNaturalWidths();
|
|
1474
1994
|
if (!natural) return;
|
|
1475
1995
|
const wrap = this._tableWrapEl;
|
|
1476
1996
|
const headerWrap = this._headerWrapEl;
|
|
1477
1997
|
const avail = wrap ? wrap.clientWidth : 0;
|
|
1478
|
-
const firstIdx = this.config.showRowNumbers &&
|
|
1998
|
+
const firstIdx = this.config.showRowNumbers && n > 1 ? 1 : 0;
|
|
1479
1999
|
const widths = this._fitColumnWidths(natural, avail, {
|
|
1480
2000
|
firstIdx,
|
|
1481
2001
|
overrides: this._colWidths,
|
|
@@ -1487,8 +2007,14 @@ var DataTable = class {
|
|
|
1487
2007
|
this._setCellWidth(tr.children[i], widths[i]);
|
|
1488
2008
|
}
|
|
1489
2009
|
});
|
|
1490
|
-
|
|
1491
|
-
|
|
2010
|
+
if (cols) {
|
|
2011
|
+
for (let i = 0; i < cols.length && i < widths.length; i++) {
|
|
2012
|
+
cols[i].style.width = `${widths[i]}px`;
|
|
2013
|
+
}
|
|
2014
|
+
} else {
|
|
2015
|
+
for (let i = 0; i < bodyCells.length && i < widths.length; i++) {
|
|
2016
|
+
this._setCellWidth(bodyCells[i], widths[i]);
|
|
2017
|
+
}
|
|
1492
2018
|
}
|
|
1493
2019
|
headerTable.style.tableLayout = "fixed";
|
|
1494
2020
|
bodyTable.style.tableLayout = "fixed";
|
|
@@ -1717,28 +2243,48 @@ var DataTable = class {
|
|
|
1717
2243
|
th.appendChild(grip);
|
|
1718
2244
|
});
|
|
1719
2245
|
}
|
|
2246
|
+
/**
|
|
2247
|
+
* CSS px per screen px. Content inside a zoomed tile reports rects and
|
|
2248
|
+
* pointer coordinates in screen pixels, while `style.width` is CSS pixels;
|
|
2249
|
+
* mixing them widened every column by the zoom factor on a mere click.
|
|
2250
|
+
*/
|
|
2251
|
+
_zoomFactor() {
|
|
2252
|
+
const el = this._tableWrapEl || this._tableEl;
|
|
2253
|
+
const css = el?.offsetWidth || 0;
|
|
2254
|
+
const screen = el?.getBoundingClientRect?.().width || 0;
|
|
2255
|
+
return css > 0 && screen > 0 ? screen / css : 1;
|
|
2256
|
+
}
|
|
1720
2257
|
_beginColResize(ev, domIdx) {
|
|
2258
|
+
if (ev.button != null && ev.button !== 0) return;
|
|
1721
2259
|
ev.preventDefault();
|
|
1722
2260
|
ev.stopPropagation();
|
|
1723
2261
|
const headerTable = this._headerTableEl;
|
|
1724
2262
|
const bodyTable = this._tableEl;
|
|
1725
2263
|
const headRow = headerTable && headerTable.querySelector("thead > tr");
|
|
1726
2264
|
if (!headRow) return;
|
|
1727
|
-
const startWidths = [...headRow.children].map(
|
|
1728
|
-
(c) => c.getBoundingClientRect().width
|
|
1729
|
-
);
|
|
1730
|
-
headerTable.style.tableLayout = "fixed";
|
|
1731
|
-
if (bodyTable) bodyTable.style.tableLayout = "fixed";
|
|
1732
|
-
startWidths.forEach((w, i) => this._pinColumnWidth(i, w));
|
|
1733
|
-
this._applyTableWidth();
|
|
1734
2265
|
const startX = ev.clientX;
|
|
2266
|
+
const THRESHOLD = 3;
|
|
1735
2267
|
const MIN = 40;
|
|
1736
|
-
|
|
2268
|
+
let startWidths = null;
|
|
2269
|
+
const begin = () => {
|
|
2270
|
+
const z = this._zoomFactor();
|
|
2271
|
+
startWidths = [...headRow.children].map((c) => {
|
|
2272
|
+
const set = parseFloat(c.style.width);
|
|
2273
|
+
return Number.isFinite(set) ? set : c.getBoundingClientRect().width / z;
|
|
2274
|
+
});
|
|
2275
|
+
headerTable.style.tableLayout = "fixed";
|
|
2276
|
+
if (bodyTable) bodyTable.style.tableLayout = "fixed";
|
|
2277
|
+
startWidths.forEach((w, i) => this._pinColumnWidth(i, w));
|
|
2278
|
+
this._applyTableWidth();
|
|
2279
|
+
document.body.classList.add("twm-dt-col-resizing");
|
|
2280
|
+
};
|
|
1737
2281
|
const onMove = (mv) => {
|
|
1738
|
-
const
|
|
1739
|
-
|
|
1740
|
-
Math.
|
|
1741
|
-
|
|
2282
|
+
const dx = mv.clientX - startX;
|
|
2283
|
+
if (!startWidths) {
|
|
2284
|
+
if (Math.abs(dx) < THRESHOLD) return;
|
|
2285
|
+
begin();
|
|
2286
|
+
}
|
|
2287
|
+
const w = Math.max(MIN, Math.round(startWidths[domIdx] + dx / this._zoomFactor()));
|
|
1742
2288
|
this._pinColumnWidth(domIdx, w);
|
|
1743
2289
|
this._applyTableWidth(domIdx);
|
|
1744
2290
|
this._syncHeaderScroll();
|
|
@@ -1746,6 +2292,7 @@ var DataTable = class {
|
|
|
1746
2292
|
const onUp = () => {
|
|
1747
2293
|
document.removeEventListener("mousemove", onMove);
|
|
1748
2294
|
document.removeEventListener("mouseup", onUp);
|
|
2295
|
+
if (!startWidths) return;
|
|
1749
2296
|
document.body.classList.remove("twm-dt-col-resizing");
|
|
1750
2297
|
this._updateCellTooltips();
|
|
1751
2298
|
this._savePersisted();
|
|
@@ -1765,7 +2312,12 @@ var DataTable = class {
|
|
|
1765
2312
|
headerTable.querySelectorAll("thead > tr").forEach((tr) => {
|
|
1766
2313
|
if (tr.children[i]) this._setCellWidth(tr.children[i], w);
|
|
1767
2314
|
});
|
|
1768
|
-
const
|
|
2315
|
+
const cols = this._cols();
|
|
2316
|
+
if (cols) {
|
|
2317
|
+
if (cols[i]) cols[i].style.width = `${w}px`;
|
|
2318
|
+
return;
|
|
2319
|
+
}
|
|
2320
|
+
const bodyRow = bodyTable && this._firstBodyRow();
|
|
1769
2321
|
if (bodyRow && bodyRow.children[i]) this._setCellWidth(bodyRow.children[i], w);
|
|
1770
2322
|
}
|
|
1771
2323
|
/** Size both tables so a widened column grows the table (→ horizontal
|
|
@@ -1814,10 +2366,12 @@ var DataTable = class {
|
|
|
1814
2366
|
/** Add a native `title` tooltip to any body cell whose text is
|
|
1815
2367
|
* clipped by its column width; remove ours once it fits again.
|
|
1816
2368
|
* Leaves caller-supplied titles (e.g. from renderCell) untouched. */
|
|
1817
|
-
_updateCellTooltips() {
|
|
2369
|
+
_updateCellTooltips(rows = null) {
|
|
1818
2370
|
const bodyTable = this._tableEl;
|
|
1819
2371
|
if (!bodyTable) return;
|
|
1820
|
-
bodyTable.querySelectorAll("tbody td")
|
|
2372
|
+
const cells = rows ? rows.flatMap((tr) => [...tr.children]) : bodyTable.querySelectorAll("tbody td");
|
|
2373
|
+
cells.forEach((td) => {
|
|
2374
|
+
if (td.parentElement?.classList.contains("twm-dt-spacer")) return;
|
|
1821
2375
|
const clipped = td.scrollWidth > td.clientWidth + 1;
|
|
1822
2376
|
if (clipped) {
|
|
1823
2377
|
if (!td.title) td.title = td.textContent;
|
|
@@ -2065,4 +2619,4 @@ export {
|
|
|
2065
2619
|
createRafResizeObserver,
|
|
2066
2620
|
DataTable
|
|
2067
2621
|
};
|
|
2068
|
-
//# sourceMappingURL=chunk-
|
|
2622
|
+
//# sourceMappingURL=chunk-CYTWMP2Y.js.map
|