@fluixi-ui/bulk-editor 0.3.0-alpha.18 → 0.3.0-alpha.19
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/bulk-editor.js +1074 -736
- package/dist/formula.js +15 -4
- package/dist/index.js +4 -12
- package/dist/state.js +700 -614
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/package.json +5 -5
package/dist/state.js
CHANGED
|
@@ -1,627 +1,713 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { batch, createEffect, createMemo, createSignal, untrack } from "@fluixi/reactive/signal";
|
|
2
2
|
import { ERR } from "@fluixi-ui/formula";
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
//#region src/state.ts
|
|
4
|
+
var clampTo = (n, max) => Math.max(0, Math.min(n, max));
|
|
5
|
+
var MIN_COL_WIDTH = 48;
|
|
6
|
+
/** Fold a column's computed values into a single footer value. */
|
|
5
7
|
function aggregateValues(kind, values) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
return nums.length ? String(Math.min(...nums)) : "";
|
|
16
|
-
case "max":
|
|
17
|
-
return nums.length ? String(Math.max(...nums)) : "";
|
|
18
|
-
default:
|
|
19
|
-
return "";
|
|
20
|
-
}
|
|
8
|
+
const nums = values.map(Number).filter((n) => !Number.isNaN(n));
|
|
9
|
+
switch (kind) {
|
|
10
|
+
case "count": return String(values.filter((v) => v !== "").length);
|
|
11
|
+
case "sum": return String(nums.reduce((a, b) => a + b, 0));
|
|
12
|
+
case "avg": return nums.length ? String(Math.round(nums.reduce((a, b) => a + b, 0) / nums.length * 100) / 100) : "";
|
|
13
|
+
case "min": return nums.length ? String(Math.min(...nums)) : "";
|
|
14
|
+
case "max": return nums.length ? String(Math.max(...nums)) : "";
|
|
15
|
+
default: return "";
|
|
16
|
+
}
|
|
21
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* Match a cell value against a filter term. Numeric terms may use comparison
|
|
20
|
+
* operators (`>20`, `<=5`, `!=0`) or modulo (`%3`, `%3=1`); anything else is a
|
|
21
|
+
* case-insensitive substring match.
|
|
22
|
+
*/
|
|
22
23
|
function matchesFilter(value, filter) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
return num === rhs;
|
|
48
|
-
default:
|
|
49
|
-
return num !== rhs;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
return value.toLowerCase().includes(f.toLowerCase());
|
|
24
|
+
const f = filter.trim();
|
|
25
|
+
if (!f) return true;
|
|
26
|
+
const mod = /^%\s*(\d+)(?:\s*=\s*(-?\d+))?$/.exec(f);
|
|
27
|
+
if (mod) {
|
|
28
|
+
const n = Number(mod[1]);
|
|
29
|
+
if (!n) return true;
|
|
30
|
+
const remainder = mod[2] !== void 0 ? Number(mod[2]) : 0;
|
|
31
|
+
return Number(value) % n === remainder;
|
|
32
|
+
}
|
|
33
|
+
const cmp = /^(>=|<=|!=|<>|>|<|=)\s*(-?\d+(?:\.\d+)?)$/.exec(f);
|
|
34
|
+
if (cmp) {
|
|
35
|
+
const num = Number(value);
|
|
36
|
+
if (Number.isNaN(num)) return false;
|
|
37
|
+
const rhs = Number(cmp[2]);
|
|
38
|
+
switch (cmp[1]) {
|
|
39
|
+
case ">": return num > rhs;
|
|
40
|
+
case ">=": return num >= rhs;
|
|
41
|
+
case "<": return num < rhs;
|
|
42
|
+
case "<=": return num <= rhs;
|
|
43
|
+
case "=": return num === rhs;
|
|
44
|
+
default: return num !== rhs;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return value.toLowerCase().includes(f.toLowerCase());
|
|
53
48
|
}
|
|
54
49
|
function colToLabel(col) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
50
|
+
let n = col + 1;
|
|
51
|
+
let s = "";
|
|
52
|
+
while (n > 0) {
|
|
53
|
+
const rem = (n - 1) % 26;
|
|
54
|
+
s = String.fromCharCode(65 + rem) + s;
|
|
55
|
+
n = Math.floor((n - 1) / 26);
|
|
56
|
+
}
|
|
57
|
+
return s;
|
|
63
58
|
}
|
|
64
59
|
function labelToCol(label) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
60
|
+
let n = 0;
|
|
61
|
+
for (const ch of label.toUpperCase()) n = n * 26 + (ch.charCodeAt(0) - 64);
|
|
62
|
+
return n - 1;
|
|
68
63
|
}
|
|
64
|
+
/** `"B3"` → `{ row: 2, col: 1 }`, or `null` if not a valid A1 address. */
|
|
69
65
|
function parseAddress(address) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
66
|
+
const m = /^([A-Za-z]+)(\d+)$/.exec(address.trim());
|
|
67
|
+
if (!m) return null;
|
|
68
|
+
return {
|
|
69
|
+
row: Number(m[2]) - 1,
|
|
70
|
+
col: labelToCol(m[1])
|
|
71
|
+
};
|
|
73
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* The headless spreadsheet engine: dimensions, a per-cell raw-value store synced
|
|
75
|
+
* from `rows().data`, a 2D range-selection model, an edit buffer, and the bulk
|
|
76
|
+
* operations (fill, clear, TSV copy/paste). No DOM, no JSX — the component wires
|
|
77
|
+
* events to these actions and reads the accessors to render.
|
|
78
|
+
*/
|
|
74
79
|
function createBulkEditorState(options) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
80
|
+
const evaluate = options.evaluate ?? ((ctx) => ctx.raw);
|
|
81
|
+
const [columnOrder, setColumnOrderState] = createSignal(null);
|
|
82
|
+
const columns = createMemo(() => {
|
|
83
|
+
const cols = options.columns();
|
|
84
|
+
const order = columnOrder();
|
|
85
|
+
if (!order) return cols;
|
|
86
|
+
const byKey = new Map(cols.map((c) => [c.key, c]));
|
|
87
|
+
const ordered = order.map((k) => byKey.get(k)).filter((c) => !!c);
|
|
88
|
+
for (const c of cols) if (!order.includes(c.key)) ordered.push(c);
|
|
89
|
+
return ordered;
|
|
90
|
+
});
|
|
91
|
+
const baseData = createMemo(() => options.rows().filter((r) => !r.isGroup));
|
|
92
|
+
const rowById = createMemo(() => {
|
|
93
|
+
const m = /* @__PURE__ */ new Map();
|
|
94
|
+
for (const r of options.rows()) if (!r.isGroup) m.set(r.id, r);
|
|
95
|
+
return m;
|
|
96
|
+
});
|
|
97
|
+
const dataRows = createMemo(() => applyView());
|
|
98
|
+
const rowCount = createMemo(() => dataRows().length);
|
|
99
|
+
const colCount = createMemo(() => columns().length);
|
|
100
|
+
const rowAt = (r) => dataRows()[r];
|
|
101
|
+
const colAt = (c) => columns()[c];
|
|
102
|
+
const cr = (n) => clampTo(n, rowCount() - 1);
|
|
103
|
+
const cc = (n) => clampTo(n, colCount() - 1);
|
|
104
|
+
const cellAddress = (r, c) => `${colToLabel(c)}${r + 1}`;
|
|
105
|
+
const cellSigs = /* @__PURE__ */ new Map();
|
|
106
|
+
const key = (rowId, colKey) => `${rowId}::${colKey}`;
|
|
107
|
+
const ensure = (rowId, colKey, init) => {
|
|
108
|
+
const k = key(rowId, colKey);
|
|
109
|
+
let sig = cellSigs.get(k);
|
|
110
|
+
if (!sig) {
|
|
111
|
+
sig = createSignal(init);
|
|
112
|
+
cellSigs.set(k, sig);
|
|
113
|
+
}
|
|
114
|
+
return sig;
|
|
115
|
+
};
|
|
116
|
+
untrack(() => {
|
|
117
|
+
const cols = options.columns();
|
|
118
|
+
for (const row of options.rows()) {
|
|
119
|
+
if (row.isGroup || !row.data) continue;
|
|
120
|
+
for (const col of cols) ensure(row.id, col.key, row.data[col.key] ?? "");
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
createEffect(() => {
|
|
124
|
+
const cols = columns();
|
|
125
|
+
for (const row of options.rows()) {
|
|
126
|
+
if (row.isGroup || !row.data) continue;
|
|
127
|
+
for (const col of cols) {
|
|
128
|
+
const val = row.data[col.key] ?? "";
|
|
129
|
+
const sig = cellSigs.get(key(row.id, col.key));
|
|
130
|
+
if (!sig) ensure(row.id, col.key, val);
|
|
131
|
+
else if (untrack(sig[0]) !== val) sig[1](val);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
const getRaw = (rowId, colKey) => {
|
|
136
|
+
const sig = cellSigs.get(key(rowId, colKey));
|
|
137
|
+
return sig ? sig[0]() : "";
|
|
138
|
+
};
|
|
139
|
+
const isColumnReadOnly = (col) => !!col.readOnly || !!col.compute;
|
|
140
|
+
const [lockedCells, setLockedCells] = createSignal(/* @__PURE__ */ new Set());
|
|
141
|
+
const isCellLocked = (rowId, colKey) => lockedCells().has(key(rowId, colKey));
|
|
142
|
+
const isCellLockedAt = (r, c) => {
|
|
143
|
+
const row = rowAt(r);
|
|
144
|
+
const col = colAt(c);
|
|
145
|
+
return row && col ? isCellLocked(row.id, col.key) : false;
|
|
146
|
+
};
|
|
147
|
+
const setCellLocked = (rowId, colKey, locked) => setLockedCells((prev) => {
|
|
148
|
+
const next = new Set(prev);
|
|
149
|
+
if (locked) next.add(key(rowId, colKey));
|
|
150
|
+
else next.delete(key(rowId, colKey));
|
|
151
|
+
return next;
|
|
152
|
+
});
|
|
153
|
+
const toggleCellLock = (rowId, colKey) => setCellLocked(rowId, colKey, !isCellLocked(rowId, colKey));
|
|
154
|
+
const lockableCols = () => columns().filter((c) => !isColumnReadOnly(c));
|
|
155
|
+
const getRowLockState = (rowId) => {
|
|
156
|
+
const cols = lockableCols();
|
|
157
|
+
if (!cols.length) return "none";
|
|
158
|
+
let locked = 0;
|
|
159
|
+
for (const col of cols) if (isCellLocked(rowId, col.key)) locked++;
|
|
160
|
+
return locked === 0 ? "none" : locked === cols.length ? "all" : "some";
|
|
161
|
+
};
|
|
162
|
+
const toggleRowLock = (rowId) => {
|
|
163
|
+
const lockAll = getRowLockState(rowId) !== "all";
|
|
164
|
+
setLockedCells((prev) => {
|
|
165
|
+
const next = new Set(prev);
|
|
166
|
+
for (const col of lockableCols()) {
|
|
167
|
+
const k = key(rowId, col.key);
|
|
168
|
+
if (lockAll) next.add(k);
|
|
169
|
+
else next.delete(k);
|
|
170
|
+
}
|
|
171
|
+
return next;
|
|
172
|
+
});
|
|
173
|
+
};
|
|
174
|
+
const getColumnLockState = (colKey) => {
|
|
175
|
+
const col = columns().find((c) => c.key === colKey);
|
|
176
|
+
const rows = baseData();
|
|
177
|
+
if (!col || isColumnReadOnly(col) || !rows.length) return "none";
|
|
178
|
+
let locked = 0;
|
|
179
|
+
for (const row of rows) if (isCellLocked(row.id, colKey)) locked++;
|
|
180
|
+
return locked === 0 ? "none" : locked === rows.length ? "all" : "some";
|
|
181
|
+
};
|
|
182
|
+
const toggleColumnLock = (colKey) => {
|
|
183
|
+
const col = columns().find((c) => c.key === colKey);
|
|
184
|
+
if (!col || isColumnReadOnly(col)) return;
|
|
185
|
+
const lockAll = getColumnLockState(colKey) !== "all";
|
|
186
|
+
setLockedCells((prev) => {
|
|
187
|
+
const next = new Set(prev);
|
|
188
|
+
for (const row of baseData()) {
|
|
189
|
+
const k = key(row.id, colKey);
|
|
190
|
+
if (lockAll) next.add(k);
|
|
191
|
+
else next.delete(k);
|
|
192
|
+
}
|
|
193
|
+
return next;
|
|
194
|
+
});
|
|
195
|
+
};
|
|
196
|
+
const lockSelection = (locked) => {
|
|
197
|
+
const rng = untrack(selectionRange);
|
|
198
|
+
if (!rng) return;
|
|
199
|
+
setLockedCells((prev) => {
|
|
200
|
+
const next = new Set(prev);
|
|
201
|
+
for (let r = rng.r1; r <= rng.r2; r++) for (let c = rng.c1; c <= rng.c2; c++) {
|
|
202
|
+
const row = rowAt(r);
|
|
203
|
+
const col = colAt(c);
|
|
204
|
+
if (!row || !col || isColumnReadOnly(col)) continue;
|
|
205
|
+
const k = key(row.id, col.key);
|
|
206
|
+
if (locked) next.add(k);
|
|
207
|
+
else next.delete(k);
|
|
208
|
+
}
|
|
209
|
+
return next;
|
|
210
|
+
});
|
|
211
|
+
};
|
|
212
|
+
const resolveRef = (address, visited) => {
|
|
213
|
+
const pos = parseAddress(address);
|
|
214
|
+
if (!pos) return "";
|
|
215
|
+
const target = baseData()[pos.row];
|
|
216
|
+
const targetCol = columns()[pos.col];
|
|
217
|
+
if (!target || !targetCol) return "";
|
|
218
|
+
return getComputed(target.id, targetCol.key, visited);
|
|
219
|
+
};
|
|
220
|
+
const getComputed = (rowId, colKey, seen) => {
|
|
221
|
+
const row = rowById().get(rowId);
|
|
222
|
+
const col = columns().find((c) => c.key === colKey);
|
|
223
|
+
if (!row || !col) return getRaw(rowId, colKey);
|
|
224
|
+
const visited = seen ?? /* @__PURE__ */ new Set();
|
|
225
|
+
const self = key(rowId, colKey);
|
|
226
|
+
if (visited.has(self)) return ERR.cycle;
|
|
227
|
+
visited.add(self);
|
|
228
|
+
try {
|
|
229
|
+
if (col.compute) return col.compute({
|
|
230
|
+
row,
|
|
231
|
+
col,
|
|
232
|
+
get: (a) => resolveRef(a, visited)
|
|
233
|
+
});
|
|
234
|
+
return evaluate({
|
|
235
|
+
raw: getRaw(rowId, colKey),
|
|
236
|
+
row,
|
|
237
|
+
col,
|
|
238
|
+
get: (a) => resolveRef(a, visited)
|
|
239
|
+
});
|
|
240
|
+
} finally {
|
|
241
|
+
visited.delete(self);
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
const getDisplay = (rowId, colKey) => {
|
|
245
|
+
const computed = getComputed(rowId, colKey);
|
|
246
|
+
const col = columns().find((c) => c.key === colKey);
|
|
247
|
+
const row = rowById().get(rowId);
|
|
248
|
+
return col?.format && row ? col.format(computed, {
|
|
249
|
+
row,
|
|
250
|
+
col
|
|
251
|
+
}) : computed;
|
|
252
|
+
};
|
|
253
|
+
const [sort, setSortState] = createSignal(null);
|
|
254
|
+
const [columnFilters, setColumnFilters] = createSignal(/* @__PURE__ */ new Map());
|
|
255
|
+
const compareRows = (a, b, colKey) => {
|
|
256
|
+
const col = columns().find((c) => c.key === colKey);
|
|
257
|
+
const va = getComputed(a.id, colKey);
|
|
258
|
+
const vb = getComputed(b.id, colKey);
|
|
259
|
+
if (col?.type === "number") return (Number(va) || 0) - (Number(vb) || 0);
|
|
260
|
+
return va.localeCompare(vb, void 0, { numeric: true });
|
|
261
|
+
};
|
|
262
|
+
function applyView() {
|
|
263
|
+
let rows = baseData();
|
|
264
|
+
const activeFilters = [...columnFilters().entries()].filter(([, t]) => t);
|
|
265
|
+
if (activeFilters.length) rows = rows.filter((row) => activeFilters.every(([colKey, text]) => matchesFilter(getComputed(row.id, colKey), text)));
|
|
266
|
+
const s = sort();
|
|
267
|
+
if (s) rows = [...rows].sort((a, b) => compareRows(a, b, s.colKey) * (s.dir === "asc" ? 1 : -1));
|
|
268
|
+
return rows;
|
|
269
|
+
}
|
|
270
|
+
const isViewActive = createMemo(() => sort() !== null || [...columnFilters().values()].some((t) => t));
|
|
271
|
+
const viewRows = createMemo(() => isViewActive() ? applyView() : options.rows());
|
|
272
|
+
const sortBy = (colKey) => setSortState((cur) => !cur || cur.colKey !== colKey ? {
|
|
273
|
+
colKey,
|
|
274
|
+
dir: "asc"
|
|
275
|
+
} : cur.dir === "asc" ? {
|
|
276
|
+
colKey,
|
|
277
|
+
dir: "desc"
|
|
278
|
+
} : null);
|
|
279
|
+
const setSort = (next) => setSortState(next);
|
|
280
|
+
const getColumnFilter = (colKey) => columnFilters().get(colKey) ?? "";
|
|
281
|
+
const setColumnFilter = (colKey, text) => setColumnFilters((prev) => {
|
|
282
|
+
const next = new Map(prev);
|
|
283
|
+
if (text) next.set(colKey, text);
|
|
284
|
+
else next.delete(colKey);
|
|
285
|
+
return next;
|
|
286
|
+
});
|
|
287
|
+
const clearFilters = () => setColumnFilters(/* @__PURE__ */ new Map());
|
|
288
|
+
const [columnWidths, setColumnWidths] = createSignal(/* @__PURE__ */ new Map());
|
|
289
|
+
const setColumnWidth = (colKey, width) => setColumnWidths((prev) => {
|
|
290
|
+
const next = new Map(prev);
|
|
291
|
+
next.set(colKey, Math.max(MIN_COL_WIDTH, Math.round(width)));
|
|
292
|
+
return next;
|
|
293
|
+
});
|
|
294
|
+
const resetColumnWidth = (colKey) => setColumnWidths((prev) => {
|
|
295
|
+
if (!prev.has(colKey)) return prev;
|
|
296
|
+
const next = new Map(prev);
|
|
297
|
+
next.delete(colKey);
|
|
298
|
+
return next;
|
|
299
|
+
});
|
|
300
|
+
const setColumnOrder = (keys) => setColumnOrderState(keys);
|
|
301
|
+
const moveColumn = (fromKey, toKey) => {
|
|
302
|
+
const order = columns().map((c) => c.key);
|
|
303
|
+
const from = order.indexOf(fromKey);
|
|
304
|
+
const to = order.indexOf(toKey);
|
|
305
|
+
if (from < 0 || to < 0 || from === to) return;
|
|
306
|
+
order.splice(to, 0, order.splice(from, 1)[0]);
|
|
307
|
+
setColumnOrderState(order);
|
|
308
|
+
};
|
|
309
|
+
const getColumnAggregate = (colKey) => {
|
|
310
|
+
const col = columns().find((c) => c.key === colKey);
|
|
311
|
+
if (!col?.aggregate) return "";
|
|
312
|
+
const rows = dataRows();
|
|
313
|
+
const values = rows.map((r) => getComputed(r.id, colKey));
|
|
314
|
+
return typeof col.aggregate === "function" ? col.aggregate(values, {
|
|
315
|
+
col,
|
|
316
|
+
rows
|
|
317
|
+
}) : aggregateValues(col.aggregate, values);
|
|
318
|
+
};
|
|
319
|
+
const getRawAt = (r, c) => {
|
|
320
|
+
const row = rowAt(r);
|
|
321
|
+
const col = colAt(c);
|
|
322
|
+
return row && col ? getRaw(row.id, col.key) : "";
|
|
323
|
+
};
|
|
324
|
+
const getComputedAt = (r, c) => {
|
|
325
|
+
const row = rowAt(r);
|
|
326
|
+
const col = colAt(c);
|
|
327
|
+
return row && col ? getComputed(row.id, col.key) : "";
|
|
328
|
+
};
|
|
329
|
+
const getDisplayAt = (r, c) => {
|
|
330
|
+
const row = rowAt(r);
|
|
331
|
+
const col = colAt(c);
|
|
332
|
+
return row && col ? getDisplay(row.id, col.key) : "";
|
|
333
|
+
};
|
|
334
|
+
const [errors, setErrors] = createSignal(/* @__PURE__ */ new Map());
|
|
335
|
+
const getError = (rowId, colKey) => errors().get(key(rowId, colKey));
|
|
336
|
+
const getErrorAt = (r, c) => {
|
|
337
|
+
const row = rowAt(r);
|
|
338
|
+
const col = colAt(c);
|
|
339
|
+
return row && col ? getError(row.id, col.key) : void 0;
|
|
340
|
+
};
|
|
341
|
+
const hasErrors = createMemo(() => errors().size > 0);
|
|
342
|
+
const [undoStack, setUndoStack] = createSignal([]);
|
|
343
|
+
const [redoStack, setRedoStack] = createSignal([]);
|
|
344
|
+
let recording = null;
|
|
345
|
+
const withHistory = (fn) => {
|
|
346
|
+
if (recording) return void fn();
|
|
347
|
+
recording = [];
|
|
348
|
+
batch(fn);
|
|
349
|
+
const changes = recording.filter((ch) => ch.before !== ch.after);
|
|
350
|
+
recording = null;
|
|
351
|
+
if (changes.length) {
|
|
352
|
+
setUndoStack((s) => [...s, changes]);
|
|
353
|
+
setRedoStack([]);
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
const writeCell = (rowId, colKey, value) => {
|
|
357
|
+
const before = getRaw(rowId, colKey);
|
|
358
|
+
if (recording) recording.push({
|
|
359
|
+
rowId,
|
|
360
|
+
colKey,
|
|
361
|
+
before,
|
|
362
|
+
after: value
|
|
363
|
+
});
|
|
364
|
+
ensure(rowId, colKey, value)[1](value);
|
|
365
|
+
const col = columns().find((c) => c.key === colKey);
|
|
366
|
+
const row = rowById().get(rowId);
|
|
367
|
+
if (col?.validate && row) {
|
|
368
|
+
const message = col.validate(value, {
|
|
369
|
+
row,
|
|
370
|
+
col
|
|
371
|
+
}) || void 0;
|
|
372
|
+
setErrors((prev) => {
|
|
373
|
+
const next = new Map(prev);
|
|
374
|
+
if (message) next.set(key(rowId, colKey), message);
|
|
375
|
+
else next.delete(key(rowId, colKey));
|
|
376
|
+
return next;
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
options.onCellChange?.(rowId, colKey, value);
|
|
380
|
+
};
|
|
381
|
+
const applyWrite = (r, c, value) => {
|
|
382
|
+
const row = rowAt(r);
|
|
383
|
+
const col = colAt(c);
|
|
384
|
+
if (!row || !col || isColumnReadOnly(col) || isCellLocked(row.id, col.key)) return;
|
|
385
|
+
writeCell(row.id, col.key, value);
|
|
386
|
+
};
|
|
387
|
+
const setCellAt = (r, c, value) => withHistory(() => applyWrite(r, c, value));
|
|
388
|
+
const undo = () => {
|
|
389
|
+
const stack = undoStack();
|
|
390
|
+
if (!stack.length) return;
|
|
391
|
+
const changes = stack[stack.length - 1];
|
|
392
|
+
batch(() => {
|
|
393
|
+
for (const ch of changes) writeCell(ch.rowId, ch.colKey, ch.before);
|
|
394
|
+
});
|
|
395
|
+
setUndoStack((s) => s.slice(0, -1));
|
|
396
|
+
setRedoStack((s) => [...s, changes]);
|
|
397
|
+
};
|
|
398
|
+
const redo = () => {
|
|
399
|
+
const stack = redoStack();
|
|
400
|
+
if (!stack.length) return;
|
|
401
|
+
const changes = stack[stack.length - 1];
|
|
402
|
+
batch(() => {
|
|
403
|
+
for (const ch of changes) writeCell(ch.rowId, ch.colKey, ch.after);
|
|
404
|
+
});
|
|
405
|
+
setRedoStack((s) => s.slice(0, -1));
|
|
406
|
+
setUndoStack((s) => [...s, changes]);
|
|
407
|
+
};
|
|
408
|
+
const canUndo = createMemo(() => undoStack().length > 0);
|
|
409
|
+
const canRedo = createMemo(() => redoStack().length > 0);
|
|
410
|
+
const [selection, setSelection] = createSignal(null);
|
|
411
|
+
const selectionRange = createMemo(() => {
|
|
412
|
+
const s = selection();
|
|
413
|
+
if (!s) return null;
|
|
414
|
+
return {
|
|
415
|
+
r1: Math.min(s.anchor.row, s.focus.row),
|
|
416
|
+
r2: Math.max(s.anchor.row, s.focus.row),
|
|
417
|
+
c1: Math.min(s.anchor.col, s.focus.col),
|
|
418
|
+
c2: Math.max(s.anchor.col, s.focus.col)
|
|
419
|
+
};
|
|
420
|
+
});
|
|
421
|
+
const hasSelection = createMemo(() => selection() !== null);
|
|
422
|
+
const isSelected = (r, c) => {
|
|
423
|
+
const rng = selectionRange();
|
|
424
|
+
return !!rng && r >= rng.r1 && r <= rng.r2 && c >= rng.c1 && c <= rng.c2;
|
|
425
|
+
};
|
|
426
|
+
const isAnchor = (r, c) => {
|
|
427
|
+
const s = selection();
|
|
428
|
+
return !!s && s.anchor.row === r && s.anchor.col === c;
|
|
429
|
+
};
|
|
430
|
+
const selectCell = (r, c, extend = false) => {
|
|
431
|
+
r = cr(r);
|
|
432
|
+
c = cc(c);
|
|
433
|
+
setSelection((p) => extend && p ? {
|
|
434
|
+
anchor: p.anchor,
|
|
435
|
+
focus: {
|
|
436
|
+
row: r,
|
|
437
|
+
col: c
|
|
438
|
+
}
|
|
439
|
+
} : {
|
|
440
|
+
anchor: {
|
|
441
|
+
row: r,
|
|
442
|
+
col: c
|
|
443
|
+
},
|
|
444
|
+
focus: {
|
|
445
|
+
row: r,
|
|
446
|
+
col: c
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
};
|
|
450
|
+
const move = (dr, dc, extend = false) => {
|
|
451
|
+
if (rowCount() === 0 || colCount() === 0) return;
|
|
452
|
+
const cur = selection();
|
|
453
|
+
const from = extend && cur ? cur.focus : cur ? cur.anchor : {
|
|
454
|
+
row: 0,
|
|
455
|
+
col: 0
|
|
456
|
+
};
|
|
457
|
+
const nr = cr(from.row + dr);
|
|
458
|
+
const nc = cc(from.col + dc);
|
|
459
|
+
if (extend && cur) setSelection({
|
|
460
|
+
anchor: cur.anchor,
|
|
461
|
+
focus: {
|
|
462
|
+
row: nr,
|
|
463
|
+
col: nc
|
|
464
|
+
}
|
|
465
|
+
});
|
|
466
|
+
else setSelection({
|
|
467
|
+
anchor: {
|
|
468
|
+
row: nr,
|
|
469
|
+
col: nc
|
|
470
|
+
},
|
|
471
|
+
focus: {
|
|
472
|
+
row: nr,
|
|
473
|
+
col: nc
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
};
|
|
477
|
+
const selectRow = (r) => {
|
|
478
|
+
if (colCount() === 0) return;
|
|
479
|
+
r = cr(r);
|
|
480
|
+
setSelection({
|
|
481
|
+
anchor: {
|
|
482
|
+
row: r,
|
|
483
|
+
col: 0
|
|
484
|
+
},
|
|
485
|
+
focus: {
|
|
486
|
+
row: r,
|
|
487
|
+
col: colCount() - 1
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
};
|
|
491
|
+
const selectColumn = (c) => {
|
|
492
|
+
if (rowCount() === 0) return;
|
|
493
|
+
c = cc(c);
|
|
494
|
+
setSelection({
|
|
495
|
+
anchor: {
|
|
496
|
+
row: 0,
|
|
497
|
+
col: c
|
|
498
|
+
},
|
|
499
|
+
focus: {
|
|
500
|
+
row: rowCount() - 1,
|
|
501
|
+
col: c
|
|
502
|
+
}
|
|
503
|
+
});
|
|
504
|
+
};
|
|
505
|
+
const selectAll = () => {
|
|
506
|
+
if (rowCount() === 0 || colCount() === 0) return;
|
|
507
|
+
setSelection({
|
|
508
|
+
anchor: {
|
|
509
|
+
row: 0,
|
|
510
|
+
col: 0
|
|
511
|
+
},
|
|
512
|
+
focus: {
|
|
513
|
+
row: rowCount() - 1,
|
|
514
|
+
col: colCount() - 1
|
|
515
|
+
}
|
|
516
|
+
});
|
|
517
|
+
};
|
|
518
|
+
const clearSelection = () => setSelection(null);
|
|
519
|
+
const [editing, setEditing] = createSignal(null);
|
|
520
|
+
const [editBuffer, setEditBuffer] = createSignal("");
|
|
521
|
+
const isEditing = (r, c) => {
|
|
522
|
+
const e = editing();
|
|
523
|
+
return !!e && e.row === r && e.col === c;
|
|
524
|
+
};
|
|
525
|
+
const startEdit = (r, c, initChar) => {
|
|
526
|
+
const col = colAt(c);
|
|
527
|
+
const row = rowAt(r);
|
|
528
|
+
if (!col || !row || isColumnReadOnly(col) || col.type === "checkbox" || isCellLocked(row.id, col.key)) return false;
|
|
529
|
+
batch(() => {
|
|
530
|
+
setEditing({
|
|
531
|
+
row: r,
|
|
532
|
+
col: c
|
|
533
|
+
});
|
|
534
|
+
setEditBuffer(initChar !== void 0 ? initChar : getRaw(row.id, col.key));
|
|
535
|
+
});
|
|
536
|
+
return true;
|
|
537
|
+
};
|
|
538
|
+
const commitEdit = () => {
|
|
539
|
+
const ed = untrack(editing);
|
|
540
|
+
if (!ed) return;
|
|
541
|
+
const val = untrack(editBuffer);
|
|
542
|
+
const rng = untrack(selectionRange);
|
|
543
|
+
setEditing(null);
|
|
544
|
+
withHistory(() => {
|
|
545
|
+
if (rng && (rng.r2 > rng.r1 || rng.c2 > rng.c1)) for (let r = rng.r1; r <= rng.r2; r++) for (let c = rng.c1; c <= rng.c2; c++) applyWrite(r, c, val);
|
|
546
|
+
else applyWrite(ed.row, ed.col, val);
|
|
547
|
+
});
|
|
548
|
+
};
|
|
549
|
+
const cancelEdit = () => setEditing(null);
|
|
550
|
+
const clearRange = () => {
|
|
551
|
+
const rng = untrack(selectionRange);
|
|
552
|
+
if (!rng) return;
|
|
553
|
+
withHistory(() => {
|
|
554
|
+
for (let r = rng.r1; r <= rng.r2; r++) for (let c = rng.c1; c <= rng.c2; c++) applyWrite(r, c, "");
|
|
555
|
+
});
|
|
556
|
+
};
|
|
557
|
+
const fillDown = () => {
|
|
558
|
+
const rng = untrack(selectionRange);
|
|
559
|
+
const s = untrack(selection);
|
|
560
|
+
if (!rng || !s) return;
|
|
561
|
+
withHistory(() => {
|
|
562
|
+
for (let c = rng.c1; c <= rng.c2; c++) {
|
|
563
|
+
const val = getRawAt(s.anchor.row, c);
|
|
564
|
+
for (let r = rng.r1; r <= rng.r2; r++) {
|
|
565
|
+
if (r === s.anchor.row) continue;
|
|
566
|
+
applyWrite(r, c, val);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
});
|
|
570
|
+
};
|
|
571
|
+
const toggleCheckbox = (r, c) => {
|
|
572
|
+
const row = rowAt(r);
|
|
573
|
+
const col = colAt(c);
|
|
574
|
+
if (!row || !col || col.type !== "checkbox" || col.readOnly) return;
|
|
575
|
+
if (col.disable?.({
|
|
576
|
+
row,
|
|
577
|
+
col
|
|
578
|
+
}) || isCellLocked(row.id, col.key)) return;
|
|
579
|
+
const next = getRaw(row.id, col.key) === "1" ? "0" : "1";
|
|
580
|
+
withHistory(() => writeCell(row.id, col.key, next));
|
|
581
|
+
};
|
|
582
|
+
const getColumnCheckedState = (colKey) => {
|
|
583
|
+
const col = columns().find((c) => c.key === colKey);
|
|
584
|
+
const rows = baseData();
|
|
585
|
+
if (!col || col.type !== "checkbox" || !rows.length) return "none";
|
|
586
|
+
let checked = 0;
|
|
587
|
+
for (const row of rows) if (getRaw(row.id, colKey) === "1") checked++;
|
|
588
|
+
return checked === 0 ? "none" : checked === rows.length ? "all" : "some";
|
|
589
|
+
};
|
|
590
|
+
const toggleColumnChecked = (colKey) => {
|
|
591
|
+
const col = columns().find((c) => c.key === colKey);
|
|
592
|
+
if (!col || col.type !== "checkbox") return;
|
|
593
|
+
const value = getColumnCheckedState(colKey) === "all" ? "0" : "1";
|
|
594
|
+
withHistory(() => {
|
|
595
|
+
for (const row of baseData()) {
|
|
596
|
+
if (isCellLocked(row.id, colKey) || col.disable?.({
|
|
597
|
+
row,
|
|
598
|
+
col
|
|
599
|
+
})) continue;
|
|
600
|
+
writeCell(row.id, colKey, value);
|
|
601
|
+
}
|
|
602
|
+
});
|
|
603
|
+
};
|
|
604
|
+
const getSelectionTSV = () => {
|
|
605
|
+
const rng = untrack(selectionRange);
|
|
606
|
+
if (!rng) return "";
|
|
607
|
+
const lines = [];
|
|
608
|
+
for (let r = rng.r1; r <= rng.r2; r++) {
|
|
609
|
+
const cells = [];
|
|
610
|
+
for (let c = rng.c1; c <= rng.c2; c++) cells.push(getComputedAt(r, c));
|
|
611
|
+
lines.push(cells.join(" "));
|
|
612
|
+
}
|
|
613
|
+
return lines.join("\n");
|
|
614
|
+
};
|
|
615
|
+
const pasteTSV = (text) => {
|
|
616
|
+
const s = untrack(selection);
|
|
617
|
+
if (!s || !text) return;
|
|
618
|
+
const grid = text.replace(/\r\n?/g, "\n").replace(/\n$/, "").split("\n").map((line) => line.split(" "));
|
|
619
|
+
withHistory(() => {
|
|
620
|
+
for (let ri = 0; ri < grid.length; ri++) for (let ci = 0; ci < grid[ri].length; ci++) applyWrite(s.anchor.row + ri, s.anchor.col + ci, grid[ri][ci]);
|
|
621
|
+
});
|
|
622
|
+
};
|
|
623
|
+
untrack(() => {
|
|
624
|
+
const cols = columns();
|
|
625
|
+
const initial = /* @__PURE__ */ new Map();
|
|
626
|
+
for (const row of dataRows()) for (const col of cols) {
|
|
627
|
+
if (!col.validate) continue;
|
|
628
|
+
const msg = col.validate(getRaw(row.id, col.key), {
|
|
629
|
+
row,
|
|
630
|
+
col
|
|
631
|
+
}) || void 0;
|
|
632
|
+
if (msg) initial.set(key(row.id, col.key), msg);
|
|
633
|
+
}
|
|
634
|
+
if (initial.size) setErrors(initial);
|
|
635
|
+
});
|
|
636
|
+
return {
|
|
637
|
+
columns,
|
|
638
|
+
dataRows,
|
|
639
|
+
viewRows,
|
|
640
|
+
rowCount,
|
|
641
|
+
colCount,
|
|
642
|
+
sort,
|
|
643
|
+
sortBy,
|
|
644
|
+
setSort,
|
|
645
|
+
columnWidths,
|
|
646
|
+
setColumnWidth,
|
|
647
|
+
resetColumnWidth,
|
|
648
|
+
columnOrder,
|
|
649
|
+
setColumnOrder,
|
|
650
|
+
moveColumn,
|
|
651
|
+
getColumnAggregate,
|
|
652
|
+
columnFilters,
|
|
653
|
+
getColumnFilter,
|
|
654
|
+
setColumnFilter,
|
|
655
|
+
clearFilters,
|
|
656
|
+
rowAt,
|
|
657
|
+
colAt,
|
|
658
|
+
cellAddress,
|
|
659
|
+
getRaw,
|
|
660
|
+
getComputed: (rowId, colKey) => getComputed(rowId, colKey),
|
|
661
|
+
getDisplay,
|
|
662
|
+
getRawAt,
|
|
663
|
+
getComputedAt,
|
|
664
|
+
getDisplayAt,
|
|
665
|
+
setCellAt,
|
|
666
|
+
isColumnReadOnly,
|
|
667
|
+
lockedCells,
|
|
668
|
+
isCellLocked,
|
|
669
|
+
isCellLockedAt,
|
|
670
|
+
setCellLocked,
|
|
671
|
+
toggleCellLock,
|
|
672
|
+
getRowLockState,
|
|
673
|
+
toggleRowLock,
|
|
674
|
+
getColumnLockState,
|
|
675
|
+
toggleColumnLock,
|
|
676
|
+
lockSelection,
|
|
677
|
+
errors,
|
|
678
|
+
getError,
|
|
679
|
+
getErrorAt,
|
|
680
|
+
hasErrors,
|
|
681
|
+
selection,
|
|
682
|
+
selectionRange,
|
|
683
|
+
hasSelection,
|
|
684
|
+
isSelected,
|
|
685
|
+
isAnchor,
|
|
686
|
+
selectCell,
|
|
687
|
+
move,
|
|
688
|
+
selectRow,
|
|
689
|
+
selectColumn,
|
|
690
|
+
selectAll,
|
|
691
|
+
clearSelection,
|
|
692
|
+
editing,
|
|
693
|
+
editBuffer,
|
|
694
|
+
setEditBuffer,
|
|
695
|
+
isEditing,
|
|
696
|
+
startEdit,
|
|
697
|
+
commitEdit,
|
|
698
|
+
cancelEdit,
|
|
699
|
+
clearRange,
|
|
700
|
+
fillDown,
|
|
701
|
+
toggleCheckbox,
|
|
702
|
+
getColumnCheckedState,
|
|
703
|
+
toggleColumnChecked,
|
|
704
|
+
getSelectionTSV,
|
|
705
|
+
pasteTSV,
|
|
706
|
+
undo,
|
|
707
|
+
redo,
|
|
708
|
+
canUndo,
|
|
709
|
+
canRedo
|
|
710
|
+
};
|
|
620
711
|
}
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
createBulkEditorState,
|
|
624
|
-
labelToCol,
|
|
625
|
-
matchesFilter,
|
|
626
|
-
parseAddress
|
|
627
|
-
};
|
|
712
|
+
//#endregion
|
|
713
|
+
export { colToLabel, createBulkEditorState, labelToCol, matchesFilter, parseAddress };
|