@jh-grid/jhgrid-js 0.1.1 → 0.1.3
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 +12 -32
- package/dist/jhgrid.esm.js +74 -26
- package/dist/jhgrid.js +74 -26
- package/dist/jhgrid.min.js +10 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,38 +16,18 @@ performance, editing, filtering, and frozen columns, running in your browser rig
|
|
|
16
16
|
|
|
17
17
|
## Features
|
|
18
18
|
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
- **
|
|
26
|
-
- **
|
|
27
|
-
- **
|
|
28
|
-
- **
|
|
29
|
-
- **
|
|
30
|
-
- **
|
|
31
|
-
- **Ctrl+Z / Ctrl+Y**: undo / redo (cell edits, row/column add/delete)
|
|
32
|
-
- **Column validation**: declarative required/pattern/min/max/length/custom rules with red-border + tooltip error display (`min`/`max` compare chronologically on a `type: 'date'` column)
|
|
33
|
-
- **In-cell action buttons**: `type: 'button'` columns render a clickable pill per row (e.g. "Delete", "Approve") independent of `editableCols`
|
|
34
|
-
- **Date / rich-text / image cell types**: `type: 'date'` opens a native date/datetime picker; `type: 'richtext'` opens an inline bold/italic/underline/strikethrough editor; `type: 'image'` renders a cell image (`fit: 'cover' | 'contain'`, size-aware decoding, shared LRU cache), plus a pluggable `CellEditors`/`CellRenderers` registry (`registerCellEditor()`/`registerCellRenderer()`) for fully custom editors and renderers
|
|
35
|
-
- **Set filter**: checkbox list of a column's distinct values in the header filter panel (`setFilterValues()`)
|
|
36
|
-
- **Quick filter**: global cross-column search term (`setQuickFilter()` / `getQuickFilter()` / `clearQuickFilter()`)
|
|
37
|
-
- **Single-column sort**: `setSort()` / `removeSort()` / `clearSort()`
|
|
38
|
-
- **Row / column CRUD**: `addRow()`/`deleteRow()`/`undeleteRow()`, `addColumn()`/`deleteColumn()`/`undeleteColumn()`, with matching `getNew*()`/`getDeleted*()` accessors for diff-based saves
|
|
39
|
-
- **Column hide/show**: `hideColumn()` / `showColumn()` / `isColumnVisible()` / `getHiddenColumns()`, plus per-row/column resize (`setRowHeight()`, `autoFitColumns()`)
|
|
40
|
-
- **Multi-level header groups**: `columnDefs[].group` (or explicit `headerRows`) merges header cells across levels
|
|
41
|
-
- **Per-row / per-cell styling callbacks**: `rowHighlighter` / `cellBackground` for conditional formatting
|
|
42
|
-
- **State snapshot/restore**: `getState()` / `setState()` for saving and restoring grid state (filters, sort, column order/visibility, edits)
|
|
43
|
-
- **Built-in localization**: `locale` option with bundled `KO_I18N` / `JA_I18N` / `ZH_I18N` text packs, per-key `i18n` overrides, and locale-aware number/date/currency cell rendering
|
|
44
|
-
- **Accessibility**: ARIA labeling, keyboard-navigable header/row focus, and automatic high-contrast (`forced-colors`) theme remapping
|
|
45
|
-
- **CSV export + print preview**: `exportCsv()`, `printGrid()`
|
|
46
|
-
- **Arrow key navigation**: keyboard-driven cell movement
|
|
47
|
-
- **Enter / Tab**: commit edit and move to next row / column
|
|
48
|
-
- Text overflow with ellipsis (`…`): O(log n) binary search
|
|
49
|
-
- Fully themeable
|
|
50
|
-
- Zero dependencies
|
|
19
|
+
- **Canvas rendering + 2D virtualization** — smooth at 60/120/144Hz, HiDPI-aware
|
|
20
|
+
- **Large-data loading** — chunk-based async loading with prefetch & cache
|
|
21
|
+
- **Frozen columns & scrollbars** — left/right freezing with draggable vertical/horizontal scrollbars
|
|
22
|
+
- **Selection & row operations** — cell/range selection, single/multi row selection, row drag reorder
|
|
23
|
+
- **Editing** — inline editing, validation, undo/redo, TSV copy & paste
|
|
24
|
+
- **Rich cell types** — dropdown, multiselect, checkbox, date, richtext, image, button, plus custom editors/renderers
|
|
25
|
+
- **Filtering & sorting** — set filter, quick filter, single-column sort
|
|
26
|
+
- **CRUD & change tracking** — row/column add/delete with diff-based persistence
|
|
27
|
+
- **Headers & styling** — multi-level headers, conditional row/cell styling
|
|
28
|
+
- **State & export** — state snapshot/restore, CSV export, print preview
|
|
29
|
+
- **i18n & accessibility** — KO/JA/ZH localization, ARIA, keyboard navigation, high-contrast support
|
|
30
|
+
- **Zero dependencies & theming** — fully themeable with no runtime dependencies
|
|
51
31
|
|
|
52
32
|
---
|
|
53
33
|
|
package/dist/jhgrid.esm.js
CHANGED
|
@@ -4,6 +4,14 @@ var DataManager = class {
|
|
|
4
4
|
#chunkSize;
|
|
5
5
|
#maxChunks;
|
|
6
6
|
#cache = /* @__PURE__ */ new Map();
|
|
7
|
+
// 1-entry cache of the chunk getRow() touched last. A bulk row-major walk (e.g. clearing an
|
|
8
|
+
// entire selection) calls getRow() for every column of the same row back-to-back -- same
|
|
9
|
+
// chunkIdx every time -- and #chunkSize more rows after that before it changes, so this turns
|
|
10
|
+
// the overwhelming majority of getRow() calls into a single index comparison instead of a Map
|
|
11
|
+
// lookup plus the LRU delete+re-insert below. Must be invalidated everywhere #cache forgets a
|
|
12
|
+
// chunk (#evict, clear) so it can never serve an entry #cache itself no longer considers current.
|
|
13
|
+
#lastChunkIdx = -1;
|
|
14
|
+
#lastChunk = null;
|
|
7
15
|
#fetching = /* @__PURE__ */ new Set();
|
|
8
16
|
#failed = /* @__PURE__ */ new Set();
|
|
9
17
|
#failedTimers = /* @__PURE__ */ new Set();
|
|
@@ -37,10 +45,13 @@ var DataManager = class {
|
|
|
37
45
|
// re-request after onChunkLoaded).
|
|
38
46
|
getRow(rowIndex) {
|
|
39
47
|
const chunkIdx = Math.floor(rowIndex / this.#chunkSize);
|
|
48
|
+
if (chunkIdx === this.#lastChunkIdx) return this.#lastChunk[rowIndex % this.#chunkSize] ?? null;
|
|
40
49
|
if (this.#cache.has(chunkIdx)) {
|
|
41
50
|
const chunk = this.#cache.get(chunkIdx);
|
|
42
51
|
this.#cache.delete(chunkIdx);
|
|
43
52
|
this.#cache.set(chunkIdx, chunk);
|
|
53
|
+
this.#lastChunkIdx = chunkIdx;
|
|
54
|
+
this.#lastChunk = chunk;
|
|
44
55
|
return chunk[rowIndex % this.#chunkSize] ?? null;
|
|
45
56
|
}
|
|
46
57
|
this.#request(chunkIdx);
|
|
@@ -201,6 +212,10 @@ var DataManager = class {
|
|
|
201
212
|
while (this.#cache.size > this.#maxChunks) {
|
|
202
213
|
const oldest = this.#cache.keys().next().value;
|
|
203
214
|
this.#cache.delete(oldest);
|
|
215
|
+
if (oldest === this.#lastChunkIdx) {
|
|
216
|
+
this.#lastChunkIdx = -1;
|
|
217
|
+
this.#lastChunk = null;
|
|
218
|
+
}
|
|
204
219
|
}
|
|
205
220
|
}
|
|
206
221
|
// Returning `false` from `callback` stops the walk. Callers that are looking for an answer
|
|
@@ -227,6 +242,8 @@ var DataManager = class {
|
|
|
227
242
|
this.#failed.clear();
|
|
228
243
|
this.#failedTimers.forEach(clearTimeout);
|
|
229
244
|
this.#failedTimers.clear();
|
|
245
|
+
this.#lastChunkIdx = -1;
|
|
246
|
+
this.#lastChunk = null;
|
|
230
247
|
}
|
|
231
248
|
};
|
|
232
249
|
|
|
@@ -1347,7 +1364,9 @@ var Renderer = class _Renderer {
|
|
|
1347
1364
|
colPositions,
|
|
1348
1365
|
hiddenNeighbors,
|
|
1349
1366
|
headerCheckboxCols,
|
|
1350
|
-
sel
|
|
1367
|
+
sel,
|
|
1368
|
+
frozenWidth,
|
|
1369
|
+
vpW
|
|
1351
1370
|
);
|
|
1352
1371
|
ctx.restore();
|
|
1353
1372
|
if (frozenCount > 0) {
|
|
@@ -1375,7 +1394,9 @@ var Renderer = class _Renderer {
|
|
|
1375
1394
|
colPositions,
|
|
1376
1395
|
hiddenNeighbors,
|
|
1377
1396
|
headerCheckboxCols,
|
|
1378
|
-
sel
|
|
1397
|
+
sel,
|
|
1398
|
+
0,
|
|
1399
|
+
frozenWidth
|
|
1379
1400
|
);
|
|
1380
1401
|
ctx.restore();
|
|
1381
1402
|
}
|
|
@@ -1404,7 +1425,9 @@ var Renderer = class _Renderer {
|
|
|
1404
1425
|
colPositions,
|
|
1405
1426
|
hiddenNeighbors,
|
|
1406
1427
|
headerCheckboxCols,
|
|
1407
|
-
sel
|
|
1428
|
+
sel,
|
|
1429
|
+
rightX,
|
|
1430
|
+
frozenRightWidth
|
|
1408
1431
|
);
|
|
1409
1432
|
ctx.restore();
|
|
1410
1433
|
}
|
|
@@ -1869,7 +1892,7 @@ var Renderer = class _Renderer {
|
|
|
1869
1892
|
#colX(c, frozenCount, frozenWidth, frozenRightCount, rightX, scrollLeft, colPositions) {
|
|
1870
1893
|
return colScreenX(c, { frozenCount, frozenWidth, frozenRightCount, rightX, colPositions }, scrollLeft);
|
|
1871
1894
|
}
|
|
1872
|
-
#drawHeader(labels, aligns, columns, sorts, filters, startCol, endCol, scrollLeft, totalH, rowH, headerRows, theme, frozenCount, frozenWidth, frozenRightCount, rightX, colPositions, hiddenNeighbors, headerCheckboxCols, sel) {
|
|
1895
|
+
#drawHeader(labels, aligns, columns, sorts, filters, startCol, endCol, scrollLeft, totalH, rowH, headerRows, theme, frozenCount, frozenWidth, frozenRightCount, rightX, colPositions, hiddenNeighbors, headerCheckboxCols, sel, clipX = 0, clipW = Infinity) {
|
|
1873
1896
|
if (startCol > endCol) return;
|
|
1874
1897
|
const ctx = this.#ctx;
|
|
1875
1898
|
const padding = theme.cellPadding;
|
|
@@ -1943,6 +1966,13 @@ var Renderer = class _Renderer {
|
|
|
1943
1966
|
const visEndCol = Math.min(cell.col + cell.colspan - 1, endCol);
|
|
1944
1967
|
textCx = colPositions[visStartCol] + xOffset;
|
|
1945
1968
|
textCw = colPositions[visEndCol + 1] - colPositions[visStartCol];
|
|
1969
|
+
const clipRight = clipX + clipW;
|
|
1970
|
+
if (textCx < clipX) {
|
|
1971
|
+
textCw -= clipX - textCx;
|
|
1972
|
+
textCx = clipX;
|
|
1973
|
+
}
|
|
1974
|
+
if (textCx + textCw > clipRight) textCw = clipRight - textCx;
|
|
1975
|
+
textCw = Math.max(0, textCw);
|
|
1946
1976
|
}
|
|
1947
1977
|
const align = cell.isLeaf ? aligns?.[cell.col] ?? "left" : cell.align ?? "center";
|
|
1948
1978
|
const iconRsv = cell.isLeaf ? FILTER_ICON_W + 4 : 0;
|
|
@@ -2322,7 +2352,7 @@ var Renderer = class _Renderer {
|
|
|
2322
2352
|
}
|
|
2323
2353
|
ctx.restore();
|
|
2324
2354
|
}
|
|
2325
|
-
#drawScrollbars({ v, h, SB, vSB = SB, hSB = SB, frozenWidth = 0, frozenRightWidth = 0, rightX = 0 }, theme, W, H) {
|
|
2355
|
+
#drawScrollbars({ v, h, SB, vSB = SB, hSB = SB, frozenWidth = 0, frozenRightWidth = 0, rightX = 0, headerH = 0 }, theme, W, H) {
|
|
2326
2356
|
if (!vSB && !hSB) return;
|
|
2327
2357
|
const ctx = this.#ctx;
|
|
2328
2358
|
const r = theme.scrollbarRadius;
|
|
@@ -2330,6 +2360,7 @@ var Renderer = class _Renderer {
|
|
|
2330
2360
|
if (vSB > 0 && hSB > 0) ctx.fillRect(W - vSB, H - hSB, vSB, hSB);
|
|
2331
2361
|
if (hSB > 0 && frozenWidth > 0) ctx.fillRect(0, H - hSB, frozenWidth, hSB);
|
|
2332
2362
|
if (hSB > 0 && frozenRightWidth > 0) ctx.fillRect(rightX, H - hSB, frozenRightWidth, hSB);
|
|
2363
|
+
if (vSB > 0 && headerH > 0) ctx.fillRect(W - vSB, 0, vSB, headerH);
|
|
2333
2364
|
ctx.fillRect(v.x, v.y, v.w, v.h);
|
|
2334
2365
|
ctx.fillStyle = theme.scrollbarThumb;
|
|
2335
2366
|
this.#roundRect(v.x + 2, v.thumbY + 2, v.w - 4, v.thumbH - 4, r);
|
|
@@ -4785,7 +4816,11 @@ function buildFilterPanelEl({
|
|
|
4785
4816
|
clearTimeout(debounce);
|
|
4786
4817
|
seq++;
|
|
4787
4818
|
});
|
|
4788
|
-
readTags = () =>
|
|
4819
|
+
readTags = () => {
|
|
4820
|
+
const pending = tagInput.value.trim();
|
|
4821
|
+
if (pending && state.contains === null && state.tags.length === 0) setContains(pending);
|
|
4822
|
+
return state.contains !== null ? { contains: state.contains } : { values: [...state.tags] };
|
|
4823
|
+
};
|
|
4789
4824
|
}
|
|
4790
4825
|
let valuesSection = null;
|
|
4791
4826
|
const valueCheckboxes = [];
|
|
@@ -4846,11 +4881,13 @@ function buildFilterPanelEl({
|
|
|
4846
4881
|
});
|
|
4847
4882
|
valueCheckboxes.forEach((cb) => cb.addEventListener("change", syncSelectAll));
|
|
4848
4883
|
valuesSearch.addEventListener("input", () => {
|
|
4884
|
+
const pristine = valueCheckboxes.every((cb) => cb.checked);
|
|
4849
4885
|
const q = valuesSearch.value.trim().toLowerCase();
|
|
4850
4886
|
let anyVisible = false;
|
|
4851
4887
|
for (const r of valueRows) {
|
|
4852
4888
|
const match = !q || r.text.toLowerCase().includes(q);
|
|
4853
4889
|
r.row.style.display = match ? "flex" : "none";
|
|
4890
|
+
if (pristine) r.cb.checked = match;
|
|
4854
4891
|
anyVisible = anyVisible || match;
|
|
4855
4892
|
}
|
|
4856
4893
|
noMatch.style.display = anyVisible ? "none" : "block";
|
|
@@ -7392,7 +7429,14 @@ var JHGrid = class _JHGrid {
|
|
|
7392
7429
|
this._pendingLocalState = null;
|
|
7393
7430
|
return Promise.resolve();
|
|
7394
7431
|
}
|
|
7395
|
-
this.
|
|
7432
|
+
const fetchData = this._isLocalData ? /* @__PURE__ */ (() => {
|
|
7433
|
+
let all = null;
|
|
7434
|
+
return (page, size) => {
|
|
7435
|
+
all ??= this._opts.fetchData(0, Number.MAX_SAFE_INTEGER, state).then((r) => r.rows);
|
|
7436
|
+
return all.then((rows) => ({ rows: rows.slice(page * size, page * size + size) }));
|
|
7437
|
+
};
|
|
7438
|
+
})() : (page, size) => this._opts.fetchData(page, size, state);
|
|
7439
|
+
this._dm.setFetch(fetchData);
|
|
7396
7440
|
return this._boot(state);
|
|
7397
7441
|
}
|
|
7398
7442
|
// Where the sweep sits across the loading bars, 0..1, or null to leave them flat.
|
|
@@ -8786,19 +8830,19 @@ var JHGrid = class _JHGrid {
|
|
|
8786
8830
|
}
|
|
8787
8831
|
_clearSelection() {
|
|
8788
8832
|
if (!this._sel) return;
|
|
8789
|
-
const
|
|
8790
|
-
|
|
8791
|
-
|
|
8792
|
-
|
|
8793
|
-
|
|
8794
|
-
|
|
8795
|
-
|
|
8833
|
+
const single = this._sel.type === "single";
|
|
8834
|
+
const r1 = single ? this._sel.row : this._sel.r1;
|
|
8835
|
+
const r2 = single ? this._sel.row : this._sel.r2;
|
|
8836
|
+
const c1 = single ? this._sel.col : this._sel.c1;
|
|
8837
|
+
const c2 = single ? this._sel.col : this._sel.c2;
|
|
8838
|
+
const editableFields = [];
|
|
8839
|
+
for (let c = c1; c <= c2; c++) {
|
|
8840
|
+
if (this._isEditable(c)) editableFields.push(this._columns[c]);
|
|
8841
|
+
}
|
|
8796
8842
|
this._editTxnBegin();
|
|
8797
|
-
|
|
8798
|
-
|
|
8799
|
-
|
|
8800
|
-
this._setEdit(r, field, "");
|
|
8801
|
-
});
|
|
8843
|
+
for (let r = r1; r <= r2; r++) {
|
|
8844
|
+
for (const field of editableFields) this._setEdit(r, field, "");
|
|
8845
|
+
}
|
|
8802
8846
|
this._editTxnCommit();
|
|
8803
8847
|
this._draw();
|
|
8804
8848
|
}
|
|
@@ -8983,11 +9027,15 @@ var JHGrid = class _JHGrid {
|
|
|
8983
9027
|
const v = data[field];
|
|
8984
9028
|
return v != null ? String(v) : "";
|
|
8985
9029
|
}
|
|
8986
|
-
// Re-runs validation for one cell and updates the validator's invalid-cell set.
|
|
9030
|
+
// Re-runs validation for one cell and updates the validator's invalid-cell set. Takes a
|
|
9031
|
+
// "row_field" key -- for callers that only have that (iterating an _edits-shaped Map). Callers
|
|
9032
|
+
// that already have row/field apart (_setEdit, validateAll) should call _revalidateRowField()
|
|
9033
|
+
// directly instead of paying to stringify them together here just to split them back apart.
|
|
8987
9034
|
_revalidateKey(key) {
|
|
8988
9035
|
const u = key.indexOf("_");
|
|
8989
|
-
|
|
8990
|
-
|
|
9036
|
+
this._revalidateRowField(Number(key.slice(0, u)), key.slice(u + 1));
|
|
9037
|
+
}
|
|
9038
|
+
_revalidateRowField(row, field) {
|
|
8991
9039
|
this._validator.revalidate(row, field, this._resolveCellStringValue(row, field));
|
|
8992
9040
|
}
|
|
8993
9041
|
// Rebuilds invalid-cell state from scratch based on the current _edits map —
|
|
@@ -9022,11 +9070,11 @@ var JHGrid = class _JHGrid {
|
|
|
9022
9070
|
const validatedFields = this._columns.filter((f) => this._colDefMap.get(f)?.validation);
|
|
9023
9071
|
if (validatedFields.length > 0) {
|
|
9024
9072
|
this._dm.forEachLoaded((_, rowIndex) => {
|
|
9025
|
-
validatedFields.forEach((field) => this.
|
|
9073
|
+
validatedFields.forEach((field) => this._revalidateRowField(rowIndex, field));
|
|
9026
9074
|
});
|
|
9027
9075
|
this._localRows.forEach((_, i) => {
|
|
9028
9076
|
const r = this._rowPlan.visualOfLocal(i);
|
|
9029
|
-
validatedFields.forEach((field) => this.
|
|
9077
|
+
validatedFields.forEach((field) => this._revalidateRowField(r, field));
|
|
9030
9078
|
});
|
|
9031
9079
|
}
|
|
9032
9080
|
this._draw();
|
|
@@ -9053,7 +9101,7 @@ var JHGrid = class _JHGrid {
|
|
|
9053
9101
|
this._edits.set(key, val);
|
|
9054
9102
|
this._editedRows.add(row);
|
|
9055
9103
|
this._opts.onCellChange?.({ row, field, newValue: val, oldValue });
|
|
9056
|
-
this.
|
|
9104
|
+
this._revalidateRowField(row, field);
|
|
9057
9105
|
this._growRowForMultilineValue(row, val);
|
|
9058
9106
|
}
|
|
9059
9107
|
// Excel grows a row's height the moment a cell picks up a line break -- typed (Alt+Enter) or
|
|
@@ -11917,7 +11965,7 @@ var JHGrid = class _JHGrid {
|
|
|
11917
11965
|
JHGrid.use(RowSelectionPlugin);
|
|
11918
11966
|
|
|
11919
11967
|
// index.js
|
|
11920
|
-
var VERSION = "0.1.
|
|
11968
|
+
var VERSION = "0.1.3";
|
|
11921
11969
|
var SUPPORTED_BROWSERS = {
|
|
11922
11970
|
chrome: 99,
|
|
11923
11971
|
edge: 99,
|
package/dist/jhgrid.js
CHANGED
|
@@ -44,6 +44,14 @@ var JHGrid = (() => {
|
|
|
44
44
|
#chunkSize;
|
|
45
45
|
#maxChunks;
|
|
46
46
|
#cache = /* @__PURE__ */ new Map();
|
|
47
|
+
// 1-entry cache of the chunk getRow() touched last. A bulk row-major walk (e.g. clearing an
|
|
48
|
+
// entire selection) calls getRow() for every column of the same row back-to-back -- same
|
|
49
|
+
// chunkIdx every time -- and #chunkSize more rows after that before it changes, so this turns
|
|
50
|
+
// the overwhelming majority of getRow() calls into a single index comparison instead of a Map
|
|
51
|
+
// lookup plus the LRU delete+re-insert below. Must be invalidated everywhere #cache forgets a
|
|
52
|
+
// chunk (#evict, clear) so it can never serve an entry #cache itself no longer considers current.
|
|
53
|
+
#lastChunkIdx = -1;
|
|
54
|
+
#lastChunk = null;
|
|
47
55
|
#fetching = /* @__PURE__ */ new Set();
|
|
48
56
|
#failed = /* @__PURE__ */ new Set();
|
|
49
57
|
#failedTimers = /* @__PURE__ */ new Set();
|
|
@@ -77,10 +85,13 @@ var JHGrid = (() => {
|
|
|
77
85
|
// re-request after onChunkLoaded).
|
|
78
86
|
getRow(rowIndex) {
|
|
79
87
|
const chunkIdx = Math.floor(rowIndex / this.#chunkSize);
|
|
88
|
+
if (chunkIdx === this.#lastChunkIdx) return this.#lastChunk[rowIndex % this.#chunkSize] ?? null;
|
|
80
89
|
if (this.#cache.has(chunkIdx)) {
|
|
81
90
|
const chunk = this.#cache.get(chunkIdx);
|
|
82
91
|
this.#cache.delete(chunkIdx);
|
|
83
92
|
this.#cache.set(chunkIdx, chunk);
|
|
93
|
+
this.#lastChunkIdx = chunkIdx;
|
|
94
|
+
this.#lastChunk = chunk;
|
|
84
95
|
return chunk[rowIndex % this.#chunkSize] ?? null;
|
|
85
96
|
}
|
|
86
97
|
this.#request(chunkIdx);
|
|
@@ -241,6 +252,10 @@ var JHGrid = (() => {
|
|
|
241
252
|
while (this.#cache.size > this.#maxChunks) {
|
|
242
253
|
const oldest = this.#cache.keys().next().value;
|
|
243
254
|
this.#cache.delete(oldest);
|
|
255
|
+
if (oldest === this.#lastChunkIdx) {
|
|
256
|
+
this.#lastChunkIdx = -1;
|
|
257
|
+
this.#lastChunk = null;
|
|
258
|
+
}
|
|
244
259
|
}
|
|
245
260
|
}
|
|
246
261
|
// Returning `false` from `callback` stops the walk. Callers that are looking for an answer
|
|
@@ -267,6 +282,8 @@ var JHGrid = (() => {
|
|
|
267
282
|
this.#failed.clear();
|
|
268
283
|
this.#failedTimers.forEach(clearTimeout);
|
|
269
284
|
this.#failedTimers.clear();
|
|
285
|
+
this.#lastChunkIdx = -1;
|
|
286
|
+
this.#lastChunk = null;
|
|
270
287
|
}
|
|
271
288
|
};
|
|
272
289
|
|
|
@@ -1387,7 +1404,9 @@ var JHGrid = (() => {
|
|
|
1387
1404
|
colPositions,
|
|
1388
1405
|
hiddenNeighbors,
|
|
1389
1406
|
headerCheckboxCols,
|
|
1390
|
-
sel
|
|
1407
|
+
sel,
|
|
1408
|
+
frozenWidth,
|
|
1409
|
+
vpW
|
|
1391
1410
|
);
|
|
1392
1411
|
ctx.restore();
|
|
1393
1412
|
if (frozenCount > 0) {
|
|
@@ -1415,7 +1434,9 @@ var JHGrid = (() => {
|
|
|
1415
1434
|
colPositions,
|
|
1416
1435
|
hiddenNeighbors,
|
|
1417
1436
|
headerCheckboxCols,
|
|
1418
|
-
sel
|
|
1437
|
+
sel,
|
|
1438
|
+
0,
|
|
1439
|
+
frozenWidth
|
|
1419
1440
|
);
|
|
1420
1441
|
ctx.restore();
|
|
1421
1442
|
}
|
|
@@ -1444,7 +1465,9 @@ var JHGrid = (() => {
|
|
|
1444
1465
|
colPositions,
|
|
1445
1466
|
hiddenNeighbors,
|
|
1446
1467
|
headerCheckboxCols,
|
|
1447
|
-
sel
|
|
1468
|
+
sel,
|
|
1469
|
+
rightX,
|
|
1470
|
+
frozenRightWidth
|
|
1448
1471
|
);
|
|
1449
1472
|
ctx.restore();
|
|
1450
1473
|
}
|
|
@@ -1909,7 +1932,7 @@ var JHGrid = (() => {
|
|
|
1909
1932
|
#colX(c, frozenCount, frozenWidth, frozenRightCount, rightX, scrollLeft, colPositions) {
|
|
1910
1933
|
return colScreenX(c, { frozenCount, frozenWidth, frozenRightCount, rightX, colPositions }, scrollLeft);
|
|
1911
1934
|
}
|
|
1912
|
-
#drawHeader(labels, aligns, columns, sorts, filters, startCol, endCol, scrollLeft, totalH, rowH, headerRows, theme, frozenCount, frozenWidth, frozenRightCount, rightX, colPositions, hiddenNeighbors, headerCheckboxCols, sel) {
|
|
1935
|
+
#drawHeader(labels, aligns, columns, sorts, filters, startCol, endCol, scrollLeft, totalH, rowH, headerRows, theme, frozenCount, frozenWidth, frozenRightCount, rightX, colPositions, hiddenNeighbors, headerCheckboxCols, sel, clipX = 0, clipW = Infinity) {
|
|
1913
1936
|
if (startCol > endCol) return;
|
|
1914
1937
|
const ctx = this.#ctx;
|
|
1915
1938
|
const padding = theme.cellPadding;
|
|
@@ -1983,6 +2006,13 @@ var JHGrid = (() => {
|
|
|
1983
2006
|
const visEndCol = Math.min(cell.col + cell.colspan - 1, endCol);
|
|
1984
2007
|
textCx = colPositions[visStartCol] + xOffset;
|
|
1985
2008
|
textCw = colPositions[visEndCol + 1] - colPositions[visStartCol];
|
|
2009
|
+
const clipRight = clipX + clipW;
|
|
2010
|
+
if (textCx < clipX) {
|
|
2011
|
+
textCw -= clipX - textCx;
|
|
2012
|
+
textCx = clipX;
|
|
2013
|
+
}
|
|
2014
|
+
if (textCx + textCw > clipRight) textCw = clipRight - textCx;
|
|
2015
|
+
textCw = Math.max(0, textCw);
|
|
1986
2016
|
}
|
|
1987
2017
|
const align = cell.isLeaf ? aligns?.[cell.col] ?? "left" : cell.align ?? "center";
|
|
1988
2018
|
const iconRsv = cell.isLeaf ? FILTER_ICON_W + 4 : 0;
|
|
@@ -2362,7 +2392,7 @@ var JHGrid = (() => {
|
|
|
2362
2392
|
}
|
|
2363
2393
|
ctx.restore();
|
|
2364
2394
|
}
|
|
2365
|
-
#drawScrollbars({ v, h, SB, vSB = SB, hSB = SB, frozenWidth = 0, frozenRightWidth = 0, rightX = 0 }, theme, W, H) {
|
|
2395
|
+
#drawScrollbars({ v, h, SB, vSB = SB, hSB = SB, frozenWidth = 0, frozenRightWidth = 0, rightX = 0, headerH = 0 }, theme, W, H) {
|
|
2366
2396
|
if (!vSB && !hSB) return;
|
|
2367
2397
|
const ctx = this.#ctx;
|
|
2368
2398
|
const r = theme.scrollbarRadius;
|
|
@@ -2370,6 +2400,7 @@ var JHGrid = (() => {
|
|
|
2370
2400
|
if (vSB > 0 && hSB > 0) ctx.fillRect(W - vSB, H - hSB, vSB, hSB);
|
|
2371
2401
|
if (hSB > 0 && frozenWidth > 0) ctx.fillRect(0, H - hSB, frozenWidth, hSB);
|
|
2372
2402
|
if (hSB > 0 && frozenRightWidth > 0) ctx.fillRect(rightX, H - hSB, frozenRightWidth, hSB);
|
|
2403
|
+
if (vSB > 0 && headerH > 0) ctx.fillRect(W - vSB, 0, vSB, headerH);
|
|
2373
2404
|
ctx.fillRect(v.x, v.y, v.w, v.h);
|
|
2374
2405
|
ctx.fillStyle = theme.scrollbarThumb;
|
|
2375
2406
|
this.#roundRect(v.x + 2, v.thumbY + 2, v.w - 4, v.thumbH - 4, r);
|
|
@@ -4825,7 +4856,11 @@ var JHGrid = (() => {
|
|
|
4825
4856
|
clearTimeout(debounce);
|
|
4826
4857
|
seq++;
|
|
4827
4858
|
});
|
|
4828
|
-
readTags = () =>
|
|
4859
|
+
readTags = () => {
|
|
4860
|
+
const pending = tagInput.value.trim();
|
|
4861
|
+
if (pending && state.contains === null && state.tags.length === 0) setContains(pending);
|
|
4862
|
+
return state.contains !== null ? { contains: state.contains } : { values: [...state.tags] };
|
|
4863
|
+
};
|
|
4829
4864
|
}
|
|
4830
4865
|
let valuesSection = null;
|
|
4831
4866
|
const valueCheckboxes = [];
|
|
@@ -4886,11 +4921,13 @@ var JHGrid = (() => {
|
|
|
4886
4921
|
});
|
|
4887
4922
|
valueCheckboxes.forEach((cb) => cb.addEventListener("change", syncSelectAll));
|
|
4888
4923
|
valuesSearch.addEventListener("input", () => {
|
|
4924
|
+
const pristine = valueCheckboxes.every((cb) => cb.checked);
|
|
4889
4925
|
const q = valuesSearch.value.trim().toLowerCase();
|
|
4890
4926
|
let anyVisible = false;
|
|
4891
4927
|
for (const r of valueRows) {
|
|
4892
4928
|
const match = !q || r.text.toLowerCase().includes(q);
|
|
4893
4929
|
r.row.style.display = match ? "flex" : "none";
|
|
4930
|
+
if (pristine) r.cb.checked = match;
|
|
4894
4931
|
anyVisible = anyVisible || match;
|
|
4895
4932
|
}
|
|
4896
4933
|
noMatch.style.display = anyVisible ? "none" : "block";
|
|
@@ -7432,7 +7469,14 @@ ${title ? `<h2>${esc(title)}</h2>` : ""}
|
|
|
7432
7469
|
this._pendingLocalState = null;
|
|
7433
7470
|
return Promise.resolve();
|
|
7434
7471
|
}
|
|
7435
|
-
this.
|
|
7472
|
+
const fetchData = this._isLocalData ? /* @__PURE__ */ (() => {
|
|
7473
|
+
let all = null;
|
|
7474
|
+
return (page, size) => {
|
|
7475
|
+
all ??= this._opts.fetchData(0, Number.MAX_SAFE_INTEGER, state).then((r) => r.rows);
|
|
7476
|
+
return all.then((rows) => ({ rows: rows.slice(page * size, page * size + size) }));
|
|
7477
|
+
};
|
|
7478
|
+
})() : (page, size) => this._opts.fetchData(page, size, state);
|
|
7479
|
+
this._dm.setFetch(fetchData);
|
|
7436
7480
|
return this._boot(state);
|
|
7437
7481
|
}
|
|
7438
7482
|
// Where the sweep sits across the loading bars, 0..1, or null to leave them flat.
|
|
@@ -8826,19 +8870,19 @@ ${title ? `<h2>${esc(title)}</h2>` : ""}
|
|
|
8826
8870
|
}
|
|
8827
8871
|
_clearSelection() {
|
|
8828
8872
|
if (!this._sel) return;
|
|
8829
|
-
const
|
|
8830
|
-
|
|
8831
|
-
|
|
8832
|
-
|
|
8833
|
-
|
|
8834
|
-
|
|
8835
|
-
|
|
8873
|
+
const single = this._sel.type === "single";
|
|
8874
|
+
const r1 = single ? this._sel.row : this._sel.r1;
|
|
8875
|
+
const r2 = single ? this._sel.row : this._sel.r2;
|
|
8876
|
+
const c1 = single ? this._sel.col : this._sel.c1;
|
|
8877
|
+
const c2 = single ? this._sel.col : this._sel.c2;
|
|
8878
|
+
const editableFields = [];
|
|
8879
|
+
for (let c = c1; c <= c2; c++) {
|
|
8880
|
+
if (this._isEditable(c)) editableFields.push(this._columns[c]);
|
|
8881
|
+
}
|
|
8836
8882
|
this._editTxnBegin();
|
|
8837
|
-
|
|
8838
|
-
|
|
8839
|
-
|
|
8840
|
-
this._setEdit(r, field, "");
|
|
8841
|
-
});
|
|
8883
|
+
for (let r = r1; r <= r2; r++) {
|
|
8884
|
+
for (const field of editableFields) this._setEdit(r, field, "");
|
|
8885
|
+
}
|
|
8842
8886
|
this._editTxnCommit();
|
|
8843
8887
|
this._draw();
|
|
8844
8888
|
}
|
|
@@ -9023,11 +9067,15 @@ ${title ? `<h2>${esc(title)}</h2>` : ""}
|
|
|
9023
9067
|
const v = data[field];
|
|
9024
9068
|
return v != null ? String(v) : "";
|
|
9025
9069
|
}
|
|
9026
|
-
// Re-runs validation for one cell and updates the validator's invalid-cell set.
|
|
9070
|
+
// Re-runs validation for one cell and updates the validator's invalid-cell set. Takes a
|
|
9071
|
+
// "row_field" key -- for callers that only have that (iterating an _edits-shaped Map). Callers
|
|
9072
|
+
// that already have row/field apart (_setEdit, validateAll) should call _revalidateRowField()
|
|
9073
|
+
// directly instead of paying to stringify them together here just to split them back apart.
|
|
9027
9074
|
_revalidateKey(key) {
|
|
9028
9075
|
const u = key.indexOf("_");
|
|
9029
|
-
|
|
9030
|
-
|
|
9076
|
+
this._revalidateRowField(Number(key.slice(0, u)), key.slice(u + 1));
|
|
9077
|
+
}
|
|
9078
|
+
_revalidateRowField(row, field) {
|
|
9031
9079
|
this._validator.revalidate(row, field, this._resolveCellStringValue(row, field));
|
|
9032
9080
|
}
|
|
9033
9081
|
// Rebuilds invalid-cell state from scratch based on the current _edits map —
|
|
@@ -9062,11 +9110,11 @@ ${title ? `<h2>${esc(title)}</h2>` : ""}
|
|
|
9062
9110
|
const validatedFields = this._columns.filter((f) => this._colDefMap.get(f)?.validation);
|
|
9063
9111
|
if (validatedFields.length > 0) {
|
|
9064
9112
|
this._dm.forEachLoaded((_, rowIndex) => {
|
|
9065
|
-
validatedFields.forEach((field) => this.
|
|
9113
|
+
validatedFields.forEach((field) => this._revalidateRowField(rowIndex, field));
|
|
9066
9114
|
});
|
|
9067
9115
|
this._localRows.forEach((_, i) => {
|
|
9068
9116
|
const r = this._rowPlan.visualOfLocal(i);
|
|
9069
|
-
validatedFields.forEach((field) => this.
|
|
9117
|
+
validatedFields.forEach((field) => this._revalidateRowField(r, field));
|
|
9070
9118
|
});
|
|
9071
9119
|
}
|
|
9072
9120
|
this._draw();
|
|
@@ -9093,7 +9141,7 @@ ${title ? `<h2>${esc(title)}</h2>` : ""}
|
|
|
9093
9141
|
this._edits.set(key, val);
|
|
9094
9142
|
this._editedRows.add(row);
|
|
9095
9143
|
this._opts.onCellChange?.({ row, field, newValue: val, oldValue });
|
|
9096
|
-
this.
|
|
9144
|
+
this._revalidateRowField(row, field);
|
|
9097
9145
|
this._growRowForMultilineValue(row, val);
|
|
9098
9146
|
}
|
|
9099
9147
|
// Excel grows a row's height the moment a cell picks up a line break -- typed (Alt+Enter) or
|
|
@@ -11957,7 +12005,7 @@ ${title ? `<h2>${esc(title)}</h2>` : ""}
|
|
|
11957
12005
|
JHGrid.use(RowSelectionPlugin);
|
|
11958
12006
|
|
|
11959
12007
|
// index.js
|
|
11960
|
-
var VERSION = "0.1.
|
|
12008
|
+
var VERSION = "0.1.3";
|
|
11961
12009
|
var SUPPORTED_BROWSERS = {
|
|
11962
12010
|
chrome: 99,
|
|
11963
12011
|
edge: 99,
|