@jh-grid/jhgrid-js 0.1.0 → 0.1.2

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
@@ -3,6 +3,9 @@
3
3
  High-performance Canvas-based data grid with smooth 2D virtualization.
4
4
  Renders millions of rows and columns with near-zero DOM overhead.
5
5
 
6
+ **[▶ Try the live demo](https://jh-grid.github.io/JHGrid/docs/demo)**: 1,000,000-row scroll
7
+ performance, editing, filtering, and frozen columns, running in your browser right now.
8
+
6
9
  ![JH Grid screenshot](docs/images/jhgrid.png)
7
10
 
8
11
  > This repository distributes the **pre-built bundle** (`jhgrid.esm.js` / `jhgrid.js` /
@@ -50,9 +53,22 @@ Renders millions of rows and columns with near-zero DOM overhead.
50
53
 
51
54
  ## Installation
52
55
 
53
- > **npm package coming soon.** For now, use one of the two options below.
56
+ ### Option A: npm (recommended for bundled apps)
57
+
58
+ ```bash
59
+ npm install @jh-grid/jhgrid-js
60
+ ```
61
+
62
+ ```js
63
+ import { JHGrid } from '@jh-grid/jhgrid-js';
64
+ ```
65
+
66
+ The package ships as **ES Modules only** and includes its own TypeScript declarations
67
+ (`index.d.ts`), so no `@types/` package is needed. Any bundler (Vite, webpack, Rollup, esbuild) and
68
+ modern Node ESM can consume it directly. CommonJS `require('@jh-grid/jhgrid-js')` is *not* supported; use a
69
+ dynamic `await import('@jh-grid/jhgrid-js')` if you must load it from a CJS file.
54
70
 
55
- ### Option A: Static ES module (no bundler)
71
+ ### Option B: Static ES module (no bundler)
56
72
 
57
73
  `jhgrid.esm.js` is a single self-contained ES module file: deploy it as-is as a static resource
58
74
  (e.g. from a Spring Boot static resource path) and import it directly, no build step required:
@@ -63,7 +79,7 @@ Renders millions of rows and columns with near-zero DOM overhead.
63
79
  </script>
64
80
  ```
65
81
 
66
- ### Option B: CDN (single bundled script)
82
+ ### Option C: CDN (single bundled script)
67
83
 
68
84
  `jhgrid.min.js` is an IIFE build served straight from this repository via jsDelivr, no npm
69
85
  install required. Everything is exposed on a single global, `JHGrid` (the grid constructor is
@@ -93,7 +109,7 @@ no fetch functions needed:
93
109
  <div id="my-grid"></div>
94
110
 
95
111
  <script type="module">
96
- import { JHGrid } from './dist/jhgrid.esm.js'; // adjust to wherever you host the file
112
+ import { JHGrid } from '@jh-grid/jhgrid-js';
97
113
 
98
114
  const grid = new JHGrid({
99
115
  container: '#my-grid',
@@ -129,7 +145,7 @@ instead:
129
145
  <div id="my-grid"></div>
130
146
 
131
147
  <script type="module">
132
- import { JHGrid } from './dist/jhgrid.esm.js'; // adjust to wherever you host the file
148
+ import { JHGrid } from '@jh-grid/jhgrid-js';
133
149
 
134
150
  const grid = new JHGrid({
135
151
  container: '#my-grid',
@@ -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
 
@@ -4314,6 +4331,12 @@ function buildFilterPanelEl({
4314
4331
  multiSortEnabled,
4315
4332
  i18n,
4316
4333
  theme,
4334
+ // wrapperHeight + openUpward: when the caller found more room above the header than below (a
4335
+ // grid scrolled to sit low on the page — see _openFilterPanel), the panel anchors its *bottom*
4336
+ // to the header's top edge and grows upward instead of down, same as a native <select> flipping
4337
+ // when it would otherwise overflow the viewport.
4338
+ wrapperHeight,
4339
+ openUpward,
4317
4340
  // Set filter (checkbox list of exact values) — distinctValues is null when the column has too
4318
4341
  // many/no loaded values yet, in which case the panel falls back to the plain text search box
4319
4342
  // below instead of rendering a checklist. selectedValues is the currently-applied array filter
@@ -4338,7 +4361,6 @@ function buildFilterPanelEl({
4338
4361
  }) {
4339
4362
  const PANEL_W = 220;
4340
4363
  const panelX = Math.max(0, Math.min(colLeft, width - PANEL_W));
4341
- const panelY = headerH;
4342
4364
  const s = (...parts) => parts.join(";");
4343
4365
  const btn = (text, action, style) => {
4344
4366
  const b = document.createElement("button");
@@ -4357,7 +4379,10 @@ function buildFilterPanelEl({
4357
4379
  el.style.cssText = s(
4358
4380
  `position:absolute`,
4359
4381
  `left:${panelX}px`,
4360
- `top:${panelY}px`,
4382
+ // Anchoring the bottom edge (instead of top) lets the panel grow upward from the header
4383
+ // without knowing its own height up front — the alternative, computing `top: headerH -
4384
+ // panelHeight`, needs the rendered height before it's laid out.
4385
+ openUpward ? `bottom:${Math.max(0, wrapperHeight - headerH)}px` : `top:${headerH}px`,
4361
4386
  `width:${PANEL_W}px`,
4362
4387
  `background:${themed(theme, "overlayBg")}`,
4363
4388
  `border:1px solid ${themed(theme, "overlayBorder")}`,
@@ -5923,6 +5948,8 @@ function resolveIntlTag(locale) {
5923
5948
  // JHGrid.js
5924
5949
  var ARROWS = { ArrowDown: [1, 0], ArrowUp: [-1, 0], ArrowRight: [0, 1], ArrowLeft: [0, -1] };
5925
5950
  var RESIZE_HIT_W = 5;
5951
+ var RESIZE_HIT_W_TOUCH = 8;
5952
+ var CANVAS_TOUCH_CSS = "touch-action:none;-webkit-touch-callout:none;-webkit-tap-highlight-color:transparent;";
5926
5953
  var MIN_COL_W = 30;
5927
5954
  var MIN_ROW_H = 16;
5928
5955
  var LONG_PRESS_MS = 500;
@@ -6344,7 +6371,7 @@ var JHGrid = class _JHGrid {
6344
6371
  this._canvas = document.createElement("canvas");
6345
6372
  this._canvas.width = Math.round(width * dpr);
6346
6373
  this._canvas.height = Math.round(height * dpr);
6347
- this._canvas.style.cssText = `display:block;width:${width}px;height:${height}px;outline:none;`;
6374
+ this._canvas.style.cssText = `display:block;width:${width}px;height:${height}px;outline:none;${CANVAS_TOUCH_CSS}`;
6348
6375
  this._canvas.setAttribute("aria-hidden", "true");
6349
6376
  this._canvas.tabIndex = -1;
6350
6377
  this._canvas.getContext("2d").scale(dpr, dpr);
@@ -6574,7 +6601,7 @@ var JHGrid = class _JHGrid {
6574
6601
  this._opts.height = gridHeight;
6575
6602
  this._wrapper.style.width = width + "px";
6576
6603
  this._wrapper.style.height = gridHeight + "px";
6577
- this._canvas.style.cssText = `display:block;width:${width}px;height:${gridHeight}px;outline:none;`;
6604
+ this._canvas.style.cssText = `display:block;width:${width}px;height:${gridHeight}px;outline:none;${CANVAS_TOUCH_CSS}`;
6578
6605
  const dpr = window.devicePixelRatio || 1;
6579
6606
  this._canvas.width = Math.round(width * dpr);
6580
6607
  this._canvas.height = Math.round(gridHeight * dpr);
@@ -6999,29 +7026,85 @@ var JHGrid = class _JHGrid {
6999
7026
  // of, or null. Checks both the hovered row's bottom edge and (falling back, like the column
7000
7027
  // check does with col > 0) the previous row's bottom edge when y lands right at a row's top
7001
7028
  // edge — both branches identify the same boundary line, resolving to "the row above it".
7002
- _hitRowBoundary(y, geo = this._geo()) {
7029
+ _hitRowBoundary(y, geo = this._geo(), hitW = RESIZE_HIT_W) {
7003
7030
  const { height: H } = this._opts;
7004
7031
  if (y < geo.headerH || y >= H - geo.hSB) return null;
7005
7032
  const relY = y - geo.headerH + this._scrollTop;
7006
7033
  const row = this._rowLayout.rowAt(relY, this._totalRows - 1);
7007
7034
  if (row < 0) return null;
7008
7035
  const bottomY = this._rowLayout.yOf(row + 1) - this._scrollTop + geo.headerH;
7009
- if (Math.abs(y - bottomY) <= RESIZE_HIT_W) return row;
7036
+ if (Math.abs(y - bottomY) <= hitW) return row;
7010
7037
  if (row > 0) {
7011
7038
  const topY = this._rowLayout.yOf(row) - this._scrollTop + geo.headerH;
7012
- if (Math.abs(y - topY) <= RESIZE_HIT_W) return row - 1;
7039
+ if (Math.abs(y - topY) <= hitW) return row - 1;
7013
7040
  }
7014
7041
  return null;
7015
7042
  }
7016
- _hitFillHandle(x, y, geo = this._geo()) {
7043
+ // Vertical/horizontal scrollbar hit test, shared by mousedown and touchstart: grabbing the
7044
+ // thumb arms this._drag (consumed by _updateScrollbarDrag on the matching move handler),
7045
+ // tapping the bare track jump-scrolls straight to that position, same as a native scrollbar.
7046
+ // Returns whether (x, y) was inside either scrollbar's hit area at all, so the caller knows to
7047
+ // stop dispatching (preventDefault + return) instead of falling through to header/cell/pan
7048
+ // handling — without this, a touch on the drawn scrollbar was indistinguishable from a touch
7049
+ // anywhere else on the canvas and just started a generic content-drag pan.
7050
+ _hitScrollbar(x, y, geo) {
7051
+ const { v, h } = geo;
7052
+ if (x >= v.x) {
7053
+ if (y >= v.thumbY && y <= v.thumbY + v.thumbH) {
7054
+ this._drag = { axis: "v", startMouse: y, startScroll: this._scrollTop };
7055
+ } else if (y >= v.y && y <= v.y + v.h) {
7056
+ const ratio = Math.max(0, Math.min(1, (y - v.y - v.thumbH / 2) / (v.h - v.thumbH)));
7057
+ this._scrollTop = geo.minScrollY + ratio * (geo.maxScrollY - geo.minScrollY);
7058
+ this._clamp(geo);
7059
+ this._draw();
7060
+ }
7061
+ return true;
7062
+ }
7063
+ if (y >= h.y) {
7064
+ if (x >= h.thumbX && x <= h.thumbX + h.thumbW) {
7065
+ this._drag = { axis: "h", startMouse: x, startScroll: this._scrollLeft };
7066
+ } else if (x >= h.x && x <= h.x + h.w) {
7067
+ const ratio = Math.max(0, Math.min(1, (x - h.x - h.thumbW / 2) / (h.w - h.thumbW)));
7068
+ this._scrollLeft = ratio * geo.maxScrollX;
7069
+ this._clamp(geo);
7070
+ this._draw();
7071
+ }
7072
+ return true;
7073
+ }
7074
+ return false;
7075
+ }
7076
+ // Applies an in-progress scrollbar thumb drag (this._drag, armed by _hitScrollbar) given the
7077
+ // pointer/touch's current raw canvas coordinates. Shared by mousemove and touchmove so the two
7078
+ // input paths can't drift apart.
7079
+ _updateScrollbarDrag(x, y) {
7080
+ const geo = this._geo();
7081
+ const { v, h } = geo;
7082
+ if (this._drag.axis === "v") {
7083
+ const ratio = (y - this._drag.startMouse) / (v.h - v.thumbH);
7084
+ this._scrollTop = this._drag.startScroll + ratio * (geo.maxScrollY - geo.minScrollY);
7085
+ } else {
7086
+ const ratio = (x - this._drag.startMouse) / (h.w - h.thumbW);
7087
+ this._scrollLeft = this._drag.startScroll + ratio * geo.maxScrollX;
7088
+ }
7089
+ this._clamp(geo);
7090
+ this._scrolling = true;
7091
+ this._dm.setHold(true);
7092
+ clearTimeout(this._scrollEndTimer);
7093
+ this._scrollEndTimer = setTimeout(() => {
7094
+ this._scrolling = false;
7095
+ this._dm.setHold(false);
7096
+ this._schedDraw();
7097
+ }, 150);
7098
+ this._schedDraw();
7099
+ }
7100
+ _hitFillHandle(x, y, geo = this._geo(), hitW = RESIZE_HIT_W) {
7017
7101
  if (!this._sel) return false;
7018
7102
  const { headerH, colPositions } = geo;
7019
7103
  const selR2 = this._sel.type === "single" ? this._sel.row : this._sel.r2;
7020
7104
  const selC2 = this._sel.type === "single" ? this._sel.col : this._sel.c2;
7021
7105
  const handleX = this._colLeft(selC2, geo) + (colPositions[selC2 + 1] - colPositions[selC2]);
7022
7106
  const handleY = this._rowLayout.yOf(selR2 + 1) - this._scrollTop + headerH;
7023
- const HIT = 5;
7024
- return Math.abs(x - handleX) <= HIT && Math.abs(y - handleY) <= HIT;
7107
+ return Math.abs(x - handleX) <= hitW && Math.abs(y - handleY) <= hitW;
7025
7108
  }
7026
7109
  _colLeft(col, geo) {
7027
7110
  return colScreenX(col, geo, this._scrollLeft);
@@ -7579,10 +7662,16 @@ var JHGrid = class _JHGrid {
7579
7662
  this._wrapper.style.overflow = "visible";
7580
7663
  const wrapperTop = this._wrapper.getBoundingClientRect().top;
7581
7664
  const viewportH = window.innerHeight || document.documentElement.clientHeight;
7582
- const maxPanelHeight = viewportH - wrapperTop - geo.headerH - 8;
7665
+ const spaceBelow = viewportH - wrapperTop - geo.headerH - 8;
7666
+ const spaceAbove = wrapperTop - 8;
7667
+ const PANEL_MIN_USABLE = 260;
7668
+ const openUpward = spaceBelow < PANEL_MIN_USABLE && spaceAbove > spaceBelow;
7669
+ const maxPanelHeight = openUpward ? spaceAbove : spaceBelow;
7583
7670
  const el = buildFilterPanelEl({
7584
7671
  colLeft: this._colLeft(col, geo),
7585
7672
  headerH: geo.headerH,
7673
+ wrapperHeight: this._opts.height,
7674
+ openUpward,
7586
7675
  width: this._opts.width,
7587
7676
  maxPanelHeight,
7588
7677
  label,
@@ -8714,19 +8803,19 @@ var JHGrid = class _JHGrid {
8714
8803
  }
8715
8804
  _clearSelection() {
8716
8805
  if (!this._sel) return;
8717
- const pairs = this._sel.type === "single" ? [[this._sel.row, this._sel.col]] : Array.from(
8718
- { length: this._sel.r2 - this._sel.r1 + 1 },
8719
- (_, ri) => Array.from(
8720
- { length: this._sel.c2 - this._sel.c1 + 1 },
8721
- (_2, ci) => [this._sel.r1 + ri, this._sel.c1 + ci]
8722
- )
8723
- ).flat();
8806
+ const single = this._sel.type === "single";
8807
+ const r1 = single ? this._sel.row : this._sel.r1;
8808
+ const r2 = single ? this._sel.row : this._sel.r2;
8809
+ const c1 = single ? this._sel.col : this._sel.c1;
8810
+ const c2 = single ? this._sel.col : this._sel.c2;
8811
+ const editableFields = [];
8812
+ for (let c = c1; c <= c2; c++) {
8813
+ if (this._isEditable(c)) editableFields.push(this._columns[c]);
8814
+ }
8724
8815
  this._editTxnBegin();
8725
- pairs.forEach(([r, c]) => {
8726
- if (!this._isEditable(c)) return;
8727
- const field = this._columns[c];
8728
- this._setEdit(r, field, "");
8729
- });
8816
+ for (let r = r1; r <= r2; r++) {
8817
+ for (const field of editableFields) this._setEdit(r, field, "");
8818
+ }
8730
8819
  this._editTxnCommit();
8731
8820
  this._draw();
8732
8821
  }
@@ -8911,11 +9000,15 @@ var JHGrid = class _JHGrid {
8911
9000
  const v = data[field];
8912
9001
  return v != null ? String(v) : "";
8913
9002
  }
8914
- // Re-runs validation for one cell and updates the validator's invalid-cell set.
9003
+ // Re-runs validation for one cell and updates the validator's invalid-cell set. Takes a
9004
+ // "row_field" key -- for callers that only have that (iterating an _edits-shaped Map). Callers
9005
+ // that already have row/field apart (_setEdit, validateAll) should call _revalidateRowField()
9006
+ // directly instead of paying to stringify them together here just to split them back apart.
8915
9007
  _revalidateKey(key) {
8916
9008
  const u = key.indexOf("_");
8917
- const row = Number(key.slice(0, u));
8918
- const field = key.slice(u + 1);
9009
+ this._revalidateRowField(Number(key.slice(0, u)), key.slice(u + 1));
9010
+ }
9011
+ _revalidateRowField(row, field) {
8919
9012
  this._validator.revalidate(row, field, this._resolveCellStringValue(row, field));
8920
9013
  }
8921
9014
  // Rebuilds invalid-cell state from scratch based on the current _edits map —
@@ -8950,11 +9043,11 @@ var JHGrid = class _JHGrid {
8950
9043
  const validatedFields = this._columns.filter((f) => this._colDefMap.get(f)?.validation);
8951
9044
  if (validatedFields.length > 0) {
8952
9045
  this._dm.forEachLoaded((_, rowIndex) => {
8953
- validatedFields.forEach((field) => this._revalidateKey(`${rowIndex}_${field}`));
9046
+ validatedFields.forEach((field) => this._revalidateRowField(rowIndex, field));
8954
9047
  });
8955
9048
  this._localRows.forEach((_, i) => {
8956
9049
  const r = this._rowPlan.visualOfLocal(i);
8957
- validatedFields.forEach((field) => this._revalidateKey(`${r}_${field}`));
9050
+ validatedFields.forEach((field) => this._revalidateRowField(r, field));
8958
9051
  });
8959
9052
  }
8960
9053
  this._draw();
@@ -8981,7 +9074,7 @@ var JHGrid = class _JHGrid {
8981
9074
  this._edits.set(key, val);
8982
9075
  this._editedRows.add(row);
8983
9076
  this._opts.onCellChange?.({ row, field, newValue: val, oldValue });
8984
- this._revalidateKey(key);
9077
+ this._revalidateRowField(row, field);
8985
9078
  this._growRowForMultilineValue(row, val);
8986
9079
  }
8987
9080
  // Excel grows a row's height the moment a cell picks up a line break -- typed (Alt+Enter) or
@@ -9636,7 +9729,6 @@ var JHGrid = class _JHGrid {
9636
9729
  ev.mousedown = (e) => {
9637
9730
  const { x, y } = this._xy(e);
9638
9731
  const geo = this._geo();
9639
- const { v, h } = geo;
9640
9732
  const suppressReopenCell = this._suppressReopenCell;
9641
9733
  this._suppressReopenCell = null;
9642
9734
  if (this._editing) this._commitEdit();
@@ -9652,27 +9744,7 @@ var JHGrid = class _JHGrid {
9652
9744
  if (withinSel) return;
9653
9745
  }
9654
9746
  }
9655
- if (x >= v.x) {
9656
- if (y >= v.thumbY && y <= v.thumbY + v.thumbH) {
9657
- this._drag = { axis: "v", startMouse: y, startScroll: this._scrollTop };
9658
- } else if (y >= v.y && y <= v.y + v.h) {
9659
- const ratio = Math.max(0, Math.min(1, (y - v.y - v.thumbH / 2) / (v.h - v.thumbH)));
9660
- this._scrollTop = geo.minScrollY + ratio * (geo.maxScrollY - geo.minScrollY);
9661
- this._clamp(geo);
9662
- this._draw();
9663
- }
9664
- e.preventDefault();
9665
- return;
9666
- }
9667
- if (y >= h.y) {
9668
- if (x >= h.thumbX && x <= h.thumbX + h.thumbW) {
9669
- this._drag = { axis: "h", startMouse: x, startScroll: this._scrollLeft };
9670
- } else if (x >= h.x && x <= h.x + h.w) {
9671
- const ratio = Math.max(0, Math.min(1, (x - h.x - h.thumbW / 2) / (h.w - h.thumbW)));
9672
- this._scrollLeft = ratio * geo.maxScrollX;
9673
- this._clamp(geo);
9674
- this._draw();
9675
- }
9747
+ if (this._hitScrollbar(x, y, geo)) {
9676
9748
  e.preventDefault();
9677
9749
  return;
9678
9750
  }
@@ -9884,25 +9956,7 @@ var JHGrid = class _JHGrid {
9884
9956
  }
9885
9957
  }
9886
9958
  if (this._drag) {
9887
- const geo = this._geo();
9888
- const { v, h } = geo;
9889
- if (this._drag.axis === "v") {
9890
- const ratio = (y - this._drag.startMouse) / (v.h - v.thumbH);
9891
- this._scrollTop = this._drag.startScroll + ratio * (geo.maxScrollY - geo.minScrollY);
9892
- } else {
9893
- const ratio = (x - this._drag.startMouse) / (h.w - h.thumbW);
9894
- this._scrollLeft = this._drag.startScroll + ratio * geo.maxScrollX;
9895
- }
9896
- this._clamp(geo);
9897
- this._scrolling = true;
9898
- this._dm.setHold(true);
9899
- clearTimeout(this._scrollEndTimer);
9900
- this._scrollEndTimer = setTimeout(() => {
9901
- this._scrolling = false;
9902
- this._dm.setHold(false);
9903
- this._schedDraw();
9904
- }, 150);
9905
- this._schedDraw();
9959
+ this._updateScrollbarDrag(x, y);
9906
9960
  return;
9907
9961
  }
9908
9962
  if (this._selDragging) {
@@ -10289,19 +10343,23 @@ var JHGrid = class _JHGrid {
10289
10343
  this._rowTap = null;
10290
10344
  const geo = this._geo();
10291
10345
  const headerH = geo.headerH;
10346
+ if (this._hitScrollbar(x, y, geo)) {
10347
+ e.preventDefault();
10348
+ return;
10349
+ }
10292
10350
  if (y >= 0 && y < headerH) {
10293
10351
  const col = this._hitHeader(x, geo);
10294
10352
  if (col !== null) {
10295
10353
  const colW = geo.colPositions[col + 1] - geo.colPositions[col];
10296
10354
  const colRight = this._colLeft(col, geo) + colW;
10297
10355
  const filterIconX = colRight - FILTER_ICON_W;
10298
- if (Math.abs(x - colRight) <= RESIZE_HIT_W) {
10356
+ if (Math.abs(x - colRight) <= RESIZE_HIT_W_TOUCH) {
10299
10357
  this._colResize = { col, startX: x, startWidth: colW, undoBefore: this._snapshotStructural() };
10300
10358
  e.preventDefault();
10301
10359
  return;
10302
10360
  }
10303
10361
  const colLeft = this._colLeft(col, geo);
10304
- if (col > 0 && Math.abs(x - colLeft) <= RESIZE_HIT_W) {
10362
+ if (col > 0 && Math.abs(x - colLeft) <= RESIZE_HIT_W_TOUCH) {
10305
10363
  const prevColW = geo.colPositions[col] - geo.colPositions[col - 1];
10306
10364
  this._colResize = { col: col - 1, startX: x, startWidth: prevColW, undoBefore: this._snapshotStructural() };
10307
10365
  e.preventDefault();
@@ -10313,7 +10371,7 @@ var JHGrid = class _JHGrid {
10313
10371
  const _hcbCx = this._colLeft(col, geo) + _hcbPad + 6;
10314
10372
  const _hcbCell = computeHeaderCells(this._opts.headerRows, this._columns, this._opts.columnLetterHeader).find((c) => c.isLeaf && c.col === col);
10315
10373
  const _hcbCy = _hcbCell ? _hcbCell.row * this._opts.headerHeight + _hcbCell.rowspan * this._opts.headerHeight / 2 : headerH - this._opts.headerHeight / 2;
10316
- if (Math.abs(x - _hcbCx) <= 9 && Math.abs(y - _hcbCy) <= 9) {
10374
+ if (Math.abs(x - _hcbCx) <= RESIZE_HIT_W_TOUCH && Math.abs(y - _hcbCy) <= RESIZE_HIT_W_TOUCH) {
10317
10375
  const _hcbChecked = !(this._headerCheckboxState.get(_hcbField) ?? false);
10318
10376
  this._headerCheckboxState.set(_hcbField, _hcbChecked);
10319
10377
  this._draw();
@@ -10343,7 +10401,7 @@ var JHGrid = class _JHGrid {
10343
10401
  return;
10344
10402
  }
10345
10403
  }
10346
- const boundaryRow = geo.rowNumW > 0 && x < geo.rowNumW && y >= geo.headerH ? this._hitRowBoundary(y, geo) : null;
10404
+ const boundaryRow = geo.rowNumW > 0 && x < geo.rowNumW && y >= geo.headerH ? this._hitRowBoundary(y, geo, RESIZE_HIT_W_TOUCH) : null;
10347
10405
  if (boundaryRow !== null) {
10348
10406
  this._rowResize = { row: boundaryRow, startY: y, startHeight: this._rowLayout.heightOf(boundaryRow), undoBefore: this._snapshotStructural() };
10349
10407
  e.preventDefault();
@@ -10365,7 +10423,7 @@ var JHGrid = class _JHGrid {
10365
10423
  }
10366
10424
  return;
10367
10425
  }
10368
- if (this._sel && this._hitFillHandle(x, y, geo)) {
10426
+ if (this._sel && this._hitFillHandle(x, y, geo, RESIZE_HIT_W_TOUCH)) {
10369
10427
  this._fillDrag = { sel: this._sel };
10370
10428
  this._fillPreview = null;
10371
10429
  e.preventDefault();
@@ -10403,6 +10461,14 @@ var JHGrid = class _JHGrid {
10403
10461
  e.preventDefault();
10404
10462
  return;
10405
10463
  }
10464
+ if (this._drag) {
10465
+ if (!e.touches.length) return;
10466
+ const t = e.touches[0];
10467
+ const rect2 = this._canvas.getBoundingClientRect();
10468
+ this._updateScrollbarDrag(t.clientX - rect2.left, t.clientY - rect2.top);
10469
+ e.preventDefault();
10470
+ return;
10471
+ }
10406
10472
  if (!this._touch) return;
10407
10473
  const rect = this._canvas.getBoundingClientRect();
10408
10474
  if (e.touches.length >= 2 && this._touch.twoFinger) {
@@ -10439,6 +10505,10 @@ var JHGrid = class _JHGrid {
10439
10505
  ev.touchend = (e) => {
10440
10506
  this._cancelLongPress();
10441
10507
  if (this._finalizeStructuralGesture()) return;
10508
+ if (this._drag) {
10509
+ this._drag = null;
10510
+ return;
10511
+ }
10442
10512
  if (this._rowTap) {
10443
10513
  const { row, ctrlKey, shiftKey } = this._rowTap;
10444
10514
  this._rowTap = null;
@@ -11868,7 +11938,7 @@ var JHGrid = class _JHGrid {
11868
11938
  JHGrid.use(RowSelectionPlugin);
11869
11939
 
11870
11940
  // index.js
11871
- var VERSION = "0.1.0";
11941
+ var VERSION = "0.1.2";
11872
11942
  var SUPPORTED_BROWSERS = {
11873
11943
  chrome: 99,
11874
11944
  edge: 99,