@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/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
 
@@ -4354,6 +4371,12 @@ var JHGrid = (() => {
4354
4371
  multiSortEnabled,
4355
4372
  i18n,
4356
4373
  theme,
4374
+ // wrapperHeight + openUpward: when the caller found more room above the header than below (a
4375
+ // grid scrolled to sit low on the page — see _openFilterPanel), the panel anchors its *bottom*
4376
+ // to the header's top edge and grows upward instead of down, same as a native <select> flipping
4377
+ // when it would otherwise overflow the viewport.
4378
+ wrapperHeight,
4379
+ openUpward,
4357
4380
  // Set filter (checkbox list of exact values) — distinctValues is null when the column has too
4358
4381
  // many/no loaded values yet, in which case the panel falls back to the plain text search box
4359
4382
  // below instead of rendering a checklist. selectedValues is the currently-applied array filter
@@ -4378,7 +4401,6 @@ var JHGrid = (() => {
4378
4401
  }) {
4379
4402
  const PANEL_W = 220;
4380
4403
  const panelX = Math.max(0, Math.min(colLeft, width - PANEL_W));
4381
- const panelY = headerH;
4382
4404
  const s = (...parts) => parts.join(";");
4383
4405
  const btn = (text, action, style) => {
4384
4406
  const b = document.createElement("button");
@@ -4397,7 +4419,10 @@ var JHGrid = (() => {
4397
4419
  el.style.cssText = s(
4398
4420
  `position:absolute`,
4399
4421
  `left:${panelX}px`,
4400
- `top:${panelY}px`,
4422
+ // Anchoring the bottom edge (instead of top) lets the panel grow upward from the header
4423
+ // without knowing its own height up front — the alternative, computing `top: headerH -
4424
+ // panelHeight`, needs the rendered height before it's laid out.
4425
+ openUpward ? `bottom:${Math.max(0, wrapperHeight - headerH)}px` : `top:${headerH}px`,
4401
4426
  `width:${PANEL_W}px`,
4402
4427
  `background:${themed(theme, "overlayBg")}`,
4403
4428
  `border:1px solid ${themed(theme, "overlayBorder")}`,
@@ -5963,6 +5988,8 @@ ${title ? `<h2>${esc(title)}</h2>` : ""}
5963
5988
  // JHGrid.js
5964
5989
  var ARROWS = { ArrowDown: [1, 0], ArrowUp: [-1, 0], ArrowRight: [0, 1], ArrowLeft: [0, -1] };
5965
5990
  var RESIZE_HIT_W = 5;
5991
+ var RESIZE_HIT_W_TOUCH = 8;
5992
+ var CANVAS_TOUCH_CSS = "touch-action:none;-webkit-touch-callout:none;-webkit-tap-highlight-color:transparent;";
5966
5993
  var MIN_COL_W = 30;
5967
5994
  var MIN_ROW_H = 16;
5968
5995
  var LONG_PRESS_MS = 500;
@@ -6384,7 +6411,7 @@ ${title ? `<h2>${esc(title)}</h2>` : ""}
6384
6411
  this._canvas = document.createElement("canvas");
6385
6412
  this._canvas.width = Math.round(width * dpr);
6386
6413
  this._canvas.height = Math.round(height * dpr);
6387
- this._canvas.style.cssText = `display:block;width:${width}px;height:${height}px;outline:none;`;
6414
+ this._canvas.style.cssText = `display:block;width:${width}px;height:${height}px;outline:none;${CANVAS_TOUCH_CSS}`;
6388
6415
  this._canvas.setAttribute("aria-hidden", "true");
6389
6416
  this._canvas.tabIndex = -1;
6390
6417
  this._canvas.getContext("2d").scale(dpr, dpr);
@@ -6614,7 +6641,7 @@ ${title ? `<h2>${esc(title)}</h2>` : ""}
6614
6641
  this._opts.height = gridHeight;
6615
6642
  this._wrapper.style.width = width + "px";
6616
6643
  this._wrapper.style.height = gridHeight + "px";
6617
- this._canvas.style.cssText = `display:block;width:${width}px;height:${gridHeight}px;outline:none;`;
6644
+ this._canvas.style.cssText = `display:block;width:${width}px;height:${gridHeight}px;outline:none;${CANVAS_TOUCH_CSS}`;
6618
6645
  const dpr = window.devicePixelRatio || 1;
6619
6646
  this._canvas.width = Math.round(width * dpr);
6620
6647
  this._canvas.height = Math.round(gridHeight * dpr);
@@ -7039,29 +7066,85 @@ ${title ? `<h2>${esc(title)}</h2>` : ""}
7039
7066
  // of, or null. Checks both the hovered row's bottom edge and (falling back, like the column
7040
7067
  // check does with col > 0) the previous row's bottom edge when y lands right at a row's top
7041
7068
  // edge — both branches identify the same boundary line, resolving to "the row above it".
7042
- _hitRowBoundary(y, geo = this._geo()) {
7069
+ _hitRowBoundary(y, geo = this._geo(), hitW = RESIZE_HIT_W) {
7043
7070
  const { height: H } = this._opts;
7044
7071
  if (y < geo.headerH || y >= H - geo.hSB) return null;
7045
7072
  const relY = y - geo.headerH + this._scrollTop;
7046
7073
  const row = this._rowLayout.rowAt(relY, this._totalRows - 1);
7047
7074
  if (row < 0) return null;
7048
7075
  const bottomY = this._rowLayout.yOf(row + 1) - this._scrollTop + geo.headerH;
7049
- if (Math.abs(y - bottomY) <= RESIZE_HIT_W) return row;
7076
+ if (Math.abs(y - bottomY) <= hitW) return row;
7050
7077
  if (row > 0) {
7051
7078
  const topY = this._rowLayout.yOf(row) - this._scrollTop + geo.headerH;
7052
- if (Math.abs(y - topY) <= RESIZE_HIT_W) return row - 1;
7079
+ if (Math.abs(y - topY) <= hitW) return row - 1;
7053
7080
  }
7054
7081
  return null;
7055
7082
  }
7056
- _hitFillHandle(x, y, geo = this._geo()) {
7083
+ // Vertical/horizontal scrollbar hit test, shared by mousedown and touchstart: grabbing the
7084
+ // thumb arms this._drag (consumed by _updateScrollbarDrag on the matching move handler),
7085
+ // tapping the bare track jump-scrolls straight to that position, same as a native scrollbar.
7086
+ // Returns whether (x, y) was inside either scrollbar's hit area at all, so the caller knows to
7087
+ // stop dispatching (preventDefault + return) instead of falling through to header/cell/pan
7088
+ // handling — without this, a touch on the drawn scrollbar was indistinguishable from a touch
7089
+ // anywhere else on the canvas and just started a generic content-drag pan.
7090
+ _hitScrollbar(x, y, geo) {
7091
+ const { v, h } = geo;
7092
+ if (x >= v.x) {
7093
+ if (y >= v.thumbY && y <= v.thumbY + v.thumbH) {
7094
+ this._drag = { axis: "v", startMouse: y, startScroll: this._scrollTop };
7095
+ } else if (y >= v.y && y <= v.y + v.h) {
7096
+ const ratio = Math.max(0, Math.min(1, (y - v.y - v.thumbH / 2) / (v.h - v.thumbH)));
7097
+ this._scrollTop = geo.minScrollY + ratio * (geo.maxScrollY - geo.minScrollY);
7098
+ this._clamp(geo);
7099
+ this._draw();
7100
+ }
7101
+ return true;
7102
+ }
7103
+ if (y >= h.y) {
7104
+ if (x >= h.thumbX && x <= h.thumbX + h.thumbW) {
7105
+ this._drag = { axis: "h", startMouse: x, startScroll: this._scrollLeft };
7106
+ } else if (x >= h.x && x <= h.x + h.w) {
7107
+ const ratio = Math.max(0, Math.min(1, (x - h.x - h.thumbW / 2) / (h.w - h.thumbW)));
7108
+ this._scrollLeft = ratio * geo.maxScrollX;
7109
+ this._clamp(geo);
7110
+ this._draw();
7111
+ }
7112
+ return true;
7113
+ }
7114
+ return false;
7115
+ }
7116
+ // Applies an in-progress scrollbar thumb drag (this._drag, armed by _hitScrollbar) given the
7117
+ // pointer/touch's current raw canvas coordinates. Shared by mousemove and touchmove so the two
7118
+ // input paths can't drift apart.
7119
+ _updateScrollbarDrag(x, y) {
7120
+ const geo = this._geo();
7121
+ const { v, h } = geo;
7122
+ if (this._drag.axis === "v") {
7123
+ const ratio = (y - this._drag.startMouse) / (v.h - v.thumbH);
7124
+ this._scrollTop = this._drag.startScroll + ratio * (geo.maxScrollY - geo.minScrollY);
7125
+ } else {
7126
+ const ratio = (x - this._drag.startMouse) / (h.w - h.thumbW);
7127
+ this._scrollLeft = this._drag.startScroll + ratio * geo.maxScrollX;
7128
+ }
7129
+ this._clamp(geo);
7130
+ this._scrolling = true;
7131
+ this._dm.setHold(true);
7132
+ clearTimeout(this._scrollEndTimer);
7133
+ this._scrollEndTimer = setTimeout(() => {
7134
+ this._scrolling = false;
7135
+ this._dm.setHold(false);
7136
+ this._schedDraw();
7137
+ }, 150);
7138
+ this._schedDraw();
7139
+ }
7140
+ _hitFillHandle(x, y, geo = this._geo(), hitW = RESIZE_HIT_W) {
7057
7141
  if (!this._sel) return false;
7058
7142
  const { headerH, colPositions } = geo;
7059
7143
  const selR2 = this._sel.type === "single" ? this._sel.row : this._sel.r2;
7060
7144
  const selC2 = this._sel.type === "single" ? this._sel.col : this._sel.c2;
7061
7145
  const handleX = this._colLeft(selC2, geo) + (colPositions[selC2 + 1] - colPositions[selC2]);
7062
7146
  const handleY = this._rowLayout.yOf(selR2 + 1) - this._scrollTop + headerH;
7063
- const HIT = 5;
7064
- return Math.abs(x - handleX) <= HIT && Math.abs(y - handleY) <= HIT;
7147
+ return Math.abs(x - handleX) <= hitW && Math.abs(y - handleY) <= hitW;
7065
7148
  }
7066
7149
  _colLeft(col, geo) {
7067
7150
  return colScreenX(col, geo, this._scrollLeft);
@@ -7619,10 +7702,16 @@ ${title ? `<h2>${esc(title)}</h2>` : ""}
7619
7702
  this._wrapper.style.overflow = "visible";
7620
7703
  const wrapperTop = this._wrapper.getBoundingClientRect().top;
7621
7704
  const viewportH = window.innerHeight || document.documentElement.clientHeight;
7622
- const maxPanelHeight = viewportH - wrapperTop - geo.headerH - 8;
7705
+ const spaceBelow = viewportH - wrapperTop - geo.headerH - 8;
7706
+ const spaceAbove = wrapperTop - 8;
7707
+ const PANEL_MIN_USABLE = 260;
7708
+ const openUpward = spaceBelow < PANEL_MIN_USABLE && spaceAbove > spaceBelow;
7709
+ const maxPanelHeight = openUpward ? spaceAbove : spaceBelow;
7623
7710
  const el = buildFilterPanelEl({
7624
7711
  colLeft: this._colLeft(col, geo),
7625
7712
  headerH: geo.headerH,
7713
+ wrapperHeight: this._opts.height,
7714
+ openUpward,
7626
7715
  width: this._opts.width,
7627
7716
  maxPanelHeight,
7628
7717
  label,
@@ -8754,19 +8843,19 @@ ${title ? `<h2>${esc(title)}</h2>` : ""}
8754
8843
  }
8755
8844
  _clearSelection() {
8756
8845
  if (!this._sel) return;
8757
- const pairs = this._sel.type === "single" ? [[this._sel.row, this._sel.col]] : Array.from(
8758
- { length: this._sel.r2 - this._sel.r1 + 1 },
8759
- (_, ri) => Array.from(
8760
- { length: this._sel.c2 - this._sel.c1 + 1 },
8761
- (_2, ci) => [this._sel.r1 + ri, this._sel.c1 + ci]
8762
- )
8763
- ).flat();
8846
+ const single = this._sel.type === "single";
8847
+ const r1 = single ? this._sel.row : this._sel.r1;
8848
+ const r2 = single ? this._sel.row : this._sel.r2;
8849
+ const c1 = single ? this._sel.col : this._sel.c1;
8850
+ const c2 = single ? this._sel.col : this._sel.c2;
8851
+ const editableFields = [];
8852
+ for (let c = c1; c <= c2; c++) {
8853
+ if (this._isEditable(c)) editableFields.push(this._columns[c]);
8854
+ }
8764
8855
  this._editTxnBegin();
8765
- pairs.forEach(([r, c]) => {
8766
- if (!this._isEditable(c)) return;
8767
- const field = this._columns[c];
8768
- this._setEdit(r, field, "");
8769
- });
8856
+ for (let r = r1; r <= r2; r++) {
8857
+ for (const field of editableFields) this._setEdit(r, field, "");
8858
+ }
8770
8859
  this._editTxnCommit();
8771
8860
  this._draw();
8772
8861
  }
@@ -8951,11 +9040,15 @@ ${title ? `<h2>${esc(title)}</h2>` : ""}
8951
9040
  const v = data[field];
8952
9041
  return v != null ? String(v) : "";
8953
9042
  }
8954
- // Re-runs validation for one cell and updates the validator's invalid-cell set.
9043
+ // Re-runs validation for one cell and updates the validator's invalid-cell set. Takes a
9044
+ // "row_field" key -- for callers that only have that (iterating an _edits-shaped Map). Callers
9045
+ // that already have row/field apart (_setEdit, validateAll) should call _revalidateRowField()
9046
+ // directly instead of paying to stringify them together here just to split them back apart.
8955
9047
  _revalidateKey(key) {
8956
9048
  const u = key.indexOf("_");
8957
- const row = Number(key.slice(0, u));
8958
- const field = key.slice(u + 1);
9049
+ this._revalidateRowField(Number(key.slice(0, u)), key.slice(u + 1));
9050
+ }
9051
+ _revalidateRowField(row, field) {
8959
9052
  this._validator.revalidate(row, field, this._resolveCellStringValue(row, field));
8960
9053
  }
8961
9054
  // Rebuilds invalid-cell state from scratch based on the current _edits map —
@@ -8990,11 +9083,11 @@ ${title ? `<h2>${esc(title)}</h2>` : ""}
8990
9083
  const validatedFields = this._columns.filter((f) => this._colDefMap.get(f)?.validation);
8991
9084
  if (validatedFields.length > 0) {
8992
9085
  this._dm.forEachLoaded((_, rowIndex) => {
8993
- validatedFields.forEach((field) => this._revalidateKey(`${rowIndex}_${field}`));
9086
+ validatedFields.forEach((field) => this._revalidateRowField(rowIndex, field));
8994
9087
  });
8995
9088
  this._localRows.forEach((_, i) => {
8996
9089
  const r = this._rowPlan.visualOfLocal(i);
8997
- validatedFields.forEach((field) => this._revalidateKey(`${r}_${field}`));
9090
+ validatedFields.forEach((field) => this._revalidateRowField(r, field));
8998
9091
  });
8999
9092
  }
9000
9093
  this._draw();
@@ -9021,7 +9114,7 @@ ${title ? `<h2>${esc(title)}</h2>` : ""}
9021
9114
  this._edits.set(key, val);
9022
9115
  this._editedRows.add(row);
9023
9116
  this._opts.onCellChange?.({ row, field, newValue: val, oldValue });
9024
- this._revalidateKey(key);
9117
+ this._revalidateRowField(row, field);
9025
9118
  this._growRowForMultilineValue(row, val);
9026
9119
  }
9027
9120
  // Excel grows a row's height the moment a cell picks up a line break -- typed (Alt+Enter) or
@@ -9676,7 +9769,6 @@ ${title ? `<h2>${esc(title)}</h2>` : ""}
9676
9769
  ev.mousedown = (e) => {
9677
9770
  const { x, y } = this._xy(e);
9678
9771
  const geo = this._geo();
9679
- const { v, h } = geo;
9680
9772
  const suppressReopenCell = this._suppressReopenCell;
9681
9773
  this._suppressReopenCell = null;
9682
9774
  if (this._editing) this._commitEdit();
@@ -9692,27 +9784,7 @@ ${title ? `<h2>${esc(title)}</h2>` : ""}
9692
9784
  if (withinSel) return;
9693
9785
  }
9694
9786
  }
9695
- if (x >= v.x) {
9696
- if (y >= v.thumbY && y <= v.thumbY + v.thumbH) {
9697
- this._drag = { axis: "v", startMouse: y, startScroll: this._scrollTop };
9698
- } else if (y >= v.y && y <= v.y + v.h) {
9699
- const ratio = Math.max(0, Math.min(1, (y - v.y - v.thumbH / 2) / (v.h - v.thumbH)));
9700
- this._scrollTop = geo.minScrollY + ratio * (geo.maxScrollY - geo.minScrollY);
9701
- this._clamp(geo);
9702
- this._draw();
9703
- }
9704
- e.preventDefault();
9705
- return;
9706
- }
9707
- if (y >= h.y) {
9708
- if (x >= h.thumbX && x <= h.thumbX + h.thumbW) {
9709
- this._drag = { axis: "h", startMouse: x, startScroll: this._scrollLeft };
9710
- } else if (x >= h.x && x <= h.x + h.w) {
9711
- const ratio = Math.max(0, Math.min(1, (x - h.x - h.thumbW / 2) / (h.w - h.thumbW)));
9712
- this._scrollLeft = ratio * geo.maxScrollX;
9713
- this._clamp(geo);
9714
- this._draw();
9715
- }
9787
+ if (this._hitScrollbar(x, y, geo)) {
9716
9788
  e.preventDefault();
9717
9789
  return;
9718
9790
  }
@@ -9924,25 +9996,7 @@ ${title ? `<h2>${esc(title)}</h2>` : ""}
9924
9996
  }
9925
9997
  }
9926
9998
  if (this._drag) {
9927
- const geo = this._geo();
9928
- const { v, h } = geo;
9929
- if (this._drag.axis === "v") {
9930
- const ratio = (y - this._drag.startMouse) / (v.h - v.thumbH);
9931
- this._scrollTop = this._drag.startScroll + ratio * (geo.maxScrollY - geo.minScrollY);
9932
- } else {
9933
- const ratio = (x - this._drag.startMouse) / (h.w - h.thumbW);
9934
- this._scrollLeft = this._drag.startScroll + ratio * geo.maxScrollX;
9935
- }
9936
- this._clamp(geo);
9937
- this._scrolling = true;
9938
- this._dm.setHold(true);
9939
- clearTimeout(this._scrollEndTimer);
9940
- this._scrollEndTimer = setTimeout(() => {
9941
- this._scrolling = false;
9942
- this._dm.setHold(false);
9943
- this._schedDraw();
9944
- }, 150);
9945
- this._schedDraw();
9999
+ this._updateScrollbarDrag(x, y);
9946
10000
  return;
9947
10001
  }
9948
10002
  if (this._selDragging) {
@@ -10329,19 +10383,23 @@ ${title ? `<h2>${esc(title)}</h2>` : ""}
10329
10383
  this._rowTap = null;
10330
10384
  const geo = this._geo();
10331
10385
  const headerH = geo.headerH;
10386
+ if (this._hitScrollbar(x, y, geo)) {
10387
+ e.preventDefault();
10388
+ return;
10389
+ }
10332
10390
  if (y >= 0 && y < headerH) {
10333
10391
  const col = this._hitHeader(x, geo);
10334
10392
  if (col !== null) {
10335
10393
  const colW = geo.colPositions[col + 1] - geo.colPositions[col];
10336
10394
  const colRight = this._colLeft(col, geo) + colW;
10337
10395
  const filterIconX = colRight - FILTER_ICON_W;
10338
- if (Math.abs(x - colRight) <= RESIZE_HIT_W) {
10396
+ if (Math.abs(x - colRight) <= RESIZE_HIT_W_TOUCH) {
10339
10397
  this._colResize = { col, startX: x, startWidth: colW, undoBefore: this._snapshotStructural() };
10340
10398
  e.preventDefault();
10341
10399
  return;
10342
10400
  }
10343
10401
  const colLeft = this._colLeft(col, geo);
10344
- if (col > 0 && Math.abs(x - colLeft) <= RESIZE_HIT_W) {
10402
+ if (col > 0 && Math.abs(x - colLeft) <= RESIZE_HIT_W_TOUCH) {
10345
10403
  const prevColW = geo.colPositions[col] - geo.colPositions[col - 1];
10346
10404
  this._colResize = { col: col - 1, startX: x, startWidth: prevColW, undoBefore: this._snapshotStructural() };
10347
10405
  e.preventDefault();
@@ -10353,7 +10411,7 @@ ${title ? `<h2>${esc(title)}</h2>` : ""}
10353
10411
  const _hcbCx = this._colLeft(col, geo) + _hcbPad + 6;
10354
10412
  const _hcbCell = computeHeaderCells(this._opts.headerRows, this._columns, this._opts.columnLetterHeader).find((c) => c.isLeaf && c.col === col);
10355
10413
  const _hcbCy = _hcbCell ? _hcbCell.row * this._opts.headerHeight + _hcbCell.rowspan * this._opts.headerHeight / 2 : headerH - this._opts.headerHeight / 2;
10356
- if (Math.abs(x - _hcbCx) <= 9 && Math.abs(y - _hcbCy) <= 9) {
10414
+ if (Math.abs(x - _hcbCx) <= RESIZE_HIT_W_TOUCH && Math.abs(y - _hcbCy) <= RESIZE_HIT_W_TOUCH) {
10357
10415
  const _hcbChecked = !(this._headerCheckboxState.get(_hcbField) ?? false);
10358
10416
  this._headerCheckboxState.set(_hcbField, _hcbChecked);
10359
10417
  this._draw();
@@ -10383,7 +10441,7 @@ ${title ? `<h2>${esc(title)}</h2>` : ""}
10383
10441
  return;
10384
10442
  }
10385
10443
  }
10386
- const boundaryRow = geo.rowNumW > 0 && x < geo.rowNumW && y >= geo.headerH ? this._hitRowBoundary(y, geo) : null;
10444
+ const boundaryRow = geo.rowNumW > 0 && x < geo.rowNumW && y >= geo.headerH ? this._hitRowBoundary(y, geo, RESIZE_HIT_W_TOUCH) : null;
10387
10445
  if (boundaryRow !== null) {
10388
10446
  this._rowResize = { row: boundaryRow, startY: y, startHeight: this._rowLayout.heightOf(boundaryRow), undoBefore: this._snapshotStructural() };
10389
10447
  e.preventDefault();
@@ -10405,7 +10463,7 @@ ${title ? `<h2>${esc(title)}</h2>` : ""}
10405
10463
  }
10406
10464
  return;
10407
10465
  }
10408
- if (this._sel && this._hitFillHandle(x, y, geo)) {
10466
+ if (this._sel && this._hitFillHandle(x, y, geo, RESIZE_HIT_W_TOUCH)) {
10409
10467
  this._fillDrag = { sel: this._sel };
10410
10468
  this._fillPreview = null;
10411
10469
  e.preventDefault();
@@ -10443,6 +10501,14 @@ ${title ? `<h2>${esc(title)}</h2>` : ""}
10443
10501
  e.preventDefault();
10444
10502
  return;
10445
10503
  }
10504
+ if (this._drag) {
10505
+ if (!e.touches.length) return;
10506
+ const t = e.touches[0];
10507
+ const rect2 = this._canvas.getBoundingClientRect();
10508
+ this._updateScrollbarDrag(t.clientX - rect2.left, t.clientY - rect2.top);
10509
+ e.preventDefault();
10510
+ return;
10511
+ }
10446
10512
  if (!this._touch) return;
10447
10513
  const rect = this._canvas.getBoundingClientRect();
10448
10514
  if (e.touches.length >= 2 && this._touch.twoFinger) {
@@ -10479,6 +10545,10 @@ ${title ? `<h2>${esc(title)}</h2>` : ""}
10479
10545
  ev.touchend = (e) => {
10480
10546
  this._cancelLongPress();
10481
10547
  if (this._finalizeStructuralGesture()) return;
10548
+ if (this._drag) {
10549
+ this._drag = null;
10550
+ return;
10551
+ }
10482
10552
  if (this._rowTap) {
10483
10553
  const { row, ctrlKey, shiftKey } = this._rowTap;
10484
10554
  this._rowTap = null;
@@ -11908,7 +11978,7 @@ ${title ? `<h2>${esc(title)}</h2>` : ""}
11908
11978
  JHGrid.use(RowSelectionPlugin);
11909
11979
 
11910
11980
  // index.js
11911
- var VERSION = "0.1.0";
11981
+ var VERSION = "0.1.2";
11912
11982
  var SUPPORTED_BROWSERS = {
11913
11983
  chrome: 99,
11914
11984
  edge: 99,