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