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,196 @@
1
+ import { loadAppearance, WidgetSettingsModal } from './chunk-SVBID2P6.js';
2
+ import { useWidgetSettings } from './chunk-WQIS72NL.js';
3
+ import './chunk-RFTLYCSF.js';
4
+ import { useState, useRef, useCallback, useEffect } from 'react';
5
+ import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
6
+
7
+ var POMO_SETTINGS_KEY = "pomodoro_appearance";
8
+ var DURATIONS = { focus: 25 * 60, short: 5 * 60, long: 15 * 60 };
9
+ var LABELS = { focus: "Focus", short: "Short", long: "Long" };
10
+ var COLORS = {
11
+ focus: { ring: "stroke-blue-500", text: "text-blue-600" },
12
+ short: { ring: "stroke-emerald-500", text: "text-emerald-600" },
13
+ long: { ring: "stroke-emerald-600", text: "text-emerald-700" }
14
+ };
15
+ function getTodayKey() {
16
+ return `pomodoro-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`;
17
+ }
18
+ function loadCount() {
19
+ return parseInt(localStorage.getItem(getTodayKey()) || "0", 10);
20
+ }
21
+ function saveCount(n) {
22
+ localStorage.setItem(getTodayKey(), String(n));
23
+ }
24
+ function beep() {
25
+ try {
26
+ const ctx = new AudioContext();
27
+ const osc = ctx.createOscillator();
28
+ const gain = ctx.createGain();
29
+ osc.connect(gain);
30
+ gain.connect(ctx.destination);
31
+ osc.frequency.value = 830;
32
+ osc.type = "sine";
33
+ gain.gain.setValueAtTime(0.3, ctx.currentTime);
34
+ gain.gain.exponentialRampToValueAtTime(1e-3, ctx.currentTime + 0.8);
35
+ osc.start();
36
+ osc.stop(ctx.currentTime + 0.8);
37
+ setTimeout(() => ctx.close(), 1e3);
38
+ } catch {
39
+ }
40
+ }
41
+ function PomodoroTimer() {
42
+ const [mode, setMode] = useState("focus");
43
+ const [remaining, setRemaining] = useState(DURATIONS.focus);
44
+ const [running, setRunning] = useState(false);
45
+ const [count, setCount] = useState(loadCount);
46
+ const [streak, setStreak] = useState(0);
47
+ const intervalRef = useRef();
48
+ const [appearance, setAppearance] = useState(() => loadAppearance(POMO_SETTINGS_KEY));
49
+ const [settingsOpen, setSettingsOpen] = useState(false);
50
+ const [configAppearance, setConfigAppearance] = useState(appearance);
51
+ useWidgetSettings(useCallback(() => {
52
+ setConfigAppearance({ ...appearance });
53
+ setSettingsOpen(true);
54
+ }, [appearance]));
55
+ const total = DURATIONS[mode];
56
+ const progress = 1 - remaining / total;
57
+ const mm = String(Math.floor(remaining / 60)).padStart(2, "0");
58
+ const ss = String(remaining % 60).padStart(2, "0");
59
+ const c = COLORS[mode];
60
+ const R = 54, C = 2 * Math.PI * R;
61
+ const notify = useCallback((title) => {
62
+ beep();
63
+ if (Notification.permission === "granted") new Notification(title);
64
+ else if (Notification.permission !== "denied") Notification.requestPermission();
65
+ }, []);
66
+ const switchMode = useCallback((next) => {
67
+ setMode(next);
68
+ setRemaining(DURATIONS[next]);
69
+ setRunning(false);
70
+ }, []);
71
+ useEffect(() => {
72
+ if (!running) {
73
+ clearInterval(intervalRef.current);
74
+ return;
75
+ }
76
+ intervalRef.current = setInterval(() => setRemaining((r) => {
77
+ if (r <= 1) {
78
+ clearInterval(intervalRef.current);
79
+ return 0;
80
+ }
81
+ return r - 1;
82
+ }), 1e3);
83
+ return () => clearInterval(intervalRef.current);
84
+ }, [running]);
85
+ useEffect(() => {
86
+ if (remaining > 0 || running === false) return;
87
+ setRunning(false);
88
+ if (mode === "focus") {
89
+ const next = streak + 1;
90
+ const newCount = count + 1;
91
+ setStreak(next);
92
+ setCount(newCount);
93
+ saveCount(newCount);
94
+ notify(next % 4 === 0 ? "Time for a long break!" : "Time for a short break!");
95
+ switchMode(next % 4 === 0 ? "long" : "short");
96
+ } else {
97
+ notify("Back to work!");
98
+ switchMode("focus");
99
+ }
100
+ }, [remaining, running, mode, streak, count, notify, switchMode]);
101
+ const reset = () => {
102
+ setRunning(false);
103
+ setRemaining(DURATIONS[mode]);
104
+ };
105
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
106
+ /* @__PURE__ */ jsxs(
107
+ "div",
108
+ {
109
+ className: "flex flex-col items-center justify-between h-full p-3 select-none",
110
+ style: { opacity: appearance.activeOpacity / 100, backdropFilter: appearance.activeBlur > 0 ? `blur(${appearance.activeBlur}px)` : void 0 },
111
+ children: [
112
+ /* @__PURE__ */ jsx("div", { className: "flex gap-0.5 rounded-lg bg-gray-100 p-0.5 text-xs font-medium w-full", children: ["focus", "short", "long"].map((m) => /* @__PURE__ */ jsx(
113
+ "button",
114
+ {
115
+ onClick: () => switchMode(m),
116
+ className: `flex-1 px-2 py-1 rounded-md transition ${mode === m ? "bg-blue-600 text-white" : "text-gray-500 hover:bg-gray-200"}`,
117
+ children: LABELS[m]
118
+ },
119
+ m
120
+ )) }),
121
+ /* @__PURE__ */ jsxs("div", { className: "relative", style: { width: 210, height: 210 }, children: [
122
+ /* @__PURE__ */ jsxs("svg", { className: "w-full h-full -rotate-90", viewBox: "0 0 120 120", children: [
123
+ /* @__PURE__ */ jsx("circle", { cx: "60", cy: "60", r: R, fill: "none", strokeWidth: "6", className: "stroke-gray-200" }),
124
+ /* @__PURE__ */ jsx(
125
+ "circle",
126
+ {
127
+ cx: "60",
128
+ cy: "60",
129
+ r: R,
130
+ fill: "none",
131
+ strokeWidth: "6",
132
+ className: `${c.ring} transition-all duration-500`,
133
+ strokeLinecap: "round",
134
+ strokeDasharray: C,
135
+ strokeDashoffset: C * (1 - progress)
136
+ }
137
+ )
138
+ ] }),
139
+ /* @__PURE__ */ jsxs("div", { className: "absolute inset-0 flex flex-col items-center justify-center", children: [
140
+ /* @__PURE__ */ jsxs("span", { className: `text-3xl font-mono font-bold ${c.text}`, children: [
141
+ mm,
142
+ ":",
143
+ ss
144
+ ] }),
145
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] text-gray-400 mt-0.5", children: LABELS[mode] })
146
+ ] })
147
+ ] }),
148
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2 w-full", children: [
149
+ /* @__PURE__ */ jsx(
150
+ "button",
151
+ {
152
+ onClick: () => setRunning((r) => !r),
153
+ className: "flex-1 py-1.5 rounded-lg text-white font-medium text-xs bg-blue-600 hover:bg-blue-700 transition",
154
+ children: running ? "Pause" : "Start"
155
+ }
156
+ ),
157
+ /* @__PURE__ */ jsx(
158
+ "button",
159
+ {
160
+ onClick: reset,
161
+ className: "flex-1 py-1.5 rounded-lg text-xs font-medium bg-gray-200 text-gray-700 hover:bg-gray-300 transition",
162
+ children: "Reset"
163
+ }
164
+ )
165
+ ] }),
166
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 text-xs text-gray-400", children: [
167
+ count > 0 ? Array.from({ length: Math.min(count, 8) }, (_, i) => /* @__PURE__ */ jsx("span", { children: "\u{1F345}" }, i)) : /* @__PURE__ */ jsx("span", { children: "No sessions yet" }),
168
+ count > 8 && /* @__PURE__ */ jsxs("span", { children: [
169
+ "+",
170
+ count - 8
171
+ ] })
172
+ ] })
173
+ ]
174
+ }
175
+ ),
176
+ /* @__PURE__ */ jsx(
177
+ WidgetSettingsModal,
178
+ {
179
+ open: settingsOpen,
180
+ onClose: () => setSettingsOpen(false),
181
+ title: "Pomodoro Settings",
182
+ appearance: configAppearance,
183
+ onAppearanceChange: setConfigAppearance,
184
+ onSave: () => {
185
+ setAppearance(configAppearance);
186
+ localStorage.setItem(POMO_SETTINGS_KEY, JSON.stringify(configAppearance));
187
+ setSettingsOpen(false);
188
+ }
189
+ }
190
+ )
191
+ ] });
192
+ }
193
+
194
+ export { PomodoroTimer as default };
195
+ //# sourceMappingURL=PomodoroTimer-HARIJN4S.js.map
196
+ //# sourceMappingURL=PomodoroTimer-HARIJN4S.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/apps/PomodoroTimer.tsx"],"names":[],"mappings":"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","file":"PomodoroTimer-HARIJN4S.js","sourcesContent":["import { useState, useEffect, useCallback, useRef } from 'react';\nimport { useWidgetSettings } from '../shell/Modal';\nimport WidgetSettingsModal, { loadAppearance, type WidgetAppearance } from '../shell/WidgetSettingsModal';\n\ntype Mode = 'focus' | 'short' | 'long';\nconst POMO_SETTINGS_KEY = 'pomodoro_appearance';\nconst DURATIONS: Record<Mode, number> = { focus: 25 * 60, short: 5 * 60, long: 15 * 60 };\nconst LABELS: Record<Mode, string> = { focus: 'Focus', short: 'Short', long: 'Long' };\nconst COLORS: Record<Mode, { ring: string; text: string }> = {\n focus: { ring: 'stroke-blue-500', text: 'text-blue-600' },\n short: { ring: 'stroke-emerald-500', text: 'text-emerald-600' },\n long: { ring: 'stroke-emerald-600', text: 'text-emerald-700' },\n};\n\nfunction getTodayKey() { return `pomodoro-${new Date().toISOString().slice(0, 10)}`; }\nfunction loadCount() { return parseInt(localStorage.getItem(getTodayKey()) || '0', 10); }\nfunction saveCount(n: number) { localStorage.setItem(getTodayKey(), String(n)); }\n\nfunction beep() {\n try {\n const ctx = new AudioContext();\n const osc = ctx.createOscillator();\n const gain = ctx.createGain();\n osc.connect(gain); gain.connect(ctx.destination);\n osc.frequency.value = 830; osc.type = 'sine';\n gain.gain.setValueAtTime(0.3, ctx.currentTime);\n gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.8);\n osc.start(); osc.stop(ctx.currentTime + 0.8);\n setTimeout(() => ctx.close(), 1000);\n } catch { /* silent fallback */ }\n}\n\nexport default function PomodoroTimer() {\n const [mode, setMode] = useState<Mode>('focus');\n const [remaining, setRemaining] = useState(DURATIONS.focus);\n const [running, setRunning] = useState(false);\n const [count, setCount] = useState(loadCount);\n const [streak, setStreak] = useState(0);\n const intervalRef = useRef<ReturnType<typeof setInterval>>();\n const [appearance, setAppearance] = useState(() => loadAppearance(POMO_SETTINGS_KEY));\n const [settingsOpen, setSettingsOpen] = useState(false);\n const [configAppearance, setConfigAppearance] = useState<WidgetAppearance>(appearance);\n\n useWidgetSettings(useCallback(() => {\n setConfigAppearance({ ...appearance });\n setSettingsOpen(true);\n }, [appearance]));\n\n const total = DURATIONS[mode];\n const progress = 1 - remaining / total;\n const mm = String(Math.floor(remaining / 60)).padStart(2, '0');\n const ss = String(remaining % 60).padStart(2, '0');\n const c = COLORS[mode];\n const R = 54, C = 2 * Math.PI * R;\n\n const notify = useCallback((title: string) => {\n beep();\n if (Notification.permission === 'granted') new Notification(title);\n else if (Notification.permission !== 'denied') Notification.requestPermission();\n }, []);\n\n const switchMode = useCallback((next: Mode) => {\n setMode(next); setRemaining(DURATIONS[next]); setRunning(false);\n }, []);\n\n useEffect(() => {\n if (!running) { clearInterval(intervalRef.current); return; }\n intervalRef.current = setInterval(() => setRemaining(r => {\n if (r <= 1) { clearInterval(intervalRef.current); return 0; }\n return r - 1;\n }), 1000);\n return () => clearInterval(intervalRef.current);\n }, [running]);\n\n useEffect(() => {\n if (remaining > 0 || running === false) return;\n setRunning(false);\n if (mode === 'focus') {\n const next = streak + 1;\n const newCount = count + 1;\n setStreak(next); setCount(newCount); saveCount(newCount);\n notify(next % 4 === 0 ? 'Time for a long break!' : 'Time for a short break!');\n switchMode(next % 4 === 0 ? 'long' : 'short');\n } else {\n notify('Back to work!');\n switchMode('focus');\n }\n }, [remaining, running, mode, streak, count, notify, switchMode]);\n\n const reset = () => { setRunning(false); setRemaining(DURATIONS[mode]); };\n\n return (\n <>\n <div className=\"flex flex-col items-center justify-between h-full p-3 select-none\"\n style={{ opacity: appearance.activeOpacity / 100, backdropFilter: appearance.activeBlur > 0 ? `blur(${appearance.activeBlur}px)` : undefined }}>\n {/* Mode tabs */}\n <div className=\"flex gap-0.5 rounded-lg bg-gray-100 p-0.5 text-xs font-medium w-full\">\n {(['focus', 'short', 'long'] as Mode[]).map(m => (\n <button key={m} onClick={() => switchMode(m)}\n className={`flex-1 px-2 py-1 rounded-md transition ${mode === m ? 'bg-blue-600 text-white' : 'text-gray-500 hover:bg-gray-200'}`}>\n {LABELS[m]}\n </button>\n ))}\n </div>\n\n {/* Circular timer */}\n <div className=\"relative\" style={{ width: 210, height: 210 }}>\n <svg className=\"w-full h-full -rotate-90\" viewBox=\"0 0 120 120\">\n <circle cx=\"60\" cy=\"60\" r={R} fill=\"none\" strokeWidth=\"6\" className=\"stroke-gray-200\" />\n <circle cx=\"60\" cy=\"60\" r={R} fill=\"none\" strokeWidth=\"6\"\n className={`${c.ring} transition-all duration-500`}\n strokeLinecap=\"round\" strokeDasharray={C} strokeDashoffset={C * (1 - progress)} />\n </svg>\n <div className=\"absolute inset-0 flex flex-col items-center justify-center\">\n <span className={`text-3xl font-mono font-bold ${c.text}`}>{mm}:{ss}</span>\n <span className=\"text-[10px] text-gray-400 mt-0.5\">{LABELS[mode]}</span>\n </div>\n </div>\n\n {/* Controls */}\n <div className=\"flex gap-2 w-full\">\n <button onClick={() => setRunning(r => !r)}\n className=\"flex-1 py-1.5 rounded-lg text-white font-medium text-xs bg-blue-600 hover:bg-blue-700 transition\">\n {running ? 'Pause' : 'Start'}\n </button>\n <button onClick={reset}\n className=\"flex-1 py-1.5 rounded-lg text-xs font-medium bg-gray-200 text-gray-700 hover:bg-gray-300 transition\">\n Reset\n </button>\n </div>\n\n {/* Session dots */}\n <div className=\"flex items-center gap-1 text-xs text-gray-400\">\n {count > 0 ? Array.from({ length: Math.min(count, 8) }, (_, i) => (\n <span key={i}>&#x1F345;</span>\n )) : <span>No sessions yet</span>}\n {count > 8 && <span>+{count - 8}</span>}\n </div>\n </div>\n <WidgetSettingsModal open={settingsOpen} onClose={() => setSettingsOpen(false)} title=\"Pomodoro Settings\"\n appearance={configAppearance} onAppearanceChange={setConfigAppearance}\n onSave={() => { setAppearance(configAppearance); localStorage.setItem(POMO_SETTINGS_KEY, JSON.stringify(configAppearance)); setSettingsOpen(false); }} />\n </>\n );\n}\n"]}
@@ -0,0 +1,446 @@
1
+ import { EditableGrid } from './chunk-GP4Y3VCB.js';
2
+ import { WindowTitle } from './chunk-WQIS72NL.js';
3
+ import './chunk-RFTLYCSF.js';
4
+ import { useRef, useState, useEffect, useCallback } from 'react';
5
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
6
+
7
+ var TITLE_DISPLAY_MAX = 24;
8
+ function truncateForTitle(s) {
9
+ return s.length > TITLE_DISPLAY_MAX ? `${s.slice(0, TITLE_DISPLAY_MAX - 1)}\u2026` : s;
10
+ }
11
+ var ALPHA = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
12
+ var DEFAULT_COLS = 10;
13
+ var DEFAULT_ROWS = 30;
14
+ function colLabel(i) {
15
+ if (i < 26) return ALPHA[i];
16
+ return ALPHA[Math.floor(i / 26) - 1] + ALPHA[i % 26];
17
+ }
18
+ function makeColumns(count) {
19
+ return Array.from({ length: count }, (_, i) => ({
20
+ key: `col_${i}`,
21
+ title: colLabel(i),
22
+ width: 100
23
+ }));
24
+ }
25
+ function makeEmptyData(rows, cols) {
26
+ return Array.from({ length: rows }, () => Array(cols).fill(""));
27
+ }
28
+ function newSheet(name) {
29
+ return {
30
+ id: crypto.randomUUID(),
31
+ name,
32
+ columns: makeColumns(DEFAULT_COLS),
33
+ data: makeEmptyData(DEFAULT_ROWS, DEFAULT_COLS),
34
+ cellStyles: {}
35
+ };
36
+ }
37
+ function parseCSV(text) {
38
+ return text.split("\n").map((line) => {
39
+ if (line.includes(" ")) return line.split(" ").map((s) => s.trim());
40
+ const parts = [];
41
+ let current = "";
42
+ let inQuotes = false;
43
+ for (const ch of line) {
44
+ if (ch === '"') {
45
+ inQuotes = !inQuotes;
46
+ continue;
47
+ }
48
+ if (ch === "," && !inQuotes) {
49
+ parts.push(current.trim());
50
+ current = "";
51
+ continue;
52
+ }
53
+ current += ch;
54
+ }
55
+ parts.push(current.trim());
56
+ return parts;
57
+ }).filter((r) => r.some((c) => c.trim()));
58
+ }
59
+ function Spreadsheet() {
60
+ const containerRef = useRef(null);
61
+ const [sheets, setSheets] = useState([newSheet("Sheet 1")]);
62
+ const [activeIdx, setActiveIdx] = useState(0);
63
+ const undoStackRef = useRef([]);
64
+ const lastCommittedRef = useRef(sheets);
65
+ const skipRecordRef = useRef(false);
66
+ useEffect(() => {
67
+ if (skipRecordRef.current) {
68
+ skipRecordRef.current = false;
69
+ } else if (lastCommittedRef.current !== sheets) {
70
+ undoStackRef.current.push(lastCommittedRef.current);
71
+ if (undoStackRef.current.length > 50) undoStackRef.current.shift();
72
+ }
73
+ lastCommittedRef.current = sheets;
74
+ }, [sheets]);
75
+ const undo = useCallback(() => {
76
+ const prev = undoStackRef.current.pop();
77
+ if (!prev) return;
78
+ skipRecordRef.current = true;
79
+ setSheets(prev);
80
+ }, []);
81
+ useEffect(() => {
82
+ const handler = (e) => {
83
+ if (!containerRef.current?.contains(document.activeElement) && !containerRef.current?.matches(":focus-within")) return;
84
+ const isUndo = (e.ctrlKey || e.metaKey) && !e.shiftKey && (e.key === "z" || e.key === "Z");
85
+ if (isUndo) {
86
+ e.preventDefault();
87
+ undo();
88
+ }
89
+ };
90
+ window.addEventListener("keydown", handler);
91
+ return () => window.removeEventListener("keydown", handler);
92
+ }, [undo]);
93
+ const [title, setTitle] = useState("Untitled");
94
+ const [editingTitle, setEditingTitle] = useState(false);
95
+ const [editingTab, setEditingTab] = useState(null);
96
+ const [tabName, setTabName] = useState("");
97
+ const fileRef = useRef(null);
98
+ const [addColCount, setAddColCount] = useState("1");
99
+ const [addRowCount, setAddRowCount] = useState("10");
100
+ const active = sheets[activeIdx] || sheets[0];
101
+ const data = active.data;
102
+ const columns = active.columns;
103
+ const cellStyles = active.cellStyles ?? {};
104
+ const [focusedCell, setFocusedCell] = useState(null);
105
+ const [selection, setSelection] = useState(null);
106
+ const targetCells = useCallback(() => {
107
+ if (selection) {
108
+ const r1 = Math.min(selection.anchor.row, selection.end.row);
109
+ const r2 = Math.max(selection.anchor.row, selection.end.row);
110
+ const c1 = Math.min(selection.anchor.col, selection.end.col);
111
+ const c2 = Math.max(selection.anchor.col, selection.end.col);
112
+ const cells = [];
113
+ for (let r = r1; r <= r2; r++) for (let c = c1; c <= c2; c++) cells.push({ row: r, col: c });
114
+ return cells;
115
+ }
116
+ return focusedCell ? [focusedCell] : [];
117
+ }, [selection, focusedCell]);
118
+ const toggleCellStyle = useCallback((key, value) => {
119
+ const cells = targetCells();
120
+ if (cells.length === 0) return;
121
+ setSheets((prev) => prev.map((s, i) => {
122
+ if (i !== activeIdx) return s;
123
+ const styles = { ...s.cellStyles ?? {} };
124
+ const firstKey = `${cells[0].row}:${cells[0].col}`;
125
+ const desired = value !== void 0 ? value : !(styles[firstKey] ?? {})[key];
126
+ for (const { row, col } of cells) {
127
+ const k = `${row}:${col}`;
128
+ styles[k] = { ...styles[k] ?? {}, [key]: desired };
129
+ }
130
+ return { ...s, cellStyles: styles };
131
+ }));
132
+ }, [targetCells, activeIdx]);
133
+ const headCell = selection ? { row: selection.anchor.row, col: selection.anchor.col } : focusedCell;
134
+ const focusedStyle = headCell ? cellStyles[`${headCell.row}:${headCell.col}`] ?? {} : {};
135
+ const hasTarget = !!headCell;
136
+ const updateActiveSheet = useCallback((update) => {
137
+ setSheets((prev) => prev.map((s, i) => i === activeIdx ? { ...s, ...update } : s));
138
+ }, [activeIdx]);
139
+ const handleChange = useCallback((newData) => {
140
+ const maxCols = newData.reduce((m, r) => Math.max(m, r.length), 0);
141
+ if (maxCols !== columns.length) {
142
+ updateActiveSheet({ data: newData, columns: makeColumns(maxCols) });
143
+ } else {
144
+ updateActiveSheet({ data: newData });
145
+ }
146
+ }, [updateActiveSheet, columns.length]);
147
+ const addSheet = () => {
148
+ const name = `Sheet ${sheets.length + 1}`;
149
+ setSheets((prev) => [...prev, newSheet(name)]);
150
+ setActiveIdx(sheets.length);
151
+ };
152
+ const removeSheet = (idx) => {
153
+ if (sheets.length <= 1) return;
154
+ setSheets((prev) => prev.filter((_, i) => i !== idx));
155
+ if (activeIdx >= idx && activeIdx > 0) setActiveIdx(activeIdx - 1);
156
+ };
157
+ const renameSheet = (idx, name) => {
158
+ setSheets((prev) => prev.map((s, i) => i === idx ? { ...s, name } : s));
159
+ setEditingTab(null);
160
+ };
161
+ const addColumns = (count) => {
162
+ const newColCount = columns.length + count;
163
+ updateActiveSheet({
164
+ columns: makeColumns(newColCount),
165
+ data: data.map((row) => [...row, ...Array(count).fill("")])
166
+ });
167
+ };
168
+ const addRows = (count) => {
169
+ updateActiveSheet({
170
+ data: [...data, ...Array.from({ length: count }, () => Array(columns.length).fill(""))]
171
+ });
172
+ };
173
+ const handleClear = () => {
174
+ updateActiveSheet({
175
+ columns: makeColumns(DEFAULT_COLS),
176
+ data: makeEmptyData(DEFAULT_ROWS, DEFAULT_COLS)
177
+ });
178
+ };
179
+ const exportCSV = () => {
180
+ const csv = data.filter((row) => row.some((c) => c.trim())).map((row) => row.map((cell) => {
181
+ if (cell.includes(",") || cell.includes('"') || cell.includes("\n"))
182
+ return `"${cell.replace(/"/g, '""')}"`;
183
+ return cell;
184
+ }).join(",")).join("\n");
185
+ const blob = new Blob([csv], { type: "text/csv" });
186
+ const url = URL.createObjectURL(blob);
187
+ const a = document.createElement("a");
188
+ a.href = url;
189
+ a.download = `${title || "spreadsheet"}.csv`;
190
+ a.click();
191
+ URL.revokeObjectURL(url);
192
+ };
193
+ const importFile = async (e) => {
194
+ const file = e.target.files?.[0];
195
+ if (!file) return;
196
+ const name = file.name.replace(/\.(csv|tsv|txt|xlsx|xls|ods)$/i, "");
197
+ if (/\.(xlsx|xls|ods)$/i.test(file.name)) {
198
+ const byteArr = new Uint8Array(await file.arrayBuffer());
199
+ const XLSX = await import('xlsx');
200
+ const wb = XLSX.read(byteArr, { type: "array" });
201
+ const newSheets = wb.SheetNames.map((sn) => {
202
+ const rows = XLSX.utils.sheet_to_json(wb.Sheets[sn], { header: 1, defval: "" });
203
+ const maxCols = Math.max(DEFAULT_COLS, rows.reduce((m, r) => Math.max(m, r.length), 0));
204
+ const padded = rows.map((r) => {
205
+ const nr = r.map((c) => String(c ?? ""));
206
+ while (nr.length < maxCols) nr.push("");
207
+ return nr;
208
+ });
209
+ while (padded.length < DEFAULT_ROWS) padded.push(Array(maxCols).fill(""));
210
+ return { id: crypto.randomUUID(), name: sn, columns: makeColumns(maxCols), data: padded };
211
+ });
212
+ setSheets(newSheets);
213
+ setActiveIdx(0);
214
+ setTitle(name);
215
+ } else {
216
+ const text = await file.text();
217
+ const parsed = parseCSV(text);
218
+ if (parsed.length === 0) return;
219
+ const maxCols = Math.max(DEFAULT_COLS, parsed.reduce((m, r) => Math.max(m, r.length), 0));
220
+ const padded = parsed.map((r) => {
221
+ while (r.length < maxCols) r.push("");
222
+ return r;
223
+ });
224
+ while (padded.length < DEFAULT_ROWS) padded.push(Array(maxCols).fill(""));
225
+ updateActiveSheet({ columns: makeColumns(maxCols), data: padded });
226
+ setTitle(name);
227
+ }
228
+ if (fileRef.current) fileRef.current.value = "";
229
+ };
230
+ const allNums = [];
231
+ data.forEach((row) => row.forEach((cell) => {
232
+ const v = parseFloat(cell);
233
+ if (!isNaN(v) && cell.trim()) allNums.push(v);
234
+ }));
235
+ const filledCount = data.reduce((c, row) => c + row.filter((cell) => cell.trim()).length, 0);
236
+ return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: "flex flex-col h-full", children: [
237
+ /* @__PURE__ */ jsx(WindowTitle, { title: `${truncateForTitle(title || "Untitled")} - Spreadsheets` }),
238
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-3 py-2 border-b border-gray-200 bg-gray-50 shrink-0", children: [
239
+ editingTitle ? /* @__PURE__ */ jsx(
240
+ "input",
241
+ {
242
+ type: "text",
243
+ value: title,
244
+ onChange: (e) => setTitle(e.target.value),
245
+ onBlur: () => setEditingTitle(false),
246
+ onKeyDown: (e) => {
247
+ if (e.key === "Enter") setEditingTitle(false);
248
+ },
249
+ autoFocus: true,
250
+ className: "text-sm font-medium text-gray-900 border border-gray-300 rounded px-2 py-0.5 w-40 focus:border-blue-500 focus:ring-blue-500"
251
+ }
252
+ ) : /* @__PURE__ */ jsx("button", { onClick: () => setEditingTitle(true), className: "text-sm font-medium text-gray-900 hover:text-blue-600 truncate max-w-[200px]", title: "Click to rename", children: title || "Untitled" }),
253
+ /* @__PURE__ */ jsx("div", { className: "h-4 w-px bg-gray-300" }),
254
+ /* @__PURE__ */ jsx("input", { ref: fileRef, type: "file", accept: ".csv,.tsv,.txt,.xlsx,.xls,.ods", onChange: importFile, className: "hidden" }),
255
+ /* @__PURE__ */ jsx(
256
+ "button",
257
+ {
258
+ onClick: () => fileRef.current?.click(),
259
+ className: "text-xs text-gray-600 hover:text-gray-900 px-2 py-1 rounded hover:bg-gray-200 transition-colors",
260
+ children: "Open"
261
+ }
262
+ ),
263
+ /* @__PURE__ */ jsx(
264
+ "button",
265
+ {
266
+ onClick: exportCSV,
267
+ className: "text-xs text-gray-600 hover:text-gray-900 px-2 py-1 rounded hover:bg-gray-200 transition-colors",
268
+ children: "Save CSV"
269
+ }
270
+ ),
271
+ /* @__PURE__ */ jsx("div", { className: "h-4 w-px bg-gray-300" }),
272
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
273
+ /* @__PURE__ */ jsx(
274
+ "input",
275
+ {
276
+ type: "number",
277
+ min: "1",
278
+ max: "50",
279
+ value: addColCount,
280
+ onChange: (e) => setAddColCount(e.target.value),
281
+ className: "w-10 text-xs text-center border border-gray-300 rounded px-1 py-0.5 focus:border-blue-500 focus:ring-blue-500"
282
+ }
283
+ ),
284
+ /* @__PURE__ */ jsx(
285
+ "button",
286
+ {
287
+ onClick: () => addColumns(parseInt(addColCount) || 1),
288
+ className: "text-xs text-gray-600 hover:text-gray-900 px-2 py-1 rounded hover:bg-gray-200 transition-colors",
289
+ children: "+ Col"
290
+ }
291
+ )
292
+ ] }),
293
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
294
+ /* @__PURE__ */ jsx(
295
+ "input",
296
+ {
297
+ type: "number",
298
+ min: "1",
299
+ max: "500",
300
+ value: addRowCount,
301
+ onChange: (e) => setAddRowCount(e.target.value),
302
+ className: "w-10 text-xs text-center border border-gray-300 rounded px-1 py-0.5 focus:border-blue-500 focus:ring-blue-500"
303
+ }
304
+ ),
305
+ /* @__PURE__ */ jsx(
306
+ "button",
307
+ {
308
+ onClick: () => addRows(parseInt(addRowCount) || 10),
309
+ className: "text-xs text-gray-600 hover:text-gray-900 px-2 py-1 rounded hover:bg-gray-200 transition-colors",
310
+ children: "+ Row"
311
+ }
312
+ )
313
+ ] }),
314
+ /* @__PURE__ */ jsx("div", { className: "h-4 w-px bg-gray-300" }),
315
+ /* @__PURE__ */ jsx(
316
+ "button",
317
+ {
318
+ onClick: handleClear,
319
+ className: "text-xs text-gray-600 hover:text-gray-900 px-2 py-1 rounded hover:bg-gray-200 transition-colors",
320
+ children: "Clear"
321
+ }
322
+ ),
323
+ /* @__PURE__ */ jsx("div", { className: "h-4 w-px bg-gray-300" }),
324
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-0.5", title: hasTarget ? "" : "Click a cell first", children: [
325
+ /* @__PURE__ */ jsx(
326
+ "button",
327
+ {
328
+ onClick: () => toggleCellStyle("bold"),
329
+ disabled: !hasTarget,
330
+ className: `px-2 py-1 text-xs rounded transition-colors font-bold ${focusedStyle.bold ? "bg-blue-100 text-blue-700" : "text-gray-600 hover:bg-gray-200"} disabled:opacity-40 disabled:cursor-not-allowed`,
331
+ children: "B"
332
+ }
333
+ ),
334
+ /* @__PURE__ */ jsx(
335
+ "button",
336
+ {
337
+ onClick: () => toggleCellStyle("italic"),
338
+ disabled: !hasTarget,
339
+ className: `px-2 py-1 text-xs rounded transition-colors italic ${focusedStyle.italic ? "bg-blue-100 text-blue-700" : "text-gray-600 hover:bg-gray-200"} disabled:opacity-40 disabled:cursor-not-allowed`,
340
+ children: "I"
341
+ }
342
+ ),
343
+ /* @__PURE__ */ jsx(
344
+ "button",
345
+ {
346
+ onClick: () => toggleCellStyle("underline"),
347
+ disabled: !hasTarget,
348
+ className: `px-2 py-1 text-xs rounded transition-colors underline ${focusedStyle.underline ? "bg-blue-100 text-blue-700" : "text-gray-600 hover:bg-gray-200"} disabled:opacity-40 disabled:cursor-not-allowed`,
349
+ children: "U"
350
+ }
351
+ ),
352
+ /* @__PURE__ */ jsxs(
353
+ "select",
354
+ {
355
+ value: focusedStyle.fontSize ?? "base",
356
+ onChange: (e) => toggleCellStyle("fontSize", e.target.value),
357
+ disabled: !hasTarget,
358
+ className: "ml-1 text-xs border border-gray-300 rounded px-1 py-0.5 bg-white disabled:opacity-40 disabled:cursor-not-allowed",
359
+ children: [
360
+ /* @__PURE__ */ jsx("option", { value: "sm", children: "XS" }),
361
+ /* @__PURE__ */ jsx("option", { value: "base", children: "S" }),
362
+ /* @__PURE__ */ jsx("option", { value: "lg", children: "M" }),
363
+ /* @__PURE__ */ jsx("option", { value: "xl", children: "L" })
364
+ ]
365
+ }
366
+ )
367
+ ] })
368
+ ] }),
369
+ /* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsx(
370
+ EditableGrid,
371
+ {
372
+ columns,
373
+ data,
374
+ onChange: handleChange,
375
+ onColumnsChange: (newCols) => updateActiveSheet({ columns: newCols }),
376
+ cellStyles,
377
+ onFocusChange: setFocusedCell,
378
+ onSelectionChange: setSelection,
379
+ minRows: DEFAULT_ROWS,
380
+ maxHeight: "100%"
381
+ }
382
+ ) }),
383
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center border-t border-gray-200 bg-gray-50 shrink-0", children: [
384
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-0.5 px-1 py-1 overflow-x-auto flex-1 min-w-0", children: [
385
+ sheets.map((sheet, idx) => /* @__PURE__ */ jsx(
386
+ "button",
387
+ {
388
+ onClick: () => setActiveIdx(idx),
389
+ onDoubleClick: () => {
390
+ setEditingTab(idx);
391
+ setTabName(sheet.name);
392
+ },
393
+ onContextMenu: (e) => {
394
+ e.preventDefault();
395
+ if (sheets.length > 1) removeSheet(idx);
396
+ },
397
+ className: `px-3 py-1 text-xs font-medium rounded-b whitespace-nowrap transition-colors ${idx === activeIdx ? "bg-white text-blue-700 border border-t-0 border-gray-300 -mt-px relative z-10" : "text-gray-500 hover:text-gray-700 hover:bg-gray-100"}`,
398
+ children: editingTab === idx ? /* @__PURE__ */ jsx(
399
+ "input",
400
+ {
401
+ type: "text",
402
+ value: tabName,
403
+ onChange: (e) => setTabName(e.target.value),
404
+ onBlur: () => renameSheet(idx, tabName || sheet.name),
405
+ onKeyDown: (e) => {
406
+ if (e.key === "Enter") renameSheet(idx, tabName || sheet.name);
407
+ if (e.key === "Escape") setEditingTab(null);
408
+ },
409
+ onClick: (e) => e.stopPropagation(),
410
+ autoFocus: true,
411
+ className: "w-20 text-xs border border-blue-400 rounded px-1 py-0 focus:ring-0 focus:outline-none"
412
+ }
413
+ ) : sheet.name
414
+ },
415
+ sheet.id
416
+ )),
417
+ /* @__PURE__ */ jsx("button", { onClick: addSheet, className: "px-2 py-1 text-xs text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded", title: "Add sheet", children: "+" })
418
+ ] }),
419
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4 px-3 py-1 text-xs text-gray-500 shrink-0 border-l border-gray-200", children: [
420
+ /* @__PURE__ */ jsxs("span", { children: [
421
+ filledCount,
422
+ " cells"
423
+ ] }),
424
+ allNums.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
425
+ /* @__PURE__ */ jsxs("span", { children: [
426
+ "Sum: ",
427
+ allNums.reduce((s, v) => s + v, 0).toLocaleString(void 0, { maximumFractionDigits: 2 })
428
+ ] }),
429
+ /* @__PURE__ */ jsxs("span", { children: [
430
+ "Avg: ",
431
+ (allNums.reduce((s, v) => s + v, 0) / allNums.length).toLocaleString(void 0, { maximumFractionDigits: 2 })
432
+ ] })
433
+ ] }),
434
+ /* @__PURE__ */ jsxs("span", { children: [
435
+ data.length,
436
+ " \xD7 ",
437
+ columns.length
438
+ ] })
439
+ ] })
440
+ ] })
441
+ ] });
442
+ }
443
+
444
+ export { Spreadsheet as default };
445
+ //# sourceMappingURL=Spreadsheet-IOKEDNS6.js.map
446
+ //# sourceMappingURL=Spreadsheet-IOKEDNS6.js.map