@velo-sci/notebook-react 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,2954 @@
1
+ // src/hooks.ts
2
+ import { createContext, useContext, useEffect, useState } from "react";
3
+ var NotebookContext = createContext(null);
4
+ function useSciNotebook() {
5
+ const engine = useContext(NotebookContext);
6
+ if (!engine) {
7
+ throw new Error("useSciNotebook must be used within a NotebookProvider or SciNotebook component");
8
+ }
9
+ return engine;
10
+ }
11
+ function useNotebook() {
12
+ const engine = useSciNotebook();
13
+ const [notebook, setNotebook] = useState(engine.getNotebook());
14
+ useEffect(() => {
15
+ return engine.on("notebook:updated", (payload) => {
16
+ setNotebook({ ...payload.data.notebook });
17
+ });
18
+ }, [engine]);
19
+ return notebook;
20
+ }
21
+ function useCell(cellId) {
22
+ const engine = useSciNotebook();
23
+ const [, forceUpdate] = useState(0);
24
+ useEffect(() => {
25
+ const handler = (payload) => {
26
+ if (payload.data.cellId === cellId || payload.type === "notebook:updated") {
27
+ forceUpdate((v) => v + 1);
28
+ }
29
+ };
30
+ const unsubUpdated = engine.on("cell:updated", handler);
31
+ const unsubMode = engine.on("cell:mode-changed", handler);
32
+ const unsubNb = engine.on("notebook:updated", handler);
33
+ return () => {
34
+ unsubUpdated();
35
+ unsubMode();
36
+ unsubNb();
37
+ };
38
+ }, [engine, cellId]);
39
+ return engine.getCell(cellId);
40
+ }
41
+ function useFocusedCell() {
42
+ const engine = useSciNotebook();
43
+ const [focusedId, setFocusedId] = useState(null);
44
+ useEffect(() => {
45
+ return engine.on("cell:focused", (payload) => {
46
+ setFocusedId(payload.data.cellId);
47
+ });
48
+ }, [engine]);
49
+ return focusedId;
50
+ }
51
+ function useNotebookEvent(type, handler) {
52
+ const engine = useSciNotebook();
53
+ useEffect(() => {
54
+ return engine.on(type, handler);
55
+ }, [engine, type, handler]);
56
+ }
57
+
58
+ // src/components/SciNotebook.tsx
59
+ import React12, { useMemo as useMemo6, useEffect as useEffect10, useState as useState12, useCallback as useCallback11 } from "react";
60
+ import { createNotebook } from "@velo-sci/notebook-core";
61
+ import { RenderPipeline } from "@velo-sci/notebook-renderer";
62
+
63
+ // src/components/Cell.tsx
64
+ import { useMemo as useMemo3, useCallback as useCallback8, useRef as useRef6, useEffect as useEffect7, useState as useState9 } from "react";
65
+
66
+ // src/components/FloatingToolbar.tsx
67
+ import { useEffect as useEffect2, useRef, useState as useState2, useCallback } from "react";
68
+ import { jsx } from "react/jsx-runtime";
69
+ var FORMAT_ACTIONS = [
70
+ { label: "B", title: "Bold (Ctrl+B)", wrap: ["**", "**"], style: "font-weight:700" },
71
+ { label: "I", title: "Italic (Ctrl+I)", wrap: ["*", "*"], style: "font-style:italic" },
72
+ { label: "S", title: "Strikethrough", wrap: ["~~", "~~"], style: "text-decoration:line-through" },
73
+ { label: "<>", title: "Inline code", wrap: ["`", "`"], style: "font-family:monospace;font-size:12px" },
74
+ { label: "H1", title: "Heading 1", prefix: "# ", wrap: null, style: "font-weight:700;font-size:12px" },
75
+ { label: "H2", title: "Heading 2", prefix: "## ", wrap: null, style: "font-weight:700;font-size:11px" },
76
+ { label: "\u{1F517}", title: "Link", wrap: ["[", "](url)"], style: "" },
77
+ { label: "\u2022", title: "Bullet list", prefix: "- ", wrap: null, style: "font-size:16px" }
78
+ ];
79
+ var FloatingToolbar = ({ cellId, textareaRef }) => {
80
+ const engine = useSciNotebook();
81
+ const toolbarRef = useRef(null);
82
+ const [pos, setPos] = useState2({ top: 0, left: 0, visible: false });
83
+ const updatePosition = useCallback(() => {
84
+ const ta = textareaRef.current;
85
+ if (!ta) return;
86
+ const start = ta.selectionStart;
87
+ const end = ta.selectionEnd;
88
+ if (start === end) {
89
+ setPos((p) => ({ ...p, visible: false }));
90
+ return;
91
+ }
92
+ const taRect = ta.getBoundingClientRect();
93
+ setPos({
94
+ top: taRect.top - 44,
95
+ left: taRect.left + taRect.width / 2,
96
+ visible: true
97
+ });
98
+ }, [textareaRef]);
99
+ useEffect2(() => {
100
+ const ta = textareaRef.current;
101
+ if (!ta) return;
102
+ const onSelect = () => {
103
+ requestAnimationFrame(updatePosition);
104
+ };
105
+ ta.addEventListener("select", onSelect);
106
+ ta.addEventListener("mouseup", onSelect);
107
+ ta.addEventListener("keyup", onSelect);
108
+ document.addEventListener("mousedown", (e) => {
109
+ if (toolbarRef.current && !toolbarRef.current.contains(e.target) && e.target !== ta) {
110
+ setPos((p) => ({ ...p, visible: false }));
111
+ }
112
+ });
113
+ return () => {
114
+ ta.removeEventListener("select", onSelect);
115
+ ta.removeEventListener("mouseup", onSelect);
116
+ ta.removeEventListener("keyup", onSelect);
117
+ };
118
+ }, [textareaRef, updatePosition]);
119
+ const applyFormat = useCallback((action) => {
120
+ const ta = textareaRef.current;
121
+ if (!ta) return;
122
+ const start = ta.selectionStart;
123
+ const end = ta.selectionEnd;
124
+ const source = ta.value;
125
+ const selected = source.slice(start, end);
126
+ let newSource;
127
+ let newCursorStart;
128
+ let newCursorEnd;
129
+ if (action.wrap) {
130
+ const [before, after] = action.wrap;
131
+ newSource = source.slice(0, start) + before + selected + after + source.slice(end);
132
+ newCursorStart = start + before.length;
133
+ newCursorEnd = end + before.length;
134
+ } else if ("prefix" in action && action.prefix) {
135
+ const lineStart = source.lastIndexOf("\n", start - 1) + 1;
136
+ const lineEnd = source.indexOf("\n", end);
137
+ const actualEnd = lineEnd === -1 ? source.length : lineEnd;
138
+ const lines = source.slice(lineStart, actualEnd).split("\n");
139
+ const prefixed = lines.map((l) => action.prefix + l).join("\n");
140
+ newSource = source.slice(0, lineStart) + prefixed + source.slice(actualEnd);
141
+ newCursorStart = start + action.prefix.length;
142
+ newCursorEnd = end + action.prefix.length * lines.length;
143
+ } else {
144
+ return;
145
+ }
146
+ engine.updateCellSource(cellId, newSource);
147
+ requestAnimationFrame(() => {
148
+ if (ta) {
149
+ ta.focus();
150
+ ta.setSelectionRange(newCursorStart, newCursorEnd);
151
+ }
152
+ });
153
+ }, [engine, cellId, textareaRef]);
154
+ if (!pos.visible) return null;
155
+ return /* @__PURE__ */ jsx(
156
+ "div",
157
+ {
158
+ ref: toolbarRef,
159
+ className: "sci-nb-floating-toolbar",
160
+ style: {
161
+ position: "fixed",
162
+ top: `${pos.top}px`,
163
+ left: `${pos.left}px`,
164
+ transform: "translateX(-50%)"
165
+ },
166
+ onMouseDown: (e) => e.preventDefault(),
167
+ children: FORMAT_ACTIONS.map((action, i) => /* @__PURE__ */ jsx(
168
+ "button",
169
+ {
170
+ className: "sci-nb-ft-btn",
171
+ title: action.title,
172
+ style: action.style ? { ...parseInlineStyle(action.style) } : void 0,
173
+ onClick: () => applyFormat(action),
174
+ children: action.label
175
+ },
176
+ i
177
+ ))
178
+ }
179
+ );
180
+ };
181
+ function parseInlineStyle(css) {
182
+ const style = {};
183
+ css.split(";").forEach((pair) => {
184
+ const [key, val] = pair.split(":");
185
+ if (key && val) {
186
+ const camelKey = key.trim().replace(/-([a-z])/g, (_, c) => c.toUpperCase());
187
+ style[camelKey] = val.trim();
188
+ }
189
+ });
190
+ return style;
191
+ }
192
+
193
+ // src/components/MathEditor.tsx
194
+ import { useState as useState3, useCallback as useCallback2, useRef as useRef2, useEffect as useEffect3 } from "react";
195
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
196
+ var MATH_CATEGORIES = [
197
+ {
198
+ name: "Estructuras",
199
+ icon: "\u2B1A",
200
+ blocks: [
201
+ { label: "a/b", latex: "\\frac{\u25A2}{\u25A2}", cursor: 6 },
202
+ { label: "\u221A", latex: "\\sqrt{\u25A2}", cursor: 6 },
203
+ { label: "\u207F\u221A", latex: "\\sqrt[\u25A2]{\u25A2}", cursor: 6 },
204
+ { label: "x\u207F", latex: "{\u25A2}^{\u25A2}", cursor: 1 },
205
+ { label: "x\u2099", latex: "{\u25A2}_{\u25A2}", cursor: 1 },
206
+ { label: "x\u0302", latex: "\\hat{\u25A2}", cursor: 5 },
207
+ { label: "x\u0304", latex: "\\bar{\u25A2}", cursor: 5 },
208
+ { label: "x\u20D7", latex: "\\vec{\u25A2}", cursor: 5 },
209
+ { label: "x\u0303", latex: "\\tilde{\u25A2}", cursor: 7 },
210
+ { label: "\u1E8B", latex: "\\dot{\u25A2}", cursor: 5 },
211
+ { label: "\u1E8D", latex: "\\ddot{\u25A2}", cursor: 6 }
212
+ ]
213
+ },
214
+ {
215
+ name: "Integrales",
216
+ icon: "\u222B",
217
+ blocks: [
218
+ { label: "\u222B", latex: "\\int{\u25A2}\\,d{\u25A2}", cursor: 5 },
219
+ { label: "\u222B\u2090\u1D47", latex: "\\int_{\u25A2}^{\u25A2}{\u25A2}\\,d{\u25A2}", cursor: 5 },
220
+ { label: "\u222C", latex: "\\iint{\u25A2}", cursor: 6 },
221
+ { label: "\u222D", latex: "\\iiint{\u25A2}", cursor: 7 },
222
+ { label: "\u222E", latex: "\\oint{\u25A2}", cursor: 6 }
223
+ ]
224
+ },
225
+ {
226
+ name: "Sumatorias",
227
+ icon: "\u2211",
228
+ blocks: [
229
+ { label: "\u2211", latex: "\\sum_{\u25A2}^{\u25A2}{\u25A2}", cursor: 5 },
230
+ { label: "\u220F", latex: "\\prod_{\u25A2}^{\u25A2}{\u25A2}", cursor: 6 },
231
+ { label: "lim", latex: "\\lim_{\u25A2 \\to \u25A2}{\u25A2}", cursor: 5 },
232
+ { label: "\u2211\u2099", latex: "\\sum_{n=\u25A2}^{\u25A2}{\u25A2}", cursor: 7 }
233
+ ]
234
+ },
235
+ {
236
+ name: "Matrices",
237
+ icon: "[ ]",
238
+ blocks: [
239
+ { label: "2\xD72", latex: "\\begin{pmatrix} \u25A2 & \u25A2 \\\\ \u25A2 & \u25A2 \\end{pmatrix}", cursor: 16 },
240
+ { label: "3\xD73", latex: "\\begin{pmatrix} \u25A2 & \u25A2 & \u25A2 \\\\ \u25A2 & \u25A2 & \u25A2 \\\\ \u25A2 & \u25A2 & \u25A2 \\end{pmatrix}", cursor: 16 },
241
+ { label: "[2\xD72]", latex: "\\begin{bmatrix} \u25A2 & \u25A2 \\\\ \u25A2 & \u25A2 \\end{bmatrix}", cursor: 16 },
242
+ { label: "|2\xD72|", latex: "\\begin{vmatrix} \u25A2 & \u25A2 \\\\ \u25A2 & \u25A2 \\end{vmatrix}", cursor: 16 },
243
+ { label: "cases", latex: "\\begin{cases} \u25A2 & \\text{si } \u25A2 \\\\ \u25A2 & \\text{si } \u25A2 \\end{cases}", cursor: 14 }
244
+ ]
245
+ },
246
+ {
247
+ name: "Griegos",
248
+ icon: "\u03B1",
249
+ blocks: [
250
+ { label: "\u03B1", latex: "\\alpha" },
251
+ { label: "\u03B2", latex: "\\beta" },
252
+ { label: "\u03B3", latex: "\\gamma" },
253
+ { label: "\u03B4", latex: "\\delta" },
254
+ { label: "\u03B5", latex: "\\epsilon" },
255
+ { label: "\u03B6", latex: "\\zeta" },
256
+ { label: "\u03B7", latex: "\\eta" },
257
+ { label: "\u03B8", latex: "\\theta" },
258
+ { label: "\u03BB", latex: "\\lambda" },
259
+ { label: "\u03BC", latex: "\\mu" },
260
+ { label: "\u03C0", latex: "\\pi" },
261
+ { label: "\u03C1", latex: "\\rho" },
262
+ { label: "\u03C3", latex: "\\sigma" },
263
+ { label: "\u03C4", latex: "\\tau" },
264
+ { label: "\u03C6", latex: "\\phi" },
265
+ { label: "\u03C8", latex: "\\psi" },
266
+ { label: "\u03C9", latex: "\\omega" },
267
+ { label: "\u0393", latex: "\\Gamma" },
268
+ { label: "\u0394", latex: "\\Delta" },
269
+ { label: "\u0398", latex: "\\Theta" },
270
+ { label: "\u039B", latex: "\\Lambda" },
271
+ { label: "\u03A3", latex: "\\Sigma" },
272
+ { label: "\u03A6", latex: "\\Phi" },
273
+ { label: "\u03A8", latex: "\\Psi" },
274
+ { label: "\u03A9", latex: "\\Omega" }
275
+ ]
276
+ },
277
+ {
278
+ name: "Operadores",
279
+ icon: "\xB1",
280
+ blocks: [
281
+ { label: "\xB1", latex: "\\pm" },
282
+ { label: "\u2213", latex: "\\mp" },
283
+ { label: "\xD7", latex: "\\times" },
284
+ { label: "\xF7", latex: "\\div" },
285
+ { label: "\xB7", latex: "\\cdot" },
286
+ { label: "\u2218", latex: "\\circ" },
287
+ { label: "\u2297", latex: "\\otimes" },
288
+ { label: "\u2295", latex: "\\oplus" },
289
+ { label: "\u2202", latex: "\\partial" },
290
+ { label: "\u2207", latex: "\\nabla" },
291
+ { label: "\u221E", latex: "\\infty" },
292
+ { label: "\u2248", latex: "\\approx" },
293
+ { label: "\u2260", latex: "\\neq" },
294
+ { label: "\u2264", latex: "\\leq" },
295
+ { label: "\u2265", latex: "\\geq" },
296
+ { label: "\u2261", latex: "\\equiv" },
297
+ { label: "\u221D", latex: "\\propto" },
298
+ { label: "\u2208", latex: "\\in" },
299
+ { label: "\u2209", latex: "\\notin" },
300
+ { label: "\u2282", latex: "\\subset" },
301
+ { label: "\u2283", latex: "\\supset" },
302
+ { label: "\u222A", latex: "\\cup" },
303
+ { label: "\u2229", latex: "\\cap" },
304
+ { label: "\u2205", latex: "\\emptyset" },
305
+ { label: "\u2200", latex: "\\forall" },
306
+ { label: "\u2203", latex: "\\exists" }
307
+ ]
308
+ },
309
+ {
310
+ name: "Flechas",
311
+ icon: "\u2192",
312
+ blocks: [
313
+ { label: "\u2192", latex: "\\rightarrow" },
314
+ { label: "\u2190", latex: "\\leftarrow" },
315
+ { label: "\u2194", latex: "\\leftrightarrow" },
316
+ { label: "\u21D2", latex: "\\Rightarrow" },
317
+ { label: "\u21D0", latex: "\\Leftarrow" },
318
+ { label: "\u21D4", latex: "\\Leftrightarrow" },
319
+ { label: "\u21A6", latex: "\\mapsto" },
320
+ { label: "\u2191", latex: "\\uparrow" },
321
+ { label: "\u2193", latex: "\\downarrow" }
322
+ ]
323
+ },
324
+ {
325
+ name: "Funciones",
326
+ icon: "f(x)",
327
+ blocks: [
328
+ { label: "sin", latex: "\\sin{\u25A2}", cursor: 5 },
329
+ { label: "cos", latex: "\\cos{\u25A2}", cursor: 5 },
330
+ { label: "tan", latex: "\\tan{\u25A2}", cursor: 5 },
331
+ { label: "log", latex: "\\log{\u25A2}", cursor: 5 },
332
+ { label: "ln", latex: "\\ln{\u25A2}", cursor: 4 },
333
+ { label: "exp", latex: "\\exp{\u25A2}", cursor: 5 },
334
+ { label: "lim", latex: "\\lim_{\u25A2}{\u25A2}", cursor: 5 },
335
+ { label: "max", latex: "\\max{\u25A2}", cursor: 5 },
336
+ { label: "min", latex: "\\min{\u25A2}", cursor: 5 },
337
+ { label: "det", latex: "\\det{\u25A2}", cursor: 5 }
338
+ ]
339
+ },
340
+ {
341
+ name: "Delimitadores",
342
+ icon: "()",
343
+ blocks: [
344
+ { label: "(\u2026)", latex: "\\left( \u25A2 \\right)", cursor: 7 },
345
+ { label: "[\u2026]", latex: "\\left[ \u25A2 \\right]", cursor: 7 },
346
+ { label: "{\u2026}", latex: "\\left\\{ \u25A2 \\right\\}", cursor: 8 },
347
+ { label: "|\u2026|", latex: "\\left| \u25A2 \\right|", cursor: 7 },
348
+ { label: "\u2016\u2026\u2016", latex: "\\left\\| \u25A2 \\right\\|", cursor: 8 },
349
+ { label: "\u230A\u2026\u230B", latex: "\\lfloor \u25A2 \\rfloor", cursor: 8 },
350
+ { label: "\u2308\u2026\u2309", latex: "\\lceil \u25A2 \\rceil", cursor: 7 }
351
+ ]
352
+ }
353
+ ];
354
+ function renderLatexPreview(latex) {
355
+ const clean = latex.replace(/^\$\$\s*/, "").replace(/\s*\$\$$/, "").trim();
356
+ if (!clean) return '<span class="sci-nb-math-preview-empty">Formula vacia</span>';
357
+ if (typeof globalThis !== "undefined" && globalThis.katex) {
358
+ try {
359
+ return globalThis.katex.renderToString(clean, {
360
+ displayMode: true,
361
+ throwOnError: false
362
+ });
363
+ } catch {
364
+ }
365
+ }
366
+ return `<code class="sci-nb-math-preview-code">${escapeHtml(clean)}</code>`;
367
+ }
368
+ function escapeHtml(s) {
369
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
370
+ }
371
+ var MathEditor = ({ cellId, source, onExit }) => {
372
+ const engine = useSciNotebook();
373
+ const textareaRef = useRef2(null);
374
+ const [activeCategory, setActiveCategory] = useState3(0);
375
+ const [showRaw, setShowRaw] = useState3(false);
376
+ const innerLatex = source.replace(/^\$\$\s*/, "").replace(/\s*\$\$$/, "").trim();
377
+ const updateSource = useCallback2((newInner) => {
378
+ engine.updateCellSource(cellId, `$$
379
+ ${newInner}
380
+ $$`);
381
+ }, [engine, cellId]);
382
+ const handleRawChange = useCallback2((e) => {
383
+ updateSource(e.target.value);
384
+ }, [updateSource]);
385
+ const insertBlock = useCallback2((block) => {
386
+ const ta = textareaRef.current;
387
+ if (!ta) {
388
+ updateSource(innerLatex + (innerLatex ? " " : "") + block.latex.replace(/▢/g, ""));
389
+ return;
390
+ }
391
+ const start = ta.selectionStart;
392
+ const end = ta.selectionEnd;
393
+ const val = ta.value;
394
+ const selected = val.slice(start, end);
395
+ let inserted = block.latex;
396
+ if (selected) {
397
+ inserted = inserted.replace("\u25A2", selected);
398
+ }
399
+ inserted = inserted.replace(/▢/g, "");
400
+ const newVal = val.slice(0, start) + inserted + val.slice(end);
401
+ updateSource(newVal);
402
+ requestAnimationFrame(() => {
403
+ if (textareaRef.current) {
404
+ const cursorPos = block.cursor != null ? start + block.cursor : start + inserted.length;
405
+ textareaRef.current.focus();
406
+ textareaRef.current.setSelectionRange(cursorPos, cursorPos);
407
+ }
408
+ });
409
+ }, [innerLatex, updateSource]);
410
+ const handleKeyDown = useCallback2((e) => {
411
+ if (e.key === "Escape") {
412
+ e.preventDefault();
413
+ onExit();
414
+ } else if (e.key === "Enter" && e.shiftKey) {
415
+ e.preventDefault();
416
+ onExit();
417
+ }
418
+ }, [onExit]);
419
+ useEffect3(() => {
420
+ if (showRaw && textareaRef.current) {
421
+ const ta = textareaRef.current;
422
+ ta.style.height = "auto";
423
+ ta.style.height = `${Math.max(60, ta.scrollHeight)}px`;
424
+ }
425
+ }, [innerLatex, showRaw]);
426
+ const category = MATH_CATEGORIES[activeCategory];
427
+ return /* @__PURE__ */ jsxs("div", { className: "sci-nb-math-editor", onKeyDown: handleKeyDown, children: [
428
+ /* @__PURE__ */ jsx2("div", { className: "sci-nb-math-tabs", children: MATH_CATEGORIES.map((cat, i) => /* @__PURE__ */ jsxs(
429
+ "button",
430
+ {
431
+ className: `sci-nb-math-tab ${i === activeCategory ? "sci-nb-math-tab--active" : ""}`,
432
+ onClick: () => setActiveCategory(i),
433
+ title: cat.name,
434
+ children: [
435
+ /* @__PURE__ */ jsx2("span", { className: "sci-nb-math-tab-icon", children: cat.icon }),
436
+ /* @__PURE__ */ jsx2("span", { className: "sci-nb-math-tab-label", children: cat.name })
437
+ ]
438
+ },
439
+ cat.name
440
+ )) }),
441
+ /* @__PURE__ */ jsx2("div", { className: "sci-nb-math-palette", children: category.blocks.map((block, i) => /* @__PURE__ */ jsx2(
442
+ "button",
443
+ {
444
+ className: "sci-nb-math-block",
445
+ onClick: () => insertBlock(block),
446
+ title: block.latex,
447
+ children: block.label
448
+ },
449
+ i
450
+ )) }),
451
+ /* @__PURE__ */ jsxs("div", { className: "sci-nb-math-editor-area", children: [
452
+ /* @__PURE__ */ jsxs("div", { className: "sci-nb-math-mode-toggle", children: [
453
+ /* @__PURE__ */ jsx2(
454
+ "button",
455
+ {
456
+ className: `sci-nb-math-mode-btn ${!showRaw ? "sci-nb-math-mode-btn--active" : ""}`,
457
+ onClick: () => setShowRaw(false),
458
+ children: "Preview"
459
+ }
460
+ ),
461
+ /* @__PURE__ */ jsx2(
462
+ "button",
463
+ {
464
+ className: `sci-nb-math-mode-btn ${showRaw ? "sci-nb-math-mode-btn--active" : ""}`,
465
+ onClick: () => setShowRaw(true),
466
+ children: "LaTeX"
467
+ }
468
+ )
469
+ ] }),
470
+ showRaw ? /* @__PURE__ */ jsx2(
471
+ "textarea",
472
+ {
473
+ ref: textareaRef,
474
+ className: "sci-nb-math-raw",
475
+ value: innerLatex,
476
+ onChange: handleRawChange,
477
+ placeholder: "Escribe LaTeX aqui...",
478
+ spellCheck: false,
479
+ autoFocus: true
480
+ }
481
+ ) : /* @__PURE__ */ jsxs("div", { className: "sci-nb-math-visual", children: [
482
+ /* @__PURE__ */ jsx2(
483
+ "div",
484
+ {
485
+ className: "sci-nb-math-preview",
486
+ dangerouslySetInnerHTML: { __html: renderLatexPreview(source) }
487
+ }
488
+ ),
489
+ /* @__PURE__ */ jsxs("p", { className: "sci-nb-math-visual-hint", children: [
490
+ "Haz click en los bloques de arriba para construir tu formula. Cambia a modo ",
491
+ /* @__PURE__ */ jsx2("strong", { children: "LaTeX" }),
492
+ " para editar directamente."
493
+ ] })
494
+ ] })
495
+ ] }),
496
+ /* @__PURE__ */ jsxs("div", { className: "sci-nb-cell-hint", children: [
497
+ /* @__PURE__ */ jsx2("kbd", { children: "Esc" }),
498
+ " salir \xB7 ",
499
+ /* @__PURE__ */ jsx2("kbd", { children: "Shift+Enter" }),
500
+ " siguiente"
501
+ ] })
502
+ ] });
503
+ };
504
+
505
+ // src/components/ImageCell.tsx
506
+ import { useState as useState4, useCallback as useCallback3, useRef as useRef3, useEffect as useEffect4 } from "react";
507
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
508
+ function parseImageSource(source, metadata) {
509
+ return {
510
+ src: source || "",
511
+ alt: metadata.alt || "",
512
+ caption: metadata.caption || "",
513
+ width: metadata.width || "100%",
514
+ align: metadata.align || "center"
515
+ };
516
+ }
517
+ var ImageCell = ({ cellId, source, metadata, onExit }) => {
518
+ const engine = useSciNotebook();
519
+ const fileInputRef = useRef3(null);
520
+ const [data, setData] = useState4(() => parseImageSource(source, metadata));
521
+ const [dragOver, setDragOver] = useState4(false);
522
+ const save = useCallback3((updates) => {
523
+ const next = { ...data, ...updates };
524
+ setData(next);
525
+ engine.updateCellSource(cellId, next.src);
526
+ engine.updateCellMetadata(cellId, {
527
+ alt: next.alt,
528
+ caption: next.caption,
529
+ width: next.width,
530
+ align: next.align
531
+ });
532
+ }, [engine, cellId, data]);
533
+ const handleFileSelect = useCallback3((file) => {
534
+ if (!file.type.startsWith("image/")) return;
535
+ const reader = new FileReader();
536
+ reader.onload = (e) => {
537
+ const dataUrl = e.target?.result;
538
+ save({ src: dataUrl });
539
+ };
540
+ reader.readAsDataURL(file);
541
+ }, [save]);
542
+ const handleDrop = useCallback3((e) => {
543
+ e.preventDefault();
544
+ setDragOver(false);
545
+ const file = e.dataTransfer.files[0];
546
+ if (file) handleFileSelect(file);
547
+ }, [handleFileSelect]);
548
+ const handleDragOver = useCallback3((e) => {
549
+ e.preventDefault();
550
+ setDragOver(true);
551
+ }, []);
552
+ const handleKeyDown = useCallback3((e) => {
553
+ if (e.key === "Escape") {
554
+ e.preventDefault();
555
+ onExit();
556
+ }
557
+ }, [onExit]);
558
+ useEffect4(() => {
559
+ const handlePaste = (e) => {
560
+ const items = e.clipboardData?.items;
561
+ if (!items) return;
562
+ for (const item of Array.from(items)) {
563
+ if (item.type.startsWith("image/")) {
564
+ e.preventDefault();
565
+ const file = item.getAsFile();
566
+ if (file) handleFileSelect(file);
567
+ return;
568
+ }
569
+ }
570
+ };
571
+ document.addEventListener("paste", handlePaste);
572
+ return () => document.removeEventListener("paste", handlePaste);
573
+ }, [handleFileSelect]);
574
+ const hasSrc = !!data.src.trim();
575
+ return /* @__PURE__ */ jsxs2("div", { className: "sci-nb-image-editor", onKeyDown: handleKeyDown, children: [
576
+ hasSrc ? /* @__PURE__ */ jsxs2("div", { className: "sci-nb-image-preview", style: { textAlign: data.align }, children: [
577
+ /* @__PURE__ */ jsx3(
578
+ "img",
579
+ {
580
+ src: data.src,
581
+ alt: data.alt,
582
+ style: { maxWidth: data.width, width: "auto", maxHeight: "400px" }
583
+ }
584
+ ),
585
+ data.caption && /* @__PURE__ */ jsx3("p", { className: "sci-nb-image-caption", children: data.caption })
586
+ ] }) : /* @__PURE__ */ jsxs2(
587
+ "div",
588
+ {
589
+ className: `sci-nb-image-dropzone ${dragOver ? "sci-nb-image-dropzone--active" : ""}`,
590
+ onDrop: handleDrop,
591
+ onDragOver: handleDragOver,
592
+ onDragLeave: () => setDragOver(false),
593
+ onClick: () => fileInputRef.current?.click(),
594
+ children: [
595
+ /* @__PURE__ */ jsxs2("svg", { width: "32", height: "32", viewBox: "0 0 32 32", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: [
596
+ /* @__PURE__ */ jsx3("rect", { x: "4", y: "4", width: "24", height: "24", rx: "3" }),
597
+ /* @__PURE__ */ jsx3("circle", { cx: "12", cy: "12", r: "2.5" }),
598
+ /* @__PURE__ */ jsx3("path", { d: "M4 22l6-6 4 4 4-4 10 10", strokeLinejoin: "round" })
599
+ ] }),
600
+ /* @__PURE__ */ jsx3("p", { children: "Arrastra, pega (Ctrl+V) o haz click para seleccionar" }),
601
+ /* @__PURE__ */ jsx3(
602
+ "input",
603
+ {
604
+ ref: fileInputRef,
605
+ type: "file",
606
+ accept: "image/*",
607
+ style: { display: "none" },
608
+ onChange: (e) => {
609
+ const file = e.target.files?.[0];
610
+ if (file) handleFileSelect(file);
611
+ }
612
+ }
613
+ )
614
+ ]
615
+ }
616
+ ),
617
+ /* @__PURE__ */ jsxs2("div", { className: "sci-nb-image-controls", children: [
618
+ /* @__PURE__ */ jsxs2("div", { className: "sci-nb-image-field", children: [
619
+ /* @__PURE__ */ jsx3("label", { children: "URL" }),
620
+ /* @__PURE__ */ jsx3(
621
+ "input",
622
+ {
623
+ type: "text",
624
+ value: data.src.startsWith("data:") ? "(archivo local)" : data.src,
625
+ onChange: (e) => save({ src: e.target.value }),
626
+ placeholder: "https://example.com/image.png",
627
+ disabled: data.src.startsWith("data:")
628
+ }
629
+ )
630
+ ] }),
631
+ /* @__PURE__ */ jsxs2("div", { className: "sci-nb-image-field", children: [
632
+ /* @__PURE__ */ jsx3("label", { children: "Alt text" }),
633
+ /* @__PURE__ */ jsx3(
634
+ "input",
635
+ {
636
+ type: "text",
637
+ value: data.alt,
638
+ onChange: (e) => save({ alt: e.target.value }),
639
+ placeholder: "Descripcion de la imagen"
640
+ }
641
+ )
642
+ ] }),
643
+ /* @__PURE__ */ jsxs2("div", { className: "sci-nb-image-field", children: [
644
+ /* @__PURE__ */ jsx3("label", { children: "Caption" }),
645
+ /* @__PURE__ */ jsx3(
646
+ "input",
647
+ {
648
+ type: "text",
649
+ value: data.caption,
650
+ onChange: (e) => save({ caption: e.target.value }),
651
+ placeholder: "Pie de imagen (opcional)"
652
+ }
653
+ )
654
+ ] }),
655
+ /* @__PURE__ */ jsxs2("div", { className: "sci-nb-image-row", children: [
656
+ /* @__PURE__ */ jsxs2("div", { className: "sci-nb-image-field sci-nb-image-field--small", children: [
657
+ /* @__PURE__ */ jsx3("label", { children: "Ancho" }),
658
+ /* @__PURE__ */ jsxs2("select", { value: data.width, onChange: (e) => save({ width: e.target.value }), children: [
659
+ /* @__PURE__ */ jsx3("option", { value: "25%", children: "25%" }),
660
+ /* @__PURE__ */ jsx3("option", { value: "50%", children: "50%" }),
661
+ /* @__PURE__ */ jsx3("option", { value: "75%", children: "75%" }),
662
+ /* @__PURE__ */ jsx3("option", { value: "100%", children: "100%" }),
663
+ /* @__PURE__ */ jsx3("option", { value: "auto", children: "Auto" })
664
+ ] })
665
+ ] }),
666
+ /* @__PURE__ */ jsxs2("div", { className: "sci-nb-image-field sci-nb-image-field--small", children: [
667
+ /* @__PURE__ */ jsx3("label", { children: "Alinear" }),
668
+ /* @__PURE__ */ jsxs2("select", { value: data.align, onChange: (e) => save({ align: e.target.value }), children: [
669
+ /* @__PURE__ */ jsx3("option", { value: "left", children: "Izquierda" }),
670
+ /* @__PURE__ */ jsx3("option", { value: "center", children: "Centro" }),
671
+ /* @__PURE__ */ jsx3("option", { value: "right", children: "Derecha" })
672
+ ] })
673
+ ] }),
674
+ hasSrc && /* @__PURE__ */ jsx3("button", { className: "sci-nb-image-clear", onClick: () => save({ src: "" }), children: "Quitar imagen" })
675
+ ] })
676
+ ] }),
677
+ /* @__PURE__ */ jsxs2("div", { className: "sci-nb-cell-hint", children: [
678
+ /* @__PURE__ */ jsx3("kbd", { children: "Esc" }),
679
+ " salir"
680
+ ] })
681
+ ] });
682
+ };
683
+ function renderImagePreview(source, metadata) {
684
+ const data = parseImageSource(source, metadata);
685
+ if (!data.src) {
686
+ return '<div class="sci-nb-image-empty"><span class="sci-nb-placeholder">Click para agregar imagen</span></div>';
687
+ }
688
+ const alignStyle = `text-align:${data.align}`;
689
+ const widthStyle = `max-width:${data.width};width:auto;max-height:400px`;
690
+ let html = `<div class="sci-nb-image-view" style="${alignStyle}">`;
691
+ html += `<img src="${escapeAttr(data.src)}" alt="${escapeAttr(data.alt)}" style="${widthStyle}" />`;
692
+ if (data.caption) {
693
+ html += `<p class="sci-nb-image-caption">${escapeHtml2(data.caption)}</p>`;
694
+ }
695
+ html += `</div>`;
696
+ return html;
697
+ }
698
+ function escapeHtml2(s) {
699
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
700
+ }
701
+ function escapeAttr(s) {
702
+ return s.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
703
+ }
704
+
705
+ // src/components/EmbedCell.tsx
706
+ import { useState as useState5, useCallback as useCallback4 } from "react";
707
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
708
+ var EMBED_PRESETS = [
709
+ { label: "YouTube", pattern: "https://www.youtube.com/embed/", icon: "\u25B6" },
710
+ { label: "CodePen", pattern: "https://codepen.io/", icon: "\u2328" },
711
+ { label: "Observable", pattern: "https://observablehq.com/embed/", icon: "\u25C9" },
712
+ { label: "Desmos", pattern: "https://www.desmos.com/calculator/", icon: "\u{1F4C8}" },
713
+ { label: "GeoGebra", pattern: "https://www.geogebra.org/material/iframe/id/", icon: "\u{1F4D0}" },
714
+ { label: "Custom URL", pattern: "", icon: "\u{1F517}" }
715
+ ];
716
+ function parseEmbedSource(source, metadata) {
717
+ return {
718
+ url: source || "",
719
+ height: metadata.height || "400px",
720
+ sandbox: metadata.sandbox || "allow-scripts allow-same-origin allow-popups",
721
+ title: metadata.title || ""
722
+ };
723
+ }
724
+ var EmbedCell = ({ cellId, source, metadata, onExit }) => {
725
+ const engine = useSciNotebook();
726
+ const [data, setData] = useState5(() => parseEmbedSource(source, metadata));
727
+ const [showPreview, setShowPreview] = useState5(!!data.url);
728
+ const save = useCallback4((updates) => {
729
+ const next = { ...data, ...updates };
730
+ setData(next);
731
+ engine.updateCellSource(cellId, next.url);
732
+ engine.updateCellMetadata(cellId, {
733
+ height: next.height,
734
+ sandbox: next.sandbox,
735
+ title: next.title
736
+ });
737
+ }, [engine, cellId, data]);
738
+ const handleKeyDown = useCallback4((e) => {
739
+ if (e.key === "Escape") {
740
+ e.preventDefault();
741
+ onExit();
742
+ }
743
+ }, [onExit]);
744
+ const hasUrl = !!data.url.trim();
745
+ return /* @__PURE__ */ jsxs3("div", { className: "sci-nb-embed-editor", onKeyDown: handleKeyDown, children: [
746
+ /* @__PURE__ */ jsx4("div", { className: "sci-nb-embed-presets", children: EMBED_PRESETS.map((preset) => /* @__PURE__ */ jsxs3(
747
+ "button",
748
+ {
749
+ className: "sci-nb-embed-preset",
750
+ onClick: () => {
751
+ if (preset.pattern) {
752
+ save({ url: preset.pattern });
753
+ }
754
+ },
755
+ title: preset.label,
756
+ children: [
757
+ /* @__PURE__ */ jsx4("span", { children: preset.icon }),
758
+ /* @__PURE__ */ jsx4("span", { children: preset.label })
759
+ ]
760
+ },
761
+ preset.label
762
+ )) }),
763
+ /* @__PURE__ */ jsxs3("div", { className: "sci-nb-embed-url-row", children: [
764
+ /* @__PURE__ */ jsx4(
765
+ "input",
766
+ {
767
+ type: "text",
768
+ className: "sci-nb-embed-url",
769
+ value: data.url,
770
+ onChange: (e) => save({ url: e.target.value }),
771
+ placeholder: "https://www.youtube.com/embed/dQw4w9WgXcQ",
772
+ autoFocus: true
773
+ }
774
+ ),
775
+ /* @__PURE__ */ jsx4(
776
+ "button",
777
+ {
778
+ className: `sci-nb-embed-preview-btn ${showPreview ? "sci-nb-embed-preview-btn--active" : ""}`,
779
+ onClick: () => setShowPreview(!showPreview),
780
+ disabled: !hasUrl,
781
+ children: showPreview ? "Ocultar" : "Preview"
782
+ }
783
+ )
784
+ ] }),
785
+ showPreview && hasUrl && /* @__PURE__ */ jsx4("div", { className: "sci-nb-embed-frame-wrap", style: { height: data.height }, children: /* @__PURE__ */ jsx4(
786
+ "iframe",
787
+ {
788
+ src: data.url,
789
+ title: data.title || "Embedded content",
790
+ sandbox: data.sandbox,
791
+ style: { width: "100%", height: "100%", border: "none", borderRadius: "6px" },
792
+ loading: "lazy",
793
+ allowFullScreen: true
794
+ }
795
+ ) }),
796
+ /* @__PURE__ */ jsxs3("div", { className: "sci-nb-embed-settings", children: [
797
+ /* @__PURE__ */ jsxs3("div", { className: "sci-nb-embed-field", children: [
798
+ /* @__PURE__ */ jsx4("label", { children: "Titulo" }),
799
+ /* @__PURE__ */ jsx4(
800
+ "input",
801
+ {
802
+ type: "text",
803
+ value: data.title,
804
+ onChange: (e) => save({ title: e.target.value }),
805
+ placeholder: "Titulo del embed (accesibilidad)"
806
+ }
807
+ )
808
+ ] }),
809
+ /* @__PURE__ */ jsxs3("div", { className: "sci-nb-embed-row", children: [
810
+ /* @__PURE__ */ jsxs3("div", { className: "sci-nb-embed-field sci-nb-embed-field--small", children: [
811
+ /* @__PURE__ */ jsx4("label", { children: "Altura" }),
812
+ /* @__PURE__ */ jsxs3("select", { value: data.height, onChange: (e) => save({ height: e.target.value }), children: [
813
+ /* @__PURE__ */ jsx4("option", { value: "200px", children: "200px" }),
814
+ /* @__PURE__ */ jsx4("option", { value: "300px", children: "300px" }),
815
+ /* @__PURE__ */ jsx4("option", { value: "400px", children: "400px" }),
816
+ /* @__PURE__ */ jsx4("option", { value: "500px", children: "500px" }),
817
+ /* @__PURE__ */ jsx4("option", { value: "600px", children: "600px" })
818
+ ] })
819
+ ] }),
820
+ /* @__PURE__ */ jsxs3("div", { className: "sci-nb-embed-field sci-nb-embed-field--small", children: [
821
+ /* @__PURE__ */ jsx4("label", { children: "Sandbox" }),
822
+ /* @__PURE__ */ jsxs3("select", { value: data.sandbox, onChange: (e) => save({ sandbox: e.target.value }), children: [
823
+ /* @__PURE__ */ jsx4("option", { value: "allow-scripts allow-same-origin allow-popups", children: "Standard" }),
824
+ /* @__PURE__ */ jsx4("option", { value: "allow-scripts", children: "Solo scripts" }),
825
+ /* @__PURE__ */ jsx4("option", { value: "", children: "Restringido" })
826
+ ] })
827
+ ] })
828
+ ] })
829
+ ] }),
830
+ /* @__PURE__ */ jsxs3("div", { className: "sci-nb-cell-hint", children: [
831
+ /* @__PURE__ */ jsx4("kbd", { children: "Esc" }),
832
+ " salir"
833
+ ] })
834
+ ] });
835
+ };
836
+ function renderEmbedPreview(source, metadata) {
837
+ const data = parseEmbedSource(source, metadata);
838
+ if (!data.url) {
839
+ return '<div class="sci-nb-embed-empty"><span class="sci-nb-placeholder">Click para agregar contenido embebido</span></div>';
840
+ }
841
+ const titleAttr = data.title ? ` title="${escapeAttr2(data.title)}"` : "";
842
+ return `<div class="sci-nb-embed-view" style="height:${data.height}">
843
+ <iframe src="${escapeAttr2(data.url)}"${titleAttr} sandbox="${escapeAttr2(data.sandbox)}" style="width:100%;height:100%;border:none;border-radius:6px" loading="lazy" allowfullscreen></iframe>
844
+ </div>`;
845
+ }
846
+ function escapeAttr2(s) {
847
+ return s.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
848
+ }
849
+
850
+ // src/components/SlashCommand.tsx
851
+ import { useState as useState6, useEffect as useEffect5, useRef as useRef4 } from "react";
852
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
853
+ var DEFAULT_COMMANDS = [
854
+ { type: "markdown", label: "Texto", description: "Bloque de texto con formato Markdown", icon: "M", keywords: ["text", "markdown", "paragraph", "texto"] },
855
+ { type: "code", label: "C\xF3digo", description: "Bloque de c\xF3digo con syntax highlighting", icon: "</>", keywords: ["code", "codigo", "script", "program"] },
856
+ { type: "latex", label: "F\xF3rmula", description: "Editor visual de f\xF3rmulas LaTeX", icon: "\u2211", keywords: ["latex", "math", "formula", "ecuacion", "equation"] },
857
+ { type: "image", label: "Imagen", description: "Imagen con drag & drop, URL, caption", icon: "\u{1F5BC}", keywords: ["image", "imagen", "foto", "picture", "img"] },
858
+ { type: "embed", label: "Embed", description: "YouTube, CodePen, Desmos, iframe", icon: "\u29C9", keywords: ["embed", "iframe", "youtube", "video", "codepen"] },
859
+ { type: "table", label: "Tabla", description: "Tabla editable con filas y columnas", icon: "\u25A6", keywords: ["table", "tabla", "grid", "spreadsheet"] },
860
+ { type: "mermaid", label: "Diagrama", description: "Diagrama Mermaid (flowchart, sequence, etc.)", icon: "\u25C7", keywords: ["mermaid", "diagram", "diagrama", "flowchart", "chart"] },
861
+ { type: "raw", label: "Raw", description: "Texto sin formato", icon: "T", keywords: ["raw", "plain", "text", "crudo"] }
862
+ ];
863
+ var SlashCommand = ({
864
+ position,
865
+ query,
866
+ onSelect,
867
+ onClose,
868
+ extraCommands
869
+ }) => {
870
+ const allCommands = extraCommands ? [...DEFAULT_COMMANDS, ...extraCommands] : DEFAULT_COMMANDS;
871
+ const filtered = query ? allCommands.filter((cmd) => {
872
+ const q = query.toLowerCase();
873
+ return cmd.label.toLowerCase().includes(q) || cmd.type.toLowerCase().includes(q) || cmd.keywords.some((k) => k.includes(q));
874
+ }) : allCommands;
875
+ const [selectedIndex, setSelectedIndex] = useState6(0);
876
+ const menuRef = useRef4(null);
877
+ useEffect5(() => {
878
+ setSelectedIndex(0);
879
+ }, [query]);
880
+ useEffect5(() => {
881
+ const handleKey = (e) => {
882
+ if (e.key === "ArrowDown") {
883
+ e.preventDefault();
884
+ setSelectedIndex((i) => (i + 1) % Math.max(filtered.length, 1));
885
+ } else if (e.key === "ArrowUp") {
886
+ e.preventDefault();
887
+ setSelectedIndex((i) => (i - 1 + filtered.length) % Math.max(filtered.length, 1));
888
+ } else if (e.key === "Enter" && filtered.length > 0) {
889
+ e.preventDefault();
890
+ onSelect(filtered[selectedIndex]?.type || "markdown");
891
+ } else if (e.key === "Escape") {
892
+ e.preventDefault();
893
+ onClose();
894
+ }
895
+ };
896
+ document.addEventListener("keydown", handleKey, true);
897
+ return () => document.removeEventListener("keydown", handleKey, true);
898
+ }, [filtered, selectedIndex, onSelect, onClose]);
899
+ useEffect5(() => {
900
+ const handleClick = (e) => {
901
+ if (menuRef.current && !menuRef.current.contains(e.target)) {
902
+ onClose();
903
+ }
904
+ };
905
+ document.addEventListener("mousedown", handleClick);
906
+ return () => document.removeEventListener("mousedown", handleClick);
907
+ }, [onClose]);
908
+ if (filtered.length === 0) {
909
+ return /* @__PURE__ */ jsx5(
910
+ "div",
911
+ {
912
+ ref: menuRef,
913
+ className: "sci-nb-slash-menu",
914
+ style: { top: position.top, left: position.left },
915
+ children: /* @__PURE__ */ jsxs4("div", { className: "sci-nb-slash-empty", children: [
916
+ 'Sin resultados para "/',
917
+ query,
918
+ '"'
919
+ ] })
920
+ }
921
+ );
922
+ }
923
+ return /* @__PURE__ */ jsxs4(
924
+ "div",
925
+ {
926
+ ref: menuRef,
927
+ className: "sci-nb-slash-menu",
928
+ style: { top: position.top, left: position.left },
929
+ children: [
930
+ /* @__PURE__ */ jsx5("div", { className: "sci-nb-slash-header", children: "Insertar bloque" }),
931
+ filtered.map((cmd, i) => /* @__PURE__ */ jsxs4(
932
+ "button",
933
+ {
934
+ className: `sci-nb-slash-item ${i === selectedIndex ? "sci-nb-slash-item--active" : ""}`,
935
+ onMouseEnter: () => setSelectedIndex(i),
936
+ onClick: () => onSelect(cmd.type),
937
+ children: [
938
+ /* @__PURE__ */ jsx5("span", { className: "sci-nb-slash-icon", children: cmd.icon }),
939
+ /* @__PURE__ */ jsxs4("div", { className: "sci-nb-slash-text", children: [
940
+ /* @__PURE__ */ jsx5("span", { className: "sci-nb-slash-label", children: cmd.label }),
941
+ /* @__PURE__ */ jsx5("span", { className: "sci-nb-slash-desc", children: cmd.description })
942
+ ] })
943
+ ]
944
+ },
945
+ cmd.type + cmd.label
946
+ ))
947
+ ]
948
+ }
949
+ );
950
+ };
951
+
952
+ // src/components/TableCell.tsx
953
+ import { useState as useState7, useCallback as useCallback6 } from "react";
954
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
955
+ function parseMarkdownTable(source) {
956
+ const lines = source.trim().split("\n").filter((l) => l.trim());
957
+ if (lines.length < 2) {
958
+ return { headers: ["Col 1", "Col 2", "Col 3"], rows: [["", "", ""], ["", "", ""]] };
959
+ }
960
+ const parseLine = (line) => line.split("|").map((c) => c.trim()).filter((_, i, arr) => i > 0 && i < arr.length);
961
+ const headers = parseLine(lines[0]);
962
+ const rows = lines.slice(2).map(parseLine);
963
+ const colCount = headers.length;
964
+ const normalizedRows = rows.map((row) => {
965
+ while (row.length < colCount) row.push("");
966
+ return row.slice(0, colCount);
967
+ });
968
+ if (normalizedRows.length === 0) {
969
+ normalizedRows.push(new Array(colCount).fill(""));
970
+ }
971
+ return { headers, rows: normalizedRows };
972
+ }
973
+ function toMarkdownTable(data) {
974
+ const { headers, rows } = data;
975
+ const headerLine = `| ${headers.join(" | ")} |`;
976
+ const sepLine = `| ${headers.map(() => "---").join(" | ")} |`;
977
+ const rowLines = rows.map((row) => `| ${row.join(" | ")} |`);
978
+ return [headerLine, sepLine, ...rowLines].join("\n");
979
+ }
980
+ var TableCell = ({ cellId, source, onExit }) => {
981
+ const engine = useSciNotebook();
982
+ const [data, setData] = useState7(() => parseMarkdownTable(source));
983
+ const syncToEngine = useCallback6((newData) => {
984
+ setData(newData);
985
+ engine.updateCellSource(cellId, toMarkdownTable(newData));
986
+ }, [engine, cellId]);
987
+ const updateHeader = useCallback6((colIdx, value) => {
988
+ const newHeaders = [...data.headers];
989
+ newHeaders[colIdx] = value;
990
+ syncToEngine({ ...data, headers: newHeaders });
991
+ }, [data, syncToEngine]);
992
+ const updateCell = useCallback6((rowIdx, colIdx, value) => {
993
+ const newRows = data.rows.map((r) => [...r]);
994
+ newRows[rowIdx][colIdx] = value;
995
+ syncToEngine({ ...data, rows: newRows });
996
+ }, [data, syncToEngine]);
997
+ const addRow = useCallback6(() => {
998
+ const newRow = new Array(data.headers.length).fill("");
999
+ syncToEngine({ ...data, rows: [...data.rows, newRow] });
1000
+ }, [data, syncToEngine]);
1001
+ const addColumn = useCallback6(() => {
1002
+ const newHeaders = [...data.headers, `Col ${data.headers.length + 1}`];
1003
+ const newRows = data.rows.map((row) => [...row, ""]);
1004
+ syncToEngine({ headers: newHeaders, rows: newRows });
1005
+ }, [data, syncToEngine]);
1006
+ const removeRow = useCallback6((rowIdx) => {
1007
+ if (data.rows.length <= 1) return;
1008
+ const newRows = data.rows.filter((_, i) => i !== rowIdx);
1009
+ syncToEngine({ ...data, rows: newRows });
1010
+ }, [data, syncToEngine]);
1011
+ const removeColumn = useCallback6((colIdx) => {
1012
+ if (data.headers.length <= 1) return;
1013
+ const newHeaders = data.headers.filter((_, i) => i !== colIdx);
1014
+ const newRows = data.rows.map((row) => row.filter((_, i) => i !== colIdx));
1015
+ syncToEngine({ headers: newHeaders, rows: newRows });
1016
+ }, [data, syncToEngine]);
1017
+ const handleKeyDown = useCallback6((e) => {
1018
+ if (e.key === "Escape") {
1019
+ e.preventDefault();
1020
+ onExit();
1021
+ } else if (e.key === "Tab" && !e.shiftKey) {
1022
+ }
1023
+ }, [onExit]);
1024
+ return /* @__PURE__ */ jsxs5("div", { className: "sci-nb-table-editor", onKeyDown: handleKeyDown, children: [
1025
+ /* @__PURE__ */ jsxs5("div", { className: "sci-nb-table-toolbar", children: [
1026
+ /* @__PURE__ */ jsx6("button", { onClick: addRow, children: "+ Fila" }),
1027
+ /* @__PURE__ */ jsx6("button", { onClick: addColumn, children: "+ Columna" })
1028
+ ] }),
1029
+ /* @__PURE__ */ jsxs5("table", { children: [
1030
+ /* @__PURE__ */ jsx6("thead", { children: /* @__PURE__ */ jsxs5("tr", { children: [
1031
+ data.headers.map((h, ci) => /* @__PURE__ */ jsx6("th", { children: /* @__PURE__ */ jsx6(
1032
+ "input",
1033
+ {
1034
+ value: h,
1035
+ onChange: (e) => updateHeader(ci, e.target.value),
1036
+ placeholder: `Col ${ci + 1}`
1037
+ }
1038
+ ) }, ci)),
1039
+ /* @__PURE__ */ jsx6("th", { style: { width: 30, padding: 0 } })
1040
+ ] }) }),
1041
+ /* @__PURE__ */ jsx6("tbody", { children: data.rows.map((row, ri) => /* @__PURE__ */ jsxs5("tr", { children: [
1042
+ row.map((cell, ci) => /* @__PURE__ */ jsx6("td", { children: /* @__PURE__ */ jsx6(
1043
+ "input",
1044
+ {
1045
+ value: cell,
1046
+ onChange: (e) => updateCell(ri, ci, e.target.value),
1047
+ placeholder: "..."
1048
+ }
1049
+ ) }, ci)),
1050
+ /* @__PURE__ */ jsx6("td", { style: { width: 30, padding: 0, textAlign: "center" }, children: /* @__PURE__ */ jsx6(
1051
+ "button",
1052
+ {
1053
+ className: "sci-nb-btn sci-nb-btn--danger",
1054
+ onClick: () => removeRow(ri),
1055
+ title: "Eliminar fila",
1056
+ style: { padding: "2px 4px", fontSize: 10, border: "none", background: "transparent" },
1057
+ children: "\u2715"
1058
+ }
1059
+ ) })
1060
+ ] }, ri)) })
1061
+ ] }),
1062
+ /* @__PURE__ */ jsxs5("div", { className: "sci-nb-cell-hint", children: [
1063
+ /* @__PURE__ */ jsx6("kbd", { children: "Tab" }),
1064
+ " siguiente celda \xB7 ",
1065
+ /* @__PURE__ */ jsx6("kbd", { children: "Esc" }),
1066
+ " salir"
1067
+ ] })
1068
+ ] });
1069
+ };
1070
+ function renderTablePreview(source) {
1071
+ const data = parseMarkdownTable(source);
1072
+ if (data.headers.length === 0) return "<p>Tabla vac\xEDa</p>";
1073
+ const ths = data.headers.map((h) => `<th>${escapeHtml3(h)}</th>`).join("");
1074
+ const trs = data.rows.map(
1075
+ (row) => `<tr>${row.map((c) => `<td>${escapeHtml3(c)}</td>`).join("")}</tr>`
1076
+ ).join("");
1077
+ return `<table class="sci-nb-rendered-table"><thead><tr>${ths}</tr></thead><tbody>${trs}</tbody></table>`;
1078
+ }
1079
+ function escapeHtml3(s) {
1080
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1081
+ }
1082
+
1083
+ // src/components/CellOutput.tsx
1084
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
1085
+ var CellOutputDisplay = ({ outputs }) => {
1086
+ if (!outputs || outputs.length === 0) return null;
1087
+ return /* @__PURE__ */ jsx7("div", { className: "sci-nb-cell-outputs", children: outputs.map((output, i) => /* @__PURE__ */ jsx7("div", { className: `sci-nb-output sci-nb-output--${output.outputType}`, children: renderOutput(output) }, i)) });
1088
+ };
1089
+ function renderOutput(output) {
1090
+ switch (output.outputType) {
1091
+ case "stream":
1092
+ return /* @__PURE__ */ jsx7("pre", { className: `sci-nb-output-stream sci-nb-output-stream--${output.name}`, children: output.text });
1093
+ case "display": {
1094
+ if (output.data["text/html"]) {
1095
+ return /* @__PURE__ */ jsx7(
1096
+ "div",
1097
+ {
1098
+ className: "sci-nb-output-html",
1099
+ dangerouslySetInnerHTML: { __html: output.data["text/html"] }
1100
+ }
1101
+ );
1102
+ }
1103
+ if (output.data["image/svg+xml"]) {
1104
+ return /* @__PURE__ */ jsx7(
1105
+ "div",
1106
+ {
1107
+ className: "sci-nb-output-svg",
1108
+ dangerouslySetInnerHTML: { __html: output.data["image/svg+xml"] }
1109
+ }
1110
+ );
1111
+ }
1112
+ if (output.data["image/png"]) {
1113
+ return /* @__PURE__ */ jsx7(
1114
+ "img",
1115
+ {
1116
+ className: "sci-nb-output-image",
1117
+ src: `data:image/png;base64,${output.data["image/png"]}`,
1118
+ alt: "Output"
1119
+ }
1120
+ );
1121
+ }
1122
+ if (output.data["image/jpeg"]) {
1123
+ return /* @__PURE__ */ jsx7(
1124
+ "img",
1125
+ {
1126
+ className: "sci-nb-output-image",
1127
+ src: `data:image/jpeg;base64,${output.data["image/jpeg"]}`,
1128
+ alt: "Output"
1129
+ }
1130
+ );
1131
+ }
1132
+ if (output.data["application/json"]) {
1133
+ return /* @__PURE__ */ jsx7("pre", { className: "sci-nb-output-json", children: JSON.stringify(JSON.parse(output.data["application/json"]), null, 2) });
1134
+ }
1135
+ if (output.data["text/plain"]) {
1136
+ return /* @__PURE__ */ jsx7("pre", { className: "sci-nb-output-text", children: output.data["text/plain"] });
1137
+ }
1138
+ return /* @__PURE__ */ jsx7("pre", { className: "sci-nb-output-text", children: "[Display output]" });
1139
+ }
1140
+ case "error":
1141
+ return /* @__PURE__ */ jsxs6("div", { className: "sci-nb-output-error", children: [
1142
+ /* @__PURE__ */ jsxs6("strong", { className: "sci-nb-output-error-name", children: [
1143
+ output.name,
1144
+ ": "
1145
+ ] }),
1146
+ /* @__PURE__ */ jsx7("span", { className: "sci-nb-output-error-msg", children: output.message }),
1147
+ output.traceback && output.traceback.length > 0 && /* @__PURE__ */ jsx7("pre", { className: "sci-nb-output-traceback", children: output.traceback.join("\n") })
1148
+ ] });
1149
+ default:
1150
+ return null;
1151
+ }
1152
+ }
1153
+
1154
+ // src/components/MermaidCell.tsx
1155
+ import { useState as useState8, useEffect as useEffect6, useRef as useRef5 } from "react";
1156
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
1157
+ var mermaidIdCounter = 0;
1158
+ var MermaidPreview = ({ source, onClick }) => {
1159
+ const [svg, setSvg] = useState8(null);
1160
+ const [error, setError] = useState8(null);
1161
+ const containerRef = useRef5(null);
1162
+ useEffect6(() => {
1163
+ const trimmed = source.trim();
1164
+ if (!trimmed) {
1165
+ setSvg(null);
1166
+ setError(null);
1167
+ return;
1168
+ }
1169
+ const mermaid2 = globalThis.mermaid;
1170
+ if (!mermaid2) {
1171
+ setError(null);
1172
+ setSvg(null);
1173
+ return;
1174
+ }
1175
+ let cancelled = false;
1176
+ const id = `sci-mermaid-${++mermaidIdCounter}`;
1177
+ (async () => {
1178
+ try {
1179
+ const result = await mermaid2.render(id, trimmed);
1180
+ if (!cancelled) {
1181
+ setSvg(result.svg);
1182
+ setError(null);
1183
+ }
1184
+ } catch (e) {
1185
+ if (!cancelled) {
1186
+ setError(e.message || String(e));
1187
+ setSvg(null);
1188
+ }
1189
+ const errEl = document.getElementById(`d${id}`);
1190
+ if (errEl) errEl.remove();
1191
+ }
1192
+ })();
1193
+ return () => {
1194
+ cancelled = true;
1195
+ };
1196
+ }, [source]);
1197
+ const mermaid = globalThis.mermaid;
1198
+ if (!source.trim()) {
1199
+ return /* @__PURE__ */ jsx8("div", { className: "sci-nb-mermaid-preview", onClick, children: /* @__PURE__ */ jsx8("span", { className: "sci-nb-placeholder", children: "Diagrama vacio \u2014 escribe sintaxis Mermaid" }) });
1200
+ }
1201
+ if (!mermaid) {
1202
+ return /* @__PURE__ */ jsxs7("div", { className: "sci-nb-mermaid-preview", onClick, children: [
1203
+ /* @__PURE__ */ jsx8("pre", { className: "sci-nb-code", children: /* @__PURE__ */ jsx8("code", { className: "language-mermaid", children: source }) }),
1204
+ /* @__PURE__ */ jsxs7("div", { style: { fontSize: 11, color: "#94a3b8", textAlign: "center", padding: 4 }, children: [
1205
+ "Mermaid no disponible. Importa ",
1206
+ /* @__PURE__ */ jsx8("code", { children: "mermaid" }),
1207
+ " y exponlo como ",
1208
+ /* @__PURE__ */ jsx8("code", { children: "globalThis.mermaid" }),
1209
+ "."
1210
+ ] })
1211
+ ] });
1212
+ }
1213
+ if (error) {
1214
+ return /* @__PURE__ */ jsxs7("div", { className: "sci-nb-mermaid-error", onClick, children: [
1215
+ /* @__PURE__ */ jsx8("strong", { children: "Mermaid error:" }),
1216
+ " ",
1217
+ error
1218
+ ] });
1219
+ }
1220
+ if (svg) {
1221
+ return /* @__PURE__ */ jsx8(
1222
+ "div",
1223
+ {
1224
+ ref: containerRef,
1225
+ className: "sci-nb-mermaid-preview",
1226
+ onClick,
1227
+ dangerouslySetInnerHTML: { __html: svg }
1228
+ }
1229
+ );
1230
+ }
1231
+ return /* @__PURE__ */ jsx8("div", { className: "sci-nb-mermaid-preview", onClick, children: /* @__PURE__ */ jsx8("span", { className: "sci-nb-placeholder", children: "Renderizando diagrama..." }) });
1232
+ };
1233
+ function initMermaid(mermaidLib, config) {
1234
+ globalThis.mermaid = mermaidLib;
1235
+ mermaidLib.initialize({
1236
+ startOnLoad: false,
1237
+ theme: "default",
1238
+ securityLevel: "loose",
1239
+ ...config
1240
+ });
1241
+ }
1242
+
1243
+ // src/components/Cell.tsx
1244
+ import { Fragment, jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
1245
+ var CELL_TYPES = [
1246
+ { value: "markdown", label: "Markdown", icon: "M" },
1247
+ { value: "code", label: "Code", icon: "</>" },
1248
+ { value: "raw", label: "Raw", icon: "T" },
1249
+ { value: "latex", label: "LaTeX", icon: "\u2211" },
1250
+ { value: "image", label: "Imagen", icon: "\u{1F5BC}" },
1251
+ { value: "embed", label: "Embed", icon: "\u29C9" }
1252
+ ];
1253
+ var PLACEHOLDERS = {
1254
+ markdown: "Escribe markdown aqui... (click para editar)",
1255
+ code: "Escribe codigo aqui...",
1256
+ raw: "Texto raw...",
1257
+ latex: "Escribe LaTeX aqui... e.g. \\int_0^1 x^2 dx",
1258
+ image: "Click para agregar imagen",
1259
+ embed: "Click para agregar contenido embebido"
1260
+ };
1261
+ var Cell = ({ cellId, pipeline, index, totalCells }) => {
1262
+ const cell = useCell(cellId);
1263
+ const engine = useSciNotebook();
1264
+ const textareaRef = useRef6(null);
1265
+ const cellRef = useRef6(null);
1266
+ const [showTypeMenu, setShowTypeMenu] = useState9(false);
1267
+ const [hovered, setHovered] = useState9(false);
1268
+ const [slashState, setSlashState] = useState9(null);
1269
+ const [dragOver, setDragOver] = useState9(null);
1270
+ const [isDragging, setIsDragging] = useState9(false);
1271
+ const rendered = useMemo3(() => {
1272
+ if (!cell) return { html: "", cellId: "", renderTime: 0, cached: false };
1273
+ return pipeline.render(cell);
1274
+ }, [cell?.source, cell?.type, cell?.metadata, pipeline]);
1275
+ useEffect7(() => {
1276
+ if (cell?.editing && textareaRef.current) {
1277
+ const ta = textareaRef.current;
1278
+ ta.style.height = "auto";
1279
+ ta.style.height = `${Math.max(40, ta.scrollHeight)}px`;
1280
+ }
1281
+ }, [cell?.source, cell?.editing]);
1282
+ useEffect7(() => {
1283
+ if (cell?.editing && textareaRef.current) {
1284
+ textareaRef.current.focus();
1285
+ }
1286
+ }, [cell?.editing]);
1287
+ useEffect7(() => {
1288
+ if (!showTypeMenu) return;
1289
+ const handler = (e) => {
1290
+ if (cellRef.current && !cellRef.current.contains(e.target)) {
1291
+ setShowTypeMenu(false);
1292
+ }
1293
+ };
1294
+ document.addEventListener("mousedown", handler);
1295
+ return () => document.removeEventListener("mousedown", handler);
1296
+ }, [showTypeMenu]);
1297
+ const handleSourceChange = useCallback8((e) => {
1298
+ const val = e.target.value;
1299
+ engine.updateCellSource(cellId, val);
1300
+ const ta = e.target;
1301
+ const cursor = ta.selectionStart;
1302
+ const textBefore = val.slice(0, cursor);
1303
+ const lastNewline = textBefore.lastIndexOf("\n");
1304
+ const lineStart = lastNewline + 1;
1305
+ const currentLine = textBefore.slice(lineStart);
1306
+ if (currentLine.startsWith("/")) {
1307
+ const query = currentLine.slice(1);
1308
+ const rect = ta.getBoundingClientRect();
1309
+ const lineHeight = 22;
1310
+ const lines = textBefore.split("\n").length;
1311
+ setSlashState({
1312
+ query,
1313
+ pos: { top: rect.top + lines * lineHeight + 4 - ta.scrollTop, left: rect.left + 8 }
1314
+ });
1315
+ } else {
1316
+ setSlashState(null);
1317
+ }
1318
+ }, [engine, cellId]);
1319
+ const enterEdit = useCallback8(() => {
1320
+ engine.setEditMode(cellId);
1321
+ engine.focusCell(cellId);
1322
+ }, [engine, cellId]);
1323
+ const exitEdit = useCallback8(() => {
1324
+ engine.setViewMode(cellId);
1325
+ }, [engine, cellId]);
1326
+ const handleSlashSelect = useCallback8((type) => {
1327
+ const val = cell?.source || "";
1328
+ const ta = textareaRef.current;
1329
+ if (ta) {
1330
+ const cursor = ta.selectionStart;
1331
+ const textBefore = val.slice(0, cursor);
1332
+ const lastNewline = textBefore.lastIndexOf("\n");
1333
+ const lineStart = lastNewline + 1;
1334
+ const cleaned = val.slice(0, lineStart) + val.slice(cursor);
1335
+ engine.updateCellSource(cellId, cleaned.trim());
1336
+ }
1337
+ engine.setCellType(cellId, type);
1338
+ setSlashState(null);
1339
+ }, [engine, cellId, cell?.source]);
1340
+ const handleKeyDown = useCallback8((e) => {
1341
+ if (slashState) {
1342
+ if (["ArrowDown", "ArrowUp", "Enter"].includes(e.key)) return;
1343
+ if (e.key === "Escape") {
1344
+ e.preventDefault();
1345
+ setSlashState(null);
1346
+ return;
1347
+ }
1348
+ }
1349
+ if (e.key === "Escape") {
1350
+ e.preventDefault();
1351
+ exitEdit();
1352
+ } else if (e.key === "Enter" && e.shiftKey) {
1353
+ e.preventDefault();
1354
+ exitEdit();
1355
+ const cells = engine.getCells();
1356
+ const idx = cells.findIndex((c) => c.id === cellId);
1357
+ if (idx < cells.length - 1) {
1358
+ engine.focusCell(cells[idx + 1].id);
1359
+ engine.setEditMode(cells[idx + 1].id);
1360
+ }
1361
+ } else if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) {
1362
+ e.preventDefault();
1363
+ exitEdit();
1364
+ } else if (e.key === "Tab" && !e.shiftKey) {
1365
+ e.preventDefault();
1366
+ const ta = e.currentTarget;
1367
+ const start = ta.selectionStart;
1368
+ const end = ta.selectionEnd;
1369
+ const val = ta.value;
1370
+ const newVal = val.substring(0, start) + " " + val.substring(end);
1371
+ engine.updateCellSource(cellId, newVal);
1372
+ requestAnimationFrame(() => {
1373
+ if (textareaRef.current) {
1374
+ textareaRef.current.selectionStart = start + 2;
1375
+ textareaRef.current.selectionEnd = start + 2;
1376
+ }
1377
+ });
1378
+ } else if (e.key === "Tab" && e.shiftKey) {
1379
+ e.preventDefault();
1380
+ const ta = e.currentTarget;
1381
+ const start = ta.selectionStart;
1382
+ const val = ta.value;
1383
+ const before = val.substring(0, start);
1384
+ const trimmed = before.replace(/ $/, "");
1385
+ if (trimmed !== before) {
1386
+ engine.updateCellSource(cellId, trimmed + val.substring(start));
1387
+ const diff = before.length - trimmed.length;
1388
+ requestAnimationFrame(() => {
1389
+ if (textareaRef.current) {
1390
+ textareaRef.current.selectionStart = start - diff;
1391
+ textareaRef.current.selectionEnd = start - diff;
1392
+ }
1393
+ });
1394
+ }
1395
+ } else if (e.key === "b" && (e.ctrlKey || e.metaKey)) {
1396
+ e.preventDefault();
1397
+ wrapSelection("**", "**");
1398
+ } else if (e.key === "i" && (e.ctrlKey || e.metaKey)) {
1399
+ e.preventDefault();
1400
+ wrapSelection("*", "*");
1401
+ }
1402
+ }, [engine, cellId, exitEdit]);
1403
+ const wrapSelection = useCallback8((before, after) => {
1404
+ const ta = textareaRef.current;
1405
+ if (!ta) return;
1406
+ const start = ta.selectionStart;
1407
+ const end = ta.selectionEnd;
1408
+ const val = ta.value;
1409
+ const newVal = val.substring(0, start) + before + val.substring(start, end) + after + val.substring(end);
1410
+ engine.updateCellSource(cellId, newVal);
1411
+ requestAnimationFrame(() => {
1412
+ if (textareaRef.current) {
1413
+ textareaRef.current.selectionStart = start + before.length;
1414
+ textareaRef.current.selectionEnd = end + before.length;
1415
+ }
1416
+ });
1417
+ }, [engine, cellId]);
1418
+ if (!cell) return null;
1419
+ const isEditing = !!cell.editing;
1420
+ const isEmpty = !cell.source.trim();
1421
+ const placeholder = PLACEHOLDERS[cell.type] || "Click to edit...";
1422
+ return /* @__PURE__ */ jsxs8(
1423
+ "div",
1424
+ {
1425
+ ref: cellRef,
1426
+ className: [
1427
+ "sci-nb-cell",
1428
+ `sci-nb-cell--${cell.type}`,
1429
+ isEditing ? "sci-nb-cell--edit" : "sci-nb-cell--view",
1430
+ hovered ? "sci-nb-cell--hover" : "",
1431
+ isDragging ? "sci-nb-cell--dragging" : "",
1432
+ dragOver === "top" ? "sci-nb-cell--drag-over-top" : "",
1433
+ dragOver === "bottom" ? "sci-nb-cell--drag-over-bottom" : ""
1434
+ ].filter(Boolean).join(" "),
1435
+ draggable: !isEditing,
1436
+ onDragStart: (e) => {
1437
+ e.dataTransfer.setData("text/plain", cellId);
1438
+ e.dataTransfer.effectAllowed = "move";
1439
+ setIsDragging(true);
1440
+ },
1441
+ onDragEnd: () => {
1442
+ setIsDragging(false);
1443
+ setDragOver(null);
1444
+ },
1445
+ onDragOver: (e) => {
1446
+ e.preventDefault();
1447
+ e.dataTransfer.dropEffect = "move";
1448
+ const rect = e.currentTarget.getBoundingClientRect();
1449
+ const midY = rect.top + rect.height / 2;
1450
+ setDragOver(e.clientY < midY ? "top" : "bottom");
1451
+ },
1452
+ onDragLeave: () => setDragOver(null),
1453
+ onDrop: (e) => {
1454
+ e.preventDefault();
1455
+ const draggedId = e.dataTransfer.getData("text/plain");
1456
+ setDragOver(null);
1457
+ if (draggedId && draggedId !== cellId) {
1458
+ const targetIdx = dragOver === "top" ? index : index + 1;
1459
+ engine.moveCell(draggedId, targetIdx);
1460
+ }
1461
+ },
1462
+ "data-testid": `cell-${cell.id}`,
1463
+ "data-editing": String(isEditing),
1464
+ "data-cell-type": cell.type,
1465
+ role: "region",
1466
+ "aria-label": `${cell.type} cell ${index + 1} of ${totalCells}${isEditing ? ", editing" : ""}`,
1467
+ "aria-selected": isEditing,
1468
+ tabIndex: 0,
1469
+ onMouseEnter: () => setHovered(true),
1470
+ onMouseLeave: () => setHovered(false),
1471
+ onClick: () => engine.focusCell(cellId),
1472
+ children: [
1473
+ /* @__PURE__ */ jsxs8("div", { className: "sci-nb-cell-gutter", children: [
1474
+ /* @__PURE__ */ jsx9("div", { className: "sci-nb-cell-handle", title: "Drag to reorder", children: /* @__PURE__ */ jsxs8("svg", { width: "12", height: "20", viewBox: "0 0 12 20", fill: "currentColor", children: [
1475
+ /* @__PURE__ */ jsx9("circle", { cx: "3", cy: "4", r: "1.5" }),
1476
+ /* @__PURE__ */ jsx9("circle", { cx: "9", cy: "4", r: "1.5" }),
1477
+ /* @__PURE__ */ jsx9("circle", { cx: "3", cy: "10", r: "1.5" }),
1478
+ /* @__PURE__ */ jsx9("circle", { cx: "9", cy: "10", r: "1.5" }),
1479
+ /* @__PURE__ */ jsx9("circle", { cx: "3", cy: "16", r: "1.5" }),
1480
+ /* @__PURE__ */ jsx9("circle", { cx: "9", cy: "16", r: "1.5" })
1481
+ ] }) }),
1482
+ /* @__PURE__ */ jsxs8("span", { className: "sci-nb-cell-index", children: [
1483
+ "[",
1484
+ index + 1,
1485
+ "]"
1486
+ ] })
1487
+ ] }),
1488
+ /* @__PURE__ */ jsxs8("div", { className: "sci-nb-cell-badge-wrap", children: [
1489
+ /* @__PURE__ */ jsx9(
1490
+ "button",
1491
+ {
1492
+ className: "sci-nb-cell-badge",
1493
+ onClick: (e) => {
1494
+ e.stopPropagation();
1495
+ setShowTypeMenu(!showTypeMenu);
1496
+ },
1497
+ title: "Change cell type",
1498
+ children: CELL_TYPES.find((ct) => ct.value === cell.type)?.icon || cell.type.slice(0, 2).toUpperCase()
1499
+ }
1500
+ ),
1501
+ showTypeMenu && /* @__PURE__ */ jsx9("div", { className: "sci-nb-type-menu", children: CELL_TYPES.map((ct) => /* @__PURE__ */ jsxs8(
1502
+ "button",
1503
+ {
1504
+ className: `sci-nb-type-option ${cell.type === ct.value ? "sci-nb-type-option--active" : ""}`,
1505
+ onClick: (e) => {
1506
+ e.stopPropagation();
1507
+ engine.setCellType(cellId, ct.value);
1508
+ setShowTypeMenu(false);
1509
+ },
1510
+ children: [
1511
+ /* @__PURE__ */ jsx9("span", { className: "sci-nb-type-option-icon", children: ct.icon }),
1512
+ ct.label
1513
+ ]
1514
+ },
1515
+ ct.value
1516
+ )) })
1517
+ ] }),
1518
+ /* @__PURE__ */ jsxs8("div", { className: "sci-nb-cell-content", children: [
1519
+ isEditing ? renderEditMode(cell, cellId, textareaRef, handleSourceChange, handleKeyDown, placeholder, exitEdit, slashState, handleSlashSelect, () => setSlashState(null)) : renderViewMode(cell, rendered.html, isEmpty, placeholder, enterEdit),
1520
+ cell.outputs && cell.outputs.length > 0 && /* @__PURE__ */ jsx9(CellOutputDisplay, { outputs: cell.outputs })
1521
+ ] }),
1522
+ /* @__PURE__ */ jsxs8("div", { className: "sci-nb-cell-actions", children: [
1523
+ /* @__PURE__ */ jsx9(
1524
+ "button",
1525
+ {
1526
+ className: "sci-nb-btn",
1527
+ onClick: (e) => {
1528
+ e.stopPropagation();
1529
+ engine.moveCell(cellId, index - 1);
1530
+ },
1531
+ disabled: index === 0,
1532
+ title: "Move up",
1533
+ children: /* @__PURE__ */ jsx9("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: /* @__PURE__ */ jsx9("path", { d: "M7 11V3M7 3L3 7M7 3l4 4", strokeLinecap: "round", strokeLinejoin: "round" }) })
1534
+ }
1535
+ ),
1536
+ /* @__PURE__ */ jsx9(
1537
+ "button",
1538
+ {
1539
+ className: "sci-nb-btn",
1540
+ onClick: (e) => {
1541
+ e.stopPropagation();
1542
+ engine.moveCell(cellId, index + 1);
1543
+ },
1544
+ disabled: index >= totalCells - 1,
1545
+ title: "Move down",
1546
+ children: /* @__PURE__ */ jsx9("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: /* @__PURE__ */ jsx9("path", { d: "M7 3v8M7 11l-4-4M7 11l4-4", strokeLinecap: "round", strokeLinejoin: "round" }) })
1547
+ }
1548
+ ),
1549
+ /* @__PURE__ */ jsx9(
1550
+ "button",
1551
+ {
1552
+ className: "sci-nb-btn",
1553
+ onClick: (e) => {
1554
+ e.stopPropagation();
1555
+ engine.duplicateCell(cellId);
1556
+ },
1557
+ title: "Duplicate cell",
1558
+ children: /* @__PURE__ */ jsxs8("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: [
1559
+ /* @__PURE__ */ jsx9("rect", { x: "4", y: "4", width: "8", height: "8", rx: "1.5" }),
1560
+ /* @__PURE__ */ jsx9("path", { d: "M10 2H3.5A1.5 1.5 0 002 3.5V10" })
1561
+ ] })
1562
+ }
1563
+ ),
1564
+ /* @__PURE__ */ jsx9(
1565
+ "button",
1566
+ {
1567
+ className: "sci-nb-btn sci-nb-btn--danger",
1568
+ onClick: (e) => {
1569
+ e.stopPropagation();
1570
+ engine.deleteCell(cellId);
1571
+ },
1572
+ title: "Delete cell",
1573
+ children: /* @__PURE__ */ jsx9("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: /* @__PURE__ */ jsx9("path", { d: "M3 4h8M5.5 4V3a1 1 0 011-1h1a1 1 0 011 1v1M6 6.5v3M8 6.5v3M4 4l.5 7a1.5 1.5 0 001.5 1.5h2A1.5 1.5 0 0010 11l.5-7", strokeLinecap: "round", strokeLinejoin: "round" }) })
1574
+ }
1575
+ )
1576
+ ] })
1577
+ ]
1578
+ }
1579
+ );
1580
+ };
1581
+ function renderEditMode(cell, cellId, textareaRef, handleSourceChange, handleKeyDown, placeholder, exitEdit, slashState, onSlashSelect, onSlashClose) {
1582
+ if (cell.type === "latex") {
1583
+ return /* @__PURE__ */ jsx9(MathEditor, { cellId, source: cell.source, onExit: exitEdit });
1584
+ }
1585
+ if (cell.type === "image") {
1586
+ return /* @__PURE__ */ jsx9(ImageCell, { cellId, source: cell.source, metadata: cell.metadata, onExit: exitEdit });
1587
+ }
1588
+ if (cell.type === "embed") {
1589
+ return /* @__PURE__ */ jsx9(EmbedCell, { cellId, source: cell.source, metadata: cell.metadata, onExit: exitEdit });
1590
+ }
1591
+ if (cell.type === "table") {
1592
+ return /* @__PURE__ */ jsx9(TableCell, { cellId, source: cell.source, metadata: cell.metadata, onExit: exitEdit });
1593
+ }
1594
+ return /* @__PURE__ */ jsxs8(Fragment, { children: [
1595
+ cell.type === "markdown" && /* @__PURE__ */ jsx9(FloatingToolbar, { cellId, textareaRef }),
1596
+ /* @__PURE__ */ jsx9(
1597
+ "textarea",
1598
+ {
1599
+ ref: textareaRef,
1600
+ className: "sci-nb-editor",
1601
+ value: cell.source,
1602
+ onChange: handleSourceChange,
1603
+ onKeyDown: handleKeyDown,
1604
+ placeholder,
1605
+ spellCheck: cell.type === "markdown",
1606
+ rows: 1
1607
+ }
1608
+ ),
1609
+ slashState && /* @__PURE__ */ jsx9(
1610
+ SlashCommand,
1611
+ {
1612
+ position: slashState.pos,
1613
+ query: slashState.query,
1614
+ onSelect: onSlashSelect,
1615
+ onClose: onSlashClose
1616
+ }
1617
+ ),
1618
+ /* @__PURE__ */ jsxs8("div", { className: "sci-nb-cell-hint", children: [
1619
+ /* @__PURE__ */ jsx9("kbd", { children: "/" }),
1620
+ " comandos \xB7 ",
1621
+ /* @__PURE__ */ jsx9("kbd", { children: "Shift+Enter" }),
1622
+ " siguiente \xB7 ",
1623
+ /* @__PURE__ */ jsx9("kbd", { children: "Esc" }),
1624
+ " salir"
1625
+ ] })
1626
+ ] });
1627
+ }
1628
+ function renderViewMode(cell, renderedHtml, isEmpty, placeholder, enterEdit) {
1629
+ if (cell.type === "image") {
1630
+ const html = renderImagePreview(cell.source, cell.metadata);
1631
+ return /* @__PURE__ */ jsx9(
1632
+ "div",
1633
+ {
1634
+ className: "sci-nb-preview",
1635
+ onClick: enterEdit,
1636
+ dangerouslySetInnerHTML: { __html: html }
1637
+ }
1638
+ );
1639
+ }
1640
+ if (cell.type === "embed") {
1641
+ const html = renderEmbedPreview(cell.source, cell.metadata);
1642
+ return /* @__PURE__ */ jsx9(
1643
+ "div",
1644
+ {
1645
+ className: "sci-nb-preview sci-nb-preview--embed",
1646
+ onClick: enterEdit,
1647
+ dangerouslySetInnerHTML: { __html: html }
1648
+ }
1649
+ );
1650
+ }
1651
+ if (cell.type === "table") {
1652
+ const html = renderTablePreview(cell.source);
1653
+ return /* @__PURE__ */ jsx9(
1654
+ "div",
1655
+ {
1656
+ className: "sci-nb-preview",
1657
+ onClick: enterEdit,
1658
+ dangerouslySetInnerHTML: { __html: html }
1659
+ }
1660
+ );
1661
+ }
1662
+ if (cell.type === "mermaid") {
1663
+ return /* @__PURE__ */ jsx9(MermaidPreview, { source: cell.source, onClick: enterEdit });
1664
+ }
1665
+ return /* @__PURE__ */ jsx9(
1666
+ "div",
1667
+ {
1668
+ className: `sci-nb-preview ${isEmpty ? "sci-nb-preview--empty" : ""}`,
1669
+ onClick: enterEdit,
1670
+ dangerouslySetInnerHTML: isEmpty ? { __html: `<span class="sci-nb-placeholder">${placeholder}</span>` } : { __html: renderedHtml }
1671
+ }
1672
+ );
1673
+ }
1674
+
1675
+ // src/components/InsertHandle.tsx
1676
+ import { useState as useState10, useRef as useRef7, useEffect as useEffect8 } from "react";
1677
+ import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
1678
+ var INSERT_TYPES = [
1679
+ { type: "markdown", label: "Markdown", icon: "M" },
1680
+ { type: "code", label: "Code", icon: "</>" },
1681
+ { type: "latex", label: "LaTeX", icon: "\u2211" },
1682
+ { type: "image", label: "Imagen", icon: "\u{1F5BC}" },
1683
+ { type: "embed", label: "Embed", icon: "\u29C9" },
1684
+ { type: "raw", label: "Raw", icon: "T" }
1685
+ ];
1686
+ var InsertHandle = ({ index }) => {
1687
+ const engine = useSciNotebook();
1688
+ const [open, setOpen] = useState10(false);
1689
+ const menuRef = useRef7(null);
1690
+ useEffect8(() => {
1691
+ if (!open) return;
1692
+ const onClickOutside = (e) => {
1693
+ if (menuRef.current && !menuRef.current.contains(e.target)) {
1694
+ setOpen(false);
1695
+ }
1696
+ };
1697
+ document.addEventListener("mousedown", onClickOutside);
1698
+ return () => document.removeEventListener("mousedown", onClickOutside);
1699
+ }, [open]);
1700
+ const handleInsert = (type) => {
1701
+ engine.insertCell(index, type);
1702
+ setOpen(false);
1703
+ requestAnimationFrame(() => {
1704
+ const cells = engine.getCells();
1705
+ if (cells[index]) {
1706
+ engine.setEditMode(cells[index].id);
1707
+ engine.focusCell(cells[index].id);
1708
+ }
1709
+ });
1710
+ };
1711
+ return /* @__PURE__ */ jsxs9("div", { className: "sci-nb-insert-handle", ref: menuRef, children: [
1712
+ /* @__PURE__ */ jsx10("div", { className: "sci-nb-insert-line", children: /* @__PURE__ */ jsx10(
1713
+ "button",
1714
+ {
1715
+ className: "sci-nb-insert-btn",
1716
+ onClick: () => setOpen(!open),
1717
+ title: "Insert cell",
1718
+ children: /* @__PURE__ */ jsxs9("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: [
1719
+ /* @__PURE__ */ jsx10("line", { x1: "8", y1: "3", x2: "8", y2: "13", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" }),
1720
+ /* @__PURE__ */ jsx10("line", { x1: "3", y1: "8", x2: "13", y2: "8", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })
1721
+ ] })
1722
+ }
1723
+ ) }),
1724
+ open && /* @__PURE__ */ jsx10("div", { className: "sci-nb-insert-menu", children: INSERT_TYPES.map((ct) => /* @__PURE__ */ jsxs9(
1725
+ "button",
1726
+ {
1727
+ className: "sci-nb-insert-option",
1728
+ onClick: () => handleInsert(ct.type),
1729
+ children: [
1730
+ /* @__PURE__ */ jsx10("span", { className: "sci-nb-insert-option-icon", children: ct.icon }),
1731
+ /* @__PURE__ */ jsx10("span", { children: ct.label })
1732
+ ]
1733
+ },
1734
+ ct.type
1735
+ )) })
1736
+ ] });
1737
+ };
1738
+
1739
+ // src/components/TOCSidebar.tsx
1740
+ import { useMemo as useMemo4, useCallback as useCallback9 } from "react";
1741
+ import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
1742
+ var TOCSidebar = ({ focusedCellId }) => {
1743
+ const notebook = useNotebook();
1744
+ const engine = useSciNotebook();
1745
+ const items = useMemo4(() => {
1746
+ if (!notebook) return [];
1747
+ const result = [];
1748
+ for (const cell of notebook.cells) {
1749
+ if (cell.type !== "markdown") continue;
1750
+ const lines = cell.source.split("\n");
1751
+ for (const line of lines) {
1752
+ const match = line.match(/^(#{1,3})\s+(.+)/);
1753
+ if (match) {
1754
+ result.push({
1755
+ cellId: cell.id,
1756
+ level: match[1].length,
1757
+ text: match[2].replace(/[*_`~#]/g, "").trim()
1758
+ });
1759
+ }
1760
+ }
1761
+ }
1762
+ return result;
1763
+ }, [notebook]);
1764
+ const handleClick = useCallback9((cellId) => {
1765
+ engine.focusCell(cellId);
1766
+ engine.setEditMode(cellId);
1767
+ const el = document.querySelector(`[data-testid="cell-${cellId}"]`);
1768
+ if (el) el.scrollIntoView({ behavior: "smooth", block: "center" });
1769
+ }, [engine]);
1770
+ if (items.length === 0) return null;
1771
+ return /* @__PURE__ */ jsxs10("nav", { className: "sci-nb-toc", children: [
1772
+ /* @__PURE__ */ jsx11("div", { className: "sci-nb-toc-title", children: "Contenido" }),
1773
+ items.map((item, i) => /* @__PURE__ */ jsx11(
1774
+ "button",
1775
+ {
1776
+ className: [
1777
+ "sci-nb-toc-item",
1778
+ `sci-nb-toc-item--h${item.level}`,
1779
+ item.cellId === focusedCellId ? "sci-nb-toc-item--active" : ""
1780
+ ].join(" "),
1781
+ onClick: () => handleClick(item.cellId),
1782
+ title: item.text,
1783
+ children: item.text
1784
+ },
1785
+ `${item.cellId}-${i}`
1786
+ ))
1787
+ ] });
1788
+ };
1789
+
1790
+ // src/components/FindReplace.tsx
1791
+ import { useState as useState11, useCallback as useCallback10, useMemo as useMemo5, useEffect as useEffect9, useRef as useRef8 } from "react";
1792
+ import { Fragment as Fragment2, jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
1793
+ var FindReplace = ({ onClose }) => {
1794
+ const notebook = useNotebook();
1795
+ const engine = useSciNotebook();
1796
+ const [query, setQuery] = useState11("");
1797
+ const [replacement, setReplacement] = useState11("");
1798
+ const [showReplace, setShowReplace] = useState11(false);
1799
+ const [caseSensitive, setCaseSensitive] = useState11(false);
1800
+ const [currentIdx, setCurrentIdx] = useState11(0);
1801
+ const inputRef = useRef8(null);
1802
+ useEffect9(() => {
1803
+ inputRef.current?.focus();
1804
+ }, []);
1805
+ const matches = useMemo5(() => {
1806
+ if (!query || !notebook) return [];
1807
+ const result = [];
1808
+ const q = caseSensitive ? query : query.toLowerCase();
1809
+ for (const cell of notebook.cells) {
1810
+ const src = caseSensitive ? cell.source : cell.source.toLowerCase();
1811
+ let pos = 0;
1812
+ while (true) {
1813
+ const idx = src.indexOf(q, pos);
1814
+ if (idx === -1) break;
1815
+ result.push({ cellId: cell.id, index: idx, length: query.length });
1816
+ pos = idx + 1;
1817
+ }
1818
+ }
1819
+ return result;
1820
+ }, [query, notebook, caseSensitive]);
1821
+ useEffect9(() => {
1822
+ setCurrentIdx(0);
1823
+ }, [query, caseSensitive]);
1824
+ const navigateToMatch = useCallback10((match) => {
1825
+ engine.focusCell(match.cellId);
1826
+ const el = document.querySelector(`[data-testid="cell-${match.cellId}"]`);
1827
+ if (el) el.scrollIntoView({ behavior: "smooth", block: "center" });
1828
+ }, [engine]);
1829
+ const goNext = useCallback10(() => {
1830
+ if (matches.length === 0) return;
1831
+ const next = (currentIdx + 1) % matches.length;
1832
+ setCurrentIdx(next);
1833
+ navigateToMatch(matches[next]);
1834
+ }, [matches, currentIdx, navigateToMatch]);
1835
+ const goPrev = useCallback10(() => {
1836
+ if (matches.length === 0) return;
1837
+ const prev = (currentIdx - 1 + matches.length) % matches.length;
1838
+ setCurrentIdx(prev);
1839
+ navigateToMatch(matches[prev]);
1840
+ }, [matches, currentIdx, navigateToMatch]);
1841
+ const replaceCurrent = useCallback10(() => {
1842
+ if (matches.length === 0) return;
1843
+ const match = matches[currentIdx];
1844
+ if (!match) return;
1845
+ const cell = notebook?.cells.find((c) => c.id === match.cellId);
1846
+ if (!cell) return;
1847
+ const newSource = cell.source.slice(0, match.index) + replacement + cell.source.slice(match.index + match.length);
1848
+ engine.updateCellSource(match.cellId, newSource);
1849
+ }, [matches, currentIdx, replacement, notebook, engine]);
1850
+ const replaceAll = useCallback10(() => {
1851
+ if (matches.length === 0 || !notebook) return;
1852
+ const byCellId = /* @__PURE__ */ new Map();
1853
+ for (const m of matches) {
1854
+ const arr = byCellId.get(m.cellId) || [];
1855
+ arr.push(m);
1856
+ byCellId.set(m.cellId, arr);
1857
+ }
1858
+ for (const [cellId, cellMatches] of byCellId) {
1859
+ const cell = notebook.cells.find((c) => c.id === cellId);
1860
+ if (!cell) continue;
1861
+ let src = cell.source;
1862
+ for (let i = cellMatches.length - 1; i >= 0; i--) {
1863
+ const m = cellMatches[i];
1864
+ src = src.slice(0, m.index) + replacement + src.slice(m.index + m.length);
1865
+ }
1866
+ engine.updateCellSource(cellId, src);
1867
+ }
1868
+ }, [matches, replacement, notebook, engine]);
1869
+ const handleKeyDown = useCallback10((e) => {
1870
+ if (e.key === "Escape") {
1871
+ e.preventDefault();
1872
+ onClose();
1873
+ } else if (e.key === "Enter") {
1874
+ e.preventDefault();
1875
+ if (e.shiftKey) goPrev();
1876
+ else goNext();
1877
+ } else if (e.key === "h" && (e.ctrlKey || e.metaKey)) {
1878
+ e.preventDefault();
1879
+ setShowReplace((v) => !v);
1880
+ }
1881
+ }, [onClose, goNext, goPrev]);
1882
+ return /* @__PURE__ */ jsxs11("div", { className: "sci-nb-find-bar", onKeyDown: handleKeyDown, children: [
1883
+ /* @__PURE__ */ jsx12(
1884
+ "input",
1885
+ {
1886
+ ref: inputRef,
1887
+ type: "text",
1888
+ value: query,
1889
+ onChange: (e) => setQuery(e.target.value),
1890
+ placeholder: "Buscar..."
1891
+ }
1892
+ ),
1893
+ /* @__PURE__ */ jsx12("span", { className: "sci-nb-find-count", children: matches.length > 0 ? `${currentIdx + 1}/${matches.length}` : query ? "0" : "" }),
1894
+ /* @__PURE__ */ jsx12("button", { onClick: goPrev, title: "Anterior (Shift+Enter)", children: "\u25B2" }),
1895
+ /* @__PURE__ */ jsx12("button", { onClick: goNext, title: "Siguiente (Enter)", children: "\u25BC" }),
1896
+ /* @__PURE__ */ jsx12(
1897
+ "button",
1898
+ {
1899
+ onClick: () => setCaseSensitive((v) => !v),
1900
+ title: "Aa: Case sensitive",
1901
+ style: { fontWeight: caseSensitive ? 700 : 400 },
1902
+ children: "Aa"
1903
+ }
1904
+ ),
1905
+ /* @__PURE__ */ jsxs11("button", { onClick: () => setShowReplace((v) => !v), title: "Reemplazar (Ctrl+H)", children: [
1906
+ showReplace ? "\u25BE" : "\u25B8",
1907
+ " Reemplazar"
1908
+ ] }),
1909
+ showReplace && /* @__PURE__ */ jsxs11(Fragment2, { children: [
1910
+ /* @__PURE__ */ jsx12(
1911
+ "input",
1912
+ {
1913
+ type: "text",
1914
+ value: replacement,
1915
+ onChange: (e) => setReplacement(e.target.value),
1916
+ placeholder: "Reemplazar con..."
1917
+ }
1918
+ ),
1919
+ /* @__PURE__ */ jsx12("button", { onClick: replaceCurrent, title: "Reemplazar actual", children: "1" }),
1920
+ /* @__PURE__ */ jsx12("button", { onClick: replaceAll, title: "Reemplazar todos", children: "\u2200" })
1921
+ ] }),
1922
+ /* @__PURE__ */ jsx12("button", { onClick: onClose, title: "Cerrar (Esc)", children: "\u2715" })
1923
+ ] });
1924
+ };
1925
+
1926
+ // src/components/SciNotebook.tsx
1927
+ import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
1928
+ var SciNotebook = ({
1929
+ notebook: initialNotebook,
1930
+ engine: providedEngine,
1931
+ plugins,
1932
+ initialContent,
1933
+ theme = "light",
1934
+ onChange,
1935
+ onCellFocus,
1936
+ readOnly = false,
1937
+ showToolbar = true,
1938
+ className,
1939
+ style,
1940
+ engineRef,
1941
+ showTOC: showTOCProp = false
1942
+ }) => {
1943
+ const engine = useMemo6(() => {
1944
+ if (providedEngine) return providedEngine;
1945
+ if (initialContent && !initialNotebook) {
1946
+ const cells2 = parseInitialContent(initialContent);
1947
+ return createNotebook({
1948
+ notebook: {
1949
+ id: "",
1950
+ title: "Untitled",
1951
+ cells: cells2,
1952
+ metadata: {},
1953
+ version: 1,
1954
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1955
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1956
+ },
1957
+ config: { plugins }
1958
+ });
1959
+ }
1960
+ return createNotebook({ notebook: initialNotebook, config: { plugins } });
1961
+ }, [providedEngine]);
1962
+ useEffect10(() => {
1963
+ if (engineRef) engineRef.current = engine;
1964
+ return () => {
1965
+ if (engineRef) engineRef.current = null;
1966
+ };
1967
+ }, [engine, engineRef]);
1968
+ const [notebook, setNotebook] = useState12(initialNotebook || engine.getNotebook());
1969
+ useEffect10(() => {
1970
+ const unsub = engine.on("notebook:updated", (payload) => {
1971
+ setNotebook(payload.data.notebook);
1972
+ if (onChange) onChange(payload.data.notebook);
1973
+ });
1974
+ return unsub;
1975
+ }, [engine, onChange]);
1976
+ useEffect10(() => {
1977
+ if (!onCellFocus) return;
1978
+ return engine.on("cell:focused", (payload) => {
1979
+ onCellFocus(payload.data.cellId);
1980
+ });
1981
+ }, [engine, onCellFocus]);
1982
+ const pipeline = useMemo6(() => new RenderPipeline(), []);
1983
+ const cells = notebook.cells;
1984
+ const [showFind, setShowFind] = useState12(false);
1985
+ const [showTOC, setShowTOC] = useState12(showTOCProp);
1986
+ const [focusedCellId, setFocusedCellId] = useState12(null);
1987
+ useEffect10(() => {
1988
+ return engine.on("cell:focused", (payload) => {
1989
+ setFocusedCellId(payload.data.cellId);
1990
+ });
1991
+ }, [engine]);
1992
+ const handleGlobalKeyDown = useCallback11((e) => {
1993
+ if (readOnly) return;
1994
+ if ((e.ctrlKey || e.metaKey) && e.key === "f") {
1995
+ e.preventDefault();
1996
+ setShowFind((v) => !v);
1997
+ return;
1998
+ }
1999
+ engine.handleKeyDown(e.nativeEvent);
2000
+ }, [engine, readOnly]);
2001
+ return /* @__PURE__ */ jsx13(NotebookContext.Provider, { value: engine, children: /* @__PURE__ */ jsxs12(
2002
+ "div",
2003
+ {
2004
+ className: `sci-nb ${className || ""}`,
2005
+ style,
2006
+ "data-theme": theme,
2007
+ onKeyDown: handleGlobalKeyDown,
2008
+ tabIndex: 0,
2009
+ children: [
2010
+ showToolbar && /* @__PURE__ */ jsxs12("div", { className: "sci-nb-toolbar", children: [
2011
+ /* @__PURE__ */ jsx13("div", { className: "sci-nb-toolbar-group", children: /* @__PURE__ */ jsx13("span", { className: "sci-nb-toolbar-title", children: notebook.title }) }),
2012
+ /* @__PURE__ */ jsxs12("div", { className: "sci-nb-toolbar-group", children: [
2013
+ /* @__PURE__ */ jsxs12(
2014
+ "button",
2015
+ {
2016
+ className: "sci-nb-toolbar-btn",
2017
+ onClick: () => engine.undo(),
2018
+ disabled: !engine.canUndo(),
2019
+ title: "Undo (Ctrl+Z)",
2020
+ children: [
2021
+ /* @__PURE__ */ jsx13("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: /* @__PURE__ */ jsx13("path", { d: "M3 7h6a3 3 0 010 6H7M3 7l3-3M3 7l3 3", strokeLinecap: "round", strokeLinejoin: "round" }) }),
2022
+ "Undo"
2023
+ ]
2024
+ }
2025
+ ),
2026
+ /* @__PURE__ */ jsxs12(
2027
+ "button",
2028
+ {
2029
+ className: "sci-nb-toolbar-btn",
2030
+ onClick: () => engine.redo(),
2031
+ disabled: !engine.canRedo(),
2032
+ title: "Redo (Ctrl+Shift+Z)",
2033
+ children: [
2034
+ "Redo",
2035
+ /* @__PURE__ */ jsx13("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: /* @__PURE__ */ jsx13("path", { d: "M11 7H5a3 3 0 000 6h2M11 7l-3-3M11 7l-3 3", strokeLinecap: "round", strokeLinejoin: "round" }) })
2036
+ ]
2037
+ }
2038
+ ),
2039
+ /* @__PURE__ */ jsx13("span", { className: "sci-nb-toolbar-sep" }),
2040
+ /* @__PURE__ */ jsx13(
2041
+ "button",
2042
+ {
2043
+ className: "sci-nb-toolbar-btn",
2044
+ onClick: () => engine.setAllEditMode(),
2045
+ title: "Edit all cells",
2046
+ children: "Edit All"
2047
+ }
2048
+ ),
2049
+ /* @__PURE__ */ jsx13(
2050
+ "button",
2051
+ {
2052
+ className: "sci-nb-toolbar-btn",
2053
+ onClick: () => engine.setAllViewMode(),
2054
+ title: "Preview all cells",
2055
+ children: "View All"
2056
+ }
2057
+ ),
2058
+ /* @__PURE__ */ jsx13("span", { className: "sci-nb-toolbar-sep" }),
2059
+ /* @__PURE__ */ jsx13(
2060
+ "button",
2061
+ {
2062
+ className: "sci-nb-toolbar-btn",
2063
+ onClick: () => setShowFind((v) => !v),
2064
+ title: "Find & Replace (Ctrl+F)",
2065
+ children: "Buscar"
2066
+ }
2067
+ ),
2068
+ /* @__PURE__ */ jsx13(
2069
+ "button",
2070
+ {
2071
+ className: `sci-nb-toolbar-btn ${showTOC ? "sci-nb-toolbar-btn--active" : ""}`,
2072
+ onClick: () => setShowTOC((v) => !v),
2073
+ title: "Table of Contents",
2074
+ children: "TOC"
2075
+ }
2076
+ )
2077
+ ] })
2078
+ ] }),
2079
+ showFind && /* @__PURE__ */ jsx13(FindReplace, { onClose: () => setShowFind(false) }),
2080
+ /* @__PURE__ */ jsxs12("div", { className: "sci-nb-layout", style: { display: "flex", gap: 16 }, children: [
2081
+ showTOC && /* @__PURE__ */ jsx13(TOCSidebar, { focusedCellId }),
2082
+ /* @__PURE__ */ jsxs12("div", { className: "sci-nb-cells", style: { flex: 1 }, children: [
2083
+ cells.length === 0 && /* @__PURE__ */ jsxs12("div", { className: "sci-nb-empty", children: [
2084
+ /* @__PURE__ */ jsx13("div", { className: "sci-nb-empty-icon", children: /* @__PURE__ */ jsxs12("svg", { width: "48", height: "48", viewBox: "0 0 48 48", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: [
2085
+ /* @__PURE__ */ jsx13("rect", { x: "8", y: "6", width: "32", height: "36", rx: "4" }),
2086
+ /* @__PURE__ */ jsx13("line", { x1: "14", y1: "14", x2: "34", y2: "14" }),
2087
+ /* @__PURE__ */ jsx13("line", { x1: "14", y1: "22", x2: "28", y2: "22" }),
2088
+ /* @__PURE__ */ jsx13("line", { x1: "14", y1: "30", x2: "22", y2: "30" })
2089
+ ] }) }),
2090
+ /* @__PURE__ */ jsx13("p", { children: "Notebook vacio. Agrega una celda para comenzar." }),
2091
+ /* @__PURE__ */ jsx13(InsertHandle, { index: 0 })
2092
+ ] }),
2093
+ cells.length > 0 && /* @__PURE__ */ jsx13(InsertHandle, { index: 0 }),
2094
+ cells.map((cell, idx) => /* @__PURE__ */ jsxs12(React12.Fragment, { children: [
2095
+ /* @__PURE__ */ jsx13(
2096
+ Cell,
2097
+ {
2098
+ cellId: cell.id,
2099
+ pipeline,
2100
+ index: idx,
2101
+ totalCells: cells.length
2102
+ }
2103
+ ),
2104
+ /* @__PURE__ */ jsx13(InsertHandle, { index: idx + 1 })
2105
+ ] }, cell.id))
2106
+ ] })
2107
+ ] })
2108
+ ]
2109
+ }
2110
+ ) });
2111
+ };
2112
+ function parseInitialContent(content) {
2113
+ const blocks = content.split(/\n---\n/).filter((b) => b.trim());
2114
+ if (blocks.length === 0) {
2115
+ return [{ id: `cell_${Date.now()}`, type: "markdown", source: content, metadata: {} }];
2116
+ }
2117
+ return blocks.map((block, i) => ({
2118
+ id: `cell_${Date.now()}_${i}`,
2119
+ type: "markdown",
2120
+ source: block.trim(),
2121
+ metadata: {}
2122
+ }));
2123
+ }
2124
+
2125
+ // src/components/LatexAutocomplete.tsx
2126
+ import { useState as useState13, useEffect as useEffect11, useRef as useRef9, useMemo as useMemo7 } from "react";
2127
+ import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
2128
+ var LATEX_COMMANDS = [
2129
+ // Structures
2130
+ { cmd: "\\frac{}{}", desc: "Fracci\xF3n", category: "struct" },
2131
+ { cmd: "\\sqrt{}", desc: "Ra\xEDz cuadrada", category: "struct" },
2132
+ { cmd: "\\sqrt[]{}", desc: "Ra\xEDz n-\xE9sima", category: "struct" },
2133
+ { cmd: "^{}", desc: "Super\xEDndice", category: "struct" },
2134
+ { cmd: "_{}", desc: "Sub\xEDndice", category: "struct" },
2135
+ { cmd: "\\hat{}", desc: "Hat", category: "struct" },
2136
+ { cmd: "\\bar{}", desc: "Bar", category: "struct" },
2137
+ { cmd: "\\vec{}", desc: "Vector", category: "struct" },
2138
+ { cmd: "\\tilde{}", desc: "Tilde", category: "struct" },
2139
+ { cmd: "\\dot{}", desc: "Dot", category: "struct" },
2140
+ { cmd: "\\ddot{}", desc: "Double dot", category: "struct" },
2141
+ { cmd: "\\overline{}", desc: "Overline", category: "struct" },
2142
+ { cmd: "\\underline{}", desc: "Underline", category: "struct" },
2143
+ { cmd: "\\overbrace{}", desc: "Overbrace", category: "struct" },
2144
+ { cmd: "\\underbrace{}", desc: "Underbrace", category: "struct" },
2145
+ // Integrals
2146
+ { cmd: "\\int", desc: "Integral", category: "calc" },
2147
+ { cmd: "\\int_{}^{}", desc: "Integral definida", category: "calc" },
2148
+ { cmd: "\\iint", desc: "Integral doble", category: "calc" },
2149
+ { cmd: "\\iiint", desc: "Integral triple", category: "calc" },
2150
+ { cmd: "\\oint", desc: "Integral de contorno", category: "calc" },
2151
+ { cmd: "\\sum_{}^{}", desc: "Sumatoria", category: "calc" },
2152
+ { cmd: "\\prod_{}^{}", desc: "Productoria", category: "calc" },
2153
+ { cmd: "\\lim_{\\to}", desc: "L\xEDmite", category: "calc" },
2154
+ { cmd: "\\partial", desc: "Derivada parcial", category: "calc" },
2155
+ { cmd: "\\nabla", desc: "Nabla", category: "calc" },
2156
+ { cmd: "\\infty", desc: "Infinito", category: "calc" },
2157
+ // Greek
2158
+ { cmd: "\\alpha", desc: "\u03B1", category: "greek" },
2159
+ { cmd: "\\beta", desc: "\u03B2", category: "greek" },
2160
+ { cmd: "\\gamma", desc: "\u03B3", category: "greek" },
2161
+ { cmd: "\\delta", desc: "\u03B4", category: "greek" },
2162
+ { cmd: "\\epsilon", desc: "\u03B5", category: "greek" },
2163
+ { cmd: "\\zeta", desc: "\u03B6", category: "greek" },
2164
+ { cmd: "\\eta", desc: "\u03B7", category: "greek" },
2165
+ { cmd: "\\theta", desc: "\u03B8", category: "greek" },
2166
+ { cmd: "\\iota", desc: "\u03B9", category: "greek" },
2167
+ { cmd: "\\kappa", desc: "\u03BA", category: "greek" },
2168
+ { cmd: "\\lambda", desc: "\u03BB", category: "greek" },
2169
+ { cmd: "\\mu", desc: "\u03BC", category: "greek" },
2170
+ { cmd: "\\nu", desc: "\u03BD", category: "greek" },
2171
+ { cmd: "\\xi", desc: "\u03BE", category: "greek" },
2172
+ { cmd: "\\pi", desc: "\u03C0", category: "greek" },
2173
+ { cmd: "\\rho", desc: "\u03C1", category: "greek" },
2174
+ { cmd: "\\sigma", desc: "\u03C3", category: "greek" },
2175
+ { cmd: "\\tau", desc: "\u03C4", category: "greek" },
2176
+ { cmd: "\\upsilon", desc: "\u03C5", category: "greek" },
2177
+ { cmd: "\\phi", desc: "\u03C6", category: "greek" },
2178
+ { cmd: "\\chi", desc: "\u03C7", category: "greek" },
2179
+ { cmd: "\\psi", desc: "\u03C8", category: "greek" },
2180
+ { cmd: "\\omega", desc: "\u03C9", category: "greek" },
2181
+ { cmd: "\\Gamma", desc: "\u0393", category: "greek" },
2182
+ { cmd: "\\Delta", desc: "\u0394", category: "greek" },
2183
+ { cmd: "\\Theta", desc: "\u0398", category: "greek" },
2184
+ { cmd: "\\Lambda", desc: "\u039B", category: "greek" },
2185
+ { cmd: "\\Sigma", desc: "\u03A3", category: "greek" },
2186
+ { cmd: "\\Phi", desc: "\u03A6", category: "greek" },
2187
+ { cmd: "\\Psi", desc: "\u03A8", category: "greek" },
2188
+ { cmd: "\\Omega", desc: "\u03A9", category: "greek" },
2189
+ // Operators
2190
+ { cmd: "\\pm", desc: "\xB1", category: "op" },
2191
+ { cmd: "\\mp", desc: "\u2213", category: "op" },
2192
+ { cmd: "\\times", desc: "\xD7", category: "op" },
2193
+ { cmd: "\\div", desc: "\xF7", category: "op" },
2194
+ { cmd: "\\cdot", desc: "\xB7", category: "op" },
2195
+ { cmd: "\\leq", desc: "\u2264", category: "op" },
2196
+ { cmd: "\\geq", desc: "\u2265", category: "op" },
2197
+ { cmd: "\\neq", desc: "\u2260", category: "op" },
2198
+ { cmd: "\\approx", desc: "\u2248", category: "op" },
2199
+ { cmd: "\\equiv", desc: "\u2261", category: "op" },
2200
+ { cmd: "\\in", desc: "\u2208", category: "op" },
2201
+ { cmd: "\\notin", desc: "\u2209", category: "op" },
2202
+ { cmd: "\\subset", desc: "\u2282", category: "op" },
2203
+ { cmd: "\\supset", desc: "\u2283", category: "op" },
2204
+ { cmd: "\\cup", desc: "\u222A", category: "op" },
2205
+ { cmd: "\\cap", desc: "\u2229", category: "op" },
2206
+ { cmd: "\\forall", desc: "\u2200", category: "op" },
2207
+ { cmd: "\\exists", desc: "\u2203", category: "op" },
2208
+ // Arrows
2209
+ { cmd: "\\rightarrow", desc: "\u2192", category: "arrow" },
2210
+ { cmd: "\\leftarrow", desc: "\u2190", category: "arrow" },
2211
+ { cmd: "\\leftrightarrow", desc: "\u2194", category: "arrow" },
2212
+ { cmd: "\\Rightarrow", desc: "\u21D2", category: "arrow" },
2213
+ { cmd: "\\Leftarrow", desc: "\u21D0", category: "arrow" },
2214
+ { cmd: "\\Leftrightarrow", desc: "\u21D4", category: "arrow" },
2215
+ { cmd: "\\mapsto", desc: "\u21A6", category: "arrow" },
2216
+ // Functions
2217
+ { cmd: "\\sin", desc: "sin", category: "func" },
2218
+ { cmd: "\\cos", desc: "cos", category: "func" },
2219
+ { cmd: "\\tan", desc: "tan", category: "func" },
2220
+ { cmd: "\\log", desc: "log", category: "func" },
2221
+ { cmd: "\\ln", desc: "ln", category: "func" },
2222
+ { cmd: "\\exp", desc: "exp", category: "func" },
2223
+ { cmd: "\\max", desc: "max", category: "func" },
2224
+ { cmd: "\\min", desc: "min", category: "func" },
2225
+ { cmd: "\\det", desc: "det", category: "func" },
2226
+ // Environments
2227
+ { cmd: "\\begin{pmatrix}\\end{pmatrix}", desc: "Matriz ()", category: "env" },
2228
+ { cmd: "\\begin{bmatrix}\\end{bmatrix}", desc: "Matriz []", category: "env" },
2229
+ { cmd: "\\begin{vmatrix}\\end{vmatrix}", desc: "Determinante ||", category: "env" },
2230
+ { cmd: "\\begin{cases}\\end{cases}", desc: "Cases {", category: "env" },
2231
+ { cmd: "\\begin{aligned}\\end{aligned}", desc: "Aligned", category: "env" },
2232
+ // Delimiters
2233
+ { cmd: "\\left(\\right)", desc: "Par\xE9ntesis auto", category: "delim" },
2234
+ { cmd: "\\left[\\right]", desc: "Corchetes auto", category: "delim" },
2235
+ { cmd: "\\left\\{\\right\\}", desc: "Llaves auto", category: "delim" },
2236
+ { cmd: "\\left|\\right|", desc: "Valor absoluto", category: "delim" },
2237
+ { cmd: "\\lfloor\\rfloor", desc: "Piso", category: "delim" },
2238
+ { cmd: "\\lceil\\rceil", desc: "Techo", category: "delim" },
2239
+ // Misc
2240
+ { cmd: "\\text{}", desc: "Texto", category: "misc" },
2241
+ { cmd: "\\mathbb{}", desc: "Blackboard bold", category: "misc" },
2242
+ { cmd: "\\mathcal{}", desc: "Caligr\xE1fico", category: "misc" },
2243
+ { cmd: "\\mathrm{}", desc: "Roman", category: "misc" },
2244
+ { cmd: "\\mathbf{}", desc: "Bold", category: "misc" },
2245
+ { cmd: "\\quad", desc: "Espacio grande", category: "misc" },
2246
+ { cmd: "\\qquad", desc: "Espacio doble", category: "misc" },
2247
+ { cmd: "\\,", desc: "Espacio fino", category: "misc" },
2248
+ { cmd: "\\ldots", desc: "Puntos ...", category: "misc" },
2249
+ { cmd: "\\cdots", desc: "Puntos centrados", category: "misc" },
2250
+ { cmd: "\\vdots", desc: "Puntos verticales", category: "misc" },
2251
+ { cmd: "\\ddots", desc: "Puntos diagonales", category: "misc" }
2252
+ ];
2253
+ var LatexAutocomplete = ({
2254
+ query,
2255
+ position,
2256
+ onSelect,
2257
+ onClose
2258
+ }) => {
2259
+ const [selectedIndex, setSelectedIndex] = useState13(0);
2260
+ const menuRef = useRef9(null);
2261
+ const filtered = useMemo7(() => {
2262
+ if (!query) return LATEX_COMMANDS.slice(0, 12);
2263
+ const q = query.toLowerCase();
2264
+ return LATEX_COMMANDS.filter(
2265
+ (c) => c.cmd.toLowerCase().includes(q) || c.desc.toLowerCase().includes(q)
2266
+ ).slice(0, 10);
2267
+ }, [query]);
2268
+ useEffect11(() => {
2269
+ setSelectedIndex(0);
2270
+ }, [query]);
2271
+ useEffect11(() => {
2272
+ const handleKey = (e) => {
2273
+ if (e.key === "ArrowDown") {
2274
+ e.preventDefault();
2275
+ e.stopPropagation();
2276
+ setSelectedIndex((i) => (i + 1) % Math.max(filtered.length, 1));
2277
+ } else if (e.key === "ArrowUp") {
2278
+ e.preventDefault();
2279
+ e.stopPropagation();
2280
+ setSelectedIndex((i) => (i - 1 + filtered.length) % Math.max(filtered.length, 1));
2281
+ } else if (e.key === "Enter" || e.key === "Tab") {
2282
+ if (filtered.length > 0) {
2283
+ e.preventDefault();
2284
+ e.stopPropagation();
2285
+ onSelect(filtered[selectedIndex]?.cmd || "");
2286
+ }
2287
+ } else if (e.key === "Escape") {
2288
+ e.preventDefault();
2289
+ onClose();
2290
+ }
2291
+ };
2292
+ document.addEventListener("keydown", handleKey, true);
2293
+ return () => document.removeEventListener("keydown", handleKey, true);
2294
+ }, [filtered, selectedIndex, onSelect, onClose]);
2295
+ if (filtered.length === 0) return null;
2296
+ return /* @__PURE__ */ jsx14(
2297
+ "div",
2298
+ {
2299
+ ref: menuRef,
2300
+ className: "sci-nb-slash-menu",
2301
+ style: { top: position.top, left: position.left, minWidth: 220 },
2302
+ children: filtered.map((item, i) => /* @__PURE__ */ jsxs13(
2303
+ "button",
2304
+ {
2305
+ className: `sci-nb-slash-item ${i === selectedIndex ? "sci-nb-slash-item--active" : ""}`,
2306
+ onMouseEnter: () => setSelectedIndex(i),
2307
+ onClick: () => onSelect(item.cmd),
2308
+ children: [
2309
+ /* @__PURE__ */ jsx14("span", { className: "sci-nb-slash-icon", style: { fontFamily: "monospace", fontSize: 11 }, children: item.desc.length <= 2 ? item.desc : item.cmd.slice(0, 4) }),
2310
+ /* @__PURE__ */ jsxs13("div", { className: "sci-nb-slash-text", children: [
2311
+ /* @__PURE__ */ jsx14("span", { className: "sci-nb-slash-label", style: { fontFamily: "monospace", fontSize: 12 }, children: item.cmd }),
2312
+ /* @__PURE__ */ jsx14("span", { className: "sci-nb-slash-desc", children: item.desc })
2313
+ ] })
2314
+ ]
2315
+ },
2316
+ item.cmd
2317
+ ))
2318
+ }
2319
+ );
2320
+ };
2321
+
2322
+ // src/components/GhostText.tsx
2323
+ import { useEffect as useEffect12, useCallback as useCallback13 } from "react";
2324
+ import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
2325
+ var GhostText = ({
2326
+ text,
2327
+ textareaRef,
2328
+ onAccept,
2329
+ onDismiss
2330
+ }) => {
2331
+ const handleKeyDown = useCallback13((e) => {
2332
+ if (e.key === "Tab" && !e.shiftKey) {
2333
+ e.preventDefault();
2334
+ e.stopPropagation();
2335
+ onAccept();
2336
+ } else if (e.key === "Escape") {
2337
+ e.preventDefault();
2338
+ onDismiss();
2339
+ }
2340
+ }, [onAccept, onDismiss]);
2341
+ useEffect12(() => {
2342
+ const ta2 = textareaRef.current;
2343
+ if (!ta2) return;
2344
+ ta2.addEventListener("keydown", handleKeyDown, true);
2345
+ return () => ta2.removeEventListener("keydown", handleKeyDown, true);
2346
+ }, [textareaRef, handleKeyDown]);
2347
+ if (!text) return null;
2348
+ const ta = textareaRef.current;
2349
+ if (!ta) return null;
2350
+ const cursorPos = ta.selectionStart;
2351
+ const before = ta.value.slice(0, cursorPos);
2352
+ const lines = before.split("\n");
2353
+ const lineHeight = 22;
2354
+ const charWidth = 7.8;
2355
+ const top = (lines.length - 1) * lineHeight;
2356
+ const left = lines[lines.length - 1].length * charWidth;
2357
+ const firstLine = text.split("\n")[0];
2358
+ const hasMore = text.includes("\n");
2359
+ return /* @__PURE__ */ jsxs14(
2360
+ "div",
2361
+ {
2362
+ className: "sci-nb-ghost-text",
2363
+ style: {
2364
+ position: "absolute",
2365
+ top: `${top + 10}px`,
2366
+ left: `${left + 12}px`,
2367
+ pointerEvents: "none",
2368
+ whiteSpace: "pre",
2369
+ fontFamily: "inherit",
2370
+ fontSize: "inherit",
2371
+ lineHeight: `${lineHeight}px`,
2372
+ zIndex: 5
2373
+ },
2374
+ children: [
2375
+ firstLine,
2376
+ hasMore ? "..." : "",
2377
+ /* @__PURE__ */ jsx15("span", { style: { fontSize: 10, opacity: 0.5, marginLeft: 8 }, children: "Tab \u21B9" })
2378
+ ]
2379
+ }
2380
+ );
2381
+ };
2382
+
2383
+ // src/components/ChatSidebar.tsx
2384
+ import { useState as useState14, useCallback as useCallback14, useRef as useRef10, useEffect as useEffect13 } from "react";
2385
+ import { jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
2386
+ var ChatSidebar = ({
2387
+ onSend,
2388
+ systemPrompt,
2389
+ onApply,
2390
+ onClose
2391
+ }) => {
2392
+ const engine = useSciNotebook();
2393
+ const [messages, setMessages] = useState14(() => {
2394
+ if (systemPrompt) {
2395
+ return [{ role: "system", content: systemPrompt, timestamp: Date.now() }];
2396
+ }
2397
+ return [];
2398
+ });
2399
+ const [input, setInput] = useState14("");
2400
+ const [loading, setLoading] = useState14(false);
2401
+ const messagesEndRef = useRef10(null);
2402
+ const inputRef = useRef10(null);
2403
+ useEffect13(() => {
2404
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
2405
+ }, [messages]);
2406
+ useEffect13(() => {
2407
+ inputRef.current?.focus();
2408
+ }, []);
2409
+ const handleSend = useCallback14(async () => {
2410
+ const text = input.trim();
2411
+ if (!text || loading) return;
2412
+ const userMsg = { role: "user", content: text, timestamp: Date.now() };
2413
+ const newMessages = [...messages, userMsg];
2414
+ setMessages(newMessages);
2415
+ setInput("");
2416
+ setLoading(true);
2417
+ try {
2418
+ if (onSend) {
2419
+ const response = await onSend(text, newMessages);
2420
+ setMessages((prev) => [
2421
+ ...prev,
2422
+ { role: "assistant", content: response, timestamp: Date.now() }
2423
+ ]);
2424
+ } else {
2425
+ setMessages((prev) => [
2426
+ ...prev,
2427
+ {
2428
+ role: "assistant",
2429
+ content: "No AI provider configured. Pass an `onSend` prop to enable AI chat.",
2430
+ timestamp: Date.now()
2431
+ }
2432
+ ]);
2433
+ }
2434
+ } catch (e) {
2435
+ setMessages((prev) => [
2436
+ ...prev,
2437
+ {
2438
+ role: "assistant",
2439
+ content: `Error: ${e.message || "Failed to get response"}`,
2440
+ timestamp: Date.now()
2441
+ }
2442
+ ]);
2443
+ } finally {
2444
+ setLoading(false);
2445
+ }
2446
+ }, [input, messages, loading, onSend]);
2447
+ const handleKeyDown = useCallback14((e) => {
2448
+ if (e.key === "Enter" && !e.shiftKey) {
2449
+ e.preventDefault();
2450
+ handleSend();
2451
+ }
2452
+ }, [handleSend]);
2453
+ const visibleMessages = messages.filter((m) => m.role !== "system");
2454
+ return /* @__PURE__ */ jsxs15("div", { className: "sci-nb-chat-sidebar", children: [
2455
+ /* @__PURE__ */ jsxs15("div", { className: "sci-nb-chat-header", children: [
2456
+ /* @__PURE__ */ jsx16("span", { children: "AI Assistant" }),
2457
+ onClose && /* @__PURE__ */ jsx16(
2458
+ "button",
2459
+ {
2460
+ onClick: onClose,
2461
+ style: { border: "none", background: "transparent", cursor: "pointer", fontSize: 16 },
2462
+ children: "\u2715"
2463
+ }
2464
+ )
2465
+ ] }),
2466
+ /* @__PURE__ */ jsxs15("div", { className: "sci-nb-chat-messages", children: [
2467
+ visibleMessages.length === 0 && /* @__PURE__ */ jsx16("div", { style: { textAlign: "center", color: "#94a3b8", padding: 24 }, children: "Ask me anything about your notebook..." }),
2468
+ visibleMessages.map((msg, i) => /* @__PURE__ */ jsxs15("div", { className: `sci-nb-chat-msg sci-nb-chat-msg--${msg.role}`, children: [
2469
+ /* @__PURE__ */ jsx16("div", { children: msg.content }),
2470
+ msg.role === "assistant" && onApply && /* @__PURE__ */ jsx16(
2471
+ "button",
2472
+ {
2473
+ onClick: () => onApply(msg.content),
2474
+ style: {
2475
+ marginTop: 4,
2476
+ fontSize: 11,
2477
+ padding: "2px 6px",
2478
+ border: "1px solid #e2e8f0",
2479
+ borderRadius: 4,
2480
+ background: "transparent",
2481
+ cursor: "pointer"
2482
+ },
2483
+ children: "Apply to cell"
2484
+ }
2485
+ )
2486
+ ] }, i)),
2487
+ loading && /* @__PURE__ */ jsx16("div", { className: "sci-nb-chat-msg sci-nb-chat-msg--assistant", style: { opacity: 0.6 }, children: "Thinking..." }),
2488
+ /* @__PURE__ */ jsx16("div", { ref: messagesEndRef })
2489
+ ] }),
2490
+ /* @__PURE__ */ jsxs15("div", { className: "sci-nb-chat-input", children: [
2491
+ /* @__PURE__ */ jsx16(
2492
+ "input",
2493
+ {
2494
+ ref: inputRef,
2495
+ type: "text",
2496
+ value: input,
2497
+ onChange: (e) => setInput(e.target.value),
2498
+ onKeyDown: handleKeyDown,
2499
+ placeholder: "Ask something...",
2500
+ disabled: loading
2501
+ }
2502
+ ),
2503
+ /* @__PURE__ */ jsx16("button", { onClick: handleSend, disabled: loading || !input.trim(), children: "Send" })
2504
+ ] })
2505
+ ] });
2506
+ };
2507
+
2508
+ // src/components/ImageResize.tsx
2509
+ import { useState as useState15, useCallback as useCallback15, useRef as useRef11, useEffect as useEffect14 } from "react";
2510
+ import { jsx as jsx17, jsxs as jsxs16 } from "react/jsx-runtime";
2511
+ var ImageResize = ({
2512
+ src,
2513
+ alt = "",
2514
+ initialWidth,
2515
+ maxWidth = "100%",
2516
+ onResize,
2517
+ children
2518
+ }) => {
2519
+ const containerRef = useRef11(null);
2520
+ const imgRef = useRef11(null);
2521
+ const [dragging, setDragging] = useState15(false);
2522
+ const [width, setWidth] = useState15(null);
2523
+ const startRef = useRef11({ x: 0, w: 0 });
2524
+ const handleMouseDown = useCallback15((e) => {
2525
+ e.preventDefault();
2526
+ e.stopPropagation();
2527
+ const img = imgRef.current;
2528
+ if (!img) return;
2529
+ startRef.current = { x: e.clientX, w: img.offsetWidth };
2530
+ setDragging(true);
2531
+ }, []);
2532
+ useEffect14(() => {
2533
+ if (!dragging) return;
2534
+ const handleMouseMove = (e) => {
2535
+ const dx = e.clientX - startRef.current.x;
2536
+ const newW = Math.max(50, startRef.current.w + dx);
2537
+ setWidth(newW);
2538
+ };
2539
+ const handleMouseUp = () => {
2540
+ setDragging(false);
2541
+ if (width !== null && containerRef.current) {
2542
+ const parentW = containerRef.current.parentElement?.offsetWidth || 1;
2543
+ const pct = Math.round(width / parentW * 100);
2544
+ onResize(`${Math.min(pct, 100)}%`);
2545
+ }
2546
+ };
2547
+ document.addEventListener("mousemove", handleMouseMove);
2548
+ document.addEventListener("mouseup", handleMouseUp);
2549
+ return () => {
2550
+ document.removeEventListener("mousemove", handleMouseMove);
2551
+ document.removeEventListener("mouseup", handleMouseUp);
2552
+ };
2553
+ }, [dragging, width, onResize]);
2554
+ const style = {
2555
+ maxWidth,
2556
+ width: width !== null ? `${width}px` : initialWidth,
2557
+ position: "relative",
2558
+ display: "inline-block"
2559
+ };
2560
+ return /* @__PURE__ */ jsxs16("div", { ref: containerRef, className: "sci-nb-image-resizable", style, children: [
2561
+ /* @__PURE__ */ jsx17(
2562
+ "img",
2563
+ {
2564
+ ref: imgRef,
2565
+ src,
2566
+ alt,
2567
+ style: { width: "100%", height: "auto", display: "block" },
2568
+ draggable: false
2569
+ }
2570
+ ),
2571
+ children,
2572
+ /* @__PURE__ */ jsx17(
2573
+ "div",
2574
+ {
2575
+ className: "sci-nb-image-resize-handle sci-nb-image-resize-handle--se",
2576
+ onMouseDown: handleMouseDown
2577
+ }
2578
+ )
2579
+ ] });
2580
+ };
2581
+
2582
+ // src/components/VirtualRenderer.tsx
2583
+ import { useState as useState16, useEffect as useEffect15, useRef as useRef12, useMemo as useMemo8, useCallback as useCallback16 } from "react";
2584
+ import { jsx as jsx18, jsxs as jsxs17 } from "react/jsx-runtime";
2585
+ var VirtualRenderer = ({
2586
+ cells,
2587
+ pipeline,
2588
+ estimatedHeight = 120,
2589
+ overscan = 5
2590
+ }) => {
2591
+ const containerRef = useRef12(null);
2592
+ const [visibleRange, setVisibleRange] = useState16({ start: 0, end: 20 });
2593
+ const cellHeights = useRef12(/* @__PURE__ */ new Map());
2594
+ const getHeight = useCallback16((index) => {
2595
+ return cellHeights.current.get(index) ?? estimatedHeight;
2596
+ }, [estimatedHeight]);
2597
+ const totalHeight = useMemo8(() => {
2598
+ let h = 0;
2599
+ for (let i = 0; i < cells.length; i++) {
2600
+ h += getHeight(i) + 32;
2601
+ }
2602
+ return h;
2603
+ }, [cells.length, getHeight]);
2604
+ useEffect15(() => {
2605
+ const container = containerRef.current;
2606
+ if (!container) return;
2607
+ const handleScroll = () => {
2608
+ const scrollTop = container.scrollTop;
2609
+ const viewportHeight = container.clientHeight;
2610
+ let accum = 0;
2611
+ let start = 0;
2612
+ let end = cells.length;
2613
+ for (let i = 0; i < cells.length; i++) {
2614
+ const h = getHeight(i) + 32;
2615
+ if (accum + h >= scrollTop && start === 0) {
2616
+ start = Math.max(0, i - overscan);
2617
+ }
2618
+ if (accum > scrollTop + viewportHeight) {
2619
+ end = Math.min(cells.length, i + overscan);
2620
+ break;
2621
+ }
2622
+ accum += h;
2623
+ }
2624
+ setVisibleRange((prev) => {
2625
+ if (prev.start === start && prev.end === end) return prev;
2626
+ return { start, end };
2627
+ });
2628
+ };
2629
+ handleScroll();
2630
+ container.addEventListener("scroll", handleScroll, { passive: true });
2631
+ return () => container.removeEventListener("scroll", handleScroll);
2632
+ }, [cells.length, getHeight, overscan]);
2633
+ const measureRef = useCallback16((index) => {
2634
+ return (el) => {
2635
+ if (!el) return;
2636
+ const observer = new ResizeObserver((entries) => {
2637
+ for (const entry of entries) {
2638
+ const h = entry.contentRect.height;
2639
+ if (h > 0 && cellHeights.current.get(index) !== h) {
2640
+ cellHeights.current.set(index, h);
2641
+ }
2642
+ }
2643
+ });
2644
+ observer.observe(el);
2645
+ };
2646
+ }, []);
2647
+ const topOffset = useMemo8(() => {
2648
+ let h = 0;
2649
+ for (let i = 0; i < visibleRange.start; i++) {
2650
+ h += getHeight(i) + 32;
2651
+ }
2652
+ return h;
2653
+ }, [visibleRange.start, getHeight]);
2654
+ const visibleCells = cells.slice(visibleRange.start, visibleRange.end);
2655
+ return /* @__PURE__ */ jsx18(
2656
+ "div",
2657
+ {
2658
+ ref: containerRef,
2659
+ className: "sci-nb-virtual-container",
2660
+ style: { height: "100%", overflow: "auto", position: "relative" },
2661
+ children: /* @__PURE__ */ jsx18("div", { style: { height: totalHeight, position: "relative" }, children: /* @__PURE__ */ jsxs17("div", { style: { position: "absolute", top: topOffset, left: 0, right: 0 }, children: [
2662
+ visibleRange.start === 0 && /* @__PURE__ */ jsx18(InsertHandle, { index: 0 }),
2663
+ visibleCells.map((cell, i) => {
2664
+ const realIndex = visibleRange.start + i;
2665
+ return /* @__PURE__ */ jsxs17("div", { ref: measureRef(realIndex), children: [
2666
+ /* @__PURE__ */ jsx18(
2667
+ Cell,
2668
+ {
2669
+ cellId: cell.id,
2670
+ pipeline,
2671
+ index: realIndex,
2672
+ totalCells: cells.length
2673
+ }
2674
+ ),
2675
+ /* @__PURE__ */ jsx18(InsertHandle, { index: realIndex + 1 })
2676
+ ] }, cell.id);
2677
+ })
2678
+ ] }) })
2679
+ }
2680
+ );
2681
+ };
2682
+
2683
+ // src/components/AIRewrite.tsx
2684
+ import { useState as useState17, useCallback as useCallback17, useRef as useRef13, useEffect as useEffect16 } from "react";
2685
+ import { jsx as jsx19, jsxs as jsxs18 } from "react/jsx-runtime";
2686
+ var AIRewrite = ({
2687
+ selectedText,
2688
+ position,
2689
+ onRewrite,
2690
+ onAccept,
2691
+ onReject
2692
+ }) => {
2693
+ const [state, setState] = useState17("prompt");
2694
+ const [instruction, setInstruction] = useState17("");
2695
+ const [result, setResult] = useState17("");
2696
+ const [error, setError] = useState17(null);
2697
+ const inputRef = useRef13(null);
2698
+ useEffect16(() => {
2699
+ inputRef.current?.focus();
2700
+ }, []);
2701
+ const handleSubmit = useCallback17(async () => {
2702
+ if (!instruction.trim()) return;
2703
+ setState("loading");
2704
+ setError(null);
2705
+ try {
2706
+ const rewritten = await onRewrite(instruction, selectedText);
2707
+ setResult(rewritten);
2708
+ setState("preview");
2709
+ } catch (e) {
2710
+ setError(e.message || "Rewrite failed");
2711
+ setState("prompt");
2712
+ }
2713
+ }, [instruction, selectedText, onRewrite]);
2714
+ const handleKeyDown = useCallback17((e) => {
2715
+ if (e.key === "Enter" && !e.shiftKey) {
2716
+ e.preventDefault();
2717
+ handleSubmit();
2718
+ } else if (e.key === "Escape") {
2719
+ e.preventDefault();
2720
+ onReject();
2721
+ }
2722
+ }, [handleSubmit, onReject]);
2723
+ const handleAccept = useCallback17(() => {
2724
+ onAccept(result);
2725
+ }, [result, onAccept]);
2726
+ const handleRetry = useCallback17(() => {
2727
+ setState("prompt");
2728
+ setResult("");
2729
+ inputRef.current?.focus();
2730
+ }, []);
2731
+ return /* @__PURE__ */ jsxs18(
2732
+ "div",
2733
+ {
2734
+ className: "sci-nb-ai-rewrite",
2735
+ style: {
2736
+ position: "absolute",
2737
+ top: position.top,
2738
+ left: position.left,
2739
+ zIndex: 100
2740
+ },
2741
+ children: [
2742
+ state === "prompt" && /* @__PURE__ */ jsxs18("div", { className: "sci-nb-ai-rewrite-prompt", children: [
2743
+ /* @__PURE__ */ jsxs18("div", { className: "sci-nb-ai-rewrite-selected", children: [
2744
+ /* @__PURE__ */ jsx19("span", { className: "sci-nb-ai-rewrite-label", children: "Selected:" }),
2745
+ /* @__PURE__ */ jsx19("span", { className: "sci-nb-ai-rewrite-text", children: selectedText.length > 80 ? selectedText.slice(0, 80) + "..." : selectedText })
2746
+ ] }),
2747
+ /* @__PURE__ */ jsxs18("div", { className: "sci-nb-ai-rewrite-input-row", children: [
2748
+ /* @__PURE__ */ jsx19(
2749
+ "input",
2750
+ {
2751
+ ref: inputRef,
2752
+ type: "text",
2753
+ value: instruction,
2754
+ onChange: (e) => setInstruction(e.target.value),
2755
+ onKeyDown: handleKeyDown,
2756
+ placeholder: "How should I rewrite this?",
2757
+ className: "sci-nb-ai-rewrite-input"
2758
+ }
2759
+ ),
2760
+ /* @__PURE__ */ jsx19(
2761
+ "button",
2762
+ {
2763
+ onClick: handleSubmit,
2764
+ disabled: !instruction.trim(),
2765
+ className: "sci-nb-ai-rewrite-btn sci-nb-ai-rewrite-btn--primary",
2766
+ children: "Rewrite"
2767
+ }
2768
+ ),
2769
+ /* @__PURE__ */ jsx19("button", { onClick: onReject, className: "sci-nb-ai-rewrite-btn", children: "Cancel" })
2770
+ ] }),
2771
+ error && /* @__PURE__ */ jsx19("div", { className: "sci-nb-ai-rewrite-error", children: error })
2772
+ ] }),
2773
+ state === "loading" && /* @__PURE__ */ jsx19("div", { className: "sci-nb-ai-rewrite-loading", children: /* @__PURE__ */ jsx19("span", { children: "Rewriting..." }) }),
2774
+ state === "preview" && /* @__PURE__ */ jsxs18("div", { className: "sci-nb-ai-rewrite-preview", children: [
2775
+ /* @__PURE__ */ jsxs18("div", { className: "sci-nb-ai-rewrite-diff", children: [
2776
+ /* @__PURE__ */ jsxs18("div", { className: "sci-nb-ai-rewrite-diff-old", children: [
2777
+ /* @__PURE__ */ jsx19("span", { className: "sci-nb-ai-rewrite-diff-label", children: "Original:" }),
2778
+ /* @__PURE__ */ jsx19("pre", { children: selectedText })
2779
+ ] }),
2780
+ /* @__PURE__ */ jsxs18("div", { className: "sci-nb-ai-rewrite-diff-new", children: [
2781
+ /* @__PURE__ */ jsx19("span", { className: "sci-nb-ai-rewrite-diff-label", children: "Rewritten:" }),
2782
+ /* @__PURE__ */ jsx19("pre", { children: result })
2783
+ ] })
2784
+ ] }),
2785
+ /* @__PURE__ */ jsxs18("div", { className: "sci-nb-ai-rewrite-actions", children: [
2786
+ /* @__PURE__ */ jsx19(
2787
+ "button",
2788
+ {
2789
+ onClick: handleAccept,
2790
+ className: "sci-nb-ai-rewrite-btn sci-nb-ai-rewrite-btn--primary",
2791
+ children: "Accept"
2792
+ }
2793
+ ),
2794
+ /* @__PURE__ */ jsx19("button", { onClick: handleRetry, className: "sci-nb-ai-rewrite-btn", children: "Retry" }),
2795
+ /* @__PURE__ */ jsx19("button", { onClick: onReject, className: "sci-nb-ai-rewrite-btn", children: "Reject" })
2796
+ ] })
2797
+ ] })
2798
+ ]
2799
+ }
2800
+ );
2801
+ };
2802
+
2803
+ // src/components/AICellGenerate.tsx
2804
+ import { useState as useState18, useCallback as useCallback18, useRef as useRef14, useEffect as useEffect17 } from "react";
2805
+ import { jsx as jsx20, jsxs as jsxs19 } from "react/jsx-runtime";
2806
+ var AICellGenerate = ({
2807
+ onGenerate,
2808
+ onAccept,
2809
+ onCancel
2810
+ }) => {
2811
+ const [state, setState] = useState18("prompt");
2812
+ const [prompt, setPrompt] = useState18("");
2813
+ const [cells, setCells] = useState18([]);
2814
+ const [error, setError] = useState18(null);
2815
+ const inputRef = useRef14(null);
2816
+ useEffect17(() => {
2817
+ inputRef.current?.focus();
2818
+ }, []);
2819
+ const handleGenerate = useCallback18(async () => {
2820
+ if (!prompt.trim()) return;
2821
+ setState("loading");
2822
+ setError(null);
2823
+ try {
2824
+ const generated = await onGenerate(prompt);
2825
+ setCells(generated);
2826
+ setState("preview");
2827
+ } catch (e) {
2828
+ setError(e.message || "Generation failed");
2829
+ setState("prompt");
2830
+ }
2831
+ }, [prompt, onGenerate]);
2832
+ const handleKeyDown = useCallback18((e) => {
2833
+ if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) {
2834
+ e.preventDefault();
2835
+ handleGenerate();
2836
+ } else if (e.key === "Escape") {
2837
+ e.preventDefault();
2838
+ onCancel();
2839
+ }
2840
+ }, [handleGenerate, onCancel]);
2841
+ const handleAccept = useCallback18(() => {
2842
+ onAccept(cells);
2843
+ }, [cells, onAccept]);
2844
+ const handleRegenerate = useCallback18(() => {
2845
+ setState("prompt");
2846
+ setCells([]);
2847
+ inputRef.current?.focus();
2848
+ }, []);
2849
+ const CELL_TYPE_LABELS = {
2850
+ markdown: "Markdown",
2851
+ code: "Code",
2852
+ latex: "LaTeX",
2853
+ table: "Table",
2854
+ mermaid: "Mermaid",
2855
+ raw: "Raw"
2856
+ };
2857
+ return /* @__PURE__ */ jsxs19("div", { className: "sci-nb-ai-generate", children: [
2858
+ state === "prompt" && /* @__PURE__ */ jsxs19("div", { className: "sci-nb-ai-generate-prompt", children: [
2859
+ /* @__PURE__ */ jsxs19("div", { className: "sci-nb-ai-generate-header", children: [
2860
+ /* @__PURE__ */ jsx20("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: /* @__PURE__ */ jsx20("path", { d: "M8 1v14M1 8h14", strokeLinecap: "round" }) }),
2861
+ /* @__PURE__ */ jsx20("span", { children: "Generate cells with AI" })
2862
+ ] }),
2863
+ /* @__PURE__ */ jsx20(
2864
+ "textarea",
2865
+ {
2866
+ ref: inputRef,
2867
+ value: prompt,
2868
+ onChange: (e) => setPrompt(e.target.value),
2869
+ onKeyDown: handleKeyDown,
2870
+ placeholder: "Describe what you want to generate...\ne.g. 'Create a markdown cell explaining Newton's second law with a LaTeX formula'",
2871
+ className: "sci-nb-ai-generate-textarea",
2872
+ rows: 3
2873
+ }
2874
+ ),
2875
+ /* @__PURE__ */ jsxs19("div", { className: "sci-nb-ai-generate-actions", children: [
2876
+ /* @__PURE__ */ jsx20(
2877
+ "button",
2878
+ {
2879
+ onClick: handleGenerate,
2880
+ disabled: !prompt.trim(),
2881
+ className: "sci-nb-ai-rewrite-btn sci-nb-ai-rewrite-btn--primary",
2882
+ children: "Generate (Ctrl+Enter)"
2883
+ }
2884
+ ),
2885
+ /* @__PURE__ */ jsx20("button", { onClick: onCancel, className: "sci-nb-ai-rewrite-btn", children: "Cancel" })
2886
+ ] }),
2887
+ error && /* @__PURE__ */ jsx20("div", { className: "sci-nb-ai-rewrite-error", children: error })
2888
+ ] }),
2889
+ state === "loading" && /* @__PURE__ */ jsx20("div", { className: "sci-nb-ai-generate-loading", children: /* @__PURE__ */ jsx20("span", { children: "Generating cells..." }) }),
2890
+ state === "preview" && /* @__PURE__ */ jsxs19("div", { className: "sci-nb-ai-generate-preview", children: [
2891
+ /* @__PURE__ */ jsx20("div", { className: "sci-nb-ai-generate-header", children: /* @__PURE__ */ jsxs19("span", { children: [
2892
+ "Generated ",
2893
+ cells.length,
2894
+ " cell",
2895
+ cells.length !== 1 ? "s" : ""
2896
+ ] }) }),
2897
+ /* @__PURE__ */ jsx20("div", { className: "sci-nb-ai-generate-cells", children: cells.map((cell, i) => /* @__PURE__ */ jsxs19("div", { className: "sci-nb-ai-generate-cell", children: [
2898
+ /* @__PURE__ */ jsx20("div", { className: "sci-nb-ai-generate-cell-badge", children: CELL_TYPE_LABELS[cell.type] || cell.type }),
2899
+ /* @__PURE__ */ jsx20("pre", { className: "sci-nb-ai-generate-cell-source", children: cell.source.length > 200 ? cell.source.slice(0, 200) + "..." : cell.source })
2900
+ ] }, i)) }),
2901
+ /* @__PURE__ */ jsxs19("div", { className: "sci-nb-ai-generate-actions", children: [
2902
+ /* @__PURE__ */ jsxs19(
2903
+ "button",
2904
+ {
2905
+ onClick: handleAccept,
2906
+ className: "sci-nb-ai-rewrite-btn sci-nb-ai-rewrite-btn--primary",
2907
+ children: [
2908
+ "Insert ",
2909
+ cells.length,
2910
+ " cell",
2911
+ cells.length !== 1 ? "s" : ""
2912
+ ]
2913
+ }
2914
+ ),
2915
+ /* @__PURE__ */ jsx20("button", { onClick: handleRegenerate, className: "sci-nb-ai-rewrite-btn", children: "Regenerate" }),
2916
+ /* @__PURE__ */ jsx20("button", { onClick: onCancel, className: "sci-nb-ai-rewrite-btn", children: "Cancel" })
2917
+ ] })
2918
+ ] })
2919
+ ] });
2920
+ };
2921
+ export {
2922
+ AICellGenerate,
2923
+ AIRewrite,
2924
+ Cell,
2925
+ CellOutputDisplay,
2926
+ ChatSidebar,
2927
+ DEFAULT_COMMANDS,
2928
+ EmbedCell,
2929
+ FindReplace,
2930
+ FloatingToolbar,
2931
+ GhostText,
2932
+ ImageCell,
2933
+ ImageResize,
2934
+ InsertHandle,
2935
+ LATEX_COMMANDS,
2936
+ LatexAutocomplete,
2937
+ MathEditor,
2938
+ MermaidPreview,
2939
+ NotebookContext,
2940
+ SciNotebook,
2941
+ SlashCommand,
2942
+ TOCSidebar,
2943
+ TableCell,
2944
+ VirtualRenderer,
2945
+ initMermaid,
2946
+ renderEmbedPreview,
2947
+ renderImagePreview,
2948
+ renderTablePreview,
2949
+ useCell,
2950
+ useFocusedCell,
2951
+ useNotebook,
2952
+ useNotebookEvent,
2953
+ useSciNotebook
2954
+ };