@freegridjs/core 1.6.0 → 1.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cdn/freegrid.css +6 -0
- package/dist/cdn/freegrid.esm.js +22 -22
- package/dist/cdn/freegrid.min.js +24 -24
- package/dist/index.d.ts +15 -6
- package/dist/index.js +843 -456
- package/dist/locales/de.d.ts +1 -1
- package/dist/locales/de.js +1 -0
- package/dist/locales/en.d.ts +1 -1
- package/dist/locales/en.js +1 -0
- package/dist/locales/es.d.ts +1 -1
- package/dist/locales/es.js +1 -0
- package/dist/locales/fr.d.ts +1 -1
- package/dist/locales/fr.js +1 -0
- package/dist/locales/id.d.ts +1 -1
- package/dist/locales/id.js +1 -0
- package/dist/locales/it.d.ts +1 -1
- package/dist/locales/it.js +1 -0
- package/dist/locales/ja.d.ts +1 -1
- package/dist/locales/ja.js +1 -0
- package/dist/locales/ko.d.ts +1 -1
- package/dist/locales/ko.js +1 -0
- package/dist/locales/pt-BR.d.ts +1 -1
- package/dist/locales/pt-BR.js +1 -0
- package/dist/locales/ru.d.ts +1 -1
- package/dist/locales/ru.js +1 -0
- package/dist/locales/th.d.ts +1 -1
- package/dist/locales/th.js +1 -0
- package/dist/locales/vi.d.ts +1 -1
- package/dist/locales/vi.js +1 -0
- package/dist/locales/zh-CN.d.ts +1 -1
- package/dist/locales/zh-CN.js +1 -0
- package/dist/locales/zh-TW.d.ts +1 -1
- package/dist/locales/zh-TW.js +1 -0
- package/dist/styles.css +6 -0
- package/dist/{types-DPOWDM8R.d.ts → types-D8F0nn1a.d.ts} +7 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,3 +1,143 @@
|
|
|
1
|
+
// src/rows/filters.ts
|
|
2
|
+
function isBlank(v) {
|
|
3
|
+
return v == null || typeof v === "string" && v.trim() === "" || typeof v === "number" && Number.isNaN(v);
|
|
4
|
+
}
|
|
5
|
+
var pad = (n) => String(n).padStart(2, "0");
|
|
6
|
+
function toDateKey(v) {
|
|
7
|
+
if (isBlank(v)) return null;
|
|
8
|
+
if (typeof v === "string" && /^\d{4}-\d{2}-\d{2}$/.test(v)) return v;
|
|
9
|
+
const d = v instanceof Date ? v : new Date(v);
|
|
10
|
+
if (Number.isNaN(d.getTime())) return null;
|
|
11
|
+
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
|
|
12
|
+
}
|
|
13
|
+
function setKey(v) {
|
|
14
|
+
if (isBlank(v)) return "";
|
|
15
|
+
if (v instanceof Date) return toDateKey(v) ?? "";
|
|
16
|
+
return String(v);
|
|
17
|
+
}
|
|
18
|
+
var all = () => true;
|
|
19
|
+
function buildPredicate(model) {
|
|
20
|
+
switch (model.type) {
|
|
21
|
+
case "combined": {
|
|
22
|
+
const parts = (Array.isArray(model.conditions) ? model.conditions : []).map(buildPredicate);
|
|
23
|
+
if (!parts.length) return all;
|
|
24
|
+
return model.operator === "or" ? (v) => parts.some((p) => p(v)) : (v) => parts.every((p) => p(v));
|
|
25
|
+
}
|
|
26
|
+
case "text": {
|
|
27
|
+
const q = (model.value ?? "").toLowerCase();
|
|
28
|
+
const s = (v) => isBlank(v) ? "" : String(v).toLowerCase();
|
|
29
|
+
switch (model.op) {
|
|
30
|
+
case "contains":
|
|
31
|
+
return (v) => s(v).includes(q);
|
|
32
|
+
case "equals":
|
|
33
|
+
return (v) => s(v) === q;
|
|
34
|
+
case "notEquals":
|
|
35
|
+
return (v) => s(v) !== q;
|
|
36
|
+
case "startsWith":
|
|
37
|
+
return (v) => s(v).startsWith(q);
|
|
38
|
+
case "endsWith":
|
|
39
|
+
return (v) => s(v).endsWith(q);
|
|
40
|
+
case "blank":
|
|
41
|
+
return isBlank;
|
|
42
|
+
case "notBlank":
|
|
43
|
+
return (v) => !isBlank(v);
|
|
44
|
+
}
|
|
45
|
+
return all;
|
|
46
|
+
}
|
|
47
|
+
case "number": {
|
|
48
|
+
const n = (v) => isBlank(v) ? NaN : Number(v);
|
|
49
|
+
if (model.op === "blank") return (v) => Number.isNaN(n(v));
|
|
50
|
+
if (model.op === "notBlank") return (v) => !Number.isNaN(n(v));
|
|
51
|
+
const a = model.value;
|
|
52
|
+
if (a == null || Number.isNaN(a)) return all;
|
|
53
|
+
switch (model.op) {
|
|
54
|
+
case "eq":
|
|
55
|
+
return (v) => n(v) === a;
|
|
56
|
+
case "ne":
|
|
57
|
+
return (v) => n(v) !== a;
|
|
58
|
+
case "lt":
|
|
59
|
+
return (v) => n(v) < a;
|
|
60
|
+
case "lte":
|
|
61
|
+
return (v) => n(v) <= a;
|
|
62
|
+
case "gt":
|
|
63
|
+
return (v) => n(v) > a;
|
|
64
|
+
case "gte":
|
|
65
|
+
return (v) => n(v) >= a;
|
|
66
|
+
case "between": {
|
|
67
|
+
const b = model.valueTo;
|
|
68
|
+
if (b == null || Number.isNaN(b)) return all;
|
|
69
|
+
const lo = Math.min(a, b), hi = Math.max(a, b);
|
|
70
|
+
return (v) => {
|
|
71
|
+
const x = n(v);
|
|
72
|
+
return x >= lo && x <= hi;
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return all;
|
|
77
|
+
}
|
|
78
|
+
case "date": {
|
|
79
|
+
if (model.op === "blank") return (v) => toDateKey(v) === null;
|
|
80
|
+
if (model.op === "notBlank") return (v) => toDateKey(v) !== null;
|
|
81
|
+
const a = model.value;
|
|
82
|
+
if (!a) return all;
|
|
83
|
+
const k = (v) => toDateKey(v);
|
|
84
|
+
switch (model.op) {
|
|
85
|
+
case "equals":
|
|
86
|
+
return (v) => k(v) === a;
|
|
87
|
+
case "before":
|
|
88
|
+
return (v) => {
|
|
89
|
+
const x = k(v);
|
|
90
|
+
return x !== null && x < a;
|
|
91
|
+
};
|
|
92
|
+
case "after":
|
|
93
|
+
return (v) => {
|
|
94
|
+
const x = k(v);
|
|
95
|
+
return x !== null && x > a;
|
|
96
|
+
};
|
|
97
|
+
case "between": {
|
|
98
|
+
const b = model.valueTo;
|
|
99
|
+
if (!b) return all;
|
|
100
|
+
const lo = a < b ? a : b, hi = a < b ? b : a;
|
|
101
|
+
return (v) => {
|
|
102
|
+
const x = k(v);
|
|
103
|
+
return x !== null && x >= lo && x <= hi;
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return all;
|
|
108
|
+
}
|
|
109
|
+
case "set": {
|
|
110
|
+
const allowed = new Set(model.values.map(setKey));
|
|
111
|
+
return (v) => allowed.has(setKey(v));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return all;
|
|
115
|
+
}
|
|
116
|
+
function quickTokens(q) {
|
|
117
|
+
const t = q.trim().toLowerCase();
|
|
118
|
+
return t ? t.split(/\s+/) : [];
|
|
119
|
+
}
|
|
120
|
+
function matchesQuick(tokens, text2) {
|
|
121
|
+
return tokens.every((t) => text2.includes(t));
|
|
122
|
+
}
|
|
123
|
+
function buildAdvanced(f, columnDef, valueOf) {
|
|
124
|
+
const parts = [];
|
|
125
|
+
for (const c of Array.isArray(f?.conditions) ? f.conditions : []) {
|
|
126
|
+
if (!c || typeof c !== "object") continue;
|
|
127
|
+
if ("conditions" in c) {
|
|
128
|
+
const sub = buildAdvanced(c, columnDef, valueOf);
|
|
129
|
+
if (sub) parts.push(sub);
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
const def = columnDef(c.colId);
|
|
133
|
+
if (!def || !c.model) continue;
|
|
134
|
+
const pred = buildPredicate(c.model);
|
|
135
|
+
parts.push((row) => pred(valueOf(def, row)));
|
|
136
|
+
}
|
|
137
|
+
if (!parts.length) return null;
|
|
138
|
+
return f.operator === "or" ? (row) => parts.some((p) => p(row)) : (row) => parts.every((p) => p(row));
|
|
139
|
+
}
|
|
140
|
+
|
|
1
141
|
// src/warn.ts
|
|
2
142
|
var seen = /* @__PURE__ */ new Set();
|
|
3
143
|
function warn(message) {
|
|
@@ -6,6 +146,128 @@ function warn(message) {
|
|
|
6
146
|
console.warn(`[FreeGrid] ${message}`);
|
|
7
147
|
}
|
|
8
148
|
|
|
149
|
+
// src/i18n/format.ts
|
|
150
|
+
var cache = /* @__PURE__ */ new Map();
|
|
151
|
+
var numberFormats = /* @__PURE__ */ new Map();
|
|
152
|
+
function numberFormat(locale, maxFraction) {
|
|
153
|
+
const key = `${locale}|${maxFraction ?? ""}`;
|
|
154
|
+
let nf = numberFormats.get(key);
|
|
155
|
+
if (!nf) {
|
|
156
|
+
nf = new Intl.NumberFormat(locale, maxFraction == null ? void 0 : { maximumFractionDigits: maxFraction });
|
|
157
|
+
numberFormats.set(key, nf);
|
|
158
|
+
}
|
|
159
|
+
return nf;
|
|
160
|
+
}
|
|
161
|
+
function parseNumberPattern(p) {
|
|
162
|
+
if (p === "integer") return { maximumFractionDigits: 0 };
|
|
163
|
+
if (p === "percent") return { style: "percent" };
|
|
164
|
+
if (p.startsWith("currency:")) return { style: "currency", currency: p.slice(9) };
|
|
165
|
+
if (!/^[#0,]*(\.[#0]+)?%?$/.test(p) || !/[#0]/.test(p)) return null;
|
|
166
|
+
const pct = p.endsWith("%");
|
|
167
|
+
const body = pct ? p.slice(0, -1) : p;
|
|
168
|
+
const frac = body.includes(".") ? body.split(".")[1] : "";
|
|
169
|
+
return {
|
|
170
|
+
useGrouping: body.includes(","),
|
|
171
|
+
minimumFractionDigits: (frac.match(/0/g) ?? []).length,
|
|
172
|
+
maximumFractionDigits: frac.length,
|
|
173
|
+
...pct ? { style: "percent" } : {}
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
var DATE_PRESETS = {
|
|
177
|
+
date: { year: "numeric", month: "2-digit", day: "2-digit" },
|
|
178
|
+
datetime: { year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit" },
|
|
179
|
+
time: { hour: "2-digit", minute: "2-digit" },
|
|
180
|
+
short: { dateStyle: "short" },
|
|
181
|
+
medium: { dateStyle: "medium" },
|
|
182
|
+
long: { dateStyle: "long" }
|
|
183
|
+
};
|
|
184
|
+
function toDate(v) {
|
|
185
|
+
if (isBlank(v)) return null;
|
|
186
|
+
if (v instanceof Date) return Number.isNaN(v.getTime()) ? null : v;
|
|
187
|
+
if (typeof v === "string") {
|
|
188
|
+
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(v);
|
|
189
|
+
if (m) return new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]));
|
|
190
|
+
}
|
|
191
|
+
if (typeof v !== "string" && typeof v !== "number") return null;
|
|
192
|
+
const d = new Date(v);
|
|
193
|
+
return Number.isNaN(d.getTime()) ? null : d;
|
|
194
|
+
}
|
|
195
|
+
function create(type, format, locale) {
|
|
196
|
+
switch (type) {
|
|
197
|
+
case "number": {
|
|
198
|
+
let opts = {};
|
|
199
|
+
if (typeof format === "string") {
|
|
200
|
+
const parsed = parseNumberPattern(format);
|
|
201
|
+
if (!parsed) warn(`unknown number format "${format}"`);
|
|
202
|
+
opts = parsed ?? {};
|
|
203
|
+
} else if (format) opts = format;
|
|
204
|
+
const nf = new Intl.NumberFormat(locale, opts);
|
|
205
|
+
return (v) => {
|
|
206
|
+
if (isBlank(v)) return "";
|
|
207
|
+
const n = typeof v === "number" ? v : Number(v);
|
|
208
|
+
return Number.isNaN(n) ? String(v) : nf.format(n);
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
case "date": {
|
|
212
|
+
if (locale.toLowerCase().startsWith("ko") && (format === void 0 || format === "date" || format === "datetime")) {
|
|
213
|
+
const pad4 = (n) => String(n).padStart(2, "0");
|
|
214
|
+
const withTime = format === "datetime";
|
|
215
|
+
return (v) => {
|
|
216
|
+
const d = toDate(v);
|
|
217
|
+
if (!d) return isBlank(v) ? "" : String(v);
|
|
218
|
+
const day = `${d.getFullYear()}-${pad4(d.getMonth() + 1)}-${pad4(d.getDate())}`;
|
|
219
|
+
return withTime ? `${day} ${pad4(d.getHours())}:${pad4(d.getMinutes())}` : day;
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
let opts = DATE_PRESETS.date;
|
|
223
|
+
if (typeof format === "string") {
|
|
224
|
+
if (!DATE_PRESETS[format]) warn(`unknown date format "${format}"`);
|
|
225
|
+
opts = DATE_PRESETS[format] ?? DATE_PRESETS.date;
|
|
226
|
+
} else if (format) opts = format;
|
|
227
|
+
const df = new Intl.DateTimeFormat(locale, opts);
|
|
228
|
+
return (v) => {
|
|
229
|
+
const d = toDate(v);
|
|
230
|
+
return d ? df.format(d) : isBlank(v) ? "" : String(v);
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
case "boolean":
|
|
234
|
+
return (v) => v === true ? "\u2713" : v === false ? "\u2717" : "";
|
|
235
|
+
default:
|
|
236
|
+
return (v) => isBlank(v) ? "" : v instanceof Date ? v.toISOString() : String(v);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
var sparklineText = (v) => Array.isArray(v) ? v.join(", ") : v == null ? "" : String(v);
|
|
240
|
+
var byDef = /* @__PURE__ */ new WeakMap();
|
|
241
|
+
var retyped = /* @__PURE__ */ new WeakMap();
|
|
242
|
+
function clearFormatterCache() {
|
|
243
|
+
byDef = /* @__PURE__ */ new WeakMap();
|
|
244
|
+
retyped = /* @__PURE__ */ new WeakMap();
|
|
245
|
+
}
|
|
246
|
+
function getFormatterAs(def, type, locale, format = def.format) {
|
|
247
|
+
if (type === def.type && format === def.format) return getFormatter(def, locale);
|
|
248
|
+
if (format !== def.format && typeof format !== "string") return getFormatter({ ...def, type, format }, locale);
|
|
249
|
+
let byKey = retyped.get(def);
|
|
250
|
+
if (!byKey) retyped.set(def, byKey = /* @__PURE__ */ new Map());
|
|
251
|
+
const key = `${type ?? ""}|${format === def.format ? "" : `=${format}`}`;
|
|
252
|
+
let d = byKey.get(key);
|
|
253
|
+
if (!d) byKey.set(key, d = { ...def, type, format });
|
|
254
|
+
return getFormatter(d, locale);
|
|
255
|
+
}
|
|
256
|
+
function getFormatter(def, locale) {
|
|
257
|
+
if (def.sparkline) return sparklineText;
|
|
258
|
+
const f = def.format;
|
|
259
|
+
const hit = byDef.get(def);
|
|
260
|
+
if (hit && hit.locale === locale && hit.type === def.type && hit.format === f) return hit.fmt;
|
|
261
|
+
const key = `${locale}|${def.type ?? "text"}|${typeof f === "string" ? f : JSON.stringify(f ?? null)}`;
|
|
262
|
+
let fmt = cache.get(key);
|
|
263
|
+
if (!fmt) {
|
|
264
|
+
fmt = create(def.type, f, locale);
|
|
265
|
+
cache.set(key, fmt);
|
|
266
|
+
}
|
|
267
|
+
byDef.set(def, { locale, type: def.type, format: f, fmt });
|
|
268
|
+
return fmt;
|
|
269
|
+
}
|
|
270
|
+
|
|
9
271
|
// src/columns/columnModel.ts
|
|
10
272
|
var SELECT_COL_ID = "__fg_select__";
|
|
11
273
|
var SELECT_COLUMN = {
|
|
@@ -91,9 +353,12 @@ var ColumnModel = class {
|
|
|
91
353
|
this.containerWidth = 0;
|
|
92
354
|
this.layout = { left: [], center: [], right: [] };
|
|
93
355
|
this.leafById = /* @__PURE__ */ new Map();
|
|
356
|
+
/** Leaf ids in the order the definitions list them (before any move by the user). */
|
|
357
|
+
this.declared = [];
|
|
94
358
|
this.setDefs(defs);
|
|
95
359
|
}
|
|
96
360
|
setDefs(defs) {
|
|
361
|
+
clearFormatterCache();
|
|
97
362
|
this.defs = defs;
|
|
98
363
|
this.byId = /* @__PURE__ */ new Map();
|
|
99
364
|
let auto = 0;
|
|
@@ -133,9 +398,36 @@ var ColumnModel = class {
|
|
|
133
398
|
return ok;
|
|
134
399
|
};
|
|
135
400
|
this.roots_ = valid(defs).map((d) => build(d, null, 0));
|
|
401
|
+
this.declared = this.leafNodes().map((n) => n.id);
|
|
136
402
|
this.depth = Math.max(1, ...this.roots_.map(maxDepth));
|
|
137
403
|
this.relayout();
|
|
138
404
|
}
|
|
405
|
+
/**
|
|
406
|
+
* New definitions that keep what the user did to the columns still there: a width, visibility,
|
|
407
|
+
* pinning or group state the user changed stays unless the new definition changes that same property,
|
|
408
|
+
* and a column order the user changed stays while the definitions list the same columns in the same order.
|
|
409
|
+
*/
|
|
410
|
+
updateDefs(defs) {
|
|
411
|
+
const before = new Map(this.byId);
|
|
412
|
+
const declared = this.declared;
|
|
413
|
+
const order = this.leafNodes().map((n) => n.id);
|
|
414
|
+
this.setDefs(defs);
|
|
415
|
+
for (const [id, node] of this.byId) {
|
|
416
|
+
const old = before.get(id);
|
|
417
|
+
if (!old || !!old.children !== !!node.children) continue;
|
|
418
|
+
const a = old.def, b = node.def;
|
|
419
|
+
if (a.width === b.width && a.flex === b.flex && a.minWidth === b.minWidth && a.maxWidth === b.maxWidth) {
|
|
420
|
+
node.width = old.width;
|
|
421
|
+
node.flex = old.flex;
|
|
422
|
+
}
|
|
423
|
+
if ((a.hide ?? false) === (b.hide ?? false)) node.hide = old.hide;
|
|
424
|
+
if (a.pinned === b.pinned && old.parent?.def.pinned === node.parent?.def.pinned) node.pinned = old.pinned;
|
|
425
|
+
if (!!a.openByDefault === !!b.openByDefault) node.open = old.open;
|
|
426
|
+
}
|
|
427
|
+
const same3 = (x, y) => x.length === y.length && x.every((v, i) => v === y[i]);
|
|
428
|
+
if (same3(declared, this.declared) && !same3(order, declared)) this.applyState(order.map((colId) => ({ colId })));
|
|
429
|
+
else this.relayout();
|
|
430
|
+
}
|
|
139
431
|
/** Returns true when the computed layout changed. */
|
|
140
432
|
setContainerWidth(width) {
|
|
141
433
|
if (width === this.containerWidth) return false;
|
|
@@ -331,7 +623,7 @@ var FormulaError = class extends Error {
|
|
|
331
623
|
this.code = code;
|
|
332
624
|
}
|
|
333
625
|
};
|
|
334
|
-
var ERROR_RE = /^#[A-Z/0]+[!?]$/;
|
|
626
|
+
var ERROR_RE = /^#(?:[A-Z/0]+[!?]|N\/A)$/;
|
|
335
627
|
var isFormulaError = (v) => typeof v === "string" && ERROR_RE.test(v);
|
|
336
628
|
function tokenize(src) {
|
|
337
629
|
const out = [];
|
|
@@ -398,13 +690,17 @@ var bool = (v) => typeof v === "boolean" ? v : typeof v === "string" && /^(true|
|
|
|
398
690
|
function round(x, digits, mode = "near") {
|
|
399
691
|
const d = Math.trunc(digits);
|
|
400
692
|
const f = mode === "near" ? Math.round : mode === "up" ? Math.ceil : Math.floor;
|
|
401
|
-
const
|
|
693
|
+
const shift = (n, e) => {
|
|
694
|
+
const [m, p = "0"] = String(n).split("e");
|
|
695
|
+
return Number(`${m}e${Number(p) + e}`);
|
|
696
|
+
};
|
|
697
|
+
const r = shift(f(shift(Math.abs(x), d)), -d);
|
|
402
698
|
return x < 0 ? -r : r;
|
|
403
699
|
}
|
|
404
700
|
function compare(a, b) {
|
|
405
|
-
const
|
|
406
|
-
if (
|
|
407
|
-
if (a instanceof Date || b instanceof Date) return num(
|
|
701
|
+
const numeric2 = (v) => typeof v === "number" || typeof v === "boolean" || isBlankValue(v);
|
|
702
|
+
if (numeric2(a) && numeric2(b)) return num(a) - num(b);
|
|
703
|
+
if (a instanceof Date || b instanceof Date) return num(toDate2(a)) - num(toDate2(b));
|
|
408
704
|
return text(a).localeCompare(text(b), void 0, { sensitivity: "accent" });
|
|
409
705
|
}
|
|
410
706
|
var flat = (a) => a.flatMap((v) => Array.isArray(v) ? v : [v]);
|
|
@@ -414,7 +710,7 @@ function noErrors(list) {
|
|
|
414
710
|
if (bad !== void 0) throw new FormulaError(bad);
|
|
415
711
|
return list;
|
|
416
712
|
}
|
|
417
|
-
function
|
|
713
|
+
function toDate2(v) {
|
|
418
714
|
if (v instanceof Date) return v;
|
|
419
715
|
if (typeof v === "number") return new Date(v);
|
|
420
716
|
if (typeof v === "string") {
|
|
@@ -423,9 +719,9 @@ function toDate(v) {
|
|
|
423
719
|
}
|
|
424
720
|
throw new FormulaError("#VALUE!");
|
|
425
721
|
}
|
|
426
|
-
var
|
|
722
|
+
var pad2 = (n) => String(n).padStart(2, "0");
|
|
427
723
|
function ymd(d) {
|
|
428
|
-
return `${d.getFullYear()}-${
|
|
724
|
+
return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}`;
|
|
429
725
|
}
|
|
430
726
|
var DAY = 864e5;
|
|
431
727
|
var dayNumber = (d) => Math.round(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()) / DAY);
|
|
@@ -476,11 +772,11 @@ var FUNCS = {
|
|
|
476
772
|
},
|
|
477
773
|
MIN: (a) => {
|
|
478
774
|
const n = numsLoose(a);
|
|
479
|
-
return n.length ? Math.min(
|
|
775
|
+
return n.length ? n.reduce((m, v) => Math.min(m, v)) : 0;
|
|
480
776
|
},
|
|
481
777
|
MAX: (a) => {
|
|
482
778
|
const n = numsLoose(a);
|
|
483
|
-
return n.length ? Math.max(
|
|
779
|
+
return n.length ? n.reduce((m, v) => Math.max(m, v)) : 0;
|
|
484
780
|
},
|
|
485
781
|
MEDIAN: (a) => {
|
|
486
782
|
const n = numsLoose(a).sort((x, y) => x - y);
|
|
@@ -568,8 +864,8 @@ var FUNCS = {
|
|
|
568
864
|
const v = a[0], f = text(a[1]);
|
|
569
865
|
const dec = /0\.(0+)/.exec(f)?.[1].length ?? 0;
|
|
570
866
|
if (/[yd]/i.test(f)) {
|
|
571
|
-
const d =
|
|
572
|
-
return f.replace(/yyyy/gi, String(d.getFullYear())).replace(/mm/g,
|
|
867
|
+
const d = toDate2(v);
|
|
868
|
+
return f.replace(/yyyy/gi, String(d.getFullYear())).replace(/mm/g, pad2(d.getMonth() + 1)).replace(/dd/gi, pad2(d.getDate()));
|
|
573
869
|
}
|
|
574
870
|
const n = num(v);
|
|
575
871
|
const s = f.includes("%") ? (n * 100).toFixed(dec) + "%" : f.includes(",") ? n.toLocaleString("en-US", { minimumFractionDigits: dec, maximumFractionDigits: dec }) : n.toFixed(dec);
|
|
@@ -588,20 +884,20 @@ var FUNCS = {
|
|
|
588
884
|
TODAY: () => ymd(/* @__PURE__ */ new Date()),
|
|
589
885
|
NOW: () => /* @__PURE__ */ new Date(),
|
|
590
886
|
DATE: (a) => ymd(new Date(num(a[0]), num(a[1]) - 1, num(a[2]))),
|
|
591
|
-
YEAR: (a) =>
|
|
592
|
-
MONTH: (a) =>
|
|
593
|
-
DAY: (a) =>
|
|
594
|
-
WEEKDAY: (a) =>
|
|
595
|
-
DAYS: (a) => dayNumber(
|
|
887
|
+
YEAR: (a) => toDate2(a[0]).getFullYear(),
|
|
888
|
+
MONTH: (a) => toDate2(a[0]).getMonth() + 1,
|
|
889
|
+
DAY: (a) => toDate2(a[0]).getDate(),
|
|
890
|
+
WEEKDAY: (a) => toDate2(a[0]).getDay() + 1,
|
|
891
|
+
DAYS: (a) => dayNumber(toDate2(a[0])) - dayNumber(toDate2(a[1])),
|
|
596
892
|
EDATE: (a) => {
|
|
597
|
-
const d =
|
|
893
|
+
const d = toDate2(a[0]);
|
|
598
894
|
const day = d.getDate();
|
|
599
895
|
const r = new Date(d.getFullYear(), d.getMonth() + num(a[1]), 1);
|
|
600
896
|
r.setDate(Math.min(day, new Date(r.getFullYear(), r.getMonth() + 1, 0).getDate()));
|
|
601
897
|
return ymd(r);
|
|
602
898
|
},
|
|
603
899
|
EOMONTH: (a) => {
|
|
604
|
-
const d =
|
|
900
|
+
const d = toDate2(a[0]);
|
|
605
901
|
return ymd(new Date(d.getFullYear(), d.getMonth() + num(a[1]) + 1, 0));
|
|
606
902
|
},
|
|
607
903
|
// over columns
|
|
@@ -1046,7 +1342,7 @@ var toNumber = (s) => {
|
|
|
1046
1342
|
const n = Number(s.replace(/[,\s₩$€¥]/g, ""));
|
|
1047
1343
|
return s.trim() !== "" && Number.isFinite(n) ? n : null;
|
|
1048
1344
|
};
|
|
1049
|
-
var
|
|
1345
|
+
var toDateKey2 = (s) => {
|
|
1050
1346
|
const m = /^(\d{4})[-./]\s*(\d{1,2})[-./]\s*(\d{1,2})\.?$/.exec(s.trim());
|
|
1051
1347
|
if (!m) return null;
|
|
1052
1348
|
const y = Number(m[1]), mo = Number(m[2]), d = Number(m[3]);
|
|
@@ -1068,11 +1364,11 @@ function parseFilterText(type, text2) {
|
|
|
1068
1364
|
return n === null ? void 0 : { type: "number", op: op2 ? op2[1] : "eq", value: n };
|
|
1069
1365
|
}
|
|
1070
1366
|
if (range) {
|
|
1071
|
-
const a =
|
|
1367
|
+
const a = toDateKey2(range[1]), b = toDateKey2(range[2]);
|
|
1072
1368
|
return !a || !b ? void 0 : { type: "date", op: "between", value: a < b ? a : b, valueTo: a < b ? b : a };
|
|
1073
1369
|
}
|
|
1074
1370
|
const op = DATE_OPS.find(([p]) => t.startsWith(p));
|
|
1075
|
-
const d =
|
|
1371
|
+
const d = toDateKey2(op ? t.slice(op[0].length) : t);
|
|
1076
1372
|
return d ? { type: "date", op: op ? op[1] : "equals", value: d } : void 0;
|
|
1077
1373
|
}
|
|
1078
1374
|
function filterText(model) {
|
|
@@ -1155,325 +1451,93 @@ var FilterRow = class {
|
|
|
1155
1451
|
const def = this.grid.columns.getNode(select.dataset.col)?.def;
|
|
1156
1452
|
if (!def) continue;
|
|
1157
1453
|
const v = select.value;
|
|
1158
|
-
this.fillOptions(select, def);
|
|
1159
|
-
if (v && ![...select.options].some((o) => o.value === v)) {
|
|
1160
|
-
const o = el("option", "", { value: v });
|
|
1161
|
-
o.textContent = String(JSON.parse(v));
|
|
1162
|
-
select.append(o);
|
|
1163
|
-
}
|
|
1164
|
-
select.value = v;
|
|
1165
|
-
}
|
|
1166
|
-
}
|
|
1167
|
-
}
|
|
1168
|
-
fillOptions(select, def) {
|
|
1169
|
-
const all2 = el("option", "", { value: "" });
|
|
1170
|
-
all2.textContent = this.grid.store.state.locale.selectAll;
|
|
1171
|
-
const opts = this.distinct(def).map((v) => {
|
|
1172
|
-
const o = el("option", "", { value: JSON.stringify(v) });
|
|
1173
|
-
o.textContent = v === true ? "\u2713" : v === false ? "\u2717" : String(v);
|
|
1174
|
-
return o;
|
|
1175
|
-
});
|
|
1176
|
-
select.replaceChildren(all2, ...opts);
|
|
1177
|
-
}
|
|
1178
|
-
cell(c, h) {
|
|
1179
|
-
const cell = el("div", "fg-filter-cell");
|
|
1180
|
-
cell.style.left = `${c.left}px`;
|
|
1181
|
-
cell.style.width = `${c.width}px`;
|
|
1182
|
-
cell.style.height = `${h}px`;
|
|
1183
|
-
const type = isSystemCol(c.id) ? null : filterTypeOf(c.def);
|
|
1184
|
-
if (!type) return cell;
|
|
1185
|
-
const loc = this.grid.store.state.locale;
|
|
1186
|
-
const label = `${loc.filter}: ${c.def.header ?? c.def.field ?? c.id}`;
|
|
1187
|
-
if (type === "set") {
|
|
1188
|
-
const select = el("select", "fg-filter-input", { "aria-label": label });
|
|
1189
|
-
select.dataset.col = c.id;
|
|
1190
|
-
this.fillOptions(select, c.def);
|
|
1191
|
-
select.addEventListener("change", () => {
|
|
1192
|
-
this.grid.setFilter(c.id, select.value ? { type: "set", values: [JSON.parse(select.value)] } : null);
|
|
1193
|
-
});
|
|
1194
|
-
cell.append(select);
|
|
1195
|
-
return cell;
|
|
1196
|
-
}
|
|
1197
|
-
const input = el("input", "fg-filter-input", { type: "text", "aria-label": label, spellcheck: "false", autocomplete: "off" });
|
|
1198
|
-
input.dataset.col = c.id;
|
|
1199
|
-
if (type === "number") input.placeholder = ">0, 10~20";
|
|
1200
|
-
if (type === "date") input.placeholder = ">2026-01-01";
|
|
1201
|
-
const apply = () => {
|
|
1202
|
-
const model = parseFilterText(type, input.value);
|
|
1203
|
-
input.classList.toggle("fg-filter-bad", model === void 0);
|
|
1204
|
-
if (model === void 0) return;
|
|
1205
|
-
this.grid.setFilter(c.id, model);
|
|
1206
|
-
};
|
|
1207
|
-
input.addEventListener("input", () => {
|
|
1208
|
-
clearTimeout(this.timers.get(c.id));
|
|
1209
|
-
this.timers.set(c.id, window.setTimeout(apply, 200));
|
|
1210
|
-
});
|
|
1211
|
-
input.addEventListener("keydown", (e) => {
|
|
1212
|
-
e.stopPropagation();
|
|
1213
|
-
if (e.key === "Enter") {
|
|
1214
|
-
clearTimeout(this.timers.get(c.id));
|
|
1215
|
-
apply();
|
|
1216
|
-
} else if (e.key === "Escape") {
|
|
1217
|
-
input.value = "";
|
|
1218
|
-
clearTimeout(this.timers.get(c.id));
|
|
1219
|
-
apply();
|
|
1220
|
-
}
|
|
1221
|
-
});
|
|
1222
|
-
cell.append(input);
|
|
1223
|
-
return cell;
|
|
1224
|
-
}
|
|
1225
|
-
/** Values for a set box: distinct, sorted, at most 200. */
|
|
1226
|
-
distinct(def) {
|
|
1227
|
-
const seen2 = /* @__PURE__ */ new Map();
|
|
1228
|
-
for (const row of this.grid.store.state.data) {
|
|
1229
|
-
const v = getCellValue(def, row);
|
|
1230
|
-
if (v === null || v === void 0 || v === "") continue;
|
|
1231
|
-
const k = JSON.stringify(v);
|
|
1232
|
-
if (!seen2.has(k)) seen2.set(k, v);
|
|
1233
|
-
if (seen2.size >= 200) break;
|
|
1234
|
-
}
|
|
1235
|
-
return [...seen2.values()].sort((a, b) => String(a).localeCompare(String(b)));
|
|
1236
|
-
}
|
|
1237
|
-
setTransform(x) {
|
|
1238
|
-
this.inners.center.style.transform = `translateX(${-x}px)`;
|
|
1239
|
-
}
|
|
1240
|
-
destroy() {
|
|
1241
|
-
for (const t of this.timers.values()) clearTimeout(t);
|
|
1242
|
-
for (const r of REGIONS2) this.inners[r].remove();
|
|
1243
|
-
}
|
|
1244
|
-
};
|
|
1245
|
-
|
|
1246
|
-
// src/rows/filters.ts
|
|
1247
|
-
function isBlank(v) {
|
|
1248
|
-
return v == null || typeof v === "string" && v.trim() === "" || typeof v === "number" && Number.isNaN(v);
|
|
1249
|
-
}
|
|
1250
|
-
var pad2 = (n) => String(n).padStart(2, "0");
|
|
1251
|
-
function toDateKey2(v) {
|
|
1252
|
-
if (isBlank(v)) return null;
|
|
1253
|
-
if (typeof v === "string" && /^\d{4}-\d{2}-\d{2}$/.test(v)) return v;
|
|
1254
|
-
const d = v instanceof Date ? v : new Date(v);
|
|
1255
|
-
if (Number.isNaN(d.getTime())) return null;
|
|
1256
|
-
return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}`;
|
|
1257
|
-
}
|
|
1258
|
-
function setKey(v) {
|
|
1259
|
-
if (isBlank(v)) return "";
|
|
1260
|
-
if (v instanceof Date) return toDateKey2(v) ?? "";
|
|
1261
|
-
return String(v);
|
|
1262
|
-
}
|
|
1263
|
-
var all = () => true;
|
|
1264
|
-
function buildPredicate(model) {
|
|
1265
|
-
switch (model.type) {
|
|
1266
|
-
case "combined": {
|
|
1267
|
-
const parts = (Array.isArray(model.conditions) ? model.conditions : []).map(buildPredicate);
|
|
1268
|
-
if (!parts.length) return all;
|
|
1269
|
-
return model.operator === "or" ? (v) => parts.some((p) => p(v)) : (v) => parts.every((p) => p(v));
|
|
1270
|
-
}
|
|
1271
|
-
case "text": {
|
|
1272
|
-
const q = (model.value ?? "").toLowerCase();
|
|
1273
|
-
const s = (v) => isBlank(v) ? "" : String(v).toLowerCase();
|
|
1274
|
-
switch (model.op) {
|
|
1275
|
-
case "contains":
|
|
1276
|
-
return (v) => s(v).includes(q);
|
|
1277
|
-
case "equals":
|
|
1278
|
-
return (v) => s(v) === q;
|
|
1279
|
-
case "notEquals":
|
|
1280
|
-
return (v) => s(v) !== q;
|
|
1281
|
-
case "startsWith":
|
|
1282
|
-
return (v) => s(v).startsWith(q);
|
|
1283
|
-
case "endsWith":
|
|
1284
|
-
return (v) => s(v).endsWith(q);
|
|
1285
|
-
case "blank":
|
|
1286
|
-
return isBlank;
|
|
1287
|
-
case "notBlank":
|
|
1288
|
-
return (v) => !isBlank(v);
|
|
1289
|
-
}
|
|
1290
|
-
return all;
|
|
1291
|
-
}
|
|
1292
|
-
case "number": {
|
|
1293
|
-
const n = (v) => isBlank(v) ? NaN : Number(v);
|
|
1294
|
-
if (model.op === "blank") return (v) => Number.isNaN(n(v));
|
|
1295
|
-
if (model.op === "notBlank") return (v) => !Number.isNaN(n(v));
|
|
1296
|
-
const a = model.value;
|
|
1297
|
-
if (a == null || Number.isNaN(a)) return all;
|
|
1298
|
-
switch (model.op) {
|
|
1299
|
-
case "eq":
|
|
1300
|
-
return (v) => n(v) === a;
|
|
1301
|
-
case "ne":
|
|
1302
|
-
return (v) => n(v) !== a;
|
|
1303
|
-
case "lt":
|
|
1304
|
-
return (v) => n(v) < a;
|
|
1305
|
-
case "lte":
|
|
1306
|
-
return (v) => n(v) <= a;
|
|
1307
|
-
case "gt":
|
|
1308
|
-
return (v) => n(v) > a;
|
|
1309
|
-
case "gte":
|
|
1310
|
-
return (v) => n(v) >= a;
|
|
1311
|
-
case "between": {
|
|
1312
|
-
const b = model.valueTo;
|
|
1313
|
-
if (b == null || Number.isNaN(b)) return all;
|
|
1314
|
-
const lo = Math.min(a, b), hi = Math.max(a, b);
|
|
1315
|
-
return (v) => {
|
|
1316
|
-
const x = n(v);
|
|
1317
|
-
return x >= lo && x <= hi;
|
|
1318
|
-
};
|
|
1319
|
-
}
|
|
1320
|
-
}
|
|
1321
|
-
return all;
|
|
1322
|
-
}
|
|
1323
|
-
case "date": {
|
|
1324
|
-
if (model.op === "blank") return (v) => toDateKey2(v) === null;
|
|
1325
|
-
if (model.op === "notBlank") return (v) => toDateKey2(v) !== null;
|
|
1326
|
-
const a = model.value;
|
|
1327
|
-
if (!a) return all;
|
|
1328
|
-
const k = (v) => toDateKey2(v);
|
|
1329
|
-
switch (model.op) {
|
|
1330
|
-
case "equals":
|
|
1331
|
-
return (v) => k(v) === a;
|
|
1332
|
-
case "before":
|
|
1333
|
-
return (v) => {
|
|
1334
|
-
const x = k(v);
|
|
1335
|
-
return x !== null && x < a;
|
|
1336
|
-
};
|
|
1337
|
-
case "after":
|
|
1338
|
-
return (v) => {
|
|
1339
|
-
const x = k(v);
|
|
1340
|
-
return x !== null && x > a;
|
|
1341
|
-
};
|
|
1342
|
-
case "between": {
|
|
1343
|
-
const b = model.valueTo;
|
|
1344
|
-
if (!b) return all;
|
|
1345
|
-
const lo = a < b ? a : b, hi = a < b ? b : a;
|
|
1346
|
-
return (v) => {
|
|
1347
|
-
const x = k(v);
|
|
1348
|
-
return x !== null && x >= lo && x <= hi;
|
|
1349
|
-
};
|
|
1350
|
-
}
|
|
1351
|
-
}
|
|
1352
|
-
return all;
|
|
1353
|
-
}
|
|
1354
|
-
case "set": {
|
|
1355
|
-
const allowed = new Set(model.values.map(setKey));
|
|
1356
|
-
return (v) => allowed.has(setKey(v));
|
|
1357
|
-
}
|
|
1358
|
-
}
|
|
1359
|
-
return all;
|
|
1360
|
-
}
|
|
1361
|
-
function quickTokens(q) {
|
|
1362
|
-
const t = q.trim().toLowerCase();
|
|
1363
|
-
return t ? t.split(/\s+/) : [];
|
|
1364
|
-
}
|
|
1365
|
-
function matchesQuick(tokens, text2) {
|
|
1366
|
-
return tokens.every((t) => text2.includes(t));
|
|
1367
|
-
}
|
|
1368
|
-
function buildAdvanced(f, columnDef, valueOf) {
|
|
1369
|
-
const parts = [];
|
|
1370
|
-
for (const c of Array.isArray(f?.conditions) ? f.conditions : []) {
|
|
1371
|
-
if (!c || typeof c !== "object") continue;
|
|
1372
|
-
if ("conditions" in c) {
|
|
1373
|
-
const sub = buildAdvanced(c, columnDef, valueOf);
|
|
1374
|
-
if (sub) parts.push(sub);
|
|
1375
|
-
continue;
|
|
1454
|
+
this.fillOptions(select, def);
|
|
1455
|
+
if (v && ![...select.options].some((o) => o.value === v)) {
|
|
1456
|
+
const o = el("option", "", { value: v });
|
|
1457
|
+
o.textContent = String(JSON.parse(v));
|
|
1458
|
+
select.append(o);
|
|
1459
|
+
}
|
|
1460
|
+
select.value = v;
|
|
1461
|
+
}
|
|
1376
1462
|
}
|
|
1377
|
-
const def = columnDef(c.colId);
|
|
1378
|
-
if (!def || !c.model) continue;
|
|
1379
|
-
const pred = buildPredicate(c.model);
|
|
1380
|
-
parts.push((row) => pred(valueOf(def, row)));
|
|
1381
1463
|
}
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
if (p.startsWith("currency:")) return { style: "currency", currency: p.slice(9) };
|
|
1392
|
-
if (!/^[#0,]*(\.[#0]+)?%?$/.test(p) || !/[#0]/.test(p)) return null;
|
|
1393
|
-
const pct = p.endsWith("%");
|
|
1394
|
-
const body = pct ? p.slice(0, -1) : p;
|
|
1395
|
-
const frac = body.includes(".") ? body.split(".")[1] : "";
|
|
1396
|
-
return {
|
|
1397
|
-
useGrouping: body.includes(","),
|
|
1398
|
-
minimumFractionDigits: (frac.match(/0/g) ?? []).length,
|
|
1399
|
-
maximumFractionDigits: frac.length,
|
|
1400
|
-
...pct ? { style: "percent" } : {}
|
|
1401
|
-
};
|
|
1402
|
-
}
|
|
1403
|
-
var DATE_PRESETS = {
|
|
1404
|
-
date: { year: "numeric", month: "2-digit", day: "2-digit" },
|
|
1405
|
-
datetime: { year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit" },
|
|
1406
|
-
time: { hour: "2-digit", minute: "2-digit" },
|
|
1407
|
-
short: { dateStyle: "short" },
|
|
1408
|
-
medium: { dateStyle: "medium" },
|
|
1409
|
-
long: { dateStyle: "long" }
|
|
1410
|
-
};
|
|
1411
|
-
function toDate2(v) {
|
|
1412
|
-
if (isBlank(v)) return null;
|
|
1413
|
-
if (v instanceof Date) return Number.isNaN(v.getTime()) ? null : v;
|
|
1414
|
-
if (typeof v === "string") {
|
|
1415
|
-
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(v);
|
|
1416
|
-
if (m) return new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]));
|
|
1464
|
+
fillOptions(select, def) {
|
|
1465
|
+
const all2 = el("option", "", { value: "" });
|
|
1466
|
+
all2.textContent = this.grid.store.state.locale.selectAll;
|
|
1467
|
+
const opts = this.distinct(def).map((v) => {
|
|
1468
|
+
const o = el("option", "", { value: JSON.stringify(v) });
|
|
1469
|
+
o.textContent = v === true ? "\u2713" : v === false ? "\u2717" : String(v);
|
|
1470
|
+
return o;
|
|
1471
|
+
});
|
|
1472
|
+
select.replaceChildren(all2, ...opts);
|
|
1417
1473
|
}
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
}
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
};
|
|
1474
|
+
cell(c, h) {
|
|
1475
|
+
const cell = el("div", "fg-filter-cell");
|
|
1476
|
+
cell.style.left = `${c.left}px`;
|
|
1477
|
+
cell.style.width = `${c.width}px`;
|
|
1478
|
+
cell.style.height = `${h}px`;
|
|
1479
|
+
const type = isSystemCol(c.id) ? null : filterTypeOf(c.def);
|
|
1480
|
+
if (!type) return cell;
|
|
1481
|
+
const loc = this.grid.store.state.locale;
|
|
1482
|
+
const label = `${loc.filter}: ${c.def.header ?? c.def.field ?? c.id}`;
|
|
1483
|
+
if (type === "set") {
|
|
1484
|
+
const select = el("select", "fg-filter-input", { "aria-label": label });
|
|
1485
|
+
select.dataset.col = c.id;
|
|
1486
|
+
this.fillOptions(select, c.def);
|
|
1487
|
+
select.addEventListener("change", () => {
|
|
1488
|
+
this.grid.setFilter(c.id, select.value ? { type: "set", values: [JSON.parse(select.value)] } : null);
|
|
1489
|
+
});
|
|
1490
|
+
cell.append(select);
|
|
1491
|
+
return cell;
|
|
1437
1492
|
}
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1493
|
+
const input = el("input", "fg-filter-input", { type: "text", "aria-label": label, spellcheck: "false", autocomplete: "off" });
|
|
1494
|
+
input.dataset.col = c.id;
|
|
1495
|
+
if (type === "number") input.placeholder = ">0, 10~20";
|
|
1496
|
+
if (type === "date") input.placeholder = ">2026-01-01";
|
|
1497
|
+
const apply = () => {
|
|
1498
|
+
const model = parseFilterText(type, input.value);
|
|
1499
|
+
input.classList.toggle("fg-filter-bad", model === void 0);
|
|
1500
|
+
if (model === void 0) return;
|
|
1501
|
+
this.grid.setFilter(c.id, model);
|
|
1502
|
+
};
|
|
1503
|
+
input.addEventListener("input", () => {
|
|
1504
|
+
clearTimeout(this.timers.get(c.id));
|
|
1505
|
+
this.timers.set(c.id, window.setTimeout(apply, 200));
|
|
1506
|
+
});
|
|
1507
|
+
input.addEventListener("keydown", (e) => {
|
|
1508
|
+
e.stopPropagation();
|
|
1509
|
+
if (e.key === "Enter") {
|
|
1510
|
+
clearTimeout(this.timers.get(c.id));
|
|
1511
|
+
apply();
|
|
1512
|
+
} else if (e.key === "Escape") {
|
|
1513
|
+
input.value = "";
|
|
1514
|
+
clearTimeout(this.timers.get(c.id));
|
|
1515
|
+
apply();
|
|
1448
1516
|
}
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1517
|
+
});
|
|
1518
|
+
cell.append(input);
|
|
1519
|
+
return cell;
|
|
1520
|
+
}
|
|
1521
|
+
/** Values for a set box: distinct, sorted, at most 200. */
|
|
1522
|
+
distinct(def) {
|
|
1523
|
+
const seen2 = /* @__PURE__ */ new Map();
|
|
1524
|
+
for (const row of this.grid.store.state.data) {
|
|
1525
|
+
const v = getCellValue(def, row);
|
|
1526
|
+
if (v === null || v === void 0 || v === "") continue;
|
|
1527
|
+
const k = JSON.stringify(v);
|
|
1528
|
+
if (!seen2.has(k)) seen2.set(k, v);
|
|
1529
|
+
if (seen2.size >= 200) break;
|
|
1459
1530
|
}
|
|
1460
|
-
|
|
1461
|
-
return (v) => v === true ? "\u2713" : v === false ? "\u2717" : "";
|
|
1462
|
-
default:
|
|
1463
|
-
return (v) => isBlank(v) ? "" : v instanceof Date ? v.toISOString() : String(v);
|
|
1531
|
+
return [...seen2.values()].sort((a, b) => String(a).localeCompare(String(b)));
|
|
1464
1532
|
}
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
if (def.sparkline) return (v) => Array.isArray(v) ? v.join(", ") : v == null ? "" : String(v);
|
|
1468
|
-
const f = def.format;
|
|
1469
|
-
const key = `${locale}|${def.type ?? "text"}|${typeof f === "string" ? f : JSON.stringify(f ?? null)}`;
|
|
1470
|
-
let fmt = cache.get(key);
|
|
1471
|
-
if (!fmt) {
|
|
1472
|
-
fmt = create(def.type, f, locale);
|
|
1473
|
-
cache.set(key, fmt);
|
|
1533
|
+
setTransform(x) {
|
|
1534
|
+
this.inners.center.style.transform = `translateX(${-x}px)`;
|
|
1474
1535
|
}
|
|
1475
|
-
|
|
1476
|
-
|
|
1536
|
+
destroy() {
|
|
1537
|
+
for (const t of this.timers.values()) clearTimeout(t);
|
|
1538
|
+
for (const r of REGIONS2) this.inners[r].remove();
|
|
1539
|
+
}
|
|
1540
|
+
};
|
|
1477
1541
|
|
|
1478
1542
|
// src/summary.ts
|
|
1479
1543
|
function numbers(values) {
|
|
@@ -1492,7 +1556,9 @@ function extreme(values, pickMax) {
|
|
|
1492
1556
|
}
|
|
1493
1557
|
const nums = numbers(values);
|
|
1494
1558
|
if (!nums.length) return null;
|
|
1495
|
-
|
|
1559
|
+
let out = nums[0];
|
|
1560
|
+
for (const n of nums) if (pickMax ? n > out : n < out) out = n;
|
|
1561
|
+
return out;
|
|
1496
1562
|
}
|
|
1497
1563
|
function aggregate(agg, values, rows) {
|
|
1498
1564
|
if (typeof agg === "function") return agg(values, rows);
|
|
@@ -1559,7 +1625,7 @@ function computeSpans(rows, def) {
|
|
|
1559
1625
|
}
|
|
1560
1626
|
return { start, end };
|
|
1561
1627
|
}
|
|
1562
|
-
const
|
|
1628
|
+
const same3 = typeof def.merge === "function" ? (a, b) => {
|
|
1563
1629
|
try {
|
|
1564
1630
|
return !!def.merge(getCellValue(def, rows[a]), getCellValue(def, rows[b]), rows[a], rows[b]);
|
|
1565
1631
|
} catch {
|
|
@@ -1574,7 +1640,7 @@ function computeSpans(rows, def) {
|
|
|
1574
1640
|
})();
|
|
1575
1641
|
let s = 0;
|
|
1576
1642
|
for (let i = 1; i <= n; i++) {
|
|
1577
|
-
if (i < n &&
|
|
1643
|
+
if (i < n && same3(i - 1, i)) continue;
|
|
1578
1644
|
for (let k = s; k < i; k++) {
|
|
1579
1645
|
start[k] = s;
|
|
1580
1646
|
end[k] = i - 1;
|
|
@@ -1652,6 +1718,17 @@ var Renderer = class {
|
|
|
1652
1718
|
this.activeCellId = "";
|
|
1653
1719
|
this.idBase = `fg${Math.random().toString(36).slice(2, 8)}`;
|
|
1654
1720
|
this.painted = { left: /* @__PURE__ */ new Map(), center: /* @__PURE__ */ new Map(), right: /* @__PURE__ */ new Map() };
|
|
1721
|
+
/**
|
|
1722
|
+
* Scroller geometry read once per frame, before any cell is written: reading it after
|
|
1723
|
+
* writing cells would make the browser lay out every painted cell again.
|
|
1724
|
+
*/
|
|
1725
|
+
this.geo = { top: 0, left: 0, cw: 0, ch: 0, ow: 0, oh: 0 };
|
|
1726
|
+
/** Sizes written before the rows last frame; when they are unchanged the geometry read at the start still holds. */
|
|
1727
|
+
this.layoutSig = "";
|
|
1728
|
+
/** Row indexes painted this frame: the only ones autoRowHeight has to measure again. */
|
|
1729
|
+
this.touched = /* @__PURE__ */ new Set();
|
|
1730
|
+
/** Measured heights were dropped: measure every painted row next time, not only the repainted ones. */
|
|
1731
|
+
this.measureAll = false;
|
|
1655
1732
|
this.pool = { left: [], center: [], right: [] };
|
|
1656
1733
|
this.frame = 0;
|
|
1657
1734
|
this.headerDirty = true;
|
|
@@ -1663,6 +1740,7 @@ var Renderer = class {
|
|
|
1663
1740
|
this.summaryEl = null;
|
|
1664
1741
|
this.summaryPanes = null;
|
|
1665
1742
|
this.summaryInners = null;
|
|
1743
|
+
this.summaryKey = null;
|
|
1666
1744
|
/** Summary rows painted inside the scrolling canvases (unpinned mode). */
|
|
1667
1745
|
this.canvasSummary = { left: [], center: [], right: [] };
|
|
1668
1746
|
/** Height of unpinned top summary rows; data rows are shifted down by it. */
|
|
@@ -1691,7 +1769,7 @@ var Renderer = class {
|
|
|
1691
1769
|
if (this.frame) cancelAnimationFrame(this.frame);
|
|
1692
1770
|
this.frame = 0;
|
|
1693
1771
|
this.render();
|
|
1694
|
-
this.grid.events.emit("scroll", this.getScroll());
|
|
1772
|
+
this.grid.events.emit("scroll", this.cards ? this.getScroll() : { top: this.geo.top, left: this.geo.left });
|
|
1695
1773
|
};
|
|
1696
1774
|
this.onSideWheel = (e) => {
|
|
1697
1775
|
if (!e.deltaY) return;
|
|
@@ -1757,6 +1835,10 @@ var Renderer = class {
|
|
|
1757
1835
|
get activeScroller() {
|
|
1758
1836
|
return this.cards?.scroller ?? this.scroller;
|
|
1759
1837
|
}
|
|
1838
|
+
readGeo() {
|
|
1839
|
+
const s = this.scroller;
|
|
1840
|
+
this.geo = { top: s.scrollTop, left: s.scrollLeft, cw: s.clientWidth, ch: s.clientHeight, ow: s.offsetWidth, oh: s.offsetHeight };
|
|
1841
|
+
}
|
|
1760
1842
|
getScroll() {
|
|
1761
1843
|
const s = this.activeScroller;
|
|
1762
1844
|
return { top: s.scrollTop, left: s.scrollLeft };
|
|
@@ -1804,7 +1886,8 @@ var Renderer = class {
|
|
|
1804
1886
|
render() {
|
|
1805
1887
|
const { columns, rowModel, store, options } = this.grid;
|
|
1806
1888
|
const { rowHeight: rh, headerHeight: hh } = this.opts;
|
|
1807
|
-
|
|
1889
|
+
this.readGeo();
|
|
1890
|
+
const vScrollbar = this.geo.ow - this.geo.cw;
|
|
1808
1891
|
if (columns.setContainerWidth(Math.max(0, this.main.clientWidth - vScrollbar))) {
|
|
1809
1892
|
this.headerDirty = true;
|
|
1810
1893
|
this.version++;
|
|
@@ -1845,7 +1928,12 @@ var Renderer = class {
|
|
|
1845
1928
|
this.root.classList.add("fg-auto-height");
|
|
1846
1929
|
this.bodyEl.style.height = `${this.rowsHeight(count) + this.topOffset + bottomExtra + Math.max(0, this.scroller.offsetHeight - this.scroller.clientHeight)}px`;
|
|
1847
1930
|
}
|
|
1848
|
-
const
|
|
1931
|
+
const sig = `${headerHeight}|${REGIONS3.map((r) => columns.regionWidth(r)).join(",")}|${this.rowsHeight(count) + this.topOffset + bottomExtra}|${summary && sumPinned ? sumPos : ""}|${sumRows}|${options.autoHeight ? 1 : 0}`;
|
|
1932
|
+
if (sig !== this.layoutSig) {
|
|
1933
|
+
this.layoutSig = sig;
|
|
1934
|
+
this.readGeo();
|
|
1935
|
+
}
|
|
1936
|
+
const hScrollbar = this.geo.oh - this.geo.ch;
|
|
1849
1937
|
this.bodyPanes.left.style.marginBottom = this.bodyPanes.right.style.marginBottom = `${hScrollbar}px`;
|
|
1850
1938
|
const g = this.gridEl;
|
|
1851
1939
|
g.setAttribute("role", options.treeData || this.grid.getGroupBy().length ? "treegrid" : "grid");
|
|
@@ -1868,7 +1956,7 @@ var Renderer = class {
|
|
|
1868
1956
|
this.orderSections(summary && sumPinned ? sumPos : null);
|
|
1869
1957
|
this.paintOverlay(count);
|
|
1870
1958
|
this.overlay.style.top = `${headerHeight}px`;
|
|
1871
|
-
const { scrollTop, scrollLeft } = this.
|
|
1959
|
+
const { top: scrollTop, left: scrollLeft } = this.geo;
|
|
1872
1960
|
this.bodyPanes.left.scrollTop = this.bodyPanes.right.scrollTop = scrollTop;
|
|
1873
1961
|
this.headerInners.center.style.transform = `translateX(${-scrollLeft}px)`;
|
|
1874
1962
|
this.filterRow?.setTransform(scrollLeft);
|
|
@@ -1951,9 +2039,12 @@ var Renderer = class {
|
|
|
1951
2039
|
const s = section ?? new Section(`fg-pinned-rows fg-pinned-${position}`);
|
|
1952
2040
|
const { columns } = this.grid;
|
|
1953
2041
|
const rh = this.opts.rowHeight;
|
|
1954
|
-
s.size(columns, rows.length * rh, this.
|
|
2042
|
+
s.size(columns, rows.length * rh, this.geo.ow - this.geo.cw);
|
|
1955
2043
|
const center = columns.region("center");
|
|
1956
|
-
const cr = colRange(center.map((c) => c.left), center.map((c) => c.width), this.
|
|
2044
|
+
const cr = colRange(center.map((c) => c.left), center.map((c) => c.width), this.geo.left, this.geo.cw, COL_OVERSCAN);
|
|
2045
|
+
const key = [rows, rows.length, this.version, this.grid.valuesVersion, cr.first, cr.last, rh, this.grid.store.state.locale.code];
|
|
2046
|
+
if (section && sameKey(key, s.key)) return s;
|
|
2047
|
+
s.key = key;
|
|
1957
2048
|
for (const r of REGIONS3) {
|
|
1958
2049
|
const cols = r === "center" ? center.slice(cr.first, cr.last + 1) : columns.region(r);
|
|
1959
2050
|
s.inners[r].replaceChildren(...rows.map((data, k) => {
|
|
@@ -1984,12 +2075,12 @@ var Renderer = class {
|
|
|
1984
2075
|
const { grid } = this;
|
|
1985
2076
|
const state = grid.store.state;
|
|
1986
2077
|
const total = grid.rowModel.server ? count : grid.viewRows().length;
|
|
1987
|
-
const key = `${this.version}|${count}|${total}|${state.selected.size}|${JSON.stringify(state.range)}|${state.locale.code}`;
|
|
2078
|
+
const key = `${this.version}.${grid.valuesVersion}|${count}|${total}|${state.selected.size}|${JSON.stringify(state.range)}|${state.locale.code}`;
|
|
1988
2079
|
if (this.statusEl && key === this.statusKey) return;
|
|
1989
2080
|
this.statusKey = key;
|
|
1990
2081
|
this.statusEl ?? (this.statusEl = el("div", "fg-statusbar", { role: "status", "aria-live": "polite" }));
|
|
1991
2082
|
const loc = state.locale;
|
|
1992
|
-
const nf =
|
|
2083
|
+
const nf = numberFormat(loc.code, 2);
|
|
1993
2084
|
const left = el("span", "fg-status-rows");
|
|
1994
2085
|
left.textContent = (count === total ? loc.statusRows.replace("{n}", nf.format(total)) : loc.statusFiltered.replace("{shown}", nf.format(count)).replace("{total}", nf.format(total))) + (state.selected.size ? ` \xB7 ${loc.statusSelected.replace("{n}", nf.format(state.selected.size))}` : "");
|
|
1995
2086
|
const right = el("span", "fg-status-aggs");
|
|
@@ -2098,27 +2189,39 @@ var Renderer = class {
|
|
|
2098
2189
|
return cell;
|
|
2099
2190
|
}
|
|
2100
2191
|
selectAllBox(locale) {
|
|
2101
|
-
const rows = this.grid.selectableRows();
|
|
2102
|
-
const sel = this.grid.store.state.selected;
|
|
2103
|
-
let n = 0;
|
|
2104
|
-
for (const r of rows) if (sel.has(r.id)) n++;
|
|
2105
2192
|
const box = el("input", "fg-checkbox fg-select-all", { type: "checkbox", tabindex: "-1", "aria-label": locale.selectAllRows });
|
|
2106
|
-
box
|
|
2107
|
-
box.indeterminate = n > 0 && n < rows.length;
|
|
2193
|
+
this.paintSelectAll(box);
|
|
2108
2194
|
box.hidden = this.grid.options.rowSelection !== "multiple" || this.grid.options.headerCheckbox === false;
|
|
2109
2195
|
return box;
|
|
2110
2196
|
}
|
|
2197
|
+
paintSelectAll(box) {
|
|
2198
|
+
const ids = this.grid.selectableIds();
|
|
2199
|
+
let n = 0;
|
|
2200
|
+
for (const id of this.grid.store.state.selected) if (ids.has(id)) n++;
|
|
2201
|
+
box.checked = ids.size > 0 && n === ids.size;
|
|
2202
|
+
box.indeterminate = n > 0 && n < ids.size;
|
|
2203
|
+
}
|
|
2204
|
+
/** Only the selection changed: update the select-all box instead of building the header again. */
|
|
2205
|
+
selectionChanged() {
|
|
2206
|
+
const boxes = this.headerEl.querySelectorAll(".fg-select-all");
|
|
2207
|
+
if (!boxes.length) return;
|
|
2208
|
+
for (const box of boxes) this.paintSelectAll(box);
|
|
2209
|
+
}
|
|
2111
2210
|
renderRows(count) {
|
|
2112
2211
|
const { columns, rowModel, store } = this.grid;
|
|
2113
2212
|
const { selected, focus } = store.state;
|
|
2114
2213
|
const allBounds = this.grid.allRangeBounds();
|
|
2115
2214
|
const spans = this.grid.mergeSpans();
|
|
2116
|
-
const
|
|
2117
|
-
const
|
|
2215
|
+
const { geo } = this;
|
|
2216
|
+
const top = Math.max(0, geo.top - this.topOffset);
|
|
2217
|
+
const range = this.grid.options.autoHeight && !rowModel.server ? { first: 0, last: count - 1 } : this.offsets ? rowRangeByOffsets(this.offsets, top, geo.ch, count, ROW_OVERSCAN) : rowRange(top, geo.ch, this.opts.rowHeight, count, ROW_OVERSCAN);
|
|
2118
2218
|
this.lastRange = range;
|
|
2119
2219
|
const center = columns.region("center");
|
|
2120
|
-
const cr = colRange(center.map((c) => c.left), center.map((c) => c.width), this.
|
|
2121
|
-
|
|
2220
|
+
const cr = colRange(center.map((c) => c.left), center.map((c) => c.width), this.geo.left, this.geo.cw, COL_OVERSCAN);
|
|
2221
|
+
const colSpans = center.some((c) => c.def.colSpan);
|
|
2222
|
+
if (colSpans) cr.first = Math.max(0, cr.first - MAX_COLSPAN_REACH);
|
|
2223
|
+
const slide = !colSpans && !spans.size;
|
|
2224
|
+
this.touched.clear();
|
|
2122
2225
|
for (const r of REGIONS3) {
|
|
2123
2226
|
const cols = r === "center" ? center.slice(cr.first, cr.last + 1) : columns.region(r);
|
|
2124
2227
|
const map = this.painted[r];
|
|
@@ -2137,8 +2240,9 @@ var Renderer = class {
|
|
|
2137
2240
|
const hits = allBounds.filter((b) => i >= b.r1 && i <= b.r2);
|
|
2138
2241
|
const inRange = hits.map((b) => `${b.c1}-${b.c2}`).join(",");
|
|
2139
2242
|
const merge2 = this.mergeState(spans, cols, i, range.first);
|
|
2140
|
-
const pos = node.kind === "detail" ? `${
|
|
2141
|
-
const
|
|
2243
|
+
const pos = node.kind === "detail" ? `${geo.left}:${geo.cw}` : "";
|
|
2244
|
+
const base = `${node.id}|${this.version}.${this.rowVersions.get(node.id) ?? 0}|${isSel ? 1 : 0}${node.kind === "group" ? `:${this.grid.selectionVersion}` : ""}|${focusCol}|${inRange}|${merge2}|${pos}`;
|
|
2245
|
+
const key = `${colKey}|${base}`;
|
|
2142
2246
|
let row = map.get(i);
|
|
2143
2247
|
if (!row) {
|
|
2144
2248
|
const rowEl = this.pool[r].pop() ?? el("div", "fg-row", { role: "row" });
|
|
@@ -2147,7 +2251,15 @@ var Renderer = class {
|
|
|
2147
2251
|
map.set(i, row);
|
|
2148
2252
|
}
|
|
2149
2253
|
if (row.key === key) continue;
|
|
2254
|
+
this.touched.add(i);
|
|
2255
|
+
if (slide && r === "center" && !node.kind && row.base === base && this.slideCells(row, node, i, center, cr, isSel, focusCol, hits)) {
|
|
2256
|
+
row.key = key;
|
|
2257
|
+
continue;
|
|
2258
|
+
}
|
|
2150
2259
|
row.key = key;
|
|
2260
|
+
row.base = r === "center" && !node.kind ? base : void 0;
|
|
2261
|
+
row.first = cr.first;
|
|
2262
|
+
row.last = cr.last;
|
|
2151
2263
|
if (row.el.dataset.kind !== (node.kind ?? "")) {
|
|
2152
2264
|
row.el.replaceChildren();
|
|
2153
2265
|
row.el.dataset.kind = node.kind ?? "";
|
|
@@ -2159,6 +2271,29 @@ var Renderer = class {
|
|
|
2159
2271
|
}
|
|
2160
2272
|
}
|
|
2161
2273
|
}
|
|
2274
|
+
/**
|
|
2275
|
+
* Same row, only the column window moved: drop the cells that left and paint the ones that came in.
|
|
2276
|
+
* False when the windows do not overlap (a full paint is as cheap).
|
|
2277
|
+
*/
|
|
2278
|
+
slideCells(row, node, i, center, cr, selected, focusCol, bounds) {
|
|
2279
|
+
const of = row.first, ol = row.last;
|
|
2280
|
+
if (cr.first > ol || cr.last < of || row.el.childElementCount !== ol - of + 1) return false;
|
|
2281
|
+
const rowEl = row.el;
|
|
2282
|
+
for (let k = of; k < cr.first; k++) rowEl.firstElementChild.remove();
|
|
2283
|
+
for (let k = ol; k > cr.last; k--) rowEl.lastElementChild.remove();
|
|
2284
|
+
const paint = (col) => {
|
|
2285
|
+
const cellEl = el("div", "fg-cell", { role: "gridcell" });
|
|
2286
|
+
this.paintCell(cellEl, node, i, col, selected, focusCol === col.id);
|
|
2287
|
+
if (col.id === this.firstDataId) this.decorate(cellEl, node);
|
|
2288
|
+
if (!isSystemCol(col.id) && bounds.some((b) => col.index >= b.c1 && col.index <= b.c2)) cellEl.classList.add("fg-cell-range");
|
|
2289
|
+
return cellEl;
|
|
2290
|
+
};
|
|
2291
|
+
if (cr.first < of) rowEl.prepend(...center.slice(cr.first, of).map(paint));
|
|
2292
|
+
if (cr.last > ol) rowEl.append(...center.slice(ol + 1, cr.last + 1).map(paint));
|
|
2293
|
+
row.first = cr.first;
|
|
2294
|
+
row.last = cr.last;
|
|
2295
|
+
return true;
|
|
2296
|
+
}
|
|
2162
2297
|
/** Per-row merge signature: n = not merged, d = draws the merged block, h = hidden inside a block. */
|
|
2163
2298
|
mergeState(spans, cols, i, first) {
|
|
2164
2299
|
if (!spans.size) return "";
|
|
@@ -2379,19 +2514,26 @@ var Renderer = class {
|
|
|
2379
2514
|
this.summaryInners[r].style.width = `${w}px`;
|
|
2380
2515
|
this.summaryInners[r].style.height = `${height}px`;
|
|
2381
2516
|
}
|
|
2382
|
-
const vScrollbar = this.
|
|
2517
|
+
const vScrollbar = this.geo.ow - this.geo.cw;
|
|
2383
2518
|
this.summaryPanes.center.style.marginRight = `${vScrollbar}px`;
|
|
2384
2519
|
}
|
|
2385
2520
|
renderSummary(count, defs, values, pinned, position) {
|
|
2521
|
+
if (!defs && !this.summaryKey) return;
|
|
2522
|
+
const center = this.grid.columns.region("center");
|
|
2523
|
+
const cr = colRange(center.map((c) => c.left), center.map((c) => c.width), this.geo.left, this.geo.cw, COL_OVERSCAN);
|
|
2524
|
+
const key = [defs, values, pinned, position, this.summaryInners, this.version, cr.first, cr.last, this.opts.rowHeight, pinned ? 0 : this.rowsHeight(count), this.grid.store.state.locale.code];
|
|
2525
|
+
if (sameKey(key, this.summaryKey)) return;
|
|
2526
|
+
this.summaryKey = key;
|
|
2386
2527
|
for (const r of REGIONS3) {
|
|
2387
2528
|
for (const old of this.canvasSummary[r]) old.remove();
|
|
2388
2529
|
this.canvasSummary[r] = [];
|
|
2389
2530
|
}
|
|
2390
|
-
if (!defs)
|
|
2531
|
+
if (!defs) {
|
|
2532
|
+
this.summaryKey = null;
|
|
2533
|
+
return;
|
|
2534
|
+
}
|
|
2391
2535
|
const { columns } = this.grid;
|
|
2392
2536
|
const rh = this.opts.rowHeight;
|
|
2393
|
-
const center = columns.region("center");
|
|
2394
|
-
const cr = colRange(center.map((c) => c.left), center.map((c) => c.width), this.scroller.scrollLeft, this.scroller.clientWidth, COL_OVERSCAN);
|
|
2395
2537
|
const dataCols = columns.displayed().filter((c) => !isSystemCol(c.id));
|
|
2396
2538
|
for (const r of REGIONS3) {
|
|
2397
2539
|
const cols = r === "center" ? center.slice(cr.first, cr.last + 1) : columns.region(r);
|
|
@@ -2433,10 +2575,10 @@ var Renderer = class {
|
|
|
2433
2575
|
let text2;
|
|
2434
2576
|
if (value == null || value === "") text2 = "";
|
|
2435
2577
|
else if (typeof value === "string") text2 = value;
|
|
2436
|
-
else if (s.agg === "count") text2 =
|
|
2578
|
+
else if (s.agg === "count") text2 = numberFormat(code).format(value);
|
|
2437
2579
|
else {
|
|
2438
2580
|
const type = cd.type ?? (typeof value === "number" ? "number" : value instanceof Date ? "date" : void 0);
|
|
2439
|
-
text2 =
|
|
2581
|
+
text2 = getFormatterAs(cd, type, code, s.format ?? cd.format)(value);
|
|
2440
2582
|
}
|
|
2441
2583
|
cell.textContent = s.prefix && text2 ? `${s.prefix} ${text2}` : text2;
|
|
2442
2584
|
return cell;
|
|
@@ -2458,24 +2600,30 @@ var Renderer = class {
|
|
|
2458
2600
|
if (this.heightOf) this.heightsVersion++;
|
|
2459
2601
|
if (!this.rowHeights.size) return;
|
|
2460
2602
|
this.rowHeights.clear();
|
|
2603
|
+
this.measureAll = true;
|
|
2461
2604
|
this.heightsVersion++;
|
|
2462
2605
|
}
|
|
2463
2606
|
/** Measure wrapped cells of the painted rows; when a height changes, lay out again next frame. */
|
|
2464
2607
|
measureRows() {
|
|
2465
2608
|
const base = this.opts.rowHeight;
|
|
2466
2609
|
const need = /* @__PURE__ */ new Map();
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2610
|
+
const rows = this.measureAll ? new Set(REGIONS3.flatMap((r) => [...this.painted[r].keys()])) : this.touched;
|
|
2611
|
+
this.measureAll = false;
|
|
2612
|
+
for (const i of rows) {
|
|
2613
|
+
const node = this.grid.rowModel.getRow(i);
|
|
2614
|
+
if (!node || node.kind) continue;
|
|
2615
|
+
let h = base;
|
|
2616
|
+
for (const r of REGIONS3) {
|
|
2617
|
+
const row = this.painted[r].get(i);
|
|
2618
|
+
if (!row) continue;
|
|
2472
2619
|
for (const cell of row.el.querySelectorAll(".fg-cell-wrap")) {
|
|
2473
2620
|
if (cell.classList.contains("fg-cell-merged") || cell.classList.contains("fg-cell-merge-hidden")) continue;
|
|
2474
2621
|
h = Math.max(h, Math.ceil(cell.scrollHeight));
|
|
2475
2622
|
}
|
|
2476
|
-
need.set(i, h);
|
|
2477
2623
|
}
|
|
2624
|
+
need.set(i, h);
|
|
2478
2625
|
}
|
|
2626
|
+
this.touched.clear();
|
|
2479
2627
|
let changed = false;
|
|
2480
2628
|
for (const [i, h] of need) {
|
|
2481
2629
|
const id = this.grid.rowModel.getRow(i).id;
|
|
@@ -2596,7 +2744,7 @@ var Renderer = class {
|
|
|
2596
2744
|
const def2 = grid.columns.getNode(g.colId)?.def;
|
|
2597
2745
|
const label = el("span", "fg-group-label");
|
|
2598
2746
|
const value2 = isBlank(g.value) ? loc.blanks : def2 ? getFormatter(def2, loc.code)(g.value) : String(g.value);
|
|
2599
|
-
const nf =
|
|
2747
|
+
const nf = numberFormat(loc.code);
|
|
2600
2748
|
const count = g.count ?? (g.aggregates ? null : g.rows.length);
|
|
2601
2749
|
label.textContent = `${def2?.header ?? def2?.field ?? g.colId}: ${value2}${count == null ? "" : ` (${nf.format(count)})`}`;
|
|
2602
2750
|
cell.replaceChildren(toggle, label);
|
|
@@ -2607,10 +2755,10 @@ var Renderer = class {
|
|
|
2607
2755
|
cell.className = `fg-cell fg-align-${align}${focused}`;
|
|
2608
2756
|
const value = def.aggFunc && !isSystemCol(col.id) ? grid.groupAggregate(node, col.id) : void 0;
|
|
2609
2757
|
if (value == null || value === "") cell.textContent = "";
|
|
2610
|
-
else if (def.aggFunc === "count") cell.textContent =
|
|
2758
|
+
else if (def.aggFunc === "count") cell.textContent = numberFormat(loc.code).format(value);
|
|
2611
2759
|
else {
|
|
2612
2760
|
const type = def.type ?? (typeof value === "number" ? "number" : value instanceof Date ? "date" : void 0);
|
|
2613
|
-
cell.textContent =
|
|
2761
|
+
cell.textContent = getFormatterAs(def, type, loc.code)(value);
|
|
2614
2762
|
}
|
|
2615
2763
|
if (cell.textContent) cell.classList.add("fg-cell-agg");
|
|
2616
2764
|
});
|
|
@@ -2637,8 +2785,8 @@ var Renderer = class {
|
|
|
2637
2785
|
}
|
|
2638
2786
|
this.details.set(master.id, panel);
|
|
2639
2787
|
}
|
|
2640
|
-
panel.style.left = `${this.
|
|
2641
|
-
panel.style.width = `${this.
|
|
2788
|
+
panel.style.left = `${this.geo.left}px`;
|
|
2789
|
+
panel.style.width = `${this.geo.cw || this.grid.columns.regionWidth("center")}px`;
|
|
2642
2790
|
if (rowEl.firstChild !== panel || rowEl.childNodes.length !== 1) rowEl.replaceChildren(panel);
|
|
2643
2791
|
}
|
|
2644
2792
|
/** Server rows that are on their way: grey bars in the data cells. */
|
|
@@ -2688,7 +2836,7 @@ var Renderer = class {
|
|
|
2688
2836
|
});
|
|
2689
2837
|
}
|
|
2690
2838
|
this.pagerEl.setAttribute("aria-label", loc.pageSize);
|
|
2691
|
-
const nf =
|
|
2839
|
+
const nf = numberFormat(loc.code);
|
|
2692
2840
|
const from = total ? page * size + 1 : 0, to = Math.min(total, (page + 1) * size);
|
|
2693
2841
|
const range = el("span", "fg-pager-range");
|
|
2694
2842
|
range.textContent = loc.pagerRange.replace("{from}", nf.format(from)).replace("{to}", nf.format(to)).replace("{total}", nf.format(total));
|
|
@@ -2855,6 +3003,8 @@ var Renderer = class {
|
|
|
2855
3003
|
};
|
|
2856
3004
|
var Section = class {
|
|
2857
3005
|
constructor(cls) {
|
|
3006
|
+
/** What the rows were last built from. */
|
|
3007
|
+
this.key = null;
|
|
2858
3008
|
const mk = (c) => ({
|
|
2859
3009
|
left: el("div", `${c} ${c}-left`),
|
|
2860
3010
|
center: el("div", `${c} ${c}-center`),
|
|
@@ -2882,6 +3032,9 @@ var Section = class {
|
|
|
2882
3032
|
this.panes.center.style.marginRight = `${vScrollbar}px`;
|
|
2883
3033
|
}
|
|
2884
3034
|
};
|
|
3035
|
+
function sameKey(a, b) {
|
|
3036
|
+
return !!b && a.length === b.length && a.every((k, i) => k === b[i]);
|
|
3037
|
+
}
|
|
2885
3038
|
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
2886
3039
|
function sparkline(nums, opt, width, rowHeight) {
|
|
2887
3040
|
const w = Math.max(20, width - 16), h = Math.max(10, rowHeight - 14);
|
|
@@ -2893,8 +3046,8 @@ function sparkline(nums, opt, width, rowHeight) {
|
|
|
2893
3046
|
svgEl.setAttribute("aria-hidden", "true");
|
|
2894
3047
|
const color = opt.color ?? "var(--fg-accent)";
|
|
2895
3048
|
const bars = opt.type === "bar";
|
|
2896
|
-
let min =
|
|
2897
|
-
let max =
|
|
3049
|
+
let min = nums.reduce((m, v) => Math.min(m, v), bars ? 0 : Infinity);
|
|
3050
|
+
let max = nums.reduce((m, v) => Math.max(m, v), bars ? 0 : -Infinity);
|
|
2898
3051
|
if (min === max) {
|
|
2899
3052
|
min -= 1;
|
|
2900
3053
|
max += 1;
|
|
@@ -3115,6 +3268,7 @@ var en = {
|
|
|
3115
3268
|
exportCsv: "Export CSV",
|
|
3116
3269
|
exportExcel: "Export Excel",
|
|
3117
3270
|
print: "Print\u2026",
|
|
3271
|
+
printLimited: "Printed the first {n} of {total} rows. Export to Excel or CSV for all of them.",
|
|
3118
3272
|
pivot: "Pivot",
|
|
3119
3273
|
pivotMode: "Pivot mode",
|
|
3120
3274
|
pivotRows: "Row groups",
|
|
@@ -4422,27 +4576,69 @@ var Finder = class {
|
|
|
4422
4576
|
this.lookup.clear();
|
|
4423
4577
|
this.index = -1;
|
|
4424
4578
|
if (this.query) {
|
|
4425
|
-
const
|
|
4426
|
-
const
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
outer: for (let r = 0; r < rows.length; r++) {
|
|
4431
|
-
if (rows[r].kind) continue;
|
|
4432
|
-
for (let c = 0; c < cols.length; c++) {
|
|
4433
|
-
const text3 = fmts[c](getCellValue(cols[c].def, rows[r].data)).toLowerCase();
|
|
4434
|
-
if (!text3.includes(this.query)) continue;
|
|
4435
|
-
this.lookup.set(`${r}|${cols[c].id}`, this.matches.length);
|
|
4436
|
-
this.matches.push({ rowIndex: r, colId: cols[c].id });
|
|
4437
|
-
if (this.matches.length >= MAX_MATCHES) break outer;
|
|
4438
|
-
}
|
|
4439
|
-
}
|
|
4579
|
+
const rows = this.grid.rowModel.rows();
|
|
4580
|
+
const scan = this.scanner();
|
|
4581
|
+
for (let r = 0; r < rows.length && this.matches.length < MAX_MATCHES; r++) scan(r, this.matches);
|
|
4582
|
+
this.matches.length = Math.min(this.matches.length, MAX_MATCHES);
|
|
4583
|
+
this.reindex();
|
|
4440
4584
|
if (this.matches.length) this.go(0);
|
|
4441
4585
|
}
|
|
4442
4586
|
this.updateCount();
|
|
4443
4587
|
this.grid.renderer.invalidate({ rows: true });
|
|
4444
4588
|
return this.matches.length;
|
|
4445
4589
|
}
|
|
4590
|
+
/** Adds the matching cells of one display row, in column order. */
|
|
4591
|
+
scanner() {
|
|
4592
|
+
const { grid } = this;
|
|
4593
|
+
const code = grid.store.state.locale.code;
|
|
4594
|
+
const cols = grid.columns.displayed().filter((c) => !isSystemCol(c.id));
|
|
4595
|
+
const fmts = cols.map((c) => getFormatter(c.def, code));
|
|
4596
|
+
const rows = grid.rowModel.rows();
|
|
4597
|
+
return (r, out) => {
|
|
4598
|
+
const node = rows[r];
|
|
4599
|
+
if (!node || node.kind) return;
|
|
4600
|
+
for (let c = 0; c < cols.length; c++) {
|
|
4601
|
+
if (fmts[c](getCellValue(cols[c].def, node.data)).toLowerCase().includes(this.query)) out.push({ rowIndex: r, colId: cols[c].id });
|
|
4602
|
+
}
|
|
4603
|
+
};
|
|
4604
|
+
}
|
|
4605
|
+
reindex() {
|
|
4606
|
+
this.lookup.clear();
|
|
4607
|
+
this.matches.forEach((m, i) => this.lookup.set(`${m.rowIndex}|${m.colId}`, i));
|
|
4608
|
+
}
|
|
4609
|
+
/**
|
|
4610
|
+
* Values changed in place (same rows, same order): check only those rows again
|
|
4611
|
+
* instead of formatting every cell of the grid.
|
|
4612
|
+
*/
|
|
4613
|
+
refreshRows(ids) {
|
|
4614
|
+
if (!this.query) return;
|
|
4615
|
+
if (this.matches.length >= MAX_MATCHES) return this.refresh();
|
|
4616
|
+
const { rowModel } = this.grid;
|
|
4617
|
+
const changed = /* @__PURE__ */ new Set();
|
|
4618
|
+
for (const id of ids) {
|
|
4619
|
+
const r = rowModel.indexOfId(id);
|
|
4620
|
+
if (r >= 0) changed.add(r);
|
|
4621
|
+
}
|
|
4622
|
+
if (!changed.size) return;
|
|
4623
|
+
const current = this.matches[this.index];
|
|
4624
|
+
const scan = this.scanner();
|
|
4625
|
+
const fresh = [];
|
|
4626
|
+
for (const r of changed) scan(r, fresh);
|
|
4627
|
+
const before = this.matches.length;
|
|
4628
|
+
const old = this.matches.filter((m) => changed.has(m.rowIndex));
|
|
4629
|
+
const sig = (list) => list.map((m) => `${m.rowIndex}|${m.colId}`).sort().join(",");
|
|
4630
|
+
if (sig(old) === sig(fresh)) return;
|
|
4631
|
+
const order = new Map(this.grid.columns.displayed().map((c, i) => [c.id, i]));
|
|
4632
|
+
this.matches = [...this.matches.filter((m) => !changed.has(m.rowIndex)), ...fresh].sort((a, b) => a.rowIndex - b.rowIndex || (order.get(a.colId) ?? 0) - (order.get(b.colId) ?? 0)).slice(0, MAX_MATCHES);
|
|
4633
|
+
this.reindex();
|
|
4634
|
+
const kept = current ? this.lookup.get(`${current.rowIndex}|${current.colId}`) : void 0;
|
|
4635
|
+
const was = this.index;
|
|
4636
|
+
if (kept !== void 0) this.index = kept;
|
|
4637
|
+
else if (this.matches.length) this.index = Math.min(Math.max(this.index, 0), this.matches.length - 1);
|
|
4638
|
+
else this.index = -1;
|
|
4639
|
+
if (before !== this.matches.length || was !== this.index || kept === void 0) this.updateCount();
|
|
4640
|
+
this.grid.renderer.invalidate({ rows: true });
|
|
4641
|
+
}
|
|
4446
4642
|
/** Re-run the current query after data, sort or filter changes. */
|
|
4447
4643
|
refresh() {
|
|
4448
4644
|
if (this.query) this.find(this.query);
|
|
@@ -4534,7 +4730,7 @@ function fillSeries(source, count) {
|
|
|
4534
4730
|
const last2 = nums[nums.length - 1];
|
|
4535
4731
|
return Array.from({ length: count }, (_, i) => Math.round((last2 + step * (i + 1)) * 1e12) / 1e12);
|
|
4536
4732
|
}
|
|
4537
|
-
const dates = source.map((v) => v instanceof Date || typeof v === "string" && /^\d{4}-\d{2}-\d{2}/.test(v) ?
|
|
4733
|
+
const dates = source.map((v) => v instanceof Date || typeof v === "string" && /^\d{4}-\d{2}-\d{2}/.test(v) ? toDate(v) : null);
|
|
4538
4734
|
if (dates.every(Boolean) && source.length >= 2) {
|
|
4539
4735
|
const times = dates.map((d) => Math.round(d.getTime() / DAY2));
|
|
4540
4736
|
const step = times[1] - times[0];
|
|
@@ -4543,7 +4739,7 @@ function fillSeries(source, count) {
|
|
|
4543
4739
|
const asDate = source[source.length - 1] instanceof Date;
|
|
4544
4740
|
return Array.from({ length: count }, (_, i) => {
|
|
4545
4741
|
const d = new Date(lastDate.getFullYear(), lastDate.getMonth(), lastDate.getDate() + step * (i + 1));
|
|
4546
|
-
return asDate ? d :
|
|
4742
|
+
return asDate ? d : toDateKey(d);
|
|
4547
4743
|
});
|
|
4548
4744
|
}
|
|
4549
4745
|
return repeat();
|
|
@@ -4634,7 +4830,7 @@ function copyValue(def, value, locale) {
|
|
|
4634
4830
|
return Number.isNaN(n) ? String(value) : String(n);
|
|
4635
4831
|
}
|
|
4636
4832
|
case "date":
|
|
4637
|
-
return
|
|
4833
|
+
return toDateKey(value) ?? String(value);
|
|
4638
4834
|
case "boolean":
|
|
4639
4835
|
return value === true ? "TRUE" : value === false ? "FALSE" : String(value);
|
|
4640
4836
|
default:
|
|
@@ -4658,7 +4854,8 @@ function parseNumber(text2) {
|
|
|
4658
4854
|
let n = Number(s);
|
|
4659
4855
|
if (percent) n /= 100;
|
|
4660
4856
|
if (negative) n = -n;
|
|
4661
|
-
|
|
4857
|
+
if (!Number.isFinite(n)) return { ok: false };
|
|
4858
|
+
return { ok: true, value: Math.abs(n) < 1e15 ? Math.round(n * 1e12) / 1e12 : n };
|
|
4662
4859
|
}
|
|
4663
4860
|
function validDate(y, m, d) {
|
|
4664
4861
|
const date = new Date(y, m - 1, d);
|
|
@@ -4671,9 +4868,9 @@ function parseDate(text2, previous) {
|
|
|
4671
4868
|
let m = /^(\d{4})\s*[-./년]\s*(\d{1,2})\s*[-./월]\s*(\d{1,2})\s*[.일]?$/.exec(s);
|
|
4672
4869
|
if (m) date = validDate(+m[1], +m[2], +m[3]);
|
|
4673
4870
|
else if (m = /^(\d{1,2})\/(\d{1,2})\/(\d{4})$/.exec(s)) date = validDate(+m[3], +m[1], +m[2]);
|
|
4674
|
-
else if (/^\d{4}-\d{2}-\d{2}T/.test(s)) date =
|
|
4871
|
+
else if (/^\d{4}-\d{2}-\d{2}T/.test(s)) date = toDate(s);
|
|
4675
4872
|
if (!date) return { ok: false };
|
|
4676
|
-
return { ok: true, value: previous instanceof Date ? date :
|
|
4873
|
+
return { ok: true, value: previous instanceof Date ? date : toDateKey(date) };
|
|
4677
4874
|
}
|
|
4678
4875
|
function parseValue(def, text2, previous) {
|
|
4679
4876
|
switch (def.type) {
|
|
@@ -4825,7 +5022,7 @@ var CellEditor = class {
|
|
|
4825
5022
|
input.type = def.type === "date" && initialText === void 0 ? "date" : "text";
|
|
4826
5023
|
if (def.type === "number") input.inputMode = "decimal";
|
|
4827
5024
|
}
|
|
4828
|
-
const start = def.type === "date" ?
|
|
5025
|
+
const start = def.type === "date" ? toDateKey(value) ?? "" : isBlank(value) ? "" : String(value);
|
|
4829
5026
|
input.value = initialText ?? start;
|
|
4830
5027
|
control = input;
|
|
4831
5028
|
read = () => ({ text: input.value });
|
|
@@ -5836,8 +6033,26 @@ function argbOf(c) {
|
|
|
5836
6033
|
}
|
|
5837
6034
|
|
|
5838
6035
|
// src/rows/sort.ts
|
|
6036
|
+
var PLAIN_NUMBER = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?$/i;
|
|
6037
|
+
function numeric(v) {
|
|
6038
|
+
if (typeof v === "number") return v;
|
|
6039
|
+
if (typeof v !== "string" || !v) return NaN;
|
|
6040
|
+
const c = v.charCodeAt(0);
|
|
6041
|
+
if (!(c >= 48 && c <= 57 || c === 43 || c === 45 || c === 46)) return NaN;
|
|
6042
|
+
return PLAIN_NUMBER.test(v) ? Number(v) : NaN;
|
|
6043
|
+
}
|
|
5839
6044
|
function compareNonBlank(a, b, collator) {
|
|
5840
6045
|
if (typeof a === "number" && typeof b === "number") return a - b;
|
|
6046
|
+
if (typeof a === "number" || typeof b === "number") {
|
|
6047
|
+
const x = numeric(a), y = numeric(b);
|
|
6048
|
+
const nx = !Number.isNaN(x), ny = !Number.isNaN(y);
|
|
6049
|
+
if (nx && ny) return x - y;
|
|
6050
|
+
if (nx !== ny) return nx ? -1 : 1;
|
|
6051
|
+
} else if (typeof a === "string" && typeof b === "string") {
|
|
6052
|
+
const x = numeric(a), y = numeric(b);
|
|
6053
|
+
if (!Number.isNaN(x) && !Number.isNaN(y)) return x - y;
|
|
6054
|
+
if (!Number.isNaN(x) || !Number.isNaN(y)) return Number.isNaN(x) ? 1 : -1;
|
|
6055
|
+
}
|
|
5841
6056
|
if (a instanceof Date && b instanceof Date) return a.getTime() - b.getTime();
|
|
5842
6057
|
if (typeof a === "boolean" && typeof b === "boolean") return Number(a) - Number(b);
|
|
5843
6058
|
return collator.compare(String(a), String(b));
|
|
@@ -5848,7 +6063,7 @@ function normalize(def, v) {
|
|
|
5848
6063
|
const n = typeof v === "number" ? v : Number(v);
|
|
5849
6064
|
return Number.isNaN(n) ? null : n;
|
|
5850
6065
|
}
|
|
5851
|
-
if (def.type === "date") return
|
|
6066
|
+
if (def.type === "date") return toDate(v)?.getTime() ?? null;
|
|
5852
6067
|
return v;
|
|
5853
6068
|
}
|
|
5854
6069
|
function compareValues(a, b, collator) {
|
|
@@ -5856,6 +6071,11 @@ function compareValues(a, b, collator) {
|
|
|
5856
6071
|
if (ba || bb) return ba === bb ? 0 : ba ? 1 : -1;
|
|
5857
6072
|
return compareNonBlank(a, b, collator);
|
|
5858
6073
|
}
|
|
6074
|
+
function compareGroupValues(a, b, collator, dir) {
|
|
6075
|
+
const ba = isBlank(a), bb = isBlank(b);
|
|
6076
|
+
if (ba || bb) return ba === bb ? 0 : ba ? 1 : -1;
|
|
6077
|
+
return compareNonBlank(a, b, collator) * dir;
|
|
6078
|
+
}
|
|
5859
6079
|
function sortNodes(nodes, sort, resolve, collator) {
|
|
5860
6080
|
const keys = sort.map((s) => ({ def: resolve(s.colId), mul: s.dir === "desc" ? -1 : 1 })).filter((k) => !!k.def);
|
|
5861
6081
|
if (!keys.length) return nodes;
|
|
@@ -6075,7 +6295,7 @@ function sheetNames(sheets) {
|
|
|
6075
6295
|
return sheets.map((s, i) => {
|
|
6076
6296
|
let base = (s.name ?? `Sheet${i + 1}`).replace(/[[\]:*?/\\]/g, " ").slice(0, 31).trim() || `Sheet${i + 1}`;
|
|
6077
6297
|
let name = base;
|
|
6078
|
-
for (let k = 2; used.has(name.toLowerCase()); k++) name = `${base.slice(0,
|
|
6298
|
+
for (let k = 2; used.has(name.toLowerCase()); k++) name = `${base.slice(0, 31 - ` (${k})`.length)} (${k})`;
|
|
6079
6299
|
used.add(name.toLowerCase());
|
|
6080
6300
|
return name;
|
|
6081
6301
|
});
|
|
@@ -6299,7 +6519,7 @@ function exportRows(grid, mode) {
|
|
|
6299
6519
|
}
|
|
6300
6520
|
var header = (def, id) => def.header ?? def.field ?? id;
|
|
6301
6521
|
var ALIGNS = ["left", "center", "right"];
|
|
6302
|
-
var
|
|
6522
|
+
var PLAIN_NUMBER2 = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?$/i;
|
|
6303
6523
|
var escHtml = (s) => String(s ?? "").replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]);
|
|
6304
6524
|
var PAPERS = ["A4", "A3", "A5", "B4", "B5", "letter", "legal"];
|
|
6305
6525
|
var PORTRAIT_WIDTH = 760;
|
|
@@ -6327,7 +6547,7 @@ function printLines(grid, rows, groupBy) {
|
|
|
6327
6547
|
b.rows.push(r);
|
|
6328
6548
|
}
|
|
6329
6549
|
const dir = sort.find((x) => x.colId === colId)?.dir === "desc" ? -1 : 1;
|
|
6330
|
-
for (const b of [...buckets.values()].sort((a, c) =>
|
|
6550
|
+
for (const b of [...buckets.values()].sort((a, c) => compareGroupValues(a.value, c.value, collator, dir))) {
|
|
6331
6551
|
out.push({ kind: "group", colId, value: b.value, rows: b.rows, level });
|
|
6332
6552
|
walk(b.rows, level + 1);
|
|
6333
6553
|
}
|
|
@@ -6357,12 +6577,13 @@ tr.title td { border: 0; padding: 0 0 4px; background: none; }
|
|
|
6357
6577
|
tr.total td { font-weight: 700; background: #f3f4f6; border-top: 1.5px solid #111827; border-bottom: 0; }
|
|
6358
6578
|
`;
|
|
6359
6579
|
var colorOk = (c) => typeof c === "string" && /^(#[0-9a-f]{3,8}|[a-z]+|rgba?\([\d\s.,%]+\)|hsla?\([\d\s.,%deg]+\))$/i.test(c.trim()) ? c.trim() : void 0;
|
|
6580
|
+
var DEFAULT_PRINT_ROWS = 1e3;
|
|
6360
6581
|
function printModel(grid, opts = {}) {
|
|
6361
6582
|
const cols = exportColumns(grid, opts.columns);
|
|
6362
6583
|
const loc = grid.store.state.locale;
|
|
6363
6584
|
const code = loc.code;
|
|
6364
6585
|
const fmts = cols.map((c) => getFormatter(c.def, code));
|
|
6365
|
-
const nf =
|
|
6586
|
+
const nf = numberFormat(code);
|
|
6366
6587
|
const numbered = opts.rowNumbers ?? !!grid.options.rowNumbers;
|
|
6367
6588
|
const align = (def) => ALIGNS.includes(def.align) ? def.align : def.type === "number" ? "right" : def.type === "boolean" ? "center" : "left";
|
|
6368
6589
|
const chains = cols.map((c) => {
|
|
@@ -6395,7 +6616,7 @@ function printModel(grid, opts = {}) {
|
|
|
6395
6616
|
if (v == null || v === "") return "";
|
|
6396
6617
|
if (agg === "count") return nf.format(v);
|
|
6397
6618
|
const type = c.def.type ?? (typeof v === "number" ? "number" : v instanceof Date ? "date" : void 0);
|
|
6398
|
-
return type === c.def.type ? fmts[i](v) :
|
|
6619
|
+
return type === c.def.type ? fmts[i](v) : getFormatterAs(c.def, type, code)(v);
|
|
6399
6620
|
};
|
|
6400
6621
|
const labelled = (kind, label, values, level = 0) => {
|
|
6401
6622
|
let lead = 0;
|
|
@@ -6410,8 +6631,11 @@ function printModel(grid, opts = {}) {
|
|
|
6410
6631
|
const groupBy = opts.groups !== false && !grid.rowModel.server && !grid.options.treeData ? grid.getGroupBy() : [];
|
|
6411
6632
|
const probe = opts.styles !== false && cols.some((c) => c.def.cellClass) ? classStyles(grid) : null;
|
|
6412
6633
|
const body = [];
|
|
6634
|
+
const maxRows = typeof opts.maxRows === "number" && opts.maxRows >= 1 ? Math.floor(opts.maxRows) : DEFAULT_PRINT_ROWS;
|
|
6635
|
+
let printedRows = 0;
|
|
6413
6636
|
try {
|
|
6414
6637
|
for (const line2 of printLines(grid, rows, groupBy)) {
|
|
6638
|
+
if (line2.kind === "row" && printedRows >= maxRows) break;
|
|
6415
6639
|
if (line2.kind === "group") {
|
|
6416
6640
|
const def = grid.columns.getNode(line2.colId)?.def;
|
|
6417
6641
|
const value = isBlank(line2.value) ? loc.blanks : def ? getFormatter(def, code)(line2.value) : String(line2.value);
|
|
@@ -6461,6 +6685,7 @@ function printModel(grid, opts = {}) {
|
|
|
6461
6685
|
cells.push(cell);
|
|
6462
6686
|
});
|
|
6463
6687
|
body.push({ kind: "row", cells });
|
|
6688
|
+
printedRows++;
|
|
6464
6689
|
}
|
|
6465
6690
|
} finally {
|
|
6466
6691
|
probe?.dispose();
|
|
@@ -6482,8 +6707,11 @@ function printModel(grid, opts = {}) {
|
|
|
6482
6707
|
const orientation = opts.orientation === "portrait" || opts.orientation === "landscape" ? opts.orientation : sum + (numbered ? 40 : 0) > PORTRAIT_WIDTH ? "landscape" : "portrait";
|
|
6483
6708
|
const printed = opts.printedAt !== false ? new Intl.DateTimeFormat(code, { dateStyle: "medium", timeStyle: "short" }).format(/* @__PURE__ */ new Date()) : "";
|
|
6484
6709
|
const repeatRows = Number.isInteger(opts.repeatRows) && opts.repeatRows > 0 ? Math.min(opts.repeatRows, body.length) : 0;
|
|
6710
|
+
const limited = rows.length > printedRows ? loc.printLimited.replace("{n}", nf.format(printedRows)).replace("{total}", nf.format(rows.length)) : "";
|
|
6711
|
+
if (limited) foot.push({ kind: "total", cells: [{ text: limited, align: "left", span: cols.length + (numbered ? 1 : 0), italic: true }] });
|
|
6485
6712
|
return {
|
|
6486
6713
|
lang: code,
|
|
6714
|
+
limited,
|
|
6487
6715
|
title: opts.title ?? "",
|
|
6488
6716
|
subtitle: opts.subtitle ?? "",
|
|
6489
6717
|
printed,
|
|
@@ -6592,7 +6820,7 @@ function buildCsv(grid, opts = {}) {
|
|
|
6592
6820
|
const v = getCellValue(c.def, r);
|
|
6593
6821
|
const t = c.def.type;
|
|
6594
6822
|
const value = copyValue(c.def, v, code);
|
|
6595
|
-
return { value, text: !(t === "number" || t === "date" || t === "boolean") || typeof v === "string" && !
|
|
6823
|
+
return { value, text: typeof v !== "number" && (!(t === "number" || t === "date" || t === "boolean") || typeof v === "string" && !PLAIN_NUMBER2.test(value)) };
|
|
6596
6824
|
}));
|
|
6597
6825
|
if (opts.headers !== false) matrix.unshift(cols.map((c) => ({ value: header(c.def, c.id), text: true })));
|
|
6598
6826
|
return toCsv(matrix, sep);
|
|
@@ -6736,7 +6964,7 @@ function buildXlsxSheet(grid, opts = {}) {
|
|
|
6736
6964
|
return Number.isFinite(n) ? { v: n, fmt } : String(v);
|
|
6737
6965
|
}
|
|
6738
6966
|
case "date": {
|
|
6739
|
-
const d =
|
|
6967
|
+
const d = toDate(v);
|
|
6740
6968
|
return d ? { v: d, fmt } : String(v);
|
|
6741
6969
|
}
|
|
6742
6970
|
case "boolean":
|
|
@@ -6826,7 +7054,7 @@ function asText(v) {
|
|
|
6826
7054
|
if (v === null || v === void 0) return "";
|
|
6827
7055
|
if (v instanceof Date) {
|
|
6828
7056
|
const hasTime = v.getHours() || v.getMinutes() || v.getSeconds();
|
|
6829
|
-
return hasTime ? v.toISOString() :
|
|
7057
|
+
return hasTime ? v.toISOString() : toDateKey(v) ?? "";
|
|
6830
7058
|
}
|
|
6831
7059
|
return String(v);
|
|
6832
7060
|
}
|
|
@@ -7090,8 +7318,9 @@ function drawScatter(root, spec, series, top, bottom, nf, short) {
|
|
|
7090
7318
|
const allX = xs.filter(finite);
|
|
7091
7319
|
const allY = others.flatMap((s) => s.values.filter(finite));
|
|
7092
7320
|
if (!allX.length || !allY.length) return;
|
|
7093
|
-
const
|
|
7094
|
-
const
|
|
7321
|
+
const lo = (a) => a.reduce((m, v) => Math.min(m, v)), hi = (a) => a.reduce((m, v) => Math.max(m, v));
|
|
7322
|
+
const xt = niceTicks(lo(allX), hi(allX));
|
|
7323
|
+
const yt = niceTicks(lo(allY), hi(allY));
|
|
7095
7324
|
const left = 56, right = W - 16;
|
|
7096
7325
|
const x = (v) => left + (v - xt[0]) / (xt[xt.length - 1] - xt[0]) * (right - left);
|
|
7097
7326
|
const y = (v) => bottom - (v - yt[0]) / (yt[yt.length - 1] - yt[0]) * (bottom - top);
|
|
@@ -7239,16 +7468,38 @@ function normalizePivot(cfg, known) {
|
|
|
7239
7468
|
totals: cfg.totals !== false
|
|
7240
7469
|
};
|
|
7241
7470
|
}
|
|
7471
|
+
function samePivot(a, b) {
|
|
7472
|
+
const same3 = (x, y) => x.length === y.length && x.every((v, i) => v === y[i]);
|
|
7473
|
+
return a.totals === b.totals && same3(a.rows, b.rows) && same3(a.columns, b.columns) && a.values.length === b.values.length && a.values.every((v, i) => v.colId === b.values[i].colId && v.aggFunc === b.values[i].aggFunc);
|
|
7474
|
+
}
|
|
7242
7475
|
function buildPivot(source, config, defOf, labels, locale) {
|
|
7243
7476
|
const cfg = normalizePivot(config, (id) => !!defOf(id));
|
|
7244
7477
|
const collator = new Intl.Collator(locale, { numeric: true, sensitivity: "base" });
|
|
7245
7478
|
const def = (id) => defOf(id);
|
|
7246
7479
|
const keyOf = (ids, r) => ids.map((id) => setKey(getCellValue(def(id), r))).join("");
|
|
7247
7480
|
const colCombos = /* @__PURE__ */ new Map();
|
|
7481
|
+
const comboOf2 = /* @__PURE__ */ new WeakMap();
|
|
7248
7482
|
for (const r of source) {
|
|
7249
7483
|
const k = keyOf(cfg.columns, r);
|
|
7484
|
+
comboOf2.set(r, k);
|
|
7250
7485
|
if (!colCombos.has(k)) colCombos.set(k, cfg.columns.map((id) => getCellValue(def(id), r)));
|
|
7251
7486
|
}
|
|
7487
|
+
const bucketsOf = /* @__PURE__ */ new WeakMap();
|
|
7488
|
+
const groupSource = (rows2) => {
|
|
7489
|
+
let b = bucketsOf.get(rows2);
|
|
7490
|
+
if (!b) {
|
|
7491
|
+
const all2 = rows2.flatMap((r) => SOURCE.get(r) ?? []);
|
|
7492
|
+
const byCombo = /* @__PURE__ */ new Map();
|
|
7493
|
+
for (const r of all2) {
|
|
7494
|
+
const k = comboOf2.get(r) ?? keyOf(cfg.columns, r);
|
|
7495
|
+
let list = byCombo.get(k);
|
|
7496
|
+
if (!list) byCombo.set(k, list = []);
|
|
7497
|
+
list.push(r);
|
|
7498
|
+
}
|
|
7499
|
+
bucketsOf.set(rows2, b = { all: all2, byCombo });
|
|
7500
|
+
}
|
|
7501
|
+
return b;
|
|
7502
|
+
};
|
|
7252
7503
|
const combos = [...colCombos.entries()].sort((a, b) => {
|
|
7253
7504
|
for (let i = 0; i < cfg.columns.length; i++) {
|
|
7254
7505
|
const c = compareValues(a[1][i], b[1][i], collator);
|
|
@@ -7263,7 +7514,6 @@ function buildPivot(source, config, defOf, labels, locale) {
|
|
|
7263
7514
|
const src = def(v.colId);
|
|
7264
7515
|
const id = `pivot|${ci}|${vi}`;
|
|
7265
7516
|
leaves.push({ id, comboKey, valueIndex: vi });
|
|
7266
|
-
const matches = (r) => comboKey === null || keyOf(cfg.columns, r) === comboKey;
|
|
7267
7517
|
const agg = v.aggFunc;
|
|
7268
7518
|
return {
|
|
7269
7519
|
id,
|
|
@@ -7274,7 +7524,8 @@ function buildPivot(source, config, defOf, labels, locale) {
|
|
|
7274
7524
|
width: 130,
|
|
7275
7525
|
// group rows: aggregate the matching source rows, not the leaf results
|
|
7276
7526
|
aggFunc: (_values, rows2) => {
|
|
7277
|
-
const
|
|
7527
|
+
const g = groupSource(rows2);
|
|
7528
|
+
const srcRows = comboKey === null ? g.all : g.byCombo.get(comboKey) ?? [];
|
|
7278
7529
|
return srcRows.length ? aggregate(agg, srcRows.map((r) => getCellValue(src, r)), srcRows) : null;
|
|
7279
7530
|
}
|
|
7280
7531
|
};
|
|
@@ -7353,13 +7604,14 @@ function buildPivot(source, config, defOf, labels, locale) {
|
|
|
7353
7604
|
}
|
|
7354
7605
|
|
|
7355
7606
|
// src/version.ts
|
|
7356
|
-
var version = "1.
|
|
7607
|
+
var version = "1.7.1";
|
|
7357
7608
|
|
|
7358
7609
|
// src/license.ts
|
|
7359
7610
|
var PUBLIC_KEY = { kty: "EC", crv: "P-256", x: "sUTKkglZmk-HMKPNub8p7AeLBKesHCU8irIkw9rJlbE", y: "j0U_aKRDR7iApY9UpaD3hUemhBLZJwDv9-QiT_hUyw8" };
|
|
7360
7611
|
var PRICING_URL = "https://freegrid.app/pricing/";
|
|
7361
7612
|
var USAGE_URL = "https://freegrid.app/api/usage";
|
|
7362
7613
|
var STATUS_URL = "https://freegrid.app/api/domain-status";
|
|
7614
|
+
var majorOf = (v) => Number.parseInt(String(v).split(".")[0], 10);
|
|
7363
7615
|
var b64urlDecode = (s) => {
|
|
7364
7616
|
const bin = atob(s.replace(/-/g, "+").replace(/_/g, "/") + "=".repeat((4 - s.length % 4) % 4));
|
|
7365
7617
|
return Uint8Array.from(bin, (c) => c.charCodeAt(0));
|
|
@@ -7393,6 +7645,7 @@ async function verifyLicense(key, host, opts = {}) {
|
|
|
7393
7645
|
try {
|
|
7394
7646
|
info = JSON.parse(new TextDecoder().decode(b64urlDecode(parts[1])));
|
|
7395
7647
|
if (!Array.isArray(info.domains) || !isDay(info.expires)) return { ok: false, reason: "format" };
|
|
7648
|
+
if (info.major !== void 0 && !(Number.isInteger(info.major) && info.major > 0)) return { ok: false, reason: "format" };
|
|
7396
7649
|
} catch {
|
|
7397
7650
|
return { ok: false, reason: "format" };
|
|
7398
7651
|
}
|
|
@@ -7408,6 +7661,7 @@ async function verifyLicense(key, host, opts = {}) {
|
|
|
7408
7661
|
}
|
|
7409
7662
|
if (today(opts.now ?? /* @__PURE__ */ new Date()) > info.expires) return { ok: false, reason: "expired", info };
|
|
7410
7663
|
if (!domainMatches(host, info.domains)) return { ok: false, reason: "domain", info };
|
|
7664
|
+
if (info.major !== void 0 && majorOf(opts.version ?? version) > info.major) return { ok: false, reason: "version", info };
|
|
7411
7665
|
return { ok: true, info };
|
|
7412
7666
|
}
|
|
7413
7667
|
var globalKey;
|
|
@@ -7422,7 +7676,8 @@ FreeGrid\uB294 \uAC1C\uC778\uC5D0\uAC8C \uBB34\uB8CC\uC785\uB2C8\uB2E4. \uD68C\u
|
|
|
7422
7676
|
format: "FreeGrid: the license key is not in the right format. / \uB77C\uC774\uC120\uC2A4 \uD0A4 \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.",
|
|
7423
7677
|
signature: "FreeGrid: the license key could not be verified. / \uB77C\uC774\uC120\uC2A4 \uD0A4\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
7424
7678
|
expired: "FreeGrid: the license key has expired. / \uB77C\uC774\uC120\uC2A4 \uAE30\uAC04\uC774 \uB05D\uB0AC\uC2B5\uB2C8\uB2E4.",
|
|
7425
|
-
domain: "FreeGrid: the license key is for a different domain. / \uB2E4\uB978 \uB3C4\uBA54\uC778\uC6A9 \uB77C\uC774\uC120\uC2A4 \uD0A4\uC785\uB2C8\uB2E4."
|
|
7679
|
+
domain: "FreeGrid: the license key is for a different domain. / \uB2E4\uB978 \uB3C4\uBA54\uC778\uC6A9 \uB77C\uC774\uC120\uC2A4 \uD0A4\uC785\uB2C8\uB2E4.",
|
|
7680
|
+
version: `FreeGrid ${version}: this perpetual license covers an earlier major version. / \uC774 \uC601\uAD6C \uB77C\uC774\uC120\uC2A4\uB294 \uC774\uC804 \uBA54\uC774\uC800 \uBC84\uC804\uC6A9\uC785\uB2C8\uB2E4.`
|
|
7426
7681
|
};
|
|
7427
7682
|
function reportUsage(info, host = typeof location !== "undefined" ? location.hostname : "") {
|
|
7428
7683
|
try {
|
|
@@ -7452,7 +7707,7 @@ function checkLicense(key = globalKey, host = typeof location !== "undefined" ?
|
|
|
7452
7707
|
status = verifyLicense(k, host).then((res) => {
|
|
7453
7708
|
reportUsage({ version, licensed: res.ok, company: res.ok ? res.info.company : void 0 }, host);
|
|
7454
7709
|
if (res.ok || res.reason === "unsupported") return res.ok || res.reason === "unsupported";
|
|
7455
|
-
const extra = res.info ? ` (${res.info.domains.join(", ")}, ${res.info.expires})` : "";
|
|
7710
|
+
const extra = res.info ? ` (${res.info.domains.join(", ")}, ${res.info.major ? `${res.info.major}.x` : res.info.expires})` : "";
|
|
7456
7711
|
console.warn(`${NOTICE[res.reason]}${extra}
|
|
7457
7712
|
${PRICING_URL}`);
|
|
7458
7713
|
return false;
|
|
@@ -7789,6 +8044,8 @@ var RowModel = class {
|
|
|
7789
8044
|
this.display = [];
|
|
7790
8045
|
this.indexById = null;
|
|
7791
8046
|
this.layoutCache = null;
|
|
8047
|
+
/** Searchable text of each row, kept between quick filter keystrokes. */
|
|
8048
|
+
this.quickCache = null;
|
|
7792
8049
|
}
|
|
7793
8050
|
update(input) {
|
|
7794
8051
|
const collator = this.deps.collator();
|
|
@@ -7883,17 +8140,29 @@ var RowModel = class {
|
|
|
7883
8140
|
this.layoutCache = { rows: this.display, rh: rowHeight, dh: detailHeight, hv: heightsVersion, offsets, uniform };
|
|
7884
8141
|
return this.layoutCache;
|
|
7885
8142
|
}
|
|
8143
|
+
quickTexts(cols) {
|
|
8144
|
+
const key = [this.nodes, this.deps.textVersion?.() ?? "", ...cols];
|
|
8145
|
+
const c = this.quickCache;
|
|
8146
|
+
if (c && c.key.length === key.length && c.key.every((k, i) => k === key[i])) return c.texts;
|
|
8147
|
+
this.quickCache = { key, texts: /* @__PURE__ */ new WeakMap() };
|
|
8148
|
+
return this.quickCache.texts;
|
|
8149
|
+
}
|
|
7886
8150
|
filter(input) {
|
|
7887
8151
|
const preds = Object.entries(input.filters).map(([colId, model]) => ({ def: this.deps.columnDef(colId), pred: buildPredicate(model) })).filter((p) => !!p.def);
|
|
7888
8152
|
const tokens = quickTokens(input.quickFilter);
|
|
7889
8153
|
const quickCols = tokens.length ? this.deps.quickColumns() : [];
|
|
7890
8154
|
const advanced = input.advancedFilter ? buildAdvanced(input.advancedFilter, (id) => this.deps.columnDef(id), getCellValue) : null;
|
|
7891
8155
|
if (!preds.length && !tokens.length && !advanced) return this.nodes;
|
|
8156
|
+
const texts = tokens.length ? this.quickTexts(quickCols) : null;
|
|
7892
8157
|
const matches = this.nodes.filter((n) => {
|
|
7893
8158
|
for (const p of preds) if (!p.pred(getCellValue(p.def, n.data))) return false;
|
|
7894
8159
|
if (advanced && !advanced(n.data)) return false;
|
|
7895
|
-
if (
|
|
7896
|
-
|
|
8160
|
+
if (texts) {
|
|
8161
|
+
let text2 = texts.get(n.data);
|
|
8162
|
+
if (text2 === void 0) {
|
|
8163
|
+
text2 = quickCols.map((d) => this.deps.format(d, getCellValue(d, n.data))).join("").toLowerCase();
|
|
8164
|
+
texts.set(n.data, text2);
|
|
8165
|
+
}
|
|
7897
8166
|
if (!matchesQuick(tokens, text2)) return false;
|
|
7898
8167
|
}
|
|
7899
8168
|
return true;
|
|
@@ -7952,7 +8221,7 @@ var RowModel = class {
|
|
|
7952
8221
|
b.nodes.push(n);
|
|
7953
8222
|
}
|
|
7954
8223
|
const dir = sort.find((s) => s.colId === colId)?.dir === "desc" ? -1 : 1;
|
|
7955
|
-
const ordered = [...buckets.entries()].sort((a, b) =>
|
|
8224
|
+
const ordered = [...buckets.entries()].sort((a, b) => compareGroupValues(a[1].value, b[1].value, collator, dir));
|
|
7956
8225
|
for (const [k, b] of ordered) {
|
|
7957
8226
|
const key = `${path}${colId}:${k}`;
|
|
7958
8227
|
const expanded = this.isOpen(d, key, level);
|
|
@@ -7985,6 +8254,25 @@ var RowModel = class {
|
|
|
7985
8254
|
list.push(n);
|
|
7986
8255
|
} else roots.push(n);
|
|
7987
8256
|
}
|
|
8257
|
+
const reached = /* @__PURE__ */ new Set();
|
|
8258
|
+
const mark = (from) => {
|
|
8259
|
+
const stack = [from];
|
|
8260
|
+
while (stack.length) {
|
|
8261
|
+
const x = stack.pop();
|
|
8262
|
+
if (reached.has(x.id)) continue;
|
|
8263
|
+
reached.add(x.id);
|
|
8264
|
+
for (const k of children.get(x.id) ?? []) stack.push(k);
|
|
8265
|
+
}
|
|
8266
|
+
};
|
|
8267
|
+
for (const r of roots) mark(r);
|
|
8268
|
+
for (const n of this.sorted) {
|
|
8269
|
+
if (reached.has(n.id)) continue;
|
|
8270
|
+
const list = children.get(String(d.getParentId(n.data)));
|
|
8271
|
+
const at = list ? list.indexOf(n) : -1;
|
|
8272
|
+
if (at >= 0) list.splice(at, 1);
|
|
8273
|
+
roots.push(n);
|
|
8274
|
+
mark(n);
|
|
8275
|
+
}
|
|
7988
8276
|
const visit = (nodes, level, seen2) => {
|
|
7989
8277
|
for (const n of nodes) {
|
|
7990
8278
|
if (seen2.has(n.id)) continue;
|
|
@@ -8455,6 +8743,47 @@ var Store = class {
|
|
|
8455
8743
|
}
|
|
8456
8744
|
};
|
|
8457
8745
|
|
|
8746
|
+
// src/same.ts
|
|
8747
|
+
var plain = (v) => !!v && typeof v === "object" && !Array.isArray(v) && Object.getPrototypeOf(v) === Object.prototype;
|
|
8748
|
+
var MAX_DEPTH2 = 8;
|
|
8749
|
+
function snapshot(v, depth = 0, seen2 = /* @__PURE__ */ new Map()) {
|
|
8750
|
+
if (depth > MAX_DEPTH2 || !(Array.isArray(v) || plain(v))) return v;
|
|
8751
|
+
const done = seen2.get(v);
|
|
8752
|
+
if (done) return done;
|
|
8753
|
+
if (Array.isArray(v)) {
|
|
8754
|
+
const out2 = [];
|
|
8755
|
+
seen2.set(v, out2);
|
|
8756
|
+
for (const x of v) out2.push(snapshot(x, depth + 1, seen2));
|
|
8757
|
+
return out2;
|
|
8758
|
+
}
|
|
8759
|
+
const out = {};
|
|
8760
|
+
seen2.set(v, out);
|
|
8761
|
+
for (const [k, x] of Object.entries(v)) out[k] = snapshot(x, depth + 1, seen2);
|
|
8762
|
+
return out;
|
|
8763
|
+
}
|
|
8764
|
+
function same2(a, b, depth = 0, seen2 = /* @__PURE__ */ new Map()) {
|
|
8765
|
+
if (a === b) return true;
|
|
8766
|
+
if (depth > MAX_DEPTH2 || !a || !b || typeof a !== "object" || typeof b !== "object") return false;
|
|
8767
|
+
const pairs = seen2.get(a);
|
|
8768
|
+
if (pairs?.has(b)) return true;
|
|
8769
|
+
if (pairs) pairs.add(b);
|
|
8770
|
+
else seen2.set(a, /* @__PURE__ */ new Set([b]));
|
|
8771
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
8772
|
+
return Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((x, i) => same2(x, b[i], depth + 1, seen2));
|
|
8773
|
+
}
|
|
8774
|
+
if (!plain(a) || !plain(b)) return false;
|
|
8775
|
+
const keys = Object.keys(a);
|
|
8776
|
+
return keys.length === Object.keys(b).length && keys.every((k) => k in b && same2(a[k], b[k], depth + 1, seen2));
|
|
8777
|
+
}
|
|
8778
|
+
function changeTracker(initial) {
|
|
8779
|
+
let last = snapshot(initial);
|
|
8780
|
+
return (next) => {
|
|
8781
|
+
if (same2(next, last)) return false;
|
|
8782
|
+
last = snapshot(next);
|
|
8783
|
+
return true;
|
|
8784
|
+
};
|
|
8785
|
+
}
|
|
8786
|
+
|
|
8458
8787
|
// src/interactions/tooltip.ts
|
|
8459
8788
|
var DELAY = 500;
|
|
8460
8789
|
function attachTooltips(grid) {
|
|
@@ -8827,9 +9156,9 @@ var FormatDialog = class {
|
|
|
8827
9156
|
o.textContent = opName(op);
|
|
8828
9157
|
opSel.append(o);
|
|
8829
9158
|
}
|
|
8830
|
-
const
|
|
8831
|
-
const v1 = el("input", "fg-input fg-cf-value", { type:
|
|
8832
|
-
const v2 = el("input", "fg-input fg-cf-value-to", { type:
|
|
9159
|
+
const numeric2 = node.def.type === "number";
|
|
9160
|
+
const v1 = el("input", "fg-input fg-cf-value", { type: numeric2 ? "number" : "text", placeholder: loc.value, "aria-label": loc.value });
|
|
9161
|
+
const v2 = el("input", "fg-input fg-cf-value-to", { type: numeric2 ? "number" : "text", placeholder: loc.valueTo, "aria-label": loc.valueTo });
|
|
8833
9162
|
const formula = el("input", "fg-input fg-cf-wide fg-cf-formula", { type: "text", placeholder: `=${node.def.field ?? colId} > 100`, "aria-label": cf.types.formula, spellcheck: "false" });
|
|
8834
9163
|
const rank = el("input", "fg-input fg-cf-rank", { type: "number", min: "1", value: "10", "aria-label": cf.count });
|
|
8835
9164
|
const pctLabel = el("label", "fg-cf-pct");
|
|
@@ -8869,7 +9198,7 @@ var FormatDialog = class {
|
|
|
8869
9198
|
const read = () => {
|
|
8870
9199
|
const type = typeSel.value;
|
|
8871
9200
|
const rule = { columns: [colId], type };
|
|
8872
|
-
const val = (input) =>
|
|
9201
|
+
const val = (input) => numeric2 && input.value !== "" ? Number(input.value) : input.value;
|
|
8873
9202
|
if (type === "value") {
|
|
8874
9203
|
rule.op = opSel.value;
|
|
8875
9204
|
if (!NO_VALUE3.includes(rule.op)) {
|
|
@@ -9383,9 +9712,22 @@ var Grid = class {
|
|
|
9383
9712
|
}
|
|
9384
9713
|
};
|
|
9385
9714
|
this.cfRules = [];
|
|
9715
|
+
this.cfRowsSrc = null;
|
|
9716
|
+
/** Copies of the last column definitions and summary options, to skip re-applying the same content. */
|
|
9717
|
+
this.sourceSnapshot = void 0;
|
|
9718
|
+
this.summarySnapshot = null;
|
|
9719
|
+
this.cfRows = [];
|
|
9386
9720
|
this.formats = new ConditionalFormatter({
|
|
9387
9721
|
rules: () => this.cfRules,
|
|
9388
|
-
|
|
9722
|
+
// same array while the row set is unchanged, so the formatter's stats cache holds across cells
|
|
9723
|
+
rows: () => {
|
|
9724
|
+
const src = this.rowModel.dataRows();
|
|
9725
|
+
if (this.cfRowsSrc !== src) {
|
|
9726
|
+
this.cfRowsSrc = src;
|
|
9727
|
+
this.cfRows = src.map((n) => n.data);
|
|
9728
|
+
}
|
|
9729
|
+
return this.cfRows;
|
|
9730
|
+
},
|
|
9389
9731
|
value: (colId, row) => {
|
|
9390
9732
|
const def = this.columns.getNode(colId)?.def;
|
|
9391
9733
|
return def ? getCellValue(def, row) : void 0;
|
|
@@ -9420,8 +9762,12 @@ var Grid = class {
|
|
|
9420
9762
|
}
|
|
9421
9763
|
};
|
|
9422
9764
|
this.density = "normal";
|
|
9765
|
+
/** Count / sum / min / max over the selected range (numbers only for the math). */
|
|
9766
|
+
this.aggMemo = null;
|
|
9423
9767
|
/** Bumped on every selection change (group row checkboxes depend on their rows). */
|
|
9424
9768
|
this.selectionVersion = 0;
|
|
9769
|
+
/** Rows select-all acts on: displayed data rows that are selectable. */
|
|
9770
|
+
this.selectableCache = null;
|
|
9425
9771
|
if (!Array.isArray(options.columns)) warn("options.columns must be an array");
|
|
9426
9772
|
if (options.data != null && !Array.isArray(options.data)) warn("options.data must be an array");
|
|
9427
9773
|
this.store = new Store({
|
|
@@ -9439,6 +9785,7 @@ var Grid = class {
|
|
|
9439
9785
|
theme: options.theme ?? "light"
|
|
9440
9786
|
});
|
|
9441
9787
|
this.sourceDefs = Array.isArray(options.columns) ? options.columns : [];
|
|
9788
|
+
this.sourceSnapshot = snapshot(this.sourceDefs);
|
|
9442
9789
|
this.columns = new ColumnModel(this.withSelect(this.sourceDefs));
|
|
9443
9790
|
this.bindFormulas();
|
|
9444
9791
|
const pg = options.pagination;
|
|
@@ -9459,7 +9806,8 @@ var Grid = class {
|
|
|
9459
9806
|
columnDef: (id) => this.columns.getNode(id)?.def,
|
|
9460
9807
|
quickColumns: () => this.columns.displayed().filter((c) => !isSystemCol(c.id)).map((c) => c.def),
|
|
9461
9808
|
format: (def, v) => getFormatter(def, this.store.state.locale.code)(v),
|
|
9462
|
-
collator: () => this.collator()
|
|
9809
|
+
collator: () => this.collator(),
|
|
9810
|
+
textVersion: () => `${this.store.state.locale.code}|${this.editVersion}`
|
|
9463
9811
|
});
|
|
9464
9812
|
this.groupBy = this.validGroupBy(options.groupBy ?? []);
|
|
9465
9813
|
if (Array.isArray(options.conditionalFormats)) this.cfRules = options.conditionalFormats.filter((r) => r && typeof r === "object" && Array.isArray(r.columns)).map((r) => ({ ...r, columns: [...r.columns] }));
|
|
@@ -9467,6 +9815,7 @@ var Grid = class {
|
|
|
9467
9815
|
if (options.treeData && !options.getRowId) warn("treeData needs getRowId so parent ids can be matched");
|
|
9468
9816
|
this.updateRows();
|
|
9469
9817
|
this.summaryOpts = options.summary ?? null;
|
|
9818
|
+
this.summarySnapshot = snapshot(this.summaryOpts);
|
|
9470
9819
|
this.refreshSummary();
|
|
9471
9820
|
const explicit = options.fontSize != null && options.fontSize > 0 ? options.fontSize : null;
|
|
9472
9821
|
if (options.fontSize != null && !(options.fontSize > 0)) warn("fontSize must be a positive number");
|
|
@@ -9522,6 +9871,10 @@ var Grid = class {
|
|
|
9522
9871
|
this.api = this.createApi();
|
|
9523
9872
|
liveGrids.set(this.renderer.root, this);
|
|
9524
9873
|
}
|
|
9874
|
+
/** Bumped whenever cell values change (edits, transactions, undo). */
|
|
9875
|
+
get valuesVersion() {
|
|
9876
|
+
return this.editVersion;
|
|
9877
|
+
}
|
|
9525
9878
|
/** The column definitions as given (setColumns), without the system columns. */
|
|
9526
9879
|
columnDefs() {
|
|
9527
9880
|
return [...this.sourceDefs];
|
|
@@ -9653,7 +10006,9 @@ var Grid = class {
|
|
|
9653
10006
|
}
|
|
9654
10007
|
const known = new Set(this.sourceLeaves().map((l) => l.id));
|
|
9655
10008
|
if (!this.pivotCfg) this.savedGroup = { groupBy: this.groupBy, expanded: this.expanded, defaultExpanded: this.defaultExpanded };
|
|
9656
|
-
|
|
10009
|
+
const next = normalizePivot(cfg, (id) => known.has(id));
|
|
10010
|
+
if (this.pivotCfg && samePivot(next, this.pivotCfg)) return;
|
|
10011
|
+
this.pivotCfg = next;
|
|
9657
10012
|
this.expanded = /* @__PURE__ */ new Map();
|
|
9658
10013
|
this.defaultExpanded = -1;
|
|
9659
10014
|
this.rebuildPivot(false);
|
|
@@ -9757,8 +10112,10 @@ var Grid = class {
|
|
|
9757
10112
|
if (next === this.store.state.data && !this.viewReads(changed.fields)) {
|
|
9758
10113
|
if (this.options.autoRowHeight || this.options.getRowHeight) this.renderer.invalidateHeights();
|
|
9759
10114
|
this.refreshSummary();
|
|
9760
|
-
this.
|
|
9761
|
-
if (
|
|
10115
|
+
const formulas = this.columns.displayed().some((c) => c.def.formula || c.def.formulas);
|
|
10116
|
+
if (formulas) this.finder.refresh();
|
|
10117
|
+
else this.finder.refreshRows(changed.ids);
|
|
10118
|
+
if (this.options.getRowHeight || formulas || this.columns.displayed().some((c) => c.def.merge || c.def.rowSpan) || this.formats.dependsOnOtherRows()) {
|
|
9762
10119
|
this.renderer.invalidate({ rows: true });
|
|
9763
10120
|
} else this.renderer.invalidateRowIds(changed.ids);
|
|
9764
10121
|
} else {
|
|
@@ -10228,8 +10585,8 @@ var Grid = class {
|
|
|
10228
10585
|
const p = this.rawParent(r);
|
|
10229
10586
|
if (p !== null && String(p) === id) return p;
|
|
10230
10587
|
}
|
|
10231
|
-
const
|
|
10232
|
-
return
|
|
10588
|
+
const numeric2 = this.store.state.data.some((r) => typeof this.rawParent(r) === "number");
|
|
10589
|
+
return numeric2 && id.trim() !== "" && Number.isFinite(Number(id)) ? Number(id) : id;
|
|
10233
10590
|
}
|
|
10234
10591
|
/** Can `row` land here? Trees refuse the row's own subtree; then the user's canDrop rule decides. */
|
|
10235
10592
|
canDropRow(row, target, position, from = this) {
|
|
@@ -10369,6 +10726,11 @@ var Grid = class {
|
|
|
10369
10726
|
warn("setSummary: summary.rows must be an array");
|
|
10370
10727
|
return;
|
|
10371
10728
|
}
|
|
10729
|
+
if (same2(opts, this.summarySnapshot)) {
|
|
10730
|
+
this.summaryOpts = opts;
|
|
10731
|
+
return;
|
|
10732
|
+
}
|
|
10733
|
+
this.summarySnapshot = snapshot(opts);
|
|
10372
10734
|
this.summaryOpts = opts;
|
|
10373
10735
|
this.refreshSummary();
|
|
10374
10736
|
this.renderer.invalidate({ header: true, rows: true });
|
|
@@ -10658,38 +11020,44 @@ var Grid = class {
|
|
|
10658
11020
|
this.spanCache = { rows, ver, spans };
|
|
10659
11021
|
return spans;
|
|
10660
11022
|
}
|
|
10661
|
-
/** Count / sum / min / max over the selected range (numbers only for the math). */
|
|
10662
11023
|
rangeAggregates() {
|
|
10663
11024
|
const all2 = this.allRangeBounds();
|
|
10664
11025
|
if (!all2.length) return null;
|
|
10665
11026
|
const displayed = this.columns.displayed();
|
|
11027
|
+
const key = [JSON.stringify(all2), this.rowModel.rows(), this.editVersion, ...displayed.map((c) => c.def)];
|
|
11028
|
+
const memo = this.aggMemo;
|
|
11029
|
+
if (memo && memo.key.length === key.length && memo.key.every((k, i) => k === key[i])) return memo.value && { ...memo.value };
|
|
10666
11030
|
const out = { cells: 0, count: 0, numbers: 0, sum: 0, min: Infinity, max: -Infinity };
|
|
10667
11031
|
const seen2 = all2.length > 1 ? /* @__PURE__ */ new Set() : null;
|
|
10668
|
-
|
|
10669
|
-
|
|
10670
|
-
|
|
10671
|
-
|
|
10672
|
-
|
|
10673
|
-
if (
|
|
10674
|
-
|
|
10675
|
-
|
|
10676
|
-
if (seen2
|
|
10677
|
-
|
|
10678
|
-
|
|
10679
|
-
|
|
10680
|
-
|
|
10681
|
-
|
|
10682
|
-
|
|
10683
|
-
|
|
10684
|
-
|
|
10685
|
-
|
|
10686
|
-
|
|
10687
|
-
|
|
10688
|
-
|
|
11032
|
+
const width = displayed.length;
|
|
11033
|
+
for (const b of all2) {
|
|
11034
|
+
const cols = displayed.slice(b.c1, b.c2 + 1).filter((c) => !isSystemCol(c.id));
|
|
11035
|
+
for (let r = b.r1; r <= b.r2; r++) {
|
|
11036
|
+
const node = this.rowModel.getRow(r);
|
|
11037
|
+
if (!node || node.kind) continue;
|
|
11038
|
+
const row = node.data;
|
|
11039
|
+
for (const c of cols) {
|
|
11040
|
+
if (seen2) {
|
|
11041
|
+
const k = r * width + c.index;
|
|
11042
|
+
if (seen2.has(k)) continue;
|
|
11043
|
+
seen2.add(k);
|
|
11044
|
+
}
|
|
11045
|
+
out.cells++;
|
|
11046
|
+
const v = getCellValue(c.def, row);
|
|
11047
|
+
if (isBlank(v)) continue;
|
|
11048
|
+
out.count++;
|
|
11049
|
+
if (typeof v === "number" || c.def.type === "number" && !Number.isNaN(Number(v))) {
|
|
11050
|
+
const n = Number(v);
|
|
11051
|
+
out.numbers++;
|
|
11052
|
+
out.sum += n;
|
|
11053
|
+
if (n < out.min) out.min = n;
|
|
11054
|
+
if (n > out.max) out.max = n;
|
|
11055
|
+
}
|
|
10689
11056
|
}
|
|
10690
11057
|
}
|
|
10691
11058
|
}
|
|
10692
|
-
|
|
11059
|
+
this.aggMemo = { key, value: out };
|
|
11060
|
+
return { ...out };
|
|
10693
11061
|
}
|
|
10694
11062
|
/** Fill from the current selection into `target`, Excel-style (one undo step). */
|
|
10695
11063
|
fillRange(target) {
|
|
@@ -11116,7 +11484,15 @@ var Grid = class {
|
|
|
11116
11484
|
const i = this.rowModel.indexOfId(id);
|
|
11117
11485
|
return i < 0 ? null : i;
|
|
11118
11486
|
}
|
|
11119
|
-
/**
|
|
11487
|
+
/** Ids of selectableRows(), kept while the rows are the same. */
|
|
11488
|
+
selectableIds() {
|
|
11489
|
+
const rows = this.rowModel.dataRows(), fn = this.options.isRowSelectable;
|
|
11490
|
+
const c = this.selectableCache;
|
|
11491
|
+
if (c && c.rows === rows && c.fn === fn && c.ver === this.editVersion) return c.ids;
|
|
11492
|
+
const ids = new Set(this.selectableRows().map((n) => n.id));
|
|
11493
|
+
this.selectableCache = { rows, fn, ver: this.editVersion, ids };
|
|
11494
|
+
return ids;
|
|
11495
|
+
}
|
|
11120
11496
|
selectableRows() {
|
|
11121
11497
|
const rows = this.rowModel.dataRows();
|
|
11122
11498
|
return this.options.isRowSelectable ? rows.filter((n) => this.rowSelectable(n.data)) : rows;
|
|
@@ -11179,13 +11555,18 @@ var Grid = class {
|
|
|
11179
11555
|
warn("setColumns: columns must be an array");
|
|
11180
11556
|
return;
|
|
11181
11557
|
}
|
|
11558
|
+
if (same2(defs, this.sourceSnapshot)) {
|
|
11559
|
+
this.sourceDefs = defs;
|
|
11560
|
+
return;
|
|
11561
|
+
}
|
|
11182
11562
|
this.menu.close();
|
|
11183
11563
|
this.sourceDefs = defs;
|
|
11564
|
+
this.sourceSnapshot = snapshot(defs);
|
|
11184
11565
|
if (this.pivotCfg) {
|
|
11185
11566
|
this.rebuildPivot();
|
|
11186
11567
|
return;
|
|
11187
11568
|
}
|
|
11188
|
-
this.columns.
|
|
11569
|
+
this.columns.updateDefs(this.withSelect(defs));
|
|
11189
11570
|
this.bindFormulas();
|
|
11190
11571
|
const known = (id) => !!this.columns.getNode(id);
|
|
11191
11572
|
const groupBy = this.groupBy.filter(known);
|
|
@@ -11197,7 +11578,7 @@ var Grid = class {
|
|
|
11197
11578
|
this.store.set({
|
|
11198
11579
|
sort: this.store.state.sort.filter((s) => known(s.colId)),
|
|
11199
11580
|
filters: Object.fromEntries(Object.entries(this.store.state.filters).filter(([k]) => known(k))),
|
|
11200
|
-
focus: null
|
|
11581
|
+
focus: this.store.state.focus && known(this.store.state.focus.colId) ? this.store.state.focus : null
|
|
11201
11582
|
});
|
|
11202
11583
|
this.columnsChanged();
|
|
11203
11584
|
}
|
|
@@ -11222,8 +11603,10 @@ var Grid = class {
|
|
|
11222
11603
|
if (has("locale")) this.groupPanel?.refresh();
|
|
11223
11604
|
if (has("filters")) this.sideBar?.refresh();
|
|
11224
11605
|
if (has("theme")) this.renderer.applyTheme();
|
|
11606
|
+
const header2 = has("sort", "filters", "quickFilter", "advancedFilter", "locale", "data");
|
|
11607
|
+
if (!header2 && has("selected")) this.renderer.selectionChanged();
|
|
11225
11608
|
this.renderer.invalidate({
|
|
11226
|
-
header:
|
|
11609
|
+
header: header2,
|
|
11227
11610
|
rows: has("data", "sort", "filters", "quickFilter", "advancedFilter", "locale")
|
|
11228
11611
|
});
|
|
11229
11612
|
}
|
|
@@ -11456,9 +11839,12 @@ function saveChanges(grid, url, options = {}) {
|
|
|
11456
11839
|
saving.set(grid, run2);
|
|
11457
11840
|
return run2;
|
|
11458
11841
|
}
|
|
11842
|
+
var loads = /* @__PURE__ */ new WeakMap();
|
|
11459
11843
|
async function loadData(grid, url, options = {}) {
|
|
11844
|
+
const seq = (loads.get(grid) ?? 0) + 1;
|
|
11845
|
+
loads.set(grid, seq);
|
|
11460
11846
|
const { rows } = await restDatasource(url, options).getRows({ start: 0, end: options.maxRows ?? 1e4, sort: [], filters: {}, quickFilter: "" });
|
|
11461
|
-
grid.setData(rows);
|
|
11847
|
+
if (loads.get(grid) === seq) grid.setData(rows);
|
|
11462
11848
|
return rows;
|
|
11463
11849
|
}
|
|
11464
11850
|
function csrfHeaders(doc = document) {
|
|
@@ -11634,6 +12020,7 @@ function connectLive(api, options) {
|
|
|
11634
12020
|
}
|
|
11635
12021
|
export {
|
|
11636
12022
|
buildWorkbook,
|
|
12023
|
+
changeTracker,
|
|
11637
12024
|
connectLive,
|
|
11638
12025
|
createGrid,
|
|
11639
12026
|
csrfHeaders,
|