@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 +21 -5
- package/dist/jhgrid.esm.js +146 -76
- package/dist/jhgrid.js +146 -76
- package/dist/jhgrid.min.js +37 -37
- package/package.json +1 -1
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
|

|
|
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
|
-
|
|
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
|
|
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
|
|
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 '
|
|
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 '
|
|
148
|
+
import { JHGrid } from '@jh-grid/jhgrid-js';
|
|
133
149
|
|
|
134
150
|
const grid = new JHGrid({
|
|
135
151
|
container: '#my-grid',
|
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
|
|
|
@@ -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
|
-
|
|
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) <=
|
|
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) <=
|
|
7039
|
+
if (Math.abs(y - topY) <= hitW) return row - 1;
|
|
7013
7040
|
}
|
|
7014
7041
|
return null;
|
|
7015
7042
|
}
|
|
7016
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
8718
|
-
|
|
8719
|
-
|
|
8720
|
-
|
|
8721
|
-
|
|
8722
|
-
|
|
8723
|
-
|
|
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
|
-
|
|
8726
|
-
|
|
8727
|
-
|
|
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
|
-
|
|
8918
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
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) <=
|
|
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) <=
|
|
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) <=
|
|
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.
|
|
11941
|
+
var VERSION = "0.1.2";
|
|
11872
11942
|
var SUPPORTED_BROWSERS = {
|
|
11873
11943
|
chrome: 99,
|
|
11874
11944
|
edge: 99,
|