efficient-shell 0.0.0-stage → 4.126.2

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