@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 CHANGED
@@ -16,38 +16,18 @@ performance, editing, filtering, and frozen columns, running in your browser rig
16
16
 
17
17
  ## Features
18
18
 
19
- - Smooth rendering via `requestAnimationFrame` + Canvas 2D, synced to your display's native refresh rate (60Hz, 120Hz, 144Hz, etc.), not capped at 60fps
20
- - 2D virtual scrolling: only visible cells are drawn
21
- - Chunk-based async data loading with prefetch & cache
22
- - HiDPI / Retina display support (devicePixelRatio scaling)
23
- - Draggable scrollbars (vertical + horizontal)
24
- - Left/right frozen columns (`frozenCols` / `frozenColsRight`)
25
- - **Cell click**: single cell selection with blue border highlight
26
- - **Cell drag**: multi-cell range selection with fill overlay
27
- - **Row selection**: single/multi row selection (`rowSelection: 'single' | 'multi'`), with an optional select-all header checkbox (`columnDefs[].headerCheckbox`)
28
- - **Row drag reorder**: drag rows by the row-number gutter (`rowReorder: true`)
29
- - **Double-click to edit**: per-column editable/readonly control
30
- - **Ctrl+C / Ctrl+V**: copy & paste (single cell or range, TSV format)
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
 
@@ -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 = () => state.contains !== null ? { contains: state.contains } : { values: [...state.tags] };
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._dm.setFetch((page, size) => this._opts.fetchData(page, size, state));
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.2";
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 = () => state.contains !== null ? { contains: state.contains } : { values: [...state.tags] };
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._dm.setFetch((page, size) => this._opts.fetchData(page, size, state));
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.2";
12008
+ var VERSION = "0.1.3";
11982
12009
  var SUPPORTED_BROWSERS = {
11983
12010
  chrome: 99,
11984
12011
  edge: 99,