@jh-grid/jhgrid-js 0.1.2 → 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 +35 -8
- package/dist/jhgrid.js +35 -8
- 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
|
@@ -1364,7 +1364,9 @@ var Renderer = class _Renderer {
|
|
|
1364
1364
|
colPositions,
|
|
1365
1365
|
hiddenNeighbors,
|
|
1366
1366
|
headerCheckboxCols,
|
|
1367
|
-
sel
|
|
1367
|
+
sel,
|
|
1368
|
+
frozenWidth,
|
|
1369
|
+
vpW
|
|
1368
1370
|
);
|
|
1369
1371
|
ctx.restore();
|
|
1370
1372
|
if (frozenCount > 0) {
|
|
@@ -1392,7 +1394,9 @@ var Renderer = class _Renderer {
|
|
|
1392
1394
|
colPositions,
|
|
1393
1395
|
hiddenNeighbors,
|
|
1394
1396
|
headerCheckboxCols,
|
|
1395
|
-
sel
|
|
1397
|
+
sel,
|
|
1398
|
+
0,
|
|
1399
|
+
frozenWidth
|
|
1396
1400
|
);
|
|
1397
1401
|
ctx.restore();
|
|
1398
1402
|
}
|
|
@@ -1421,7 +1425,9 @@ var Renderer = class _Renderer {
|
|
|
1421
1425
|
colPositions,
|
|
1422
1426
|
hiddenNeighbors,
|
|
1423
1427
|
headerCheckboxCols,
|
|
1424
|
-
sel
|
|
1428
|
+
sel,
|
|
1429
|
+
rightX,
|
|
1430
|
+
frozenRightWidth
|
|
1425
1431
|
);
|
|
1426
1432
|
ctx.restore();
|
|
1427
1433
|
}
|
|
@@ -1886,7 +1892,7 @@ var Renderer = class _Renderer {
|
|
|
1886
1892
|
#colX(c, frozenCount, frozenWidth, frozenRightCount, rightX, scrollLeft, colPositions) {
|
|
1887
1893
|
return colScreenX(c, { frozenCount, frozenWidth, frozenRightCount, rightX, colPositions }, scrollLeft);
|
|
1888
1894
|
}
|
|
1889
|
-
#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) {
|
|
1890
1896
|
if (startCol > endCol) return;
|
|
1891
1897
|
const ctx = this.#ctx;
|
|
1892
1898
|
const padding = theme.cellPadding;
|
|
@@ -1960,6 +1966,13 @@ var Renderer = class _Renderer {
|
|
|
1960
1966
|
const visEndCol = Math.min(cell.col + cell.colspan - 1, endCol);
|
|
1961
1967
|
textCx = colPositions[visStartCol] + xOffset;
|
|
1962
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);
|
|
1963
1976
|
}
|
|
1964
1977
|
const align = cell.isLeaf ? aligns?.[cell.col] ?? "left" : cell.align ?? "center";
|
|
1965
1978
|
const iconRsv = cell.isLeaf ? FILTER_ICON_W + 4 : 0;
|
|
@@ -2339,7 +2352,7 @@ var Renderer = class _Renderer {
|
|
|
2339
2352
|
}
|
|
2340
2353
|
ctx.restore();
|
|
2341
2354
|
}
|
|
2342
|
-
#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) {
|
|
2343
2356
|
if (!vSB && !hSB) return;
|
|
2344
2357
|
const ctx = this.#ctx;
|
|
2345
2358
|
const r = theme.scrollbarRadius;
|
|
@@ -2347,6 +2360,7 @@ var Renderer = class _Renderer {
|
|
|
2347
2360
|
if (vSB > 0 && hSB > 0) ctx.fillRect(W - vSB, H - hSB, vSB, hSB);
|
|
2348
2361
|
if (hSB > 0 && frozenWidth > 0) ctx.fillRect(0, H - hSB, frozenWidth, hSB);
|
|
2349
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);
|
|
2350
2364
|
ctx.fillRect(v.x, v.y, v.w, v.h);
|
|
2351
2365
|
ctx.fillStyle = theme.scrollbarThumb;
|
|
2352
2366
|
this.#roundRect(v.x + 2, v.thumbY + 2, v.w - 4, v.thumbH - 4, r);
|
|
@@ -4802,7 +4816,11 @@ function buildFilterPanelEl({
|
|
|
4802
4816
|
clearTimeout(debounce);
|
|
4803
4817
|
seq++;
|
|
4804
4818
|
});
|
|
4805
|
-
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
|
+
};
|
|
4806
4824
|
}
|
|
4807
4825
|
let valuesSection = null;
|
|
4808
4826
|
const valueCheckboxes = [];
|
|
@@ -4863,11 +4881,13 @@ function buildFilterPanelEl({
|
|
|
4863
4881
|
});
|
|
4864
4882
|
valueCheckboxes.forEach((cb) => cb.addEventListener("change", syncSelectAll));
|
|
4865
4883
|
valuesSearch.addEventListener("input", () => {
|
|
4884
|
+
const pristine = valueCheckboxes.every((cb) => cb.checked);
|
|
4866
4885
|
const q = valuesSearch.value.trim().toLowerCase();
|
|
4867
4886
|
let anyVisible = false;
|
|
4868
4887
|
for (const r of valueRows) {
|
|
4869
4888
|
const match = !q || r.text.toLowerCase().includes(q);
|
|
4870
4889
|
r.row.style.display = match ? "flex" : "none";
|
|
4890
|
+
if (pristine) r.cb.checked = match;
|
|
4871
4891
|
anyVisible = anyVisible || match;
|
|
4872
4892
|
}
|
|
4873
4893
|
noMatch.style.display = anyVisible ? "none" : "block";
|
|
@@ -7409,7 +7429,14 @@ var JHGrid = class _JHGrid {
|
|
|
7409
7429
|
this._pendingLocalState = null;
|
|
7410
7430
|
return Promise.resolve();
|
|
7411
7431
|
}
|
|
7412
|
-
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);
|
|
7413
7440
|
return this._boot(state);
|
|
7414
7441
|
}
|
|
7415
7442
|
// Where the sweep sits across the loading bars, 0..1, or null to leave them flat.
|
|
@@ -11938,7 +11965,7 @@ var JHGrid = class _JHGrid {
|
|
|
11938
11965
|
JHGrid.use(RowSelectionPlugin);
|
|
11939
11966
|
|
|
11940
11967
|
// index.js
|
|
11941
|
-
var VERSION = "0.1.
|
|
11968
|
+
var VERSION = "0.1.3";
|
|
11942
11969
|
var SUPPORTED_BROWSERS = {
|
|
11943
11970
|
chrome: 99,
|
|
11944
11971
|
edge: 99,
|
package/dist/jhgrid.js
CHANGED
|
@@ -1404,7 +1404,9 @@ var JHGrid = (() => {
|
|
|
1404
1404
|
colPositions,
|
|
1405
1405
|
hiddenNeighbors,
|
|
1406
1406
|
headerCheckboxCols,
|
|
1407
|
-
sel
|
|
1407
|
+
sel,
|
|
1408
|
+
frozenWidth,
|
|
1409
|
+
vpW
|
|
1408
1410
|
);
|
|
1409
1411
|
ctx.restore();
|
|
1410
1412
|
if (frozenCount > 0) {
|
|
@@ -1432,7 +1434,9 @@ var JHGrid = (() => {
|
|
|
1432
1434
|
colPositions,
|
|
1433
1435
|
hiddenNeighbors,
|
|
1434
1436
|
headerCheckboxCols,
|
|
1435
|
-
sel
|
|
1437
|
+
sel,
|
|
1438
|
+
0,
|
|
1439
|
+
frozenWidth
|
|
1436
1440
|
);
|
|
1437
1441
|
ctx.restore();
|
|
1438
1442
|
}
|
|
@@ -1461,7 +1465,9 @@ var JHGrid = (() => {
|
|
|
1461
1465
|
colPositions,
|
|
1462
1466
|
hiddenNeighbors,
|
|
1463
1467
|
headerCheckboxCols,
|
|
1464
|
-
sel
|
|
1468
|
+
sel,
|
|
1469
|
+
rightX,
|
|
1470
|
+
frozenRightWidth
|
|
1465
1471
|
);
|
|
1466
1472
|
ctx.restore();
|
|
1467
1473
|
}
|
|
@@ -1926,7 +1932,7 @@ var JHGrid = (() => {
|
|
|
1926
1932
|
#colX(c, frozenCount, frozenWidth, frozenRightCount, rightX, scrollLeft, colPositions) {
|
|
1927
1933
|
return colScreenX(c, { frozenCount, frozenWidth, frozenRightCount, rightX, colPositions }, scrollLeft);
|
|
1928
1934
|
}
|
|
1929
|
-
#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) {
|
|
1930
1936
|
if (startCol > endCol) return;
|
|
1931
1937
|
const ctx = this.#ctx;
|
|
1932
1938
|
const padding = theme.cellPadding;
|
|
@@ -2000,6 +2006,13 @@ var JHGrid = (() => {
|
|
|
2000
2006
|
const visEndCol = Math.min(cell.col + cell.colspan - 1, endCol);
|
|
2001
2007
|
textCx = colPositions[visStartCol] + xOffset;
|
|
2002
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);
|
|
2003
2016
|
}
|
|
2004
2017
|
const align = cell.isLeaf ? aligns?.[cell.col] ?? "left" : cell.align ?? "center";
|
|
2005
2018
|
const iconRsv = cell.isLeaf ? FILTER_ICON_W + 4 : 0;
|
|
@@ -2379,7 +2392,7 @@ var JHGrid = (() => {
|
|
|
2379
2392
|
}
|
|
2380
2393
|
ctx.restore();
|
|
2381
2394
|
}
|
|
2382
|
-
#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) {
|
|
2383
2396
|
if (!vSB && !hSB) return;
|
|
2384
2397
|
const ctx = this.#ctx;
|
|
2385
2398
|
const r = theme.scrollbarRadius;
|
|
@@ -2387,6 +2400,7 @@ var JHGrid = (() => {
|
|
|
2387
2400
|
if (vSB > 0 && hSB > 0) ctx.fillRect(W - vSB, H - hSB, vSB, hSB);
|
|
2388
2401
|
if (hSB > 0 && frozenWidth > 0) ctx.fillRect(0, H - hSB, frozenWidth, hSB);
|
|
2389
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);
|
|
2390
2404
|
ctx.fillRect(v.x, v.y, v.w, v.h);
|
|
2391
2405
|
ctx.fillStyle = theme.scrollbarThumb;
|
|
2392
2406
|
this.#roundRect(v.x + 2, v.thumbY + 2, v.w - 4, v.thumbH - 4, r);
|
|
@@ -4842,7 +4856,11 @@ var JHGrid = (() => {
|
|
|
4842
4856
|
clearTimeout(debounce);
|
|
4843
4857
|
seq++;
|
|
4844
4858
|
});
|
|
4845
|
-
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
|
+
};
|
|
4846
4864
|
}
|
|
4847
4865
|
let valuesSection = null;
|
|
4848
4866
|
const valueCheckboxes = [];
|
|
@@ -4903,11 +4921,13 @@ var JHGrid = (() => {
|
|
|
4903
4921
|
});
|
|
4904
4922
|
valueCheckboxes.forEach((cb) => cb.addEventListener("change", syncSelectAll));
|
|
4905
4923
|
valuesSearch.addEventListener("input", () => {
|
|
4924
|
+
const pristine = valueCheckboxes.every((cb) => cb.checked);
|
|
4906
4925
|
const q = valuesSearch.value.trim().toLowerCase();
|
|
4907
4926
|
let anyVisible = false;
|
|
4908
4927
|
for (const r of valueRows) {
|
|
4909
4928
|
const match = !q || r.text.toLowerCase().includes(q);
|
|
4910
4929
|
r.row.style.display = match ? "flex" : "none";
|
|
4930
|
+
if (pristine) r.cb.checked = match;
|
|
4911
4931
|
anyVisible = anyVisible || match;
|
|
4912
4932
|
}
|
|
4913
4933
|
noMatch.style.display = anyVisible ? "none" : "block";
|
|
@@ -7449,7 +7469,14 @@ ${title ? `<h2>${esc(title)}</h2>` : ""}
|
|
|
7449
7469
|
this._pendingLocalState = null;
|
|
7450
7470
|
return Promise.resolve();
|
|
7451
7471
|
}
|
|
7452
|
-
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);
|
|
7453
7480
|
return this._boot(state);
|
|
7454
7481
|
}
|
|
7455
7482
|
// Where the sweep sits across the loading bars, 0..1, or null to leave them flat.
|
|
@@ -11978,7 +12005,7 @@ ${title ? `<h2>${esc(title)}</h2>` : ""}
|
|
|
11978
12005
|
JHGrid.use(RowSelectionPlugin);
|
|
11979
12006
|
|
|
11980
12007
|
// index.js
|
|
11981
|
-
var VERSION = "0.1.
|
|
12008
|
+
var VERSION = "0.1.3";
|
|
11982
12009
|
var SUPPORTED_BROWSERS = {
|
|
11983
12010
|
chrome: 99,
|
|
11984
12011
|
edge: 99,
|