y11n-spreadsheet 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +303 -0
  2. package/dist/index.js +1470 -0
  3. package/package.json +49 -0
package/dist/index.js ADDED
@@ -0,0 +1,1470 @@
1
+ import { css as k, LitElement as V, html as _, nothing as x } from "lit";
2
+ import { property as w, state as E, customElement as D, query as A } from "lit/decorators.js";
3
+ import { repeat as N } from "lit/directives/repeat.js";
4
+ function y(i, e) {
5
+ return `${i}:${e}`;
6
+ }
7
+ function j(i) {
8
+ const [e, t] = i.split(":").map(Number);
9
+ return { row: e, col: t };
10
+ }
11
+ function R(i) {
12
+ let e = "", t = i;
13
+ for (; t >= 0; )
14
+ e = String.fromCharCode(t % 26 + 65) + e, t = Math.floor(t / 26) - 1;
15
+ return e;
16
+ }
17
+ function M(i) {
18
+ let e = 0;
19
+ for (let t = 0; t < i.length; t++)
20
+ e = e * 26 + (i.charCodeAt(t) - 64);
21
+ return e - 1;
22
+ }
23
+ function S(i) {
24
+ const e = i.match(/^([A-Z]+)(\d+)$/i);
25
+ if (!e) throw new Error(`Invalid cell reference: ${i}`);
26
+ const t = M(e[1].toUpperCase());
27
+ return { row: parseInt(e[2], 10) - 1, col: t };
28
+ }
29
+ function O(i) {
30
+ return `${R(i.col)}${i.row + 1}`;
31
+ }
32
+ class B {
33
+ constructor(e, t, s) {
34
+ this.anchor = { row: 0, col: 0 }, this.head = { row: 0, col: 0 }, this._isDragging = !1, this.host = e, this._maxRows = t, this._maxCols = s, e.addController(this);
35
+ }
36
+ hostConnected() {
37
+ }
38
+ hostDisconnected() {
39
+ this._isDragging = !1;
40
+ }
41
+ /** Update grid bounds (e.g., when rows/cols properties change) */
42
+ setBounds(e, t) {
43
+ this._maxRows = e, this._maxCols = t;
44
+ }
45
+ /** Get the normalized selection range (top-left to bottom-right) */
46
+ get range() {
47
+ return {
48
+ start: {
49
+ row: Math.min(this.anchor.row, this.head.row),
50
+ col: Math.min(this.anchor.col, this.head.col)
51
+ },
52
+ end: {
53
+ row: Math.max(this.anchor.row, this.head.row),
54
+ col: Math.max(this.anchor.col, this.head.col)
55
+ }
56
+ };
57
+ }
58
+ /** Get the currently active cell (head of selection) */
59
+ get activeCell() {
60
+ return { ...this.head };
61
+ }
62
+ /** Check if a cell is within the current selection */
63
+ isCellSelected(e, t) {
64
+ const s = this.range;
65
+ return e >= s.start.row && e <= s.end.row && t >= s.start.col && t <= s.end.col;
66
+ }
67
+ /** Check if a cell is the active cell (head) */
68
+ isCellActive(e, t) {
69
+ return e === this.head.row && t === this.head.col;
70
+ }
71
+ // ─── Mouse Interaction ──────────────────────────────
72
+ /** Called on mousedown/pointerdown on a cell */
73
+ startSelection(e, t, s = !1) {
74
+ if (s)
75
+ this.head = this.clamp({ row: e, col: t });
76
+ else {
77
+ const r = this.clamp({ row: e, col: t });
78
+ this.anchor = { ...r }, this.head = { ...r };
79
+ }
80
+ this._isDragging = !0, this.host.requestUpdate();
81
+ }
82
+ /** Called on mousemove during drag */
83
+ extendSelection(e, t) {
84
+ this._isDragging && (this.head = this.clamp({ row: e, col: t }), this.host.requestUpdate());
85
+ }
86
+ /** Called on mouseup */
87
+ endSelection() {
88
+ this._isDragging = !1;
89
+ }
90
+ get isDragging() {
91
+ return this._isDragging;
92
+ }
93
+ // ─── Keyboard Navigation ────────────────────────────
94
+ /** Move the active cell by delta. If extend=true, extends selection. */
95
+ move(e, t, s = !1) {
96
+ const r = this.clamp({
97
+ row: this.head.row + e,
98
+ col: this.head.col + t
99
+ });
100
+ this.head = r, s || (this.anchor = { ...r }), this.host.requestUpdate();
101
+ }
102
+ /** Move to a specific cell */
103
+ moveTo(e, t, s = !1) {
104
+ const r = this.clamp({ row: e, col: t });
105
+ this.head = r, s || (this.anchor = { ...r }), this.host.requestUpdate();
106
+ }
107
+ /** Select all cells */
108
+ selectAll() {
109
+ this.anchor = { row: 0, col: 0 }, this.head = { row: this._maxRows - 1, col: this._maxCols - 1 }, this.host.requestUpdate();
110
+ }
111
+ /** Clear selection (collapse to active cell) */
112
+ clearSelection() {
113
+ this.anchor = { ...this.head }, this.host.requestUpdate();
114
+ }
115
+ // ─── Helpers ────────────────────────────────────────
116
+ clamp(e) {
117
+ return {
118
+ row: Math.max(0, Math.min(e.row, this._maxRows - 1)),
119
+ col: Math.max(0, Math.min(e.col, this._maxCols - 1))
120
+ };
121
+ }
122
+ }
123
+ const U = 64;
124
+ class F {
125
+ constructor() {
126
+ this.functions = /* @__PURE__ */ new Map(), this.data = /* @__PURE__ */ new Map(), this.evaluating = /* @__PURE__ */ new Set(), this._evalDepth = 0, this._deps = /* @__PURE__ */ new Map(), this._reverseDeps = /* @__PURE__ */ new Map(), this._trackingCellKey = null, this.registerBuiltins();
127
+ }
128
+ /** Register a user-defined function */
129
+ registerFunction(e, t) {
130
+ this.functions.set(e.toUpperCase(), t);
131
+ }
132
+ /** Update the data reference for evaluation */
133
+ setData(e) {
134
+ this.data = e, this._deps.clear(), this._reverseDeps.clear();
135
+ }
136
+ /**
137
+ * Evaluate a raw value. If it starts with '=', parse and execute the formula.
138
+ * Otherwise, return the value as-is (possibly coerced).
139
+ *
140
+ * @param forCellKey - If provided, tracks formula dependencies for this cell key.
141
+ */
142
+ evaluate(e, t) {
143
+ if (!e || e.trim() === "")
144
+ return t && this._clearDepsFor(t), { displayValue: "", type: "text" };
145
+ if (!e.startsWith("="))
146
+ return t && this._clearDepsFor(t), this.coerceValue(e);
147
+ try {
148
+ t && (this._clearDepsFor(t), this._trackingCellKey = t);
149
+ const s = e.substring(1), r = this.parseExpression(s);
150
+ return this.coerceValue(String(r));
151
+ } catch (s) {
152
+ const r = s instanceof Error ? s.message : "";
153
+ return r.startsWith("#") ? { displayValue: r, type: "error" } : { displayValue: "#ERROR!", type: "error" };
154
+ } finally {
155
+ this._trackingCellKey = null;
156
+ }
157
+ }
158
+ /**
159
+ * Re-evaluate all formula cells in the grid.
160
+ * Rebuilds the dependency graph. Returns a set of cell keys that changed.
161
+ */
162
+ recalculate() {
163
+ this._deps.clear(), this._reverseDeps.clear();
164
+ const e = /* @__PURE__ */ new Set();
165
+ for (const [t, s] of this.data)
166
+ if (s.rawValue.startsWith("=")) {
167
+ const r = this.evaluate(s.rawValue, t);
168
+ (s.displayValue !== r.displayValue || s.type !== r.type) && (s.displayValue = r.displayValue, s.type = r.type, e.add(t));
169
+ }
170
+ return e;
171
+ }
172
+ /**
173
+ * Recalculate only formulas affected by the given changed cell keys.
174
+ * Uses the dependency graph for targeted recalculation (BFS order).
175
+ */
176
+ recalculateAffected(e) {
177
+ if (this._reverseDeps.size === 0 && this.data.size > 0)
178
+ return this.recalculate();
179
+ const t = /* @__PURE__ */ new Set(), s = new Set(e);
180
+ for (const n of e) {
181
+ const l = this.data.get(n);
182
+ if (l != null && l.rawValue.startsWith("=")) {
183
+ const c = this.evaluate(l.rawValue, n);
184
+ (l.displayValue !== c.displayValue || l.type !== c.type) && (l.displayValue = c.displayValue, l.type = c.type, t.add(n));
185
+ }
186
+ }
187
+ const r = [], a = /* @__PURE__ */ new Set(), o = [...e];
188
+ for (; o.length > 0; ) {
189
+ const n = o.shift(), l = this._reverseDeps.get(n);
190
+ if (l)
191
+ for (const c of l)
192
+ !a.has(c) && !s.has(c) && (a.add(c), r.push(c), o.push(c));
193
+ }
194
+ for (const n of r) {
195
+ const l = this.data.get(n);
196
+ if (l != null && l.rawValue.startsWith("=")) {
197
+ const c = this.evaluate(l.rawValue, n);
198
+ (l.displayValue !== c.displayValue || l.type !== c.type) && (l.displayValue = c.displayValue, l.type = c.type, t.add(n));
199
+ }
200
+ }
201
+ return t;
202
+ }
203
+ // ─── Dependency Tracking ─────────────────────────────
204
+ _clearDepsFor(e) {
205
+ var s;
206
+ const t = this._deps.get(e);
207
+ if (t) {
208
+ for (const r of t)
209
+ (s = this._reverseDeps.get(r)) == null || s.delete(e);
210
+ this._deps.delete(e);
211
+ }
212
+ }
213
+ _trackDep(e) {
214
+ if (!this._trackingCellKey) return;
215
+ let t = this._deps.get(this._trackingCellKey);
216
+ t || (t = /* @__PURE__ */ new Set(), this._deps.set(this._trackingCellKey, t)), t.add(e);
217
+ let s = this._reverseDeps.get(e);
218
+ s || (s = /* @__PURE__ */ new Set(), this._reverseDeps.set(e, s)), s.add(this._trackingCellKey);
219
+ }
220
+ // ─── Value Coercion ──────────────────────────────────
221
+ coerceValue(e) {
222
+ if (e === "#ERROR!" || e === "#REF!" || e === "#DIV/0!" || e === "#NAME?" || e === "#CIRC!")
223
+ return { displayValue: e, type: "error" };
224
+ const t = e.toUpperCase();
225
+ if (t === "TRUE" || t === "FALSE")
226
+ return { displayValue: t, type: "boolean" };
227
+ const s = Number(e);
228
+ return e.trim() !== "" && !isNaN(s) ? { displayValue: String(s), type: "number" } : { displayValue: e, type: "text" };
229
+ }
230
+ // ─── Lexer ───────────────────────────────────────────
231
+ tokenize(e) {
232
+ const t = [];
233
+ let s = 0;
234
+ for (; s < e.length; ) {
235
+ const r = e[s];
236
+ if (/\s/.test(r)) {
237
+ s++;
238
+ continue;
239
+ }
240
+ if (r === '"') {
241
+ let a = "";
242
+ for (s++; s < e.length && e[s] !== '"'; )
243
+ a += e[s], s++;
244
+ s++, t.push({ type: "STRING", value: a });
245
+ continue;
246
+ }
247
+ if (/\d/.test(r) || r === "." && s + 1 < e.length && /\d/.test(e[s + 1])) {
248
+ let a = "";
249
+ for (; s < e.length && (/\d/.test(e[s]) || e[s] === "."); )
250
+ a += e[s], s++;
251
+ t.push({ type: "NUMBER", value: a });
252
+ continue;
253
+ }
254
+ if (r === "+" || r === "-" || r === "*" || r === "/" || r === "&") {
255
+ t.push({ type: "OPERATOR", value: r }), s++;
256
+ continue;
257
+ }
258
+ if (r === "<" || r === ">") {
259
+ s + 1 < e.length && e[s + 1] === "=" ? (t.push({ type: "OPERATOR", value: r + "=" }), s += 2) : r === "<" && s + 1 < e.length && e[s + 1] === ">" ? (t.push({ type: "OPERATOR", value: "<>" }), s += 2) : (t.push({ type: "OPERATOR", value: r }), s++);
260
+ continue;
261
+ }
262
+ if (r === "=") {
263
+ t.push({ type: "OPERATOR", value: "=" }), s++;
264
+ continue;
265
+ }
266
+ if (r === "(") {
267
+ t.push({ type: "LPAREN", value: "(" }), s++;
268
+ continue;
269
+ }
270
+ if (r === ")") {
271
+ t.push({ type: "RPAREN", value: ")" }), s++;
272
+ continue;
273
+ }
274
+ if (r === ",") {
275
+ t.push({ type: "COMMA", value: "," }), s++;
276
+ continue;
277
+ }
278
+ if (/[A-Za-z_]/.test(r)) {
279
+ let a = "";
280
+ for (; s < e.length && /[A-Za-z0-9_]/.test(e[s]); )
281
+ a += e[s], s++;
282
+ const o = a.toUpperCase();
283
+ if (o === "TRUE" || o === "FALSE") {
284
+ t.push({ type: "BOOLEAN", value: o });
285
+ continue;
286
+ }
287
+ if (s < e.length && e[s] === ":" && /^[A-Z]+\d+$/i.test(a)) {
288
+ s++;
289
+ let l = "";
290
+ for (; s < e.length && /[A-Za-z0-9]/.test(e[s]); )
291
+ l += e[s], s++;
292
+ if (/^[A-Z]+\d+$/i.test(l)) {
293
+ t.push({ type: "RANGE", value: `${a.toUpperCase()}:${l.toUpperCase()}` });
294
+ continue;
295
+ }
296
+ throw new Error(`Invalid range: ${a}:${l}`);
297
+ }
298
+ let n = s;
299
+ for (; n < e.length && /\s/.test(e[n]); ) n++;
300
+ if (n < e.length && e[n] === "(") {
301
+ t.push({ type: "FUNC", value: o });
302
+ continue;
303
+ }
304
+ if (/^[A-Z]+\d+$/i.test(a)) {
305
+ t.push({ type: "REF", value: a.toUpperCase() });
306
+ continue;
307
+ }
308
+ t.push({ type: "FUNC", value: o });
309
+ continue;
310
+ }
311
+ throw new Error(`Unexpected character: ${r}`);
312
+ }
313
+ return t.push({ type: "EOF", value: "" }), t;
314
+ }
315
+ // ─── Parser ──────────────────────────────────────────
316
+ //
317
+ // Parser state is passed as a mutable object through the recursive
318
+ // descent chain so that nested evaluations (resolveRef / resolveRange)
319
+ // each get their own independent state without save/restore.
320
+ _peek(e) {
321
+ return e.tokens[e.pos];
322
+ }
323
+ _consume(e, t) {
324
+ const s = e.tokens[e.pos];
325
+ if (t && s.type !== t)
326
+ throw new Error(`Expected ${t} but got ${s.type} (${s.value})`);
327
+ return e.pos++, s;
328
+ }
329
+ parseExpression(e) {
330
+ if (++this._evalDepth > U)
331
+ throw this._evalDepth--, new Error("#ERROR!");
332
+ try {
333
+ const t = { tokens: this.tokenize(e), pos: 0 };
334
+ return this._parseComparison(t);
335
+ } finally {
336
+ this._evalDepth--;
337
+ }
338
+ }
339
+ _parseComparison(e) {
340
+ let t = this._parseConcatenation(e);
341
+ for (; this._peek(e).type === "OPERATOR" && ["=", "<>", "<", ">", "<=", ">="].includes(this._peek(e).value); ) {
342
+ const s = this._consume(e).value, r = this._parseConcatenation(e);
343
+ t = this._compareValues(t, r, s);
344
+ }
345
+ return t;
346
+ }
347
+ _parseConcatenation(e) {
348
+ let t = this._parseAddSub(e);
349
+ for (; this._peek(e).type === "OPERATOR" && this._peek(e).value === "&"; ) {
350
+ this._consume(e);
351
+ const s = this._parseAddSub(e);
352
+ t = String(t) + String(s);
353
+ }
354
+ return t;
355
+ }
356
+ _parseAddSub(e) {
357
+ let t = this._parseMulDiv(e);
358
+ for (; this._peek(e).type === "OPERATOR" && (this._peek(e).value === "+" || this._peek(e).value === "-"); ) {
359
+ const s = this._consume(e).value, r = this._parseMulDiv(e);
360
+ s === "+" ? t = Number(t) + Number(r) : t = Number(t) - Number(r);
361
+ }
362
+ return t;
363
+ }
364
+ _parseMulDiv(e) {
365
+ let t = this._parseUnary(e);
366
+ for (; this._peek(e).type === "OPERATOR" && (this._peek(e).value === "*" || this._peek(e).value === "/"); ) {
367
+ const s = this._consume(e).value, r = this._parseUnary(e);
368
+ if (s === "*") t = Number(t) * Number(r);
369
+ else {
370
+ const a = Number(r);
371
+ if (a === 0) throw new Error("#DIV/0!");
372
+ t = Number(t) / a;
373
+ }
374
+ }
375
+ return t;
376
+ }
377
+ _parseUnary(e) {
378
+ return this._peek(e).type === "OPERATOR" && this._peek(e).value === "-" ? (this._consume(e), -Number(this._parsePrimary(e))) : this._peek(e).type === "OPERATOR" && this._peek(e).value === "+" ? (this._consume(e), Number(this._parsePrimary(e))) : this._parsePrimary(e);
379
+ }
380
+ _parsePrimary(e) {
381
+ const t = this._peek(e);
382
+ switch (t.type) {
383
+ case "NUMBER":
384
+ return this._consume(e), parseFloat(t.value);
385
+ case "STRING":
386
+ return this._consume(e), t.value;
387
+ case "BOOLEAN":
388
+ return this._consume(e), t.value === "TRUE";
389
+ case "REF":
390
+ return this._consume(e), this._resolveRef(t.value);
391
+ case "RANGE":
392
+ return this._consume(e), this._resolveRange(t.value);
393
+ case "FUNC":
394
+ return this._parseFunction(e);
395
+ case "LPAREN":
396
+ this._consume(e);
397
+ const s = this._parseComparison(e);
398
+ return this._consume(e, "RPAREN"), s;
399
+ default:
400
+ throw new Error(`Unexpected token: ${t.type} (${t.value})`);
401
+ }
402
+ }
403
+ _parseFunction(e) {
404
+ const t = this._consume(e, "FUNC").value;
405
+ this._consume(e, "LPAREN");
406
+ const s = [];
407
+ if (this._peek(e).type !== "RPAREN")
408
+ for (s.push(this._parseComparison(e)); this._peek(e).type === "COMMA"; )
409
+ this._consume(e), s.push(this._parseComparison(e));
410
+ this._consume(e, "RPAREN");
411
+ const r = this.functions.get(t);
412
+ if (!r) throw new Error("#NAME?");
413
+ const a = this._createContext(), o = [];
414
+ for (const n of s)
415
+ Array.isArray(n) ? o.push(...n) : o.push(n);
416
+ return r(a, ...o);
417
+ }
418
+ // ─── Comparison helper ────────────────────────────────
419
+ _compareValues(e, t, s) {
420
+ const r = Number(e), a = Number(t), o = !isNaN(r) && !isNaN(a) && String(e).trim() !== "" && String(t).trim() !== "";
421
+ switch (s) {
422
+ case "=":
423
+ return e === t || o && r === a;
424
+ case "<>":
425
+ return e !== t && (!o || r !== a);
426
+ case "<":
427
+ return o ? r < a : String(e) < String(t);
428
+ case ">":
429
+ return o ? r > a : String(e) > String(t);
430
+ case "<=":
431
+ return o ? r <= a : String(e) <= String(t);
432
+ case ">=":
433
+ return o ? r >= a : String(e) >= String(t);
434
+ default:
435
+ return !1;
436
+ }
437
+ }
438
+ // ─── Reference resolution ───────────────────────────
439
+ //
440
+ // Each nested evaluation calls parseExpression which creates its
441
+ // own ParserState, so no save/restore is needed.
442
+ _resolveRef(e) {
443
+ const t = S(e), s = y(t.row, t.col);
444
+ if (this._trackDep(s), this.evaluating.has(s))
445
+ throw new Error("#CIRC!");
446
+ const r = this.data.get(s);
447
+ if (!r) return 0;
448
+ if (r.rawValue.startsWith("=")) {
449
+ const o = this._trackingCellKey;
450
+ this._trackingCellKey = null, this.evaluating.add(s);
451
+ try {
452
+ return this.parseExpression(r.rawValue.substring(1));
453
+ } catch {
454
+ throw new Error("#ERROR!");
455
+ } finally {
456
+ this._trackingCellKey = o, this.evaluating.delete(s);
457
+ }
458
+ }
459
+ const a = Number(r.rawValue);
460
+ return !isNaN(a) && r.rawValue.trim() !== "" ? a : r.rawValue.toUpperCase() === "TRUE" ? !0 : r.rawValue.toUpperCase() === "FALSE" ? !1 : r.rawValue;
461
+ }
462
+ _resolveRange(e) {
463
+ const [t, s] = e.split(":"), r = S(t), a = S(s), o = Math.min(r.row, a.row), n = Math.max(r.row, a.row), l = Math.min(r.col, a.col), c = Math.max(r.col, a.col), h = [];
464
+ for (let d = o; d <= n; d++)
465
+ for (let g = l; g <= c; g++) {
466
+ const m = y(d, g);
467
+ this._trackDep(m);
468
+ const p = this.data.get(m);
469
+ if (p)
470
+ if (p.rawValue.startsWith("=")) {
471
+ const C = this._trackingCellKey;
472
+ this._trackingCellKey = null, this.evaluating.add(m);
473
+ try {
474
+ h.push(this.parseExpression(p.rawValue.substring(1)));
475
+ } catch {
476
+ h.push(0);
477
+ } finally {
478
+ this._trackingCellKey = C, this.evaluating.delete(m);
479
+ }
480
+ } else {
481
+ const C = Number(p.rawValue);
482
+ !isNaN(C) && p.rawValue.trim() !== "" ? h.push(C) : p.rawValue.toUpperCase() === "TRUE" ? h.push(!0) : p.rawValue.toUpperCase() === "FALSE" ? h.push(!1) : h.push(p.rawValue);
483
+ }
484
+ }
485
+ return h;
486
+ }
487
+ _createContext() {
488
+ return {
489
+ getCellValue: (e) => {
490
+ if (e.includes(":") && !/[A-Z]/i.test(e.charAt(0))) {
491
+ const t = this.data.get(e);
492
+ if (!t) return 0;
493
+ const s = Number(t.rawValue);
494
+ return !isNaN(s) && t.rawValue.trim() !== "" ? s : t.rawValue;
495
+ }
496
+ return this._resolveRef(e);
497
+ },
498
+ getRangeValues: (e, t) => {
499
+ const s = S(e), r = S(t), a = Math.min(s.row, r.row), o = Math.max(s.row, r.row), n = Math.min(s.col, r.col), l = Math.max(s.col, r.col), c = [];
500
+ for (let h = a; h <= o; h++)
501
+ for (let d = n; d <= l; d++) {
502
+ const g = y(h, d), m = this.data.get(g);
503
+ if (m) {
504
+ const p = Number(m.rawValue);
505
+ c.push(!isNaN(p) && m.rawValue.trim() !== "" ? p : m.rawValue);
506
+ }
507
+ }
508
+ return c;
509
+ }
510
+ };
511
+ }
512
+ // ─── Built-in Functions ─────────────────────────────
513
+ registerBuiltins() {
514
+ this.registerFunction("SUM", (e, ...t) => t.reduce((s, r) => s + (Number(r) || 0), 0)), this.registerFunction("AVERAGE", (e, ...t) => {
515
+ const s = t.filter((a) => typeof a == "number" || !isNaN(Number(a)));
516
+ return s.length === 0 ? 0 : s.reduce((a, o) => a + Number(o), 0) / s.length;
517
+ }), this.registerFunction("MIN", (e, ...t) => {
518
+ const s = t.map(Number).filter((r) => !isNaN(r));
519
+ return s.length ? Math.min(...s) : 0;
520
+ }), this.registerFunction("MAX", (e, ...t) => {
521
+ const s = t.map(Number).filter((r) => !isNaN(r));
522
+ return s.length ? Math.max(...s) : 0;
523
+ }), this.registerFunction("COUNT", (e, ...t) => t.filter((s) => typeof s == "number" || !isNaN(Number(s)) && String(s).trim() !== "").length), this.registerFunction("COUNTA", (e, ...t) => t.filter((s) => s !== "" && s !== null && s !== void 0).length), this.registerFunction("IF", (e, t, s, r) => t ? s : r), this.registerFunction("CONCAT", (e, ...t) => t.map(String).join("")), this.registerFunction("ABS", (e, t) => Math.abs(Number(t))), this.registerFunction("ROUND", (e, t, s) => {
524
+ const r = Number(s) || 0, a = Math.pow(10, r);
525
+ return Math.round(Number(t) * a) / a;
526
+ }), this.registerFunction("UPPER", (e, t) => String(t).toUpperCase()), this.registerFunction("LOWER", (e, t) => String(t).toLowerCase()), this.registerFunction("LEN", (e, t) => String(t).length), this.registerFunction("TRIM", (e, t) => String(t).trim());
527
+ }
528
+ }
529
+ class P {
530
+ /**
531
+ * Copy the selected range to the clipboard as TSV.
532
+ */
533
+ async copy(e, t) {
534
+ const s = this.serializeRange(e, t);
535
+ try {
536
+ await navigator.clipboard.writeText(s);
537
+ } catch {
538
+ this.fallbackCopy(s);
539
+ }
540
+ }
541
+ /**
542
+ * Cut: copy to clipboard and return the keys that should be cleared.
543
+ */
544
+ async cut(e, t) {
545
+ await this.copy(e, t);
546
+ const s = [];
547
+ for (let r = t.start.row; r <= t.end.row; r++)
548
+ for (let a = t.start.col; a <= t.end.col; a++)
549
+ s.push(y(r, a));
550
+ return s;
551
+ }
552
+ /**
553
+ * Parse clipboard content and return the updates to apply.
554
+ * The target is the top-left cell where paste begins.
555
+ */
556
+ async paste(e, t, s, r) {
557
+ let a;
558
+ try {
559
+ a = await navigator.clipboard.readText();
560
+ } catch {
561
+ return null;
562
+ }
563
+ return this.parseTSV(a, e, t, s, r);
564
+ }
565
+ /**
566
+ * Parse a TSV string and generate cell updates.
567
+ * Exported for testability with paste events where text is already available.
568
+ */
569
+ parseTSV(e, t, s, r, a) {
570
+ const o = e.split(/\r?\n/);
571
+ o.length > 0 && o[o.length - 1] === "" && o.pop();
572
+ const n = [];
573
+ for (let l = 0; l < o.length; l++) {
574
+ const c = o[l].split(" ");
575
+ for (let h = 0; h < c.length; h++) {
576
+ const d = t + l, g = s + h;
577
+ d < r && g < a && n.push({ id: y(d, g), value: c[h] });
578
+ }
579
+ }
580
+ return n;
581
+ }
582
+ // ─── Serialization ──────────────────────────────────
583
+ serializeRange(e, t) {
584
+ const s = [];
585
+ for (let r = t.start.row; r <= t.end.row; r++) {
586
+ const a = [];
587
+ for (let o = t.start.col; o <= t.end.col; o++) {
588
+ const n = e.get(y(r, o));
589
+ a.push((n == null ? void 0 : n.displayValue) ?? "");
590
+ }
591
+ s.push(a.join(" "));
592
+ }
593
+ return s.join(`
594
+ `);
595
+ }
596
+ // ─── Fallback ───────────────────────────────────────
597
+ /** @deprecated Uses document.execCommand('copy') which is deprecated. Only used as a fallback when the Clipboard API is unavailable. */
598
+ fallbackCopy(e) {
599
+ console.warn("y11n-spreadsheet: Clipboard API unavailable, using deprecated execCommand fallback.");
600
+ const t = document.createElement("textarea");
601
+ t.value = e, t.style.position = "fixed", t.style.left = "-9999px", document.body.appendChild(t), t.select();
602
+ try {
603
+ document.execCommand("copy");
604
+ } catch {
605
+ }
606
+ document.body.removeChild(t);
607
+ }
608
+ }
609
+ var T = Object.defineProperty, z = Object.getOwnPropertyDescriptor, b = (i, e, t, s) => {
610
+ for (var r = s > 1 ? void 0 : s ? z(e, t) : e, a = i.length - 1, o; a >= 0; a--)
611
+ (o = i[a]) && (r = (s ? o(e, t, r) : o(r)) || r);
612
+ return s && r && T(e, t, r), r;
613
+ };
614
+ let v = class extends V {
615
+ constructor() {
616
+ super(...arguments), this.cellRef = "A1", this.rawValue = "", this.displayValue = "", this.mode = "raw", this.readOnly = !1, this._draft = "", this._suppressNextBlur = !1;
617
+ }
618
+ willUpdate(i) {
619
+ (i.has("mode") || i.has("rawValue") || i.has("displayValue")) && (this._draft = this._sourceValue());
620
+ }
621
+ _sourceValue() {
622
+ return this.mode === "raw" ? this.rawValue : this.displayValue;
623
+ }
624
+ _setMode(i) {
625
+ this.mode !== i && this.dispatchEvent(
626
+ new CustomEvent("formula-bar-mode-change", {
627
+ detail: { mode: i },
628
+ bubbles: !0,
629
+ composed: !0
630
+ })
631
+ );
632
+ }
633
+ _onInput(i) {
634
+ this._draft = i.target.value;
635
+ }
636
+ _commit() {
637
+ this.dispatchEvent(
638
+ new CustomEvent("formula-bar-commit", {
639
+ detail: { value: this._draft },
640
+ bubbles: !0,
641
+ composed: !0
642
+ })
643
+ );
644
+ }
645
+ _onKeydown(i) {
646
+ i.key === "Enter" ? (i.preventDefault(), this._commit()) : i.key === "Escape" && (i.preventDefault(), this._draft = this._sourceValue(), this._suppressNextBlur = !0);
647
+ }
648
+ _onBlur() {
649
+ if (this._suppressNextBlur) {
650
+ this._suppressNextBlur = !1;
651
+ return;
652
+ }
653
+ this._commit();
654
+ }
655
+ render() {
656
+ return _`
657
+ <output class="cell-ref" aria-live="polite" aria-atomic="true">${this.cellRef}</output>
658
+ <div class="mode-toggle" role="group" aria-label="Formula bar mode">
659
+ <button
660
+ type="button"
661
+ aria-pressed="${this.mode === "raw"}"
662
+ @click="${() => this._setMode("raw")}"
663
+ >
664
+ Raw
665
+ </button>
666
+ <button
667
+ type="button"
668
+ aria-pressed="${this.mode === "formatted"}"
669
+ @click="${() => this._setMode("formatted")}"
670
+ >
671
+ Formatted
672
+ </button>
673
+ </div>
674
+ <input
675
+ type="text"
676
+ .value="${this._draft}"
677
+ ?disabled="${this.readOnly}"
678
+ @input="${this._onInput}"
679
+ @keydown="${this._onKeydown}"
680
+ @blur="${this._onBlur}"
681
+ aria-label="Formula bar"
682
+ />
683
+ `;
684
+ }
685
+ };
686
+ v.styles = k`
687
+ :host {
688
+ display: grid;
689
+ grid-template-columns: minmax(48px, auto) auto minmax(0, 1fr);
690
+ align-items: stretch;
691
+ gap: 8px;
692
+ padding: 8px;
693
+ border-bottom: 1px solid var(--ls-border-color, #e0e0e0);
694
+ background: var(--ls-formula-bar-bg, #f8fafc);
695
+ color: var(--ls-text-color, #333);
696
+ font-family: var(--ls-font-family, system-ui, -apple-system, sans-serif);
697
+ font-size: var(--ls-font-size, 13px);
698
+ }
699
+
700
+ .cell-ref {
701
+ display: inline-flex;
702
+ align-items: center;
703
+ justify-content: center;
704
+ min-width: 48px;
705
+ padding: 0 8px;
706
+ border: 1px solid var(--ls-border-color, #e0e0e0);
707
+ border-radius: 4px;
708
+ font-weight: 600;
709
+ background: var(--ls-formula-ref-bg, #fff);
710
+ }
711
+
712
+ .mode-toggle {
713
+ display: inline-flex;
714
+ border: 1px solid var(--ls-border-color, #e0e0e0);
715
+ border-radius: 4px;
716
+ overflow: hidden;
717
+ background: #fff;
718
+ }
719
+
720
+ .mode-toggle button {
721
+ border: 0;
722
+ background: transparent;
723
+ padding: 0 10px;
724
+ min-height: 32px;
725
+ cursor: pointer;
726
+ font: inherit;
727
+ color: inherit;
728
+ }
729
+
730
+ .mode-toggle button[aria-pressed='true'] {
731
+ background: var(--ls-formula-mode-active-bg, #dbeafe);
732
+ font-weight: 600;
733
+ }
734
+
735
+ input {
736
+ width: 100%;
737
+ min-height: 32px;
738
+ padding: 0 10px;
739
+ border: 1px solid var(--ls-border-color, #e0e0e0);
740
+ border-radius: 4px;
741
+ font: inherit;
742
+ color: inherit;
743
+ outline: none;
744
+ background: #fff;
745
+ }
746
+
747
+ input:focus {
748
+ border-color: #1a73e8;
749
+ box-shadow: 0 0 0 1px rgba(26, 115, 232, 0.2);
750
+ }
751
+
752
+ button:disabled,
753
+ input:disabled {
754
+ opacity: 0.7;
755
+ cursor: not-allowed;
756
+ }
757
+ `;
758
+ b([
759
+ w({ type: String, attribute: "cell-ref" })
760
+ ], v.prototype, "cellRef", 2);
761
+ b([
762
+ w({ type: String, attribute: "raw-value" })
763
+ ], v.prototype, "rawValue", 2);
764
+ b([
765
+ w({ type: String, attribute: "display-value" })
766
+ ], v.prototype, "displayValue", 2);
767
+ b([
768
+ w({ type: String })
769
+ ], v.prototype, "mode", 2);
770
+ b([
771
+ w({ type: Boolean, attribute: "read-only", reflect: !0 })
772
+ ], v.prototype, "readOnly", 2);
773
+ b([
774
+ E()
775
+ ], v.prototype, "_draft", 2);
776
+ v = b([
777
+ D("y11n-formula-bar")
778
+ ], v);
779
+ var I = Object.defineProperty, L = Object.getOwnPropertyDescriptor, f = (i, e, t, s) => {
780
+ for (var r = s > 1 ? void 0 : s ? L(e, t) : e, a = i.length - 1, o; a >= 0; a--)
781
+ (o = i[a]) && (r = (s ? o(e, t, r) : o(r)) || r);
782
+ return s && r && I(e, t, r), r;
783
+ };
784
+ let u = class extends V {
785
+ constructor() {
786
+ super(...arguments), this.rows = 100, this.cols = 26, this.data = /* @__PURE__ */ new Map(), this.readOnly = !1, this.functions = {}, this._isEditing = !1, this._editValue = "", this._formulaBarMode = "raw", this._scrollTop = 0, this._scrollLeft = 0, this._selection = new B(this, this.rows, this.cols), this._formulaEngine = new F(), this._clipboardManager = new P(), this._internalData = /* @__PURE__ */ new Map(), this._editingCellKey = null, this._undoStack = [], this._redoStack = [], this._maxHistory = 100;
787
+ }
788
+ // ─── Lifecycle ──────────────────────────────────────
789
+ connectedCallback() {
790
+ super.connectedCallback(), this._syncData();
791
+ }
792
+ willUpdate(i) {
793
+ if (i.has("data") && this._syncData(), (i.has("rows") || i.has("cols")) && (this.rows = Math.max(1, Math.round(this.rows)), this.cols = Math.max(1, Math.round(this.cols)), this._selection.setBounds(this.rows, this.cols)), i.has("functions"))
794
+ for (const [e, t] of Object.entries(this.functions))
795
+ this._formulaEngine.registerFunction(e, t);
796
+ }
797
+ // ─── Public API Methods ─────────────────────────────
798
+ /** Returns the current state of the grid data */
799
+ getData() {
800
+ return new Map(this._internalData);
801
+ }
802
+ /** Hard reset of the grid data */
803
+ setData(i) {
804
+ this.data = i, this._syncData(), this.requestUpdate();
805
+ }
806
+ /** Dynamically register a formula function */
807
+ registerFunction(i, e) {
808
+ this._formulaEngine.registerFunction(i, e), this._recalcAll();
809
+ }
810
+ // ─── Data Sync ──────────────────────────────────────
811
+ _syncData() {
812
+ this._internalData = new Map(this.data), this._undoStack = [], this._redoStack = [], this._formulaEngine.setData(this._internalData), this._recalcAll();
813
+ }
814
+ _recalcAll() {
815
+ this._formulaEngine.recalculate();
816
+ }
817
+ _recalcAffected(i) {
818
+ this._formulaEngine.recalculateAffected(i);
819
+ }
820
+ // ─── Cell Access ────────────────────────────────────
821
+ _getCell(i, e) {
822
+ return this._internalData.get(y(i, e));
823
+ }
824
+ _getCellDisplay(i, e) {
825
+ const t = this._getCell(i, e);
826
+ return (t == null ? void 0 : t.displayValue) ?? "";
827
+ }
828
+ _getCellRaw(i, e) {
829
+ const t = this._getCell(i, e);
830
+ return (t == null ? void 0 : t.rawValue) ?? "";
831
+ }
832
+ _getActiveCellKey() {
833
+ const { row: i, col: e } = this._selection.activeCell;
834
+ return y(i, e);
835
+ }
836
+ _getActiveCellRef() {
837
+ return O(this._selection.activeCell);
838
+ }
839
+ _setCellRaw(i, e) {
840
+ const t = this._internalData.get(i), s = this._formulaEngine.evaluate(e, i);
841
+ this._internalData.set(i, {
842
+ rawValue: e,
843
+ displayValue: s.displayValue,
844
+ type: s.type,
845
+ style: t == null ? void 0 : t.style
846
+ });
847
+ }
848
+ _applyRawValueByString(i, e) {
849
+ if (e === "") {
850
+ this._internalData.delete(i);
851
+ return;
852
+ }
853
+ this._setCellRaw(i, e);
854
+ }
855
+ _snapshotSelection() {
856
+ return {
857
+ anchor: { ...this._selection.anchor },
858
+ head: { ...this._selection.head }
859
+ };
860
+ }
861
+ _restoreSelection(i) {
862
+ this._selection.moveTo(i.anchor.row, i.anchor.col), this._selection.moveTo(i.head.row, i.head.col, !0), this._dispatchSelectionChange(), this.updateComplete.then(() => this._focusActiveCell());
863
+ }
864
+ _buildCommandBatch(i, e, t, s) {
865
+ var a;
866
+ const r = [];
867
+ for (const o of i) {
868
+ const n = ((a = this._internalData.get(o.id)) == null ? void 0 : a.rawValue) ?? "", l = o.value;
869
+ n !== l && r.push({ id: o.id, before: n, after: l });
870
+ }
871
+ return r.length === 0 ? null : {
872
+ deltas: r,
873
+ selectionBefore: t,
874
+ selectionAfter: s,
875
+ operation: e
876
+ };
877
+ }
878
+ _pushHistory(i) {
879
+ this._undoStack.push(i), this._redoStack = [], this._undoStack.length > this._maxHistory && this._undoStack.shift();
880
+ }
881
+ _finalizeBatch(i, e, t) {
882
+ this._recalcAffected(i.deltas.map((r) => r.id)), (e !== "user" || i.operation !== "edit") && this._dispatchDataChange({
883
+ updates: i.deltas.map((r) => ({ id: r.id, value: r[t] })),
884
+ source: e,
885
+ operation: i.operation
886
+ }), this.requestUpdate();
887
+ }
888
+ _applyBatch(i, e) {
889
+ for (const t of i.deltas)
890
+ this._applyRawValueByString(t.id, t.after);
891
+ this._restoreSelection(i.selectionAfter), this._finalizeBatch(i, e, "after");
892
+ }
893
+ _revertBatch(i) {
894
+ for (const e of i.deltas)
895
+ this._applyRawValueByString(e.id, e.before);
896
+ this._restoreSelection(i.selectionBefore), this._finalizeBatch(i, "undo", "before");
897
+ }
898
+ _executeUserBatch(i) {
899
+ this._applyBatch(i, "user"), this._pushHistory(i);
900
+ }
901
+ _commitRawValue(i, e) {
902
+ const t = this._internalData.get(i), s = (t == null ? void 0 : t.rawValue) ?? "";
903
+ if (e === s) return !1;
904
+ const r = this._snapshotSelection(), a = this._buildCommandBatch(
905
+ [{ id: i, value: e }],
906
+ "edit",
907
+ r,
908
+ r
909
+ );
910
+ return a ? (this._executeUserBatch(a), this._dispatchCellChange({ cellId: i, value: e, oldValue: s }), !0) : !1;
911
+ }
912
+ _undo() {
913
+ if (this.readOnly) return;
914
+ const i = this._undoStack.pop();
915
+ i && (this._revertBatch(i), this._redoStack.push(i));
916
+ }
917
+ _redo() {
918
+ if (this.readOnly) return;
919
+ const i = this._redoStack.pop();
920
+ i && (this._applyBatch(i, "redo"), this._undoStack.push(i), this._undoStack.length > this._maxHistory && this._undoStack.shift());
921
+ }
922
+ // ─── Editing ────────────────────────────────────────
923
+ _startEditing(i) {
924
+ if (this.readOnly || this._isEditing) return;
925
+ const { row: e, col: t } = this._selection.activeCell, s = y(e, t);
926
+ this._editingCellKey = s;
927
+ const r = this._internalData.get(s);
928
+ this._editValue = i ?? (r == null ? void 0 : r.rawValue) ?? "", this._isEditing = !0;
929
+ }
930
+ _commitEdit() {
931
+ if (!this._isEditing || !this._editingCellKey) return;
932
+ const i = this._editingCellKey, e = this._editValue;
933
+ this._isEditing = !1, this._editingCellKey = null, this._commitRawValue(i, e), this.updateComplete.then(() => this._focusActiveCell());
934
+ }
935
+ _cancelEdit() {
936
+ this._isEditing = !1, this._editingCellKey = null, this.updateComplete.then(() => this._focusActiveCell());
937
+ }
938
+ // ─── Focus Management ───────────────────────────────
939
+ _focusActiveCell() {
940
+ var s;
941
+ const { row: i, col: e } = this._selection.activeCell, t = (s = this.shadowRoot) == null ? void 0 : s.querySelector(
942
+ `[data-row="${i}"][data-col="${e}"]`
943
+ );
944
+ t && t.focus();
945
+ }
946
+ // ─── Keyboard Handling ──────────────────────────────
947
+ _handleGridKeydown(i) {
948
+ if (this._isEditing) {
949
+ this._handleEditorKeydown(i);
950
+ return;
951
+ }
952
+ const e = i.shiftKey, t = i.ctrlKey || i.metaKey;
953
+ switch (i.stopPropagation(), i.key) {
954
+ case "ArrowUp":
955
+ i.preventDefault(), this._selection.move(-1, 0, e), this._dispatchSelectionChange(), this.updateComplete.then(() => this._focusActiveCell());
956
+ break;
957
+ case "ArrowDown":
958
+ i.preventDefault(), this._selection.move(1, 0, e), this._dispatchSelectionChange(), this.updateComplete.then(() => this._focusActiveCell());
959
+ break;
960
+ case "ArrowLeft":
961
+ i.preventDefault(), this._selection.move(0, -1, e), this._dispatchSelectionChange(), this.updateComplete.then(() => this._focusActiveCell());
962
+ break;
963
+ case "ArrowRight":
964
+ i.preventDefault(), this._selection.move(0, 1, e), this._dispatchSelectionChange(), this.updateComplete.then(() => this._focusActiveCell());
965
+ break;
966
+ case "Tab":
967
+ i.preventDefault(), e ? this._selection.move(0, -1) : this._selection.move(0, 1), this._dispatchSelectionChange(), this.updateComplete.then(() => this._focusActiveCell());
968
+ break;
969
+ case "Enter":
970
+ i.preventDefault(), this.readOnly || this._startEditing();
971
+ break;
972
+ case "Escape":
973
+ i.preventDefault(), this._selection.clearSelection(), this._dispatchSelectionChange();
974
+ break;
975
+ case "Delete":
976
+ case "Backspace":
977
+ i.preventDefault(), this.readOnly || this._clearSelectedCells();
978
+ break;
979
+ case "a":
980
+ t && (i.preventDefault(), this._selection.selectAll(), this._dispatchSelectionChange());
981
+ break;
982
+ case "c":
983
+ t && (i.preventDefault(), this._handleCopy());
984
+ break;
985
+ case "x":
986
+ t && (i.preventDefault(), this._handleCut());
987
+ break;
988
+ case "v":
989
+ t && (i.preventDefault(), this._handlePaste());
990
+ break;
991
+ case "z":
992
+ case "Z":
993
+ t && (i.preventDefault(), e ? this._redo() : this._undo());
994
+ break;
995
+ default:
996
+ !t && !i.altKey && i.key.length === 1 && !this.readOnly && (i.preventDefault(), this._startEditing(i.key));
997
+ break;
998
+ }
999
+ }
1000
+ _handleEditorKeydown(i) {
1001
+ switch (i.stopPropagation(), i.key) {
1002
+ case "Enter":
1003
+ i.preventDefault(), this._commitEdit(), this._selection.move(1, 0), this._dispatchSelectionChange(), this.updateComplete.then(() => this._focusActiveCell());
1004
+ break;
1005
+ case "Tab":
1006
+ i.preventDefault(), this._commitEdit(), i.shiftKey ? this._selection.move(0, -1) : this._selection.move(0, 1), this._dispatchSelectionChange(), this.updateComplete.then(() => this._focusActiveCell());
1007
+ break;
1008
+ case "Escape":
1009
+ i.preventDefault(), this._cancelEdit();
1010
+ break;
1011
+ }
1012
+ }
1013
+ // ─── Mouse Handling ─────────────────────────────────
1014
+ _handleCellPointerDown(i) {
1015
+ const t = i.target.closest("[data-row]");
1016
+ if (!t) return;
1017
+ const s = parseInt(t.dataset.row, 10), r = parseInt(t.dataset.col, 10);
1018
+ this._isEditing && this._commitEdit(), this._selection.startSelection(s, r, i.shiftKey), this._dispatchSelectionChange(), t.focus();
1019
+ const a = new AbortController(), { signal: o } = a, n = () => a.abort(), l = (h) => {
1020
+ var m, p;
1021
+ const d = (m = this.shadowRoot) == null ? void 0 : m.elementFromPoint(h.clientX, h.clientY), g = (p = d == null ? void 0 : d.closest) == null ? void 0 : p.call(d, "[data-row]");
1022
+ if (g) {
1023
+ const C = parseInt(g.dataset.row, 10), $ = parseInt(g.dataset.col, 10);
1024
+ this._selection.extendSelection(C, $), this._dispatchSelectionChange();
1025
+ }
1026
+ }, c = () => {
1027
+ this._selection.endSelection(), n();
1028
+ };
1029
+ document.addEventListener("pointermove", l, { signal: o }), document.addEventListener("pointerup", c, { signal: o }), document.addEventListener("pointercancel", c, { signal: o }), window.addEventListener("blur", c, { signal: o });
1030
+ }
1031
+ _handleCellDblClick(i) {
1032
+ if (this.readOnly) return;
1033
+ const t = i.target.closest("[data-row]");
1034
+ if (!t) return;
1035
+ const s = parseInt(t.dataset.row, 10), r = parseInt(t.dataset.col, 10);
1036
+ this._selection.moveTo(s, r), this._startEditing();
1037
+ }
1038
+ // ─── Clipboard ──────────────────────────────────────
1039
+ async _handleCopy() {
1040
+ await this._clipboardManager.copy(this._internalData, this._selection.range);
1041
+ }
1042
+ async _handleCut() {
1043
+ if (this.readOnly) return;
1044
+ const i = await this._clipboardManager.cut(
1045
+ this._internalData,
1046
+ this._selection.range
1047
+ ), e = [], t = this._snapshotSelection();
1048
+ for (const r of i)
1049
+ this._internalData.has(r) && e.push({ id: r, value: "" });
1050
+ const s = this._buildCommandBatch(e, "cut", t, t);
1051
+ s && this._executeUserBatch(s);
1052
+ }
1053
+ async _handlePaste() {
1054
+ if (this.readOnly) return;
1055
+ const { row: i, col: e } = this._selection.activeCell, t = await this._clipboardManager.paste(i, e, this.rows, this.cols);
1056
+ if (t && t.length > 0) {
1057
+ const s = this._snapshotSelection(), r = this._buildCommandBatch(t, "paste", s, s);
1058
+ r && this._executeUserBatch(r);
1059
+ }
1060
+ }
1061
+ // ─── Cell Clearing ──────────────────────────────────
1062
+ _clearSelectedCells() {
1063
+ const i = this._selection.range, e = [], t = this._snapshotSelection();
1064
+ for (let r = i.start.row; r <= i.end.row; r++)
1065
+ for (let a = i.start.col; a <= i.end.col; a++) {
1066
+ const o = y(r, a);
1067
+ this._internalData.has(o) && e.push({ id: o, value: "" });
1068
+ }
1069
+ const s = this._buildCommandBatch(e, "clear", t, t);
1070
+ s && this._executeUserBatch(s);
1071
+ }
1072
+ // ─── Scroll Handling ────────────────────────────────
1073
+ _handleScroll(i) {
1074
+ const e = i.target;
1075
+ this._scrollTop = e.scrollTop, this._scrollLeft = e.scrollLeft;
1076
+ }
1077
+ // ─── Editor Input ───────────────────────────────────
1078
+ _handleEditorInput(i) {
1079
+ this._editValue = i.target.value;
1080
+ }
1081
+ _handleEditorBlur() {
1082
+ this._isEditing && this._commitEdit();
1083
+ }
1084
+ _handleFormulaBarModeChange(i) {
1085
+ this._formulaBarMode = i.detail.mode;
1086
+ }
1087
+ _handleFormulaBarCommit(i) {
1088
+ this.readOnly || (this._isEditing && (this._isEditing = !1, this._editingCellKey = null), this._commitRawValue(this._getActiveCellKey(), i.detail.value));
1089
+ }
1090
+ // ─── Event Dispatching ──────────────────────────────
1091
+ _dispatchCellChange(i) {
1092
+ this.dispatchEvent(new CustomEvent("cell-change", { detail: i, bubbles: !0, composed: !0 }));
1093
+ }
1094
+ _dispatchSelectionChange() {
1095
+ const i = { range: this._selection.range };
1096
+ this.dispatchEvent(
1097
+ new CustomEvent("selection-change", { detail: i, bubbles: !0, composed: !0 })
1098
+ );
1099
+ }
1100
+ _dispatchDataChange(i) {
1101
+ this.dispatchEvent(
1102
+ new CustomEvent("data-change", { detail: i, bubbles: !0, composed: !0 })
1103
+ );
1104
+ }
1105
+ // ─── Virtual Rendering ──────────────────────────────
1106
+ /**
1107
+ * Compute which rows and columns are visible based on scroll position.
1108
+ * We add a buffer of extra rows/cols for smooth scrolling.
1109
+ */
1110
+ _getVisibleRange() {
1111
+ const i = this._grid;
1112
+ if (!i)
1113
+ return { startRow: 0, endRow: Math.min(this.rows, 40), startCol: 0, endCol: this.cols };
1114
+ const e = this._getCSSVarPx("--ls-cell-height", 28), t = this._getCSSVarPx("--ls-cell-width", 100), s = i.clientHeight, r = i.clientWidth, a = 5, o = Math.max(0, Math.floor(this._scrollTop / e) - a), n = Math.min(this.rows, Math.ceil((this._scrollTop + s) / e) + a), l = Math.max(0, Math.floor(this._scrollLeft / t) - a), c = Math.min(this.cols, Math.ceil((this._scrollLeft + r) / t) + a);
1115
+ return { startRow: o, endRow: n, startCol: l, endCol: c };
1116
+ }
1117
+ _getCSSVarPx(i, e) {
1118
+ const t = getComputedStyle(this).getPropertyValue(i);
1119
+ if (t) {
1120
+ const s = parseInt(t, 10);
1121
+ if (!isNaN(s)) return s;
1122
+ }
1123
+ return e;
1124
+ }
1125
+ // ─── Editor Positioning ─────────────────────────────
1126
+ _getEditorStyle() {
1127
+ if (!this._isEditing) return "display:none;";
1128
+ const { row: i, col: e } = this._selection.activeCell, t = this._getCSSVarPx("--ls-cell-height", 28), s = this._getCSSVarPx("--ls-cell-width", 100), r = 50, a = (i + 1) * t, o = r + e * s;
1129
+ return `
1130
+ display: block;
1131
+ position: absolute;
1132
+ top: ${a}px;
1133
+ left: ${o}px;
1134
+ width: ${s}px;
1135
+ height: ${t}px;
1136
+ z-index: 10;
1137
+ `;
1138
+ }
1139
+ render() {
1140
+ const { startRow: i, endRow: e, startCol: t, endCol: s } = this._getVisibleRange(), r = this._selection.activeCell, a = this._getCellRaw(r.row, r.col), o = this._getCellDisplay(r.row, r.col);
1141
+ return _`
1142
+ <div class="ls-wrapper">
1143
+ <y11n-formula-bar
1144
+ cell-ref="${this._getActiveCellRef()}"
1145
+ raw-value="${a}"
1146
+ display-value="${o}"
1147
+ .mode="${this._formulaBarMode}"
1148
+ .readOnly="${this.readOnly}"
1149
+ @formula-bar-mode-change="${this._handleFormulaBarModeChange}"
1150
+ @formula-bar-commit="${this._handleFormulaBarCommit}"
1151
+ ></y11n-formula-bar>
1152
+
1153
+ <div class="ls-grid-shell">
1154
+ <input
1155
+ id="editor"
1156
+ part="editor"
1157
+ style="${this._getEditorStyle()}"
1158
+ .value="${this._editValue}"
1159
+ @input="${this._handleEditorInput}"
1160
+ @keydown="${this._handleEditorKeydown}"
1161
+ @blur="${this._handleEditorBlur}"
1162
+ aria-label="Cell editor"
1163
+ />
1164
+
1165
+ <div
1166
+ class="ls-grid"
1167
+ role="grid"
1168
+ aria-rowcount="${this.rows + 1}"
1169
+ aria-colcount="${this.cols + 1}"
1170
+ aria-readonly="${this.readOnly}"
1171
+ style="--cols: ${this.cols};"
1172
+ tabindex="-1"
1173
+ @keydown="${this._handleGridKeydown}"
1174
+ @scroll="${this._handleScroll}"
1175
+ >
1176
+ ${this._renderHeaderRow(t, s)}
1177
+ ${this._renderRows(i, e, t, s)}
1178
+ </div>
1179
+ </div>
1180
+
1181
+ <div class="sr-only" role="status" aria-live="polite" aria-atomic="true">
1182
+ ${this._getAnnouncement(r)}
1183
+ </div>
1184
+ </div>
1185
+ `;
1186
+ }
1187
+ _renderHeaderRow(i, e) {
1188
+ const t = [];
1189
+ for (let r = i; r < e; r++)
1190
+ t.push({ letter: R(r), index: r });
1191
+ const s = this._getCSSVarPx("--ls-cell-width", 100);
1192
+ return _`
1193
+ <div class="ls-header-row" role="row" aria-rowindex="1">
1194
+ <div class="ls-corner-header" role="columnheader"></div>
1195
+ ${i > 0 ? _`<div style="width: ${i * s}px;"></div>` : x}
1196
+ ${t.map(
1197
+ (r) => _`
1198
+ <div
1199
+ class="ls-col-header"
1200
+ role="columnheader"
1201
+ aria-colindex="${r.index + 2}"
1202
+ >
1203
+ ${r.letter}
1204
+ </div>
1205
+ `
1206
+ )}
1207
+ ${e < this.cols ? _`<div style="width: ${(this.cols - e) * s}px;"></div>` : x}
1208
+ </div>
1209
+ `;
1210
+ }
1211
+ _renderRows(i, e, t, s) {
1212
+ const r = this._getCSSVarPx("--ls-cell-height", 28), a = Array.from({ length: e - i }, (o, n) => i + n);
1213
+ return _`
1214
+ ${i > 0 ? _`<div style="grid-column: 1 / -1; height: ${i * r}px;"></div>` : x}
1215
+ ${N(a, (o) => o, (o) => this._renderRow(o, t, s))}
1216
+ ${e < this.rows ? _`<div style="grid-column: 1 / -1; height: ${(this.rows - e) * r}px;"></div>` : x}
1217
+ `;
1218
+ }
1219
+ _renderRow(i, e, t) {
1220
+ const s = this._getCSSVarPx("--ls-cell-width", 100), r = [];
1221
+ for (let a = e; a < t; a++) {
1222
+ const o = this._selection.isCellSelected(i, a), n = this._selection.isCellActive(i, a), l = this._getCellDisplay(i, a), c = y(i, a);
1223
+ r.push(_`
1224
+ <div
1225
+ class="ls-cell ${n ? "active-cell" : ""}"
1226
+ role="gridcell"
1227
+ aria-colindex="${a + 2}"
1228
+ aria-selected="${o}"
1229
+ aria-readonly="${this.readOnly}"
1230
+ data-row="${i}"
1231
+ data-col="${a}"
1232
+ data-key="${c}"
1233
+ tabindex="${n ? 0 : -1}"
1234
+ @pointerdown="${this._handleCellPointerDown}"
1235
+ @dblclick="${this._handleCellDblClick}"
1236
+ >
1237
+ <span class="cell-text">${l}</span>
1238
+ </div>
1239
+ `);
1240
+ }
1241
+ return _`
1242
+ <div class="ls-row" role="row" aria-rowindex="${i + 2}">
1243
+ <div class="ls-row-header" role="rowheader">${i + 1}</div>
1244
+ ${e > 0 ? _`<div style="width: ${e * s}px;"></div>` : x}
1245
+ ${r}
1246
+ ${t < this.cols ? _`<div style="width: ${(this.cols - t) * s}px;"></div>` : x}
1247
+ </div>
1248
+ `;
1249
+ }
1250
+ _getAnnouncement(i) {
1251
+ const e = `${R(i.col)}${i.row + 1}`, t = this._getCellDisplay(i.row, i.col);
1252
+ return t ? `${e}: ${t}` : e;
1253
+ }
1254
+ updated() {
1255
+ if (this._isEditing && this._editor) {
1256
+ const i = this._editor, e = this._editValue.length;
1257
+ i.focus(), i.setSelectionRange(e, e);
1258
+ }
1259
+ }
1260
+ };
1261
+ u.styles = k`
1262
+ :host {
1263
+ display: block;
1264
+ position: relative;
1265
+ font-family: var(--ls-font-family, system-ui, -apple-system, sans-serif);
1266
+ font-size: var(--ls-font-size, 13px);
1267
+ color: var(--ls-text-color, #333);
1268
+ --_cell-width: var(--ls-cell-width, 100px);
1269
+ --_cell-height: var(--ls-cell-height, 28px);
1270
+ --_header-bg: var(--ls-header-bg, #f3f3f3);
1271
+ --_border-color: var(--ls-border-color, #e0e0e0);
1272
+ --_selection-border: var(--ls-selection-border, 2px solid #1a73e8);
1273
+ --_selection-bg: var(--ls-selection-bg, rgba(26, 115, 232, 0.1));
1274
+ --_focus-ring: var(--ls-focus-ring, 2px solid #1a73e8);
1275
+ --_editor-bg: var(--ls-editor-bg, #fff);
1276
+ --_editor-shadow: var(--ls-editor-shadow, 0 2px 6px rgba(0,0,0,0.2));
1277
+ --_row-header-width: 50px;
1278
+ }
1279
+
1280
+ *,
1281
+ *::before,
1282
+ *::after {
1283
+ box-sizing: border-box;
1284
+ }
1285
+
1286
+ .ls-wrapper {
1287
+ position: relative;
1288
+ display: flex;
1289
+ flex-direction: column;
1290
+ width: 100%;
1291
+ height: 100%;
1292
+ overflow: hidden;
1293
+ }
1294
+
1295
+ .ls-grid-shell {
1296
+ position: relative;
1297
+ flex: 1 1 auto;
1298
+ min-height: 0;
1299
+ overflow: hidden;
1300
+ }
1301
+
1302
+ .ls-grid {
1303
+ display: grid;
1304
+ grid-template-columns: var(--_row-header-width) repeat(var(--cols), var(--_cell-width));
1305
+ overflow: auto;
1306
+ position: relative;
1307
+ width: 100%;
1308
+ height: 100%;
1309
+ outline: none;
1310
+ }
1311
+
1312
+ .ls-header-row {
1313
+ display: contents;
1314
+ }
1315
+
1316
+ .ls-row {
1317
+ display: contents;
1318
+ }
1319
+
1320
+ .ls-corner-header,
1321
+ .ls-col-header {
1322
+ position: sticky;
1323
+ top: 0;
1324
+ z-index: 3;
1325
+ display: flex;
1326
+ align-items: center;
1327
+ justify-content: center;
1328
+ height: var(--_cell-height);
1329
+ background: var(--_header-bg);
1330
+ border-right: 1px solid var(--_border-color);
1331
+ border-bottom: 2px solid var(--_border-color);
1332
+ font-weight: 600;
1333
+ user-select: none;
1334
+ }
1335
+
1336
+ .ls-corner-header {
1337
+ position: sticky;
1338
+ left: 0;
1339
+ z-index: 4;
1340
+ }
1341
+
1342
+ .ls-row-header {
1343
+ position: sticky;
1344
+ left: 0;
1345
+ z-index: 2;
1346
+ display: flex;
1347
+ align-items: center;
1348
+ justify-content: center;
1349
+ height: var(--_cell-height);
1350
+ background: var(--_header-bg);
1351
+ border-right: 2px solid var(--_border-color);
1352
+ border-bottom: 1px solid var(--_border-color);
1353
+ font-weight: 600;
1354
+ user-select: none;
1355
+ }
1356
+
1357
+ .ls-cell {
1358
+ display: flex;
1359
+ align-items: center;
1360
+ padding: 0 4px;
1361
+ height: var(--_cell-height);
1362
+ border-right: 1px solid var(--_border-color);
1363
+ border-bottom: 1px solid var(--_border-color);
1364
+ overflow: hidden;
1365
+ white-space: nowrap;
1366
+ text-overflow: ellipsis;
1367
+ cursor: cell;
1368
+ outline: none;
1369
+ user-select: none;
1370
+ }
1371
+
1372
+ .ls-cell:focus {
1373
+ outline: var(--_focus-ring);
1374
+ outline-offset: -2px;
1375
+ z-index: 1;
1376
+ }
1377
+
1378
+ .ls-cell[aria-selected="true"] {
1379
+ background: var(--_selection-bg);
1380
+ }
1381
+
1382
+ .ls-cell.active-cell {
1383
+ outline: var(--_selection-border);
1384
+ outline-offset: -2px;
1385
+ z-index: 1;
1386
+ }
1387
+
1388
+ .ls-cell .cell-text {
1389
+ overflow: hidden;
1390
+ text-overflow: ellipsis;
1391
+ }
1392
+
1393
+ #editor {
1394
+ position: absolute;
1395
+ box-sizing: border-box;
1396
+ padding: 0 4px;
1397
+ border: var(--_selection-border);
1398
+ background: var(--_editor-bg);
1399
+ box-shadow: var(--_editor-shadow);
1400
+ font-family: inherit;
1401
+ font-size: inherit;
1402
+ color: inherit;
1403
+ outline: none;
1404
+ z-index: 10;
1405
+ }
1406
+
1407
+ /* Visually hidden live region for screen reader announcements */
1408
+ .sr-only {
1409
+ position: absolute;
1410
+ width: 1px;
1411
+ height: 1px;
1412
+ padding: 0;
1413
+ margin: -1px;
1414
+ overflow: hidden;
1415
+ clip: rect(0, 0, 0, 0);
1416
+ border: 0;
1417
+ }
1418
+ `;
1419
+ f([
1420
+ w({ type: Number })
1421
+ ], u.prototype, "rows", 2);
1422
+ f([
1423
+ w({ type: Number })
1424
+ ], u.prototype, "cols", 2);
1425
+ f([
1426
+ w({ attribute: !1 })
1427
+ ], u.prototype, "data", 2);
1428
+ f([
1429
+ w({ type: Boolean, attribute: "read-only", reflect: !0 })
1430
+ ], u.prototype, "readOnly", 2);
1431
+ f([
1432
+ w({ attribute: !1 })
1433
+ ], u.prototype, "functions", 2);
1434
+ f([
1435
+ E()
1436
+ ], u.prototype, "_isEditing", 2);
1437
+ f([
1438
+ E()
1439
+ ], u.prototype, "_editValue", 2);
1440
+ f([
1441
+ E()
1442
+ ], u.prototype, "_formulaBarMode", 2);
1443
+ f([
1444
+ E()
1445
+ ], u.prototype, "_scrollTop", 2);
1446
+ f([
1447
+ E()
1448
+ ], u.prototype, "_scrollLeft", 2);
1449
+ f([
1450
+ A("#editor")
1451
+ ], u.prototype, "_editor", 2);
1452
+ f([
1453
+ A(".ls-grid")
1454
+ ], u.prototype, "_grid", 2);
1455
+ u = f([
1456
+ D("y11n-spreadsheet")
1457
+ ], u);
1458
+ export {
1459
+ P as ClipboardManager,
1460
+ F as FormulaEngine,
1461
+ B as SelectionManager,
1462
+ v as Y11nFormulaBar,
1463
+ u as Y11nSpreadsheet,
1464
+ y as cellKey,
1465
+ R as colToLetter,
1466
+ O as coordToRef,
1467
+ M as letterToCol,
1468
+ j as parseKey,
1469
+ S as refToCoord
1470
+ };