react-os-shell 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +242 -0
  3. package/dist/Calculator-BNBRNV4P.js +184 -0
  4. package/dist/Calculator-BNBRNV4P.js.map +1 -0
  5. package/dist/Calendar-5EYUVGUU.js +423 -0
  6. package/dist/Calendar-5EYUVGUU.js.map +1 -0
  7. package/dist/Checkers-MIAHIKJH.js +214 -0
  8. package/dist/Checkers-MIAHIKJH.js.map +1 -0
  9. package/dist/Chess-C5BY45NA.js +190 -0
  10. package/dist/Chess-C5BY45NA.js.map +1 -0
  11. package/dist/ConfirmDialog-ZP4AHVUD.js +3 -0
  12. package/dist/ConfirmDialog-ZP4AHVUD.js.map +1 -0
  13. package/dist/CurrencyConverter-TYPU2IRF.js +223 -0
  14. package/dist/CurrencyConverter-TYPU2IRF.js.map +1 -0
  15. package/dist/Email-JEYYJ3YV.js +1835 -0
  16. package/dist/Email-JEYYJ3YV.js.map +1 -0
  17. package/dist/Game2048-3RH3ELRD.js +191 -0
  18. package/dist/Game2048-3RH3ELRD.js.map +1 -0
  19. package/dist/GeminiChat-BXLBJFT4.js +184 -0
  20. package/dist/GeminiChat-BXLBJFT4.js.map +1 -0
  21. package/dist/Minesweeper-VQGLAZON.js +270 -0
  22. package/dist/Minesweeper-VQGLAZON.js.map +1 -0
  23. package/dist/Notepad-YTZRCAXX.js +389 -0
  24. package/dist/Notepad-YTZRCAXX.js.map +1 -0
  25. package/dist/PomodoroTimer-HARIJN4S.js +196 -0
  26. package/dist/PomodoroTimer-HARIJN4S.js.map +1 -0
  27. package/dist/Spreadsheet-IOKEDNS6.js +446 -0
  28. package/dist/Spreadsheet-IOKEDNS6.js.map +1 -0
  29. package/dist/Sudoku-XHLYCEVT.js +197 -0
  30. package/dist/Sudoku-XHLYCEVT.js.map +1 -0
  31. package/dist/Tetris-ZHCZYL24.js +243 -0
  32. package/dist/Tetris-ZHCZYL24.js.map +1 -0
  33. package/dist/Weather-ROZ7TRNW.js +310 -0
  34. package/dist/Weather-ROZ7TRNW.js.map +1 -0
  35. package/dist/apps/index.d.ts +55 -0
  36. package/dist/apps/index.js +48 -0
  37. package/dist/apps/index.js.map +1 -0
  38. package/dist/chunk-5O2KEISQ.js +155 -0
  39. package/dist/chunk-5O2KEISQ.js.map +1 -0
  40. package/dist/chunk-D7PYW2QS.js +265 -0
  41. package/dist/chunk-D7PYW2QS.js.map +1 -0
  42. package/dist/chunk-GP4Y3VCB.js +806 -0
  43. package/dist/chunk-GP4Y3VCB.js.map +1 -0
  44. package/dist/chunk-NSU7OHPC.js +39 -0
  45. package/dist/chunk-NSU7OHPC.js.map +1 -0
  46. package/dist/chunk-PDFQNHW7.js +24 -0
  47. package/dist/chunk-PDFQNHW7.js.map +1 -0
  48. package/dist/chunk-RFTLYCSF.js +144 -0
  49. package/dist/chunk-RFTLYCSF.js.map +1 -0
  50. package/dist/chunk-SVBID2P6.js +142 -0
  51. package/dist/chunk-SVBID2P6.js.map +1 -0
  52. package/dist/chunk-TFGOLXGD.js +41 -0
  53. package/dist/chunk-TFGOLXGD.js.map +1 -0
  54. package/dist/chunk-WIJ45SYD.js +120 -0
  55. package/dist/chunk-WIJ45SYD.js.map +1 -0
  56. package/dist/chunk-WQIS72NL.js +1470 -0
  57. package/dist/chunk-WQIS72NL.js.map +1 -0
  58. package/dist/index.d.ts +642 -0
  59. package/dist/index.js +3443 -0
  60. package/dist/index.js.map +1 -0
  61. package/dist/sounds-NT4DEZGD.js +3 -0
  62. package/dist/sounds-NT4DEZGD.js.map +1 -0
  63. package/dist/styles.css +174 -0
  64. package/dist/types-CFIZ1_xt.d.ts +67 -0
  65. package/package.json +76 -0
