@ricsam/react-spreadsheets 0.0.0 → 0.0.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.
@@ -0,0 +1,3991 @@
1
+ // src/spreadsheet/spreadsheet.tsx
2
+ import {
3
+ useInitializeSelectionManager,
4
+ useSelectionManager
5
+ } from "@ricsam/selection-manager";
6
+ import React2, {
7
+ forwardRef,
8
+ useCallback as useCallback3,
9
+ useEffect as useEffect3,
10
+ useImperativeHandle,
11
+ useMemo,
12
+ useRef as useRef3,
13
+ useState as useState4
14
+ } from "react";
15
+
16
+ // src/utils/use-resize-observer.ts
17
+ import { useCallback, useRef, useState } from "react";
18
+ function useResizeObserver() {
19
+ const [size, setSize] = useState({
20
+ width: 0,
21
+ height: 0
22
+ });
23
+ const observerRef = useRef(null);
24
+ const ref = useCallback((element) => {
25
+ observerRef.current?.disconnect();
26
+ observerRef.current = null;
27
+ if (!element)
28
+ return;
29
+ const rect = element.getBoundingClientRect();
30
+ setSize((previous) => previous.width === rect.width && previous.height === rect.height ? previous : { width: rect.width, height: rect.height });
31
+ if (typeof ResizeObserver === "undefined")
32
+ return;
33
+ const observer = new ResizeObserver((entries) => {
34
+ const entry = entries[0];
35
+ if (!entry)
36
+ return;
37
+ const { width, height } = entry.contentRect;
38
+ setSize((previous) => previous.width === width && previous.height === height ? previous : { width, height });
39
+ });
40
+ observer.observe(element);
41
+ observerRef.current = observer;
42
+ }, []);
43
+ return [size, ref];
44
+ }
45
+
46
+ // src/utils/use-color-scheme.ts
47
+ import { useEffect, useState as useState2 } from "react";
48
+ function useColorScheme(element) {
49
+ const [scheme, setScheme] = useState2("light");
50
+ useEffect(() => {
51
+ if (typeof window === "undefined" || !window.matchMedia)
52
+ return;
53
+ const query = window.matchMedia("(prefers-color-scheme: dark)");
54
+ const resolve = () => {
55
+ if (element && typeof getComputedStyle === "function") {
56
+ const declared = getComputedStyle(element).colorScheme;
57
+ if (declared === "dark")
58
+ return "dark";
59
+ if (declared === "light")
60
+ return "light";
61
+ }
62
+ return query.matches ? "dark" : "light";
63
+ };
64
+ setScheme(resolve());
65
+ const onChange = () => setScheme(resolve());
66
+ query.addEventListener("change", onChange);
67
+ return () => query.removeEventListener("change", onChange);
68
+ }, [element]);
69
+ return scheme;
70
+ }
71
+
72
+ // src/utils/cn.ts
73
+ function cn(...values) {
74
+ const out = [];
75
+ const walk = (value) => {
76
+ if (!value && value !== 0)
77
+ return;
78
+ if (typeof value === "string" || typeof value === "number") {
79
+ out.push(String(value));
80
+ return;
81
+ }
82
+ if (Array.isArray(value)) {
83
+ value.forEach(walk);
84
+ return;
85
+ }
86
+ if (typeof value === "object") {
87
+ for (const [key, enabled] of Object.entries(value)) {
88
+ if (enabled)
89
+ out.push(key);
90
+ }
91
+ }
92
+ };
93
+ values.forEach(walk);
94
+ return out.join(" ");
95
+ }
96
+
97
+ // src/spreadsheet/constants.ts
98
+ var DEFAULT_CELL_WIDTH = 100;
99
+ var DEFAULT_CELL_HEIGHT = 30;
100
+ var HEADER_WIDTH = 50;
101
+ var HEADER_HEIGHT = 30;
102
+
103
+ // src/spreadsheet/utils.ts
104
+ var columnToIndex = (col) => {
105
+ let result = 0;
106
+ for (let i = 0;i < col.length; i++) {
107
+ result = result * 26 + (col.charCodeAt(i) - 64);
108
+ }
109
+ return result - 1;
110
+ };
111
+ var indexToColumn = (index) => {
112
+ let result = "";
113
+ let num = index + 1;
114
+ while (num > 0) {
115
+ const remainder = (num - 1) % 26;
116
+ result = String.fromCharCode(65 + remainder) + result;
117
+ num = Math.floor((num - 1) / 26);
118
+ }
119
+ return result;
120
+ };
121
+ function getRowNumber(index) {
122
+ return index + 1;
123
+ }
124
+ function getCellReference({ rowIndex, colIndex }) {
125
+ return `${indexToColumn(colIndex)}${getRowNumber(rowIndex)}`;
126
+ }
127
+ function rowToLetter(row) {
128
+ let result = "";
129
+ let num = row - 1;
130
+ do {
131
+ result = String.fromCharCode(65 + num % 26) + result;
132
+ num = Math.floor(num / 26) - 1;
133
+ } while (num >= 0);
134
+ return result;
135
+ }
136
+ function letterToRow(letter) {
137
+ let result = 0;
138
+ for (let i = 0;i < letter.length; i++) {
139
+ result = result * 26 + (letter.charCodeAt(i) - 64);
140
+ }
141
+ return result;
142
+ }
143
+ var parseCellReference = (cellReference) => {
144
+ const match = cellReference.match(/^([A-Z]+)(\d+)$/);
145
+ const column = match?.[1];
146
+ const rowText = match?.[2];
147
+ if (!column || !rowText) {
148
+ throw new Error(`Invalid cell reference: ${cellReference}`);
149
+ }
150
+ const row = parseInt(rowText, 10);
151
+ const columnIndex = columnToIndex(column);
152
+ const rowIndex = row - 1;
153
+ return {
154
+ column,
155
+ row,
156
+ columnIndex,
157
+ rowIndex
158
+ };
159
+ };
160
+ function findCell(map, search) {
161
+ for (const [key, cell] of map) {
162
+ if (cell === search) {
163
+ const parsed = parseCellReference(key);
164
+ if (!parsed) {
165
+ return;
166
+ }
167
+ return {
168
+ cellReference: cell,
169
+ ...parsed
170
+ };
171
+ }
172
+ }
173
+ }
174
+ function extractColumnByHeader(map, header) {
175
+ for (const [cell, value] of map) {
176
+ if (value === header) {
177
+ const parsed = parseCellReference(cell);
178
+ if (!parsed) {
179
+ return [];
180
+ }
181
+ return extractColumn(map, parsed.columnIndex, parsed.rowIndex + 1);
182
+ }
183
+ }
184
+ return [];
185
+ }
186
+ function extractColumn(map, colIndex, rowIndex = 0, handleEmpty) {
187
+ const rows = [];
188
+ for (const [cell, value] of map) {
189
+ const parsed = parseCellReference(cell);
190
+ if (!parsed)
191
+ continue;
192
+ if (parsed.columnIndex === colIndex && parsed.rowIndex >= rowIndex) {
193
+ rows.push([parsed.rowIndex, value]);
194
+ }
195
+ }
196
+ rows.sort((a, b) => a[0] - b[0]);
197
+ if (!handleEmpty) {
198
+ return rows.map(([, value]) => value);
199
+ }
200
+ if (rows.length === 0) {
201
+ return [];
202
+ }
203
+ const result = [];
204
+ const lastRow = rows[rows.length - 1];
205
+ if (!lastRow) {
206
+ return [];
207
+ }
208
+ const maxRowIndex = lastRow[0];
209
+ const rowMap = new Map(rows);
210
+ for (let currentRow = rowIndex;currentRow <= maxRowIndex; currentRow++) {
211
+ if (rowMap.has(currentRow)) {
212
+ result.push(rowMap.get(currentRow));
213
+ } else {
214
+ result.push(handleEmpty(currentRow));
215
+ }
216
+ }
217
+ return result;
218
+ }
219
+
220
+ // src/clipboard/clipboard-manager.ts
221
+ class ClipboardUtils {
222
+ extractCellsFromSelection(selectionManager, cellData) {
223
+ const state = selectionManager.getState();
224
+ const selection = state.selections[state.selections.length - 1];
225
+ if (!selection)
226
+ return;
227
+ const finiteEndRow = selection.end.row.type === "number" ? selection.end.row.value : undefined;
228
+ const finiteEndCol = selection.end.col.type === "number" ? selection.end.col.value : undefined;
229
+ const startRow = Math.min(selection.start.row, finiteEndRow ?? selection.start.row);
230
+ const startCol = Math.min(selection.start.col, finiteEndCol ?? selection.start.col);
231
+ let endRow = startRow;
232
+ let endCol = startCol;
233
+ const cells = [];
234
+ cellData.forEach((value, key) => {
235
+ let parsed;
236
+ try {
237
+ parsed = parseCellReference(key);
238
+ } catch {
239
+ return;
240
+ }
241
+ const { rowIndex, columnIndex } = parsed;
242
+ if (rowIndex < startRow || columnIndex < startCol)
243
+ return;
244
+ if (finiteEndRow !== undefined && rowIndex > finiteEndRow)
245
+ return;
246
+ if (finiteEndCol !== undefined && columnIndex > finiteEndCol)
247
+ return;
248
+ endRow = Math.max(endRow, rowIndex);
249
+ endCol = Math.max(endCol, columnIndex);
250
+ cells.push({
251
+ relative: {
252
+ rowIndex: rowIndex - startRow,
253
+ columnIndex: columnIndex - startCol
254
+ },
255
+ absolute: { rowIndex, columnIndex },
256
+ value,
257
+ key
258
+ });
259
+ });
260
+ if (finiteEndRow !== undefined)
261
+ endRow = finiteEndRow;
262
+ if (finiteEndCol !== undefined)
263
+ endCol = finiteEndCol;
264
+ return {
265
+ width: endCol - startCol + 1,
266
+ height: endRow - startRow + 1,
267
+ cells
268
+ };
269
+ }
270
+ createExportGrid(width, height) {
271
+ return Array.from({ length: Math.max(0, height) }, () => Array.from({ length: Math.max(0, width) }).fill(""));
272
+ }
273
+ getTsvString(grid) {
274
+ return grid.map((row) => row.map((cell) => cell === undefined ? "" : String(cell)).join("\t")).join(`
275
+ `);
276
+ }
277
+ writeToOsClipboard(grid) {
278
+ const text = this.getTsvString(grid);
279
+ if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
280
+ navigator.clipboard.writeText(text).catch(() => {});
281
+ }
282
+ return text;
283
+ }
284
+ }
285
+
286
+ // src/grid/frame.tsx
287
+ import { useRef as useRef2, useEffect as useEffect2, useState as useState3, useCallback as useCallback2 } from "react";
288
+
289
+ // src/grid/icons.tsx
290
+ import { jsx } from "react/jsx-runtime";
291
+ var iconBase = (size) => ({
292
+ width: size,
293
+ height: size,
294
+ viewBox: "0 0 24 24",
295
+ fill: "currentColor",
296
+ "aria-hidden": true,
297
+ focusable: false
298
+ });
299
+ function DeleteIcon({ size = 16, ...props }) {
300
+ return /* @__PURE__ */ jsx("svg", {
301
+ ...iconBase(size),
302
+ ...props,
303
+ children: /* @__PURE__ */ jsx("path", {
304
+ d: "M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
305
+ })
306
+ });
307
+ }
308
+ function GridOnIcon({ size = 16, ...props }) {
309
+ return /* @__PURE__ */ jsx("svg", {
310
+ ...iconBase(size),
311
+ ...props,
312
+ children: /* @__PURE__ */ jsx("path", {
313
+ d: "M20 2H4c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zM8 20H4v-4h4v4zm0-6H4v-4h4v4zm0-6H4V4h4v4zm6 12h-4v-4h4v4zm0-6h-4v-4h4v4zm0-6h-4V4h4v4zm6 12h-4v-4h4v4zm0-6h-4v-4h4v4zm0-6h-4V4h4v4z"
314
+ })
315
+ });
316
+ }
317
+ function GridOffIcon({ size = 16, ...props }) {
318
+ return /* @__PURE__ */ jsx("svg", {
319
+ ...iconBase(size),
320
+ ...props,
321
+ children: /* @__PURE__ */ jsx("path", {
322
+ d: "M8 4v1.45l2 2V4h4v4h-3.45l2 2H14v1.45l2 2V10h4v4h-3.45l2 2H20v1.45l1.71 1.71c.18-.28.29-.6.29-.96V4c0-1.1-.9-2-2-2H4.55L6.55 4H8zm12 0v4h-4V4h4zM1.27 1.27L0 2.55l2 2V20c0 1.1.9 2 2 2h15.46l2 2 1.27-1.27L1.27 1.27zM10 12.55L11.45 14H10v-1.45zm-6-6L5.45 8H4V6.55zM8 20H4v-4h4v4zm0-6H4v-4h4v4zm6 6h-4v-4h4v4zm2 0v-1.45L17.45 20H16z"
323
+ })
324
+ });
325
+ }
326
+
327
+ // src/grid/frame.tsx
328
+ import { jsx as jsx2, jsxs, Fragment } from "react/jsx-runtime";
329
+ function Frame({
330
+ child,
331
+ isSelected,
332
+ zoom,
333
+ viewport$,
334
+ onMove,
335
+ onResize,
336
+ onSnapToCellsToggle,
337
+ onReset,
338
+ onMinimize,
339
+ onMaximize,
340
+ onClose,
341
+ onInteractionEnd
342
+ }) {
343
+ const [isDragging, setIsDragging] = useState3(false);
344
+ const [isResizing, setIsResizing] = useState3(null);
345
+ const [isHoveringHandle, setIsHoveringHandle] = useState3(null);
346
+ const [isHoveringFrame, setIsHoveringFrame] = useState3(false);
347
+ const titleBarRef = useRef2(null);
348
+ const onMoveRef = useRef2(onMove);
349
+ const onResizeRef = useRef2(onResize);
350
+ const onInteractionEndRef = useRef2(onInteractionEnd);
351
+ const dragStartRef = useRef2({
352
+ x: 0,
353
+ y: 0,
354
+ viewportX: 0,
355
+ viewportY: 0,
356
+ viewportZoom: 1,
357
+ interactionScale: 1
358
+ });
359
+ const resizeStartRef = useRef2({
360
+ x: 0,
361
+ y: 0,
362
+ width: 0,
363
+ height: 0,
364
+ viewportX: 0,
365
+ viewportY: 0,
366
+ viewportZoom: 1,
367
+ interactionScale: 1
368
+ });
369
+ useEffect2(() => {
370
+ onMoveRef.current = onMove;
371
+ onResizeRef.current = onResize;
372
+ onInteractionEndRef.current = onInteractionEnd;
373
+ }, [onMove, onResize, onInteractionEnd]);
374
+ const getInteractionScale = useCallback2(() => {
375
+ const renderedWidth = titleBarRef.current?.getBoundingClientRect().width;
376
+ if (!renderedWidth || !Number.isFinite(renderedWidth) || child.width <= 0) {
377
+ return zoom;
378
+ }
379
+ return renderedWidth / child.width;
380
+ }, [child.width, zoom]);
381
+ const handleMouseDown = useCallback2((e) => {
382
+ if (!isSelected)
383
+ return;
384
+ const viewport = viewport$.currentViewport;
385
+ e.preventDefault();
386
+ e.stopPropagation();
387
+ setIsDragging(true);
388
+ dragStartRef.current = {
389
+ x: e.clientX,
390
+ y: e.clientY,
391
+ viewportX: viewport.x,
392
+ viewportY: viewport.y,
393
+ viewportZoom: viewport.zoom,
394
+ interactionScale: getInteractionScale()
395
+ };
396
+ }, [getInteractionScale, isSelected, viewport$]);
397
+ const handleResizeMouseDown = useCallback2((e, handle) => {
398
+ if (!isSelected)
399
+ return;
400
+ const viewport = viewport$.currentViewport;
401
+ e.preventDefault();
402
+ e.stopPropagation();
403
+ setIsResizing(handle);
404
+ resizeStartRef.current = {
405
+ x: e.clientX,
406
+ y: e.clientY,
407
+ width: child.width,
408
+ height: child.height,
409
+ viewportX: viewport.x,
410
+ viewportY: viewport.y,
411
+ viewportZoom: viewport.zoom,
412
+ interactionScale: getInteractionScale()
413
+ };
414
+ }, [getInteractionScale, isSelected, child.width, child.height, viewport$]);
415
+ useEffect2(() => {
416
+ if (!isDragging && !isResizing)
417
+ return;
418
+ const handleMouseMove = (e) => {
419
+ const viewport = viewport$.currentViewport;
420
+ if (isDragging) {
421
+ const mouseDeltaX = (e.clientX - dragStartRef.current.x) / dragStartRef.current.interactionScale;
422
+ const mouseDeltaY = (e.clientY - dragStartRef.current.y) / dragStartRef.current.interactionScale;
423
+ const viewportDeltaX = (viewport.x - dragStartRef.current.viewportX) / viewport.zoom;
424
+ const viewportDeltaY = (viewport.y - dragStartRef.current.viewportY) / viewport.zoom;
425
+ const totalDeltaX = mouseDeltaX - viewportDeltaX;
426
+ const totalDeltaY = mouseDeltaY - viewportDeltaY;
427
+ onMoveRef.current(totalDeltaX, totalDeltaY);
428
+ dragStartRef.current = {
429
+ x: e.clientX,
430
+ y: e.clientY,
431
+ viewportX: viewport.x,
432
+ viewportY: viewport.y,
433
+ viewportZoom: viewport.zoom,
434
+ interactionScale: dragStartRef.current.interactionScale
435
+ };
436
+ } else if (isResizing) {
437
+ const mouseDeltaX = (e.clientX - resizeStartRef.current.x) / resizeStartRef.current.interactionScale;
438
+ const mouseDeltaY = (e.clientY - resizeStartRef.current.y) / resizeStartRef.current.interactionScale;
439
+ const viewportDeltaX = (viewport.x - resizeStartRef.current.viewportX) / zoom;
440
+ const viewportDeltaY = (viewport.y - resizeStartRef.current.viewportY) / zoom;
441
+ const totalDeltaX = mouseDeltaX - viewportDeltaX;
442
+ const totalDeltaY = mouseDeltaY - viewportDeltaY;
443
+ let deltaWidth = 0;
444
+ let deltaHeight = 0;
445
+ switch (isResizing) {
446
+ case "right":
447
+ deltaWidth = totalDeltaX;
448
+ break;
449
+ case "left":
450
+ deltaWidth = -totalDeltaX;
451
+ break;
452
+ case "bottom":
453
+ deltaHeight = totalDeltaY;
454
+ break;
455
+ case "top":
456
+ deltaHeight = -totalDeltaY;
457
+ break;
458
+ case "bottom-right":
459
+ deltaWidth = totalDeltaX;
460
+ deltaHeight = totalDeltaY;
461
+ break;
462
+ case "bottom-left":
463
+ deltaWidth = -totalDeltaX;
464
+ deltaHeight = totalDeltaY;
465
+ break;
466
+ case "top-right":
467
+ deltaWidth = totalDeltaX;
468
+ deltaHeight = -totalDeltaY;
469
+ break;
470
+ case "top-left":
471
+ deltaWidth = -totalDeltaX;
472
+ deltaHeight = -totalDeltaY;
473
+ break;
474
+ }
475
+ onResizeRef.current(deltaWidth, deltaHeight, e.shiftKey, isResizing);
476
+ resizeStartRef.current.x = e.clientX;
477
+ resizeStartRef.current.y = e.clientY;
478
+ resizeStartRef.current.viewportX = viewport.x;
479
+ resizeStartRef.current.viewportY = viewport.y;
480
+ resizeStartRef.current.viewportZoom = viewport.zoom;
481
+ }
482
+ };
483
+ const handleViewportChange = (newViewport) => {
484
+ if (isDragging) {
485
+ const zoomChanged = newViewport.zoom !== dragStartRef.current.viewportZoom;
486
+ if (!zoomChanged) {
487
+ const viewportDeltaX = (newViewport.x - dragStartRef.current.viewportX) / newViewport.zoom;
488
+ const viewportDeltaY = (newViewport.y - dragStartRef.current.viewportY) / newViewport.zoom;
489
+ onMoveRef.current(-viewportDeltaX, -viewportDeltaY);
490
+ }
491
+ dragStartRef.current.viewportX = newViewport.x;
492
+ dragStartRef.current.viewportY = newViewport.y;
493
+ dragStartRef.current.viewportZoom = newViewport.zoom;
494
+ } else if (isResizing) {
495
+ const zoomChanged = newViewport.zoom !== resizeStartRef.current.viewportZoom;
496
+ if (!zoomChanged) {
497
+ const viewportDeltaX = (newViewport.x - resizeStartRef.current.viewportX) / newViewport.zoom;
498
+ const viewportDeltaY = (newViewport.y - resizeStartRef.current.viewportY) / newViewport.zoom;
499
+ let deltaWidth = 0;
500
+ let deltaHeight = 0;
501
+ switch (isResizing) {
502
+ case "right":
503
+ deltaWidth = -viewportDeltaX;
504
+ break;
505
+ case "left":
506
+ deltaWidth = viewportDeltaX;
507
+ break;
508
+ case "bottom":
509
+ deltaHeight = -viewportDeltaY;
510
+ break;
511
+ case "top":
512
+ deltaHeight = viewportDeltaY;
513
+ break;
514
+ case "bottom-right":
515
+ deltaWidth = -viewportDeltaX;
516
+ deltaHeight = -viewportDeltaY;
517
+ break;
518
+ case "bottom-left":
519
+ deltaWidth = viewportDeltaX;
520
+ deltaHeight = -viewportDeltaY;
521
+ break;
522
+ case "top-right":
523
+ deltaWidth = -viewportDeltaX;
524
+ deltaHeight = viewportDeltaY;
525
+ break;
526
+ case "top-left":
527
+ deltaWidth = viewportDeltaX;
528
+ deltaHeight = viewportDeltaY;
529
+ break;
530
+ }
531
+ if (deltaWidth !== 0 || deltaHeight !== 0) {
532
+ onResizeRef.current(deltaWidth, deltaHeight, false, isResizing);
533
+ }
534
+ }
535
+ resizeStartRef.current.viewportX = newViewport.x;
536
+ resizeStartRef.current.viewportY = newViewport.y;
537
+ resizeStartRef.current.viewportZoom = newViewport.zoom;
538
+ }
539
+ };
540
+ const handleMouseUp = () => {
541
+ setIsDragging(false);
542
+ setIsResizing(null);
543
+ onInteractionEndRef.current?.();
544
+ };
545
+ const unsubscribe = viewport$.subscribe(handleViewportChange);
546
+ document.addEventListener("mousemove", handleMouseMove);
547
+ document.addEventListener("mouseup", handleMouseUp);
548
+ return () => {
549
+ document.removeEventListener("mousemove", handleMouseMove);
550
+ document.removeEventListener("mouseup", handleMouseUp);
551
+ unsubscribe();
552
+ };
553
+ }, [isDragging, isResizing, zoom, viewport$]);
554
+ const isTextBox = child.component === "RichText";
555
+ const isSnappedToCells = Boolean(child.snapToCells);
556
+ if (!isSelected && (!child.minimized || isTextBox))
557
+ return null;
558
+ const handleSize = 4;
559
+ const textBoxHandleSize = 8;
560
+ const textBoxEdgeSize = 8;
561
+ const titleBarHeight = 32;
562
+ const getResizeHandleStyle = (handle, baseStyle) => ({
563
+ ...baseStyle,
564
+ background: isHoveringHandle === handle || isResizing === handle ? "rgba(102, 126, 234, 1)" : "rgba(102, 126, 234, 0.4)",
565
+ transition: "all 0.2s ease-in-out",
566
+ opacity: 1,
567
+ zIndex: isHoveringHandle === handle || isResizing === handle ? 30 : handle.includes("-") ? 20 : 10
568
+ });
569
+ const getTextBoxResizeHandleStyle = (handle, baseStyle) => ({
570
+ ...baseStyle,
571
+ position: "absolute",
572
+ width: textBoxHandleSize,
573
+ height: textBoxHandleSize,
574
+ boxSizing: "border-box",
575
+ border: "1px solid #1a73e8",
576
+ background: isHoveringHandle === handle || isResizing === handle ? "#e8f0fe" : "#ffffff",
577
+ borderRadius: 1,
578
+ pointerEvents: "auto",
579
+ zIndex: 30
580
+ });
581
+ const getTextBoxDragEdgeStyle = (baseStyle) => ({
582
+ ...baseStyle,
583
+ position: "absolute",
584
+ background: "transparent",
585
+ cursor: isDragging ? "grabbing" : "move",
586
+ pointerEvents: "auto",
587
+ zIndex: 20
588
+ });
589
+ if (isTextBox) {
590
+ return /* @__PURE__ */ jsxs("div", {
591
+ ref: titleBarRef,
592
+ style: {
593
+ position: "absolute",
594
+ inset: 0,
595
+ pointerEvents: "none",
596
+ border: "1px solid #1a73e8",
597
+ boxSizing: "border-box",
598
+ zIndex: 20
599
+ },
600
+ children: [
601
+ onSnapToCellsToggle && /* @__PURE__ */ jsx2("button", {
602
+ type: "button",
603
+ title: isSnappedToCells ? "Disable cell snapping" : "Snap to cells",
604
+ "aria-pressed": isSnappedToCells,
605
+ onMouseDown: (ev) => {
606
+ ev.stopPropagation();
607
+ ev.preventDefault();
608
+ },
609
+ onMouseUp: (ev) => {
610
+ ev.stopPropagation();
611
+ ev.preventDefault();
612
+ },
613
+ onClick: (e) => {
614
+ console.log("123");
615
+ e.preventDefault();
616
+ e.stopPropagation();
617
+ onSnapToCellsToggle(!isSnappedToCells);
618
+ },
619
+ style: {
620
+ position: "absolute",
621
+ top: -30,
622
+ right: 0,
623
+ width: 26,
624
+ height: 26,
625
+ display: "flex",
626
+ alignItems: "center",
627
+ justifyContent: "center",
628
+ padding: 0,
629
+ border: "1px solid #1a73e8",
630
+ borderRadius: 4,
631
+ background: isSnappedToCells ? "#e8f0fe" : "#ffffff",
632
+ color: "#1a73e8",
633
+ cursor: "pointer",
634
+ pointerEvents: "auto",
635
+ boxShadow: "0 1px 3px rgba(0, 0, 0, 0.2)",
636
+ zIndex: 40
637
+ },
638
+ children: isSnappedToCells ? /* @__PURE__ */ jsx2(GridOnIcon, {
639
+ size: 16
640
+ }) : /* @__PURE__ */ jsx2(GridOffIcon, {
641
+ size: 16
642
+ })
643
+ }),
644
+ child.canClose && /* @__PURE__ */ jsx2("button", {
645
+ type: "button",
646
+ title: "Delete text box",
647
+ onMouseDown: (ev) => {
648
+ ev.stopPropagation();
649
+ ev.preventDefault();
650
+ },
651
+ onMouseUp: (ev) => {
652
+ ev.stopPropagation();
653
+ ev.preventDefault();
654
+ },
655
+ onClick: (e) => {
656
+ e.preventDefault();
657
+ e.stopPropagation();
658
+ onClose();
659
+ },
660
+ style: {
661
+ position: "absolute",
662
+ top: -30,
663
+ right: onSnapToCellsToggle ? 30 : 0,
664
+ width: 26,
665
+ height: 26,
666
+ display: "flex",
667
+ alignItems: "center",
668
+ justifyContent: "center",
669
+ padding: 0,
670
+ border: "1px solid #d93025",
671
+ borderRadius: 4,
672
+ background: "#ffffff",
673
+ color: "#d93025",
674
+ cursor: "pointer",
675
+ pointerEvents: "auto",
676
+ boxShadow: "0 1px 3px rgba(0, 0, 0, 0.2)",
677
+ zIndex: 40
678
+ },
679
+ children: /* @__PURE__ */ jsx2(DeleteIcon, {
680
+ size: 16
681
+ })
682
+ }),
683
+ /* @__PURE__ */ jsx2("div", {
684
+ style: getTextBoxDragEdgeStyle({
685
+ top: -textBoxEdgeSize / 2,
686
+ left: textBoxHandleSize,
687
+ right: textBoxHandleSize,
688
+ height: textBoxEdgeSize
689
+ }),
690
+ onMouseDown: handleMouseDown
691
+ }),
692
+ /* @__PURE__ */ jsx2("div", {
693
+ style: getTextBoxDragEdgeStyle({
694
+ bottom: -textBoxEdgeSize / 2,
695
+ left: textBoxHandleSize,
696
+ right: textBoxHandleSize,
697
+ height: textBoxEdgeSize
698
+ }),
699
+ onMouseDown: handleMouseDown
700
+ }),
701
+ /* @__PURE__ */ jsx2("div", {
702
+ style: getTextBoxDragEdgeStyle({
703
+ top: textBoxHandleSize,
704
+ bottom: textBoxHandleSize,
705
+ left: -textBoxEdgeSize / 2,
706
+ width: textBoxEdgeSize
707
+ }),
708
+ onMouseDown: handleMouseDown
709
+ }),
710
+ /* @__PURE__ */ jsx2("div", {
711
+ style: getTextBoxDragEdgeStyle({
712
+ top: textBoxHandleSize,
713
+ bottom: textBoxHandleSize,
714
+ right: -textBoxEdgeSize / 2,
715
+ width: textBoxEdgeSize
716
+ }),
717
+ onMouseDown: handleMouseDown
718
+ }),
719
+ /* @__PURE__ */ jsx2("div", {
720
+ style: getTextBoxResizeHandleStyle("top-left", {
721
+ top: -textBoxHandleSize / 2,
722
+ left: -textBoxHandleSize / 2,
723
+ cursor: "nw-resize"
724
+ }),
725
+ onMouseDown: (e) => handleResizeMouseDown(e, "top-left"),
726
+ onMouseEnter: () => setIsHoveringHandle("top-left"),
727
+ onMouseLeave: () => setIsHoveringHandle(null)
728
+ }),
729
+ /* @__PURE__ */ jsx2("div", {
730
+ style: getTextBoxResizeHandleStyle("top", {
731
+ top: -textBoxHandleSize / 2,
732
+ left: "50%",
733
+ marginLeft: -textBoxHandleSize / 2,
734
+ cursor: "n-resize"
735
+ }),
736
+ onMouseDown: (e) => handleResizeMouseDown(e, "top"),
737
+ onMouseEnter: () => setIsHoveringHandle("top"),
738
+ onMouseLeave: () => setIsHoveringHandle(null)
739
+ }),
740
+ /* @__PURE__ */ jsx2("div", {
741
+ style: getTextBoxResizeHandleStyle("top-right", {
742
+ top: -textBoxHandleSize / 2,
743
+ right: -textBoxHandleSize / 2,
744
+ cursor: "ne-resize"
745
+ }),
746
+ onMouseDown: (e) => handleResizeMouseDown(e, "top-right"),
747
+ onMouseEnter: () => setIsHoveringHandle("top-right"),
748
+ onMouseLeave: () => setIsHoveringHandle(null)
749
+ }),
750
+ /* @__PURE__ */ jsx2("div", {
751
+ style: getTextBoxResizeHandleStyle("right", {
752
+ top: "50%",
753
+ right: -textBoxHandleSize / 2,
754
+ marginTop: -textBoxHandleSize / 2,
755
+ cursor: "e-resize"
756
+ }),
757
+ onMouseDown: (e) => handleResizeMouseDown(e, "right"),
758
+ onMouseEnter: () => setIsHoveringHandle("right"),
759
+ onMouseLeave: () => setIsHoveringHandle(null)
760
+ }),
761
+ /* @__PURE__ */ jsx2("div", {
762
+ style: getTextBoxResizeHandleStyle("bottom-right", {
763
+ bottom: -textBoxHandleSize / 2,
764
+ right: -textBoxHandleSize / 2,
765
+ cursor: "se-resize"
766
+ }),
767
+ onMouseDown: (e) => handleResizeMouseDown(e, "bottom-right"),
768
+ onMouseEnter: () => setIsHoveringHandle("bottom-right"),
769
+ onMouseLeave: () => setIsHoveringHandle(null)
770
+ }),
771
+ /* @__PURE__ */ jsx2("div", {
772
+ style: getTextBoxResizeHandleStyle("bottom", {
773
+ bottom: -textBoxHandleSize / 2,
774
+ left: "50%",
775
+ marginLeft: -textBoxHandleSize / 2,
776
+ cursor: "s-resize"
777
+ }),
778
+ onMouseDown: (e) => handleResizeMouseDown(e, "bottom"),
779
+ onMouseEnter: () => setIsHoveringHandle("bottom"),
780
+ onMouseLeave: () => setIsHoveringHandle(null)
781
+ }),
782
+ /* @__PURE__ */ jsx2("div", {
783
+ style: getTextBoxResizeHandleStyle("bottom-left", {
784
+ bottom: -textBoxHandleSize / 2,
785
+ left: -textBoxHandleSize / 2,
786
+ cursor: "sw-resize"
787
+ }),
788
+ onMouseDown: (e) => handleResizeMouseDown(e, "bottom-left"),
789
+ onMouseEnter: () => setIsHoveringHandle("bottom-left"),
790
+ onMouseLeave: () => setIsHoveringHandle(null)
791
+ }),
792
+ /* @__PURE__ */ jsx2("div", {
793
+ style: getTextBoxResizeHandleStyle("left", {
794
+ top: "50%",
795
+ left: -textBoxHandleSize / 2,
796
+ marginTop: -textBoxHandleSize / 2,
797
+ cursor: "w-resize"
798
+ }),
799
+ onMouseDown: (e) => handleResizeMouseDown(e, "left"),
800
+ onMouseEnter: () => setIsHoveringHandle("left"),
801
+ onMouseLeave: () => setIsHoveringHandle(null)
802
+ })
803
+ ]
804
+ });
805
+ }
806
+ return /* @__PURE__ */ jsxs(Fragment, {
807
+ children: [
808
+ /* @__PURE__ */ jsxs("div", {
809
+ ref: titleBarRef,
810
+ style: {
811
+ position: "absolute",
812
+ top: -titleBarHeight,
813
+ left: 0,
814
+ right: 0,
815
+ height: titleBarHeight,
816
+ background: child.minimized && !isSelected ? "linear-gradient(135deg, rgba(107, 114, 128, 0.95) 0%, rgba(75, 85, 99, 0.95) 100%)" : "linear-gradient(135deg, rgba(102, 126, 234, 0.95) 0%, rgba(118, 75, 162, 0.95) 100%)",
817
+ backdropFilter: "blur(12px)",
818
+ border: "1px solid rgba(255, 255, 255, 0.2)",
819
+ borderBottom: "1px solid rgba(102, 126, 234, 0.4)",
820
+ color: "white",
821
+ display: "flex",
822
+ alignItems: "center",
823
+ justifyContent: "space-between",
824
+ padding: "0 16px",
825
+ cursor: isDragging ? "grabbing" : "grab",
826
+ userSelect: "none",
827
+ fontSize: "13px",
828
+ fontWeight: 600,
829
+ letterSpacing: "0.025em",
830
+ opacity: child.minimized && !isSelected ? 0.9 : 1,
831
+ transition: "all 0.3s cubic-bezier(0.4, 0, 0.2, 1)",
832
+ boxShadow: "0 2px 8px rgba(0, 0, 0, 0.1)"
833
+ },
834
+ onMouseDown: handleMouseDown,
835
+ children: [
836
+ /* @__PURE__ */ jsx2("span", {
837
+ style: {
838
+ overflow: "hidden",
839
+ textOverflow: "ellipsis",
840
+ whiteSpace: "nowrap",
841
+ textShadow: "0 1px 2px rgba(0, 0, 0, 0.3)"
842
+ },
843
+ children: child.title
844
+ }),
845
+ /* @__PURE__ */ jsxs("div", {
846
+ style: { display: "flex", gap: "6px", alignItems: "center" },
847
+ children: [
848
+ /* @__PURE__ */ jsx2("button", {
849
+ onClick: (e) => {
850
+ e.stopPropagation();
851
+ onMinimize();
852
+ },
853
+ style: {
854
+ background: "rgba(255, 255, 255, 0.1)",
855
+ border: "1px solid rgba(255, 255, 255, 0.2)",
856
+ borderRadius: "6px",
857
+ color: "white",
858
+ cursor: "pointer",
859
+ padding: "4px 8px",
860
+ fontSize: "12px",
861
+ fontWeight: "600",
862
+ lineHeight: 1,
863
+ transition: "all 0.2s cubic-bezier(0.4, 0, 0.2, 1)",
864
+ backdropFilter: "blur(8px)"
865
+ },
866
+ onMouseEnter: (e) => {
867
+ e.currentTarget.style.background = "rgba(255, 255, 255, 0.2)";
868
+ e.currentTarget.style.transform = "translateY(-1px)";
869
+ e.currentTarget.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.2)";
870
+ },
871
+ onMouseLeave: (e) => {
872
+ e.currentTarget.style.background = "rgba(255, 255, 255, 0.1)";
873
+ e.currentTarget.style.transform = "translateY(0)";
874
+ e.currentTarget.style.boxShadow = "none";
875
+ },
876
+ title: child.minimized ? "Restore" : "Minimize",
877
+ children: child.minimized ? "□" : "−"
878
+ }),
879
+ onMaximize && /* @__PURE__ */ jsx2("button", {
880
+ onClick: (e) => {
881
+ e.stopPropagation();
882
+ onMaximize();
883
+ },
884
+ style: {
885
+ background: "rgba(255, 255, 255, 0.1)",
886
+ border: "1px solid rgba(255, 255, 255, 0.2)",
887
+ borderRadius: "6px",
888
+ color: "white",
889
+ cursor: "pointer",
890
+ padding: "4px 8px",
891
+ fontSize: "12px",
892
+ fontWeight: "600",
893
+ lineHeight: 1,
894
+ transition: "all 0.2s cubic-bezier(0.4, 0, 0.2, 1)",
895
+ backdropFilter: "blur(8px)"
896
+ },
897
+ onMouseEnter: (e) => {
898
+ e.currentTarget.style.background = "rgba(255, 255, 255, 0.2)";
899
+ e.currentTarget.style.transform = "translateY(-1px)";
900
+ e.currentTarget.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.2)";
901
+ },
902
+ onMouseLeave: (e) => {
903
+ e.currentTarget.style.background = "rgba(255, 255, 255, 0.1)";
904
+ e.currentTarget.style.transform = "translateY(0)";
905
+ e.currentTarget.style.boxShadow = "none";
906
+ },
907
+ title: child.maximized ? "Restore" : "Maximize",
908
+ children: child.maximized ? "◱" : "⤢"
909
+ }),
910
+ /* @__PURE__ */ jsx2("button", {
911
+ onClick: (e) => {
912
+ e.stopPropagation();
913
+ onReset();
914
+ },
915
+ style: {
916
+ background: "rgba(255, 255, 255, 0.1)",
917
+ border: "1px solid rgba(255, 255, 255, 0.2)",
918
+ borderRadius: "6px",
919
+ color: "white",
920
+ cursor: child.initial ? "pointer" : "auto",
921
+ padding: "4px 8px",
922
+ fontSize: "12px",
923
+ fontWeight: "600",
924
+ lineHeight: 1,
925
+ transition: "all 0.2s cubic-bezier(0.4, 0, 0.2, 1)",
926
+ backdropFilter: "blur(8px)",
927
+ opacity: child.initial ? 1 : 0.2
928
+ },
929
+ disabled: !child.initial,
930
+ onMouseEnter: (e) => {
931
+ e.currentTarget.style.background = "rgba(255, 255, 255, 0.2)";
932
+ e.currentTarget.style.transform = "translateY(-1px)";
933
+ e.currentTarget.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.2)";
934
+ },
935
+ onMouseLeave: (e) => {
936
+ e.currentTarget.style.background = "rgba(255, 255, 255, 0.1)";
937
+ e.currentTarget.style.transform = "translateY(0)";
938
+ e.currentTarget.style.boxShadow = "none";
939
+ },
940
+ title: "Reset",
941
+ children: "↻"
942
+ }),
943
+ child.canClose && /* @__PURE__ */ jsx2("button", {
944
+ onClick: (e) => {
945
+ e.stopPropagation();
946
+ onClose();
947
+ },
948
+ style: {
949
+ background: "rgba(255, 255, 255, 0.1)",
950
+ border: "1px solid rgba(255, 255, 255, 0.2)",
951
+ borderRadius: "6px",
952
+ color: "white",
953
+ cursor: "pointer",
954
+ padding: "4px 8px",
955
+ fontSize: "12px",
956
+ fontWeight: "600",
957
+ lineHeight: 1,
958
+ transition: "all 0.2s cubic-bezier(0.4, 0, 0.2, 1)",
959
+ backdropFilter: "blur(8px)"
960
+ },
961
+ onMouseEnter: (e) => {
962
+ e.currentTarget.style.background = "rgba(255, 255, 255, 0.2)";
963
+ e.currentTarget.style.transform = "translateY(-1px)";
964
+ e.currentTarget.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.2)";
965
+ },
966
+ onMouseLeave: (e) => {
967
+ e.currentTarget.style.background = "rgba(255, 255, 255, 0.1)";
968
+ e.currentTarget.style.transform = "translateY(0)";
969
+ e.currentTarget.style.boxShadow = "none";
970
+ },
971
+ title: "Close",
972
+ children: "×"
973
+ })
974
+ ]
975
+ })
976
+ ]
977
+ }),
978
+ isSelected && !child.minimized && /* @__PURE__ */ jsxs("div", {
979
+ onMouseEnter: () => !isResizing && !isDragging && setIsHoveringFrame(true),
980
+ onMouseLeave: () => !isResizing && !isDragging && setIsHoveringFrame(false),
981
+ children: [
982
+ /* @__PURE__ */ jsx2("div", {
983
+ style: getResizeHandleStyle("top", {
984
+ position: "absolute",
985
+ top: -titleBarHeight - handleSize,
986
+ left: 0,
987
+ right: 0,
988
+ height: handleSize,
989
+ cursor: "n-resize"
990
+ }),
991
+ onMouseDown: (e) => handleResizeMouseDown(e, "top"),
992
+ onMouseEnter: () => setIsHoveringHandle("top"),
993
+ onMouseLeave: () => setIsHoveringHandle(null)
994
+ }),
995
+ /* @__PURE__ */ jsx2("div", {
996
+ style: getResizeHandleStyle("bottom", {
997
+ position: "absolute",
998
+ bottom: -handleSize,
999
+ left: 0,
1000
+ right: 0,
1001
+ height: handleSize,
1002
+ cursor: "s-resize"
1003
+ }),
1004
+ onMouseDown: (e) => handleResizeMouseDown(e, "bottom"),
1005
+ onMouseEnter: () => setIsHoveringHandle("bottom"),
1006
+ onMouseLeave: () => setIsHoveringHandle(null)
1007
+ }),
1008
+ /* @__PURE__ */ jsx2("div", {
1009
+ style: getResizeHandleStyle("left", {
1010
+ position: "absolute",
1011
+ top: -titleBarHeight,
1012
+ left: -handleSize,
1013
+ bottom: 0,
1014
+ width: handleSize,
1015
+ cursor: "w-resize"
1016
+ }),
1017
+ onMouseDown: (e) => handleResizeMouseDown(e, "left"),
1018
+ onMouseEnter: () => setIsHoveringHandle("left"),
1019
+ onMouseLeave: () => setIsHoveringHandle(null)
1020
+ }),
1021
+ /* @__PURE__ */ jsx2("div", {
1022
+ style: getResizeHandleStyle("right", {
1023
+ position: "absolute",
1024
+ top: -titleBarHeight,
1025
+ right: -handleSize,
1026
+ bottom: 0,
1027
+ width: handleSize,
1028
+ cursor: "e-resize"
1029
+ }),
1030
+ onMouseDown: (e) => handleResizeMouseDown(e, "right"),
1031
+ onMouseEnter: () => setIsHoveringHandle("right"),
1032
+ onMouseLeave: () => setIsHoveringHandle(null)
1033
+ }),
1034
+ /* @__PURE__ */ jsx2("div", {
1035
+ style: getResizeHandleStyle("top-left", {
1036
+ position: "absolute",
1037
+ top: -titleBarHeight - handleSize,
1038
+ left: -handleSize,
1039
+ width: handleSize,
1040
+ height: handleSize,
1041
+ cursor: "nw-resize"
1042
+ }),
1043
+ onMouseDown: (e) => handleResizeMouseDown(e, "top-left"),
1044
+ onMouseEnter: () => setIsHoveringHandle("top-left"),
1045
+ onMouseLeave: () => setIsHoveringHandle(null)
1046
+ }),
1047
+ /* @__PURE__ */ jsx2("div", {
1048
+ style: getResizeHandleStyle("top-right", {
1049
+ position: "absolute",
1050
+ top: -titleBarHeight - handleSize,
1051
+ right: -handleSize,
1052
+ width: handleSize,
1053
+ height: handleSize,
1054
+ cursor: "ne-resize"
1055
+ }),
1056
+ onMouseDown: (e) => handleResizeMouseDown(e, "top-right"),
1057
+ onMouseEnter: () => setIsHoveringHandle("top-right"),
1058
+ onMouseLeave: () => setIsHoveringHandle(null)
1059
+ }),
1060
+ /* @__PURE__ */ jsx2("div", {
1061
+ style: getResizeHandleStyle("bottom-left", {
1062
+ position: "absolute",
1063
+ bottom: -handleSize,
1064
+ left: -handleSize,
1065
+ width: handleSize,
1066
+ height: handleSize,
1067
+ cursor: "sw-resize"
1068
+ }),
1069
+ onMouseDown: (e) => handleResizeMouseDown(e, "bottom-left"),
1070
+ onMouseEnter: () => setIsHoveringHandle("bottom-left"),
1071
+ onMouseLeave: () => setIsHoveringHandle(null)
1072
+ }),
1073
+ /* @__PURE__ */ jsx2("div", {
1074
+ style: getResizeHandleStyle("bottom-right", {
1075
+ position: "absolute",
1076
+ bottom: -handleSize,
1077
+ right: -handleSize,
1078
+ width: handleSize,
1079
+ height: handleSize,
1080
+ cursor: "se-resize"
1081
+ }),
1082
+ onMouseDown: (e) => handleResizeMouseDown(e, "bottom-right"),
1083
+ onMouseEnter: () => setIsHoveringHandle("bottom-right"),
1084
+ onMouseLeave: () => setIsHoveringHandle(null)
1085
+ })
1086
+ ]
1087
+ })
1088
+ ]
1089
+ });
1090
+ }
1091
+
1092
+ // src/grid/types.ts
1093
+ class ViewportStream {
1094
+ currentViewport;
1095
+ constructor(initialViewport) {
1096
+ this.currentViewport = initialViewport;
1097
+ }
1098
+ subscribers = [];
1099
+ subscribe(callback) {
1100
+ this.subscribers.push(callback);
1101
+ return () => {
1102
+ const index = this.subscribers.indexOf(callback);
1103
+ if (index > -1) {
1104
+ this.subscribers.splice(index, 1);
1105
+ }
1106
+ };
1107
+ }
1108
+ next(nextViewport) {
1109
+ this.currentViewport = nextViewport;
1110
+ this.subscribers.forEach((callback) => callback(nextViewport));
1111
+ }
1112
+ }
1113
+
1114
+ // src/spreadsheet/snapping.ts
1115
+ var normalizeCellSnapAnchor = (anchor) => {
1116
+ const startCol = Math.max(0, Math.floor(anchor.startCol));
1117
+ const startRow = Math.max(0, Math.floor(anchor.startRow));
1118
+ return {
1119
+ startCol,
1120
+ startRow,
1121
+ endCol: Math.max(startCol + 1, Math.floor(anchor.endCol)),
1122
+ endRow: Math.max(startRow + 1, Math.floor(anchor.endRow))
1123
+ };
1124
+ };
1125
+ var getColumnWidthByIndex = (colIndex, columnWidths) => {
1126
+ const width = columnWidths.get(indexToColumn(colIndex));
1127
+ return typeof width === "number" && Number.isFinite(width) && width > 0 ? width : DEFAULT_CELL_WIDTH;
1128
+ };
1129
+ var getRowHeightByIndex = (rowIndex, rowHeights) => {
1130
+ const height = rowHeights.get(rowIndex + 1);
1131
+ return typeof height === "number" && Number.isFinite(height) && height > 0 ? height : DEFAULT_CELL_HEIGHT;
1132
+ };
1133
+ var getColumnEdgePosition = (edgeIndex, columnWidths) => {
1134
+ const safeEdgeIndex = Math.max(0, Math.floor(edgeIndex));
1135
+ let x = 0;
1136
+ for (let colIndex = 0;colIndex < safeEdgeIndex; colIndex++) {
1137
+ x += getColumnWidthByIndex(colIndex, columnWidths);
1138
+ }
1139
+ return x;
1140
+ };
1141
+ var getRowEdgePosition = (edgeIndex, rowHeights) => {
1142
+ const safeEdgeIndex = Math.max(0, Math.floor(edgeIndex));
1143
+ let y = 0;
1144
+ for (let rowIndex = 0;rowIndex < safeEdgeIndex; rowIndex++) {
1145
+ y += getRowHeightByIndex(rowIndex, rowHeights);
1146
+ }
1147
+ return y;
1148
+ };
1149
+ var getNearestColumnEdgeIndex = (x, columnWidths) => {
1150
+ const targetX = Math.max(0, x);
1151
+ let edgeIndex = 0;
1152
+ let currentX = 0;
1153
+ while (true) {
1154
+ const nextX = currentX + getColumnWidthByIndex(edgeIndex, columnWidths);
1155
+ if (targetX <= nextX) {
1156
+ return targetX - currentX <= nextX - targetX ? edgeIndex : edgeIndex + 1;
1157
+ }
1158
+ currentX = nextX;
1159
+ edgeIndex++;
1160
+ }
1161
+ };
1162
+ var getNearestRowEdgeIndex = (y, rowHeights) => {
1163
+ const targetY = Math.max(0, y);
1164
+ let edgeIndex = 0;
1165
+ let currentY = 0;
1166
+ while (true) {
1167
+ const nextY = currentY + getRowHeightByIndex(edgeIndex, rowHeights);
1168
+ if (targetY <= nextY) {
1169
+ return targetY - currentY <= nextY - targetY ? edgeIndex : edgeIndex + 1;
1170
+ }
1171
+ currentY = nextY;
1172
+ edgeIndex++;
1173
+ }
1174
+ };
1175
+ var getCellSnapAnchorFromRect = (rect, columnWidths, rowHeights) => {
1176
+ const startCol = getNearestColumnEdgeIndex(rect.x, columnWidths);
1177
+ const startRow = getNearestRowEdgeIndex(rect.y, rowHeights);
1178
+ const endCol = getNearestColumnEdgeIndex(rect.x + Math.max(0, rect.width), columnWidths);
1179
+ const endRow = getNearestRowEdgeIndex(rect.y + Math.max(0, rect.height), rowHeights);
1180
+ return normalizeCellSnapAnchor({
1181
+ startCol,
1182
+ startRow,
1183
+ endCol,
1184
+ endRow
1185
+ });
1186
+ };
1187
+ var getRectFromCellSnapAnchor = (anchor, columnWidths, rowHeights) => {
1188
+ const normalizedAnchor = normalizeCellSnapAnchor(anchor);
1189
+ const x = getColumnEdgePosition(normalizedAnchor.startCol, columnWidths);
1190
+ const y = getRowEdgePosition(normalizedAnchor.startRow, rowHeights);
1191
+ const right = getColumnEdgePosition(normalizedAnchor.endCol, columnWidths);
1192
+ const bottom = getRowEdgePosition(normalizedAnchor.endRow, rowHeights);
1193
+ return {
1194
+ x,
1195
+ y,
1196
+ width: right - x,
1197
+ height: bottom - y
1198
+ };
1199
+ };
1200
+ var moveCellSnapAnchorToNearestOrigin = (anchor, rect, columnWidths, rowHeights) => {
1201
+ const normalizedAnchor = normalizeCellSnapAnchor(anchor);
1202
+ const colSpan = normalizedAnchor.endCol - normalizedAnchor.startCol;
1203
+ const rowSpan = normalizedAnchor.endRow - normalizedAnchor.startRow;
1204
+ const startCol = getNearestColumnEdgeIndex(rect.x, columnWidths);
1205
+ const startRow = getNearestRowEdgeIndex(rect.y, rowHeights);
1206
+ return {
1207
+ startCol,
1208
+ startRow,
1209
+ endCol: startCol + colSpan,
1210
+ endRow: startRow + rowSpan
1211
+ };
1212
+ };
1213
+ var handleIncludesLeft = (handle) => handle === "left" || handle === "top-left" || handle === "bottom-left";
1214
+ var handleIncludesRight = (handle) => handle === "right" || handle === "top-right" || handle === "bottom-right";
1215
+ var handleIncludesTop = (handle) => handle === "top" || handle === "top-left" || handle === "top-right";
1216
+ var handleIncludesBottom = (handle) => handle === "bottom" || handle === "bottom-left" || handle === "bottom-right";
1217
+ var resizeCellSnapAnchorToNearestEdges = (anchor, rect, handle, columnWidths, rowHeights) => {
1218
+ const nextAnchor = normalizeCellSnapAnchor(anchor);
1219
+ if (handleIncludesLeft(handle)) {
1220
+ nextAnchor.startCol = getNearestColumnEdgeIndex(rect.x, columnWidths);
1221
+ }
1222
+ if (handleIncludesRight(handle)) {
1223
+ nextAnchor.endCol = getNearestColumnEdgeIndex(rect.x + Math.max(0, rect.width), columnWidths);
1224
+ }
1225
+ if (handleIncludesTop(handle)) {
1226
+ nextAnchor.startRow = getNearestRowEdgeIndex(rect.y, rowHeights);
1227
+ }
1228
+ if (handleIncludesBottom(handle)) {
1229
+ nextAnchor.endRow = getNearestRowEdgeIndex(rect.y + Math.max(0, rect.height), rowHeights);
1230
+ }
1231
+ if (nextAnchor.startCol >= nextAnchor.endCol) {
1232
+ if (handleIncludesLeft(handle) && !handleIncludesRight(handle)) {
1233
+ nextAnchor.startCol = Math.max(0, nextAnchor.endCol - 1);
1234
+ } else {
1235
+ nextAnchor.endCol = nextAnchor.startCol + 1;
1236
+ }
1237
+ }
1238
+ if (nextAnchor.startRow >= nextAnchor.endRow) {
1239
+ if (handleIncludesTop(handle) && !handleIncludesBottom(handle)) {
1240
+ nextAnchor.startRow = Math.max(0, nextAnchor.endRow - 1);
1241
+ } else {
1242
+ nextAnchor.endRow = nextAnchor.startRow + 1;
1243
+ }
1244
+ }
1245
+ return nextAnchor;
1246
+ };
1247
+
1248
+ // src/spreadsheet/spreadsheet.tsx
1249
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
1250
+ var resolveGridLineColor = (element, colorScheme) => {
1251
+ const fallback = colorScheme === "dark" ? "#4b5563" : "#e1e5e9";
1252
+ if (!element || typeof getComputedStyle !== "function") {
1253
+ return fallback;
1254
+ }
1255
+ const declared = getComputedStyle(element).getPropertyValue("--rsp-gridline").trim();
1256
+ if (!declared || declared.includes("light-dark(")) {
1257
+ return fallback;
1258
+ }
1259
+ return declared;
1260
+ };
1261
+ var hasBorderStyle = (style) => Boolean(style?.borderColor || style?.borderTopColor || style?.borderRightColor || style?.borderBottomColor || style?.borderLeftColor);
1262
+ var CellComponent = React2.memo(({
1263
+ value,
1264
+ row,
1265
+ col,
1266
+ selectionManager,
1267
+ cellToPixelBase,
1268
+ getColumnWidth,
1269
+ getRowHeight,
1270
+ customCellStyle,
1271
+ customCellRenderer,
1272
+ inputValue,
1273
+ editInputVersion,
1274
+ width,
1275
+ height
1276
+ }) => {
1277
+ const cellRef = useCallback3((el) => {
1278
+ if (el) {
1279
+ const rowIndex2 = row - 1;
1280
+ const colIndex2 = columnToIndex(col);
1281
+ return selectionManager.setupCellElement(el, {
1282
+ row: rowIndex2,
1283
+ col: colIndex2
1284
+ });
1285
+ }
1286
+ }, [row, col, selectionManager]);
1287
+ const colIndex = columnToIndex(col);
1288
+ const rowIndex = row - 1;
1289
+ const isSelected = useSelectionManager(selectionManager, () => selectionManager.isSelected({ row: rowIndex, col: colIndex }));
1290
+ const isBeingEdited = useSelectionManager(selectionManager, () => selectionManager.isEditingCell(rowIndex, colIndex));
1291
+ const { x, y } = cellToPixelBase(row, col);
1292
+ const cellStyleCallbackData = {
1293
+ id: `${col}${row}`,
1294
+ row: getRowNumber(row),
1295
+ col,
1296
+ rowIndex: row - 1,
1297
+ colIndex: columnToIndex(col),
1298
+ value,
1299
+ isSelected,
1300
+ isBeingEdited: isBeingEdited || false
1301
+ };
1302
+ const customStyle = customCellStyle?.(cellStyleCallbackData);
1303
+ const canHaveFillHandle = useSelectionManager(selectionManager, () => {
1304
+ return selectionManager.canCellHaveFillHandle({
1305
+ row: rowIndex,
1306
+ col: colIndex
1307
+ });
1308
+ });
1309
+ const inputRef = useCallback3((el) => {
1310
+ if (el) {
1311
+ const rowIndex2 = row - 1;
1312
+ const colIndex2 = columnToIndex(col);
1313
+ return selectionManager.setupInputElement(el, {
1314
+ rowIndex: rowIndex2,
1315
+ colIndex: colIndex2
1316
+ });
1317
+ }
1318
+ }, [row, col, selectionManager]);
1319
+ return /* @__PURE__ */ jsxs2("div", {
1320
+ ref: cellRef,
1321
+ className: cn("rsp-cell", {
1322
+ "rsp-cell-multiline": typeof value === "string" && value.includes(`
1323
+ `),
1324
+ "rsp-cell-selected": isSelected,
1325
+ "rsp-cell-editing": isBeingEdited,
1326
+ "rsp-cell-plain": !(hasBorderStyle(customStyle) && !customStyle?.backgroundColor)
1327
+ }),
1328
+ "data-testid": `spreadsheet-cell-${col}${row}`,
1329
+ style: {
1330
+ left: x,
1331
+ top: y,
1332
+ width: getColumnWidth(col) - 1,
1333
+ height: getRowHeight(row) - 1,
1334
+ ...customStyle
1335
+ },
1336
+ children: [
1337
+ isBeingEdited ? /* @__PURE__ */ jsx3("textarea", {
1338
+ className: "rsp-cell-editor",
1339
+ "data-testid": `spreadsheet-cell-input-${col}${row}`,
1340
+ ref: inputRef,
1341
+ defaultValue: inputValue?.toString() ?? "",
1342
+ onKeyDownCapture: (event) => {
1343
+ if (event.key === "Enter")
1344
+ event.preventDefault();
1345
+ },
1346
+ style: {
1347
+ left: x + 4,
1348
+ top: y + 4,
1349
+ width,
1350
+ height
1351
+ }
1352
+ }, editInputVersion) : customCellRenderer ? customCellRenderer(cellStyleCallbackData) : value,
1353
+ canHaveFillHandle && /* @__PURE__ */ jsx3("div", {
1354
+ className: "rsp-fill-handle",
1355
+ "data-fill-handle": true,
1356
+ "data-testid": `spreadsheet-fill-handle-${col}${row}`
1357
+ })
1358
+ ]
1359
+ });
1360
+ });
1361
+ CellComponent.displayName = "CellComponent";
1362
+ var HeaderComponent = React2.memo(({
1363
+ index,
1364
+ type,
1365
+ selectionManager,
1366
+ reverseHeaders,
1367
+ getColumnWidth,
1368
+ getRowHeight,
1369
+ handleColumnResizeStart,
1370
+ handleColumnResizeDoubleClick,
1371
+ handleRowResizeStart,
1372
+ handleRowResizeDoubleClick
1373
+ }) => {
1374
+ const headerRef = useCallback3((el) => {
1375
+ if (el) {
1376
+ return selectionManager.setupHeaderElement(el, index, type);
1377
+ }
1378
+ }, [index, type, selectionManager]);
1379
+ const { position, size } = React2.useMemo(() => {
1380
+ if (type === "col") {
1381
+ const col = indexToColumn(index);
1382
+ const width = getColumnWidth(col);
1383
+ const height = HEADER_HEIGHT;
1384
+ let x = 0;
1385
+ for (let i = 0;i < index; i++) {
1386
+ const colKey = indexToColumn(i);
1387
+ x += getColumnWidth(colKey);
1388
+ }
1389
+ return {
1390
+ position: { x, y: 0 },
1391
+ size: { width, height }
1392
+ };
1393
+ } else {
1394
+ const row = index + 1;
1395
+ const width = HEADER_WIDTH;
1396
+ const height = getRowHeight(row);
1397
+ let y = 0;
1398
+ for (let i = 1;i <= index; i++) {
1399
+ y += getRowHeight(i);
1400
+ }
1401
+ return {
1402
+ position: { x: 0, y },
1403
+ size: { width, height }
1404
+ };
1405
+ }
1406
+ }, [index, type, getColumnWidth, getRowHeight]);
1407
+ const isSelected = useSelectionManager(selectionManager, () => {
1408
+ if (type === "col") {
1409
+ return selectionManager.isWholeColSelected(index);
1410
+ } else {
1411
+ return selectionManager.isWholeRowSelected(index);
1412
+ }
1413
+ });
1414
+ const content = type === "col" ? reverseHeaders ? index + 1 : indexToColumn(index) : reverseHeaders ? rowToLetter(index + 1) : index + 1;
1415
+ return /* @__PURE__ */ jsxs2("div", {
1416
+ ref: headerRef,
1417
+ className: cn("rsp-header", `rsp-header-${type}`, {
1418
+ "rsp-header-selected": isSelected
1419
+ }),
1420
+ "data-testid": `spreadsheet-${type}-header-${type === "col" ? indexToColumn(index) : index + 1}`,
1421
+ style: {
1422
+ left: position.x,
1423
+ top: position.y,
1424
+ width: size.width - 1,
1425
+ height: size.height - 1
1426
+ },
1427
+ children: [
1428
+ content,
1429
+ type === "col" && /* @__PURE__ */ jsx3("div", {
1430
+ className: "rsp-col-resize-handle",
1431
+ "data-testid": `spreadsheet-column-resize-handle-${indexToColumn(index)}`,
1432
+ onMouseDown: (e) => handleColumnResizeStart(e, indexToColumn(index)),
1433
+ onDoubleClick: (e) => handleColumnResizeDoubleClick(e, indexToColumn(index))
1434
+ }),
1435
+ type === "row" && /* @__PURE__ */ jsx3("div", {
1436
+ className: "rsp-row-resize-handle",
1437
+ "data-testid": `spreadsheet-row-resize-handle-${index + 1}`,
1438
+ onMouseDown: (e) => handleRowResizeStart(e, index + 1),
1439
+ onDoubleClick: (e) => handleRowResizeDoubleClick(e, index + 1)
1440
+ })
1441
+ ]
1442
+ });
1443
+ });
1444
+ HeaderComponent.displayName = "HeaderComponent";
1445
+ var isEditableKeyboardTarget = (target) => {
1446
+ if (!(target instanceof HTMLElement)) {
1447
+ return false;
1448
+ }
1449
+ return Boolean(target.closest('input, textarea, select, [contenteditable="true"]'));
1450
+ };
1451
+ var columnWidthsRecordToMap = (columnWidths) => {
1452
+ return new Map(Object.entries(columnWidths || {}).filter((entry) => {
1453
+ const width = entry[1];
1454
+ return typeof width === "number" && Number.isFinite(width) && width > 0;
1455
+ }));
1456
+ };
1457
+ var columnWidthsMapToRecord = (columnWidths) => {
1458
+ const result = {};
1459
+ columnWidths.forEach((width, column) => {
1460
+ if (Number.isFinite(width) && width > 0) {
1461
+ result[column] = width;
1462
+ }
1463
+ });
1464
+ return result;
1465
+ };
1466
+ var rowHeightsRecordToMap = (rowHeights) => {
1467
+ const entries = Object.entries(rowHeights || {}).flatMap(([row, height]) => {
1468
+ const rowNumber = Number(row);
1469
+ if (Number.isInteger(rowNumber) && rowNumber > 0 && Number.isFinite(height) && height > 0) {
1470
+ return [[rowNumber, height]];
1471
+ }
1472
+ return [];
1473
+ });
1474
+ return new Map(entries);
1475
+ };
1476
+ var rowHeightsMapToRecord = (rowHeights) => {
1477
+ const result = {};
1478
+ rowHeights.forEach((height, row) => {
1479
+ if (Number.isInteger(row) && row > 0 && Number.isFinite(height) && height > 0) {
1480
+ result[row] = height;
1481
+ }
1482
+ });
1483
+ return result;
1484
+ };
1485
+ var isSnappedTextBox = (child) => child.component === "RichText" && Boolean(child.snapToCells);
1486
+ var areCellSnapAnchorsEqual = (a, b) => a.startCol === b.startCol && a.startRow === b.startRow && a.endCol === b.endCol && a.endRow === b.endRow;
1487
+ var applyCellSnapAnchorToTextBox = (child, anchor, columnWidths, rowHeights) => {
1488
+ const normalizedAnchor = normalizeCellSnapAnchor(anchor);
1489
+ const rect = getRectFromCellSnapAnchor(normalizedAnchor, columnWidths, rowHeights);
1490
+ return {
1491
+ ...child,
1492
+ snapToCells: true,
1493
+ cellAnchor: normalizedAnchor,
1494
+ x: rect.x,
1495
+ y: rect.y,
1496
+ width: rect.width,
1497
+ height: rect.height
1498
+ };
1499
+ };
1500
+ var syncSnappedTextBoxRects = (children, columnWidths, rowHeights) => {
1501
+ let changed = false;
1502
+ const nextChildren = children.map((child) => {
1503
+ if (!isSnappedTextBox(child)) {
1504
+ return child;
1505
+ }
1506
+ const fallbackAnchor = getCellSnapAnchorFromRect(child, columnWidths, rowHeights);
1507
+ const anchor = child.cellAnchor ? normalizeCellSnapAnchor(child.cellAnchor) : fallbackAnchor;
1508
+ const rect = getRectFromCellSnapAnchor(anchor, columnWidths, rowHeights);
1509
+ const currentAnchor = child.cellAnchor ? normalizeCellSnapAnchor(child.cellAnchor) : undefined;
1510
+ const anchorChanged = !currentAnchor || !areCellSnapAnchorsEqual(currentAnchor, anchor);
1511
+ const rectChanged = child.x !== rect.x || child.y !== rect.y || child.width !== rect.width || child.height !== rect.height;
1512
+ if (!anchorChanged && !rectChanged) {
1513
+ return child;
1514
+ }
1515
+ changed = true;
1516
+ return {
1517
+ ...child,
1518
+ cellAnchor: anchor,
1519
+ x: rect.x,
1520
+ y: rect.y,
1521
+ width: rect.width,
1522
+ height: rect.height
1523
+ };
1524
+ });
1525
+ return changed ? nextChildren : children;
1526
+ };
1527
+ var isCellSnapResizeHandle = (handle) => {
1528
+ return handle === "top" || handle === "right" || handle === "bottom" || handle === "left" || handle === "top-left" || handle === "top-right" || handle === "bottom-left" || handle === "bottom-right";
1529
+ };
1530
+
1531
+ class SpreadsheetClipboardManager extends ClipboardUtils {
1532
+ cellDataRef;
1533
+ selectionManager;
1534
+ constructor(cellDataRef, selectionManager) {
1535
+ super();
1536
+ this.cellDataRef = cellDataRef;
1537
+ this.selectionManager = selectionManager;
1538
+ }
1539
+ triggerCopy() {
1540
+ const extractedCells = this.extractCellsFromSelection(this.selectionManager, this.cellDataRef.current);
1541
+ if (!extractedCells)
1542
+ return;
1543
+ const { width, height, cells } = extractedCells;
1544
+ const exportGrid = this.createExportGrid(width, height);
1545
+ cells.forEach(({ relative, value }) => {
1546
+ const row = exportGrid[relative.rowIndex];
1547
+ if (!row)
1548
+ return;
1549
+ row[relative.columnIndex] = value === undefined ? "" : String(value);
1550
+ });
1551
+ this.writeToOsClipboard(exportGrid);
1552
+ }
1553
+ triggerPaste(updates) {
1554
+ this.selectionManager.saveCellValues(updates);
1555
+ }
1556
+ }
1557
+ var Spreadsheet = forwardRef(({
1558
+ style,
1559
+ className,
1560
+ containerProps,
1561
+ cellData: controlledCellData,
1562
+ onCellDataChange,
1563
+ onCellDataChangeError,
1564
+ columnWidths: controlledColumnWidths,
1565
+ onColumnWidthsChange,
1566
+ rowHeights: controlledRowHeights,
1567
+ onRowHeightsChange,
1568
+ customCellStyle,
1569
+ customCellRenderer,
1570
+ parseValue,
1571
+ selection,
1572
+ components,
1573
+ overlayChildren: controlledOverlayChildren,
1574
+ selectedOverlayId: controlledSelectedOverlayId,
1575
+ onOverlaySelect,
1576
+ onOverlayChildrenChange,
1577
+ overlayPlaceholder,
1578
+ parentSelected = true,
1579
+ disableClipboard = false
1580
+ }, ref) => {
1581
+ const [{ width, height }, containerRef] = useResizeObserver();
1582
+ const [containerDivRef, setContainerDivRef] = useState4(null);
1583
+ const colorScheme = useColorScheme(containerDivRef);
1584
+ const selectionManager = useInitializeSelectionManager({
1585
+ getNumCols: () => ({ type: "infinity" }),
1586
+ getNumRows: () => ({ type: "infinity" }),
1587
+ initialState: selection?.initialState,
1588
+ state: selection?.state,
1589
+ onStateChange: selection?.onStateChange,
1590
+ containerElement: containerDivRef,
1591
+ formats: selection?.formats,
1592
+ disableAutoClipboard: true
1593
+ });
1594
+ const effects = selection?.effects;
1595
+ useEffect3(() => {
1596
+ if (effects) {
1597
+ return effects(selectionManager);
1598
+ }
1599
+ }, [selectionManager, effects]);
1600
+ const [internalCellData, setInternalCellData] = useState4(new Map);
1601
+ const [retryingEdit, setRetryingEdit] = useState4(null);
1602
+ const retryingEditVersionRef = useRef3(0);
1603
+ const cellData = controlledCellData ?? internalCellData;
1604
+ const cellDataRef = useRef3(cellData);
1605
+ cellDataRef.current = cellData;
1606
+ const isControlled = controlledCellData !== undefined;
1607
+ const prevIsControlledRef = useRef3(isControlled);
1608
+ selectionManager.getCellsWithData = (area) => {
1609
+ const cells = [];
1610
+ cellData.forEach((value, key) => {
1611
+ const { rowIndex, columnIndex } = parseCellReference(key);
1612
+ if (columnIndex >= area.start.col && (area.end.col.type === "infinity" || columnIndex <= area.end.col.value) && rowIndex >= area.start.row && (area.end.row.type === "infinity" || rowIndex <= area.end.row.value)) {
1613
+ cells.push({ rowIndex, colIndex: columnIndex });
1614
+ }
1615
+ });
1616
+ return cells;
1617
+ };
1618
+ React2.useEffect(() => {
1619
+ if (isControlled !== prevIsControlledRef.current) {
1620
+ prevIsControlledRef.current = isControlled;
1621
+ console.warn(`The component went from ${prevIsControlledRef.current ? "controlled" : "uncontrolled"} to ${isControlled ? "controlled" : "uncontrolled"}, this is not supported`);
1622
+ }
1623
+ }, [controlledCellData, isControlled]);
1624
+ const [viewport, setViewport] = useState4({
1625
+ scrollX: 0,
1626
+ scrollY: 0,
1627
+ zoom: 1
1628
+ });
1629
+ const controlledColumnWidthsMap = useMemo(() => controlledColumnWidths ? columnWidthsRecordToMap(controlledColumnWidths) : undefined, [controlledColumnWidths]);
1630
+ const [internalColumnWidths, setInternalColumnWidths] = useState4(() => columnWidthsRecordToMap(controlledColumnWidths));
1631
+ const [transientColumnWidths, setTransientColumnWidths] = useState4(null);
1632
+ const columnWidths = transientColumnWidths ?? controlledColumnWidthsMap ?? internalColumnWidths;
1633
+ const columnWidthsRef = useRef3(columnWidths);
1634
+ columnWidthsRef.current = columnWidths;
1635
+ const previousControlledColumnWidthsRef = useRef3(controlledColumnWidths);
1636
+ useEffect3(() => {
1637
+ if (controlledColumnWidths !== previousControlledColumnWidthsRef.current) {
1638
+ previousControlledColumnWidthsRef.current = controlledColumnWidths;
1639
+ setTransientColumnWidths(null);
1640
+ }
1641
+ }, [controlledColumnWidths]);
1642
+ const controlledRowHeightsMap = useMemo(() => controlledRowHeights ? rowHeightsRecordToMap(controlledRowHeights) : undefined, [controlledRowHeights]);
1643
+ const [internalRowHeights, setInternalRowHeights] = useState4(() => rowHeightsRecordToMap(controlledRowHeights));
1644
+ const [transientRowHeights, setTransientRowHeights] = useState4(null);
1645
+ const rowHeights = transientRowHeights ?? controlledRowHeightsMap ?? internalRowHeights;
1646
+ const rowHeightsRef = useRef3(rowHeights);
1647
+ rowHeightsRef.current = rowHeights;
1648
+ const previousControlledRowHeightsRef = useRef3(controlledRowHeights);
1649
+ useEffect3(() => {
1650
+ if (controlledRowHeights !== previousControlledRowHeightsRef.current) {
1651
+ previousControlledRowHeightsRef.current = controlledRowHeights;
1652
+ setTransientRowHeights(null);
1653
+ }
1654
+ }, [controlledRowHeights]);
1655
+ const [isResizingColumn, setIsResizingColumn] = useState4(null);
1656
+ const [isResizingRow, setIsResizingRow] = useState4(null);
1657
+ const [resizeStartX, setResizeStartX] = useState4(0);
1658
+ const [resizeStartY, setResizeStartY] = useState4(0);
1659
+ const [resizeStartSize, setResizeStartSize] = useState4(0);
1660
+ const [internalOverlayChildren, setInternalOverlayChildren] = useState4([]);
1661
+ const [transientOverlayChildren, setTransientOverlayChildren] = useState4(null);
1662
+ const [internalSelectedOverlayId, setInternalSelectedOverlayId] = useState4(null);
1663
+ const [mountedPersistentOverlayIds, setMountedPersistentOverlayIds] = useState4(new Set);
1664
+ const [placeholderPos, setPlaceholderPos] = useState4(null);
1665
+ const overlayChildren = transientOverlayChildren ?? controlledOverlayChildren ?? internalOverlayChildren;
1666
+ const selectedOverlayId = controlledSelectedOverlayId !== undefined ? controlledSelectedOverlayId : internalSelectedOverlayId;
1667
+ const previousControlledOverlayChildrenRef = useRef3(controlledOverlayChildren);
1668
+ useEffect3(() => {
1669
+ if (controlledOverlayChildren !== previousControlledOverlayChildrenRef.current) {
1670
+ previousControlledOverlayChildrenRef.current = controlledOverlayChildren;
1671
+ setTransientOverlayChildren(null);
1672
+ }
1673
+ }, [controlledOverlayChildren]);
1674
+ const overlayChildrenRef = useRef3(overlayChildren);
1675
+ overlayChildrenRef.current = overlayChildren;
1676
+ const overlayInteractionDraftRectsRef = useRef3({});
1677
+ const pendingSnappedTextBoxLayoutCommitRef = useRef3(false);
1678
+ const canvasRef = useRef3(null);
1679
+ const overlayContainerRef = useRef3(null);
1680
+ const viewportStreamRef = useRef3(new ViewportStream({
1681
+ x: viewport.scrollX,
1682
+ y: viewport.scrollY,
1683
+ zoom: viewport.zoom
1684
+ }));
1685
+ React2.useEffect(() => {
1686
+ viewportStreamRef.current.next({
1687
+ x: viewport.scrollX,
1688
+ y: viewport.scrollY,
1689
+ zoom: viewport.zoom
1690
+ });
1691
+ }, [viewport.scrollX, viewport.scrollY, viewport.zoom]);
1692
+ const gridDims = useMemo(() => ({
1693
+ cellWidth: DEFAULT_CELL_WIDTH * viewport.zoom,
1694
+ cellHeight: DEFAULT_CELL_HEIGHT * viewport.zoom,
1695
+ headerWidth: HEADER_WIDTH * viewport.zoom,
1696
+ headerHeight: HEADER_HEIGHT * viewport.zoom
1697
+ }), [viewport.zoom]);
1698
+ React2.useEffect(() => {
1699
+ if (!overlayPlaceholder || !containerDivRef)
1700
+ return;
1701
+ const handleMouseMove = (e) => {
1702
+ const rect = containerDivRef.getBoundingClientRect();
1703
+ const mouseX = e.clientX - rect.left;
1704
+ const mouseY = e.clientY - rect.top;
1705
+ if (mouseX >= gridDims.headerWidth && mouseY >= gridDims.headerHeight) {
1706
+ const sheetX = (mouseX - gridDims.headerWidth + viewport.scrollX) / viewport.zoom - overlayPlaceholder.width / 2;
1707
+ const sheetY = (mouseY - gridDims.headerHeight + viewport.scrollY) / viewport.zoom - overlayPlaceholder.height / 2;
1708
+ const clampedX = Math.max(0, sheetX);
1709
+ const clampedY = Math.max(0, sheetY);
1710
+ setPlaceholderPos({ x: clampedX, y: clampedY });
1711
+ } else {
1712
+ setPlaceholderPos(null);
1713
+ }
1714
+ };
1715
+ const handleClick = (e) => {
1716
+ if (placeholderPos) {
1717
+ e.stopPropagation();
1718
+ const finalX = Math.max(0, placeholderPos.x);
1719
+ const finalY = Math.max(0, placeholderPos.y);
1720
+ overlayPlaceholder.onPlaced(finalX, finalY);
1721
+ setPlaceholderPos(null);
1722
+ }
1723
+ };
1724
+ const handleKeyDown = (e) => {
1725
+ if (e.key === "Escape") {
1726
+ overlayPlaceholder.onCancel?.();
1727
+ setPlaceholderPos(null);
1728
+ }
1729
+ };
1730
+ const handleContextMenu = (e) => {
1731
+ e.preventDefault();
1732
+ overlayPlaceholder.onCancel?.();
1733
+ setPlaceholderPos(null);
1734
+ };
1735
+ containerDivRef.addEventListener("mousemove", handleMouseMove);
1736
+ containerDivRef.addEventListener("click", handleClick);
1737
+ window.addEventListener("keydown", handleKeyDown);
1738
+ window.addEventListener("contextmenu", handleContextMenu);
1739
+ return () => {
1740
+ containerDivRef.removeEventListener("mousemove", handleMouseMove);
1741
+ containerDivRef.removeEventListener("click", handleClick);
1742
+ window.removeEventListener("keydown", handleKeyDown);
1743
+ window.removeEventListener("contextmenu", handleContextMenu);
1744
+ };
1745
+ }, [overlayPlaceholder, containerDivRef, viewport, gridDims, placeholderPos]);
1746
+ const effectsRefOb = {
1747
+ onCellDataChange,
1748
+ onCellDataChangeError,
1749
+ controlledCellData,
1750
+ parseValue,
1751
+ cellData,
1752
+ viewport,
1753
+ gridDims,
1754
+ height
1755
+ };
1756
+ const effectsRef = useRef3(effectsRefOb);
1757
+ effectsRef.current = effectsRefOb;
1758
+ const setupCanvas = useCallback3((canvas, width2, height2) => {
1759
+ const ctx = canvas.getContext("2d");
1760
+ if (!ctx)
1761
+ return null;
1762
+ const devicePixelRatio = window.devicePixelRatio || 1;
1763
+ canvas.width = width2 * devicePixelRatio;
1764
+ canvas.height = height2 * devicePixelRatio;
1765
+ canvas.style.width = width2 + "px";
1766
+ canvas.style.height = height2 + "px";
1767
+ ctx.scale(devicePixelRatio, devicePixelRatio);
1768
+ return ctx;
1769
+ }, []);
1770
+ const drawGrid = useCallback3(() => {
1771
+ if (!canvasRef.current || !width || !height)
1772
+ return;
1773
+ const ctx = setupCanvas(canvasRef.current, width, height);
1774
+ if (!ctx)
1775
+ return;
1776
+ ctx.clearRect(0, 0, width, height);
1777
+ ctx.strokeStyle = resolveGridLineColor(canvasRef.current, colorScheme);
1778
+ ctx.lineWidth = 1;
1779
+ let x = gridDims.headerWidth;
1780
+ let colIndex = 0;
1781
+ while (x < viewport.scrollX + gridDims.headerWidth) {
1782
+ const colWidth = (columnWidths.get(indexToColumn(colIndex)) || DEFAULT_CELL_WIDTH) * viewport.zoom;
1783
+ if (x + colWidth > viewport.scrollX + gridDims.headerWidth) {
1784
+ break;
1785
+ }
1786
+ x += colWidth;
1787
+ colIndex++;
1788
+ }
1789
+ while (x - viewport.scrollX < width) {
1790
+ const drawX = x - viewport.scrollX;
1791
+ if (drawX >= gridDims.headerWidth && drawX <= width) {
1792
+ ctx.beginPath();
1793
+ ctx.moveTo(drawX, gridDims.headerHeight);
1794
+ ctx.lineTo(drawX, height);
1795
+ ctx.stroke();
1796
+ }
1797
+ const colWidth = (columnWidths.get(indexToColumn(colIndex)) || DEFAULT_CELL_WIDTH) * viewport.zoom;
1798
+ x += colWidth;
1799
+ colIndex++;
1800
+ }
1801
+ let y = gridDims.headerHeight;
1802
+ let rowNum = 1;
1803
+ while (y < viewport.scrollY + gridDims.headerHeight) {
1804
+ const rowHeight = (rowHeights.get(rowNum) || DEFAULT_CELL_HEIGHT) * viewport.zoom;
1805
+ if (y + rowHeight > viewport.scrollY + gridDims.headerHeight) {
1806
+ break;
1807
+ }
1808
+ y += rowHeight;
1809
+ rowNum++;
1810
+ }
1811
+ while (y - viewport.scrollY < height) {
1812
+ const drawY = y - viewport.scrollY;
1813
+ if (drawY >= gridDims.headerHeight && drawY <= height) {
1814
+ ctx.beginPath();
1815
+ ctx.moveTo(gridDims.headerWidth, drawY);
1816
+ ctx.lineTo(width, drawY);
1817
+ ctx.stroke();
1818
+ }
1819
+ const rowHeight = (rowHeights.get(rowNum) || DEFAULT_CELL_HEIGHT) * viewport.zoom;
1820
+ y += rowHeight;
1821
+ rowNum++;
1822
+ }
1823
+ }, [
1824
+ columnWidths,
1825
+ gridDims.headerHeight,
1826
+ gridDims.headerWidth,
1827
+ height,
1828
+ rowHeights,
1829
+ setupCanvas,
1830
+ viewport.scrollX,
1831
+ viewport.scrollY,
1832
+ viewport.zoom,
1833
+ width,
1834
+ colorScheme
1835
+ ]);
1836
+ const getVisibleCells = useCallback3(() => {
1837
+ if (!width || !height)
1838
+ return [];
1839
+ const cells = [];
1840
+ let x = 0;
1841
+ let startCol = 0;
1842
+ while (x < viewport.scrollX) {
1843
+ const colWidth = (columnWidths.get(indexToColumn(startCol)) || DEFAULT_CELL_WIDTH) * viewport.zoom;
1844
+ if (x + colWidth > viewport.scrollX) {
1845
+ break;
1846
+ }
1847
+ x += colWidth;
1848
+ startCol++;
1849
+ }
1850
+ let y = 0;
1851
+ let startRow = 0;
1852
+ while (y < viewport.scrollY) {
1853
+ const rowHeight = (rowHeights.get(startRow + 1) || DEFAULT_CELL_HEIGHT) * viewport.zoom;
1854
+ if (y + rowHeight > viewport.scrollY) {
1855
+ break;
1856
+ }
1857
+ y += rowHeight;
1858
+ startRow++;
1859
+ }
1860
+ let rowY = y;
1861
+ let row = startRow;
1862
+ while (rowY < viewport.scrollY + height) {
1863
+ let colX = x;
1864
+ let col = startCol;
1865
+ while (colX < viewport.scrollX + width) {
1866
+ const colKey = indexToColumn(col);
1867
+ const rowNum = getRowNumber(row);
1868
+ const cellKey = `${colKey}${rowNum}`;
1869
+ cells.push({
1870
+ value: cellData.get(cellKey) ?? "",
1871
+ row: rowNum,
1872
+ col: colKey
1873
+ });
1874
+ const colWidth = (columnWidths.get(colKey) || DEFAULT_CELL_WIDTH) * viewport.zoom;
1875
+ colX += colWidth;
1876
+ col++;
1877
+ }
1878
+ const rowHeight = (rowHeights.get(row + 1) || DEFAULT_CELL_HEIGHT) * viewport.zoom;
1879
+ rowY += rowHeight;
1880
+ row++;
1881
+ }
1882
+ return cells;
1883
+ }, [
1884
+ cellData,
1885
+ columnWidths,
1886
+ height,
1887
+ rowHeights,
1888
+ viewport.scrollX,
1889
+ viewport.scrollY,
1890
+ viewport.zoom,
1891
+ width
1892
+ ]);
1893
+ const getColumnWidth = useCallback3((col) => {
1894
+ return columnWidths.get(col) || DEFAULT_CELL_WIDTH;
1895
+ }, [columnWidths]);
1896
+ const getRowHeight = useCallback3((row) => {
1897
+ return rowHeights.get(row) || DEFAULT_CELL_HEIGHT;
1898
+ }, [rowHeights]);
1899
+ const cellToPixelBase = useCallback3((row, col) => {
1900
+ const colIndex = columnToIndex(col);
1901
+ const rowIndex = row - 1;
1902
+ let x = HEADER_WIDTH;
1903
+ for (let i = 0;i < colIndex; i++) {
1904
+ const colKey = indexToColumn(i);
1905
+ x += columnWidths.get(colKey) || DEFAULT_CELL_WIDTH;
1906
+ }
1907
+ let y = HEADER_HEIGHT;
1908
+ for (let i = 1;i <= rowIndex; i++) {
1909
+ y += rowHeights.get(i) || DEFAULT_CELL_HEIGHT;
1910
+ }
1911
+ return { x, y };
1912
+ }, [columnWidths, rowHeights]);
1913
+ React2.useEffect(() => {
1914
+ return selectionManager.listenToUpdateData((updates) => {
1915
+ const editingState = selectionManager.isEditing;
1916
+ try {
1917
+ const { cellData: cellData2, onCellDataChange: onCellDataChange2 } = effectsRef.current;
1918
+ const newData = new Map(cellData2);
1919
+ updates.forEach(({ value, rowIndex, colIndex }) => {
1920
+ const key = `${indexToColumn(colIndex)}${rowIndex + 1}`;
1921
+ const parseValue2 = effectsRef.current.parseValue;
1922
+ if (value === "") {
1923
+ newData.delete(key);
1924
+ } else {
1925
+ let parsedValue = value;
1926
+ if (parseValue2 && typeof value === "string") {
1927
+ const match = key.match(/^([A-Z]+)(\d+)$/);
1928
+ const col = match?.[1];
1929
+ const rowText = match?.[2];
1930
+ if (col && rowText) {
1931
+ const row = parseInt(rowText, 10);
1932
+ const colIndex2 = columnToIndex(col);
1933
+ const rowIndex2 = row - 1;
1934
+ parsedValue = parseValue2(value, {
1935
+ id: key,
1936
+ row,
1937
+ col,
1938
+ rowIndex: rowIndex2,
1939
+ colIndex: colIndex2
1940
+ });
1941
+ }
1942
+ }
1943
+ newData.set(key, parsedValue);
1944
+ }
1945
+ });
1946
+ onCellDataChange2?.(newData);
1947
+ setInternalCellData(newData);
1948
+ } catch (error) {
1949
+ console.error("Failed to update spreadsheet cell data:", error);
1950
+ effectsRef.current.onCellDataChangeError?.(error);
1951
+ const rejectedUpdate = updates.length === 1 ? updates[0] : undefined;
1952
+ if (editingState.type === "cell" && rejectedUpdate && rejectedUpdate.rowIndex === editingState.row && rejectedUpdate.colIndex === editingState.col) {
1953
+ const rejectedValue = rejectedUpdate.value === undefined ? "" : String(rejectedUpdate.value);
1954
+ queueMicrotask(() => {
1955
+ retryingEditVersionRef.current += 1;
1956
+ setRetryingEdit({
1957
+ rowIndex: editingState.row,
1958
+ colIndex: editingState.col,
1959
+ version: retryingEditVersionRef.current
1960
+ });
1961
+ selectionManager.editCell(editingState.row, editingState.col, rejectedValue);
1962
+ });
1963
+ }
1964
+ }
1965
+ });
1966
+ }, [selectionManager]);
1967
+ const clipboardManager = useMemo(() => {
1968
+ return new SpreadsheetClipboardManager(cellDataRef, selectionManager);
1969
+ }, [selectionManager]);
1970
+ React2.useEffect(() => {
1971
+ if (disableClipboard)
1972
+ return;
1973
+ selectionManager.listenToCopy(() => {
1974
+ clipboardManager.triggerCopy();
1975
+ });
1976
+ }, [selectionManager, clipboardManager, disableClipboard]);
1977
+ React2.useEffect(() => {
1978
+ if (disableClipboard)
1979
+ return;
1980
+ selectionManager.listenToPaste(({ updates }) => {
1981
+ clipboardManager.triggerPaste(updates);
1982
+ });
1983
+ }, [selectionManager, clipboardManager, disableClipboard]);
1984
+ const handleWheel = useCallback3((e) => {
1985
+ e.preventDefault();
1986
+ e.stopPropagation();
1987
+ setViewport((prev) => ({
1988
+ ...prev,
1989
+ scrollX: Math.max(0, prev.scrollX + e.deltaX),
1990
+ scrollY: Math.max(0, prev.scrollY + e.deltaY)
1991
+ }));
1992
+ }, []);
1993
+ const commitOverlayChildren = useCallback3((children) => {
1994
+ overlayChildrenRef.current = children;
1995
+ if (onOverlayChildrenChange) {
1996
+ onOverlayChildrenChange(children);
1997
+ setTransientOverlayChildren(children);
1998
+ return;
1999
+ }
2000
+ setInternalOverlayChildren(children);
2001
+ setTransientOverlayChildren(null);
2002
+ }, [onOverlayChildrenChange]);
2003
+ const updateOverlayChildrenLocally = useCallback3((updater) => {
2004
+ const nextChildren = updater(overlayChildrenRef.current);
2005
+ overlayChildrenRef.current = nextChildren;
2006
+ setTransientOverlayChildren(nextChildren);
2007
+ if (!onOverlayChildrenChange) {
2008
+ setInternalOverlayChildren(nextChildren);
2009
+ }
2010
+ }, [onOverlayChildrenChange]);
2011
+ const updateSnappedTextBoxRectsLocally = useCallback3((nextColumnWidths, nextRowHeights) => {
2012
+ const nextChildren = syncSnappedTextBoxRects(overlayChildrenRef.current, nextColumnWidths, nextRowHeights);
2013
+ if (nextChildren === overlayChildrenRef.current) {
2014
+ return;
2015
+ }
2016
+ overlayChildrenRef.current = nextChildren;
2017
+ pendingSnappedTextBoxLayoutCommitRef.current = true;
2018
+ setTransientOverlayChildren(nextChildren);
2019
+ if (!onOverlayChildrenChange) {
2020
+ setInternalOverlayChildren(nextChildren);
2021
+ }
2022
+ }, [onOverlayChildrenChange]);
2023
+ const commitSnappedTextBoxRects = useCallback3((nextColumnWidths, nextRowHeights) => {
2024
+ const nextChildren = syncSnappedTextBoxRects(overlayChildrenRef.current, nextColumnWidths, nextRowHeights);
2025
+ if (nextChildren !== overlayChildrenRef.current) {
2026
+ commitOverlayChildren(nextChildren);
2027
+ pendingSnappedTextBoxLayoutCommitRef.current = false;
2028
+ return;
2029
+ }
2030
+ if (pendingSnappedTextBoxLayoutCommitRef.current) {
2031
+ commitOverlayChildren(overlayChildrenRef.current);
2032
+ pendingSnappedTextBoxLayoutCommitRef.current = false;
2033
+ }
2034
+ }, [commitOverlayChildren]);
2035
+ const updateColumnWidthsLocally = useCallback3((updater) => {
2036
+ const nextColumnWidths = updater(columnWidthsRef.current);
2037
+ columnWidthsRef.current = nextColumnWidths;
2038
+ setTransientColumnWidths(nextColumnWidths);
2039
+ updateSnappedTextBoxRectsLocally(nextColumnWidths, rowHeightsRef.current);
2040
+ if (!onColumnWidthsChange) {
2041
+ setInternalColumnWidths(nextColumnWidths);
2042
+ }
2043
+ }, [onColumnWidthsChange, updateSnappedTextBoxRectsLocally]);
2044
+ const commitColumnWidths = useCallback3((nextColumnWidths) => {
2045
+ columnWidthsRef.current = nextColumnWidths;
2046
+ if (onColumnWidthsChange) {
2047
+ onColumnWidthsChange(columnWidthsMapToRecord(nextColumnWidths));
2048
+ setTransientColumnWidths(nextColumnWidths);
2049
+ commitSnappedTextBoxRects(nextColumnWidths, rowHeightsRef.current);
2050
+ return;
2051
+ }
2052
+ setInternalColumnWidths(nextColumnWidths);
2053
+ setTransientColumnWidths(null);
2054
+ commitSnappedTextBoxRects(nextColumnWidths, rowHeightsRef.current);
2055
+ }, [commitSnappedTextBoxRects, onColumnWidthsChange]);
2056
+ const updateRowHeightsLocally = useCallback3((updater) => {
2057
+ const nextRowHeights = updater(rowHeightsRef.current);
2058
+ rowHeightsRef.current = nextRowHeights;
2059
+ setTransientRowHeights(nextRowHeights);
2060
+ updateSnappedTextBoxRectsLocally(columnWidthsRef.current, nextRowHeights);
2061
+ if (!onRowHeightsChange) {
2062
+ setInternalRowHeights(nextRowHeights);
2063
+ }
2064
+ }, [onRowHeightsChange, updateSnappedTextBoxRectsLocally]);
2065
+ const commitRowHeights = useCallback3((nextRowHeights) => {
2066
+ rowHeightsRef.current = nextRowHeights;
2067
+ if (onRowHeightsChange) {
2068
+ onRowHeightsChange(rowHeightsMapToRecord(nextRowHeights));
2069
+ setTransientRowHeights(nextRowHeights);
2070
+ commitSnappedTextBoxRects(columnWidthsRef.current, nextRowHeights);
2071
+ return;
2072
+ }
2073
+ setInternalRowHeights(nextRowHeights);
2074
+ setTransientRowHeights(null);
2075
+ commitSnappedTextBoxRects(columnWidthsRef.current, nextRowHeights);
2076
+ }, [commitSnappedTextBoxRects, onRowHeightsChange]);
2077
+ const handleColumnResizeStart = useCallback3((e, col) => {
2078
+ e.preventDefault();
2079
+ e.stopPropagation();
2080
+ setIsResizingColumn(col);
2081
+ setResizeStartX(e.clientX);
2082
+ setResizeStartSize(getColumnWidth(col));
2083
+ }, [getColumnWidth]);
2084
+ const handleRowResizeStart = useCallback3((e, row) => {
2085
+ e.preventDefault();
2086
+ e.stopPropagation();
2087
+ setIsResizingRow(row);
2088
+ setResizeStartY(e.clientY);
2089
+ setResizeStartSize(getRowHeight(row));
2090
+ }, [getRowHeight]);
2091
+ const handleResizeMove = useCallback3((e) => {
2092
+ if (isResizingColumn) {
2093
+ const delta = e.clientX - resizeStartX;
2094
+ const newWidth = Math.max(20, resizeStartSize + delta / viewport.zoom);
2095
+ updateColumnWidthsLocally((prev) => new Map(prev).set(isResizingColumn, newWidth));
2096
+ } else if (isResizingRow !== null) {
2097
+ const delta = e.clientY - resizeStartY;
2098
+ const newHeight = Math.max(20, resizeStartSize + delta / viewport.zoom);
2099
+ updateRowHeightsLocally((prev) => new Map(prev).set(isResizingRow, newHeight));
2100
+ }
2101
+ }, [
2102
+ isResizingColumn,
2103
+ isResizingRow,
2104
+ resizeStartX,
2105
+ resizeStartY,
2106
+ resizeStartSize,
2107
+ updateColumnWidthsLocally,
2108
+ updateRowHeightsLocally,
2109
+ viewport.zoom
2110
+ ]);
2111
+ const handleResizeEnd = useCallback3(() => {
2112
+ if (isResizingColumn) {
2113
+ commitColumnWidths(columnWidthsRef.current);
2114
+ }
2115
+ if (isResizingRow !== null) {
2116
+ commitRowHeights(rowHeightsRef.current);
2117
+ }
2118
+ setIsResizingColumn(null);
2119
+ setIsResizingRow(null);
2120
+ }, [commitColumnWidths, commitRowHeights, isResizingColumn, isResizingRow]);
2121
+ const handleColumnResizeDoubleClick = useCallback3((e, col) => {
2122
+ e.preventDefault();
2123
+ e.stopPropagation();
2124
+ const { viewport: viewport2, gridDims: gridDims2, height: height2, cellData: cellData2 } = effectsRef.current;
2125
+ let maxWidth = 50;
2126
+ const startRow = Math.max(0, Math.floor(viewport2.scrollY / gridDims2.cellHeight));
2127
+ const endRow = Math.ceil((viewport2.scrollY + height2) / gridDims2.cellHeight) + 1;
2128
+ for (let row = startRow;row <= endRow; row++) {
2129
+ const cellKey = `${col}${getRowNumber(row)}`;
2130
+ const content = cellData2.get(cellKey) ?? "";
2131
+ if (content) {
2132
+ const estimatedWidth = String(content).length * 8 + 20;
2133
+ maxWidth = Math.max(maxWidth, estimatedWidth);
2134
+ }
2135
+ }
2136
+ commitColumnWidths(new Map(columnWidthsRef.current).set(col, maxWidth));
2137
+ }, [commitColumnWidths]);
2138
+ const handleRowResizeDoubleClick = useCallback3((e, row) => {
2139
+ e.preventDefault();
2140
+ e.stopPropagation();
2141
+ commitRowHeights(new Map(rowHeightsRef.current).set(row, DEFAULT_CELL_HEIGHT));
2142
+ }, [commitRowHeights]);
2143
+ const commitCurrentOverlayChildren = useCallback3(() => {
2144
+ overlayInteractionDraftRectsRef.current = {};
2145
+ commitOverlayChildren(overlayChildrenRef.current);
2146
+ }, [commitOverlayChildren]);
2147
+ const setSelectedOverlay = useCallback3((childId) => {
2148
+ if (onOverlaySelect) {
2149
+ onOverlaySelect(childId);
2150
+ }
2151
+ setInternalSelectedOverlayId(childId);
2152
+ }, [onOverlaySelect]);
2153
+ const handleOverlayClick = useCallback3((childId, e) => {
2154
+ e.stopPropagation();
2155
+ if (!parentSelected) {
2156
+ return;
2157
+ }
2158
+ selectionManager.blur();
2159
+ setSelectedOverlay(childId);
2160
+ }, [setSelectedOverlay, selectionManager, parentSelected]);
2161
+ const handleOverlayBackgroundClick = useCallback3(() => {
2162
+ setSelectedOverlay(null);
2163
+ }, [setSelectedOverlay]);
2164
+ const getOverlayInteractionDraftRect = useCallback3((child) => {
2165
+ const currentDraft = overlayInteractionDraftRectsRef.current[child.id];
2166
+ if (currentDraft) {
2167
+ return currentDraft;
2168
+ }
2169
+ const draft = {
2170
+ x: child.x,
2171
+ y: child.y,
2172
+ width: child.width,
2173
+ height: child.height
2174
+ };
2175
+ overlayInteractionDraftRectsRef.current[child.id] = draft;
2176
+ return draft;
2177
+ }, []);
2178
+ const handleOverlayMove = useCallback3((childId, deltaX, deltaY) => {
2179
+ updateOverlayChildrenLocally((children) => children.map((child) => {
2180
+ if (child.id !== childId)
2181
+ return child;
2182
+ if (isSnappedTextBox(child)) {
2183
+ const draft = getOverlayInteractionDraftRect(child);
2184
+ draft.x = Math.max(0, draft.x + deltaX);
2185
+ draft.y = Math.max(0, draft.y + deltaY);
2186
+ const baseAnchor = child.cellAnchor ?? getCellSnapAnchorFromRect(child, columnWidthsRef.current, rowHeightsRef.current);
2187
+ const nextAnchor = moveCellSnapAnchorToNearestOrigin(baseAnchor, draft, columnWidthsRef.current, rowHeightsRef.current);
2188
+ return applyCellSnapAnchorToTextBox(child, nextAnchor, columnWidthsRef.current, rowHeightsRef.current);
2189
+ }
2190
+ const newX = Math.max(0, child.x + deltaX);
2191
+ const newY = Math.max(0, child.y + deltaY);
2192
+ return { ...child, x: newX, y: newY };
2193
+ }));
2194
+ }, [getOverlayInteractionDraftRect, updateOverlayChildrenLocally]);
2195
+ const handleOverlayResize = useCallback3((childId, deltaWidth, deltaHeight, shiftKey, handle) => {
2196
+ updateOverlayChildrenLocally((children) => children.map((child) => {
2197
+ if (child.id !== childId)
2198
+ return child;
2199
+ if (isSnappedTextBox(child) && isCellSnapResizeHandle(handle)) {
2200
+ const draft = getOverlayInteractionDraftRect(child);
2201
+ let nextX = draft.x;
2202
+ let nextY = draft.y;
2203
+ let nextWidth = draft.width;
2204
+ let nextHeight = draft.height;
2205
+ if (handle === "left" || handle === "top-left" || handle === "bottom-left") {
2206
+ nextX = draft.x - deltaWidth;
2207
+ nextWidth = draft.width + deltaWidth;
2208
+ }
2209
+ if (handle === "right" || handle === "top-right" || handle === "bottom-right") {
2210
+ nextWidth = draft.width + deltaWidth;
2211
+ }
2212
+ if (handle === "top" || handle === "top-left" || handle === "top-right") {
2213
+ nextY = draft.y - deltaHeight;
2214
+ nextHeight = draft.height + deltaHeight;
2215
+ }
2216
+ if (handle === "bottom" || handle === "bottom-left" || handle === "bottom-right") {
2217
+ nextHeight = draft.height + deltaHeight;
2218
+ }
2219
+ if (nextX < 0) {
2220
+ nextWidth += nextX;
2221
+ nextX = 0;
2222
+ }
2223
+ if (nextY < 0) {
2224
+ nextHeight += nextY;
2225
+ nextY = 0;
2226
+ }
2227
+ draft.x = nextX;
2228
+ draft.y = nextY;
2229
+ draft.width = Math.max(1, nextWidth);
2230
+ draft.height = Math.max(1, nextHeight);
2231
+ const baseAnchor = child.cellAnchor ?? getCellSnapAnchorFromRect(child, columnWidthsRef.current, rowHeightsRef.current);
2232
+ const nextAnchor = resizeCellSnapAnchorToNearestEdges(baseAnchor, draft, handle, columnWidthsRef.current, rowHeightsRef.current);
2233
+ return applyCellSnapAnchorToTextBox(child, nextAnchor, columnWidthsRef.current, rowHeightsRef.current);
2234
+ }
2235
+ if (shiftKey && (deltaWidth !== 0 || deltaHeight !== 0)) {
2236
+ const currentZoom = child.zoom || 1;
2237
+ const currentVisualWidth = child.width;
2238
+ const currentVisualHeight = child.height;
2239
+ const widthScale = deltaWidth !== 0 ? 1 + deltaWidth / currentVisualWidth : 1;
2240
+ const heightScale = deltaHeight !== 0 ? 1 + deltaHeight / currentVisualHeight : 1;
2241
+ let scaleDelta;
2242
+ if (handle.includes("-")) {
2243
+ scaleDelta = Math.abs(widthScale - 1) > Math.abs(heightScale - 1) ? widthScale : heightScale;
2244
+ } else if (handle === "left" || handle === "right") {
2245
+ scaleDelta = widthScale;
2246
+ } else if (handle === "top" || handle === "bottom") {
2247
+ scaleDelta = heightScale;
2248
+ } else {
2249
+ scaleDelta = Math.max(widthScale, heightScale);
2250
+ }
2251
+ const newZoom = currentZoom * scaleDelta;
2252
+ const newWidth = child.width * scaleDelta;
2253
+ const newHeight = child.height * scaleDelta;
2254
+ const deltaX = newWidth - child.width;
2255
+ const deltaY = newHeight - child.height;
2256
+ let newX = child.x;
2257
+ let newY = child.y;
2258
+ if (handle === "left" || handle === "bottom-left") {
2259
+ newX = child.x - deltaX;
2260
+ }
2261
+ if (handle === "top" || handle === "top-right") {
2262
+ newY = child.y - deltaY;
2263
+ }
2264
+ if (handle === "top-left") {
2265
+ newX = child.x - deltaX;
2266
+ newY = child.y - deltaY;
2267
+ }
2268
+ newX = Math.max(0, newX);
2269
+ newY = Math.max(0, newY);
2270
+ return {
2271
+ ...child,
2272
+ x: newX,
2273
+ y: newY,
2274
+ width: newWidth,
2275
+ height: newHeight,
2276
+ zoom: newZoom
2277
+ };
2278
+ } else {
2279
+ let newX = child.x;
2280
+ let newY = child.y;
2281
+ if (handle === "left" || handle === "bottom-left") {
2282
+ newX = child.x - deltaWidth;
2283
+ }
2284
+ if (handle === "top" || handle === "top-right") {
2285
+ newY = child.y - deltaHeight;
2286
+ }
2287
+ if (handle === "top-left") {
2288
+ newX = child.x - deltaWidth;
2289
+ newY = child.y - deltaHeight;
2290
+ }
2291
+ newX = Math.max(0, newX);
2292
+ newY = Math.max(0, newY);
2293
+ return {
2294
+ ...child,
2295
+ x: newX,
2296
+ y: newY,
2297
+ width: child.width + deltaWidth,
2298
+ height: child.height + deltaHeight,
2299
+ zoom: child.zoom
2300
+ };
2301
+ }
2302
+ }));
2303
+ }, [getOverlayInteractionDraftRect, updateOverlayChildrenLocally]);
2304
+ const handleOverlaySnapToCellsToggle = useCallback3((childId, enabled) => {
2305
+ overlayInteractionDraftRectsRef.current = {};
2306
+ commitOverlayChildren(overlayChildrenRef.current.map((child) => {
2307
+ if (child.id !== childId || child.component !== "RichText") {
2308
+ return child;
2309
+ }
2310
+ if (!enabled) {
2311
+ return {
2312
+ ...child,
2313
+ snapToCells: false,
2314
+ cellAnchor: undefined
2315
+ };
2316
+ }
2317
+ const anchor = getCellSnapAnchorFromRect(child, columnWidthsRef.current, rowHeightsRef.current);
2318
+ return applyCellSnapAnchorToTextBox(child, anchor, columnWidthsRef.current, rowHeightsRef.current);
2319
+ }));
2320
+ }, [commitOverlayChildren]);
2321
+ const handleOverlayReset = useCallback3((childId) => {
2322
+ commitOverlayChildren(overlayChildrenRef.current.map((child) => {
2323
+ if (child.id !== childId || !child.initial)
2324
+ return child;
2325
+ return {
2326
+ ...child,
2327
+ x: child.initial.x,
2328
+ y: child.initial.y,
2329
+ width: child.initial.width,
2330
+ height: child.initial.height,
2331
+ zoom: child.initial.zoom || 1,
2332
+ minimized: false
2333
+ };
2334
+ }));
2335
+ }, [commitOverlayChildren]);
2336
+ const handleOverlayMinimize = useCallback3((childId) => {
2337
+ commitOverlayChildren(overlayChildrenRef.current.map((child) => child.id === childId && child.component !== "RichText" ? { ...child, minimized: !child.minimized } : child));
2338
+ }, [commitOverlayChildren]);
2339
+ const handleOverlayClose = useCallback3((childId) => {
2340
+ commitOverlayChildren(overlayChildrenRef.current.filter((child) => child.id !== childId));
2341
+ }, [commitOverlayChildren]);
2342
+ useEffect3(() => {
2343
+ const handleOverlayDeleteKeyDown = (event) => {
2344
+ if (!selectedOverlayId || event.key !== "Backspace" && event.key !== "Delete") {
2345
+ return;
2346
+ }
2347
+ if (isEditableKeyboardTarget(event.target)) {
2348
+ return;
2349
+ }
2350
+ const selectedOverlay = overlayChildrenRef.current.find((child) => child.id === selectedOverlayId);
2351
+ if (selectedOverlay?.component !== "RichText") {
2352
+ return;
2353
+ }
2354
+ event.preventDefault();
2355
+ event.stopPropagation();
2356
+ setSelectedOverlay(null);
2357
+ handleOverlayClose(selectedOverlayId);
2358
+ };
2359
+ document.addEventListener("keydown", handleOverlayDeleteKeyDown);
2360
+ return () => {
2361
+ document.removeEventListener("keydown", handleOverlayDeleteKeyDown);
2362
+ };
2363
+ }, [handleOverlayClose, selectedOverlayId, setSelectedOverlay]);
2364
+ useEffect3(() => {
2365
+ drawGrid();
2366
+ }, [drawGrid]);
2367
+ useEffect3(() => {
2368
+ if (isResizingColumn || isResizingRow !== null) {
2369
+ document.addEventListener("mousemove", handleResizeMove);
2370
+ document.addEventListener("mouseup", handleResizeEnd);
2371
+ document.body.style.cursor = isResizingColumn ? "col-resize" : "row-resize";
2372
+ return () => {
2373
+ document.removeEventListener("mousemove", handleResizeMove);
2374
+ document.removeEventListener("mouseup", handleResizeEnd);
2375
+ document.body.style.cursor = "";
2376
+ };
2377
+ }
2378
+ }, [isResizingColumn, isResizingRow, handleResizeMove, handleResizeEnd]);
2379
+ useEffect3(() => {
2380
+ const editingCleanup = selectionManager.observeStateChange((state) => state.isEditing.type, (type) => {
2381
+ if (type !== "none") {
2382
+ const handleEditingKeydown = (e) => {
2383
+ if ((e.metaKey || e.ctrlKey) && e.key === "v") {
2384
+ return;
2385
+ }
2386
+ if (e.key === "Delete" || e.key === "Backspace") {
2387
+ return;
2388
+ }
2389
+ };
2390
+ window.addEventListener("keydown", handleEditingKeydown);
2391
+ return () => {
2392
+ window.removeEventListener("keydown", handleEditingKeydown);
2393
+ };
2394
+ }
2395
+ }, true);
2396
+ return editingCleanup;
2397
+ }, [selectionManager]);
2398
+ React2.useEffect(() => {
2399
+ return selectionManager.observeStateChange((state) => state.hasFocus, (hasFocus2) => {
2400
+ if (hasFocus2 && selectedOverlayId) {
2401
+ setSelectedOverlay(null);
2402
+ }
2403
+ });
2404
+ }, [selectionManager, selectedOverlayId, setSelectedOverlay]);
2405
+ const getVisibleColumnHeaders = useCallback3(() => {
2406
+ if (!width)
2407
+ return [];
2408
+ const headers = [];
2409
+ let x = 0;
2410
+ let startCol = 0;
2411
+ while (x < viewport.scrollX) {
2412
+ const col = indexToColumn(startCol);
2413
+ const colWidth = getColumnWidth(col) * viewport.zoom;
2414
+ if (x + colWidth > viewport.scrollX) {
2415
+ break;
2416
+ }
2417
+ x += colWidth;
2418
+ startCol++;
2419
+ }
2420
+ while (x < viewport.scrollX + width) {
2421
+ const col = indexToColumn(startCol);
2422
+ const colWidth = getColumnWidth(col) * viewport.zoom;
2423
+ headers.push({
2424
+ col,
2425
+ colIndex: startCol
2426
+ });
2427
+ x += colWidth;
2428
+ startCol++;
2429
+ }
2430
+ return headers;
2431
+ }, [width, viewport, getColumnWidth]);
2432
+ const getVisibleRowHeaders = useCallback3(() => {
2433
+ if (!height)
2434
+ return [];
2435
+ const headers = [];
2436
+ let y = 0;
2437
+ let rowNum = 1;
2438
+ while (y < viewport.scrollY) {
2439
+ const rowHeight = getRowHeight(rowNum) * viewport.zoom;
2440
+ if (y + rowHeight > viewport.scrollY) {
2441
+ break;
2442
+ }
2443
+ y += rowHeight;
2444
+ rowNum++;
2445
+ }
2446
+ while (y < viewport.scrollY + height) {
2447
+ const rowHeight = getRowHeight(rowNum) * viewport.zoom;
2448
+ headers.push({
2449
+ row: rowNum,
2450
+ rowIndex: rowNum - 1
2451
+ });
2452
+ y += rowHeight;
2453
+ rowNum++;
2454
+ }
2455
+ return headers;
2456
+ }, [height, viewport, getRowHeight]);
2457
+ const { visibleOverlays, overlaysToRender } = useMemo(() => {
2458
+ if (!width || !height || !overlayChildren) {
2459
+ return { visibleOverlays: [], overlaysToRender: [] };
2460
+ }
2461
+ const visible = [];
2462
+ const toRender = [];
2463
+ const newMountedPersistentIds = new Set(mountedPersistentOverlayIds);
2464
+ overlayChildren.forEach((child) => {
2465
+ const childLeft = child.x * viewport.zoom - viewport.scrollX + gridDims.headerWidth;
2466
+ const childTop = child.y * viewport.zoom - viewport.scrollY + gridDims.headerHeight;
2467
+ const childRight = childLeft + child.width * viewport.zoom;
2468
+ const childBottom = childTop + child.height * viewport.zoom;
2469
+ const margin = 100;
2470
+ const viewportLeft = gridDims.headerWidth - margin;
2471
+ const viewportTop = gridDims.headerHeight - margin;
2472
+ const viewportRight = width + margin;
2473
+ const viewportBottom = height + margin;
2474
+ const isVisible = childRight >= viewportLeft && childLeft <= viewportRight && childBottom >= viewportTop && childTop <= viewportBottom;
2475
+ if (isVisible) {
2476
+ visible.push(child);
2477
+ toRender.push(child);
2478
+ if (child.persistent) {
2479
+ newMountedPersistentIds.add(child.id);
2480
+ }
2481
+ } else if (child.persistent && mountedPersistentOverlayIds.has(child.id)) {
2482
+ toRender.push(child);
2483
+ }
2484
+ });
2485
+ if (newMountedPersistentIds.size !== mountedPersistentOverlayIds.size || [...newMountedPersistentIds].some((id) => !mountedPersistentOverlayIds.has(id))) {
2486
+ setMountedPersistentOverlayIds(newMountedPersistentIds);
2487
+ }
2488
+ return { visibleOverlays: visible, overlaysToRender: toRender };
2489
+ }, [
2490
+ overlayChildren,
2491
+ viewport.zoom,
2492
+ viewport.scrollX,
2493
+ viewport.scrollY,
2494
+ width,
2495
+ height,
2496
+ gridDims.headerWidth,
2497
+ gridDims.headerHeight,
2498
+ mountedPersistentOverlayIds
2499
+ ]);
2500
+ const visibleCells = React2.useMemo(() => getVisibleCells(), [getVisibleCells]);
2501
+ const visibleColumnHeaders = React2.useMemo(() => getVisibleColumnHeaders(), [getVisibleColumnHeaders]);
2502
+ const visibleRowHeaders = React2.useMemo(() => getVisibleRowHeaders(), [getVisibleRowHeaders]);
2503
+ const hasFocus = useSelectionManager(selectionManager, () => selectionManager.hasFocus);
2504
+ React2.useEffect(() => {
2505
+ if (!containerDivRef || !hasFocus || !parentSelected) {
2506
+ return;
2507
+ }
2508
+ containerDivRef.addEventListener("wheel", handleWheel, {
2509
+ passive: false
2510
+ });
2511
+ return () => {
2512
+ containerDivRef.removeEventListener("wheel", handleWheel);
2513
+ };
2514
+ }, [containerDivRef, handleWheel, hasFocus, parentSelected]);
2515
+ useImperativeHandle(ref, () => ({
2516
+ focus: () => {
2517
+ selectionManager.focus();
2518
+ },
2519
+ blur: () => {
2520
+ selectionManager.blur();
2521
+ }
2522
+ }), [selectionManager]);
2523
+ return /* @__PURE__ */ jsx3("div", {
2524
+ className: cn("rsp-root", className),
2525
+ style: { position: "relative", ...style },
2526
+ ...containerProps,
2527
+ children: /* @__PURE__ */ jsx3("div", {
2528
+ style: { position: "absolute", inset: 0 },
2529
+ ref: containerRef,
2530
+ children: Boolean(width && height) && /* @__PURE__ */ jsxs2("div", {
2531
+ ref: setContainerDivRef,
2532
+ className: cn("rsp-container", { "rsp-focused": hasFocus }),
2533
+ "data-testid": "spreadsheet-container",
2534
+ style: {
2535
+ width,
2536
+ height
2537
+ },
2538
+ tabIndex: 0,
2539
+ children: [
2540
+ /* @__PURE__ */ jsx3("canvas", {
2541
+ ref: canvasRef,
2542
+ className: "rsp-canvas",
2543
+ "data-testid": "spreadsheet-canvas",
2544
+ style: { width, height }
2545
+ }),
2546
+ /* @__PURE__ */ jsx3("div", {
2547
+ className: "rsp-column-headers",
2548
+ "data-testid": "spreadsheet-column-headers",
2549
+ style: {
2550
+ left: gridDims.headerWidth,
2551
+ right: 0,
2552
+ height: gridDims.headerHeight,
2553
+ zIndex: 20
2554
+ },
2555
+ children: /* @__PURE__ */ jsx3("div", {
2556
+ style: {
2557
+ transform: `translateX(${-viewport.scrollX}px) scale(${viewport.zoom})`,
2558
+ transformOrigin: "0 0"
2559
+ },
2560
+ children: visibleColumnHeaders.map(({ col, colIndex }) => {
2561
+ return /* @__PURE__ */ jsx3(HeaderComponent, {
2562
+ index: colIndex,
2563
+ type: "col",
2564
+ selectionManager,
2565
+ getColumnWidth,
2566
+ getRowHeight,
2567
+ handleColumnResizeStart,
2568
+ handleColumnResizeDoubleClick,
2569
+ handleRowResizeStart,
2570
+ handleRowResizeDoubleClick
2571
+ }, col);
2572
+ })
2573
+ })
2574
+ }),
2575
+ /* @__PURE__ */ jsx3("div", {
2576
+ className: "rsp-row-headers",
2577
+ "data-testid": "spreadsheet-row-headers",
2578
+ style: {
2579
+ top: gridDims.headerHeight,
2580
+ bottom: 0,
2581
+ width: gridDims.headerWidth,
2582
+ zIndex: 20
2583
+ },
2584
+ children: /* @__PURE__ */ jsx3("div", {
2585
+ style: {
2586
+ transform: `translateY(${-viewport.scrollY}px) scale(${viewport.zoom})`,
2587
+ transformOrigin: "0 0"
2588
+ },
2589
+ children: visibleRowHeaders.map(({ row, rowIndex }) => {
2590
+ return /* @__PURE__ */ jsx3(HeaderComponent, {
2591
+ index: rowIndex,
2592
+ type: "row",
2593
+ selectionManager,
2594
+ getColumnWidth,
2595
+ getRowHeight,
2596
+ handleColumnResizeStart,
2597
+ handleColumnResizeDoubleClick,
2598
+ handleRowResizeStart,
2599
+ handleRowResizeDoubleClick
2600
+ }, row);
2601
+ })
2602
+ })
2603
+ }),
2604
+ /* @__PURE__ */ jsx3("div", {
2605
+ className: "rsp-corner-header",
2606
+ "data-testid": "spreadsheet-corner-header",
2607
+ style: {
2608
+ width: gridDims.headerWidth,
2609
+ height: gridDims.headerHeight,
2610
+ zIndex: 40
2611
+ }
2612
+ }),
2613
+ /* @__PURE__ */ jsx3("div", {
2614
+ className: "rsp-cells",
2615
+ "data-testid": "spreadsheet-cells",
2616
+ style: {
2617
+ transform: `translate(${-viewport.scrollX}px, ${-viewport.scrollY}px) scale(${viewport.zoom})`,
2618
+ transformOrigin: "0 0"
2619
+ },
2620
+ children: visibleCells.map((cell) => /* @__PURE__ */ jsx3(CellComponent, {
2621
+ value: cellData.get(`${cell.col}${cell.row}`) ?? "",
2622
+ row: cell.row,
2623
+ col: cell.col,
2624
+ selectionManager,
2625
+ cellToPixelBase,
2626
+ getColumnWidth,
2627
+ getRowHeight,
2628
+ customCellStyle,
2629
+ customCellRenderer,
2630
+ inputValue: cellData.get(`${cell.col}${cell.row}`) ?? "",
2631
+ editInputVersion: retryingEdit?.rowIndex === cell.row - 1 && retryingEdit?.colIndex === columnToIndex(cell.col) ? retryingEdit.version : 0,
2632
+ width,
2633
+ height
2634
+ }, `${cell.col}${cell.row}`))
2635
+ }),
2636
+ components && overlayChildren && overlayChildren.length > 0 && /* @__PURE__ */ jsxs2("div", {
2637
+ ref: overlayContainerRef,
2638
+ className: "rsp-overlays",
2639
+ "data-testid": "spreadsheet-overlays",
2640
+ onClick: handleOverlayBackgroundClick,
2641
+ children: [
2642
+ overlaysToRender.map((child) => {
2643
+ const isChildVisible = visibleOverlays.includes(child);
2644
+ const isSelected = selectedOverlayId === child.id;
2645
+ const childZoom = child.zoom || 1;
2646
+ const isTextBox = child.component === "RichText";
2647
+ const isMinimized = Boolean(child.minimized && !isTextBox);
2648
+ return /* @__PURE__ */ jsxs2("div", {
2649
+ style: {
2650
+ position: "absolute",
2651
+ width: child.width,
2652
+ height: isMinimized ? 0 : child.height,
2653
+ transform: `translate(${child.x * viewport.zoom - viewport.scrollX + gridDims.headerWidth}px, ${child.y * viewport.zoom - viewport.scrollY + gridDims.headerHeight}px) scale(${viewport.zoom})`,
2654
+ transformOrigin: "0 0",
2655
+ display: isChildVisible ? "block" : "none",
2656
+ pointerEvents: "auto",
2657
+ zIndex: isSelected ? 50 : 5
2658
+ },
2659
+ onClick: (e) => handleOverlayClick(child.id, e),
2660
+ children: [
2661
+ /* @__PURE__ */ jsx3(Frame, {
2662
+ child,
2663
+ isSelected,
2664
+ zoom: viewport.zoom,
2665
+ viewport$: viewportStreamRef.current,
2666
+ onMove: (dx, dy) => handleOverlayMove(child.id, dx, dy),
2667
+ onResize: (dw, dh, shift, handle) => handleOverlayResize(child.id, dw, dh, shift, handle),
2668
+ onSnapToCellsToggle: (enabled) => handleOverlaySnapToCellsToggle(child.id, enabled),
2669
+ onReset: () => handleOverlayReset(child.id),
2670
+ onMinimize: () => handleOverlayMinimize(child.id),
2671
+ onClose: () => handleOverlayClose(child.id),
2672
+ onInteractionEnd: commitCurrentOverlayChildren
2673
+ }),
2674
+ !isMinimized && /* @__PURE__ */ jsx3("div", {
2675
+ style: {
2676
+ transform: childZoom !== 1 ? `scale(${childZoom})` : undefined,
2677
+ transformOrigin: "0 0",
2678
+ width: child.width / childZoom,
2679
+ height: child.height / childZoom,
2680
+ position: "absolute",
2681
+ overflow: "hidden",
2682
+ top: 0,
2683
+ left: 0
2684
+ },
2685
+ children: components[child.component]?.(child, {
2686
+ isSelected,
2687
+ isMinimized: !!child.minimized
2688
+ })
2689
+ })
2690
+ ]
2691
+ }, child.id);
2692
+ }),
2693
+ overlayPlaceholder && placeholderPos && /* @__PURE__ */ jsx3("div", {
2694
+ className: "rsp-overlay-placeholder",
2695
+ style: {
2696
+ width: overlayPlaceholder.width,
2697
+ height: overlayPlaceholder.height,
2698
+ transform: `translate(${placeholderPos.x * viewport.zoom - viewport.scrollX + gridDims.headerWidth}px, ${placeholderPos.y * viewport.zoom - viewport.scrollY + gridDims.headerHeight}px) scale(${viewport.zoom})`,
2699
+ transformOrigin: "0 0"
2700
+ },
2701
+ children: /* @__PURE__ */ jsx3("div", {
2702
+ className: "rsp-overlay-placeholder-label",
2703
+ children: overlayPlaceholder.title
2704
+ })
2705
+ })
2706
+ ]
2707
+ })
2708
+ ]
2709
+ })
2710
+ })
2711
+ });
2712
+ });
2713
+ Spreadsheet.displayName = "Spreadsheet";
2714
+ // src/workbook/workbook.tsx
2715
+ import {
2716
+ parseCellReference as parseCellReference2,
2717
+ getCellReference as getCellReference3
2718
+ } from "@ricsam/formula-engine";
2719
+ import React3, { useCallback as useCallback4, useRef as useRef4, useState as useState5 } from "react";
2720
+
2721
+ // src/workbook/border-sides.ts
2722
+ function normalizeBorderSides(sides, defaultAllWhenUnset = false) {
2723
+ if (!sides) {
2724
+ return {
2725
+ top: defaultAllWhenUnset,
2726
+ right: defaultAllWhenUnset,
2727
+ bottom: defaultAllWhenUnset,
2728
+ left: defaultAllWhenUnset
2729
+ };
2730
+ }
2731
+ return {
2732
+ top: Boolean(sides.top),
2733
+ right: Boolean(sides.right),
2734
+ bottom: Boolean(sides.bottom),
2735
+ left: Boolean(sides.left)
2736
+ };
2737
+ }
2738
+ function hasAnyBorderSide(sides) {
2739
+ return sides.top || sides.right || sides.bottom || sides.left;
2740
+ }
2741
+
2742
+ // src/workbook/cell-data-type.ts
2743
+ var TRUE_LITERALS = new Set(["true", "yes"]);
2744
+ var FALSE_LITERALS = new Set(["false", "no"]);
2745
+ var parseNumeric = (value) => {
2746
+ const trimmed = value.trim();
2747
+ if (trimmed === "")
2748
+ return;
2749
+ const normalized = trimmed.includes(",") && !trimmed.includes(".") && trimmed.split(",").length === 2 ? trimmed.replace(",", ".") : trimmed;
2750
+ const parsed = Number(normalized);
2751
+ return Number.isFinite(parsed) ? parsed : undefined;
2752
+ };
2753
+ function coerceCellInput(value, dataType = "general") {
2754
+ if (value === "")
2755
+ return;
2756
+ if (value.startsWith("="))
2757
+ return value;
2758
+ switch (dataType) {
2759
+ case "text":
2760
+ return value;
2761
+ case "number": {
2762
+ const parsed = parseNumeric(value);
2763
+ return parsed === undefined ? value : parsed;
2764
+ }
2765
+ case "boolean": {
2766
+ const lower = value.trim().toLowerCase();
2767
+ if (TRUE_LITERALS.has(lower))
2768
+ return true;
2769
+ if (FALSE_LITERALS.has(lower))
2770
+ return false;
2771
+ return value;
2772
+ }
2773
+ case "general":
2774
+ default: {
2775
+ const lower = value.trim().toLowerCase();
2776
+ if (TRUE_LITERALS.has(lower))
2777
+ return true;
2778
+ if (FALSE_LITERALS.has(lower))
2779
+ return false;
2780
+ const parsed = parseNumeric(value);
2781
+ if (parsed !== undefined && String(parsed) === value.trim()) {
2782
+ return parsed;
2783
+ }
2784
+ return value;
2785
+ }
2786
+ }
2787
+ }
2788
+ function getCellDisplayText(value) {
2789
+ if (value === undefined)
2790
+ return "";
2791
+ if (typeof value === "boolean")
2792
+ return value ? "TRUE" : "FALSE";
2793
+ if (typeof value === "number") {
2794
+ return value.toLocaleString("en-US", {
2795
+ minimumFractionDigits: 0,
2796
+ maximumFractionDigits: 10
2797
+ });
2798
+ }
2799
+ return String(value);
2800
+ }
2801
+ function getCellDisplayValue(value) {
2802
+ if (value === undefined)
2803
+ return "";
2804
+ if (typeof value === "boolean")
2805
+ return value ? "TRUE" : "FALSE";
2806
+ return String(value);
2807
+ }
2808
+
2809
+ // src/workbook/cell-edit-error.ts
2810
+ var listeners = new Set;
2811
+ function onCellEditError(listener) {
2812
+ listeners.add(listener);
2813
+ return () => {
2814
+ listeners.delete(listener);
2815
+ };
2816
+ }
2817
+ function queueCellEditError(error) {
2818
+ if (listeners.size === 0) {
2819
+ console.error("Cell edit rejected:", error);
2820
+ return;
2821
+ }
2822
+ listeners.forEach((listener) => {
2823
+ try {
2824
+ listener(error);
2825
+ } catch (listenerError) {
2826
+ console.error("Cell edit error listener threw:", listenerError);
2827
+ }
2828
+ });
2829
+ }
2830
+
2831
+ // src/workbook/workbook-selection-manager.ts
2832
+ import { indexToColumn as indexToColumn2 } from "@ricsam/formula-engine";
2833
+
2834
+ class WorkbookSelectionManager {
2835
+ managers = new Map;
2836
+ lastFocusedManager = null;
2837
+ listeners = new Set;
2838
+ cleanupFunctions = new Map;
2839
+ smAreaToSpreadsheetRange(area) {
2840
+ const rowEnd = area.end.row.type === "infinity" ? { type: "infinity", sign: "positive" } : { type: "number", value: area.end.row.value };
2841
+ const colEnd = area.end.col.type === "infinity" ? { type: "infinity", sign: "positive" } : { type: "number", value: area.end.col.value };
2842
+ return {
2843
+ start: {
2844
+ col: area.start.col,
2845
+ row: area.start.row
2846
+ },
2847
+ end: {
2848
+ row: rowEnd,
2849
+ col: colEnd
2850
+ }
2851
+ };
2852
+ }
2853
+ add(manager, context) {
2854
+ this.managers.set(manager, context);
2855
+ const focusCleanup = manager.observeStateChange((state) => state.hasFocus, (hasFocus) => {
2856
+ if (hasFocus) {
2857
+ this.lastFocusedManager = { sm: manager, context };
2858
+ this.emitChange();
2859
+ }
2860
+ });
2861
+ const selectionCleanup = manager.observeStateChange((state) => JSON.stringify(state.selections), (selectionsJson) => {
2862
+ const selections = JSON.parse(selectionsJson);
2863
+ if (selections && selections.length > 0) {
2864
+ this.lastFocusedManager = { sm: manager, context };
2865
+ this.emitChange();
2866
+ }
2867
+ });
2868
+ const cleanup = () => {
2869
+ focusCleanup();
2870
+ selectionCleanup();
2871
+ this.managers.delete(manager);
2872
+ this.cleanupFunctions.delete(manager);
2873
+ if (this.lastFocusedManager?.sm === manager) {
2874
+ this.lastFocusedManager = null;
2875
+ }
2876
+ };
2877
+ this.cleanupFunctions.set(manager, cleanup);
2878
+ return cleanup;
2879
+ }
2880
+ getLastFocusedSelectionManager() {
2881
+ return this.lastFocusedManager;
2882
+ }
2883
+ getSelections() {
2884
+ if (!this.lastFocusedManager) {
2885
+ return [];
2886
+ }
2887
+ const { sm, context } = this.lastFocusedManager;
2888
+ const state = sm.getState();
2889
+ return state.selections.map((area) => ({
2890
+ workbookName: context.workbookName,
2891
+ sheetName: context.sheetName,
2892
+ range: this.smAreaToSpreadsheetRange(area)
2893
+ }));
2894
+ }
2895
+ getLastSelection() {
2896
+ const selections = this.getSelections();
2897
+ return selections[selections.length - 1] ?? null;
2898
+ }
2899
+ onSelectionChange(callback) {
2900
+ this.listeners.add(callback);
2901
+ return () => {
2902
+ this.listeners.delete(callback);
2903
+ };
2904
+ }
2905
+ formatLastSelectionAsRange() {
2906
+ const lastSelection = this.getLastSelection();
2907
+ if (!lastSelection) {
2908
+ return "";
2909
+ }
2910
+ const { workbookName, sheetName, range } = lastSelection;
2911
+ const startCol = indexToColumn2(range.start.col);
2912
+ const startRow = range.start.row + 1;
2913
+ let endCol;
2914
+ let endRow;
2915
+ if (range.end.col.type === "infinity") {
2916
+ endCol = "∞";
2917
+ } else {
2918
+ endCol = indexToColumn2(range.end.col.value);
2919
+ }
2920
+ if (range.end.row.type === "infinity") {
2921
+ endRow = "∞";
2922
+ } else {
2923
+ endRow = String(range.end.row.value + 1);
2924
+ }
2925
+ let rangeStr;
2926
+ const isColInfinity = range.end.col.type === "infinity";
2927
+ const isRowInfinity = range.end.row.type === "infinity";
2928
+ if (isColInfinity && isRowInfinity) {
2929
+ rangeStr = `${startCol}${startRow}:INFINITY`;
2930
+ } else if (isColInfinity) {
2931
+ rangeStr = `${startCol}${startRow}:${endRow}`;
2932
+ } else if (isRowInfinity) {
2933
+ rangeStr = `${startCol}${startRow}:${endCol}`;
2934
+ } else {
2935
+ rangeStr = `${startCol}${startRow}:${endCol}${endRow}`;
2936
+ }
2937
+ const needsQuotes = /[ '!]/.test(sheetName);
2938
+ const sheetRef = needsQuotes ? `'${sheetName.replace(/'/g, "''")}'` : sheetName;
2939
+ return `[${workbookName}]${sheetRef}!${rangeStr}`;
2940
+ }
2941
+ blur() {
2942
+ this.managers.forEach((context, manager) => {
2943
+ manager.clearSelections();
2944
+ manager.blur();
2945
+ });
2946
+ this.lastFocusedManager = null;
2947
+ this.emitChange();
2948
+ }
2949
+ focusWorkbook(workbookName) {
2950
+ for (const [manager, context] of this.managers) {
2951
+ if (context.workbookName === workbookName) {
2952
+ manager.focus();
2953
+ this.lastFocusedManager = { sm: manager, context };
2954
+ this.emitChange();
2955
+ return;
2956
+ }
2957
+ }
2958
+ }
2959
+ focusSheet(workbookName, sheetName) {
2960
+ for (const [manager, context] of this.managers) {
2961
+ if (context.workbookName === workbookName && context.sheetName === sheetName) {
2962
+ manager.focus();
2963
+ this.lastFocusedManager = { sm: manager, context };
2964
+ this.emitChange();
2965
+ return true;
2966
+ }
2967
+ }
2968
+ return false;
2969
+ }
2970
+ focusCell(workbookName, sheetName, row, col) {
2971
+ for (const [manager, context] of this.managers) {
2972
+ if (context.workbookName === workbookName && context.sheetName === sheetName) {
2973
+ const selection = {
2974
+ start: { row, col },
2975
+ end: {
2976
+ row: { type: "number", value: row },
2977
+ col: { type: "number", value: col }
2978
+ }
2979
+ };
2980
+ manager.setState({ selections: [selection] });
2981
+ manager.focus();
2982
+ this.lastFocusedManager = { sm: manager, context };
2983
+ this.emitChange();
2984
+ return true;
2985
+ }
2986
+ }
2987
+ return false;
2988
+ }
2989
+ emitChange() {
2990
+ const currentSelection = this.getLastSelection();
2991
+ const selections = currentSelection ? [currentSelection] : [];
2992
+ this.listeners.forEach((listener) => {
2993
+ listener(selections);
2994
+ });
2995
+ }
2996
+ }
2997
+
2998
+ // src/workbook/workbook.tsx
2999
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
3000
+ var getWrapCellStyle = (style) => style?.wrapText ? {
3001
+ whiteSpace: "pre-wrap",
3002
+ overflowWrap: "anywhere",
3003
+ wordBreak: "normal",
3004
+ textOverflow: "clip",
3005
+ alignItems: "flex-start"
3006
+ } : {};
3007
+ var getWrapContentStyle = (style) => style?.wrapText ? {
3008
+ whiteSpace: "pre-wrap",
3009
+ overflowWrap: "anywhere",
3010
+ wordBreak: "normal",
3011
+ textOverflow: "clip",
3012
+ width: "100%",
3013
+ minWidth: 0
3014
+ } : {};
3015
+ var getBorderCellStyle = (style) => (() => {
3016
+ if (!style?.borderColor) {
3017
+ return {};
3018
+ }
3019
+ const borderSides = normalizeBorderSides(style.borderSides, true);
3020
+ if (!hasAnyBorderSide(borderSides)) {
3021
+ return {};
3022
+ }
3023
+ return {
3024
+ ...borderSides.top && { borderTopColor: style.borderColor },
3025
+ ...borderSides.right && { borderRightColor: style.borderColor },
3026
+ ...borderSides.bottom && { borderBottomColor: style.borderColor },
3027
+ ...borderSides.left && { borderLeftColor: style.borderColor }
3028
+ };
3029
+ })();
3030
+ var Edit2Icon = ({ className = "", ...props }) => /* @__PURE__ */ jsxs3("svg", {
3031
+ xmlns: "http://www.w3.org/2000/svg",
3032
+ width: "16",
3033
+ height: "16",
3034
+ viewBox: "0 0 24 24",
3035
+ fill: "none",
3036
+ stroke: "currentColor",
3037
+ strokeWidth: "2",
3038
+ strokeLinecap: "round",
3039
+ strokeLinejoin: "round",
3040
+ className,
3041
+ ...props,
3042
+ children: [
3043
+ /* @__PURE__ */ jsx4("path", {
3044
+ d: "M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"
3045
+ }),
3046
+ /* @__PURE__ */ jsx4("path", {
3047
+ d: "m15 5 4 4"
3048
+ })
3049
+ ]
3050
+ });
3051
+ var Trash2Icon = ({ className = "", ...props }) => /* @__PURE__ */ jsxs3("svg", {
3052
+ xmlns: "http://www.w3.org/2000/svg",
3053
+ width: "16",
3054
+ height: "16",
3055
+ viewBox: "0 0 24 24",
3056
+ fill: "none",
3057
+ stroke: "currentColor",
3058
+ strokeWidth: "2",
3059
+ strokeLinecap: "round",
3060
+ strokeLinejoin: "round",
3061
+ className,
3062
+ ...props,
3063
+ children: [
3064
+ /* @__PURE__ */ jsx4("path", {
3065
+ d: "M3 6h18"
3066
+ }),
3067
+ /* @__PURE__ */ jsx4("path", {
3068
+ d: "M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"
3069
+ }),
3070
+ /* @__PURE__ */ jsx4("path", {
3071
+ d: "M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"
3072
+ }),
3073
+ /* @__PURE__ */ jsx4("line", {
3074
+ x1: "10",
3075
+ x2: "10",
3076
+ y1: "11",
3077
+ y2: "17"
3078
+ }),
3079
+ /* @__PURE__ */ jsx4("line", {
3080
+ x1: "14",
3081
+ x2: "14",
3082
+ y1: "11",
3083
+ y2: "17"
3084
+ })
3085
+ ]
3086
+ });
3087
+ var PlusIcon = ({ className = "", ...props }) => /* @__PURE__ */ jsxs3("svg", {
3088
+ xmlns: "http://www.w3.org/2000/svg",
3089
+ width: "16",
3090
+ height: "16",
3091
+ viewBox: "0 0 24 24",
3092
+ fill: "none",
3093
+ stroke: "currentColor",
3094
+ strokeWidth: "2",
3095
+ strokeLinecap: "round",
3096
+ strokeLinejoin: "round",
3097
+ className,
3098
+ ...props,
3099
+ children: [
3100
+ /* @__PURE__ */ jsx4("path", {
3101
+ d: "M5 12h14"
3102
+ }),
3103
+ /* @__PURE__ */ jsx4("path", {
3104
+ d: "M12 5v14"
3105
+ })
3106
+ ]
3107
+ });
3108
+ var MinusIcon = ({ className = "", ...props }) => /* @__PURE__ */ jsx4("svg", {
3109
+ xmlns: "http://www.w3.org/2000/svg",
3110
+ width: "16",
3111
+ height: "16",
3112
+ viewBox: "0 0 24 24",
3113
+ fill: "none",
3114
+ stroke: "currentColor",
3115
+ strokeWidth: "2",
3116
+ strokeLinecap: "round",
3117
+ strokeLinejoin: "round",
3118
+ className,
3119
+ ...props,
3120
+ children: /* @__PURE__ */ jsx4("path", {
3121
+ d: "M5 12h14"
3122
+ })
3123
+ });
3124
+ function useEngine(engine) {
3125
+ const [state, setState] = useState5(() => engine.getState());
3126
+ React3.useEffect(() => {
3127
+ return engine.onUpdate(() => {
3128
+ setState(engine.getState());
3129
+ });
3130
+ }, [engine]);
3131
+ return state;
3132
+ }
3133
+
3134
+ class WorkbookClipboardManager extends ClipboardUtils {
3135
+ engine;
3136
+ constructor(engine) {
3137
+ super();
3138
+ this.engine = engine;
3139
+ }
3140
+ copiedCells = [];
3141
+ signature = "";
3142
+ isCut = false;
3143
+ triggerCopy(context) {
3144
+ const cellData = this.engine.getSheet({
3145
+ workbookName: context.workbookName,
3146
+ sheetName: context.sheetName
3147
+ })?.content;
3148
+ if (!cellData)
3149
+ return;
3150
+ const extractedCells = this.extractCellsFromSelection(context.selectionManager, cellData);
3151
+ if (!extractedCells)
3152
+ return;
3153
+ const { width, height, cells } = extractedCells;
3154
+ const valueExportGrid = this.createExportGrid(width, height);
3155
+ const formulaExportGrid = this.createExportGrid(width, height);
3156
+ this.copiedCells = [];
3157
+ this.isCut = context.cut ?? false;
3158
+ cells.forEach(({ relative, absolute }) => {
3159
+ const cellAddress = {
3160
+ workbookName: context.workbookName,
3161
+ sheetName: context.sheetName,
3162
+ colIndex: absolute.columnIndex,
3163
+ rowIndex: absolute.rowIndex
3164
+ };
3165
+ this.copiedCells.push(cellAddress);
3166
+ const value = this.engine.getCellValue(cellAddress, false);
3167
+ const formula = cellData.get(getCellReference3(cellAddress));
3168
+ valueExportGrid[relative.rowIndex][relative.columnIndex] = getCellDisplayValue(value);
3169
+ formulaExportGrid[relative.rowIndex][relative.columnIndex] = formula;
3170
+ });
3171
+ this.signature = this.getTsvString(context.copyType === "formula" ? formulaExportGrid : valueExportGrid);
3172
+ this.writeToOsClipboard(context.copyType === "formula" ? formulaExportGrid : valueExportGrid);
3173
+ }
3174
+ triggerPaste(context) {
3175
+ if (context.rawString === this.signature) {
3176
+ const selections = context.selectionManager.selections;
3177
+ if (!selections || selections.length === 0)
3178
+ return;
3179
+ const convertSMAreaToSpreadsheetRange = (area) => {
3180
+ return {
3181
+ start: {
3182
+ col: area.start.col,
3183
+ row: area.start.row
3184
+ },
3185
+ end: {
3186
+ col: area.end.col.type === "infinity" ? { type: "infinity", sign: "positive" } : { type: "number", value: area.end.col.value },
3187
+ row: area.end.row.type === "infinity" ? { type: "infinity", sign: "positive" } : { type: "number", value: area.end.row.value }
3188
+ }
3189
+ };
3190
+ };
3191
+ const areas = selections.map(convertSMAreaToSpreadsheetRange);
3192
+ console.log("pasting", { cut: this.isCut });
3193
+ this.engine.smartPaste(this.copiedCells, {
3194
+ workbookName: context.workbookName,
3195
+ sheetName: context.sheetName,
3196
+ areas
3197
+ }, {
3198
+ cut: this.isCut,
3199
+ type: context.pasteType,
3200
+ include: "all"
3201
+ });
3202
+ this.isCut = false;
3203
+ } else {
3204
+ context.selectionManager.saveCellValues(context.updates);
3205
+ }
3206
+ }
3207
+ }
3208
+ function FormulaSheet({
3209
+ sheetName,
3210
+ workbookName,
3211
+ engine,
3212
+ verboseErrors = false,
3213
+ selection,
3214
+ selectionManager,
3215
+ customCellStyle,
3216
+ customCellRenderer,
3217
+ onCellDataChangeError = queueCellEditError,
3218
+ components,
3219
+ overlayChildren,
3220
+ selectedOverlayId,
3221
+ onOverlaySelect,
3222
+ onOverlayChildrenChange,
3223
+ overlayPlaceholder,
3224
+ clipboardManager: controlledClipboardManager,
3225
+ isSelected = false,
3226
+ toolbar,
3227
+ showFormulas = false,
3228
+ ...rest
3229
+ }) {
3230
+ const [selectedArea, setSelectedArea] = useState5(null);
3231
+ const clipboardManager = React3.useMemo(() => controlledClipboardManager ?? new WorkbookClipboardManager(engine), [controlledClipboardManager, engine]);
3232
+ const state = useEngine(engine);
3233
+ const sheet = state.workbooks.get(workbookName)?.sheets.get(sheetName);
3234
+ const onCellDataChange = useCallback4((updatedSpreadsheet) => {
3235
+ const data = new Map(updatedSpreadsheet);
3236
+ data.forEach((value, key) => {
3237
+ if (typeof value === "string") {
3238
+ const { colIndex, rowIndex } = parseCellReference2(key);
3239
+ const coercedValue = coerceCellInput(value, engine.getCellDataType({ workbookName, sheetName, colIndex, rowIndex }));
3240
+ if (coercedValue === undefined) {
3241
+ data.delete(key);
3242
+ } else {
3243
+ data.set(key, coercedValue);
3244
+ }
3245
+ }
3246
+ });
3247
+ engine.setSheetContent({ sheetName, workbookName }, data);
3248
+ }, [sheetName, engine, workbookName]);
3249
+ const passedSelectionEffects = selection?.effects;
3250
+ const selectionManagerEffects = useCallback4((spreadsheetSelectionManager) => {
3251
+ const convertSmAreaToSpreadsheetRange = (area) => {
3252
+ const rowEnd = area.end.row.type === "infinity" ? {
3253
+ type: "infinity",
3254
+ sign: "positive"
3255
+ } : {
3256
+ type: "number",
3257
+ value: area.end.row.value
3258
+ };
3259
+ const colEnd = area.end.col.type === "infinity" ? {
3260
+ type: "infinity",
3261
+ sign: "positive"
3262
+ } : {
3263
+ type: "number",
3264
+ value: area.end.col.value
3265
+ };
3266
+ return {
3267
+ start: {
3268
+ col: area.start.col,
3269
+ row: area.start.row
3270
+ },
3271
+ end: {
3272
+ row: rowEnd,
3273
+ col: colEnd
3274
+ }
3275
+ };
3276
+ };
3277
+ const cleanups = [
3278
+ spreadsheetSelectionManager.listenToCopy((cut) => {
3279
+ console.log("cut??", cut);
3280
+ clipboardManager.triggerCopy({
3281
+ workbookName,
3282
+ sheetName,
3283
+ selectionManager: spreadsheetSelectionManager,
3284
+ copyType: showFormulas ? "formula" : "value",
3285
+ cut
3286
+ });
3287
+ }),
3288
+ spreadsheetSelectionManager.listenToPaste(({ updates, rawString }) => {
3289
+ clipboardManager.triggerPaste({
3290
+ workbookName,
3291
+ sheetName,
3292
+ selectionManager: spreadsheetSelectionManager,
3293
+ updates,
3294
+ rawString,
3295
+ pasteType: "formula"
3296
+ });
3297
+ }),
3298
+ spreadsheetSelectionManager.listenToFill((ev) => {
3299
+ if (ev.type === "extend") {
3300
+ engine.autoFill({ sheetName, workbookName }, convertSmAreaToSpreadsheetRange(ev.seedRange), [convertSmAreaToSpreadsheetRange(ev.fillRange)], ev.direction);
3301
+ } else {
3302
+ engine.clearSpreadsheetRange({
3303
+ sheetName,
3304
+ workbookName,
3305
+ range: convertSmAreaToSpreadsheetRange(ev.rangeToClear)
3306
+ });
3307
+ }
3308
+ }),
3309
+ spreadsheetSelectionManager.observeStateChange((state2) => {
3310
+ const currentSelection = state2.selections.length === 1 ? state2.selections[0] : undefined;
3311
+ return currentSelection;
3312
+ }, (selection2) => {
3313
+ setSelectedArea(selection2 ?? null);
3314
+ })
3315
+ ];
3316
+ const handleUndoRedoKeyDown = (event) => {
3317
+ if (!spreadsheetSelectionManager.hasFocus)
3318
+ return;
3319
+ if (spreadsheetSelectionManager.isEditing.type !== "none")
3320
+ return;
3321
+ const target = event.target;
3322
+ if (target instanceof HTMLElement) {
3323
+ const isEditableTarget = target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.tagName === "SELECT" || target.isContentEditable || Boolean(target.closest('[contenteditable="true"]'));
3324
+ if (isEditableTarget)
3325
+ return;
3326
+ }
3327
+ if (!event.metaKey && !event.ctrlKey)
3328
+ return;
3329
+ const key = event.key.toLowerCase();
3330
+ const isUndo = key === "z" && !event.shiftKey;
3331
+ const isRedo = key === "y" || key === "z" && event.shiftKey;
3332
+ if (!isUndo && !isRedo)
3333
+ return;
3334
+ event.preventDefault();
3335
+ event.stopPropagation();
3336
+ if (isUndo) {
3337
+ engine.undo();
3338
+ } else {
3339
+ engine.redo();
3340
+ }
3341
+ };
3342
+ document.addEventListener("keydown", handleUndoRedoKeyDown);
3343
+ cleanups.push(() => {
3344
+ document.removeEventListener("keydown", handleUndoRedoKeyDown);
3345
+ });
3346
+ if (selectionManager) {
3347
+ const workbookManagerCleanup = selectionManager.add(spreadsheetSelectionManager, {
3348
+ workbookName,
3349
+ sheetName
3350
+ });
3351
+ cleanups.push(workbookManagerCleanup);
3352
+ }
3353
+ let userCleanup;
3354
+ if (passedSelectionEffects) {
3355
+ userCleanup = passedSelectionEffects(spreadsheetSelectionManager);
3356
+ }
3357
+ return () => {
3358
+ cleanups.forEach((cleanup) => cleanup());
3359
+ if (userCleanup) {
3360
+ userCleanup();
3361
+ }
3362
+ };
3363
+ }, [
3364
+ clipboardManager,
3365
+ engine,
3366
+ passedSelectionEffects,
3367
+ selectionManager,
3368
+ sheetName,
3369
+ showFormulas,
3370
+ workbookName
3371
+ ]);
3372
+ if (!sheet) {
3373
+ return /* @__PURE__ */ jsxs3("div", {
3374
+ className: "rsp-root rsp-workbook",
3375
+ children: [
3376
+ toolbar,
3377
+ /* @__PURE__ */ jsx4("div", {
3378
+ style: { flex: 1 }
3379
+ })
3380
+ ]
3381
+ });
3382
+ }
3383
+ return /* @__PURE__ */ jsxs3("div", {
3384
+ className: "rsp-root rsp-workbook",
3385
+ children: [
3386
+ toolbar,
3387
+ /* @__PURE__ */ jsx4("div", {
3388
+ className: "rsp-workbook-body",
3389
+ children: /* @__PURE__ */ jsx4(Spreadsheet, {
3390
+ disableClipboard: true,
3391
+ style: { height: "100%", width: "100%" },
3392
+ cellData: sheet?.content,
3393
+ onCellDataChange,
3394
+ onCellDataChangeError,
3395
+ ...rest,
3396
+ components,
3397
+ overlayChildren,
3398
+ selectedOverlayId,
3399
+ onOverlaySelect,
3400
+ onOverlayChildrenChange,
3401
+ overlayPlaceholder,
3402
+ parentSelected: isSelected,
3403
+ selection: {
3404
+ ...selection,
3405
+ effects: selectionManagerEffects
3406
+ },
3407
+ containerProps: isSelected && selectedArea ? {
3408
+ "data-grid-no-interaction": "true"
3409
+ } : {},
3410
+ customCellStyle: (cell) => {
3411
+ const conditionalStyle = engine.getCellStyle({
3412
+ sheetName,
3413
+ colIndex: cell.colIndex,
3414
+ rowIndex: cell.rowIndex,
3415
+ workbookName
3416
+ });
3417
+ const tableInfo = engine.isCellInTable({
3418
+ sheetName,
3419
+ colIndex: cell.colIndex,
3420
+ rowIndex: cell.rowIndex,
3421
+ workbookName
3422
+ });
3423
+ if (!tableInfo) {
3424
+ if (conditionalStyle) {
3425
+ return {
3426
+ ...conditionalStyle.backgroundColor && {
3427
+ backgroundColor: conditionalStyle.backgroundColor
3428
+ },
3429
+ ...conditionalStyle.color && {
3430
+ color: conditionalStyle.color
3431
+ },
3432
+ ...conditionalStyle.fontSize && {
3433
+ fontSize: conditionalStyle.fontSize
3434
+ },
3435
+ ...conditionalStyle.bold && {
3436
+ fontWeight: "bold"
3437
+ },
3438
+ ...conditionalStyle.italic && {
3439
+ fontStyle: "italic"
3440
+ },
3441
+ ...conditionalStyle.underline && {
3442
+ textDecoration: "underline"
3443
+ },
3444
+ ...getBorderCellStyle(conditionalStyle),
3445
+ ...getWrapCellStyle(conditionalStyle)
3446
+ };
3447
+ }
3448
+ return {};
3449
+ }
3450
+ const isHeaderRow = cell.rowIndex === tableInfo.start.rowIndex;
3451
+ const isFirstColumn = cell.colIndex === tableInfo.start.colIndex;
3452
+ const isLastColumn = cell.colIndex === tableInfo.start.colIndex + tableInfo.headers.size - 1;
3453
+ const isLastRow = tableInfo.endRow.type === "number" ? cell.rowIndex === tableInfo.endRow.value : false;
3454
+ const style = {
3455
+ border: "1px solid var(--table-border-light)"
3456
+ };
3457
+ if (isHeaderRow) {
3458
+ style.backgroundColor = "var(--table-header-bg)";
3459
+ style.color = "var(--table-header-color)";
3460
+ style.fontWeight = "bold";
3461
+ style.borderBottom = "2px solid var(--table-header-border)";
3462
+ } else {
3463
+ const dataRowIndex = cell.rowIndex - tableInfo.start.rowIndex - 1;
3464
+ if (dataRowIndex % 2 === 0) {
3465
+ style.backgroundColor = "var(--table-even-row-bg)";
3466
+ } else {
3467
+ style.backgroundColor = "var(--table-odd-row-bg)";
3468
+ }
3469
+ }
3470
+ if (isFirstColumn) {
3471
+ style.borderLeft = "2px solid var(--table-border-color)";
3472
+ }
3473
+ if (isLastColumn) {
3474
+ style.borderRight = "2px solid var(--table-border-color)";
3475
+ }
3476
+ if (isHeaderRow) {
3477
+ style.borderTop = "2px solid var(--table-border-color)";
3478
+ }
3479
+ if (isLastRow) {
3480
+ style.borderBottom = "2px solid var(--table-border-color)";
3481
+ }
3482
+ if (conditionalStyle?.backgroundColor) {
3483
+ style.backgroundColor = conditionalStyle.backgroundColor;
3484
+ }
3485
+ if (conditionalStyle?.color) {
3486
+ style.color = conditionalStyle.color;
3487
+ }
3488
+ if (conditionalStyle?.fontSize) {
3489
+ style.fontSize = conditionalStyle.fontSize;
3490
+ }
3491
+ if (conditionalStyle?.bold) {
3492
+ style.fontWeight = "bold";
3493
+ }
3494
+ if (conditionalStyle?.italic) {
3495
+ style.fontStyle = "italic";
3496
+ }
3497
+ if (conditionalStyle?.underline) {
3498
+ style.textDecoration = "underline";
3499
+ }
3500
+ Object.assign(style, getBorderCellStyle(conditionalStyle));
3501
+ Object.assign(style, getWrapCellStyle(conditionalStyle));
3502
+ if (customCellStyle) {
3503
+ return customCellStyle(cell, style);
3504
+ }
3505
+ return style;
3506
+ },
3507
+ customCellRenderer: (cell) => {
3508
+ const cellRef = getCellReference3({
3509
+ colIndex: cell.colIndex,
3510
+ rowIndex: cell.rowIndex
3511
+ });
3512
+ const value = showFormulas ? sheet?.content.get(cellRef) : engine.getCellValue({
3513
+ sheetName,
3514
+ workbookName,
3515
+ colIndex: cell.colIndex,
3516
+ rowIndex: cell.rowIndex
3517
+ }, verboseErrors);
3518
+ const conditionalStyle = engine.getCellStyle({
3519
+ sheetName,
3520
+ colIndex: cell.colIndex,
3521
+ rowIndex: cell.rowIndex,
3522
+ workbookName
3523
+ });
3524
+ const conditionalStyleProps = {};
3525
+ if (conditionalStyle?.backgroundColor) {
3526
+ conditionalStyleProps.backgroundColor = conditionalStyle.backgroundColor;
3527
+ }
3528
+ if (conditionalStyle?.color) {
3529
+ conditionalStyleProps.color = conditionalStyle.color;
3530
+ }
3531
+ if (conditionalStyle?.fontSize) {
3532
+ conditionalStyleProps.fontSize = conditionalStyle.fontSize;
3533
+ }
3534
+ if (conditionalStyle?.bold) {
3535
+ conditionalStyleProps.fontWeight = "bold";
3536
+ }
3537
+ if (conditionalStyle?.italic) {
3538
+ conditionalStyleProps.fontStyle = "italic";
3539
+ }
3540
+ if (conditionalStyle?.underline) {
3541
+ conditionalStyleProps.textDecoration = "underline";
3542
+ }
3543
+ Object.assign(conditionalStyleProps, getWrapContentStyle(conditionalStyle));
3544
+ let defaultElement;
3545
+ if (showFormulas) {
3546
+ defaultElement = /* @__PURE__ */ jsx4("div", {
3547
+ "data-conditional-background": conditionalStyle?.backgroundColor,
3548
+ "data-conditional-text-color": conditionalStyle?.color,
3549
+ style: conditionalStyleProps,
3550
+ children: getCellDisplayText(value)
3551
+ });
3552
+ } else if (typeof value === "boolean") {
3553
+ defaultElement = /* @__PURE__ */ jsx4("div", {
3554
+ "data-conditional-background": conditionalStyle?.backgroundColor,
3555
+ "data-conditional-text-color": conditionalStyle?.color,
3556
+ "data-type": "boolean",
3557
+ style: {
3558
+ ...conditionalStyleProps,
3559
+ width: "100%",
3560
+ textAlign: "center",
3561
+ fontFamily: "'Courier New', monospace"
3562
+ },
3563
+ children: value ? "TRUE" : "FALSE"
3564
+ });
3565
+ } else if (typeof value === "number") {
3566
+ defaultElement = /* @__PURE__ */ jsx4("div", {
3567
+ "data-conditional-background": conditionalStyle?.backgroundColor,
3568
+ "data-conditional-text-color": conditionalStyle?.color,
3569
+ "data-type": "number",
3570
+ style: { ...conditionalStyleProps, width: "100%", textAlign: "right" },
3571
+ children: value.toLocaleString("en-US", {
3572
+ minimumFractionDigits: 0,
3573
+ maximumFractionDigits: 10
3574
+ })
3575
+ });
3576
+ } else {
3577
+ defaultElement = /* @__PURE__ */ jsx4("div", {
3578
+ "data-conditional-background": conditionalStyle?.backgroundColor,
3579
+ "data-conditional-text-color": conditionalStyle?.color,
3580
+ style: { ...conditionalStyleProps, width: "100%", textAlign: "left" },
3581
+ children: getCellDisplayText(value)
3582
+ });
3583
+ }
3584
+ if (customCellRenderer) {
3585
+ return customCellRenderer(cell, defaultElement);
3586
+ }
3587
+ return defaultElement;
3588
+ }
3589
+ })
3590
+ })
3591
+ ]
3592
+ });
3593
+ }
3594
+ function FormulaWorkbook({
3595
+ clipboardManager: controlledClipboardManager,
3596
+ workbookName,
3597
+ engine,
3598
+ activeSheet: controlledActiveSheet,
3599
+ onActiveSheetChange,
3600
+ verboseErrors = false,
3601
+ selectionManager,
3602
+ components,
3603
+ sheetOverlayChildren,
3604
+ sheetSelectedOverlayIds,
3605
+ sheetLayouts,
3606
+ onSheetColumnWidthsChange,
3607
+ onSheetRowHeightsChange,
3608
+ onOverlaySelect,
3609
+ onOverlayChildrenChange,
3610
+ overlayPlaceholder,
3611
+ isSelected = false,
3612
+ toolbar,
3613
+ showFormulas = false,
3614
+ ...rest
3615
+ }) {
3616
+ const state = useEngine(engine);
3617
+ const [renamingSheet, setRenamingSheet] = useState5(null);
3618
+ const [newSheetName, setNewSheetName] = useState5("");
3619
+ const [spreadsheetZoom, setSpreadsheetZoom] = useState5(1);
3620
+ const renameCommittedRef = useRef4(false);
3621
+ const clipboardManager = React3.useMemo(() => controlledClipboardManager ?? new WorkbookClipboardManager(engine), [controlledClipboardManager, engine]);
3622
+ const sheetNames = engine.getOrderedSheetNames(workbookName);
3623
+ const [internalActiveSheet, setInternalActiveSheet] = useState5(() => {
3624
+ return sheetNames[0] || "";
3625
+ });
3626
+ const isControlledRef = useRef4(controlledActiveSheet !== undefined);
3627
+ React3.useEffect(() => {
3628
+ const isCurrentlyControlled = controlledActiveSheet !== undefined;
3629
+ if (isControlledRef.current !== isCurrentlyControlled) {
3630
+ console.error(`FormulaWorkbook: Component changed from ${isControlledRef.current ? "controlled" : "uncontrolled"} to ${isCurrentlyControlled ? "controlled" : "uncontrolled"} mode. ` + `A component should not switch between controlled and uncontrolled modes. ` + `Decide between using activeSheet and onActiveSheetChange props (controlled) or neither (uncontrolled) for the lifetime of the component.`);
3631
+ }
3632
+ isControlledRef.current = isCurrentlyControlled;
3633
+ }, [controlledActiveSheet]);
3634
+ const activeSheet = controlledActiveSheet ?? internalActiveSheet;
3635
+ const handleActiveSheetChange = useCallback4((sheetName) => {
3636
+ if (onActiveSheetChange) {
3637
+ onActiveSheetChange(sheetName);
3638
+ } else {
3639
+ setInternalActiveSheet(sheetName);
3640
+ }
3641
+ }, [onActiveSheetChange]);
3642
+ const activeSheetOverlayChildren = sheetOverlayChildren?.[activeSheet];
3643
+ const activeSheetSelectedOverlayId = sheetSelectedOverlayIds?.[activeSheet];
3644
+ const activeSheetColumnWidths = sheetLayouts?.[activeSheet]?.columnWidths;
3645
+ const activeSheetRowHeights = sheetLayouts?.[activeSheet]?.rowHeights;
3646
+ const handleOverlaySelect = useCallback4((overlayId) => {
3647
+ onOverlaySelect?.(activeSheet, overlayId);
3648
+ }, [onOverlaySelect, activeSheet]);
3649
+ const handleOverlayChildrenChange = useCallback4((children) => {
3650
+ onOverlayChildrenChange?.(activeSheet, children);
3651
+ }, [onOverlayChildrenChange, activeSheet]);
3652
+ const handleColumnWidthsChange = useCallback4((columnWidths) => {
3653
+ onSheetColumnWidthsChange?.(activeSheet, columnWidths);
3654
+ }, [activeSheet, onSheetColumnWidthsChange]);
3655
+ const handleRowHeightsChange = useCallback4((rowHeights) => {
3656
+ onSheetRowHeightsChange?.(activeSheet, rowHeights);
3657
+ }, [activeSheet, onSheetRowHeightsChange]);
3658
+ const addSheet = () => {
3659
+ try {
3660
+ const newSheet = engine.createSheet({
3661
+ workbookName
3662
+ });
3663
+ handleActiveSheetChange(newSheet.name);
3664
+ } catch (error) {
3665
+ console.error("Failed to add sheet:", error);
3666
+ }
3667
+ };
3668
+ const startRenaming = (sheetName) => {
3669
+ renameCommittedRef.current = false;
3670
+ setRenamingSheet(sheetName);
3671
+ setNewSheetName(sheetName);
3672
+ };
3673
+ const cancelRenaming = () => {
3674
+ setRenamingSheet(null);
3675
+ setNewSheetName("");
3676
+ };
3677
+ const renameSheet = (oldName, newName) => {
3678
+ if (renameCommittedRef.current) {
3679
+ cancelRenaming();
3680
+ return;
3681
+ }
3682
+ if (!newName.trim() || newName.trim() === oldName) {
3683
+ cancelRenaming();
3684
+ return;
3685
+ }
3686
+ try {
3687
+ renameCommittedRef.current = true;
3688
+ engine.renameSheet({
3689
+ workbookName,
3690
+ sheetName: oldName,
3691
+ newSheetName: newName.trim()
3692
+ });
3693
+ if (activeSheet === oldName) {
3694
+ handleActiveSheetChange(newName.trim());
3695
+ }
3696
+ cancelRenaming();
3697
+ } catch (error) {
3698
+ console.error("Failed to rename sheet:", error);
3699
+ cancelRenaming();
3700
+ }
3701
+ };
3702
+ const deleteSheet = (sheetName) => {
3703
+ if (sheetNames.length <= 1) {
3704
+ console.error("Cannot delete the last sheet");
3705
+ return;
3706
+ }
3707
+ try {
3708
+ engine.removeSheet({ workbookName, sheetName });
3709
+ if (activeSheet === sheetName) {
3710
+ const remainingSheets = sheetNames.filter((name) => name !== sheetName);
3711
+ const nextSheet = remainingSheets[0];
3712
+ if (nextSheet) {
3713
+ handleActiveSheetChange(nextSheet);
3714
+ }
3715
+ }
3716
+ } catch (error) {
3717
+ console.error("Failed to delete sheet:", error);
3718
+ }
3719
+ };
3720
+ return /* @__PURE__ */ jsxs3("div", {
3721
+ className: "rsp-root rsp-workbook",
3722
+ children: [
3723
+ toolbar,
3724
+ /* @__PURE__ */ jsxs3("div", {
3725
+ className: "rsp-workbook-header",
3726
+ children: [
3727
+ /* @__PURE__ */ jsx4("div", {
3728
+ style: {
3729
+ display: "flex",
3730
+ alignItems: "center",
3731
+ gap: 8,
3732
+ flex: 1,
3733
+ overflow: "hidden",
3734
+ textOverflow: "ellipsis"
3735
+ },
3736
+ children: /* @__PURE__ */ jsx4("span", {
3737
+ className: "rsp-workbook-name",
3738
+ "data-testid": `workbook-name-${workbookName}`,
3739
+ children: workbookName
3740
+ })
3741
+ }),
3742
+ /* @__PURE__ */ jsxs3("span", {
3743
+ className: "rsp-zoom-value",
3744
+ "data-testid": `sheet-count-${workbookName}`,
3745
+ children: [
3746
+ "Sheets: ",
3747
+ sheetNames.length
3748
+ ]
3749
+ })
3750
+ ]
3751
+ }),
3752
+ /* @__PURE__ */ jsx4("div", {
3753
+ style: { flex: 1, overflow: "hidden" },
3754
+ children: /* @__PURE__ */ jsx4("div", {
3755
+ style: {
3756
+ transform: spreadsheetZoom !== 1 ? `scale(${spreadsheetZoom})` : undefined,
3757
+ transformOrigin: "0 0",
3758
+ width: spreadsheetZoom !== 1 ? `${100 / spreadsheetZoom}%` : "100%",
3759
+ height: spreadsheetZoom !== 1 ? `${100 / spreadsheetZoom}%` : "100%"
3760
+ },
3761
+ children: /* @__PURE__ */ jsx4(FormulaSheet, {
3762
+ sheetName: activeSheet,
3763
+ workbookName,
3764
+ engine,
3765
+ verboseErrors,
3766
+ selectionManager,
3767
+ ...rest,
3768
+ components,
3769
+ overlayChildren: activeSheetOverlayChildren,
3770
+ selectedOverlayId: activeSheetSelectedOverlayId,
3771
+ columnWidths: activeSheetColumnWidths,
3772
+ onColumnWidthsChange: handleColumnWidthsChange,
3773
+ rowHeights: activeSheetRowHeights,
3774
+ onRowHeightsChange: handleRowHeightsChange,
3775
+ onOverlaySelect: handleOverlaySelect,
3776
+ onOverlayChildrenChange: handleOverlayChildrenChange,
3777
+ overlayPlaceholder,
3778
+ clipboardManager,
3779
+ isSelected,
3780
+ showFormulas
3781
+ }, `${workbookName}-${activeSheet}`)
3782
+ })
3783
+ }),
3784
+ /* @__PURE__ */ jsxs3("div", {
3785
+ className: "rsp-sheet-tabs",
3786
+ children: [
3787
+ /* @__PURE__ */ jsxs3("div", {
3788
+ className: "rsp-sheet-tabs-scroll",
3789
+ children: [
3790
+ /* @__PURE__ */ jsx4("div", {
3791
+ style: { display: "flex", alignItems: "center", gap: 4 },
3792
+ children: sheetNames.map((sheetName) => {
3793
+ const isRenaming = renamingSheet === sheetName;
3794
+ const isActive = activeSheet === sheetName;
3795
+ return /* @__PURE__ */ jsx4("div", {
3796
+ children: isRenaming ? /* @__PURE__ */ jsx4("div", {
3797
+ style: { display: "flex", alignItems: "center", gap: 4 },
3798
+ children: /* @__PURE__ */ jsx4("input", {
3799
+ className: "rsp-sheet-tab-input",
3800
+ type: "text",
3801
+ value: newSheetName,
3802
+ onChange: (e) => setNewSheetName(e.target.value),
3803
+ onKeyDown: (e) => {
3804
+ if (e.key === "Enter") {
3805
+ renameSheet(sheetName, newSheetName);
3806
+ } else if (e.key === "Escape") {
3807
+ cancelRenaming();
3808
+ }
3809
+ },
3810
+ onBlur: () => {
3811
+ if (newSheetName.trim() && newSheetName.trim() !== sheetName) {
3812
+ renameSheet(sheetName, newSheetName);
3813
+ } else {
3814
+ cancelRenaming();
3815
+ }
3816
+ },
3817
+ autoFocus: true,
3818
+ "data-testid": `rename-sheet-input-${sheetName}`
3819
+ })
3820
+ }) : /* @__PURE__ */ jsxs3("div", {
3821
+ className: cn("rsp-sheet-tab", {
3822
+ "rsp-sheet-tab-active": isActive
3823
+ }),
3824
+ onClick: () => handleActiveSheetChange(sheetName),
3825
+ onDoubleClick: () => startRenaming(sheetName),
3826
+ "data-testid": `sheet-tab-${sheetName}`,
3827
+ children: [
3828
+ /* @__PURE__ */ jsx4("span", {
3829
+ children: sheetName
3830
+ }),
3831
+ /* @__PURE__ */ jsxs3("div", {
3832
+ className: "sheet-tab-actions",
3833
+ children: [
3834
+ /* @__PURE__ */ jsx4("button", {
3835
+ type: "button",
3836
+ className: "rsp-icon-button",
3837
+ onClick: (e) => {
3838
+ e.stopPropagation();
3839
+ startRenaming(sheetName);
3840
+ },
3841
+ title: "Rename Sheet",
3842
+ "data-testid": `rename-sheet-${sheetName}`,
3843
+ children: /* @__PURE__ */ jsx4(Edit2Icon, {
3844
+ style: { width: 12, height: 12 }
3845
+ })
3846
+ }),
3847
+ sheetNames.length > 1 && /* @__PURE__ */ jsx4("button", {
3848
+ type: "button",
3849
+ className: "rsp-icon-button rsp-icon-button-delete",
3850
+ onClick: (e) => {
3851
+ e.stopPropagation();
3852
+ if (confirm(`Are you sure you want to delete sheet "${sheetName}"?`)) {
3853
+ deleteSheet(sheetName);
3854
+ }
3855
+ },
3856
+ title: "Delete Sheet",
3857
+ "data-testid": `delete-sheet-${sheetName}`,
3858
+ children: /* @__PURE__ */ jsx4(Trash2Icon, {
3859
+ style: { width: 12, height: 12 }
3860
+ })
3861
+ })
3862
+ ]
3863
+ })
3864
+ ]
3865
+ })
3866
+ }, sheetName);
3867
+ })
3868
+ }),
3869
+ /* @__PURE__ */ jsx4("button", {
3870
+ type: "button",
3871
+ className: "rsp-add-sheet-button",
3872
+ onClick: addSheet,
3873
+ title: "Add Sheet",
3874
+ "data-testid": `add-sheet-${workbookName}`,
3875
+ children: /* @__PURE__ */ jsx4(PlusIcon, {
3876
+ style: { width: 12, height: 12 }
3877
+ })
3878
+ })
3879
+ ]
3880
+ }),
3881
+ /* @__PURE__ */ jsxs3("div", {
3882
+ className: "rsp-zoom-controls",
3883
+ children: [
3884
+ /* @__PURE__ */ jsx4("button", {
3885
+ type: "button",
3886
+ className: "rsp-icon-button",
3887
+ onClick: () => setSpreadsheetZoom(Math.max(0.5, spreadsheetZoom - 0.1)),
3888
+ disabled: spreadsheetZoom <= 0.5,
3889
+ title: "Zoom Out",
3890
+ style: { opacity: spreadsheetZoom <= 0.5 ? 0.5 : 1 },
3891
+ children: /* @__PURE__ */ jsx4(MinusIcon, {
3892
+ style: { width: 12, height: 12 }
3893
+ })
3894
+ }),
3895
+ /* @__PURE__ */ jsxs3("span", {
3896
+ className: "rsp-zoom-value",
3897
+ children: [
3898
+ Math.round(spreadsheetZoom * 100),
3899
+ "%"
3900
+ ]
3901
+ }),
3902
+ /* @__PURE__ */ jsx4("button", {
3903
+ type: "button",
3904
+ className: "rsp-icon-button",
3905
+ onClick: () => setSpreadsheetZoom(Math.min(2, spreadsheetZoom + 0.1)),
3906
+ disabled: spreadsheetZoom >= 2,
3907
+ title: "Zoom In",
3908
+ style: { opacity: spreadsheetZoom >= 2 ? 0.5 : 1 },
3909
+ children: /* @__PURE__ */ jsx4(PlusIcon, {
3910
+ style: { width: 12, height: 12 }
3911
+ })
3912
+ })
3913
+ ]
3914
+ })
3915
+ ]
3916
+ })
3917
+ ]
3918
+ });
3919
+ }
3920
+ // src/spreadsheet/zoom-constants.ts
3921
+ var ZOOM_FACTOR = 0.05;
3922
+ var CONTROLLED_ZOOM_FACTOR = 0.01;
3923
+ var DEFAULT_ZOOM = 1;
3924
+ var MIN_ZOOM = 0.5;
3925
+ var MAX_ZOOM = 3;
3926
+ // src/utils/use-click-away.ts
3927
+ import { useEffect as useEffect4 } from "react";
3928
+ var useClickAway = (ref, callback) => {
3929
+ useEffect4(() => {
3930
+ const handleClickAway = (event) => {
3931
+ if (ref) {
3932
+ const away = !ref.contains(event.target);
3933
+ callback(away);
3934
+ }
3935
+ };
3936
+ document.addEventListener("mousedown", handleClickAway);
3937
+ return () => {
3938
+ document.removeEventListener("mousedown", handleClickAway);
3939
+ };
3940
+ }, [ref, callback]);
3941
+ };
3942
+ export {
3943
+ CONTROLLED_ZOOM_FACTOR,
3944
+ ClipboardUtils,
3945
+ DEFAULT_CELL_HEIGHT,
3946
+ DEFAULT_CELL_WIDTH,
3947
+ DEFAULT_ZOOM,
3948
+ FormulaSheet,
3949
+ FormulaWorkbook,
3950
+ HEADER_HEIGHT,
3951
+ HEADER_WIDTH,
3952
+ MAX_ZOOM,
3953
+ MIN_ZOOM,
3954
+ Spreadsheet,
3955
+ ViewportStream,
3956
+ WorkbookClipboardManager,
3957
+ WorkbookSelectionManager,
3958
+ ZOOM_FACTOR,
3959
+ cn,
3960
+ coerceCellInput,
3961
+ columnToIndex,
3962
+ extractColumn,
3963
+ extractColumnByHeader,
3964
+ findCell,
3965
+ getCellDisplayText,
3966
+ getCellDisplayValue,
3967
+ getCellReference,
3968
+ getCellSnapAnchorFromRect,
3969
+ getColumnEdgePosition,
3970
+ getNearestColumnEdgeIndex,
3971
+ getNearestRowEdgeIndex,
3972
+ getRectFromCellSnapAnchor,
3973
+ getRowEdgePosition,
3974
+ getRowNumber,
3975
+ hasAnyBorderSide,
3976
+ indexToColumn,
3977
+ letterToRow,
3978
+ moveCellSnapAnchorToNearestOrigin,
3979
+ normalizeBorderSides,
3980
+ normalizeCellSnapAnchor,
3981
+ onCellEditError,
3982
+ parseCellReference,
3983
+ queueCellEditError,
3984
+ resizeCellSnapAnchorToNearestEdges,
3985
+ rowToLetter,
3986
+ useClickAway,
3987
+ useColorScheme,
3988
+ useResizeObserver
3989
+ };
3990
+
3991
+ //# debugId=FCE294496834A26064756E2164756E21