@freegridjs/core 0.8.1 → 1.0.0
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/LICENSE +3 -1
- package/README.md +14 -11
- package/dist/cdn/freegrid.css +49 -0
- package/dist/cdn/freegrid.esm.js +17 -8
- package/dist/cdn/freegrid.min.js +17 -8
- package/dist/index.d.ts +54 -39
- package/dist/index.js +1813 -334
- package/dist/locales/de.d.ts +1 -1
- package/dist/locales/de.js +8 -0
- package/dist/locales/en.d.ts +1 -1
- package/dist/locales/en.js +8 -0
- package/dist/locales/es.d.ts +1 -1
- package/dist/locales/es.js +8 -0
- package/dist/locales/fr.d.ts +1 -1
- package/dist/locales/fr.js +8 -0
- package/dist/locales/id.d.ts +5 -0
- package/dist/locales/id.js +113 -0
- package/dist/locales/it.d.ts +5 -0
- package/dist/locales/it.js +113 -0
- package/dist/locales/ja.d.ts +1 -1
- package/dist/locales/ja.js +8 -0
- package/dist/locales/ko.d.ts +1 -1
- package/dist/locales/ko.js +8 -0
- package/dist/locales/pt-BR.d.ts +5 -0
- package/dist/locales/pt-BR.js +113 -0
- package/dist/locales/ru.d.ts +5 -0
- package/dist/locales/ru.js +113 -0
- package/dist/locales/th.d.ts +5 -0
- package/dist/locales/th.js +113 -0
- package/dist/locales/vi.d.ts +5 -0
- package/dist/locales/vi.js +113 -0
- package/dist/locales/zh-CN.d.ts +1 -1
- package/dist/locales/zh-CN.js +8 -0
- package/dist/locales/zh-TW.d.ts +5 -0
- package/dist/locales/zh-TW.js +113 -0
- package/dist/styles.css +49 -0
- package/dist/{types-e45suTSB.d.ts → types-D9gm8TyF.d.ts} +218 -6
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -32,7 +32,20 @@ var DRAG_COLUMN = {
|
|
|
32
32
|
filter: false,
|
|
33
33
|
chooser: false
|
|
34
34
|
};
|
|
35
|
-
var
|
|
35
|
+
var ROWNUM_COL_ID = "__fg_rownum__";
|
|
36
|
+
var rowNumberColumn = (width = 52, header2 = "#") => ({
|
|
37
|
+
id: ROWNUM_COL_ID,
|
|
38
|
+
header: header2,
|
|
39
|
+
width,
|
|
40
|
+
minWidth: width,
|
|
41
|
+
maxWidth: width,
|
|
42
|
+
pinned: "left",
|
|
43
|
+
sortable: false,
|
|
44
|
+
resizable: false,
|
|
45
|
+
filter: false,
|
|
46
|
+
chooser: false
|
|
47
|
+
});
|
|
48
|
+
var isSystemCol = (id) => id === SELECT_COL_ID || id === DRAG_COL_ID || id === ROWNUM_COL_ID;
|
|
36
49
|
var DEFAULT_WIDTH = 120;
|
|
37
50
|
var DEFAULT_MIN_WIDTH = 40;
|
|
38
51
|
var REGIONS = ["left", "center", "right"];
|
|
@@ -107,7 +120,8 @@ var ColumnModel = class {
|
|
|
107
120
|
width: clampWidth(def, def.width ?? DEFAULT_WIDTH),
|
|
108
121
|
flex: def.flex && def.flex > 0 ? def.flex : null,
|
|
109
122
|
hide: def.hide ?? false,
|
|
110
|
-
pinned: def.pinned ?? parent?.pinned ?? null
|
|
123
|
+
pinned: def.pinned ?? parent?.pinned ?? null,
|
|
124
|
+
open: !!def.openByDefault
|
|
111
125
|
};
|
|
112
126
|
this.byId.set(id, node);
|
|
113
127
|
if (isGroup) node.children = valid(def.children).map((c) => build(c, node, depth + 1));
|
|
@@ -228,7 +242,7 @@ var ColumnModel = class {
|
|
|
228
242
|
return cells;
|
|
229
243
|
}
|
|
230
244
|
getState() {
|
|
231
|
-
return this.leafNodes().filter((n) => !isSystemCol(n.id)).map((n) => ({ colId: n.id, width: this.leafById.get(n.id)?.width ?? n.width, hide: n.hide, pinned: n.pinned }));
|
|
245
|
+
return this.leafNodes().filter((n) => !isSystemCol(n.id)).map((n) => ({ colId: n.id, width: this.leafById.get(n.id)?.width ?? n.width, hide: !!n.hide, pinned: n.pinned ?? null }));
|
|
232
246
|
}
|
|
233
247
|
applyState(state) {
|
|
234
248
|
const rank = /* @__PURE__ */ new Map();
|
|
@@ -236,11 +250,14 @@ var ColumnModel = class {
|
|
|
236
250
|
rank.set(s.colId, i);
|
|
237
251
|
const node = this.byId.get(s.colId);
|
|
238
252
|
if (!node || node.children || isSystemCol(s.colId)) return;
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
253
|
+
if (typeof s.width === "number" && Number.isFinite(s.width)) {
|
|
254
|
+
node.flex = null;
|
|
255
|
+
node.width = clampWidth(node.def, s.width);
|
|
256
|
+
}
|
|
257
|
+
if (typeof s.hide === "boolean") node.hide = s.hide;
|
|
258
|
+
if ("pinned" in s) node.pinned = s.pinned === "left" || s.pinned === "right" ? s.pinned : null;
|
|
243
259
|
});
|
|
260
|
+
rank.set(ROWNUM_COL_ID, -3);
|
|
244
261
|
rank.set(DRAG_COL_ID, -2);
|
|
245
262
|
rank.set(SELECT_COL_ID, -1);
|
|
246
263
|
const nodeRank = (n) => n.children ? Math.min(Infinity, ...n.children.map(nodeRank)) : rank.get(n.id) ?? Infinity;
|
|
@@ -259,8 +276,33 @@ var ColumnModel = class {
|
|
|
259
276
|
this.roots_.forEach(walk);
|
|
260
277
|
return out;
|
|
261
278
|
}
|
|
279
|
+
/** A group whose children use columnGroupShow, so its header gets an open/close toggle. */
|
|
280
|
+
isCollapsible(node) {
|
|
281
|
+
return !!node.children?.some((c) => c.def.columnGroupShow === "open" || c.def.columnGroupShow === "closed");
|
|
282
|
+
}
|
|
283
|
+
setGroupOpen(id, open) {
|
|
284
|
+
const node = this.byId.get(id);
|
|
285
|
+
if (!node?.children || node.open === open) return false;
|
|
286
|
+
node.open = open;
|
|
287
|
+
this.relayout();
|
|
288
|
+
return true;
|
|
289
|
+
}
|
|
290
|
+
/** Every collapsible group and whether it is open. */
|
|
291
|
+
groupOpenState() {
|
|
292
|
+
const out = {};
|
|
293
|
+
for (const n of this.byId.values()) if (this.isCollapsible(n)) out[n.id] = n.open;
|
|
294
|
+
return out;
|
|
295
|
+
}
|
|
296
|
+
/** columnGroupShow: hidden while its group (or any group above) is in the other state. */
|
|
297
|
+
shownByGroups(n) {
|
|
298
|
+
for (let c = n; c?.parent; c = c.parent) {
|
|
299
|
+
const show = c.def.columnGroupShow;
|
|
300
|
+
if (show === "open" && !c.parent.open || show === "closed" && c.parent.open) return false;
|
|
301
|
+
}
|
|
302
|
+
return true;
|
|
303
|
+
}
|
|
262
304
|
relayout() {
|
|
263
|
-
const visible = this.leafNodes().filter((n) => !n.hide);
|
|
305
|
+
const visible = this.leafNodes().filter((n) => !n.hide && this.shownByGroups(n));
|
|
264
306
|
const fixed = visible.reduce((s, n) => s + (n.flex ? 0 : n.width), 0);
|
|
265
307
|
const totalFlex = visible.reduce((s, n) => s + (n.flex ?? 0), 0);
|
|
266
308
|
const free = Math.max(0, this.containerWidth - fixed);
|
|
@@ -282,215 +324,6 @@ var ColumnModel = class {
|
|
|
282
324
|
}
|
|
283
325
|
};
|
|
284
326
|
|
|
285
|
-
// src/rows/filters.ts
|
|
286
|
-
function isBlank(v) {
|
|
287
|
-
return v == null || typeof v === "string" && v.trim() === "" || typeof v === "number" && Number.isNaN(v);
|
|
288
|
-
}
|
|
289
|
-
var pad = (n) => String(n).padStart(2, "0");
|
|
290
|
-
function toDateKey(v) {
|
|
291
|
-
if (isBlank(v)) return null;
|
|
292
|
-
if (typeof v === "string" && /^\d{4}-\d{2}-\d{2}$/.test(v)) return v;
|
|
293
|
-
const d = v instanceof Date ? v : new Date(v);
|
|
294
|
-
if (Number.isNaN(d.getTime())) return null;
|
|
295
|
-
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
|
|
296
|
-
}
|
|
297
|
-
function setKey(v) {
|
|
298
|
-
if (isBlank(v)) return "";
|
|
299
|
-
if (v instanceof Date) return toDateKey(v) ?? "";
|
|
300
|
-
return String(v);
|
|
301
|
-
}
|
|
302
|
-
var all = () => true;
|
|
303
|
-
function buildPredicate(model) {
|
|
304
|
-
switch (model.type) {
|
|
305
|
-
case "text": {
|
|
306
|
-
const q = (model.value ?? "").toLowerCase();
|
|
307
|
-
const s = (v) => isBlank(v) ? "" : String(v).toLowerCase();
|
|
308
|
-
switch (model.op) {
|
|
309
|
-
case "contains":
|
|
310
|
-
return (v) => s(v).includes(q);
|
|
311
|
-
case "equals":
|
|
312
|
-
return (v) => s(v) === q;
|
|
313
|
-
case "notEquals":
|
|
314
|
-
return (v) => s(v) !== q;
|
|
315
|
-
case "startsWith":
|
|
316
|
-
return (v) => s(v).startsWith(q);
|
|
317
|
-
case "endsWith":
|
|
318
|
-
return (v) => s(v).endsWith(q);
|
|
319
|
-
case "blank":
|
|
320
|
-
return isBlank;
|
|
321
|
-
case "notBlank":
|
|
322
|
-
return (v) => !isBlank(v);
|
|
323
|
-
}
|
|
324
|
-
return all;
|
|
325
|
-
}
|
|
326
|
-
case "number": {
|
|
327
|
-
const n = (v) => isBlank(v) ? NaN : Number(v);
|
|
328
|
-
if (model.op === "blank") return (v) => Number.isNaN(n(v));
|
|
329
|
-
if (model.op === "notBlank") return (v) => !Number.isNaN(n(v));
|
|
330
|
-
const a = model.value;
|
|
331
|
-
if (a == null || Number.isNaN(a)) return all;
|
|
332
|
-
switch (model.op) {
|
|
333
|
-
case "eq":
|
|
334
|
-
return (v) => n(v) === a;
|
|
335
|
-
case "ne":
|
|
336
|
-
return (v) => n(v) !== a;
|
|
337
|
-
case "lt":
|
|
338
|
-
return (v) => n(v) < a;
|
|
339
|
-
case "lte":
|
|
340
|
-
return (v) => n(v) <= a;
|
|
341
|
-
case "gt":
|
|
342
|
-
return (v) => n(v) > a;
|
|
343
|
-
case "gte":
|
|
344
|
-
return (v) => n(v) >= a;
|
|
345
|
-
case "between": {
|
|
346
|
-
const b = model.valueTo;
|
|
347
|
-
if (b == null || Number.isNaN(b)) return all;
|
|
348
|
-
const lo = Math.min(a, b), hi = Math.max(a, b);
|
|
349
|
-
return (v) => {
|
|
350
|
-
const x = n(v);
|
|
351
|
-
return x >= lo && x <= hi;
|
|
352
|
-
};
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
return all;
|
|
356
|
-
}
|
|
357
|
-
case "date": {
|
|
358
|
-
if (model.op === "blank") return (v) => toDateKey(v) === null;
|
|
359
|
-
if (model.op === "notBlank") return (v) => toDateKey(v) !== null;
|
|
360
|
-
const a = model.value;
|
|
361
|
-
if (!a) return all;
|
|
362
|
-
const k = (v) => toDateKey(v);
|
|
363
|
-
switch (model.op) {
|
|
364
|
-
case "equals":
|
|
365
|
-
return (v) => k(v) === a;
|
|
366
|
-
case "before":
|
|
367
|
-
return (v) => {
|
|
368
|
-
const x = k(v);
|
|
369
|
-
return x !== null && x < a;
|
|
370
|
-
};
|
|
371
|
-
case "after":
|
|
372
|
-
return (v) => {
|
|
373
|
-
const x = k(v);
|
|
374
|
-
return x !== null && x > a;
|
|
375
|
-
};
|
|
376
|
-
case "between": {
|
|
377
|
-
const b = model.valueTo;
|
|
378
|
-
if (!b) return all;
|
|
379
|
-
const lo = a < b ? a : b, hi = a < b ? b : a;
|
|
380
|
-
return (v) => {
|
|
381
|
-
const x = k(v);
|
|
382
|
-
return x !== null && x >= lo && x <= hi;
|
|
383
|
-
};
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
return all;
|
|
387
|
-
}
|
|
388
|
-
case "set": {
|
|
389
|
-
const allowed = new Set(model.values.map(setKey));
|
|
390
|
-
return (v) => allowed.has(setKey(v));
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
return all;
|
|
394
|
-
}
|
|
395
|
-
function quickTokens(q) {
|
|
396
|
-
const t = q.trim().toLowerCase();
|
|
397
|
-
return t ? t.split(/\s+/) : [];
|
|
398
|
-
}
|
|
399
|
-
function matchesQuick(tokens, text2) {
|
|
400
|
-
return tokens.every((t) => text2.includes(t));
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
// src/i18n/format.ts
|
|
404
|
-
var cache = /* @__PURE__ */ new Map();
|
|
405
|
-
function parseNumberPattern(p) {
|
|
406
|
-
if (p === "integer") return { maximumFractionDigits: 0 };
|
|
407
|
-
if (p === "percent") return { style: "percent" };
|
|
408
|
-
if (p.startsWith("currency:")) return { style: "currency", currency: p.slice(9) };
|
|
409
|
-
if (!/^[#0,]*(\.[#0]+)?%?$/.test(p) || !/[#0]/.test(p)) return null;
|
|
410
|
-
const pct = p.endsWith("%");
|
|
411
|
-
const body = pct ? p.slice(0, -1) : p;
|
|
412
|
-
const frac = body.includes(".") ? body.split(".")[1] : "";
|
|
413
|
-
return {
|
|
414
|
-
useGrouping: body.includes(","),
|
|
415
|
-
minimumFractionDigits: (frac.match(/0/g) ?? []).length,
|
|
416
|
-
maximumFractionDigits: frac.length,
|
|
417
|
-
...pct ? { style: "percent" } : {}
|
|
418
|
-
};
|
|
419
|
-
}
|
|
420
|
-
var DATE_PRESETS = {
|
|
421
|
-
date: { year: "numeric", month: "2-digit", day: "2-digit" },
|
|
422
|
-
datetime: { year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit" },
|
|
423
|
-
time: { hour: "2-digit", minute: "2-digit" },
|
|
424
|
-
short: { dateStyle: "short" },
|
|
425
|
-
medium: { dateStyle: "medium" },
|
|
426
|
-
long: { dateStyle: "long" }
|
|
427
|
-
};
|
|
428
|
-
function toDate(v) {
|
|
429
|
-
if (isBlank(v)) return null;
|
|
430
|
-
if (v instanceof Date) return Number.isNaN(v.getTime()) ? null : v;
|
|
431
|
-
if (typeof v === "string") {
|
|
432
|
-
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(v);
|
|
433
|
-
if (m) return new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]));
|
|
434
|
-
}
|
|
435
|
-
if (typeof v !== "string" && typeof v !== "number") return null;
|
|
436
|
-
const d = new Date(v);
|
|
437
|
-
return Number.isNaN(d.getTime()) ? null : d;
|
|
438
|
-
}
|
|
439
|
-
function create(type, format, locale) {
|
|
440
|
-
switch (type) {
|
|
441
|
-
case "number": {
|
|
442
|
-
let opts = {};
|
|
443
|
-
if (typeof format === "string") {
|
|
444
|
-
const parsed = parseNumberPattern(format);
|
|
445
|
-
if (!parsed) warn(`unknown number format "${format}"`);
|
|
446
|
-
opts = parsed ?? {};
|
|
447
|
-
} else if (format) opts = format;
|
|
448
|
-
const nf = new Intl.NumberFormat(locale, opts);
|
|
449
|
-
return (v) => {
|
|
450
|
-
if (isBlank(v)) return "";
|
|
451
|
-
const n = typeof v === "number" ? v : Number(v);
|
|
452
|
-
return Number.isNaN(n) ? String(v) : nf.format(n);
|
|
453
|
-
};
|
|
454
|
-
}
|
|
455
|
-
case "date": {
|
|
456
|
-
if (locale.toLowerCase().startsWith("ko") && (format === void 0 || format === "date" || format === "datetime")) {
|
|
457
|
-
const pad3 = (n) => String(n).padStart(2, "0");
|
|
458
|
-
const withTime = format === "datetime";
|
|
459
|
-
return (v) => {
|
|
460
|
-
const d = toDate(v);
|
|
461
|
-
if (!d) return isBlank(v) ? "" : String(v);
|
|
462
|
-
const day = `${d.getFullYear()}-${pad3(d.getMonth() + 1)}-${pad3(d.getDate())}`;
|
|
463
|
-
return withTime ? `${day} ${pad3(d.getHours())}:${pad3(d.getMinutes())}` : day;
|
|
464
|
-
};
|
|
465
|
-
}
|
|
466
|
-
let opts = DATE_PRESETS.date;
|
|
467
|
-
if (typeof format === "string") {
|
|
468
|
-
if (!DATE_PRESETS[format]) warn(`unknown date format "${format}"`);
|
|
469
|
-
opts = DATE_PRESETS[format] ?? DATE_PRESETS.date;
|
|
470
|
-
} else if (format) opts = format;
|
|
471
|
-
const df = new Intl.DateTimeFormat(locale, opts);
|
|
472
|
-
return (v) => {
|
|
473
|
-
const d = toDate(v);
|
|
474
|
-
return d ? df.format(d) : isBlank(v) ? "" : String(v);
|
|
475
|
-
};
|
|
476
|
-
}
|
|
477
|
-
case "boolean":
|
|
478
|
-
return (v) => v === true ? "\u2713" : v === false ? "\u2717" : "";
|
|
479
|
-
default:
|
|
480
|
-
return (v) => isBlank(v) ? "" : v instanceof Date ? v.toISOString() : String(v);
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
function getFormatter(def, locale) {
|
|
484
|
-
const f = def.format;
|
|
485
|
-
const key = `${locale}|${def.type ?? "text"}|${typeof f === "string" ? f : JSON.stringify(f ?? null)}`;
|
|
486
|
-
let fmt = cache.get(key);
|
|
487
|
-
if (!fmt) {
|
|
488
|
-
fmt = create(def.type, f, locale);
|
|
489
|
-
cache.set(key, fmt);
|
|
490
|
-
}
|
|
491
|
-
return fmt;
|
|
492
|
-
}
|
|
493
|
-
|
|
494
327
|
// src/formula.ts
|
|
495
328
|
var FormulaError = class extends Error {
|
|
496
329
|
constructor(code) {
|
|
@@ -670,50 +503,465 @@ var Parser = class {
|
|
|
670
503
|
const cond = typeof args[0] === "boolean" ? args[0] : num(args[0]) !== 0;
|
|
671
504
|
return cond ? args[1] : args.length > 2 ? args[2] : false;
|
|
672
505
|
}
|
|
673
|
-
const fn = Object.hasOwn(FUNCS, upper) ? FUNCS[upper] : void 0;
|
|
674
|
-
if (!fn) throw new FormulaError("#NAME?");
|
|
675
|
-
return fn(args);
|
|
506
|
+
const fn = Object.hasOwn(FUNCS, upper) ? FUNCS[upper] : void 0;
|
|
507
|
+
if (!fn) throw new FormulaError("#NAME?");
|
|
508
|
+
return fn(args);
|
|
509
|
+
}
|
|
510
|
+
if (/^(true|false)$/i.test(name)) return name.toLowerCase() === "true";
|
|
511
|
+
return this.resolve(name);
|
|
512
|
+
}
|
|
513
|
+
};
|
|
514
|
+
var MAX_DEPTH = 32;
|
|
515
|
+
function evaluateFormula(formula, row, seen2 = /* @__PURE__ */ new Set(), depth = 0) {
|
|
516
|
+
try {
|
|
517
|
+
const resolve = (name) => {
|
|
518
|
+
if (!row || !Object.hasOwn(row, name)) throw new FormulaError("#REF!");
|
|
519
|
+
const v2 = row[name];
|
|
520
|
+
if (typeof v2 === "string" && v2.startsWith("=")) {
|
|
521
|
+
if (seen2.has(name) || depth >= MAX_DEPTH) throw new FormulaError("#CYCLE!");
|
|
522
|
+
const r = evaluateFormula(v2, row, /* @__PURE__ */ new Set([...seen2, name]), depth + 1);
|
|
523
|
+
if (typeof r === "string" && /^#[A-Z/0]+[!?]$/.test(r)) throw new FormulaError(r);
|
|
524
|
+
return r;
|
|
525
|
+
}
|
|
526
|
+
return v2;
|
|
527
|
+
};
|
|
528
|
+
const v = new Parser(tokenize(formula.slice(1)), resolve).parse();
|
|
529
|
+
if (typeof v === "number" && !Number.isFinite(v)) return "#NUM!";
|
|
530
|
+
return v;
|
|
531
|
+
} catch (e) {
|
|
532
|
+
return e instanceof FormulaError ? e.code : "#ERROR!";
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
var isFormula = (v) => typeof v === "string" && v.length > 1 && v.startsWith("=");
|
|
536
|
+
|
|
537
|
+
// src/rows/values.ts
|
|
538
|
+
function getCellValue(def, row) {
|
|
539
|
+
if (def.valueGetter) {
|
|
540
|
+
try {
|
|
541
|
+
return def.valueGetter(row);
|
|
542
|
+
} catch {
|
|
543
|
+
warn(`valueGetter of column "${def.id ?? def.field}" threw; rendering an empty cell`);
|
|
544
|
+
return void 0;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
if (def.field == null) return void 0;
|
|
548
|
+
const raw = row[def.field];
|
|
549
|
+
return def.formulas && isFormula(raw) ? evaluateFormula(raw, row, /* @__PURE__ */ new Set([def.field])) : raw;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// src/interactions/filterRow.ts
|
|
553
|
+
var REGIONS2 = ["left", "center", "right"];
|
|
554
|
+
var NUM_OPS = [[">=", "gte"], ["<=", "lte"], ["<>", "ne"], ["!=", "ne"], [">", "gt"], ["<", "lt"], ["=", "eq"]];
|
|
555
|
+
var DATE_OPS = [[">", "after"], ["<", "before"], ["=", "equals"]];
|
|
556
|
+
var RANGE = /^(.+?)\s*(?:~|\.\.)\s*(.+)$/;
|
|
557
|
+
var toNumber = (s) => {
|
|
558
|
+
const n = Number(s.replace(/[,\s₩$€¥]/g, ""));
|
|
559
|
+
return s.trim() !== "" && Number.isFinite(n) ? n : null;
|
|
560
|
+
};
|
|
561
|
+
var toDateKey = (s) => {
|
|
562
|
+
const m = /^(\d{4})[-./]\s*(\d{1,2})[-./]\s*(\d{1,2})\.?$/.exec(s.trim());
|
|
563
|
+
return m ? `${m[1]}-${m[2].padStart(2, "0")}-${m[3].padStart(2, "0")}` : null;
|
|
564
|
+
};
|
|
565
|
+
function parseFilterText(type, text2) {
|
|
566
|
+
const t = text2.trim();
|
|
567
|
+
if (!t) return null;
|
|
568
|
+
if (type === "text") return { type: "text", op: "contains", value: t };
|
|
569
|
+
const range = RANGE.exec(t);
|
|
570
|
+
if (type === "number") {
|
|
571
|
+
if (range) {
|
|
572
|
+
const a = toNumber(range[1]), b = toNumber(range[2]);
|
|
573
|
+
return a === null || b === null ? void 0 : { type: "number", op: "between", value: Math.min(a, b), valueTo: Math.max(a, b) };
|
|
574
|
+
}
|
|
575
|
+
const op2 = NUM_OPS.find(([p]) => t.startsWith(p));
|
|
576
|
+
const n = toNumber(op2 ? t.slice(op2[0].length) : t);
|
|
577
|
+
return n === null ? void 0 : { type: "number", op: op2 ? op2[1] : "eq", value: n };
|
|
578
|
+
}
|
|
579
|
+
if (range) {
|
|
580
|
+
const a = toDateKey(range[1]), b = toDateKey(range[2]);
|
|
581
|
+
return !a || !b ? void 0 : { type: "date", op: "between", value: a < b ? a : b, valueTo: a < b ? b : a };
|
|
582
|
+
}
|
|
583
|
+
const op = DATE_OPS.find(([p]) => t.startsWith(p));
|
|
584
|
+
const d = toDateKey(op ? t.slice(op[0].length) : t);
|
|
585
|
+
return d ? { type: "date", op: op ? op[1] : "equals", value: d } : void 0;
|
|
586
|
+
}
|
|
587
|
+
function filterText(model) {
|
|
588
|
+
if (!model) return "";
|
|
589
|
+
if (model.type === "text") return model.op === "contains" ? model.value ?? "" : "";
|
|
590
|
+
if (model.type === "number") {
|
|
591
|
+
if (model.op === "between") return `${model.value}~${model.valueTo}`;
|
|
592
|
+
const sym = NUM_OPS.find(([, op]) => op === model.op)?.[0];
|
|
593
|
+
return sym && model.value !== void 0 ? `${sym === "=" ? "" : sym}${model.value}` : "";
|
|
594
|
+
}
|
|
595
|
+
if (model.type === "date") {
|
|
596
|
+
if (model.op === "between") return `${model.value}~${model.valueTo}`;
|
|
597
|
+
const sym = DATE_OPS.find(([, op]) => op === model.op)?.[0];
|
|
598
|
+
return sym && model.value ? `${sym === "=" ? "" : sym}${model.value}` : "";
|
|
599
|
+
}
|
|
600
|
+
return "";
|
|
601
|
+
}
|
|
602
|
+
var FilterRow = class {
|
|
603
|
+
constructor(grid, panes) {
|
|
604
|
+
this.grid = grid;
|
|
605
|
+
this.layout = "";
|
|
606
|
+
this.timers = /* @__PURE__ */ new Map();
|
|
607
|
+
this.data = null;
|
|
608
|
+
this.synced = null;
|
|
609
|
+
this.rebuilt = false;
|
|
610
|
+
this.inners = {};
|
|
611
|
+
for (const r of REGIONS2) {
|
|
612
|
+
this.inners[r] = el("div", "fg-filter-row", { role: "row" });
|
|
613
|
+
panes[r].append(this.inners[r]);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
get height() {
|
|
617
|
+
return Math.round(this.grid.renderer.opts.rowHeight * 0.95);
|
|
618
|
+
}
|
|
619
|
+
render() {
|
|
620
|
+
const { columns, store } = this.grid;
|
|
621
|
+
const h = this.height;
|
|
622
|
+
const cols = columns.displayed();
|
|
623
|
+
if (store.state.data !== this.data) {
|
|
624
|
+
this.data = store.state.data;
|
|
625
|
+
this.layout = "";
|
|
626
|
+
}
|
|
627
|
+
const key = `${h}|${store.state.locale.code}|${cols.map((c) => `${c.id}:${c.left}:${c.width}:${c.region}`).join(",")}`;
|
|
628
|
+
if (key !== this.layout) {
|
|
629
|
+
this.layout = key;
|
|
630
|
+
for (const r of REGIONS2) {
|
|
631
|
+
const inner = this.inners[r];
|
|
632
|
+
inner.style.width = `${columns.regionWidth(r)}px`;
|
|
633
|
+
inner.style.height = `${h}px`;
|
|
634
|
+
inner.replaceChildren(...columns.region(r).map((c) => this.cell(c, h)));
|
|
635
|
+
}
|
|
636
|
+
this.rebuilt = true;
|
|
637
|
+
}
|
|
638
|
+
this.sync();
|
|
639
|
+
}
|
|
640
|
+
/** Show filters set elsewhere (menu, API) in the boxes that are not being typed in. */
|
|
641
|
+
sync() {
|
|
642
|
+
const filters = this.grid.store.state.filters;
|
|
643
|
+
if (filters === this.synced && !this.rebuilt) return;
|
|
644
|
+
this.synced = filters;
|
|
645
|
+
this.rebuilt = false;
|
|
646
|
+
for (const r of REGIONS2) {
|
|
647
|
+
for (const box of this.inners[r].querySelectorAll("[data-col]")) {
|
|
648
|
+
const colId = box.dataset.col;
|
|
649
|
+
const model = filters[colId];
|
|
650
|
+
box.closest(".fg-filter-cell").classList.toggle("fg-filter-active", !!model);
|
|
651
|
+
if (box === box.ownerDocument.activeElement || box.getRootNode().activeElement === box) continue;
|
|
652
|
+
if (box instanceof HTMLSelectElement) {
|
|
653
|
+
const v = model?.type === "set" && model.values.length === 1 ? JSON.stringify(model.values[0]) : "";
|
|
654
|
+
if ([...box.options].some((o) => o.value === v)) box.value = v;
|
|
655
|
+
} else box.value = filterText(model);
|
|
656
|
+
box.classList.remove("fg-filter-bad");
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
cell(c, h) {
|
|
661
|
+
const cell = el("div", "fg-filter-cell");
|
|
662
|
+
cell.style.left = `${c.left}px`;
|
|
663
|
+
cell.style.width = `${c.width}px`;
|
|
664
|
+
cell.style.height = `${h}px`;
|
|
665
|
+
const type = isSystemCol(c.id) ? null : filterTypeOf(c.def);
|
|
666
|
+
if (!type) return cell;
|
|
667
|
+
const loc = this.grid.store.state.locale;
|
|
668
|
+
const label = `${loc.filter}: ${c.def.header ?? c.def.field ?? c.id}`;
|
|
669
|
+
if (type === "set") {
|
|
670
|
+
const select = el("select", "fg-filter-input", { "aria-label": label });
|
|
671
|
+
select.dataset.col = c.id;
|
|
672
|
+
const all2 = el("option", "", { value: "" });
|
|
673
|
+
all2.textContent = loc.selectAll;
|
|
674
|
+
select.append(all2);
|
|
675
|
+
for (const v of this.distinct(c.def)) {
|
|
676
|
+
const o = el("option", "", { value: JSON.stringify(v) });
|
|
677
|
+
o.textContent = v === true ? "\u2713" : v === false ? "\u2717" : String(v);
|
|
678
|
+
select.append(o);
|
|
679
|
+
}
|
|
680
|
+
select.addEventListener("change", () => {
|
|
681
|
+
this.grid.setFilter(c.id, select.value ? { type: "set", values: [JSON.parse(select.value)] } : null);
|
|
682
|
+
});
|
|
683
|
+
cell.append(select);
|
|
684
|
+
return cell;
|
|
685
|
+
}
|
|
686
|
+
const input = el("input", "fg-filter-input", { type: "text", "aria-label": label, spellcheck: "false", autocomplete: "off" });
|
|
687
|
+
input.dataset.col = c.id;
|
|
688
|
+
if (type === "number") input.placeholder = ">0, 10~20";
|
|
689
|
+
if (type === "date") input.placeholder = ">2026-01-01";
|
|
690
|
+
const apply = () => {
|
|
691
|
+
const model = parseFilterText(type, input.value);
|
|
692
|
+
input.classList.toggle("fg-filter-bad", model === void 0);
|
|
693
|
+
if (model === void 0) return;
|
|
694
|
+
this.grid.setFilter(c.id, model);
|
|
695
|
+
};
|
|
696
|
+
input.addEventListener("input", () => {
|
|
697
|
+
clearTimeout(this.timers.get(c.id));
|
|
698
|
+
this.timers.set(c.id, window.setTimeout(apply, 200));
|
|
699
|
+
});
|
|
700
|
+
input.addEventListener("keydown", (e) => {
|
|
701
|
+
e.stopPropagation();
|
|
702
|
+
if (e.key === "Enter") {
|
|
703
|
+
clearTimeout(this.timers.get(c.id));
|
|
704
|
+
apply();
|
|
705
|
+
} else if (e.key === "Escape") {
|
|
706
|
+
input.value = "";
|
|
707
|
+
clearTimeout(this.timers.get(c.id));
|
|
708
|
+
apply();
|
|
709
|
+
}
|
|
710
|
+
});
|
|
711
|
+
cell.append(input);
|
|
712
|
+
return cell;
|
|
713
|
+
}
|
|
714
|
+
/** Values for a set box: distinct, sorted, at most 200. */
|
|
715
|
+
distinct(def) {
|
|
716
|
+
const seen2 = /* @__PURE__ */ new Map();
|
|
717
|
+
for (const row of this.grid.store.state.data) {
|
|
718
|
+
const v = getCellValue(def, row);
|
|
719
|
+
if (v === null || v === void 0 || v === "") continue;
|
|
720
|
+
const k = JSON.stringify(v);
|
|
721
|
+
if (!seen2.has(k)) seen2.set(k, v);
|
|
722
|
+
if (seen2.size >= 200) break;
|
|
723
|
+
}
|
|
724
|
+
return [...seen2.values()].sort((a, b) => String(a).localeCompare(String(b)));
|
|
725
|
+
}
|
|
726
|
+
setTransform(x) {
|
|
727
|
+
this.inners.center.style.transform = `translateX(${-x}px)`;
|
|
728
|
+
}
|
|
729
|
+
destroy() {
|
|
730
|
+
for (const t of this.timers.values()) clearTimeout(t);
|
|
731
|
+
for (const r of REGIONS2) this.inners[r].remove();
|
|
732
|
+
}
|
|
733
|
+
};
|
|
734
|
+
|
|
735
|
+
// src/rows/filters.ts
|
|
736
|
+
function isBlank(v) {
|
|
737
|
+
return v == null || typeof v === "string" && v.trim() === "" || typeof v === "number" && Number.isNaN(v);
|
|
738
|
+
}
|
|
739
|
+
var pad = (n) => String(n).padStart(2, "0");
|
|
740
|
+
function toDateKey2(v) {
|
|
741
|
+
if (isBlank(v)) return null;
|
|
742
|
+
if (typeof v === "string" && /^\d{4}-\d{2}-\d{2}$/.test(v)) return v;
|
|
743
|
+
const d = v instanceof Date ? v : new Date(v);
|
|
744
|
+
if (Number.isNaN(d.getTime())) return null;
|
|
745
|
+
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
|
|
746
|
+
}
|
|
747
|
+
function setKey(v) {
|
|
748
|
+
if (isBlank(v)) return "";
|
|
749
|
+
if (v instanceof Date) return toDateKey2(v) ?? "";
|
|
750
|
+
return String(v);
|
|
751
|
+
}
|
|
752
|
+
var all = () => true;
|
|
753
|
+
function buildPredicate(model) {
|
|
754
|
+
switch (model.type) {
|
|
755
|
+
case "combined": {
|
|
756
|
+
const parts = (Array.isArray(model.conditions) ? model.conditions : []).map(buildPredicate);
|
|
757
|
+
if (!parts.length) return all;
|
|
758
|
+
return model.operator === "or" ? (v) => parts.some((p) => p(v)) : (v) => parts.every((p) => p(v));
|
|
759
|
+
}
|
|
760
|
+
case "text": {
|
|
761
|
+
const q = (model.value ?? "").toLowerCase();
|
|
762
|
+
const s = (v) => isBlank(v) ? "" : String(v).toLowerCase();
|
|
763
|
+
switch (model.op) {
|
|
764
|
+
case "contains":
|
|
765
|
+
return (v) => s(v).includes(q);
|
|
766
|
+
case "equals":
|
|
767
|
+
return (v) => s(v) === q;
|
|
768
|
+
case "notEquals":
|
|
769
|
+
return (v) => s(v) !== q;
|
|
770
|
+
case "startsWith":
|
|
771
|
+
return (v) => s(v).startsWith(q);
|
|
772
|
+
case "endsWith":
|
|
773
|
+
return (v) => s(v).endsWith(q);
|
|
774
|
+
case "blank":
|
|
775
|
+
return isBlank;
|
|
776
|
+
case "notBlank":
|
|
777
|
+
return (v) => !isBlank(v);
|
|
778
|
+
}
|
|
779
|
+
return all;
|
|
780
|
+
}
|
|
781
|
+
case "number": {
|
|
782
|
+
const n = (v) => isBlank(v) ? NaN : Number(v);
|
|
783
|
+
if (model.op === "blank") return (v) => Number.isNaN(n(v));
|
|
784
|
+
if (model.op === "notBlank") return (v) => !Number.isNaN(n(v));
|
|
785
|
+
const a = model.value;
|
|
786
|
+
if (a == null || Number.isNaN(a)) return all;
|
|
787
|
+
switch (model.op) {
|
|
788
|
+
case "eq":
|
|
789
|
+
return (v) => n(v) === a;
|
|
790
|
+
case "ne":
|
|
791
|
+
return (v) => n(v) !== a;
|
|
792
|
+
case "lt":
|
|
793
|
+
return (v) => n(v) < a;
|
|
794
|
+
case "lte":
|
|
795
|
+
return (v) => n(v) <= a;
|
|
796
|
+
case "gt":
|
|
797
|
+
return (v) => n(v) > a;
|
|
798
|
+
case "gte":
|
|
799
|
+
return (v) => n(v) >= a;
|
|
800
|
+
case "between": {
|
|
801
|
+
const b = model.valueTo;
|
|
802
|
+
if (b == null || Number.isNaN(b)) return all;
|
|
803
|
+
const lo = Math.min(a, b), hi = Math.max(a, b);
|
|
804
|
+
return (v) => {
|
|
805
|
+
const x = n(v);
|
|
806
|
+
return x >= lo && x <= hi;
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
return all;
|
|
811
|
+
}
|
|
812
|
+
case "date": {
|
|
813
|
+
if (model.op === "blank") return (v) => toDateKey2(v) === null;
|
|
814
|
+
if (model.op === "notBlank") return (v) => toDateKey2(v) !== null;
|
|
815
|
+
const a = model.value;
|
|
816
|
+
if (!a) return all;
|
|
817
|
+
const k = (v) => toDateKey2(v);
|
|
818
|
+
switch (model.op) {
|
|
819
|
+
case "equals":
|
|
820
|
+
return (v) => k(v) === a;
|
|
821
|
+
case "before":
|
|
822
|
+
return (v) => {
|
|
823
|
+
const x = k(v);
|
|
824
|
+
return x !== null && x < a;
|
|
825
|
+
};
|
|
826
|
+
case "after":
|
|
827
|
+
return (v) => {
|
|
828
|
+
const x = k(v);
|
|
829
|
+
return x !== null && x > a;
|
|
830
|
+
};
|
|
831
|
+
case "between": {
|
|
832
|
+
const b = model.valueTo;
|
|
833
|
+
if (!b) return all;
|
|
834
|
+
const lo = a < b ? a : b, hi = a < b ? b : a;
|
|
835
|
+
return (v) => {
|
|
836
|
+
const x = k(v);
|
|
837
|
+
return x !== null && x >= lo && x <= hi;
|
|
838
|
+
};
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
return all;
|
|
842
|
+
}
|
|
843
|
+
case "set": {
|
|
844
|
+
const allowed = new Set(model.values.map(setKey));
|
|
845
|
+
return (v) => allowed.has(setKey(v));
|
|
676
846
|
}
|
|
677
|
-
if (/^(true|false)$/i.test(name)) return name.toLowerCase() === "true";
|
|
678
|
-
return this.resolve(name);
|
|
679
847
|
}
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
function
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
848
|
+
return all;
|
|
849
|
+
}
|
|
850
|
+
function quickTokens(q) {
|
|
851
|
+
const t = q.trim().toLowerCase();
|
|
852
|
+
return t ? t.split(/\s+/) : [];
|
|
853
|
+
}
|
|
854
|
+
function matchesQuick(tokens, text2) {
|
|
855
|
+
return tokens.every((t) => text2.includes(t));
|
|
856
|
+
}
|
|
857
|
+
function buildAdvanced(f, columnDef, valueOf) {
|
|
858
|
+
const parts = [];
|
|
859
|
+
for (const c of Array.isArray(f?.conditions) ? f.conditions : []) {
|
|
860
|
+
if (!c || typeof c !== "object") continue;
|
|
861
|
+
if ("conditions" in c) {
|
|
862
|
+
const sub = buildAdvanced(c, columnDef, valueOf);
|
|
863
|
+
if (sub) parts.push(sub);
|
|
864
|
+
continue;
|
|
865
|
+
}
|
|
866
|
+
const def = columnDef(c.colId);
|
|
867
|
+
if (!def || !c.model) continue;
|
|
868
|
+
const pred = buildPredicate(c.model);
|
|
869
|
+
parts.push((row) => pred(valueOf(def, row)));
|
|
700
870
|
}
|
|
871
|
+
if (!parts.length) return null;
|
|
872
|
+
return f.operator === "or" ? (row) => parts.some((p) => p(row)) : (row) => parts.every((p) => p(row));
|
|
701
873
|
}
|
|
702
|
-
var isFormula = (v) => typeof v === "string" && v.length > 1 && v.startsWith("=");
|
|
703
874
|
|
|
704
|
-
// src/
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
875
|
+
// src/i18n/format.ts
|
|
876
|
+
var cache = /* @__PURE__ */ new Map();
|
|
877
|
+
function parseNumberPattern(p) {
|
|
878
|
+
if (p === "integer") return { maximumFractionDigits: 0 };
|
|
879
|
+
if (p === "percent") return { style: "percent" };
|
|
880
|
+
if (p.startsWith("currency:")) return { style: "currency", currency: p.slice(9) };
|
|
881
|
+
if (!/^[#0,]*(\.[#0]+)?%?$/.test(p) || !/[#0]/.test(p)) return null;
|
|
882
|
+
const pct = p.endsWith("%");
|
|
883
|
+
const body = pct ? p.slice(0, -1) : p;
|
|
884
|
+
const frac = body.includes(".") ? body.split(".")[1] : "";
|
|
885
|
+
return {
|
|
886
|
+
useGrouping: body.includes(","),
|
|
887
|
+
minimumFractionDigits: (frac.match(/0/g) ?? []).length,
|
|
888
|
+
maximumFractionDigits: frac.length,
|
|
889
|
+
...pct ? { style: "percent" } : {}
|
|
890
|
+
};
|
|
891
|
+
}
|
|
892
|
+
var DATE_PRESETS = {
|
|
893
|
+
date: { year: "numeric", month: "2-digit", day: "2-digit" },
|
|
894
|
+
datetime: { year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit" },
|
|
895
|
+
time: { hour: "2-digit", minute: "2-digit" },
|
|
896
|
+
short: { dateStyle: "short" },
|
|
897
|
+
medium: { dateStyle: "medium" },
|
|
898
|
+
long: { dateStyle: "long" }
|
|
899
|
+
};
|
|
900
|
+
function toDate(v) {
|
|
901
|
+
if (isBlank(v)) return null;
|
|
902
|
+
if (v instanceof Date) return Number.isNaN(v.getTime()) ? null : v;
|
|
903
|
+
if (typeof v === "string") {
|
|
904
|
+
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(v);
|
|
905
|
+
if (m) return new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]));
|
|
906
|
+
}
|
|
907
|
+
if (typeof v !== "string" && typeof v !== "number") return null;
|
|
908
|
+
const d = new Date(v);
|
|
909
|
+
return Number.isNaN(d.getTime()) ? null : d;
|
|
910
|
+
}
|
|
911
|
+
function create(type, format, locale) {
|
|
912
|
+
switch (type) {
|
|
913
|
+
case "number": {
|
|
914
|
+
let opts = {};
|
|
915
|
+
if (typeof format === "string") {
|
|
916
|
+
const parsed = parseNumberPattern(format);
|
|
917
|
+
if (!parsed) warn(`unknown number format "${format}"`);
|
|
918
|
+
opts = parsed ?? {};
|
|
919
|
+
} else if (format) opts = format;
|
|
920
|
+
const nf = new Intl.NumberFormat(locale, opts);
|
|
921
|
+
return (v) => {
|
|
922
|
+
if (isBlank(v)) return "";
|
|
923
|
+
const n = typeof v === "number" ? v : Number(v);
|
|
924
|
+
return Number.isNaN(n) ? String(v) : nf.format(n);
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
case "date": {
|
|
928
|
+
if (locale.toLowerCase().startsWith("ko") && (format === void 0 || format === "date" || format === "datetime")) {
|
|
929
|
+
const pad3 = (n) => String(n).padStart(2, "0");
|
|
930
|
+
const withTime = format === "datetime";
|
|
931
|
+
return (v) => {
|
|
932
|
+
const d = toDate(v);
|
|
933
|
+
if (!d) return isBlank(v) ? "" : String(v);
|
|
934
|
+
const day = `${d.getFullYear()}-${pad3(d.getMonth() + 1)}-${pad3(d.getDate())}`;
|
|
935
|
+
return withTime ? `${day} ${pad3(d.getHours())}:${pad3(d.getMinutes())}` : day;
|
|
936
|
+
};
|
|
937
|
+
}
|
|
938
|
+
let opts = DATE_PRESETS.date;
|
|
939
|
+
if (typeof format === "string") {
|
|
940
|
+
if (!DATE_PRESETS[format]) warn(`unknown date format "${format}"`);
|
|
941
|
+
opts = DATE_PRESETS[format] ?? DATE_PRESETS.date;
|
|
942
|
+
} else if (format) opts = format;
|
|
943
|
+
const df = new Intl.DateTimeFormat(locale, opts);
|
|
944
|
+
return (v) => {
|
|
945
|
+
const d = toDate(v);
|
|
946
|
+
return d ? df.format(d) : isBlank(v) ? "" : String(v);
|
|
947
|
+
};
|
|
712
948
|
}
|
|
949
|
+
case "boolean":
|
|
950
|
+
return (v) => v === true ? "\u2713" : v === false ? "\u2717" : "";
|
|
951
|
+
default:
|
|
952
|
+
return (v) => isBlank(v) ? "" : v instanceof Date ? v.toISOString() : String(v);
|
|
713
953
|
}
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
954
|
+
}
|
|
955
|
+
function getFormatter(def, locale) {
|
|
956
|
+
if (def.sparkline) return (v) => Array.isArray(v) ? v.join(", ") : v == null ? "" : String(v);
|
|
957
|
+
const f = def.format;
|
|
958
|
+
const key = `${locale}|${def.type ?? "text"}|${typeof f === "string" ? f : JSON.stringify(f ?? null)}`;
|
|
959
|
+
let fmt = cache.get(key);
|
|
960
|
+
if (!fmt) {
|
|
961
|
+
fmt = create(def.type, f, locale);
|
|
962
|
+
cache.set(key, fmt);
|
|
963
|
+
}
|
|
964
|
+
return fmt;
|
|
717
965
|
}
|
|
718
966
|
|
|
719
967
|
// src/summary.ts
|
|
@@ -871,7 +1119,7 @@ function rowRangeByOffsets(offsets, scrollTop, height, count, overscan = 4) {
|
|
|
871
1119
|
}
|
|
872
1120
|
|
|
873
1121
|
// src/render/renderer.ts
|
|
874
|
-
var
|
|
1122
|
+
var REGIONS3 = ["left", "center", "right"];
|
|
875
1123
|
var ROW_OVERSCAN = 6;
|
|
876
1124
|
var MAX_COLSPAN_REACH = 8;
|
|
877
1125
|
var COL_OVERSCAN = 2;
|
|
@@ -888,6 +1136,8 @@ var Renderer = class {
|
|
|
888
1136
|
constructor(host, grid, opts) {
|
|
889
1137
|
this.grid = grid;
|
|
890
1138
|
this.opts = opts;
|
|
1139
|
+
/** Boxes under the header (filterRow option). */
|
|
1140
|
+
this.filterRow = null;
|
|
891
1141
|
this.activeCellId = "";
|
|
892
1142
|
this.idBase = `fg${Math.random().toString(36).slice(2, 8)}`;
|
|
893
1143
|
this.painted = { left: /* @__PURE__ */ new Map(), center: /* @__PURE__ */ new Map(), right: /* @__PURE__ */ new Map() };
|
|
@@ -935,6 +1185,8 @@ var Renderer = class {
|
|
|
935
1185
|
e.preventDefault();
|
|
936
1186
|
this.scroller.scrollTop += e.deltaY;
|
|
937
1187
|
};
|
|
1188
|
+
this.overlayKey = "";
|
|
1189
|
+
this.heightFn = null;
|
|
938
1190
|
this.onCardClick = (e) => {
|
|
939
1191
|
const target = e.target;
|
|
940
1192
|
const card = target.closest(".fg-card");
|
|
@@ -964,10 +1216,10 @@ var Renderer = class {
|
|
|
964
1216
|
});
|
|
965
1217
|
this.headerPanes = panes("fg-header-pane");
|
|
966
1218
|
this.headerInners = panes("fg-header-inner");
|
|
967
|
-
for (const r of
|
|
1219
|
+
for (const r of REGIONS3) this.headerInners[r].setAttribute("role", "row");
|
|
968
1220
|
this.bodyPanes = panes("fg-body-pane");
|
|
969
1221
|
this.canvases = panes("fg-canvas");
|
|
970
|
-
for (const r of
|
|
1222
|
+
for (const r of REGIONS3) {
|
|
971
1223
|
this.headerPanes[r].dataset.region = r;
|
|
972
1224
|
this.bodyPanes[r].dataset.region = r;
|
|
973
1225
|
this.headerPanes[r].append(this.headerInners[r]);
|
|
@@ -976,6 +1228,7 @@ var Renderer = class {
|
|
|
976
1228
|
this.bodyEl.append(this.bodyPanes[r]);
|
|
977
1229
|
}
|
|
978
1230
|
this.scroller = this.bodyPanes.center;
|
|
1231
|
+
if (grid.options.filterRow) this.filterRow = new FilterRow(grid, this.headerPanes);
|
|
979
1232
|
this.overlay = el("div", "fg-overlay", { "aria-live": "polite" });
|
|
980
1233
|
this.main = el("div", "fg-main");
|
|
981
1234
|
this.gridEl.append(this.headerEl, this.bodyEl);
|
|
@@ -1008,7 +1261,7 @@ var Renderer = class {
|
|
|
1008
1261
|
}
|
|
1009
1262
|
applyTheme() {
|
|
1010
1263
|
const rtl = this.grid.options.direction === "rtl";
|
|
1011
|
-
this.root.className = `fg-root fg-theme-${this.grid.store.state.theme}${rtl ? " fg-rtl" : ""}${this.cardMode ? " fg-card-mode" : ""}`;
|
|
1264
|
+
this.root.className = `fg-root fg-theme-${this.grid.store.state.theme}${rtl ? " fg-rtl" : ""}${this.cardMode ? " fg-card-mode" : ""}${this.grid.density !== "normal" ? ` fg-density-${this.grid.density}` : ""}`;
|
|
1012
1265
|
this.root.setAttribute("dir", rtl ? "rtl" : "ltr");
|
|
1013
1266
|
}
|
|
1014
1267
|
/** Right-to-left layout: the main area is mirrored, so screen deltas along x flip sign. */
|
|
@@ -1037,7 +1290,7 @@ var Renderer = class {
|
|
|
1037
1290
|
}
|
|
1038
1291
|
const count = rowModel.getRowCount();
|
|
1039
1292
|
const auto = !!this.grid.options.autoRowHeight && !rowModel.server;
|
|
1040
|
-
const lay = rowModel.layout(rh, this.detailHeight(), auto ? this.rowHeights : void 0, this.heightsVersion);
|
|
1293
|
+
const lay = rowModel.layout(rh, this.detailHeight(), auto ? this.rowHeights : void 0, this.heightsVersion, rowModel.server ? void 0 : this.heightOf);
|
|
1041
1294
|
this.offsets = lay.uniform ? null : lay.offsets;
|
|
1042
1295
|
this.firstDataId = columns.displayed().find((c) => !isSystemCol(c.id))?.id ?? "";
|
|
1043
1296
|
this.groupLabelId = columns.region("center").find((c) => !isSystemCol(c.id))?.id ?? this.firstDataId;
|
|
@@ -1046,8 +1299,7 @@ var Renderer = class {
|
|
|
1046
1299
|
this.renderStatusBar(rowModel.dataCount());
|
|
1047
1300
|
this.renderPager();
|
|
1048
1301
|
this.orderSections(null);
|
|
1049
|
-
this.
|
|
1050
|
-
this.overlay.textContent = count === 0 && rowModel.loading() ? store.state.locale.loading : store.state.locale.noRows;
|
|
1302
|
+
this.paintOverlay(count);
|
|
1051
1303
|
return;
|
|
1052
1304
|
}
|
|
1053
1305
|
const summary = this.grid.summary();
|
|
@@ -1057,9 +1309,9 @@ var Renderer = class {
|
|
|
1057
1309
|
this.topOffset = summary && !sumPinned && sumPos === "top" ? sumRows * rh : 0;
|
|
1058
1310
|
const bottomExtra = summary && !sumPinned && sumPos === "bottom" ? sumRows * rh : 0;
|
|
1059
1311
|
this.syncSummaryPane(summary && sumPinned ? sumPos : null, sumRows * rh);
|
|
1060
|
-
const headerHeight = columns.depth * hh;
|
|
1312
|
+
const headerHeight = columns.depth * hh + (this.filterRow?.height ?? 0);
|
|
1061
1313
|
this.headerEl.style.height = `${headerHeight}px`;
|
|
1062
|
-
for (const r of
|
|
1314
|
+
for (const r of REGIONS3) {
|
|
1063
1315
|
const w = columns.regionWidth(r);
|
|
1064
1316
|
if (r !== "center") {
|
|
1065
1317
|
this.headerPanes[r].style.width = this.bodyPanes[r].style.width = `${w}px`;
|
|
@@ -1068,17 +1320,22 @@ var Renderer = class {
|
|
|
1068
1320
|
this.canvases[r].style.width = `${w}px`;
|
|
1069
1321
|
this.canvases[r].style.height = `${this.rowsHeight(count) + this.topOffset + bottomExtra}px`;
|
|
1070
1322
|
}
|
|
1323
|
+
if (options.autoHeight && !this.useCards()) {
|
|
1324
|
+
this.root.classList.add("fg-auto-height");
|
|
1325
|
+
this.bodyEl.style.height = `${this.rowsHeight(count) + this.topOffset + bottomExtra + Math.max(0, this.scroller.offsetHeight - this.scroller.clientHeight)}px`;
|
|
1326
|
+
}
|
|
1071
1327
|
const hScrollbar = this.scroller.offsetHeight - this.scroller.clientHeight;
|
|
1072
1328
|
this.bodyPanes.left.style.marginBottom = this.bodyPanes.right.style.marginBottom = `${hScrollbar}px`;
|
|
1073
1329
|
const g = this.gridEl;
|
|
1074
1330
|
g.setAttribute("role", options.treeData || this.grid.getGroupBy().length ? "treegrid" : "grid");
|
|
1075
|
-
g.setAttribute("aria-rowcount", String(count + columns.depth));
|
|
1331
|
+
g.setAttribute("aria-rowcount", String(count + columns.depth + (this.filterRow ? 1 : 0)));
|
|
1076
1332
|
g.setAttribute("aria-colcount", String(columns.displayed().length));
|
|
1077
1333
|
if (options.rowSelection === "multiple") g.setAttribute("aria-multiselectable", "true");
|
|
1078
1334
|
if (this.headerDirty) {
|
|
1079
1335
|
this.headerDirty = false;
|
|
1080
1336
|
this.renderHeader();
|
|
1081
1337
|
}
|
|
1338
|
+
this.filterRow?.render();
|
|
1082
1339
|
this.renderRows(count);
|
|
1083
1340
|
this.updateActiveCell();
|
|
1084
1341
|
if (auto) this.measureRows();
|
|
@@ -1088,12 +1345,12 @@ var Renderer = class {
|
|
|
1088
1345
|
this.renderStatusBar(rowModel.dataCount());
|
|
1089
1346
|
this.renderPager();
|
|
1090
1347
|
this.orderSections(summary && sumPinned ? sumPos : null);
|
|
1091
|
-
this.
|
|
1092
|
-
this.overlay.textContent = count === 0 && rowModel.loading() ? store.state.locale.loading : store.state.locale.noRows;
|
|
1348
|
+
this.paintOverlay(count);
|
|
1093
1349
|
this.overlay.style.top = `${headerHeight}px`;
|
|
1094
1350
|
const { scrollTop, scrollLeft } = this.scroller;
|
|
1095
1351
|
this.bodyPanes.left.scrollTop = this.bodyPanes.right.scrollTop = scrollTop;
|
|
1096
1352
|
this.headerInners.center.style.transform = `translateX(${-scrollLeft}px)`;
|
|
1353
|
+
this.filterRow?.setTransform(scrollLeft);
|
|
1097
1354
|
if (this.summaryInners) this.summaryInners.center.style.transform = `translateX(${-scrollLeft}px)`;
|
|
1098
1355
|
for (const s of [this.pinnedTop, this.pinnedBottom]) if (s) s.inners.center.style.transform = `translateX(${-scrollLeft}px)`;
|
|
1099
1356
|
this.grid.fillHandle.update();
|
|
@@ -1120,6 +1377,37 @@ var Renderer = class {
|
|
|
1120
1377
|
ref = b;
|
|
1121
1378
|
}
|
|
1122
1379
|
}
|
|
1380
|
+
/** "No rows" / "Loading" over the body, or the noRowsOverlay / loadingOverlay given in the options. */
|
|
1381
|
+
paintOverlay(count) {
|
|
1382
|
+
const { grid } = this;
|
|
1383
|
+
const { store, rowModel, options } = grid;
|
|
1384
|
+
const kind = grid.forcedOverlay ?? (count > 0 ? null : rowModel.loading() ? "loading" : "noRows");
|
|
1385
|
+
this.overlay.hidden = !kind;
|
|
1386
|
+
if (!kind) {
|
|
1387
|
+
this.overlayKey = "";
|
|
1388
|
+
return;
|
|
1389
|
+
}
|
|
1390
|
+
const filtered = !rowModel.server && store.state.data.length > 0;
|
|
1391
|
+
const custom = kind === "loading" ? options.loadingOverlay : options.noRowsOverlay;
|
|
1392
|
+
const key = `${kind}|${filtered}|${store.state.locale.code}`;
|
|
1393
|
+
if (key === this.overlayKey) return;
|
|
1394
|
+
this.overlayKey = key;
|
|
1395
|
+
this.overlay.classList.toggle("fg-overlay-custom", typeof custom === "function");
|
|
1396
|
+
if (custom === void 0 || custom === null) {
|
|
1397
|
+
this.overlay.textContent = kind === "loading" ? store.state.locale.loading : store.state.locale.noRows;
|
|
1398
|
+
} else if (typeof custom === "string") {
|
|
1399
|
+
this.overlay.textContent = custom;
|
|
1400
|
+
} else {
|
|
1401
|
+
try {
|
|
1402
|
+
const out = custom({ filtered });
|
|
1403
|
+
if (typeof out === "string") this.overlay.textContent = out;
|
|
1404
|
+
else this.overlay.replaceChildren(out);
|
|
1405
|
+
} catch {
|
|
1406
|
+
warn(`${kind === "loading" ? "loadingOverlay" : "noRowsOverlay"} threw`);
|
|
1407
|
+
this.overlay.textContent = kind === "loading" ? store.state.locale.loading : store.state.locale.noRows;
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1123
1411
|
/** Point aria-activedescendant at the focused cell so screen readers announce it. */
|
|
1124
1412
|
updateActiveCell() {
|
|
1125
1413
|
const prev = this.activeCellId ? this.root.getRootNode().getElementById?.(this.activeCellId) ?? null : null;
|
|
@@ -1145,7 +1433,7 @@ var Renderer = class {
|
|
|
1145
1433
|
s.size(columns, rows.length * rh, this.scroller.offsetWidth - this.scroller.clientWidth);
|
|
1146
1434
|
const center = columns.region("center");
|
|
1147
1435
|
const cr = colRange(center.map((c) => c.left), center.map((c) => c.width), this.scroller.scrollLeft, this.scroller.clientWidth, COL_OVERSCAN);
|
|
1148
|
-
for (const r of
|
|
1436
|
+
for (const r of REGIONS3) {
|
|
1149
1437
|
const cols = r === "center" ? center.slice(cr.first, cr.last + 1) : columns.region(r);
|
|
1150
1438
|
s.inners[r].replaceChildren(...rows.map((data, k) => {
|
|
1151
1439
|
const rowEl = el("div", "fg-row fg-row-pinned", { role: "row" });
|
|
@@ -1195,7 +1483,7 @@ var Renderer = class {
|
|
|
1195
1483
|
renderHeader() {
|
|
1196
1484
|
const { columns, store } = this.grid;
|
|
1197
1485
|
const { sort, filters, locale } = store.state;
|
|
1198
|
-
for (const r of
|
|
1486
|
+
for (const r of REGIONS3) {
|
|
1199
1487
|
const inner = this.headerInners[r];
|
|
1200
1488
|
inner.style.width = `${columns.regionWidth(r)}px`;
|
|
1201
1489
|
inner.style.height = `${columns.depth * this.opts.headerHeight}px`;
|
|
@@ -1211,6 +1499,10 @@ var Renderer = class {
|
|
|
1211
1499
|
cell.style.width = `${c.width}px`;
|
|
1212
1500
|
cell.style.height = `${c.rowSpan * hh}px`;
|
|
1213
1501
|
if (isSystemCol(c.id)) {
|
|
1502
|
+
if (c.id === ROWNUM_COL_ID) {
|
|
1503
|
+
cell.classList.add("fg-header-rownum");
|
|
1504
|
+
cell.textContent = c.label;
|
|
1505
|
+
}
|
|
1214
1506
|
if (c.id === SELECT_COL_ID) {
|
|
1215
1507
|
cell.classList.add("fg-header-select");
|
|
1216
1508
|
cell.append(this.selectAllBox(locale));
|
|
@@ -1230,6 +1522,23 @@ var Renderer = class {
|
|
|
1230
1522
|
label.textContent = c.label;
|
|
1231
1523
|
}
|
|
1232
1524
|
}
|
|
1525
|
+
let toggleEl = null;
|
|
1526
|
+
if (c.isGroup && this.grid.columns.isCollapsible(c.node)) {
|
|
1527
|
+
const open = c.node.open;
|
|
1528
|
+
const t = el("button", open ? "fg-colgroup-toggle fg-expanded" : "fg-colgroup-toggle", {
|
|
1529
|
+
type: "button",
|
|
1530
|
+
tabindex: "-1",
|
|
1531
|
+
"aria-expanded": String(open),
|
|
1532
|
+
"aria-label": open ? locale.collapseAll : locale.expandAll
|
|
1533
|
+
});
|
|
1534
|
+
t.textContent = open ? "\u2212" : "+";
|
|
1535
|
+
t.addEventListener("click", (e) => {
|
|
1536
|
+
e.stopPropagation();
|
|
1537
|
+
this.grid.setColumnGroupOpened(c.id, !open);
|
|
1538
|
+
});
|
|
1539
|
+
cell.classList.add("fg-header-collapsible");
|
|
1540
|
+
toggleEl = t;
|
|
1541
|
+
}
|
|
1233
1542
|
if (hdef.headerClass) {
|
|
1234
1543
|
try {
|
|
1235
1544
|
const extra = typeof hdef.headerClass === "function" ? hdef.headerClass(hdef) : hdef.headerClass;
|
|
@@ -1239,6 +1548,7 @@ var Renderer = class {
|
|
|
1239
1548
|
}
|
|
1240
1549
|
}
|
|
1241
1550
|
cell.append(label);
|
|
1551
|
+
if (toggleEl) cell.append(toggleEl);
|
|
1242
1552
|
if (c.isGroup || !c.column) return cell;
|
|
1243
1553
|
const def = c.node.def;
|
|
1244
1554
|
if (def.wrapHeaderText ?? this.grid.options.wrapHeaderText) cell.classList.add("fg-header-wrap");
|
|
@@ -1283,12 +1593,12 @@ var Renderer = class {
|
|
|
1283
1593
|
const allBounds = this.grid.allRangeBounds();
|
|
1284
1594
|
const spans = this.grid.mergeSpans();
|
|
1285
1595
|
const top = Math.max(0, this.scroller.scrollTop - this.topOffset);
|
|
1286
|
-
const range = this.offsets ? rowRangeByOffsets(this.offsets, top, this.scroller.clientHeight, count, ROW_OVERSCAN) : rowRange(top, this.scroller.clientHeight, this.opts.rowHeight, count, ROW_OVERSCAN);
|
|
1596
|
+
const range = this.grid.options.autoHeight && !rowModel.server ? { first: 0, last: count - 1 } : this.offsets ? rowRangeByOffsets(this.offsets, top, this.scroller.clientHeight, count, ROW_OVERSCAN) : rowRange(top, this.scroller.clientHeight, this.opts.rowHeight, count, ROW_OVERSCAN);
|
|
1287
1597
|
this.lastRange = range;
|
|
1288
1598
|
const center = columns.region("center");
|
|
1289
1599
|
const cr = colRange(center.map((c) => c.left), center.map((c) => c.width), this.scroller.scrollLeft, this.scroller.clientWidth, COL_OVERSCAN);
|
|
1290
1600
|
if (center.some((c) => c.def.colSpan)) cr.first = Math.max(0, cr.first - MAX_COLSPAN_REACH);
|
|
1291
|
-
for (const r of
|
|
1601
|
+
for (const r of REGIONS3) {
|
|
1292
1602
|
const cols = r === "center" ? center.slice(cr.first, cr.last + 1) : columns.region(r);
|
|
1293
1603
|
const map = this.painted[r];
|
|
1294
1604
|
for (const [i, row] of map) {
|
|
@@ -1307,7 +1617,7 @@ var Renderer = class {
|
|
|
1307
1617
|
const inRange = hits.map((b) => `${b.c1}-${b.c2}`).join(",");
|
|
1308
1618
|
const merge2 = this.mergeState(spans, cols, i, range.first);
|
|
1309
1619
|
const pos = node.kind === "detail" ? `${this.scroller.scrollLeft}:${this.scroller.clientWidth}` : "";
|
|
1310
|
-
const key = `${node.id}|${this.version}|${colKey}|${isSel ? 1 : 0}|${focusCol}|${inRange}|${merge2}|${pos}`;
|
|
1620
|
+
const key = `${node.id}|${this.version}|${colKey}|${isSel ? 1 : 0}${node.kind === "group" ? `:${this.grid.selectionVersion}` : ""}|${focusCol}|${inRange}|${merge2}|${pos}`;
|
|
1311
1621
|
let row = map.get(i);
|
|
1312
1622
|
if (!row) {
|
|
1313
1623
|
const rowEl = this.pool[r].pop() ?? el("div", "fg-row", { role: "row" });
|
|
@@ -1342,7 +1652,7 @@ var Renderer = class {
|
|
|
1342
1652
|
}
|
|
1343
1653
|
paintRow(rowEl, node, i, cols, selected, focusCol, bounds, spans = /* @__PURE__ */ new Map(), first = 0) {
|
|
1344
1654
|
const rh = this.opts.rowHeight;
|
|
1345
|
-
rowEl.className = `fg-row${i % 2 ? " fg-row-odd" : ""}${selected ? " fg-row-selected" : ""}${this.grid.tracker.created.has(node.data) ? " fg-row-created" : ""}`;
|
|
1655
|
+
rowEl.className = `fg-row${i % 2 ? " fg-row-odd" : ""}${selected ? " fg-row-selected" : ""}${this.grid.tracker.created.has(node.data) ? " fg-row-created" : ""}${this.grid.editor.editingRow(node.id) ? " fg-row-editing" : ""}`;
|
|
1346
1656
|
this.placeRow(rowEl, i);
|
|
1347
1657
|
if (this.grid.options.rowSelection) rowEl.setAttribute("aria-selected", String(selected));
|
|
1348
1658
|
if (node.level !== void 0) rowEl.setAttribute("aria-level", String(node.level + 1));
|
|
@@ -1396,6 +1706,12 @@ var Renderer = class {
|
|
|
1396
1706
|
cell.dataset.col = col.id;
|
|
1397
1707
|
cell.setAttribute("aria-colindex", String(col.index + 1));
|
|
1398
1708
|
let cls = focused ? "fg-cell fg-cell-focused" : "fg-cell";
|
|
1709
|
+
if (col.id === ROWNUM_COL_ID) {
|
|
1710
|
+
cell.className = `${cls} fg-cell-rownum`;
|
|
1711
|
+
const n = i >= 0 && !node.kind ? this.grid.rowNumber(i) : null;
|
|
1712
|
+
cell.textContent = n === null ? "" : String(n);
|
|
1713
|
+
return;
|
|
1714
|
+
}
|
|
1399
1715
|
if (col.id === DRAG_COL_ID) {
|
|
1400
1716
|
cell.className = `${cls} fg-cell-drag`;
|
|
1401
1717
|
const handle = el("span", this.grid.canMoveRows() ? "fg-drag-handle" : "fg-drag-handle fg-drag-disabled", { "aria-hidden": "true" });
|
|
@@ -1431,7 +1747,15 @@ var Renderer = class {
|
|
|
1431
1747
|
if (def.wrapText) cls += " fg-cell-wrap";
|
|
1432
1748
|
if (typeof def.editable === "function" && i >= 0 && !cellEditable(def, node.data, i)) cls += " fg-cell-readonly";
|
|
1433
1749
|
if (i >= 0) cls += this.grid.finder.classFor(i, col.id);
|
|
1750
|
+
if (this.grid.isFlashing(node.id, col.id)) cls += " fg-cell-flash";
|
|
1434
1751
|
cell.className = cls;
|
|
1752
|
+
if (def.sparkline && !def.renderer) {
|
|
1753
|
+
const nums = Array.isArray(value) ? value.map(Number).filter(Number.isFinite) : [];
|
|
1754
|
+
if (nums.length) cell.replaceChildren(sparkline(nums, def.sparkline === true ? {} : def.sparkline, col.width, this.opts.rowHeight));
|
|
1755
|
+
else cell.replaceChildren();
|
|
1756
|
+
cell.setAttribute("aria-label", Array.isArray(value) ? value.join(", ") : "");
|
|
1757
|
+
return;
|
|
1758
|
+
}
|
|
1435
1759
|
if (!def.renderer) {
|
|
1436
1760
|
cell.textContent = formatted;
|
|
1437
1761
|
return;
|
|
@@ -1479,14 +1803,14 @@ var Renderer = class {
|
|
|
1479
1803
|
this.summaryEl = el("div", "fg-summary", { role: "rowgroup" });
|
|
1480
1804
|
this.summaryPanes = mk("fg-summary-pane");
|
|
1481
1805
|
this.summaryInners = mk("fg-summary-inner");
|
|
1482
|
-
for (const r of
|
|
1806
|
+
for (const r of REGIONS3) {
|
|
1483
1807
|
this.summaryPanes[r].append(this.summaryInners[r]);
|
|
1484
1808
|
this.summaryEl.append(this.summaryPanes[r]);
|
|
1485
1809
|
}
|
|
1486
1810
|
}
|
|
1487
1811
|
this.summaryEl.className = `fg-summary fg-summary-${position}`;
|
|
1488
1812
|
this.summaryEl.style.height = `${height}px`;
|
|
1489
|
-
for (const r of
|
|
1813
|
+
for (const r of REGIONS3) {
|
|
1490
1814
|
const w = this.grid.columns.regionWidth(r);
|
|
1491
1815
|
if (r !== "center") {
|
|
1492
1816
|
this.summaryPanes[r].style.width = `${w}px`;
|
|
@@ -1499,7 +1823,7 @@ var Renderer = class {
|
|
|
1499
1823
|
this.summaryPanes.center.style.marginRight = `${vScrollbar}px`;
|
|
1500
1824
|
}
|
|
1501
1825
|
renderSummary(count, defs, values, pinned, position) {
|
|
1502
|
-
for (const r of
|
|
1826
|
+
for (const r of REGIONS3) {
|
|
1503
1827
|
for (const old of this.canvasSummary[r]) old.remove();
|
|
1504
1828
|
this.canvasSummary[r] = [];
|
|
1505
1829
|
}
|
|
@@ -1509,7 +1833,7 @@ var Renderer = class {
|
|
|
1509
1833
|
const center = columns.region("center");
|
|
1510
1834
|
const cr = colRange(center.map((c) => c.left), center.map((c) => c.width), this.scroller.scrollLeft, this.scroller.clientWidth, COL_OVERSCAN);
|
|
1511
1835
|
const dataCols = columns.displayed().filter((c) => !isSystemCol(c.id));
|
|
1512
|
-
for (const r of
|
|
1836
|
+
for (const r of REGIONS3) {
|
|
1513
1837
|
const cols = r === "center" ? center.slice(cr.first, cr.last + 1) : columns.region(r);
|
|
1514
1838
|
const rowsEls = defs.map((def, k) => {
|
|
1515
1839
|
const labelCol = def.label != null ? dataCols.find((c) => !(c.id in (def.columns ?? {})))?.id ?? null : null;
|
|
@@ -1557,8 +1881,21 @@ var Renderer = class {
|
|
|
1557
1881
|
cell.textContent = s.prefix && text2 ? `${s.prefix} ${text2}` : text2;
|
|
1558
1882
|
return cell;
|
|
1559
1883
|
}
|
|
1884
|
+
/** getRowHeight, guarded: a throwing function means the default height. */
|
|
1885
|
+
get heightOf() {
|
|
1886
|
+
const fn = this.grid.options.getRowHeight;
|
|
1887
|
+
if (!fn) return void 0;
|
|
1888
|
+
return this.heightFn ?? (this.heightFn = (row) => {
|
|
1889
|
+
try {
|
|
1890
|
+
return fn(row) ?? void 0;
|
|
1891
|
+
} catch {
|
|
1892
|
+
return void 0;
|
|
1893
|
+
}
|
|
1894
|
+
});
|
|
1895
|
+
}
|
|
1560
1896
|
/** Forget measured row heights (data, widths or fonts changed); rows are measured again on screen. */
|
|
1561
1897
|
invalidateHeights() {
|
|
1898
|
+
if (this.heightOf) this.heightsVersion++;
|
|
1562
1899
|
if (!this.rowHeights.size) return;
|
|
1563
1900
|
this.rowHeights.clear();
|
|
1564
1901
|
this.heightsVersion++;
|
|
@@ -1567,7 +1904,7 @@ var Renderer = class {
|
|
|
1567
1904
|
measureRows() {
|
|
1568
1905
|
const base = this.opts.rowHeight;
|
|
1569
1906
|
const need = /* @__PURE__ */ new Map();
|
|
1570
|
-
for (const r of
|
|
1907
|
+
for (const r of REGIONS3) {
|
|
1571
1908
|
for (const [i, row] of this.painted[r]) {
|
|
1572
1909
|
const node = this.grid.rowModel.getRow(i);
|
|
1573
1910
|
if (!node || node.kind) continue;
|
|
@@ -1613,7 +1950,7 @@ var Renderer = class {
|
|
|
1613
1950
|
rowEl.style.transform = `translateY(${this.rowTop(i)}px)`;
|
|
1614
1951
|
rowEl.style.height = `${this.rowH(i)}px`;
|
|
1615
1952
|
rowEl.dataset.row = String(i);
|
|
1616
|
-
rowEl.setAttribute("aria-rowindex", String(i + 1 + this.grid.columns.depth));
|
|
1953
|
+
rowEl.setAttribute("aria-rowindex", String(i + 1 + this.grid.columns.depth + (this.filterRow ? 1 : 0)));
|
|
1617
1954
|
}
|
|
1618
1955
|
/** Tree indentation and chevron, detail toggle, group-leaf indentation in the first data column. */
|
|
1619
1956
|
decorate(cell, node) {
|
|
@@ -1673,6 +2010,19 @@ var Renderer = class {
|
|
|
1673
2010
|
cell.dataset.col = col.id;
|
|
1674
2011
|
cell.setAttribute("aria-colindex", String(col.index + 1));
|
|
1675
2012
|
const focused = focusCol === col.id ? " fg-cell-focused" : "";
|
|
2013
|
+
if (col.id === SELECT_COL_ID) {
|
|
2014
|
+
cell.className = `fg-cell fg-cell-select${focused}`;
|
|
2015
|
+
if (grid.options.rowSelection !== "multiple") {
|
|
2016
|
+
cell.replaceChildren();
|
|
2017
|
+
return;
|
|
2018
|
+
}
|
|
2019
|
+
const state = grid.groupSelection(node);
|
|
2020
|
+
const box = el("input", "fg-checkbox", { type: "checkbox", tabindex: "-1", "aria-label": loc.selectAllRows });
|
|
2021
|
+
box.checked = state === "all";
|
|
2022
|
+
box.indeterminate = state === "some";
|
|
2023
|
+
cell.replaceChildren(box);
|
|
2024
|
+
return;
|
|
2025
|
+
}
|
|
1676
2026
|
if (col.id === this.groupLabelId) {
|
|
1677
2027
|
cell.className = `fg-cell fg-group-cell${focused}`;
|
|
1678
2028
|
cell.style.setProperty("--fg-level", String(node.level ?? 0));
|
|
@@ -1687,7 +2037,8 @@ var Renderer = class {
|
|
|
1687
2037
|
const label = el("span", "fg-group-label");
|
|
1688
2038
|
const value2 = isBlank(g.value) ? loc.blanks : def2 ? getFormatter(def2, loc.code)(g.value) : String(g.value);
|
|
1689
2039
|
const nf = new Intl.NumberFormat(loc.code);
|
|
1690
|
-
|
|
2040
|
+
const count = g.count ?? (g.aggregates ? null : g.rows.length);
|
|
2041
|
+
label.textContent = `${def2?.header ?? def2?.field ?? g.colId}: ${value2}${count == null ? "" : ` (${nf.format(count)})`}`;
|
|
1691
2042
|
cell.replaceChildren(toggle, label);
|
|
1692
2043
|
return;
|
|
1693
2044
|
}
|
|
@@ -1936,6 +2287,7 @@ var Renderer = class {
|
|
|
1936
2287
|
return Math.max(1, Math.floor(this.scroller.clientHeight / this.opts.rowHeight));
|
|
1937
2288
|
}
|
|
1938
2289
|
destroy() {
|
|
2290
|
+
this.filterRow?.destroy();
|
|
1939
2291
|
if (this.frame) cancelAnimationFrame(this.frame);
|
|
1940
2292
|
this.scroller.removeEventListener("scroll", this.onScroll);
|
|
1941
2293
|
this.root.remove();
|
|
@@ -1951,14 +2303,14 @@ var Section = class {
|
|
|
1951
2303
|
this.el = el("div", cls, { role: "rowgroup" });
|
|
1952
2304
|
this.panes = mk("fg-section-pane");
|
|
1953
2305
|
this.inners = mk("fg-section-inner");
|
|
1954
|
-
for (const r of
|
|
2306
|
+
for (const r of REGIONS3) {
|
|
1955
2307
|
this.panes[r].append(this.inners[r]);
|
|
1956
2308
|
this.el.append(this.panes[r]);
|
|
1957
2309
|
}
|
|
1958
2310
|
}
|
|
1959
2311
|
size(columns, height, vScrollbar) {
|
|
1960
2312
|
this.el.style.height = `${height}px`;
|
|
1961
|
-
for (const r of
|
|
2313
|
+
for (const r of REGIONS3) {
|
|
1962
2314
|
const w = columns.regionWidth(r);
|
|
1963
2315
|
if (r !== "center") {
|
|
1964
2316
|
this.panes[r].style.width = `${w}px`;
|
|
@@ -1970,6 +2322,44 @@ var Section = class {
|
|
|
1970
2322
|
this.panes.center.style.marginRight = `${vScrollbar}px`;
|
|
1971
2323
|
}
|
|
1972
2324
|
};
|
|
2325
|
+
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
2326
|
+
function sparkline(nums, opt, width, rowHeight) {
|
|
2327
|
+
const w = Math.max(20, width - 16), h = Math.max(10, rowHeight - 14);
|
|
2328
|
+
const svgEl = document.createElementNS(SVG_NS, "svg");
|
|
2329
|
+
svgEl.setAttribute("class", "fg-sparkline");
|
|
2330
|
+
svgEl.setAttribute("width", String(w));
|
|
2331
|
+
svgEl.setAttribute("height", String(h));
|
|
2332
|
+
svgEl.setAttribute("viewBox", `0 0 ${w} ${h}`);
|
|
2333
|
+
svgEl.setAttribute("aria-hidden", "true");
|
|
2334
|
+
const color = opt.color ?? "var(--fg-accent)";
|
|
2335
|
+
const bars = opt.type === "bar";
|
|
2336
|
+
let min = bars ? Math.min(0, ...nums) : Math.min(...nums);
|
|
2337
|
+
let max = bars ? Math.max(0, ...nums) : Math.max(...nums);
|
|
2338
|
+
if (min === max) {
|
|
2339
|
+
min -= 1;
|
|
2340
|
+
max += 1;
|
|
2341
|
+
}
|
|
2342
|
+
const span = max - min;
|
|
2343
|
+
const y = (v) => h - 1 - (v - min) / span * (h - 2);
|
|
2344
|
+
const mk = (tag, attrs) => {
|
|
2345
|
+
const n = document.createElementNS(SVG_NS, tag);
|
|
2346
|
+
for (const [k, v] of Object.entries(attrs)) n.setAttribute(k, String(v));
|
|
2347
|
+
svgEl.append(n);
|
|
2348
|
+
return n;
|
|
2349
|
+
};
|
|
2350
|
+
if (opt.type === "bar") {
|
|
2351
|
+
const bw = w / nums.length;
|
|
2352
|
+
nums.forEach((v, i) => mk("rect", { x: i * bw + 1, y: Math.min(y(v), y(0)), width: Math.max(1, bw - 2), height: Math.max(1, Math.abs(y(v) - y(0))), fill: v < 0 ? "#ef4444" : color }));
|
|
2353
|
+
return svgEl;
|
|
2354
|
+
}
|
|
2355
|
+
const step = nums.length > 1 ? (w - 2) / (nums.length - 1) : 0;
|
|
2356
|
+
const pts = nums.map((v, i) => `${1 + i * step},${y(v)}`).join(" ");
|
|
2357
|
+
if (opt.type === "area") mk("polygon", { points: `1,${h} ${pts} ${1 + (nums.length - 1) * step},${h}`, fill: color, "fill-opacity": 0.2 });
|
|
2358
|
+
mk("polyline", { points: pts, fill: "none", stroke: color, "stroke-width": 1.5, "stroke-linejoin": "round" });
|
|
2359
|
+
const last = nums.length - 1;
|
|
2360
|
+
mk("circle", { cx: 1 + last * step, cy: y(nums[last]), r: 2, fill: color });
|
|
2361
|
+
return svgEl;
|
|
2362
|
+
}
|
|
1973
2363
|
|
|
1974
2364
|
// src/interactions/groupPanel.ts
|
|
1975
2365
|
var DRAG_THRESHOLD = 4;
|
|
@@ -2119,11 +2509,16 @@ var en = {
|
|
|
2119
2509
|
hideColumn: "Hide column",
|
|
2120
2510
|
close: "Close",
|
|
2121
2511
|
copy: "Copy",
|
|
2512
|
+
cut: "Cut",
|
|
2122
2513
|
copyWithHeaders: "Copy with headers",
|
|
2123
2514
|
paste: "Paste",
|
|
2124
2515
|
pasteHint: "Clipboard access was blocked. Press Ctrl+V to paste.",
|
|
2125
2516
|
required: "This field is required",
|
|
2126
2517
|
invalidValue: "Invalid value",
|
|
2518
|
+
and: "And",
|
|
2519
|
+
or: "Or",
|
|
2520
|
+
advancedFilter: "Advanced filter",
|
|
2521
|
+
addCondition: "Add condition",
|
|
2127
2522
|
duplicate: "This value is already used",
|
|
2128
2523
|
undo: "Undo",
|
|
2129
2524
|
redo: "Redo",
|
|
@@ -2171,6 +2566,9 @@ var en = {
|
|
|
2171
2566
|
chartLine: "Line chart",
|
|
2172
2567
|
chartArea: "Area chart",
|
|
2173
2568
|
chartPie: "Pie chart",
|
|
2569
|
+
chartDonut: "Donut chart",
|
|
2570
|
+
chartCombo: "Bar and line chart",
|
|
2571
|
+
chartScatter: "Scatter chart",
|
|
2174
2572
|
freeBadge: "Free version",
|
|
2175
2573
|
ops: {
|
|
2176
2574
|
contains: "Contains",
|
|
@@ -2202,7 +2600,11 @@ function matchCode(tag) {
|
|
|
2202
2600
|
const lower = tag.toLowerCase();
|
|
2203
2601
|
for (const k of registry.keys()) if (k.toLowerCase() === lower) return k;
|
|
2204
2602
|
const lang = lower.split("-")[0];
|
|
2205
|
-
if (lang === "zh"
|
|
2603
|
+
if (lang === "zh") {
|
|
2604
|
+
const traditional = /-(tw|hk|mo|hant)(-|$)/.test(lower);
|
|
2605
|
+
if (traditional && registry.has("zh-TW")) return "zh-TW";
|
|
2606
|
+
if (registry.has("zh-CN")) return "zh-CN";
|
|
2607
|
+
}
|
|
2206
2608
|
for (const k of registry.keys()) if (k.toLowerCase().split("-")[0] === lang) return k;
|
|
2207
2609
|
return void 0;
|
|
2208
2610
|
}
|
|
@@ -2344,6 +2746,9 @@ function attachBody(grid) {
|
|
|
2344
2746
|
const before = grid.store.state.focus;
|
|
2345
2747
|
const hit = renderer.cellAt(e.target);
|
|
2346
2748
|
if (hit && grid.rowModel.getRow(hit.rowIndex)?.kind) {
|
|
2749
|
+
if (hit.colId === SELECT_COL_ID && grid.rowModel.getRow(hit.rowIndex).kind === "group") {
|
|
2750
|
+
grid.toggleGroupSelection(grid.rowModel.getRow(hit.rowIndex));
|
|
2751
|
+
}
|
|
2347
2752
|
renderer.gridEl.focus({ preventScroll: true });
|
|
2348
2753
|
grid.setFocus(hit.rowIndex, hit.colId);
|
|
2349
2754
|
grid.setRange(null);
|
|
@@ -2352,6 +2757,14 @@ function attachBody(grid) {
|
|
|
2352
2757
|
const ev = toEvent(e);
|
|
2353
2758
|
if (!ev) return;
|
|
2354
2759
|
renderer.gridEl.focus({ preventScroll: true });
|
|
2760
|
+
if (ev.colId === ROWNUM_COL_ID) {
|
|
2761
|
+
const data = grid.columns.displayed().filter((c) => !isSystemCol(c.id));
|
|
2762
|
+
if (data.length) {
|
|
2763
|
+
grid.setFocus(ev.rowIndex, data[0].id);
|
|
2764
|
+
grid.setRange({ start: { rowIndex: ev.rowIndex, colId: data[0].id }, end: { rowIndex: ev.rowIndex, colId: data[data.length - 1].id } });
|
|
2765
|
+
}
|
|
2766
|
+
return;
|
|
2767
|
+
}
|
|
2355
2768
|
const dragged = suppressClick;
|
|
2356
2769
|
suppressClick = false;
|
|
2357
2770
|
const prev = grid.store.state.focus;
|
|
@@ -2418,6 +2831,9 @@ function attachBody(grid) {
|
|
|
2418
2831
|
case "copy":
|
|
2419
2832
|
grid.copyToClipboard(false);
|
|
2420
2833
|
return true;
|
|
2834
|
+
case "cut":
|
|
2835
|
+
grid.cutToClipboard();
|
|
2836
|
+
return true;
|
|
2421
2837
|
case "selectAll":
|
|
2422
2838
|
grid.selectAllCells();
|
|
2423
2839
|
return true;
|
|
@@ -2653,6 +3069,14 @@ function attachBody(grid) {
|
|
|
2653
3069
|
e.clipboardData.setData("text/plain", text2);
|
|
2654
3070
|
e.preventDefault();
|
|
2655
3071
|
};
|
|
3072
|
+
const onCut = (e) => {
|
|
3073
|
+
if (fromField(e) || nested(e)) return;
|
|
3074
|
+
const text2 = grid.selectionTSV(false);
|
|
3075
|
+
if (!text2 || !e.clipboardData) return;
|
|
3076
|
+
e.clipboardData.setData("text/plain", text2);
|
|
3077
|
+
e.preventDefault();
|
|
3078
|
+
grid.clearCells();
|
|
3079
|
+
};
|
|
2656
3080
|
const onPaste = (e) => {
|
|
2657
3081
|
if (fromField(e) || nested(e)) return;
|
|
2658
3082
|
const text2 = e.clipboardData?.getData("text/plain");
|
|
@@ -2666,6 +3090,7 @@ function attachBody(grid) {
|
|
|
2666
3090
|
root.addEventListener("keydown", onKeyDown);
|
|
2667
3091
|
root.addEventListener("copy", onCopy);
|
|
2668
3092
|
root.addEventListener("paste", onPaste);
|
|
3093
|
+
root.addEventListener("cut", onCut);
|
|
2669
3094
|
return () => {
|
|
2670
3095
|
body.removeEventListener("click", onClick);
|
|
2671
3096
|
body.removeEventListener("dblclick", onDblClick);
|
|
@@ -2673,6 +3098,7 @@ function attachBody(grid) {
|
|
|
2673
3098
|
root.removeEventListener("keydown", onKeyDown);
|
|
2674
3099
|
root.removeEventListener("copy", onCopy);
|
|
2675
3100
|
root.removeEventListener("paste", onPaste);
|
|
3101
|
+
root.removeEventListener("cut", onCut);
|
|
2676
3102
|
};
|
|
2677
3103
|
}
|
|
2678
3104
|
|
|
@@ -3147,6 +3573,8 @@ var ContextMenu = class {
|
|
|
3147
3573
|
const items = [
|
|
3148
3574
|
{ label: loc.copy, shortcut: shortcut("C"), action: () => grid.copyToClipboard(false) },
|
|
3149
3575
|
{ label: loc.copyWithHeaders, action: () => grid.copyToClipboard(true) },
|
|
3576
|
+
// after the copy items, so defaultItems indexes from earlier versions still point at the same entries
|
|
3577
|
+
{ label: loc.cut, shortcut: shortcut("X"), action: () => grid.cutToClipboard() },
|
|
3150
3578
|
...grid.store.state.selected.size ? [{ label: loc.copyRows, action: () => grid.copyToClipboard(false, true) }] : [],
|
|
3151
3579
|
{ label: loc.paste, shortcut: shortcut("V"), action: () => this.pasteFromClipboard() }
|
|
3152
3580
|
];
|
|
@@ -3186,6 +3614,7 @@ var ContextMenu = class {
|
|
|
3186
3614
|
{ label: loc.exportCsv, action: () => grid.api.exportCsv() },
|
|
3187
3615
|
{ label: loc.exportExcel, action: () => grid.api.exportXlsx() }
|
|
3188
3616
|
);
|
|
3617
|
+
if (grid.options.advancedFilter !== false && !grid.options.pivot) items.push("separator", { label: `${loc.advancedFilter}\u2026`, action: () => grid.filterBuilder.open() });
|
|
3189
3618
|
return items;
|
|
3190
3619
|
}
|
|
3191
3620
|
pasteFromClipboard() {
|
|
@@ -3532,7 +3961,7 @@ function fillSeries(source, count) {
|
|
|
3532
3961
|
const asDate = source[source.length - 1] instanceof Date;
|
|
3533
3962
|
return Array.from({ length: count }, (_, i) => {
|
|
3534
3963
|
const d = new Date(lastDate.getFullYear(), lastDate.getMonth(), lastDate.getDate() + step * (i + 1));
|
|
3535
|
-
return asDate ? d :
|
|
3964
|
+
return asDate ? d : toDateKey2(d);
|
|
3536
3965
|
});
|
|
3537
3966
|
}
|
|
3538
3967
|
return repeat();
|
|
@@ -3616,13 +4045,14 @@ function parseDelimited(text2, sep) {
|
|
|
3616
4045
|
}
|
|
3617
4046
|
function copyValue(def, value, locale) {
|
|
3618
4047
|
if (isBlank(value)) return "";
|
|
4048
|
+
if (Array.isArray(value)) return value.join(", ");
|
|
3619
4049
|
switch (def.type) {
|
|
3620
4050
|
case "number": {
|
|
3621
4051
|
const n = typeof value === "number" ? value : Number(value);
|
|
3622
4052
|
return Number.isNaN(n) ? String(value) : String(n);
|
|
3623
4053
|
}
|
|
3624
4054
|
case "date":
|
|
3625
|
-
return
|
|
4055
|
+
return toDateKey2(value) ?? String(value);
|
|
3626
4056
|
case "boolean":
|
|
3627
4057
|
return value === true ? "TRUE" : value === false ? "FALSE" : String(value);
|
|
3628
4058
|
default:
|
|
@@ -3661,7 +4091,7 @@ function parseDate(text2, previous) {
|
|
|
3661
4091
|
else if (m = /^(\d{1,2})\/(\d{1,2})\/(\d{4})$/.exec(s)) date = validDate(+m[3], +m[1], +m[2]);
|
|
3662
4092
|
else if (/^\d{4}-\d{2}-\d{2}T/.test(s)) date = toDate(s);
|
|
3663
4093
|
if (!date) return { ok: false };
|
|
3664
|
-
return { ok: true, value: previous instanceof Date ? date :
|
|
4094
|
+
return { ok: true, value: previous instanceof Date ? date : toDateKey2(date) };
|
|
3665
4095
|
}
|
|
3666
4096
|
function parseValue(def, text2, previous) {
|
|
3667
4097
|
switch (def.type) {
|
|
@@ -3698,6 +4128,49 @@ var CellEditor = class {
|
|
|
3698
4128
|
constructor(grid) {
|
|
3699
4129
|
this.grid = grid;
|
|
3700
4130
|
this.cur = null;
|
|
4131
|
+
/** editType 'row': the row being edited and its values before the first change. */
|
|
4132
|
+
this.session = null;
|
|
4133
|
+
grid.events.on("focusChange", ({ rowIndex }) => {
|
|
4134
|
+
if (this.session && !this.cur && this.grid.rowModel.getRow(rowIndex)?.data !== this.session.row) this.endRow(true);
|
|
4135
|
+
});
|
|
4136
|
+
}
|
|
4137
|
+
get rowMode() {
|
|
4138
|
+
return this.grid.options.editType === "row";
|
|
4139
|
+
}
|
|
4140
|
+
/** Is this row id being edited as a whole row? */
|
|
4141
|
+
editingRow(id) {
|
|
4142
|
+
return this.session?.id === id;
|
|
4143
|
+
}
|
|
4144
|
+
beginRow(row, rowIndex) {
|
|
4145
|
+
if (this.session?.row === row) return;
|
|
4146
|
+
if (this.session) this.endRow(true);
|
|
4147
|
+
this.session = { row, id: this.grid.rowModel.idOf(row), before: { ...row } };
|
|
4148
|
+
this.grid.renderer.invalidate({ rows: true });
|
|
4149
|
+
this.grid.events.emit("rowEditStart", { row, rowIndex });
|
|
4150
|
+
}
|
|
4151
|
+
/** Finish the row session: keep the values, or put the row back as it was. */
|
|
4152
|
+
endRow(commit) {
|
|
4153
|
+
const s = this.session;
|
|
4154
|
+
if (!s) return;
|
|
4155
|
+
if (this.cur) {
|
|
4156
|
+
if (commit) this.commit(null);
|
|
4157
|
+
else this.close(false);
|
|
4158
|
+
}
|
|
4159
|
+
this.session = null;
|
|
4160
|
+
const { grid } = this;
|
|
4161
|
+
const rowIndex = grid.rowModel.indexOfId(s.id);
|
|
4162
|
+
const cols = grid.columns.displayed().filter((c) => c.def.field && !isSystemCol(c.id));
|
|
4163
|
+
if (!commit) {
|
|
4164
|
+
const changes2 = [];
|
|
4165
|
+
for (const c of cols) {
|
|
4166
|
+
const f = c.def.field;
|
|
4167
|
+
if (!Object.is(s.row[f], s.before[f])) grid.writeCell(changes2, s.row, f, c.id, rowIndex, s.before[f]);
|
|
4168
|
+
}
|
|
4169
|
+
grid.endCellTx(changes2);
|
|
4170
|
+
}
|
|
4171
|
+
const changes = cols.filter((c) => !Object.is(s.row[c.def.field], s.before[c.def.field])).map((c) => ({ colId: c.id, oldValue: s.before[c.def.field], newValue: s.row[c.def.field] }));
|
|
4172
|
+
grid.renderer.invalidate({ rows: true });
|
|
4173
|
+
grid.events.emit("rowEditStop", { row: s.row, rowIndex, committed: commit, changes });
|
|
3701
4174
|
}
|
|
3702
4175
|
get active() {
|
|
3703
4176
|
return this.cur && { rowIndex: this.cur.rowIndex, colId: this.cur.colId };
|
|
@@ -3719,6 +4192,7 @@ var CellEditor = class {
|
|
|
3719
4192
|
const def = col.def;
|
|
3720
4193
|
const row = grid.rowModel.getRow(rowIndex).data;
|
|
3721
4194
|
const value = row[def.field];
|
|
4195
|
+
if (this.rowMode) this.beginRow(row, rowIndex);
|
|
3722
4196
|
grid.setFocus(rowIndex, colId);
|
|
3723
4197
|
renderer.ensureVisible(rowIndex, colId);
|
|
3724
4198
|
renderer.render();
|
|
@@ -3774,7 +4248,7 @@ var CellEditor = class {
|
|
|
3774
4248
|
input.type = def.type === "date" && initialText === void 0 ? "date" : "text";
|
|
3775
4249
|
if (def.type === "number") input.inputMode = "decimal";
|
|
3776
4250
|
}
|
|
3777
|
-
const start = def.type === "date" ?
|
|
4251
|
+
const start = def.type === "date" ? toDateKey2(value) ?? "" : isBlank(value) ? "" : String(value);
|
|
3778
4252
|
input.value = initialText ?? start;
|
|
3779
4253
|
control = input;
|
|
3780
4254
|
read = () => ({ text: input.value });
|
|
@@ -3848,11 +4322,15 @@ var CellEditor = class {
|
|
|
3848
4322
|
if (error !== null) return this.fail(value, error);
|
|
3849
4323
|
this.close(true);
|
|
3850
4324
|
grid.applyEdit(cur.rowIndex, cur.colId, value);
|
|
4325
|
+
if (this.session && (move === "down" || move === "up")) {
|
|
4326
|
+
this.endRow(true);
|
|
4327
|
+
}
|
|
3851
4328
|
this.move(cur.rowIndex, cur.colId, move);
|
|
3852
4329
|
return true;
|
|
3853
4330
|
}
|
|
3854
4331
|
cancel() {
|
|
3855
4332
|
this.close(false);
|
|
4333
|
+
if (this.session) this.endRow(false);
|
|
3856
4334
|
}
|
|
3857
4335
|
fail(value, message) {
|
|
3858
4336
|
const cur = this.cur;
|
|
@@ -3886,11 +4364,11 @@ var CellEditor = class {
|
|
|
3886
4364
|
let i = cols.findIndex((c) => c.id === colId) + step;
|
|
3887
4365
|
if (i >= cols.length) {
|
|
3888
4366
|
i = 0;
|
|
3889
|
-
r++;
|
|
4367
|
+
if (!this.session) r++;
|
|
3890
4368
|
}
|
|
3891
4369
|
if (i < 0) {
|
|
3892
4370
|
i = cols.length - 1;
|
|
3893
|
-
r--;
|
|
4371
|
+
if (!this.session) r--;
|
|
3894
4372
|
}
|
|
3895
4373
|
if (r < 0 || r >= count) return;
|
|
3896
4374
|
this.start(r, cols[i].id);
|
|
@@ -4490,6 +4968,79 @@ function exportRows(grid, mode) {
|
|
|
4490
4968
|
return grid.rowModel.dataRows().map((n) => n.data);
|
|
4491
4969
|
}
|
|
4492
4970
|
var header = (def, id) => def.header ?? def.field ?? id;
|
|
4971
|
+
var escHtml = (s) => String(s ?? "").replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]);
|
|
4972
|
+
function buildPrintHtml(grid, opts = {}) {
|
|
4973
|
+
const cols = exportColumns(grid, opts.columns);
|
|
4974
|
+
const code = grid.store.state.locale.code;
|
|
4975
|
+
const fmts = cols.map((c) => getFormatter(c.def, code));
|
|
4976
|
+
const chains = cols.map((c) => {
|
|
4977
|
+
const chain = [];
|
|
4978
|
+
for (let n = c.node; n; n = n.parent) chain.unshift(n);
|
|
4979
|
+
return chain;
|
|
4980
|
+
});
|
|
4981
|
+
const depth = Math.max(1, ...chains.map((ch) => ch.length));
|
|
4982
|
+
const head = [];
|
|
4983
|
+
for (let level = 0; level < depth; level++) {
|
|
4984
|
+
let tr = "";
|
|
4985
|
+
for (let ci = 0; ci < cols.length; ci++) {
|
|
4986
|
+
const chain = chains[ci];
|
|
4987
|
+
const leaf = chain.length - 1;
|
|
4988
|
+
if (level < leaf) {
|
|
4989
|
+
const g = chain[level];
|
|
4990
|
+
if (ci > 0 && chains[ci - 1][level] === g) continue;
|
|
4991
|
+
let span = 1;
|
|
4992
|
+
while (ci + span < cols.length && chains[ci + span][level] === g) span++;
|
|
4993
|
+
tr += `<th colspan="${span}">${escHtml(header(g.def, g.id))}</th>`;
|
|
4994
|
+
} else if (level === leaf) {
|
|
4995
|
+
tr += `<th colspan="1" rowspan="${depth - level}">${escHtml(header(cols[ci].def, cols[ci].id))}</th>`;
|
|
4996
|
+
}
|
|
4997
|
+
}
|
|
4998
|
+
head.push(`<tr>${tr}</tr>`);
|
|
4999
|
+
}
|
|
5000
|
+
const align = (def) => def.align ?? (def.type === "number" ? "right" : def.type === "boolean" ? "center" : "left");
|
|
5001
|
+
const body = exportRows(grid, opts.rows).map((r) => `<tr>${cols.map((c, i) => {
|
|
5002
|
+
const v = getCellValue(c.def, r);
|
|
5003
|
+
return `<td style="text-align:${align(c.def)}">${escHtml(isBlank(v) ? "" : fmts[i](v))}</td>`;
|
|
5004
|
+
}).join("")}</tr>`).join("");
|
|
5005
|
+
const title = escHtml(opts.title ?? document.title ?? "");
|
|
5006
|
+
return `<!doctype html><html lang="${escHtml(code)}"><head><meta charset="utf-8"><title>${title}</title><style>
|
|
5007
|
+
@page { size: auto; margin: 12mm; }
|
|
5008
|
+
body { font: 11px/1.4 system-ui, 'Pretendard', 'Malgun Gothic', sans-serif; color: #111; margin: 0; }
|
|
5009
|
+
h1 { font-size: 15px; margin: 0 0 8px; }
|
|
5010
|
+
table { border-collapse: collapse; width: 100%; }
|
|
5011
|
+
th, td { border: 1px solid #999; padding: 3px 6px; }
|
|
5012
|
+
th { background: #eee; font-weight: 600; }
|
|
5013
|
+
thead { display: table-header-group; }
|
|
5014
|
+
tr { page-break-inside: avoid; }
|
|
5015
|
+
</style></head><body>${opts.title ? `<h1>${title}</h1>` : ""}<table><thead>${opts.headers === false ? "" : head.join("")}</thead><tbody>${body}</tbody></table></body></html>`;
|
|
5016
|
+
}
|
|
5017
|
+
function printHtml(html) {
|
|
5018
|
+
if (typeof document === "undefined") return;
|
|
5019
|
+
const frame = document.createElement("iframe");
|
|
5020
|
+
frame.style.cssText = "position:fixed;right:0;bottom:0;width:0;height:0;border:0";
|
|
5021
|
+
document.body.append(frame);
|
|
5022
|
+
const doc = frame.contentDocument;
|
|
5023
|
+
if (!doc || !frame.contentWindow) {
|
|
5024
|
+
frame.remove();
|
|
5025
|
+
warn("printing is not supported in this environment");
|
|
5026
|
+
return;
|
|
5027
|
+
}
|
|
5028
|
+
doc.open();
|
|
5029
|
+
doc.write(html);
|
|
5030
|
+
doc.close();
|
|
5031
|
+
const win = frame.contentWindow;
|
|
5032
|
+
const done = () => setTimeout(() => frame.remove(), 1e3);
|
|
5033
|
+
win.addEventListener("afterprint", done);
|
|
5034
|
+
setTimeout(() => {
|
|
5035
|
+
try {
|
|
5036
|
+
win.focus();
|
|
5037
|
+
win.print();
|
|
5038
|
+
} catch {
|
|
5039
|
+
warn("printing is not supported in this environment");
|
|
5040
|
+
}
|
|
5041
|
+
done();
|
|
5042
|
+
}, 50);
|
|
5043
|
+
}
|
|
4493
5044
|
function buildCsv(grid, opts = {}) {
|
|
4494
5045
|
const cols = exportColumns(grid, opts.columns);
|
|
4495
5046
|
const code = grid.store.state.locale.code;
|
|
@@ -4569,6 +5120,9 @@ function classStyles(grid) {
|
|
|
4569
5120
|
};
|
|
4570
5121
|
}
|
|
4571
5122
|
function buildXlsx(grid, opts = {}) {
|
|
5123
|
+
return writeXlsx([buildXlsxSheet(grid, opts)]);
|
|
5124
|
+
}
|
|
5125
|
+
function buildXlsxSheet(grid, opts = {}) {
|
|
4572
5126
|
const cols = exportColumns(grid, opts.columns);
|
|
4573
5127
|
const code = grid.store.state.locale.code;
|
|
4574
5128
|
const rows = [];
|
|
@@ -4674,14 +5228,14 @@ function buildXlsx(grid, opts = {}) {
|
|
|
4674
5228
|
const s = computeSpans(data, c.def);
|
|
4675
5229
|
for (let i = 0; i < data.length; i++) if (s.start[i] === i && s.end[i] > i) mergeRange(i, ci, s.end[i], ci);
|
|
4676
5230
|
});
|
|
4677
|
-
return
|
|
5231
|
+
return {
|
|
4678
5232
|
name: opts.sheetName ?? "Sheet1",
|
|
4679
5233
|
rows,
|
|
4680
5234
|
widths: cols.map((c) => Math.round(c.width / 7)),
|
|
4681
5235
|
merges,
|
|
4682
5236
|
freezeRows: headRows,
|
|
4683
5237
|
autoFilter: headRows > 0
|
|
4684
|
-
}
|
|
5238
|
+
};
|
|
4685
5239
|
}
|
|
4686
5240
|
function download(filename, parts, type) {
|
|
4687
5241
|
if (typeof URL === "undefined" || typeof URL.createObjectURL !== "function" || typeof document === "undefined") {
|
|
@@ -4703,7 +5257,7 @@ function asText(v) {
|
|
|
4703
5257
|
if (v === null || v === void 0) return "";
|
|
4704
5258
|
if (v instanceof Date) {
|
|
4705
5259
|
const hasTime = v.getHours() || v.getMinutes() || v.getSeconds();
|
|
4706
|
-
return hasTime ? v.toISOString() :
|
|
5260
|
+
return hasTime ? v.toISOString() : toDateKey2(v) ?? "";
|
|
4707
5261
|
}
|
|
4708
5262
|
return String(v);
|
|
4709
5263
|
}
|
|
@@ -4811,7 +5365,8 @@ function renderChart(el2, spec) {
|
|
|
4811
5365
|
top = 36;
|
|
4812
5366
|
}
|
|
4813
5367
|
const legend = svg("g", {}, "fg-chart-legend");
|
|
4814
|
-
const
|
|
5368
|
+
const round2 = spec.type === "pie" || spec.type === "donut";
|
|
5369
|
+
const names = round2 ? cats : spec.type === "scatter" ? series.slice(1).map((s) => s.name) : series.map((s) => s.name);
|
|
4815
5370
|
let lx = 16;
|
|
4816
5371
|
names.forEach((name, i) => {
|
|
4817
5372
|
const item = svg("g", { transform: `translate(${lx},${H - 10})` }, "fg-chart-legend-item");
|
|
@@ -4823,7 +5378,13 @@ function renderChart(el2, spec) {
|
|
|
4823
5378
|
lx += 24 + Math.min(140, name.length * 7);
|
|
4824
5379
|
});
|
|
4825
5380
|
const bottom = H - 52;
|
|
4826
|
-
if (spec.type === "
|
|
5381
|
+
if (spec.type === "scatter") {
|
|
5382
|
+
drawScatter(root, spec, series, top, bottom, nf, short);
|
|
5383
|
+
root.append(legend);
|
|
5384
|
+
el2.replaceChildren(root);
|
|
5385
|
+
return;
|
|
5386
|
+
}
|
|
5387
|
+
if (round2) {
|
|
4827
5388
|
const values = (series[0]?.values ?? []).map((v) => v !== null && v > 0 ? v : 0);
|
|
4828
5389
|
const total = values.reduce((a, b) => a + b, 0);
|
|
4829
5390
|
const cx = W / 2, cy = (top + bottom) / 2, r = Math.max(10, (bottom - top) / 2 - 6);
|
|
@@ -4839,6 +5400,7 @@ function renderChart(el2, spec) {
|
|
|
4839
5400
|
root.append(slice);
|
|
4840
5401
|
angle = a2;
|
|
4841
5402
|
});
|
|
5403
|
+
if (spec.type === "donut") root.append(svg("circle", { cx, cy, r: r * 0.58, fill: "var(--fg-bg, #fff)" }, "fg-chart-donut-hole"));
|
|
4842
5404
|
root.append(legend);
|
|
4843
5405
|
el2.replaceChildren(root);
|
|
4844
5406
|
return;
|
|
@@ -4879,11 +5441,25 @@ function renderChart(el2, spec) {
|
|
|
4879
5441
|
cats.forEach((c, i) => {
|
|
4880
5442
|
if (i % every) return;
|
|
4881
5443
|
const label = svg("text", { x: left + band * (i + 0.5), y: bottom + 16, "text-anchor": "middle" });
|
|
4882
|
-
|
|
5444
|
+
const fit = Math.max(3, Math.min(12, Math.floor(band * every / 10)));
|
|
5445
|
+
label.textContent = c.length > fit ? `${c.slice(0, fit - 1)}\u2026` : c;
|
|
4883
5446
|
axis.append(label);
|
|
4884
5447
|
});
|
|
4885
5448
|
root.append(axis);
|
|
4886
5449
|
const plot = svg("g", {}, "fg-chart-plot");
|
|
5450
|
+
const combo = spec.type === "combo";
|
|
5451
|
+
if (combo) {
|
|
5452
|
+
const inner = band * 0.6;
|
|
5453
|
+
const s = series[0];
|
|
5454
|
+
cats.forEach((c, ci) => {
|
|
5455
|
+
const v = s?.values[ci];
|
|
5456
|
+
if (v === null || v === void 0 || !Number.isFinite(v)) return;
|
|
5457
|
+
const x = left + band * ci + (band - inner) / 2;
|
|
5458
|
+
const rect = svg("rect", { x, y: Math.min(y(0), y(v)), width: Math.max(1, inner - 2), height: Math.max(0.5, Math.abs(y(v) - y(0))), fill: chartColor(0), rx: 2 }, "fg-chart-bar");
|
|
5459
|
+
tip(rect, `${s.name} \xB7 ${c}: ${nf.format(v)}`);
|
|
5460
|
+
plot.append(rect);
|
|
5461
|
+
});
|
|
5462
|
+
}
|
|
4887
5463
|
if (spec.type === "bar" || stacked) {
|
|
4888
5464
|
const inner = band * 0.72;
|
|
4889
5465
|
const bw = stacked ? inner : inner / Math.max(1, series.length);
|
|
@@ -4907,6 +5483,7 @@ function renderChart(el2, spec) {
|
|
|
4907
5483
|
});
|
|
4908
5484
|
} else {
|
|
4909
5485
|
series.forEach((s, si) => {
|
|
5486
|
+
if (combo && si === 0) return;
|
|
4910
5487
|
const pts = s.values.map((v, ci) => v === null || v === void 0 || !Number.isFinite(v) ? null : [left + band * (ci + 0.5), y(v), v]);
|
|
4911
5488
|
let d = "";
|
|
4912
5489
|
let pen = false;
|
|
@@ -4934,14 +5511,56 @@ function renderChart(el2, spec) {
|
|
|
4934
5511
|
});
|
|
4935
5512
|
});
|
|
4936
5513
|
}
|
|
4937
|
-
root.append(plot, legend);
|
|
4938
|
-
el2.replaceChildren(root);
|
|
5514
|
+
root.append(plot, legend);
|
|
5515
|
+
el2.replaceChildren(root);
|
|
5516
|
+
}
|
|
5517
|
+
function drawScatter(root, spec, series, top, bottom, nf, short) {
|
|
5518
|
+
const xs = series[0]?.values ?? [];
|
|
5519
|
+
const others = series.slice(1);
|
|
5520
|
+
const finite = (v) => v !== null && v !== void 0 && Number.isFinite(v);
|
|
5521
|
+
const allX = xs.filter(finite);
|
|
5522
|
+
const allY = others.flatMap((s) => s.values.filter(finite));
|
|
5523
|
+
if (!allX.length || !allY.length) return;
|
|
5524
|
+
const xt = niceTicks(Math.min(...allX), Math.max(...allX));
|
|
5525
|
+
const yt = niceTicks(Math.min(...allY), Math.max(...allY));
|
|
5526
|
+
const left = 56, right = W - 16;
|
|
5527
|
+
const x = (v) => left + (v - xt[0]) / (xt[xt.length - 1] - xt[0]) * (right - left);
|
|
5528
|
+
const y = (v) => bottom - (v - yt[0]) / (yt[yt.length - 1] - yt[0]) * (bottom - top);
|
|
5529
|
+
const axis = svg("g", {}, "fg-chart-axis");
|
|
5530
|
+
for (const t of yt) {
|
|
5531
|
+
axis.append(svg("line", { x1: left, x2: right, y1: y(t), y2: y(t) }, t === 0 ? "fg-chart-zero" : "fg-chart-grid"));
|
|
5532
|
+
const label = svg("text", { x: left - 6, y: y(t) + 4, "text-anchor": "end" });
|
|
5533
|
+
label.textContent = short.format(t);
|
|
5534
|
+
axis.append(label);
|
|
5535
|
+
}
|
|
5536
|
+
xt.forEach((t, i) => {
|
|
5537
|
+
const anchor = i === 0 ? "start" : i === xt.length - 1 ? "end" : "middle";
|
|
5538
|
+
const label = svg("text", { x: x(t), y: bottom + 16, "text-anchor": anchor });
|
|
5539
|
+
label.textContent = short.format(t);
|
|
5540
|
+
axis.append(label);
|
|
5541
|
+
});
|
|
5542
|
+
const xName = svg("text", { x: right, y: bottom + 32, "text-anchor": "end" }, "fg-chart-axis-name");
|
|
5543
|
+
xName.textContent = `${series[0]?.name ?? ""} \u2192`;
|
|
5544
|
+
axis.append(xName);
|
|
5545
|
+
root.append(axis);
|
|
5546
|
+
const plot = svg("g", {}, "fg-chart-plot");
|
|
5547
|
+
xs.forEach((xv, i) => {
|
|
5548
|
+
if (!finite(xv)) return;
|
|
5549
|
+
others.forEach((s, si) => {
|
|
5550
|
+
const yv = s.values[i];
|
|
5551
|
+
if (!finite(yv)) return;
|
|
5552
|
+
const dot = svg("circle", { cx: x(xv), cy: y(yv), r: 4, fill: chartColor(si), "fill-opacity": 0.8 }, "fg-chart-point");
|
|
5553
|
+
tip(dot, `${spec.categories[i] ?? ""}: ${series[0].name} ${nf.format(xv)}, ${s.name} ${nf.format(yv)}`);
|
|
5554
|
+
plot.append(dot);
|
|
5555
|
+
});
|
|
5556
|
+
});
|
|
5557
|
+
root.append(plot);
|
|
4939
5558
|
}
|
|
4940
5559
|
|
|
4941
5560
|
// src/interactions/charts.ts
|
|
4942
5561
|
var MAX_SERIES = 12;
|
|
4943
5562
|
var MAX_ROWS = 500;
|
|
4944
|
-
var TYPES = ["bar", "stackedBar", "line", "area", "pie"];
|
|
5563
|
+
var TYPES = ["bar", "stackedBar", "line", "area", "combo", "pie", "donut", "scatter"];
|
|
4945
5564
|
var GridCharts = class {
|
|
4946
5565
|
constructor(grid) {
|
|
4947
5566
|
this.grid = grid;
|
|
@@ -4993,7 +5612,7 @@ var GridCharts = class {
|
|
|
4993
5612
|
const body = el("div", "fg-chart-body");
|
|
4994
5613
|
let panel = null;
|
|
4995
5614
|
const draw = () => renderChart(body, { type, title: opts.title, locale: loc().code, ...data() });
|
|
4996
|
-
const typeLabel = (t) => ({ bar: loc().chartBar, stackedBar: loc().chartStacked, line: loc().chartLine, area: loc().chartArea, pie: loc().chartPie })[t];
|
|
5615
|
+
const typeLabel = (t) => ({ bar: loc().chartBar, stackedBar: loc().chartStacked, line: loc().chartLine, area: loc().chartArea, pie: loc().chartPie, donut: loc().chartDonut, combo: loc().chartCombo, scatter: loc().chartScatter })[t];
|
|
4997
5616
|
const syncButtons = () => panel?.querySelectorAll(".fg-chart-type").forEach((b) => b.setAttribute("aria-pressed", String(b.dataset.type === type)));
|
|
4998
5617
|
if (opts.container) opts.container.append(body);
|
|
4999
5618
|
else {
|
|
@@ -5217,7 +5836,7 @@ function buildPivot(source, config, defOf, labels, locale) {
|
|
|
5217
5836
|
}
|
|
5218
5837
|
|
|
5219
5838
|
// src/version.ts
|
|
5220
|
-
var version = "0.
|
|
5839
|
+
var version = "1.0.0";
|
|
5221
5840
|
|
|
5222
5841
|
// src/license.ts
|
|
5223
5842
|
var PUBLIC_KEY = { kty: "EC", crv: "P-256", x: "sUTKkglZmk-HMKPNub8p7AeLBKesHCU8irIkw9rJlbE", y: "j0U_aKRDR7iApY9UpaD3hUemhBLZJwDv9-QiT_hUyw8" };
|
|
@@ -5475,18 +6094,23 @@ var ColumnMenu = class {
|
|
|
5475
6094
|
t.textContent = loc.filter;
|
|
5476
6095
|
return t;
|
|
5477
6096
|
}
|
|
5478
|
-
|
|
5479
|
-
|
|
5480
|
-
const
|
|
5481
|
-
const box = el("div", "fg-menu-section fg-filter");
|
|
6097
|
+
/** One condition: operator, value and (for between) a second value. */
|
|
6098
|
+
conditionRow(type, loc, cur, optional, apply) {
|
|
6099
|
+
const row = el("div", "fg-filter-cond");
|
|
5482
6100
|
const select = el("select", "fg-filter-op", { "aria-label": loc.filter });
|
|
6101
|
+
if (optional) {
|
|
6102
|
+
const none = el("option");
|
|
6103
|
+
none.value = "";
|
|
6104
|
+
none.textContent = "\u2014";
|
|
6105
|
+
select.append(none);
|
|
6106
|
+
}
|
|
5483
6107
|
for (const op of OPS[type]) {
|
|
5484
6108
|
const o = el("option");
|
|
5485
6109
|
o.value = op;
|
|
5486
6110
|
o.textContent = loc.ops[op];
|
|
5487
6111
|
select.append(o);
|
|
5488
6112
|
}
|
|
5489
|
-
select.value = cur?.op ?? OPS[type][0];
|
|
6113
|
+
select.value = cur?.op ?? (optional ? "" : OPS[type][0]);
|
|
5490
6114
|
const inputType = type === "text" ? "text" : type;
|
|
5491
6115
|
const v1 = el("input", "fg-input fg-filter-value", { type: inputType, placeholder: loc.value, "aria-label": loc.value });
|
|
5492
6116
|
const v2 = el("input", "fg-input fg-filter-value-to", { type: inputType, placeholder: loc.valueTo, "aria-label": loc.valueTo });
|
|
@@ -5495,27 +6119,55 @@ var ColumnMenu = class {
|
|
|
5495
6119
|
v2.value = "valueTo" in cur && cur.valueTo != null ? String(cur.valueTo) : "";
|
|
5496
6120
|
}
|
|
5497
6121
|
const sync = () => {
|
|
5498
|
-
v1.hidden = NO_VALUE.includes(select.value);
|
|
6122
|
+
v1.hidden = !select.value || NO_VALUE.includes(select.value);
|
|
5499
6123
|
v2.hidden = select.value !== "between";
|
|
5500
6124
|
};
|
|
5501
6125
|
select.addEventListener("change", sync);
|
|
5502
6126
|
sync();
|
|
5503
|
-
const
|
|
6127
|
+
for (const input of [v1, v2]) input.addEventListener("keydown", (e) => e.key === "Enter" && apply());
|
|
6128
|
+
row.append(select, v1, v2);
|
|
6129
|
+
const read = () => {
|
|
5504
6130
|
const op = select.value;
|
|
6131
|
+
if (!op) return null;
|
|
5505
6132
|
const needsValue = !NO_VALUE.includes(op);
|
|
5506
|
-
let model = null;
|
|
5507
6133
|
if (type === "number") {
|
|
5508
6134
|
const a = v1.value === "" ? void 0 : Number(v1.value);
|
|
5509
6135
|
const b = v2.value === "" ? void 0 : Number(v2.value);
|
|
5510
|
-
|
|
5511
|
-
}
|
|
5512
|
-
|
|
5513
|
-
|
|
6136
|
+
return !needsValue || a !== void 0 ? { type, op, value: a, valueTo: b } : null;
|
|
6137
|
+
}
|
|
6138
|
+
if (type === "date") {
|
|
6139
|
+
return !needsValue || v1.value ? { type, op, value: v1.value || void 0, valueTo: v2.value || void 0 } : null;
|
|
6140
|
+
}
|
|
6141
|
+
return !needsValue || v1.value ? { type, op, value: v1.value } : null;
|
|
6142
|
+
};
|
|
6143
|
+
return { row, read };
|
|
6144
|
+
}
|
|
6145
|
+
conditionFilter(colId, type, loc, done) {
|
|
6146
|
+
const raw = this.grid.store.state.filters[colId];
|
|
6147
|
+
const parts = raw?.type === "combined" ? raw.conditions : raw ? [raw] : [];
|
|
6148
|
+
const first = parts[0]?.type === type ? parts[0] : void 0;
|
|
6149
|
+
const second = parts[1]?.type === type ? parts[1] : void 0;
|
|
6150
|
+
const box = el("div", "fg-menu-section fg-filter");
|
|
6151
|
+
const apply = () => {
|
|
6152
|
+
const a = c1.read();
|
|
6153
|
+
const b = c2.read();
|
|
6154
|
+
const operator = join.querySelector("input:checked")?.value === "or" ? "or" : "and";
|
|
6155
|
+
const model = a && b ? { type: "combined", operator, conditions: [a, b] } : a ?? b;
|
|
5514
6156
|
this.grid.setFilter(colId, model);
|
|
5515
6157
|
done();
|
|
5516
6158
|
};
|
|
5517
|
-
|
|
5518
|
-
|
|
6159
|
+
const c1 = this.conditionRow(type, loc, first, false, apply);
|
|
6160
|
+
const c2 = this.conditionRow(type, loc, second, true, apply);
|
|
6161
|
+
const join = el("div", "fg-filter-join", { role: "radiogroup" });
|
|
6162
|
+
const name = `fg-join-${colId}-${Math.random().toString(36).slice(2, 7)}`;
|
|
6163
|
+
for (const op of ["and", "or"]) {
|
|
6164
|
+
const label = el("label");
|
|
6165
|
+
const r = el("input", "", { type: "radio", name, value: op });
|
|
6166
|
+
r.checked = (raw?.type === "combined" ? raw.operator : "and") === op;
|
|
6167
|
+
label.append(r, ` ${loc[op]}`);
|
|
6168
|
+
join.append(label);
|
|
6169
|
+
}
|
|
6170
|
+
box.append(this.title(loc), c1.row, join, c2.row, this.actions(colId, loc, apply, done));
|
|
5519
6171
|
return box;
|
|
5520
6172
|
}
|
|
5521
6173
|
setFilter(colId, loc, done) {
|
|
@@ -5619,7 +6271,7 @@ var RowModel = class {
|
|
|
5619
6271
|
const d = input.display;
|
|
5620
6272
|
const treeChanged = !!d?.getParentId !== !!this.last.display?.getParentId;
|
|
5621
6273
|
const dataChanged = input.data !== this.last.data;
|
|
5622
|
-
const filterChanged = dataChanged || treeChanged || input.filters !== this.last.filters || input.quickFilter !== this.last.quickFilter;
|
|
6274
|
+
const filterChanged = dataChanged || treeChanged || input.filters !== this.last.filters || input.quickFilter !== this.last.quickFilter || input.advancedFilter !== this.last.advancedFilter;
|
|
5623
6275
|
const sortChanged = filterChanged || input.sort !== this.last.sort || collator !== this.last.collator;
|
|
5624
6276
|
const displayChanged = sortChanged || d?.version !== this.last.displayVersion || d?.groupBy !== this.last.display?.groupBy;
|
|
5625
6277
|
if (dataChanged) {
|
|
@@ -5685,7 +6337,7 @@ var RowModel = class {
|
|
|
5685
6337
|
* Prefix-sum offsets of the display rows: detail rows use `detailHeight`, rows with a
|
|
5686
6338
|
* measured height (auto row height) use it, everything else `rowHeight`.
|
|
5687
6339
|
*/
|
|
5688
|
-
layout(rowHeight, detailHeight, heights, heightsVersion = 0) {
|
|
6340
|
+
layout(rowHeight, detailHeight, heights, heightsVersion = 0, heightOf) {
|
|
5689
6341
|
const c = this.layoutCache;
|
|
5690
6342
|
if (c && c.rows === this.display && c.rh === rowHeight && c.dh === detailHeight && c.hv === heightsVersion) return c;
|
|
5691
6343
|
const n = this.display.length;
|
|
@@ -5696,7 +6348,11 @@ var RowModel = class {
|
|
|
5696
6348
|
const node = this.display[i];
|
|
5697
6349
|
let h = rowHeight;
|
|
5698
6350
|
if (node.kind === "detail") h = detailHeight;
|
|
5699
|
-
else if (
|
|
6351
|
+
else if (!node.kind) {
|
|
6352
|
+
if (measured) h = measured.get(node.id) ?? rowHeight;
|
|
6353
|
+
const fixed = heightOf?.(node.data);
|
|
6354
|
+
if (typeof fixed === "number" && fixed > 0) h = measured ? Math.max(h, fixed) : fixed;
|
|
6355
|
+
}
|
|
5700
6356
|
if (h !== rowHeight) uniform = false;
|
|
5701
6357
|
offsets[i + 1] = offsets[i] + h;
|
|
5702
6358
|
}
|
|
@@ -5707,9 +6363,11 @@ var RowModel = class {
|
|
|
5707
6363
|
const preds = Object.entries(input.filters).map(([colId, model]) => ({ def: this.deps.columnDef(colId), pred: buildPredicate(model) })).filter((p) => !!p.def);
|
|
5708
6364
|
const tokens = quickTokens(input.quickFilter);
|
|
5709
6365
|
const quickCols = tokens.length ? this.deps.quickColumns() : [];
|
|
5710
|
-
|
|
6366
|
+
const advanced = input.advancedFilter ? buildAdvanced(input.advancedFilter, (id) => this.deps.columnDef(id), getCellValue) : null;
|
|
6367
|
+
if (!preds.length && !tokens.length && !advanced) return this.nodes;
|
|
5711
6368
|
const matches = this.nodes.filter((n) => {
|
|
5712
6369
|
for (const p of preds) if (!p.pred(getCellValue(p.def, n.data))) return false;
|
|
6370
|
+
if (advanced && !advanced(n.data)) return false;
|
|
5713
6371
|
if (tokens.length) {
|
|
5714
6372
|
const text2 = quickCols.map((d) => this.deps.format(d, getCellValue(d, n.data))).join("").toLowerCase();
|
|
5715
6373
|
if (!matchesQuick(tokens, text2)) return false;
|
|
@@ -5819,6 +6477,149 @@ var RowModel = class {
|
|
|
5819
6477
|
}
|
|
5820
6478
|
};
|
|
5821
6479
|
|
|
6480
|
+
// src/rows/serverGroups.ts
|
|
6481
|
+
var LEVEL_ROWS = 1e4;
|
|
6482
|
+
var ServerGroups = class {
|
|
6483
|
+
constructor(datasource, getRowId, groupBy, query, onChange) {
|
|
6484
|
+
this.datasource = datasource;
|
|
6485
|
+
this.getRowId = getRowId;
|
|
6486
|
+
this.groupBy = groupBy;
|
|
6487
|
+
this.query = query;
|
|
6488
|
+
this.onChange = onChange;
|
|
6489
|
+
this.levels = /* @__PURE__ */ new Map();
|
|
6490
|
+
this.gen = 0;
|
|
6491
|
+
this.display = null;
|
|
6492
|
+
this.ids = null;
|
|
6493
|
+
this.version = 0;
|
|
6494
|
+
this.builtFor = -1;
|
|
6495
|
+
}
|
|
6496
|
+
matches(groupBy, q) {
|
|
6497
|
+
return groupBy.join("") === this.groupBy.join("") && q.sort === this.query.sort && q.filters === this.query.filters && q.quickFilter === this.query.quickFilter && q.advancedFilter === this.query.advancedFilter;
|
|
6498
|
+
}
|
|
6499
|
+
purge() {
|
|
6500
|
+
this.gen++;
|
|
6501
|
+
this.levels.clear();
|
|
6502
|
+
this.touch();
|
|
6503
|
+
}
|
|
6504
|
+
touch() {
|
|
6505
|
+
this.version++;
|
|
6506
|
+
this.display = null;
|
|
6507
|
+
this.ids = null;
|
|
6508
|
+
}
|
|
6509
|
+
load(path, keys, level) {
|
|
6510
|
+
if (this.levels.has(path)) return;
|
|
6511
|
+
const gen = this.gen;
|
|
6512
|
+
this.levels.set(path, { state: "loading", nodes: [] });
|
|
6513
|
+
const last = level >= this.groupBy.length;
|
|
6514
|
+
const q = this.query;
|
|
6515
|
+
let result;
|
|
6516
|
+
try {
|
|
6517
|
+
result = Promise.resolve(this.datasource.getRows({
|
|
6518
|
+
start: 0,
|
|
6519
|
+
end: LEVEL_ROWS,
|
|
6520
|
+
sort: [...q.sort],
|
|
6521
|
+
filters: { ...q.filters },
|
|
6522
|
+
quickFilter: q.quickFilter,
|
|
6523
|
+
advancedFilter: q.advancedFilter,
|
|
6524
|
+
groupBy: [...this.groupBy],
|
|
6525
|
+
groupKeys: [...keys]
|
|
6526
|
+
}));
|
|
6527
|
+
} catch (e) {
|
|
6528
|
+
result = Promise.reject(e);
|
|
6529
|
+
}
|
|
6530
|
+
result.then((res) => {
|
|
6531
|
+
if (gen !== this.gen) return;
|
|
6532
|
+
const rows = Array.isArray(res?.rows) ? res.rows.filter((r) => r !== null && typeof r === "object") : [];
|
|
6533
|
+
if (!Array.isArray(res?.rows)) warn("datasource.getRows must resolve to { rows: Row[] }");
|
|
6534
|
+
const nodes = last ? rows.map((data, i) => ({ id: this.getRowId ? String(this.getRowId(data)) : autoId(data), data, sourceIndex: i, level })) : rows.map((data) => {
|
|
6535
|
+
const colId = this.groupBy[level];
|
|
6536
|
+
const value = data[colId];
|
|
6537
|
+
const key = `${path ? `${path}|` : ""}${colId}:${setKey(value)}`;
|
|
6538
|
+
const count = typeof data.count === "number" ? data.count : typeof data.childCount === "number" ? data.childCount : void 0;
|
|
6539
|
+
return {
|
|
6540
|
+
id: `group:${key}`,
|
|
6541
|
+
data: {},
|
|
6542
|
+
sourceIndex: -1,
|
|
6543
|
+
kind: "group",
|
|
6544
|
+
level,
|
|
6545
|
+
group: { key, colId, value, rows: [], expanded: false, count, aggregates: data, keys: [...keys, value] }
|
|
6546
|
+
};
|
|
6547
|
+
});
|
|
6548
|
+
this.levels.set(path, { state: "loaded", nodes });
|
|
6549
|
+
this.touch();
|
|
6550
|
+
this.onChange();
|
|
6551
|
+
}, () => {
|
|
6552
|
+
if (gen !== this.gen) return;
|
|
6553
|
+
this.levels.set(path, { state: "failed", nodes: [] });
|
|
6554
|
+
this.touch();
|
|
6555
|
+
this.onChange();
|
|
6556
|
+
});
|
|
6557
|
+
}
|
|
6558
|
+
isOpen(d, key, level) {
|
|
6559
|
+
const explicit = d.expanded.get(key);
|
|
6560
|
+
if (explicit !== void 0) return explicit;
|
|
6561
|
+
return d.defaultExpanded === -1 || level < d.defaultExpanded;
|
|
6562
|
+
}
|
|
6563
|
+
/** Display rows: open groups show their loaded children, or a loading row while they arrive. */
|
|
6564
|
+
rows(d, displayVersion) {
|
|
6565
|
+
if (this.display && this.builtFor === displayVersion) return this.display;
|
|
6566
|
+
const out = [];
|
|
6567
|
+
const walk = (path, keys, level) => {
|
|
6568
|
+
const lv = this.levels.get(path);
|
|
6569
|
+
if (!lv) {
|
|
6570
|
+
this.load(path, keys, level);
|
|
6571
|
+
out.push({ id: `loading:${path}`, data: {}, sourceIndex: -1, kind: "loading", level });
|
|
6572
|
+
return;
|
|
6573
|
+
}
|
|
6574
|
+
if (lv.state !== "loaded") {
|
|
6575
|
+
if (lv.state === "loading") out.push({ id: `loading:${path}`, data: {}, sourceIndex: -1, kind: "loading", level });
|
|
6576
|
+
return;
|
|
6577
|
+
}
|
|
6578
|
+
for (const n of lv.nodes) {
|
|
6579
|
+
if (n.kind !== "group") {
|
|
6580
|
+
out.push(n);
|
|
6581
|
+
continue;
|
|
6582
|
+
}
|
|
6583
|
+
const g = n.group;
|
|
6584
|
+
const open = this.isOpen(d, g.key, level);
|
|
6585
|
+
const node = { ...n, group: { ...g, expanded: open } };
|
|
6586
|
+
out.push(node);
|
|
6587
|
+
if (open) walk(g.key, g.keys ?? [], level + 1);
|
|
6588
|
+
}
|
|
6589
|
+
};
|
|
6590
|
+
walk("", [], 0);
|
|
6591
|
+
this.display = out;
|
|
6592
|
+
this.builtFor = displayVersion;
|
|
6593
|
+
this.ids = null;
|
|
6594
|
+
return out;
|
|
6595
|
+
}
|
|
6596
|
+
dataRows() {
|
|
6597
|
+
const out = [];
|
|
6598
|
+
for (const lv of this.levels.values()) for (const n of lv.nodes) if (!n.kind) out.push(n);
|
|
6599
|
+
return out;
|
|
6600
|
+
}
|
|
6601
|
+
/** Rows matching the filters, from the counts of the top level when the server sent them. */
|
|
6602
|
+
dataCount() {
|
|
6603
|
+
const top = this.levels.get("");
|
|
6604
|
+
if (top?.state === "loaded" && top.nodes.every((n) => typeof n.group?.count === "number")) {
|
|
6605
|
+
return top.nodes.reduce((s, n) => s + (n.group?.count ?? 0), 0);
|
|
6606
|
+
}
|
|
6607
|
+
return this.dataRows().length;
|
|
6608
|
+
}
|
|
6609
|
+
loading() {
|
|
6610
|
+
for (const lv of this.levels.values()) if (lv.state === "loading") return true;
|
|
6611
|
+
return false;
|
|
6612
|
+
}
|
|
6613
|
+
indexOf(id) {
|
|
6614
|
+
if (!this.display) return -1;
|
|
6615
|
+
if (!this.ids) {
|
|
6616
|
+
this.ids = /* @__PURE__ */ new Map();
|
|
6617
|
+
this.display.forEach((n, i) => this.ids.set(n.id, i));
|
|
6618
|
+
}
|
|
6619
|
+
return this.ids.get(id) ?? -1;
|
|
6620
|
+
}
|
|
6621
|
+
};
|
|
6622
|
+
|
|
5822
6623
|
// src/rows/serverRowModel.ts
|
|
5823
6624
|
var RETRY_MS = 3e3;
|
|
5824
6625
|
var NO_OFFSETS = new Float64Array(0);
|
|
@@ -5839,6 +6640,9 @@ var ServerRowModel = class {
|
|
|
5839
6640
|
this.lastBlock = 0;
|
|
5840
6641
|
this.version = 0;
|
|
5841
6642
|
this.cache = null;
|
|
6643
|
+
/** Server-side grouping, while groupBy has columns. */
|
|
6644
|
+
this.groups = null;
|
|
6645
|
+
this.displayIn = null;
|
|
5842
6646
|
}
|
|
5843
6647
|
get bs() {
|
|
5844
6648
|
return this.page ? this.page.size : this.deps.blockSize;
|
|
@@ -5847,11 +6651,27 @@ var ServerRowModel = class {
|
|
|
5847
6651
|
return this.page ? this.page.index * this.page.size : 0;
|
|
5848
6652
|
}
|
|
5849
6653
|
update(input) {
|
|
6654
|
+
const groupBy = input.display?.groupBy ?? [];
|
|
6655
|
+
if (groupBy.length && !input.display?.getParentId) {
|
|
6656
|
+
const q2 = { sort: input.sort, filters: input.filters, quickFilter: input.quickFilter, advancedFilter: input.advancedFilter ?? null };
|
|
6657
|
+
if (!this.groups || !this.groups.matches(groupBy, q2)) {
|
|
6658
|
+
this.groups = new ServerGroups(this.deps.datasource, this.deps.getRowId, [...groupBy], q2, () => this.deps.onLoaded(0, 0, null));
|
|
6659
|
+
this.touch();
|
|
6660
|
+
}
|
|
6661
|
+
this.displayIn = input.display ?? null;
|
|
6662
|
+
this.query = null;
|
|
6663
|
+
return;
|
|
6664
|
+
}
|
|
6665
|
+
if (this.groups) {
|
|
6666
|
+
this.groups = null;
|
|
6667
|
+
this.query = null;
|
|
6668
|
+
this.touch();
|
|
6669
|
+
}
|
|
5850
6670
|
const q = this.query;
|
|
5851
|
-
const queryChanged = !q || q.sort !== input.sort || q.filters !== input.filters || q.quickFilter !== input.quickFilter;
|
|
6671
|
+
const queryChanged = !q || q.sort !== input.sort || q.filters !== input.filters || q.quickFilter !== input.quickFilter || q.advancedFilter !== (input.advancedFilter ?? null);
|
|
5852
6672
|
const p = input.page ?? null;
|
|
5853
6673
|
const sizeChanged = (p?.size ?? 0) !== (this.page?.size ?? 0);
|
|
5854
|
-
this.query = { sort: input.sort, filters: input.filters, quickFilter: input.quickFilter };
|
|
6674
|
+
this.query = { sort: input.sort, filters: input.filters, quickFilter: input.quickFilter, advancedFilter: input.advancedFilter ?? null };
|
|
5855
6675
|
this.page = p ? { ...p } : null;
|
|
5856
6676
|
if (queryChanged || sizeChanged) this.purge();
|
|
5857
6677
|
this.ensure(Math.floor(this.base / this.bs));
|
|
@@ -5860,6 +6680,10 @@ var ServerRowModel = class {
|
|
|
5860
6680
|
}
|
|
5861
6681
|
/** Forget every row and start over (sort/filter change or refreshRows). */
|
|
5862
6682
|
purge() {
|
|
6683
|
+
if (this.groups) {
|
|
6684
|
+
this.groups.purge();
|
|
6685
|
+
return;
|
|
6686
|
+
}
|
|
5863
6687
|
this.gen++;
|
|
5864
6688
|
this.blocks.clear();
|
|
5865
6689
|
this.total = null;
|
|
@@ -5879,13 +6703,16 @@ var ServerRowModel = class {
|
|
|
5879
6703
|
return this.loadedEnd + (last && last.nodes.length < this.bs ? 0 : this.bs);
|
|
5880
6704
|
}
|
|
5881
6705
|
getRowCount() {
|
|
6706
|
+
if (this.groups) return this.groupRows().length;
|
|
5882
6707
|
const all2 = this.fullCount();
|
|
5883
6708
|
return this.page ? Math.max(0, Math.min(this.page.size, all2 - this.base)) : all2;
|
|
5884
6709
|
}
|
|
5885
6710
|
dataCount() {
|
|
6711
|
+
if (this.groups) return this.groups.dataCount();
|
|
5886
6712
|
return this.fullCount();
|
|
5887
6713
|
}
|
|
5888
6714
|
loading() {
|
|
6715
|
+
if (this.groups) return this.groups.loading();
|
|
5889
6716
|
for (const b of this.blocks.values()) if (b.state === "loading") return true;
|
|
5890
6717
|
return false;
|
|
5891
6718
|
}
|
|
@@ -5897,7 +6724,11 @@ var ServerRowModel = class {
|
|
|
5897
6724
|
const b = this.blocks.get(k);
|
|
5898
6725
|
return b?.state === "loaded" ? b.nodes[abs - k * this.bs] ?? null : null;
|
|
5899
6726
|
}
|
|
6727
|
+
groupRows() {
|
|
6728
|
+
return this.groups.rows(this.displayIn, this.displayIn.version * 1e6 + this.groups.version);
|
|
6729
|
+
}
|
|
5900
6730
|
getRow(index) {
|
|
6731
|
+
if (this.groups) return this.groupRows()[index];
|
|
5901
6732
|
const abs = this.base + index;
|
|
5902
6733
|
const hit = this.peek(abs);
|
|
5903
6734
|
if (hit) return hit;
|
|
@@ -5920,7 +6751,7 @@ var ServerRowModel = class {
|
|
|
5920
6751
|
};
|
|
5921
6752
|
let result;
|
|
5922
6753
|
try {
|
|
5923
|
-
result = Promise.resolve(this.deps.datasource.getRows({ start, end, sort: [...q.sort], filters: { ...q.filters }, quickFilter: q.quickFilter }));
|
|
6754
|
+
result = Promise.resolve(this.deps.datasource.getRows({ start, end, sort: [...q.sort], filters: { ...q.filters }, quickFilter: q.quickFilter, advancedFilter: q.advancedFilter }));
|
|
5924
6755
|
} catch (e) {
|
|
5925
6756
|
result = Promise.reject(e);
|
|
5926
6757
|
}
|
|
@@ -5967,6 +6798,7 @@ var ServerRowModel = class {
|
|
|
5967
6798
|
}
|
|
5968
6799
|
/** Every row of the window, with placeholders for those not loaded (does not request anything). */
|
|
5969
6800
|
rows() {
|
|
6801
|
+
if (this.groups) return this.groupRows();
|
|
5970
6802
|
const c = this.built();
|
|
5971
6803
|
if (!c.rows) {
|
|
5972
6804
|
const n = this.getRowCount();
|
|
@@ -5976,15 +6808,21 @@ var ServerRowModel = class {
|
|
|
5976
6808
|
return c.rows;
|
|
5977
6809
|
}
|
|
5978
6810
|
dataRows() {
|
|
6811
|
+
if (this.groups) return this.groups.dataRows();
|
|
5979
6812
|
return this.built().data;
|
|
5980
6813
|
}
|
|
5981
6814
|
allNodes() {
|
|
6815
|
+
if (this.groups) return this.groups.dataRows();
|
|
5982
6816
|
return this.built().data;
|
|
5983
6817
|
}
|
|
5984
6818
|
idOf(row) {
|
|
5985
6819
|
return this.deps.getRowId ? String(this.deps.getRowId(row)) : autoId(row);
|
|
5986
6820
|
}
|
|
5987
6821
|
indexOfId(id) {
|
|
6822
|
+
if (this.groups) {
|
|
6823
|
+
this.groupRows();
|
|
6824
|
+
return this.groups.indexOf(id);
|
|
6825
|
+
}
|
|
5988
6826
|
const abs = this.built().ids.get(id);
|
|
5989
6827
|
if (abs === void 0) return -1;
|
|
5990
6828
|
const i = abs - this.base;
|
|
@@ -6035,10 +6873,272 @@ var Store = class {
|
|
|
6035
6873
|
}
|
|
6036
6874
|
};
|
|
6037
6875
|
|
|
6876
|
+
// src/interactions/tooltip.ts
|
|
6877
|
+
var DELAY = 500;
|
|
6878
|
+
function attachTooltips(grid) {
|
|
6879
|
+
const root = grid.renderer.root;
|
|
6880
|
+
let timer = 0;
|
|
6881
|
+
let tipEl = null;
|
|
6882
|
+
let target = null;
|
|
6883
|
+
const hide = () => {
|
|
6884
|
+
clearTimeout(timer);
|
|
6885
|
+
tipEl?.remove();
|
|
6886
|
+
tipEl = null;
|
|
6887
|
+
target = null;
|
|
6888
|
+
};
|
|
6889
|
+
const textFor = (cell) => {
|
|
6890
|
+
const colId = cell.dataset.col;
|
|
6891
|
+
if (!colId || isSystemCol(colId)) return null;
|
|
6892
|
+
const node = grid.columns.getNode(colId);
|
|
6893
|
+
if (!node) return null;
|
|
6894
|
+
if (cell.classList.contains("fg-header-cell")) return node.def.headerTooltip ?? null;
|
|
6895
|
+
const rowEl = cell.closest(".fg-row[data-row]");
|
|
6896
|
+
if (!rowEl || !cell.closest(".fg-body")) return null;
|
|
6897
|
+
const rowIndex = Number(rowEl.dataset.row);
|
|
6898
|
+
const row = grid.rowModel.getRow(rowIndex);
|
|
6899
|
+
if (!row || row.kind) return null;
|
|
6900
|
+
const def = node.def;
|
|
6901
|
+
const value = getCellValue(def, row.data);
|
|
6902
|
+
const formatted = getFormatter(def, grid.store.state.locale.code)(value);
|
|
6903
|
+
if (typeof def.tooltip === "function") {
|
|
6904
|
+
try {
|
|
6905
|
+
const t = def.tooltip({ value, formatted, row: row.data, rowIndex, column: def });
|
|
6906
|
+
return t ? String(t) : null;
|
|
6907
|
+
} catch {
|
|
6908
|
+
warn(`tooltip of column "${colId}" threw`);
|
|
6909
|
+
return null;
|
|
6910
|
+
}
|
|
6911
|
+
}
|
|
6912
|
+
if (def.tooltip === true) return formatted || null;
|
|
6913
|
+
if (grid.options.overflowTooltip !== false && def.tooltip !== false && cell.scrollWidth > cell.clientWidth + 1) return cell.textContent || null;
|
|
6914
|
+
return null;
|
|
6915
|
+
};
|
|
6916
|
+
const show = (cell, text2) => {
|
|
6917
|
+
tipEl?.remove();
|
|
6918
|
+
tipEl = el("div", `fg-tooltip fg-theme-${grid.store.state.theme}`, { role: "tooltip" });
|
|
6919
|
+
tipEl.textContent = text2;
|
|
6920
|
+
grid.applyFontTo(tipEl);
|
|
6921
|
+
popupParent(root).append(tipEl);
|
|
6922
|
+
const box = cell.getBoundingClientRect();
|
|
6923
|
+
const w = tipEl.offsetWidth, h = tipEl.offsetHeight;
|
|
6924
|
+
const vw = window.innerWidth || 1024, vh = window.innerHeight || 768;
|
|
6925
|
+
const top = box.bottom + 6 + h > vh ? box.top - h - 6 : box.bottom + 6;
|
|
6926
|
+
tipEl.style.left = `${Math.max(4, Math.min(vw - w - 4, box.left))}px`;
|
|
6927
|
+
tipEl.style.top = `${Math.max(4, top)}px`;
|
|
6928
|
+
};
|
|
6929
|
+
const onOver = (e) => {
|
|
6930
|
+
const cell = e.target.closest?.(".fg-cell, .fg-header-cell");
|
|
6931
|
+
if (!cell || cell === target || !root.contains(cell)) return;
|
|
6932
|
+
hide();
|
|
6933
|
+
target = cell;
|
|
6934
|
+
timer = window.setTimeout(() => {
|
|
6935
|
+
if (target !== cell || !cell.isConnected) return;
|
|
6936
|
+
const text2 = textFor(cell);
|
|
6937
|
+
if (text2) show(cell, text2);
|
|
6938
|
+
}, DELAY);
|
|
6939
|
+
};
|
|
6940
|
+
const onOut = (e) => {
|
|
6941
|
+
const to = e.relatedTarget;
|
|
6942
|
+
if (target && to && target.contains(to)) return;
|
|
6943
|
+
hide();
|
|
6944
|
+
};
|
|
6945
|
+
root.addEventListener("pointerover", onOver);
|
|
6946
|
+
root.addEventListener("pointerout", onOut);
|
|
6947
|
+
root.addEventListener("pointerdown", hide);
|
|
6948
|
+
root.addEventListener("keydown", hide);
|
|
6949
|
+
root.addEventListener("wheel", hide, { passive: true });
|
|
6950
|
+
return () => {
|
|
6951
|
+
hide();
|
|
6952
|
+
root.removeEventListener("pointerover", onOver);
|
|
6953
|
+
root.removeEventListener("pointerout", onOut);
|
|
6954
|
+
root.removeEventListener("pointerdown", hide);
|
|
6955
|
+
root.removeEventListener("keydown", hide);
|
|
6956
|
+
root.removeEventListener("wheel", hide);
|
|
6957
|
+
};
|
|
6958
|
+
}
|
|
6959
|
+
|
|
6960
|
+
// src/interactions/filterBuilder.ts
|
|
6961
|
+
var NO_VALUE2 = ["blank", "notBlank"];
|
|
6962
|
+
var FilterBuilder = class {
|
|
6963
|
+
constructor(grid) {
|
|
6964
|
+
this.grid = grid;
|
|
6965
|
+
this.popup = null;
|
|
6966
|
+
this.off = [];
|
|
6967
|
+
}
|
|
6968
|
+
get isOpen() {
|
|
6969
|
+
return !!this.popup;
|
|
6970
|
+
}
|
|
6971
|
+
/** Columns that can be filtered, with the condition type the builder uses for them. */
|
|
6972
|
+
columns() {
|
|
6973
|
+
return this.grid.columns.getState().map((s) => this.grid.columns.getNode(s.colId)).filter((n) => n && !n.children && !isSystemCol(n.id) && filterTypeOf(n.def)).map((n) => {
|
|
6974
|
+
const ft = filterTypeOf(n.def);
|
|
6975
|
+
const kind = ft === "number" ? "number" : ft === "date" ? "date" : "text";
|
|
6976
|
+
return { id: n.id, label: n.def.header ?? n.def.field ?? n.id, kind };
|
|
6977
|
+
});
|
|
6978
|
+
}
|
|
6979
|
+
open() {
|
|
6980
|
+
this.close();
|
|
6981
|
+
const { grid } = this;
|
|
6982
|
+
const loc = grid.store.state.locale;
|
|
6983
|
+
const cols = this.columns();
|
|
6984
|
+
const popup = el("div", `fg-menu fg-filter-builder fg-theme-${grid.store.state.theme}`, { role: "dialog", "aria-label": loc.advancedFilter });
|
|
6985
|
+
grid.applyFontTo(popup);
|
|
6986
|
+
const head = el("div", "fg-chooser-head");
|
|
6987
|
+
const title = el("span", "fg-menu-title");
|
|
6988
|
+
title.textContent = loc.advancedFilter;
|
|
6989
|
+
const x = el("button", "fg-chooser-close", { type: "button", "aria-label": loc.close });
|
|
6990
|
+
x.textContent = "\xD7";
|
|
6991
|
+
x.addEventListener("click", () => this.close());
|
|
6992
|
+
head.append(title, x);
|
|
6993
|
+
const cur = grid.store.state.advancedFilter;
|
|
6994
|
+
const join = el("div", "fg-filter-join", { role: "radiogroup" });
|
|
6995
|
+
const name = `fg-fb-join-${Math.random().toString(36).slice(2, 7)}`;
|
|
6996
|
+
for (const op of ["and", "or"]) {
|
|
6997
|
+
const label = el("label");
|
|
6998
|
+
const r2 = el("input", "", { type: "radio", name, value: op });
|
|
6999
|
+
r2.checked = (cur?.operator ?? "and") === op;
|
|
7000
|
+
label.append(r2, ` ${loc[op]}`);
|
|
7001
|
+
join.append(label);
|
|
7002
|
+
}
|
|
7003
|
+
const list = el("div", "fg-fb-list");
|
|
7004
|
+
const rows = [];
|
|
7005
|
+
const remove = (row) => {
|
|
7006
|
+
rows.splice(rows.indexOf(row), 1);
|
|
7007
|
+
row.el.remove();
|
|
7008
|
+
};
|
|
7009
|
+
const conditionRow = (start) => {
|
|
7010
|
+
const rowEl = el("div", "fg-fb-row");
|
|
7011
|
+
const colSel = el("select", "fg-filter-op fg-fb-col", { "aria-label": loc.columns });
|
|
7012
|
+
for (const c of cols) {
|
|
7013
|
+
const o = el("option");
|
|
7014
|
+
o.value = c.id;
|
|
7015
|
+
o.textContent = c.label;
|
|
7016
|
+
colSel.append(o);
|
|
7017
|
+
}
|
|
7018
|
+
const opSel = el("select", "fg-filter-op fg-fb-op", { "aria-label": loc.filter });
|
|
7019
|
+
const value = el("input", "fg-input fg-fb-value", { type: "text", placeholder: loc.value, "aria-label": loc.value });
|
|
7020
|
+
const valueTo = el("input", "fg-input fg-fb-value-to", { type: "text", placeholder: loc.valueTo, "aria-label": loc.valueTo });
|
|
7021
|
+
const del = el("button", "fg-chooser-close fg-fb-remove", { type: "button", "aria-label": loc.close });
|
|
7022
|
+
del.textContent = "\xD7";
|
|
7023
|
+
const kindOf = () => cols.find((c) => c.id === colSel.value)?.kind ?? "text";
|
|
7024
|
+
const fillOps = (keep) => {
|
|
7025
|
+
const kind = kindOf();
|
|
7026
|
+
opSel.replaceChildren(...OPS[kind].map((op) => {
|
|
7027
|
+
const o = el("option");
|
|
7028
|
+
o.value = op;
|
|
7029
|
+
o.textContent = loc.ops[op];
|
|
7030
|
+
return o;
|
|
7031
|
+
}));
|
|
7032
|
+
if (keep && OPS[kind].includes(keep)) opSel.value = keep;
|
|
7033
|
+
const type = kind === "text" ? "text" : kind;
|
|
7034
|
+
value.type = valueTo.type = type;
|
|
7035
|
+
sync();
|
|
7036
|
+
};
|
|
7037
|
+
const sync = () => {
|
|
7038
|
+
value.hidden = NO_VALUE2.includes(opSel.value);
|
|
7039
|
+
valueTo.hidden = opSel.value !== "between";
|
|
7040
|
+
};
|
|
7041
|
+
colSel.addEventListener("change", () => fillOps(opSel.value));
|
|
7042
|
+
opSel.addEventListener("change", sync);
|
|
7043
|
+
if (start && cols.some((c) => c.id === start.colId)) colSel.value = start.colId;
|
|
7044
|
+
const m = start?.model;
|
|
7045
|
+
fillOps(m?.op);
|
|
7046
|
+
if (m?.value != null) value.value = String(m.value);
|
|
7047
|
+
if (m?.valueTo != null) valueTo.value = String(m.valueTo);
|
|
7048
|
+
rowEl.append(colSel, opSel, value, valueTo, del);
|
|
7049
|
+
const state = {
|
|
7050
|
+
el: rowEl,
|
|
7051
|
+
read: () => {
|
|
7052
|
+
const kind = kindOf();
|
|
7053
|
+
const op = opSel.value;
|
|
7054
|
+
const needs = !NO_VALUE2.includes(op);
|
|
7055
|
+
let model = null;
|
|
7056
|
+
if (kind === "number") {
|
|
7057
|
+
const a = value.value === "" ? void 0 : Number(value.value);
|
|
7058
|
+
const b = valueTo.value === "" ? void 0 : Number(valueTo.value);
|
|
7059
|
+
if (!needs || a !== void 0 && !Number.isNaN(a)) model = op === "between" ? { type: "number", op, value: a, valueTo: b } : { type: "number", op, value: a };
|
|
7060
|
+
} else if (kind === "date") {
|
|
7061
|
+
if (!needs || value.value) model = op === "between" ? { type: "date", op, value: value.value, valueTo: valueTo.value } : { type: "date", op, value: value.value || void 0 };
|
|
7062
|
+
} else if (!needs || value.value) model = { type: "text", op, value: value.value };
|
|
7063
|
+
return model ? { colId: colSel.value, model } : null;
|
|
7064
|
+
}
|
|
7065
|
+
};
|
|
7066
|
+
del.addEventListener("click", () => remove(state));
|
|
7067
|
+
return state;
|
|
7068
|
+
};
|
|
7069
|
+
const groupRow = (g) => {
|
|
7070
|
+
const rowEl = el("div", "fg-fb-row fg-fb-group");
|
|
7071
|
+
const text2 = el("span", "fg-fb-group-label");
|
|
7072
|
+
text2.textContent = `( ${g.conditions.length} \xD7 ${loc[g.operator]} )`;
|
|
7073
|
+
const del = el("button", "fg-chooser-close fg-fb-remove", { type: "button", "aria-label": loc.close });
|
|
7074
|
+
del.textContent = "\xD7";
|
|
7075
|
+
rowEl.append(text2, del);
|
|
7076
|
+
const state = { el: rowEl, read: () => g };
|
|
7077
|
+
del.addEventListener("click", () => remove(state));
|
|
7078
|
+
return state;
|
|
7079
|
+
};
|
|
7080
|
+
const add = (row) => {
|
|
7081
|
+
rows.push(row);
|
|
7082
|
+
list.append(row.el);
|
|
7083
|
+
};
|
|
7084
|
+
for (const c of cur?.conditions ?? []) add("conditions" in c ? groupRow(c) : conditionRow(c));
|
|
7085
|
+
const addBtn = el("button", "fg-btn fg-fb-add", { type: "button" });
|
|
7086
|
+
addBtn.textContent = `+ ${loc.addCondition}`;
|
|
7087
|
+
addBtn.addEventListener("click", () => {
|
|
7088
|
+
const row = conditionRow();
|
|
7089
|
+
add(row);
|
|
7090
|
+
row.el.querySelector(".fg-fb-value").focus();
|
|
7091
|
+
});
|
|
7092
|
+
const actions = el("div", "fg-menu-actions");
|
|
7093
|
+
const reset = el("button", "fg-btn", { type: "button" });
|
|
7094
|
+
reset.textContent = loc.reset;
|
|
7095
|
+
reset.addEventListener("click", () => {
|
|
7096
|
+
grid.setAdvancedFilter(null);
|
|
7097
|
+
this.close();
|
|
7098
|
+
});
|
|
7099
|
+
const ok = el("button", "fg-btn fg-btn-primary", { type: "button" });
|
|
7100
|
+
ok.textContent = loc.apply;
|
|
7101
|
+
ok.addEventListener("click", () => {
|
|
7102
|
+
const conditions = rows.map((r2) => r2.read()).filter((c) => !!c);
|
|
7103
|
+
const operator = join.querySelector("input:checked")?.value === "or" ? "or" : "and";
|
|
7104
|
+
grid.setAdvancedFilter(conditions.length ? { operator, conditions } : null);
|
|
7105
|
+
this.close();
|
|
7106
|
+
});
|
|
7107
|
+
actions.append(reset, ok);
|
|
7108
|
+
popup.append(head, join, list, addBtn, actions);
|
|
7109
|
+
popupParent(grid.renderer.root).append(popup);
|
|
7110
|
+
const r = grid.renderer.root.getBoundingClientRect();
|
|
7111
|
+
const w = popup.offsetWidth || 460;
|
|
7112
|
+
popup.style.left = `${Math.max(4, Math.min(r.left + 24, (window.innerWidth || 1024) - w - 4))}px`;
|
|
7113
|
+
popup.style.top = `${Math.max(4, r.top + 24)}px`;
|
|
7114
|
+
this.popup = popup;
|
|
7115
|
+
if (!rows.length) addBtn.click();
|
|
7116
|
+
const onDown = (e) => {
|
|
7117
|
+
if (!eventInside(e, popup)) this.close();
|
|
7118
|
+
};
|
|
7119
|
+
const onKey = (e) => {
|
|
7120
|
+
if (e.key === "Escape") this.close();
|
|
7121
|
+
};
|
|
7122
|
+
document.addEventListener("pointerdown", onDown, true);
|
|
7123
|
+
document.addEventListener("keydown", onKey, true);
|
|
7124
|
+
this.off.push(
|
|
7125
|
+
() => document.removeEventListener("pointerdown", onDown, true),
|
|
7126
|
+
() => document.removeEventListener("keydown", onKey, true)
|
|
7127
|
+
);
|
|
7128
|
+
}
|
|
7129
|
+
close() {
|
|
7130
|
+
for (const f of this.off) f();
|
|
7131
|
+
this.off = [];
|
|
7132
|
+
this.popup?.remove();
|
|
7133
|
+
this.popup = null;
|
|
7134
|
+
}
|
|
7135
|
+
};
|
|
7136
|
+
|
|
6038
7137
|
// src/grid.ts
|
|
6039
7138
|
var autoRowHeight = (size) => Math.round(size * 2.75);
|
|
6040
7139
|
var autoHeaderHeight = (size) => Math.round(size * 3.1);
|
|
6041
7140
|
var DEFAULT_FONT_SIZE = 13;
|
|
7141
|
+
var DENSITY = { compact: [0.8, 0.85], normal: [1, 1], comfortable: [1.25, 1.15] };
|
|
6042
7142
|
var NO_GROUPS = [];
|
|
6043
7143
|
var DEFAULT_PAGE_SIZE = 100;
|
|
6044
7144
|
var NO_SPANS = /* @__PURE__ */ new Map();
|
|
@@ -6078,6 +7178,18 @@ var Grid = class {
|
|
|
6078
7178
|
/** 0-based page and rows per page (pagination only). */
|
|
6079
7179
|
this.page = 0;
|
|
6080
7180
|
this.pageSize = DEFAULT_PAGE_SIZE;
|
|
7181
|
+
// ── pagination and server rows ──────────────────────────────
|
|
7182
|
+
/** Cells flashing after a transaction: `${rowId}\u0000${colId}` → time the flash ends. */
|
|
7183
|
+
/** showOverlay: an overlay the app asked for, shown whatever the row count. */
|
|
7184
|
+
this.forcedOverlay = null;
|
|
7185
|
+
this.filterBuilder = new FilterBuilder(this);
|
|
7186
|
+
this.flashing = /* @__PURE__ */ new Map();
|
|
7187
|
+
this.flashTimer = 0;
|
|
7188
|
+
this.txQueue = [];
|
|
7189
|
+
this.txTimer = 0;
|
|
7190
|
+
/** A transaction is replacing the data: keep the selected range. */
|
|
7191
|
+
this.keepRange = false;
|
|
7192
|
+
this.rowNumberCache = null;
|
|
6081
7193
|
/** Tree rows whose children were loaded (or are loading) through treeData.loadChildren. */
|
|
6082
7194
|
this.treeLoaded = /* @__PURE__ */ new Set();
|
|
6083
7195
|
this.treeLoading = /* @__PURE__ */ new Set();
|
|
@@ -6089,6 +7201,9 @@ var Grid = class {
|
|
|
6089
7201
|
return false;
|
|
6090
7202
|
}
|
|
6091
7203
|
};
|
|
7204
|
+
this.density = "normal";
|
|
7205
|
+
/** Bumped on every selection change (group row checkboxes depend on their rows). */
|
|
7206
|
+
this.selectionVersion = 0;
|
|
6092
7207
|
if (!Array.isArray(options.columns)) warn("options.columns must be an array");
|
|
6093
7208
|
if (options.data != null && !Array.isArray(options.data)) warn("options.data must be an array");
|
|
6094
7209
|
this.store = new Store({
|
|
@@ -6101,6 +7216,7 @@ var Grid = class {
|
|
|
6101
7216
|
focus: null,
|
|
6102
7217
|
range: null,
|
|
6103
7218
|
extraRanges: [],
|
|
7219
|
+
advancedFilter: null,
|
|
6104
7220
|
locale: resolveLocale(options.locale),
|
|
6105
7221
|
theme: options.theme ?? "light"
|
|
6106
7222
|
});
|
|
@@ -6137,9 +7253,11 @@ var Grid = class {
|
|
|
6137
7253
|
this.font = { size: explicit, family: options.fontFamily ?? null };
|
|
6138
7254
|
const size = explicit ?? DEFAULT_FONT_SIZE;
|
|
6139
7255
|
this.renderer = new Renderer(host, this, {
|
|
6140
|
-
rowHeight: options.rowHeight ?? autoRowHeight(size),
|
|
6141
|
-
headerHeight: options.headerHeight ?? autoHeaderHeight(size)
|
|
7256
|
+
rowHeight: options.rowHeight ?? Math.round(autoRowHeight(size) * DENSITY[this.densityOf(options.density)][0]),
|
|
7257
|
+
headerHeight: options.headerHeight ?? Math.round(autoHeaderHeight(size) * DENSITY[this.densityOf(options.density)][1])
|
|
6142
7258
|
});
|
|
7259
|
+
this.density = this.densityOf(options.density);
|
|
7260
|
+
this.renderer.applyTheme();
|
|
6143
7261
|
this.renderer.applyFont(this.font.size, this.font.family);
|
|
6144
7262
|
this.menu = new ColumnMenu(this);
|
|
6145
7263
|
this.chooser = new ColumnChooserDialog(this);
|
|
@@ -6164,7 +7282,9 @@ var Grid = class {
|
|
|
6164
7282
|
() => this.editor.cancel(),
|
|
6165
7283
|
() => this.fillHandle.destroy(),
|
|
6166
7284
|
() => this.groupPanel?.destroy(),
|
|
6167
|
-
() => this.charts.destroy()
|
|
7285
|
+
() => this.charts.destroy(),
|
|
7286
|
+
() => this.filterBuilder.close(),
|
|
7287
|
+
attachTooltips(this)
|
|
6168
7288
|
);
|
|
6169
7289
|
if (typeof ResizeObserver !== "undefined") {
|
|
6170
7290
|
const ro = new ResizeObserver(() => this.renderer.schedule());
|
|
@@ -6313,7 +7433,181 @@ var Grid = class {
|
|
|
6313
7433
|
this.renderer.invalidate({ header: true, rows: true });
|
|
6314
7434
|
this.refreshChoosers();
|
|
6315
7435
|
}
|
|
6316
|
-
|
|
7436
|
+
isFlashing(rowId, colId) {
|
|
7437
|
+
return this.flashing.size > 0 && this.flashing.has(`${rowId}\0${colId}`);
|
|
7438
|
+
}
|
|
7439
|
+
flash(keys) {
|
|
7440
|
+
const until = Date.now() + 1e3;
|
|
7441
|
+
for (const k of keys) this.flashing.set(k, until);
|
|
7442
|
+
if (this.flashTimer) return;
|
|
7443
|
+
const tick = () => {
|
|
7444
|
+
const now = Date.now();
|
|
7445
|
+
for (const [k, t] of this.flashing) if (t <= now) this.flashing.delete(k);
|
|
7446
|
+
this.renderer.invalidate({ rows: true });
|
|
7447
|
+
this.flashTimer = this.flashing.size && !this.destroyed ? window.setTimeout(tick, 250) : 0;
|
|
7448
|
+
};
|
|
7449
|
+
this.flashTimer = window.setTimeout(tick, 1e3);
|
|
7450
|
+
}
|
|
7451
|
+
applyTransaction(tx) {
|
|
7452
|
+
const result = { added: [], updated: [], removed: [], notFound: [] };
|
|
7453
|
+
if (this.clientOnly("applyTransaction") || !tx || typeof tx !== "object") return result;
|
|
7454
|
+
const data = this.store.state.data;
|
|
7455
|
+
const getId = this.options.getRowId;
|
|
7456
|
+
const keyOf = (r) => {
|
|
7457
|
+
if (!r || typeof r !== "object") return null;
|
|
7458
|
+
if (data.includes(r)) return this.rowModel.idOf(r);
|
|
7459
|
+
try {
|
|
7460
|
+
return getId ? String(getId(r)) : null;
|
|
7461
|
+
} catch {
|
|
7462
|
+
return null;
|
|
7463
|
+
}
|
|
7464
|
+
};
|
|
7465
|
+
const byId = new Map(data.map((r) => [this.rowModel.idOf(r), r]));
|
|
7466
|
+
const gone = /* @__PURE__ */ new Set();
|
|
7467
|
+
for (const r of tx.remove ?? []) {
|
|
7468
|
+
const k = keyOf(r);
|
|
7469
|
+
const row = k === null ? void 0 : byId.get(k);
|
|
7470
|
+
if (row && !gone.has(row)) {
|
|
7471
|
+
gone.add(row);
|
|
7472
|
+
result.removed.push(row);
|
|
7473
|
+
} else if (!row) result.notFound.push(r);
|
|
7474
|
+
}
|
|
7475
|
+
const colsByField = /* @__PURE__ */ new Map();
|
|
7476
|
+
for (const st of this.columns.getState()) {
|
|
7477
|
+
const f = this.columns.getNode(st.colId)?.def.field;
|
|
7478
|
+
if (f) colsByField.set(f, [...colsByField.get(f) ?? [], st.colId]);
|
|
7479
|
+
}
|
|
7480
|
+
const flashKeys = [];
|
|
7481
|
+
for (const r of tx.update ?? []) {
|
|
7482
|
+
const k = keyOf(r);
|
|
7483
|
+
const row = k === null ? void 0 : byId.get(k);
|
|
7484
|
+
if (!row || gone.has(row)) {
|
|
7485
|
+
result.notFound.push(r);
|
|
7486
|
+
continue;
|
|
7487
|
+
}
|
|
7488
|
+
for (const [field, value] of Object.entries(r)) {
|
|
7489
|
+
if (Object.is(row[field], value)) continue;
|
|
7490
|
+
row[field] = value;
|
|
7491
|
+
for (const colId of colsByField.get(field) ?? []) flashKeys.push(`${k}\0${colId}`);
|
|
7492
|
+
}
|
|
7493
|
+
result.updated.push(row);
|
|
7494
|
+
}
|
|
7495
|
+
const added = cleanRows(Array.isArray(tx.add) ? tx.add : []);
|
|
7496
|
+
result.added.push(...added);
|
|
7497
|
+
if (!result.added.length && !result.updated.length && !result.removed.length) {
|
|
7498
|
+
if (result.notFound.length) this.events.emit("transactionApplied", result);
|
|
7499
|
+
return result;
|
|
7500
|
+
}
|
|
7501
|
+
const next = gone.size ? data.filter((r) => !gone.has(r)) : [...data];
|
|
7502
|
+
const at = typeof tx.addIndex === "number" ? Math.max(0, Math.min(next.length, Math.floor(tx.addIndex))) : next.length;
|
|
7503
|
+
next.splice(at, 0, ...added);
|
|
7504
|
+
this.editVersion++;
|
|
7505
|
+
this.renderer.invalidateHeights();
|
|
7506
|
+
this.keepRange = true;
|
|
7507
|
+
try {
|
|
7508
|
+
this.replaceData(next);
|
|
7509
|
+
} finally {
|
|
7510
|
+
this.keepRange = false;
|
|
7511
|
+
}
|
|
7512
|
+
if (flashKeys.length && this.options.flashCells !== false) this.flash(flashKeys);
|
|
7513
|
+
this.events.emit("transactionApplied", result);
|
|
7514
|
+
return result;
|
|
7515
|
+
}
|
|
7516
|
+
applyTransactionAsync(tx) {
|
|
7517
|
+
if (!tx || typeof tx !== "object") return;
|
|
7518
|
+
this.txQueue.push(tx);
|
|
7519
|
+
if (this.txTimer) return;
|
|
7520
|
+
this.txTimer = window.setTimeout(() => {
|
|
7521
|
+
this.txTimer = 0;
|
|
7522
|
+
const queue = this.txQueue;
|
|
7523
|
+
this.txQueue = [];
|
|
7524
|
+
if (this.destroyed || !queue.length) return;
|
|
7525
|
+
this.applyTransaction({
|
|
7526
|
+
add: queue.flatMap((t) => t.add ?? []),
|
|
7527
|
+
addIndex: queue.find((t) => typeof t.addIndex === "number")?.addIndex,
|
|
7528
|
+
update: queue.flatMap((t) => t.update ?? []),
|
|
7529
|
+
remove: queue.flatMap((t) => t.remove ?? [])
|
|
7530
|
+
});
|
|
7531
|
+
}, 50);
|
|
7532
|
+
}
|
|
7533
|
+
setAdvancedFilter(filter) {
|
|
7534
|
+
const next = filter && typeof filter === "object" && Array.isArray(filter.conditions) && filter.conditions.length ? filter : null;
|
|
7535
|
+
if (next === this.store.state.advancedFilter) return;
|
|
7536
|
+
this.store.set({ advancedFilter: next });
|
|
7537
|
+
this.events.emit("advancedFilterChange", { advancedFilter: next });
|
|
7538
|
+
}
|
|
7539
|
+
setColumnGroupOpened(groupId, open) {
|
|
7540
|
+
if (!this.columns.setGroupOpen(groupId, !!open)) return;
|
|
7541
|
+
this.layoutChanged();
|
|
7542
|
+
this.events.emit("columnGroupOpened", { groupId, open: !!open });
|
|
7543
|
+
}
|
|
7544
|
+
getState() {
|
|
7545
|
+
const s = this.store.state;
|
|
7546
|
+
const state = {
|
|
7547
|
+
columns: this.columns.getState(),
|
|
7548
|
+
columnGroups: this.columns.groupOpenState(),
|
|
7549
|
+
advancedFilter: s.advancedFilter ? JSON.parse(JSON.stringify(s.advancedFilter)) : null,
|
|
7550
|
+
sort: s.sort.map((x) => ({ ...x })),
|
|
7551
|
+
filters: JSON.parse(JSON.stringify(s.filters)),
|
|
7552
|
+
quickFilter: s.quickFilter,
|
|
7553
|
+
groupBy: [...this.groupBy],
|
|
7554
|
+
pivot: this.pivotCfg ? JSON.parse(JSON.stringify(this.pivotCfg)) : null,
|
|
7555
|
+
expanded: Object.fromEntries(this.expanded),
|
|
7556
|
+
selected: [...s.selected]
|
|
7557
|
+
};
|
|
7558
|
+
if (this.options.pagination) {
|
|
7559
|
+
state.page = this.page;
|
|
7560
|
+
state.pageSize = this.pageSize;
|
|
7561
|
+
}
|
|
7562
|
+
return state;
|
|
7563
|
+
}
|
|
7564
|
+
setState(state, options = {}) {
|
|
7565
|
+
if (!state || typeof state !== "object") return;
|
|
7566
|
+
const want = (k) => state[k] !== void 0 && (!options.parts || options.parts.includes(k));
|
|
7567
|
+
const known = (id) => !!this.columns.getNode(id);
|
|
7568
|
+
if (want("columns") && Array.isArray(state.columns)) {
|
|
7569
|
+
this.columns.applyState(state.columns.filter((c) => c && known(c.colId)));
|
|
7570
|
+
this.columnsChanged();
|
|
7571
|
+
}
|
|
7572
|
+
if (want("columnGroups") && state.columnGroups && typeof state.columnGroups === "object") {
|
|
7573
|
+
for (const [id, open] of Object.entries(state.columnGroups)) if (typeof open === "boolean") this.setColumnGroupOpened(id, open);
|
|
7574
|
+
}
|
|
7575
|
+
if (want("groupBy") && Array.isArray(state.groupBy)) this.setGroupBy(state.groupBy.filter(known));
|
|
7576
|
+
if (want("pivot")) this.setPivot(state.pivot ?? null);
|
|
7577
|
+
if (want("sort") && Array.isArray(state.sort)) this.setSort(state.sort.filter((x) => x && known(x.colId)));
|
|
7578
|
+
if (want("filters") && state.filters && typeof state.filters === "object") {
|
|
7579
|
+
const filters = Object.fromEntries(Object.entries(state.filters).filter(([id]) => known(id)));
|
|
7580
|
+
this.store.set({ filters });
|
|
7581
|
+
this.events.emit("filterChange", { filters: { ...filters } });
|
|
7582
|
+
}
|
|
7583
|
+
if (want("advancedFilter")) this.setAdvancedFilter(state.advancedFilter ?? null);
|
|
7584
|
+
if (want("quickFilter") && typeof state.quickFilter === "string") this.store.set({ quickFilter: state.quickFilter });
|
|
7585
|
+
if (want("expanded") && state.expanded && typeof state.expanded === "object") {
|
|
7586
|
+
this.expanded = new Map(Object.entries(state.expanded).filter(([, v]) => typeof v === "boolean"));
|
|
7587
|
+
this.displayChanged();
|
|
7588
|
+
}
|
|
7589
|
+
if (want("pageSize") && typeof state.pageSize === "number") this.setPageSize(state.pageSize);
|
|
7590
|
+
if (want("page") && typeof state.page === "number") this.setPage(state.page);
|
|
7591
|
+
if (want("selected") && Array.isArray(state.selected)) {
|
|
7592
|
+
const ids = new Set(this.rowModel.allNodes().map((n) => n.id));
|
|
7593
|
+
this.setSelected(new Set(state.selected.map(String).filter((id) => ids.has(id))));
|
|
7594
|
+
}
|
|
7595
|
+
}
|
|
7596
|
+
/** Number shown in the row-number column for display row `i` (data rows only; continues across pages). */
|
|
7597
|
+
rowNumber(i) {
|
|
7598
|
+
const rows = this.rowModel.rows();
|
|
7599
|
+
if (this.rowNumberCache?.rows !== rows) {
|
|
7600
|
+
const nums = new Int32Array(rows.length);
|
|
7601
|
+
let n = 0;
|
|
7602
|
+
rows.forEach((r, k) => {
|
|
7603
|
+
nums[k] = r.kind ? 0 : ++n;
|
|
7604
|
+
});
|
|
7605
|
+
this.rowNumberCache = { rows, nums };
|
|
7606
|
+
}
|
|
7607
|
+
const v = this.rowNumberCache.nums[i];
|
|
7608
|
+
if (!v) return null;
|
|
7609
|
+
return v + (this.options.pagination && !this.rowModel.server ? this.page * this.pageSize : 0);
|
|
7610
|
+
}
|
|
6317
7611
|
getPage() {
|
|
6318
7612
|
return this.options.pagination ? this.page : 0;
|
|
6319
7613
|
}
|
|
@@ -6428,7 +7722,6 @@ var Grid = class {
|
|
|
6428
7722
|
}
|
|
6429
7723
|
validGroupBy(ids) {
|
|
6430
7724
|
if (!Array.isArray(ids)) return [];
|
|
6431
|
-
if (ids.length && this.clientOnly("grouping")) return [];
|
|
6432
7725
|
return [...new Set(ids)].filter((id) => {
|
|
6433
7726
|
const node = this.columns.getNode(id);
|
|
6434
7727
|
if (!node || node.children || isSystemCol(id)) {
|
|
@@ -6515,6 +7808,7 @@ var Grid = class {
|
|
|
6515
7808
|
groupAggregate(node, colId) {
|
|
6516
7809
|
const def = this.columns.getNode(colId)?.def;
|
|
6517
7810
|
if (!node.group || !def?.aggFunc) return void 0;
|
|
7811
|
+
if (node.group.aggregates) return node.group.aggregates[colId] ?? (def.field ? node.group.aggregates[def.field] : void 0);
|
|
6518
7812
|
if (this.aggCache.ver !== this.editVersion) this.aggCache = { ver: this.editVersion, map: /* @__PURE__ */ new WeakMap() };
|
|
6519
7813
|
let m = this.aggCache.map.get(node.group);
|
|
6520
7814
|
if (!m) this.aggCache.map.set(node.group, m = /* @__PURE__ */ new Map());
|
|
@@ -6688,13 +7982,30 @@ var Grid = class {
|
|
|
6688
7982
|
const family = font.family === void 0 ? this.font.family : font.family || null;
|
|
6689
7983
|
this.font = { size, family };
|
|
6690
7984
|
const effective = size ?? DEFAULT_FONT_SIZE;
|
|
6691
|
-
|
|
6692
|
-
if (this.options.headerHeight == null) this.renderer.opts.headerHeight = autoHeaderHeight(effective);
|
|
7985
|
+
this.applyAutoHeights(effective);
|
|
6693
7986
|
this.renderer.applyFont(size, family);
|
|
6694
7987
|
this.renderer.invalidateHeights();
|
|
6695
7988
|
this.menu.close();
|
|
6696
7989
|
this.layoutChanged();
|
|
6697
7990
|
}
|
|
7991
|
+
densityOf(d) {
|
|
7992
|
+
return d === "compact" || d === "comfortable" ? d : "normal";
|
|
7993
|
+
}
|
|
7994
|
+
/** Row and header heights from the font size and density, unless the options fix them. */
|
|
7995
|
+
applyAutoHeights(fontSize) {
|
|
7996
|
+
const [r, h] = DENSITY[this.density];
|
|
7997
|
+
if (this.options.rowHeight == null) this.renderer.opts.rowHeight = Math.round(autoRowHeight(fontSize) * r);
|
|
7998
|
+
if (this.options.headerHeight == null) this.renderer.opts.headerHeight = Math.round(autoHeaderHeight(fontSize) * h);
|
|
7999
|
+
}
|
|
8000
|
+
setDensity(d) {
|
|
8001
|
+
const next = this.densityOf(d);
|
|
8002
|
+
if (next === this.density) return;
|
|
8003
|
+
this.density = next;
|
|
8004
|
+
this.renderer.applyTheme();
|
|
8005
|
+
this.applyAutoHeights(this.font.size ?? DEFAULT_FONT_SIZE);
|
|
8006
|
+
this.renderer.invalidateHeights();
|
|
8007
|
+
this.layoutChanged();
|
|
8008
|
+
}
|
|
6698
8009
|
/** Copy the grid font variables onto an element rendered outside the root (popups, drag ghost). */
|
|
6699
8010
|
applyFontTo(target) {
|
|
6700
8011
|
if (this.font.size) target.style.setProperty("--fg-font-size", `${this.font.size}px`);
|
|
@@ -6857,6 +8168,12 @@ var Grid = class {
|
|
|
6857
8168
|
}
|
|
6858
8169
|
return copyValue(def, value, code);
|
|
6859
8170
|
}
|
|
8171
|
+
/** Copy, then clear the editable cells of the selection. */
|
|
8172
|
+
cutToClipboard() {
|
|
8173
|
+
const text2 = this.copyToClipboard(false);
|
|
8174
|
+
if (text2) this.clearCells();
|
|
8175
|
+
return text2;
|
|
8176
|
+
}
|
|
6860
8177
|
copyToClipboard(headers, rows = false) {
|
|
6861
8178
|
const text2 = this.selectionTSV(headers, rows);
|
|
6862
8179
|
if (text2 && typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
|
|
@@ -7346,6 +8663,7 @@ var Grid = class {
|
|
|
7346
8663
|
this.events.emit("focusChange", { rowIndex, colId });
|
|
7347
8664
|
}
|
|
7348
8665
|
setSelected(selected, anchorId = this.store.state.anchorId) {
|
|
8666
|
+
this.selectionVersion++;
|
|
7349
8667
|
this.store.set({ selected, anchorId });
|
|
7350
8668
|
this.events.emit("selectionChange", { selected: this.selectedRows() });
|
|
7351
8669
|
}
|
|
@@ -7359,6 +8677,55 @@ var Grid = class {
|
|
|
7359
8677
|
return false;
|
|
7360
8678
|
}
|
|
7361
8679
|
}
|
|
8680
|
+
/** Selectable data row ids under a group row, or the descendants of a tree row. */
|
|
8681
|
+
groupMemberIds(node) {
|
|
8682
|
+
if (node.group) return node.group.rows.filter((r) => this.rowSelectable(r)).map((r) => this.rowModel.idOf(r));
|
|
8683
|
+
const getParent = this.options.treeData?.getParentId;
|
|
8684
|
+
if (!getParent) return [];
|
|
8685
|
+
const kids = /* @__PURE__ */ new Map();
|
|
8686
|
+
for (const r of this.store.state.data) {
|
|
8687
|
+
let p = null;
|
|
8688
|
+
try {
|
|
8689
|
+
p = getParent(r);
|
|
8690
|
+
} catch {
|
|
8691
|
+
p = null;
|
|
8692
|
+
}
|
|
8693
|
+
if (p == null) continue;
|
|
8694
|
+
const k = String(p);
|
|
8695
|
+
const list = kids.get(k);
|
|
8696
|
+
if (list) list.push(r);
|
|
8697
|
+
else kids.set(k, [r]);
|
|
8698
|
+
}
|
|
8699
|
+
const out = [];
|
|
8700
|
+
const seen2 = /* @__PURE__ */ new Set([node.id]);
|
|
8701
|
+
const queue = [node.id];
|
|
8702
|
+
while (queue.length) {
|
|
8703
|
+
for (const r of kids.get(queue.shift()) ?? []) {
|
|
8704
|
+
const id = this.rowModel.idOf(r);
|
|
8705
|
+
if (seen2.has(id)) continue;
|
|
8706
|
+
seen2.add(id);
|
|
8707
|
+
queue.push(id);
|
|
8708
|
+
if (this.rowSelectable(r)) out.push(id);
|
|
8709
|
+
}
|
|
8710
|
+
}
|
|
8711
|
+
return out;
|
|
8712
|
+
}
|
|
8713
|
+
/** 'all', 'some' or 'none' of a group row's rows are selected. */
|
|
8714
|
+
groupSelection(node) {
|
|
8715
|
+
const ids = this.groupMemberIds(node);
|
|
8716
|
+
const sel = this.store.state.selected;
|
|
8717
|
+
const n = ids.filter((id) => sel.has(id)).length;
|
|
8718
|
+
return n === 0 ? "none" : n === ids.length ? "all" : "some";
|
|
8719
|
+
}
|
|
8720
|
+
/** Group row checkbox: select every row of the group, or clear them when all were selected. */
|
|
8721
|
+
toggleGroupSelection(node) {
|
|
8722
|
+
if (this.options.rowSelection !== "multiple") return;
|
|
8723
|
+
const ids = this.groupMemberIds(node);
|
|
8724
|
+
const next = new Set(this.store.state.selected);
|
|
8725
|
+
const all2 = ids.length > 0 && ids.every((id) => next.has(id));
|
|
8726
|
+
for (const id of ids) all2 ? next.delete(id) : next.add(id);
|
|
8727
|
+
this.setSelected(next);
|
|
8728
|
+
}
|
|
7362
8729
|
selectFromClick(index, toggle, range) {
|
|
7363
8730
|
const mode = this.options.rowSelection;
|
|
7364
8731
|
if (!mode || index < 0 || index >= this.rowModel.getRowCount()) return;
|
|
@@ -7381,6 +8748,10 @@ var Grid = class {
|
|
|
7381
8748
|
if (n && (n.kind || !this.rowSelectable(n.data))) r.selected.delete(id);
|
|
7382
8749
|
}
|
|
7383
8750
|
}
|
|
8751
|
+
if (mode === "multiple" && this.options.groupSelectsChildren && this.options.treeData && target.hasChildren) {
|
|
8752
|
+
const on = r.selected.has(target.id);
|
|
8753
|
+
for (const id of this.groupMemberIds(target)) on ? r.selected.add(id) : r.selected.delete(id);
|
|
8754
|
+
}
|
|
7384
8755
|
this.setSelected(r.selected, this.rowModel.getRow(r.anchorIndex)?.id ?? null);
|
|
7385
8756
|
}
|
|
7386
8757
|
/** Displayed index of the selection anchor, or null when it is not displayed (filtered out or removed). */
|
|
@@ -7412,6 +8783,11 @@ var Grid = class {
|
|
|
7412
8783
|
}
|
|
7413
8784
|
withSelect(defs) {
|
|
7414
8785
|
const sys = [];
|
|
8786
|
+
const rn = this.options.rowNumbers;
|
|
8787
|
+
if (rn) {
|
|
8788
|
+
const o = typeof rn === "object" ? rn : {};
|
|
8789
|
+
sys.push(rowNumberColumn(typeof o.width === "number" && o.width > 0 ? o.width : void 0, typeof o.header === "string" ? o.header : void 0));
|
|
8790
|
+
}
|
|
7415
8791
|
if (this.options.rowDrag) sys.push(DRAG_COLUMN);
|
|
7416
8792
|
if (this.options.rowSelection) sys.push(SELECT_COLUMN);
|
|
7417
8793
|
return [...sys, ...defs];
|
|
@@ -7478,21 +8854,21 @@ var Grid = class {
|
|
|
7478
8854
|
this.sideBar?.refresh();
|
|
7479
8855
|
this.rowModel.invalidate();
|
|
7480
8856
|
}
|
|
7481
|
-
const resetPage = has("sort", "filters", "quickFilter") && this.page !== 0;
|
|
8857
|
+
const resetPage = has("sort", "filters", "quickFilter", "advancedFilter") && this.page !== 0;
|
|
7482
8858
|
if (resetPage) this.page = 0;
|
|
7483
|
-
if (has("data", "sort", "filters", "quickFilter", "locale")) this.updateRows();
|
|
8859
|
+
if (has("data", "sort", "filters", "quickFilter", "advancedFilter", "locale")) this.updateRows();
|
|
7484
8860
|
if (resetPage) this.emitPage();
|
|
7485
|
-
if (has("data", "sort", "filters", "quickFilter") && this.store.state.range) this.setRange(null);
|
|
7486
|
-
if (has("data", "sort", "filters", "quickFilter", "locale")) this.finder.refresh();
|
|
7487
|
-
if (has("data", "filters", "quickFilter", "locale") || has("selected") && this.summaryOpts?.scope === "selected") {
|
|
8861
|
+
if (has("data", "sort", "filters", "quickFilter", "advancedFilter") && this.store.state.range && !this.keepRange) this.setRange(null);
|
|
8862
|
+
if (has("data", "sort", "filters", "quickFilter", "advancedFilter", "locale")) this.finder.refresh();
|
|
8863
|
+
if (has("data", "filters", "quickFilter", "advancedFilter", "locale") || has("selected") && this.summaryOpts?.scope === "selected") {
|
|
7488
8864
|
this.refreshSummary();
|
|
7489
8865
|
}
|
|
7490
8866
|
if (has("locale")) this.groupPanel?.refresh();
|
|
7491
8867
|
if (has("filters")) this.sideBar?.refresh();
|
|
7492
8868
|
if (has("theme")) this.renderer.applyTheme();
|
|
7493
8869
|
this.renderer.invalidate({
|
|
7494
|
-
header: has("sort", "filters", "quickFilter", "locale", "selected", "data"),
|
|
7495
|
-
rows: has("data", "sort", "filters", "quickFilter", "locale")
|
|
8870
|
+
header: has("sort", "filters", "quickFilter", "advancedFilter", "locale", "selected", "data"),
|
|
8871
|
+
rows: has("data", "sort", "filters", "quickFilter", "advancedFilter", "locale")
|
|
7496
8872
|
});
|
|
7497
8873
|
}
|
|
7498
8874
|
createApi() {
|
|
@@ -7523,6 +8899,24 @@ var Grid = class {
|
|
|
7523
8899
|
getDisplayedRowCount: guard(() => this.rowModel.getRowCount()),
|
|
7524
8900
|
scrollToRow: guard((i) => this.renderer.scrollToRow(i)),
|
|
7525
8901
|
validate: guard(() => this.validateAll()),
|
|
8902
|
+
setDensity: guard((d) => this.setDensity(d)),
|
|
8903
|
+
getDensity: guard(() => this.density),
|
|
8904
|
+
showOverlay: guard((kind) => {
|
|
8905
|
+
this.forcedOverlay = kind === "loading" || kind === "noRows" ? kind : null;
|
|
8906
|
+
this.renderer.invalidate();
|
|
8907
|
+
}),
|
|
8908
|
+
getXlsxSheet: guard((o) => buildXlsxSheet(this, o)),
|
|
8909
|
+
print: guard((o) => printHtml(buildPrintHtml(this, o))),
|
|
8910
|
+
getPrintHtml: guard((o) => buildPrintHtml(this, o)),
|
|
8911
|
+
setColumnGroupOpened: guard((id, open) => this.setColumnGroupOpened(id, open)),
|
|
8912
|
+
setAdvancedFilter: guard((f) => this.setAdvancedFilter(f)),
|
|
8913
|
+
getAdvancedFilter: guard(() => this.store.state.advancedFilter ? JSON.parse(JSON.stringify(this.store.state.advancedFilter)) : null),
|
|
8914
|
+
openFilterBuilder: guard(() => this.filterBuilder.open()),
|
|
8915
|
+
isColumnGroupOpened: guard((id) => !!this.columns.getNode(id)?.open),
|
|
8916
|
+
applyTransaction: guard((tx) => this.applyTransaction(tx)),
|
|
8917
|
+
applyTransactionAsync: guard((tx) => this.applyTransactionAsync(tx)),
|
|
8918
|
+
getState: guard(() => this.getState()),
|
|
8919
|
+
setState: guard((s, o) => this.setState(s, o)),
|
|
7526
8920
|
getScrollPosition: guard(() => this.renderer.getScroll()),
|
|
7527
8921
|
setScrollPosition: guard((p) => this.renderer.setScroll(p ?? {})),
|
|
7528
8922
|
syncScroll: guard((other, axis = "both") => this.syncScroll(other, axis)),
|
|
@@ -7546,6 +8940,8 @@ var Grid = class {
|
|
|
7546
8940
|
setRanges: guard((list) => this.setRanges(list)),
|
|
7547
8941
|
copySelection: guard((o) => this.copyToClipboard(!!o?.headers, !!o?.rows)),
|
|
7548
8942
|
pasteText: guard((text2) => this.pasteText(text2)),
|
|
8943
|
+
cutSelection: guard(() => this.cutToClipboard()),
|
|
8944
|
+
stopRowEdit: guard((cancel) => this.editor.endRow(!cancel)),
|
|
7549
8945
|
startEdit: guard((rowIndex, colId) => this.editor.start(rowIndex, colId)),
|
|
7550
8946
|
stopEdit: guard((cancel) => cancel ? this.editor.cancel() : void this.editor.commit(null)),
|
|
7551
8947
|
addRows: guard((rows, index) => this.addRows(rows, index)),
|
|
@@ -7597,6 +8993,8 @@ var Grid = class {
|
|
|
7597
8993
|
if (this.destroyed) return;
|
|
7598
8994
|
this.destroyed = true;
|
|
7599
8995
|
liveGrids.delete(this.renderer.root);
|
|
8996
|
+
clearTimeout(this.flashTimer);
|
|
8997
|
+
clearTimeout(this.txTimer);
|
|
7600
8998
|
for (const d of this.disposers) d();
|
|
7601
8999
|
this.renderer.destroy();
|
|
7602
9000
|
this.events.clear();
|
|
@@ -7605,14 +9003,95 @@ var Grid = class {
|
|
|
7605
9003
|
function createGrid(host, options) {
|
|
7606
9004
|
return new Grid(host, options).api;
|
|
7607
9005
|
}
|
|
9006
|
+
|
|
9007
|
+
// src/workbook.ts
|
|
9008
|
+
function buildWorkbook(sheets) {
|
|
9009
|
+
const list = (Array.isArray(sheets) ? sheets : []).filter((s) => s && s.grid && typeof s.grid.getXlsxSheet === "function");
|
|
9010
|
+
if (!list.length) {
|
|
9011
|
+
warn("buildWorkbook: no grids given; the file has one empty sheet");
|
|
9012
|
+
return writeXlsx([{ name: "Sheet1", rows: [] }]);
|
|
9013
|
+
}
|
|
9014
|
+
return writeXlsx(list.map((s, i) => s.grid.getXlsxSheet({ ...s.options, sheetName: s.sheetName ?? s.options?.sheetName ?? `Sheet${i + 1}` })));
|
|
9015
|
+
}
|
|
9016
|
+
function exportWorkbook(filename, sheets) {
|
|
9017
|
+
download(filename || "export.xlsx", [buildWorkbook(sheets)], "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
|
|
9018
|
+
}
|
|
9019
|
+
|
|
9020
|
+
// src/rest.ts
|
|
9021
|
+
var headersOf = (h) => typeof h === "function" ? h() : h ?? {};
|
|
9022
|
+
async function send(url, method, body, opts) {
|
|
9023
|
+
let target = url;
|
|
9024
|
+
const init = { method, credentials: opts.credentials ?? "same-origin", headers: { Accept: "application/json", ...headersOf(opts.headers) } };
|
|
9025
|
+
if (method === "GET") {
|
|
9026
|
+
const q = new URLSearchParams();
|
|
9027
|
+
for (const [k, v] of Object.entries(body)) q.set(k, typeof v === "string" ? v : JSON.stringify(v));
|
|
9028
|
+
target += (url.includes("?") ? "&" : "?") + q.toString();
|
|
9029
|
+
} else {
|
|
9030
|
+
init.headers["Content-Type"] = "application/json";
|
|
9031
|
+
init.body = JSON.stringify(body);
|
|
9032
|
+
}
|
|
9033
|
+
const res = await fetch(target, init);
|
|
9034
|
+
if (!res.ok) throw new Error(`HTTP ${res.status} from ${url}`);
|
|
9035
|
+
const text2 = await res.text();
|
|
9036
|
+
return text2 ? JSON.parse(text2) : null;
|
|
9037
|
+
}
|
|
9038
|
+
function readRows(json) {
|
|
9039
|
+
if (Array.isArray(json)) return { rows: json };
|
|
9040
|
+
const o = json ?? {};
|
|
9041
|
+
const rows = [o.rows, o.list, o.content, o.data, o.items, o.resultList].find(Array.isArray);
|
|
9042
|
+
const total = [o.total, o.totalCount, o.totalElements, o.totCnt, o.count].find((v) => typeof v === "number");
|
|
9043
|
+
return { rows: rows ?? [], total };
|
|
9044
|
+
}
|
|
9045
|
+
function restDatasource(url, options = {}) {
|
|
9046
|
+
return {
|
|
9047
|
+
async getRows(p) {
|
|
9048
|
+
const size = Math.max(1, p.end - p.start);
|
|
9049
|
+
const body = {
|
|
9050
|
+
start: p.start,
|
|
9051
|
+
end: p.end,
|
|
9052
|
+
page: Math.floor(p.start / size),
|
|
9053
|
+
size,
|
|
9054
|
+
sort: p.sort,
|
|
9055
|
+
filters: p.filters,
|
|
9056
|
+
quickFilter: p.quickFilter,
|
|
9057
|
+
...options.params?.() ?? {}
|
|
9058
|
+
};
|
|
9059
|
+
const json = await send(url, options.method ?? "POST", body, options);
|
|
9060
|
+
return (options.transform ?? readRows)(json);
|
|
9061
|
+
}
|
|
9062
|
+
};
|
|
9063
|
+
}
|
|
9064
|
+
async function saveChanges(grid, url, options = {}) {
|
|
9065
|
+
if (!grid.hasChanges()) return null;
|
|
9066
|
+
const { created, updated, deleted } = grid.getModifiedRows();
|
|
9067
|
+
const json = await send(url, "POST", { created, updated, deleted, ...options.params?.() ?? {} }, options);
|
|
9068
|
+
grid.commitChanges();
|
|
9069
|
+
return json;
|
|
9070
|
+
}
|
|
9071
|
+
async function loadData(grid, url, options = {}) {
|
|
9072
|
+
const { rows } = await restDatasource(url, options).getRows({ start: 0, end: options.maxRows ?? 1e4, sort: [], filters: {}, quickFilter: "" });
|
|
9073
|
+
grid.setData(rows);
|
|
9074
|
+
return rows;
|
|
9075
|
+
}
|
|
9076
|
+
function csrfHeaders(doc = document) {
|
|
9077
|
+
const token = doc.querySelector('meta[name="_csrf"]')?.content;
|
|
9078
|
+
const header2 = doc.querySelector('meta[name="_csrf_header"]')?.content || "X-CSRF-TOKEN";
|
|
9079
|
+
return token ? { [header2]: token } : {};
|
|
9080
|
+
}
|
|
7608
9081
|
export {
|
|
9082
|
+
buildWorkbook,
|
|
7609
9083
|
createGrid,
|
|
9084
|
+
csrfHeaders,
|
|
7610
9085
|
en_default as en,
|
|
9086
|
+
exportWorkbook,
|
|
9087
|
+
loadData,
|
|
7611
9088
|
parseCsv,
|
|
7612
9089
|
readXlsx,
|
|
7613
9090
|
registerLocale,
|
|
7614
9091
|
renderChart,
|
|
7615
9092
|
resolveLocale,
|
|
9093
|
+
restDatasource,
|
|
9094
|
+
saveChanges,
|
|
7616
9095
|
setLicenseKey,
|
|
7617
9096
|
verifyLicense,
|
|
7618
9097
|
version,
|