@@ -0,0 +1,806 @@
1
+ import { useRef, useState, useEffect, useCallback, useMemo } from 'react';
2
+ import { createPortal } from 'react-dom';
3
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
4
+
5
+ // src/shell/EditableGrid.tsx
6
+ function rangeContains(anchor, end, row, col) {
7
+ const r1 = Math.min(anchor.row, end.row), r2 = Math.max(anchor.row, end.row);
8
+ const c1 = Math.min(anchor.col, end.col), c2 = Math.max(anchor.col, end.col);
9
+ return row >= r1 && row <= r2 && col >= c1 && col <= c2;
10
+ }
11
+ function EditableGrid({ columns, data, onChange, onColumnsChange, fixedRows = false, minRows = 15, maxHeight = "260px", cellStyles, onFocusChange, onSelectionChange }) {
12
+ const tableRef = useRef(null);
13
+ const [focus, setFocus] = useState(null);
14
+ useEffect(() => {
15
+ onFocusChange?.(focus);
16
+ }, [focus, onFocusChange]);
17
+ const [colWidths, setColWidths] = useState({});
18
+ const resizing = useRef(null);
19
+ const [rowHeights, setRowHeights] = useState({});
20
+ const rowResizing = useRef(null);
21
+ const [dragRow, setDragRow] = useState(null);
22
+ const [dragOverRow, setDragOverRow] = useState(null);
23
+ const [dragCol, setDragCol] = useState(null);
24
+ const [dragOverCol, setDragOverCol] = useState(null);
25
+ const [editingCell, setEditingCell] = useState(null);
26
+ const [selAnchor, setSelAnchor] = useState(null);
27
+ const [selEnd, setSelEnd] = useState(null);
28
+ useEffect(() => {
29
+ onSelectionChange?.(selAnchor && selEnd ? { anchor: selAnchor, end: selEnd } : null);
30
+ }, [selAnchor, selEnd, onSelectionChange]);
31
+ const dragging = useRef(false);
32
+ const [fillTarget, setFillTarget] = useState(null);
33
+ const filling = useRef(false);
34
+ const selRefForFill = useRef({ a: null, e: null });
35
+ selRefForFill.current = { a: selAnchor, e: selEnd };
36
+ const hasRange = selAnchor && selEnd && (selAnchor.row !== selEnd.row || selAnchor.col !== selEnd.col);
37
+ const rows = [...data];
38
+ if (!fixedRows) {
39
+ while (rows.length < minRows) rows.push(Array(columns.length).fill(""));
40
+ }
41
+ const updateCell = useCallback((row, col, value) => {
42
+ const next = rows.map((r) => [...r]);
43
+ while (next.length <= row) next.push(Array(columns.length).fill(""));
44
+ while (next[row].length < columns.length) next[row].push("");
45
+ next[row][col] = value;
46
+ onChange(next);
47
+ }, [rows, columns.length, onChange]);
48
+ const handleMouseDown = useCallback((e, row, col) => {
49
+ if (e.button !== 0) return;
50
+ const active = document.activeElement;
51
+ if (active?.dataset?.row && active?.dataset?.col) {
52
+ const ar = parseInt(active.dataset.row);
53
+ const ac = parseInt(active.dataset.col);
54
+ const val = active.textContent || "";
55
+ if (val !== (rows[ar]?.[ac] || "")) {
56
+ const next = rows.map((r) => [...r]);
57
+ if (next[ar]) {
58
+ next[ar][ac] = val;
59
+ onChange(next);
60
+ }
61
+ }
62
+ }
63
+ dragging.current = true;
64
+ if (e.shiftKey && selAnchor) {
65
+ setSelEnd({ row, col });
66
+ } else {
67
+ setSelAnchor({ row, col });
68
+ setSelEnd({ row, col });
69
+ }
70
+ }, [rows, onChange, selAnchor]);
71
+ const handleMouseEnter = useCallback((row, col) => {
72
+ if (dragging.current) {
73
+ setSelEnd({ row, col });
74
+ }
75
+ }, []);
76
+ useEffect(() => {
77
+ const handleMouseUp = () => {
78
+ dragging.current = false;
79
+ };
80
+ window.addEventListener("mouseup", handleMouseUp);
81
+ return () => window.removeEventListener("mouseup", handleMouseUp);
82
+ }, []);
83
+ useEffect(() => {
84
+ const handler = (e) => {
85
+ if (!(e.ctrlKey || e.metaKey) || e.key !== "c") return;
86
+ if (!selAnchor || !selEnd) return;
87
+ const nativeSel = window.getSelection();
88
+ if (nativeSel && nativeSel.toString().length > 0 && !hasRange) return;
89
+ const r1 = Math.min(selAnchor.row, selEnd.row), r2 = Math.max(selAnchor.row, selEnd.row);
90
+ const c1 = Math.min(selAnchor.col, selEnd.col), c2 = Math.max(selAnchor.col, selEnd.col);
91
+ const text = [];
92
+ for (let r = r1; r <= r2; r++) {
93
+ const rowCells = [];
94
+ for (let c = c1; c <= c2; c++) {
95
+ rowCells.push(rows[r]?.[c] || "");
96
+ }
97
+ text.push(rowCells.join(" "));
98
+ }
99
+ const tsv = text.join("\n");
100
+ if (tsv) {
101
+ e.preventDefault();
102
+ navigator.clipboard.writeText(tsv);
103
+ }
104
+ };
105
+ window.addEventListener("keydown", handler);
106
+ return () => window.removeEventListener("keydown", handler);
107
+ }, [selAnchor, selEnd, rows, hasRange]);
108
+ useEffect(() => {
109
+ const handler = (e) => {
110
+ if (!focus) return;
111
+ const active = document.activeElement;
112
+ if (active?.isContentEditable) return;
113
+ if (!tableRef.current?.contains(active)) return;
114
+ const text = e.clipboardData?.getData("text/plain");
115
+ if (!text) return;
116
+ e.preventDefault();
117
+ const pastedRows = text.split("\n").filter((l) => l).map((line) => line.split(" "));
118
+ const next = rows.map((r) => [...r]);
119
+ for (let r = 0; r < pastedRows.length; r++) {
120
+ const targetRow = focus.row + r;
121
+ while (next.length <= targetRow) next.push(Array(columns.length).fill(""));
122
+ while (next[targetRow].length < columns.length) next[targetRow].push("");
123
+ for (let c = 0; c < pastedRows[r].length; c++) {
124
+ const targetCol = focus.col + c;
125
+ if (targetCol >= columns.length) break;
126
+ if (columns[targetCol].readOnly) continue;
127
+ next[targetRow][targetCol] = pastedRows[r][c].trim();
128
+ }
129
+ }
130
+ onChange(next);
131
+ setSelAnchor({ row: focus.row, col: focus.col });
132
+ setSelEnd({ row: Math.min(focus.row + pastedRows.length - 1, next.length - 1), col: Math.min(focus.col + (pastedRows[0]?.length || 1) - 1, columns.length - 1) });
133
+ };
134
+ window.addEventListener("paste", handler);
135
+ return () => window.removeEventListener("paste", handler);
136
+ }, [focus, rows, columns, onChange]);
137
+ const handlePaste = useCallback((e, startRow, startCol) => {
138
+ const text = e.clipboardData.getData("text/plain");
139
+ if (!text) return;
140
+ const pastedRows = text.split("\n").map((line) => line.split(" "));
141
+ if (pastedRows.length <= 1 && pastedRows[0]?.length <= 1) return;
142
+ e.preventDefault();
143
+ const next = rows.map((r) => [...r]);
144
+ for (let r = 0; r < pastedRows.length; r++) {
145
+ const targetRow = startRow + r;
146
+ while (next.length <= targetRow) next.push(Array(columns.length).fill(""));
147
+ while (next[targetRow].length < columns.length) next[targetRow].push("");
148
+ for (let c = 0; c < pastedRows[r].length; c++) {
149
+ const targetCol = startCol + c;
150
+ if (targetCol >= columns.length) break;
151
+ if (columns[targetCol].readOnly) continue;
152
+ next[targetRow][targetCol] = pastedRows[r][c].trim();
153
+ }
154
+ }
155
+ onChange(next);
156
+ }, [rows, columns, onChange]);
157
+ const handleKeyDown = useCallback((e, row, col) => {
158
+ let nextRow = row;
159
+ let nextCol = col;
160
+ if (e.key === "Tab") {
161
+ e.preventDefault();
162
+ nextCol = e.shiftKey ? col - 1 : col + 1;
163
+ if (nextCol >= columns.length) {
164
+ nextCol = 0;
165
+ nextRow = row + 1;
166
+ }
167
+ if (nextCol < 0) {
168
+ nextCol = columns.length - 1;
169
+ nextRow = row - 1;
170
+ }
171
+ } else if (e.key === "Enter") {
172
+ e.preventDefault();
173
+ nextRow = e.shiftKey ? row - 1 : row + 1;
174
+ } else if (e.key === "ArrowDown") {
175
+ e.preventDefault();
176
+ nextRow = row + 1;
177
+ } else if (e.key === "ArrowUp") {
178
+ e.preventDefault();
179
+ nextRow = row - 1;
180
+ } else if (e.key === "ArrowLeft") {
181
+ e.preventDefault();
182
+ nextCol = col - 1;
183
+ } else if (e.key === "ArrowRight") {
184
+ e.preventDefault();
185
+ nextCol = col + 1;
186
+ } else if (e.key === "Delete" || e.key === "Backspace") {
187
+ const target = e.target;
188
+ if (target.isContentEditable) {
189
+ if (target.textContent && window.getSelection()?.toString() === target.textContent) {
190
+ e.preventDefault();
191
+ updateCell(row, col, "");
192
+ }
193
+ return;
194
+ }
195
+ e.preventDefault();
196
+ if (selAnchor && selEnd) {
197
+ const r1 = Math.min(selAnchor.row, selEnd.row), r2 = Math.max(selAnchor.row, selEnd.row);
198
+ const c1 = Math.min(selAnchor.col, selEnd.col), c2 = Math.max(selAnchor.col, selEnd.col);
199
+ const next = rows.map((r) => [...r]);
200
+ for (let r = r1; r <= r2; r++) for (let c = c1; c <= c2; c++) {
201
+ if (next[r] && !columns[c]?.readOnly) next[r][c] = "";
202
+ }
203
+ onChange(next);
204
+ } else {
205
+ updateCell(row, col, "");
206
+ }
207
+ return;
208
+ } else {
209
+ return;
210
+ }
211
+ while (nextCol >= 0 && nextCol < columns.length && columns[nextCol].readOnly) {
212
+ nextCol += e.key === "Tab" && e.shiftKey ? -1 : 1;
213
+ }
214
+ if (nextRow >= 0 && nextRow < rows.length && nextCol >= 0 && nextCol < columns.length) {
215
+ const cell = tableRef.current?.querySelector(`[data-row="${nextRow}"][data-col="${nextCol}"]`);
216
+ if (cell) {
217
+ cell.focus({ preventScroll: true });
218
+ cell.scrollIntoView({ block: "nearest", inline: "nearest" });
219
+ setFocus({ row: nextRow, col: nextCol });
220
+ setEditingCell(null);
221
+ setSelAnchor({ row: nextRow, col: nextCol });
222
+ setSelEnd({ row: nextRow, col: nextCol });
223
+ }
224
+ }
225
+ }, [columns, rows, rows.length, updateCell, selAnchor, selEnd, onChange]);
226
+ const ensureRows = useCallback((row) => {
227
+ if (fixedRows) return;
228
+ if (row >= rows.length - 2) {
229
+ const next = rows.map((r) => [...r]);
230
+ for (let i = 0; i < 5; i++) next.push(Array(columns.length).fill(""));
231
+ onChange(next);
232
+ }
233
+ }, [fixedRows, rows, columns.length, onChange]);
234
+ const [ctxMenu, setCtxMenu] = useState(null);
235
+ useEffect(() => {
236
+ if (!ctxMenu) return;
237
+ const handler = () => setCtxMenu(null);
238
+ window.addEventListener("pointerdown", handler);
239
+ return () => window.removeEventListener("pointerdown", handler);
240
+ }, [ctxMenu]);
241
+ const insertRow = useCallback((at) => {
242
+ const next = rows.map((r) => [...r]);
243
+ next.splice(at, 0, Array(columns.length).fill(""));
244
+ onChange(next);
245
+ }, [rows, columns.length, onChange]);
246
+ const deleteRow = useCallback((at) => {
247
+ if (rows.length <= 1) return;
248
+ const next = rows.filter((_, i) => i !== at);
249
+ onChange(next);
250
+ }, [rows, onChange]);
251
+ const insertCol = useCallback((at) => {
252
+ const next = rows.map((r) => {
253
+ const nr = [...r];
254
+ nr.splice(at, 0, "");
255
+ return nr;
256
+ });
257
+ onChange(next);
258
+ }, [rows, onChange]);
259
+ const deleteCol = useCallback((at) => {
260
+ if (columns.length <= 1) return;
261
+ const next = rows.map((r) => {
262
+ const nr = [...r];
263
+ nr.splice(at, 1);
264
+ return nr;
265
+ });
266
+ onChange(next);
267
+ }, [rows, columns.length, onChange]);
268
+ const handleRowCtx = useCallback((e, ri) => {
269
+ e.preventDefault();
270
+ if (fixedRows) return;
271
+ setCtxMenu({ x: e.clientX, y: e.clientY, items: [
272
+ { label: `Insert row above`, onClick: () => {
273
+ insertRow(ri);
274
+ setCtxMenu(null);
275
+ } },
276
+ { label: `Insert row below`, onClick: () => {
277
+ insertRow(ri + 1);
278
+ setCtxMenu(null);
279
+ } },
280
+ { label: `Delete row ${ri + 1}`, onClick: () => {
281
+ deleteRow(ri);
282
+ setCtxMenu(null);
283
+ } }
284
+ ] });
285
+ }, [fixedRows, insertRow, deleteRow]);
286
+ const handleColCtx = useCallback((e, ci) => {
287
+ e.preventDefault();
288
+ if (fixedRows) return;
289
+ setCtxMenu({ x: e.clientX, y: e.clientY, items: [
290
+ { label: `Insert column left`, onClick: () => {
291
+ insertCol(ci);
292
+ setCtxMenu(null);
293
+ } },
294
+ { label: `Insert column right`, onClick: () => {
295
+ insertCol(ci + 1);
296
+ setCtxMenu(null);
297
+ } },
298
+ { label: `Delete column ${columns[ci]?.title || ci + 1}`, onClick: () => {
299
+ deleteCol(ci);
300
+ setCtxMenu(null);
301
+ } }
302
+ ] });
303
+ }, [fixedRows, columns, insertCol, deleteCol]);
304
+ const getColWidth = (ci) => colWidths[ci] ?? columns[ci]?.width ?? 150;
305
+ useEffect(() => {
306
+ const handleMouseMove = (e) => {
307
+ if (!resizing.current) return;
308
+ const diff = e.clientX - resizing.current.startX;
309
+ const newW = Math.max(40, resizing.current.startW + diff);
310
+ setColWidths((prev) => ({ ...prev, [resizing.current.col]: newW }));
311
+ };
312
+ const handleMouseUp = () => {
313
+ if (resizing.current && onColumnsChange) {
314
+ const updated = columns.map((c, i) => ({ ...c, width: colWidths[i] ?? c.width }));
315
+ updated[resizing.current.col] = { ...updated[resizing.current.col], width: colWidths[resizing.current.col] ?? columns[resizing.current.col].width };
316
+ onColumnsChange(updated);
317
+ }
318
+ resizing.current = null;
319
+ document.body.style.cursor = "";
320
+ };
321
+ window.addEventListener("mousemove", handleMouseMove);
322
+ window.addEventListener("mouseup", handleMouseUp);
323
+ return () => {
324
+ window.removeEventListener("mousemove", handleMouseMove);
325
+ window.removeEventListener("mouseup", handleMouseUp);
326
+ };
327
+ }, [columns, colWidths, onColumnsChange]);
328
+ const startColResize = (e, ci) => {
329
+ e.preventDefault();
330
+ e.stopPropagation();
331
+ resizing.current = { col: ci, startX: e.clientX, startW: getColWidth(ci) };
332
+ document.body.style.cursor = "col-resize";
333
+ };
334
+ const getRowHeight = (ri) => rowHeights[ri] ?? 28;
335
+ useEffect(() => {
336
+ const handleMove = (e) => {
337
+ if (!rowResizing.current) return;
338
+ const diff = e.clientY - rowResizing.current.startY;
339
+ const newH = Math.max(20, rowResizing.current.startH + diff);
340
+ setRowHeights((prev) => ({ ...prev, [rowResizing.current.row]: newH }));
341
+ };
342
+ const handleUp = () => {
343
+ rowResizing.current = null;
344
+ document.body.style.cursor = "";
345
+ };
346
+ window.addEventListener("mousemove", handleMove);
347
+ window.addEventListener("mouseup", handleUp);
348
+ return () => {
349
+ window.removeEventListener("mousemove", handleMove);
350
+ window.removeEventListener("mouseup", handleUp);
351
+ };
352
+ }, []);
353
+ const startRowResize = (e, ri) => {
354
+ e.preventDefault();
355
+ e.stopPropagation();
356
+ rowResizing.current = { row: ri, startY: e.clientY, startH: getRowHeight(ri) };
357
+ document.body.style.cursor = "row-resize";
358
+ };
359
+ const autoFitColumn = useCallback((ci) => {
360
+ const canvas = document.createElement("canvas");
361
+ const ctx = canvas.getContext("2d");
362
+ if (!ctx) return;
363
+ ctx.font = "12px ui-monospace, SFMono-Regular, Menlo, monospace";
364
+ let maxW = 0;
365
+ for (const row of rows) {
366
+ const plain = String(row[ci] ?? "").replace(/<[^>]*>/g, "");
367
+ const w = ctx.measureText(plain).width;
368
+ if (w > maxW) maxW = w;
369
+ }
370
+ const headerW = ctx.measureText(columns[ci]?.title ?? "").width;
371
+ if (headerW > maxW) maxW = headerW;
372
+ const finalW = Math.max(40, Math.ceil(maxW + 24));
373
+ setColWidths((prev) => ({ ...prev, [ci]: finalW }));
374
+ if (onColumnsChange) {
375
+ const updated = columns.map((c, i) => ({ ...c, width: i === ci ? finalW : colWidths[i] ?? c.width }));
376
+ onColumnsChange(updated);
377
+ }
378
+ }, [rows, columns, colWidths, onColumnsChange]);
379
+ const autoFitRow = useCallback((ri) => {
380
+ setRowHeights((prev) => {
381
+ const next = { ...prev };
382
+ delete next[ri];
383
+ return next;
384
+ });
385
+ }, []);
386
+ const applyFill = useCallback((target) => {
387
+ const { a: sa, e: se } = selRefForFill.current;
388
+ if (!sa || !se) return;
389
+ const r1 = Math.min(sa.row, se.row);
390
+ const r2 = Math.max(sa.row, se.row);
391
+ const c1 = Math.min(sa.col, se.col);
392
+ const c2 = Math.max(sa.col, se.col);
393
+ let nr1 = r1, nr2 = r2, nc1 = c1, nc2 = c2;
394
+ if (target.row > r2) nr2 = target.row;
395
+ else if (target.row < r1) nr1 = target.row;
396
+ if (target.col > c2) nc2 = target.col;
397
+ else if (target.col < c1) nc1 = target.col;
398
+ const selH = r2 - r1 + 1;
399
+ const selW = c2 - c1 + 1;
400
+ const next = rows.map((r) => [...r]);
401
+ while (next.length <= nr2) next.push(Array(columns.length).fill(""));
402
+ for (const row of next) while (row.length <= nc2) row.push("");
403
+ function asSeries(values) {
404
+ const nums = values.map((v) => parseFloat(v));
405
+ if (nums.length < 2 || nums.some((n) => Number.isNaN(n))) return null;
406
+ const step = nums[1] - nums[0];
407
+ for (let i = 2; i < nums.length; i++) {
408
+ if (Math.abs(nums[i] - nums[i - 1] - step) > 1e-9) return null;
409
+ }
410
+ return { step };
411
+ }
412
+ for (let r = nr1; r <= nr2; r++) {
413
+ for (let c = nc1; c <= nc2; c++) {
414
+ if (r >= r1 && r <= r2 && c >= c1 && c <= c2) continue;
415
+ let value;
416
+ if (selW === 1 && nc1 === c1 && nc2 === c2) {
417
+ const colVals = Array.from({ length: selH }, (_, i) => next[r1 + i]?.[c] ?? "");
418
+ const series = asSeries(colVals);
419
+ if (series) {
420
+ const last = parseFloat(next[r2]?.[c] ?? "0");
421
+ const first = parseFloat(next[r1]?.[c] ?? "0");
422
+ const offset = r > r2 ? r - r2 : -(r1 - r);
423
+ value = String((r > r2 ? last : first) + series.step * offset);
424
+ } else {
425
+ const dr = ((r - r1) % selH + selH) % selH;
426
+ value = next[r1 + dr]?.[c] ?? "";
427
+ }
428
+ } else if (selH === 1 && nr1 === r1 && nr2 === r2) {
429
+ const rowVals = Array.from({ length: selW }, (_, i) => next[r]?.[c1 + i] ?? "");
430
+ const series = asSeries(rowVals);
431
+ if (series) {
432
+ const last = parseFloat(next[r]?.[c2] ?? "0");
433
+ const first = parseFloat(next[r]?.[c1] ?? "0");
434
+ const offset = c > c2 ? c - c2 : -(c1 - c);
435
+ value = String((c > c2 ? last : first) + series.step * offset);
436
+ } else {
437
+ const dc = ((c - c1) % selW + selW) % selW;
438
+ value = next[r]?.[c1 + dc] ?? "";
439
+ }
440
+ } else {
441
+ const dr = ((r - r1) % selH + selH) % selH;
442
+ const dc = ((c - c1) % selW + selW) % selW;
443
+ value = next[r1 + dr]?.[c1 + dc] ?? "";
444
+ }
445
+ next[r][c] = value;
446
+ }
447
+ }
448
+ onChange(next);
449
+ setSelAnchor({ row: nr1, col: nc1 });
450
+ setSelEnd({ row: nr2, col: nc2 });
451
+ }, [rows, columns.length, onChange]);
452
+ useEffect(() => {
453
+ const handleMove = (e) => {
454
+ if (!filling.current) return;
455
+ const el = document.elementFromPoint(e.clientX, e.clientY);
456
+ const inner = el?.closest?.("[data-row][data-col]");
457
+ if (!inner) return;
458
+ const r = parseInt(inner.dataset.row);
459
+ const c = parseInt(inner.dataset.col);
460
+ setFillTarget({ row: r, col: c });
461
+ };
462
+ const handleUp = () => {
463
+ if (filling.current && fillTarget) applyFill(fillTarget);
464
+ filling.current = false;
465
+ setFillTarget(null);
466
+ document.body.style.cursor = "";
467
+ };
468
+ window.addEventListener("mousemove", handleMove);
469
+ window.addEventListener("mouseup", handleUp);
470
+ return () => {
471
+ window.removeEventListener("mousemove", handleMove);
472
+ window.removeEventListener("mouseup", handleUp);
473
+ };
474
+ }, [fillTarget, applyFill]);
475
+ const startFill = (e) => {
476
+ e.preventDefault();
477
+ e.stopPropagation();
478
+ filling.current = true;
479
+ document.body.style.cursor = "crosshair";
480
+ };
481
+ const handleRowDragStart = (ri) => setDragRow(ri);
482
+ const handleRowDragOver = (e, ri) => {
483
+ e.preventDefault();
484
+ setDragOverRow(ri);
485
+ };
486
+ const handleRowDrop = (ri) => {
487
+ if (dragRow === null || dragRow === ri) {
488
+ setDragRow(null);
489
+ setDragOverRow(null);
490
+ return;
491
+ }
492
+ const next = rows.map((r) => [...r]);
493
+ const [moved] = next.splice(dragRow, 1);
494
+ next.splice(ri, 0, moved);
495
+ onChange(next);
496
+ setDragRow(null);
497
+ setDragOverRow(null);
498
+ };
499
+ const handleColDragStart = (ci) => setDragCol(ci);
500
+ const handleColDragOver = (e, ci) => {
501
+ e.preventDefault();
502
+ setDragOverCol(ci);
503
+ };
504
+ const handleColDrop = (ci) => {
505
+ if (dragCol === null || dragCol === ci) {
506
+ setDragCol(null);
507
+ setDragOverCol(null);
508
+ return;
509
+ }
510
+ const next = rows.map((r) => {
511
+ const nr = [...r];
512
+ const [moved] = nr.splice(dragCol, 1);
513
+ nr.splice(ci, 0, moved);
514
+ return nr;
515
+ });
516
+ onChange(next);
517
+ if (onColumnsChange) {
518
+ const newCols = [...columns];
519
+ const [moved] = newCols.splice(dragCol, 1);
520
+ newCols.splice(ci, 0, moved);
521
+ onColumnsChange(newCols);
522
+ }
523
+ setDragCol(null);
524
+ setDragOverCol(null);
525
+ };
526
+ const ROW_HEIGHT = 28;
527
+ const BUFFER = 20;
528
+ const containerRef = useRef(null);
529
+ const [scrollTop, setScrollTop] = useState(0);
530
+ const [containerHeight, setContainerHeight] = useState(800);
531
+ useEffect(() => {
532
+ const el = containerRef.current;
533
+ if (!el) return;
534
+ const measure = () => setContainerHeight(el.clientHeight || 800);
535
+ measure();
536
+ const observer = new ResizeObserver(measure);
537
+ observer.observe(el);
538
+ return () => observer.disconnect();
539
+ }, []);
540
+ const useVirtualization = rows.length > 100;
541
+ const visibleStart = useVirtualization ? Math.max(0, Math.floor(scrollTop / ROW_HEIGHT) - BUFFER) : 0;
542
+ const visibleEnd = useVirtualization ? Math.min(rows.length, Math.ceil((scrollTop + containerHeight) / ROW_HEIGHT) + BUFFER) : rows.length;
543
+ const visibleRows = useMemo(() => rows.slice(visibleStart, visibleEnd), [rows, visibleStart, visibleEnd]);
544
+ const topPad = visibleStart * ROW_HEIGHT;
545
+ const bottomPad = (rows.length - visibleEnd) * ROW_HEIGHT;
546
+ const thCls = "px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase bg-gray-100 border-b border-r border-gray-200 select-none";
547
+ const tdCls = "px-0 py-0 border-b border-r border-gray-200 text-sm";
548
+ return /* @__PURE__ */ jsxs(
549
+ "div",
550
+ {
551
+ ref: containerRef,
552
+ className: "border border-gray-300 rounded overflow-scroll grid-scroll",
553
+ style: { maxHeight, height: maxHeight },
554
+ onScroll: (e) => setScrollTop(e.target.scrollTop),
555
+ children: [
556
+ /* @__PURE__ */ jsxs("table", { ref: tableRef, className: "border-collapse select-none", style: { tableLayout: "fixed", minWidth: 36 + columns.reduce((s, c) => s + getColWidth(columns.indexOf(c)), 0) }, children: [
557
+ /* @__PURE__ */ jsxs("colgroup", { children: [
558
+ /* @__PURE__ */ jsx("col", { style: { width: 36 } }),
559
+ columns.map((c, ci) => /* @__PURE__ */ jsx("col", { style: { width: getColWidth(ci) } }, c.key))
560
+ ] }),
561
+ /* @__PURE__ */ jsx("thead", { className: "sticky top-0 z-10", children: /* @__PURE__ */ jsxs("tr", { children: [
562
+ /* @__PURE__ */ jsx(
563
+ "th",
564
+ {
565
+ className: thCls + " text-center w-9 cursor-pointer hover:bg-gray-200",
566
+ onClick: () => {
567
+ setSelAnchor({ row: 0, col: 0 });
568
+ setSelEnd({ row: rows.length - 1, col: columns.length - 1 });
569
+ },
570
+ title: "Select all",
571
+ children: "#"
572
+ }
573
+ ),
574
+ columns.map((c, ci) => {
575
+ const colSelected = selAnchor && selEnd && Math.min(selAnchor.col, selEnd.col) <= ci && ci <= Math.max(selAnchor.col, selEnd.col) && Math.min(selAnchor.row, selEnd.row) === 0 && Math.max(selAnchor.row, selEnd.row) === rows.length - 1;
576
+ return /* @__PURE__ */ jsxs(
577
+ "th",
578
+ {
579
+ className: `${thCls} cursor-pointer hover:bg-gray-200 relative${colSelected ? " !bg-blue-200" : ""}${dragOverCol === ci ? " !bg-blue-100" : ""}`,
580
+ style: { width: getColWidth(ci) },
581
+ draggable: true,
582
+ onDragStart: () => handleColDragStart(ci),
583
+ onDragOver: (e) => handleColDragOver(e, ci),
584
+ onDrop: () => handleColDrop(ci),
585
+ onDragEnd: () => {
586
+ setDragCol(null);
587
+ setDragOverCol(null);
588
+ },
589
+ onClick: (e) => {
590
+ if (e.shiftKey && selAnchor) {
591
+ setSelEnd({ row: rows.length - 1, col: ci });
592
+ } else {
593
+ setSelAnchor({ row: 0, col: ci });
594
+ setSelEnd({ row: rows.length - 1, col: ci });
595
+ }
596
+ },
597
+ onContextMenu: (e) => handleColCtx(e, ci),
598
+ children: [
599
+ c.title,
600
+ /* @__PURE__ */ jsx(
601
+ "div",
602
+ {
603
+ className: "absolute -right-1 top-0 bottom-0 w-2 cursor-col-resize z-20",
604
+ onMouseDown: (e) => startColResize(e, ci),
605
+ onDoubleClick: (e) => {
606
+ e.stopPropagation();
607
+ autoFitColumn(ci);
608
+ },
609
+ title: "Drag to resize \xB7 double-click to auto-fit"
610
+ }
611
+ )
612
+ ]
613
+ },
614
+ c.key
615
+ );
616
+ })
617
+ ] }) }),
618
+ /* @__PURE__ */ jsxs("tbody", { children: [
619
+ topPad > 0 && /* @__PURE__ */ jsx("tr", { style: { height: topPad } }),
620
+ visibleRows.map((row, vi) => {
621
+ const ri = visibleStart + vi;
622
+ const rowSelected = selAnchor && selEnd && Math.min(selAnchor.row, selEnd.row) <= ri && ri <= Math.max(selAnchor.row, selEnd.row) && Math.min(selAnchor.col, selEnd.col) === 0 && Math.max(selAnchor.col, selEnd.col) === columns.length - 1;
623
+ return /* @__PURE__ */ jsxs("tr", { style: { height: getRowHeight(ri) }, children: [
624
+ /* @__PURE__ */ jsxs(
625
+ "td",
626
+ {
627
+ className: `relative px-1 py-1 text-center text-[10px] text-gray-400 border-b border-r border-gray-200 bg-gray-50 select-none cursor-pointer hover:bg-gray-200${rowSelected ? " !bg-blue-200 !text-gray-700" : ""}${dragOverRow === ri ? " !bg-blue-100" : ""}`,
628
+ draggable: true,
629
+ onDragStart: () => handleRowDragStart(ri),
630
+ onDragOver: (e) => handleRowDragOver(e, ri),
631
+ onDrop: () => handleRowDrop(ri),
632
+ onDragEnd: () => {
633
+ setDragRow(null);
634
+ setDragOverRow(null);
635
+ },
636
+ onClick: (e) => {
637
+ if (e.shiftKey && selAnchor) {
638
+ setSelEnd({ row: ri, col: columns.length - 1 });
639
+ } else {
640
+ setSelAnchor({ row: ri, col: 0 });
641
+ setSelEnd({ row: ri, col: columns.length - 1 });
642
+ }
643
+ },
644
+ onContextMenu: (e) => handleRowCtx(e, ri),
645
+ children: [
646
+ ri + 1,
647
+ /* @__PURE__ */ jsx(
648
+ "div",
649
+ {
650
+ className: "absolute -bottom-1 left-0 right-0 h-2 cursor-row-resize z-20",
651
+ onMouseDown: (e) => startRowResize(e, ri),
652
+ onDoubleClick: (e) => {
653
+ e.stopPropagation();
654
+ autoFitRow(ri);
655
+ },
656
+ title: "Drag to resize \xB7 double-click to auto-fit"
657
+ }
658
+ )
659
+ ]
660
+ }
661
+ ),
662
+ columns.map((col, ci) => {
663
+ const inRange = selAnchor && selEnd && rangeContains(selAnchor, selEnd, ri, ci);
664
+ const isEditing = editingCell?.row === ri && editingCell?.col === ci && !col.readOnly;
665
+ const isFocused = focus?.row === ri && focus?.col === ci;
666
+ const cellStyle = cellStyles?.[`${ri}:${ci}`];
667
+ const selR2 = selAnchor && selEnd ? Math.max(selAnchor.row, selEnd.row) : -1;
668
+ const selC2 = selAnchor && selEnd ? Math.max(selAnchor.col, selEnd.col) : -1;
669
+ const isFillCorner = selAnchor && selEnd && ri === selR2 && ci === selC2;
670
+ const inFillPreview = filling.current && fillTarget && selAnchor && selEnd && (() => {
671
+ const r1 = Math.min(selAnchor.row, selEnd.row), r2 = Math.max(selAnchor.row, selEnd.row);
672
+ const c1 = Math.min(selAnchor.col, selEnd.col), c2 = Math.max(selAnchor.col, selEnd.col);
673
+ let nr1 = r1, nr2 = r2, nc1 = c1, nc2 = c2;
674
+ if (fillTarget.row > r2) nr2 = fillTarget.row;
675
+ else if (fillTarget.row < r1) nr1 = fillTarget.row;
676
+ if (fillTarget.col > c2) nc2 = fillTarget.col;
677
+ else if (fillTarget.col < c1) nc1 = fillTarget.col;
678
+ return ri >= nr1 && ri <= nr2 && ci >= nc1 && ci <= nc2 && !(ri >= r1 && ri <= r2 && ci >= c1 && ci <= c2);
679
+ })();
680
+ const fontSizeCls = cellStyle?.fontSize === "sm" ? "text-[11px]" : cellStyle?.fontSize === "lg" ? "text-sm" : cellStyle?.fontSize === "xl" ? "text-base" : "text-xs";
681
+ const styleCls = `${cellStyle?.bold ? " font-bold" : ""}${cellStyle?.italic ? " italic" : ""}${cellStyle?.underline ? " underline" : ""}`;
682
+ return /* @__PURE__ */ jsxs(
683
+ "td",
684
+ {
685
+ className: `relative ${tdCls}${col.readOnly ? " bg-gray-50 text-gray-500 cursor-default" : " cursor-cell"}${inRange ? " !bg-blue-100" : ""}${isFocused && !inRange ? " ring-2 ring-inset ring-blue-400" : ""}${inFillPreview ? " !bg-blue-50 ring-1 ring-inset ring-blue-300" : ""}`,
686
+ onMouseDown: (e) => {
687
+ if (e.target === e.currentTarget) {
688
+ handleMouseDown(e, ri, ci);
689
+ const inner = e.currentTarget.querySelector("[data-row][data-col]");
690
+ inner?.focus();
691
+ }
692
+ },
693
+ onMouseEnter: () => handleMouseEnter(ri, ci),
694
+ onClick: (e) => {
695
+ if (e.target === e.currentTarget) {
696
+ const inner = e.currentTarget.querySelector("[data-row][data-col]");
697
+ inner?.focus();
698
+ }
699
+ },
700
+ onDoubleClick: (e) => {
701
+ if (e.target === e.currentTarget && !col.readOnly) setEditingCell({ row: ri, col: ci });
702
+ },
703
+ children: [
704
+ /* @__PURE__ */ jsx(
705
+ "div",
706
+ {
707
+ contentEditable: isEditing,
708
+ suppressContentEditableWarning: true,
709
+ tabIndex: 0,
710
+ "data-row": ri,
711
+ "data-col": ci,
712
+ className: `w-full h-full px-2 py-1 outline-none whitespace-nowrap overflow-hidden ${col.align === "right" ? "text-right" : col.align === "center" ? "text-center" : ""} ${col.readOnly ? "cursor-default" : "cursor-cell"} font-mono ${fontSizeCls}${styleCls}`,
713
+ onMouseDown: (e) => handleMouseDown(e, ri, ci),
714
+ onMouseEnter: () => handleMouseEnter(ri, ci),
715
+ onDoubleClick: () => {
716
+ if (!col.readOnly) setEditingCell({ row: ri, col: ci });
717
+ },
718
+ onFocus: () => {
719
+ setFocus({ row: ri, col: ci });
720
+ if (!dragging.current) {
721
+ setSelAnchor({ row: ri, col: ci });
722
+ setSelEnd({ row: ri, col: ci });
723
+ }
724
+ },
725
+ onBlur: (e) => {
726
+ const val = e.currentTarget.textContent || "";
727
+ if (val !== (row[ci] || "")) updateCell(ri, ci, val);
728
+ setEditingCell(null);
729
+ },
730
+ onInput: () => ensureRows(ri),
731
+ onPaste: (e) => handlePaste(e, ri, ci),
732
+ onKeyDown: (e) => {
733
+ if (!isEditing && !col.readOnly && e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
734
+ e.preventDefault();
735
+ updateCell(ri, ci, e.key);
736
+ setEditingCell({ row: ri, col: ci });
737
+ requestAnimationFrame(() => {
738
+ const el = tableRef.current?.querySelector(`[data-row="${ri}"][data-col="${ci}"]`);
739
+ if (el) {
740
+ el.focus();
741
+ const range = document.createRange();
742
+ range.selectNodeContents(el);
743
+ range.collapse(false);
744
+ const sel = window.getSelection();
745
+ sel?.removeAllRanges();
746
+ sel?.addRange(range);
747
+ }
748
+ });
749
+ return;
750
+ }
751
+ handleKeyDown(e, ri, ci);
752
+ },
753
+ dangerouslySetInnerHTML: { __html: row[ci] || "" }
754
+ }
755
+ ),
756
+ isFillCorner && /* @__PURE__ */ jsx(
757
+ "div",
758
+ {
759
+ onMouseDown: startFill,
760
+ title: "Drag to fill",
761
+ className: "absolute -bottom-[3px] -right-[3px] w-[7px] h-[7px] bg-blue-500 border border-white cursor-crosshair z-30 hover:bg-blue-600"
762
+ }
763
+ )
764
+ ]
765
+ },
766
+ ci
767
+ );
768
+ })
769
+ ] }, ri);
770
+ }),
771
+ bottomPad > 0 && /* @__PURE__ */ jsx("tr", { style: { height: bottomPad } })
772
+ ] })
773
+ ] }),
774
+ ctxMenu && createPortal(
775
+ /* @__PURE__ */ jsxs(Fragment, { children: [
776
+ /* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-[200]", onPointerDown: () => setCtxMenu(null) }),
777
+ /* @__PURE__ */ jsx(
778
+ "div",
779
+ {
780
+ className: "fixed z-[201] bg-white rounded-lg shadow-lg border border-gray-200 py-1 min-w-[160px]",
781
+ style: { left: ctxMenu.x, top: ctxMenu.y },
782
+ children: ctxMenu.items.map((item, i) => /* @__PURE__ */ jsx(
783
+ "button",
784
+ {
785
+ onPointerDown: (e) => {
786
+ e.stopPropagation();
787
+ item.onClick();
788
+ },
789
+ className: "w-full text-left px-3 py-1.5 text-sm text-gray-700 hover:bg-gray-100",
790
+ children: item.label
791
+ },
792
+ i
793
+ ))
794
+ }
795
+ )
796
+ ] }),
797
+ document.body
798
+ )
799
+ ]
800
+ }
801
+ );
802
+ }
803
+
804
+ export { EditableGrid };
805
+ //# sourceMappingURL=chunk-GP4Y3VCB.js.map
806
+ //# sourceMappingURL=chunk-GP4Y3VCB.js.map