@braincrew-lab/langchain-canvas 0.1.13 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import { loadOptional } from './chunk-YZZSJJMQ.js';
2
- import { useCanvasStore } from './chunk-KKLWKR5G.js';
2
+ import { useCanvasStore } from './chunk-S54GJDSJ.js';
3
3
  import { lazy, useMemo, useState, useRef, useEffect, useCallback, Suspense } from 'react';
4
4
  import '@fortune-sheet/react/dist/index.css';
5
5
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
@@ -252,6 +252,65 @@ function TableRenderer({ artifact }) {
252
252
  const range = sel?.[0]?.[type] ?? [0, 0];
253
253
  wbRef.current?.insertRowOrColumn(type, Math.max(0, range[1]), 1, "rightbottom");
254
254
  };
255
+ const [selection, setSelection] = useState(null);
256
+ const [boldOn, setBoldOn] = useState(false);
257
+ const [fillColor, setFillColor] = useState("#fef3c7");
258
+ const [textColor, setTextColor] = useState("#111827");
259
+ const handleSelectionChange = useCallback((_sheetId, sel) => {
260
+ setSelection({ row: [...sel.row], column: [...sel.column] });
261
+ const bl = wbRef.current?.getCellValue?.(sel.row[0], sel.column[0], { type: "bl" });
262
+ setBoldOn(bl === 1 || bl === "1");
263
+ }, []);
264
+ const workbookHooks = useMemo(() => ({ afterSelectionChange: handleSelectionChange }), [handleSelectionChange]);
265
+ const applyFormat = (attr, value) => {
266
+ const sel = wbRef.current?.getSelection?.();
267
+ if (!sel?.length) return;
268
+ const ranges = sel.map((s) => ({ row: [s.row[0], s.row[1]], column: [s.column[0], s.column[1]] }));
269
+ wbRef.current?.setCellFormatByRange(attr, value, ranges);
270
+ };
271
+ const toggleBold = () => {
272
+ applyFormat("bl", boldOn ? 0 : 1);
273
+ setBoldOn((b) => !b);
274
+ };
275
+ const cleanStyling = () => {
276
+ const wb = wbRef.current;
277
+ const sheet = wb?.getSheet?.();
278
+ const sheetId = sheet?.id;
279
+ if (!wb || !sheet || !sheetId) return;
280
+ const ops = [];
281
+ sheet.celldata.forEach(({ r, c, v }) => {
282
+ if (!v || typeof v !== "object") return;
283
+ const cell = v;
284
+ if (cell.bg != null) ops.push({ op: "remove", id: sheetId, path: ["data", r, c, "bg"] });
285
+ if (cell.fc != null) ops.push({ op: "remove", id: sheetId, path: ["data", r, c, "fc"] });
286
+ const spans = cell.ct?.s;
287
+ if (!Array.isArray(spans)) return;
288
+ spans.forEach((span, i) => {
289
+ if (!span || typeof span !== "object") return;
290
+ const run = span;
291
+ if (run.bg != null) ops.push({ op: "remove", id: sheetId, path: ["data", r, c, "ct", "s", i, "bg"] });
292
+ if (run.fc != null) ops.push({ op: "remove", id: sheetId, path: ["data", r, c, "ct", "s", i, "fc"] });
293
+ });
294
+ });
295
+ if (ops.length) wb.applyOp(ops);
296
+ };
297
+ const [frozen, setFrozen] = useState(false);
298
+ const toggleFreeze = () => {
299
+ const wb = wbRef.current;
300
+ if (!wb) return;
301
+ if (frozen) {
302
+ const sheetId = wb.getSheet?.()?.id;
303
+ if (sheetId) wb.applyOp([{ op: "remove", id: sheetId, path: ["frozen"] }]);
304
+ } else {
305
+ wb.freeze("row", { row: 0, column: 0 });
306
+ }
307
+ setFrozen((f) => !f);
308
+ };
309
+ useEffect(() => {
310
+ setFrozen(!viewActive && hasSheet && !!artifact.data.sheet?.[0]?.frozen);
311
+ setSelection(null);
312
+ setBoldOn(false);
313
+ }, [wbKey]);
255
314
  if (!mounted) {
256
315
  return /* @__PURE__ */ jsx("div", { className: "cv-sheet cv-sheet--empty", children: "Loading spreadsheet\u2026" });
257
316
  }
@@ -265,6 +324,65 @@ function TableRenderer({ artifact }) {
265
324
  /* @__PURE__ */ jsxs("div", { className: "cv-sheet-tools", children: [
266
325
  /* @__PURE__ */ jsx("button", { type: "button", onClick: () => insert("column"), children: "\uFF0B Column" }),
267
326
  /* @__PURE__ */ jsx("button", { type: "button", onClick: () => insert("row"), children: "\uFF0B Row" }),
327
+ /* @__PURE__ */ jsx("span", { className: "cv-sheet-tools__sep" }),
328
+ /* @__PURE__ */ jsxs("span", { className: "cv-sheet-tools__fmt", children: [
329
+ /* @__PURE__ */ jsx(
330
+ "button",
331
+ {
332
+ type: "button",
333
+ className: `cv-sheet-tools__bold${boldOn ? " cv-sheet-tools__bold--on" : ""}`,
334
+ title: "Bold",
335
+ "aria-pressed": boldOn,
336
+ disabled: !selection,
337
+ onClick: toggleBold,
338
+ children: "B"
339
+ }
340
+ ),
341
+ /* @__PURE__ */ jsx(
342
+ "input",
343
+ {
344
+ type: "color",
345
+ className: "cv-sheet-tools__color cv-sheet-tools__color--fill",
346
+ title: "Fill color",
347
+ disabled: !selection,
348
+ value: fillColor,
349
+ onChange: (e) => {
350
+ setFillColor(e.target.value);
351
+ applyFormat("bg", e.target.value);
352
+ }
353
+ }
354
+ ),
355
+ /* @__PURE__ */ jsx(
356
+ "input",
357
+ {
358
+ type: "color",
359
+ className: "cv-sheet-tools__color cv-sheet-tools__color--text",
360
+ title: "Text color",
361
+ disabled: !selection,
362
+ value: textColor,
363
+ onChange: (e) => {
364
+ setTextColor(e.target.value);
365
+ applyFormat("fc", e.target.value);
366
+ }
367
+ }
368
+ ),
369
+ /* @__PURE__ */ jsx("button", { type: "button", className: "cv-sheet-tools__align", title: "Align left", disabled: !selection, onClick: () => applyFormat("ht", 1), children: "\u21E4" }),
370
+ /* @__PURE__ */ jsx("button", { type: "button", className: "cv-sheet-tools__align", title: "Align center", disabled: !selection, onClick: () => applyFormat("ht", 0), children: "\u2194" }),
371
+ /* @__PURE__ */ jsx("button", { type: "button", className: "cv-sheet-tools__align", title: "Align right", disabled: !selection, onClick: () => applyFormat("ht", 2), children: "\u21E5" })
372
+ ] }),
373
+ /* @__PURE__ */ jsx("span", { className: "cv-sheet-tools__sep" }),
374
+ /* @__PURE__ */ jsx("button", { type: "button", className: "cv-sheet-tools__clean", title: "Remove all cell fills and font colors", onClick: cleanStyling, children: "Clean styling" }),
375
+ /* @__PURE__ */ jsx(
376
+ "button",
377
+ {
378
+ type: "button",
379
+ className: `cv-sheet-tools__freeze${frozen ? " cv-sheet-tools__freeze--on" : ""}`,
380
+ title: frozen ? "Unfreeze the header row" : "Keep the header row visible while scrolling",
381
+ "aria-pressed": frozen,
382
+ onClick: toggleFreeze,
383
+ children: "Freeze header"
384
+ }
385
+ ),
268
386
  columns.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
269
387
  /* @__PURE__ */ jsx("span", { className: "cv-sheet-tools__sep" }),
270
388
  /* @__PURE__ */ jsxs(
@@ -294,7 +412,7 @@ function TableRenderer({ artifact }) {
294
412
  ] }),
295
413
  /* @__PURE__ */ jsx("span", { className: "cv-sheet-tools__hint", children: "Right-click a header for more, or drag to edit" })
296
414
  ] }),
297
- /* @__PURE__ */ jsx("div", { className: "cv-sheet", ref: rootRef, children: /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx("div", { className: "cv-sheet--empty", children: "Loading\u2026" }), children: /* @__PURE__ */ jsx(Workbook, { ref: wbRef, data: initialData, onChange: handleChange }, wbKey) }) })
415
+ /* @__PURE__ */ jsx("div", { className: "cv-sheet", ref: rootRef, children: /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx("div", { className: "cv-sheet--empty", children: "Loading\u2026" }), children: /* @__PURE__ */ jsx(Workbook, { ref: wbRef, data: initialData, onChange: handleChange, hooks: workbookHooks }, wbKey) }) })
298
416
  ] });
299
417
  }
300
418
 
@@ -129,6 +129,14 @@ function lastOf(order, excludeId) {
129
129
  const remaining = order.filter((id) => id !== excludeId);
130
130
  return remaining.length ? remaining[remaining.length - 1] : null;
131
131
  }
132
+ function notifyTimeTravel(store, before) {
133
+ const handler = store.onUserEdit;
134
+ if (!handler || store.canvas === before) return;
135
+ for (const id of Object.keys(store.canvas.artifacts)) {
136
+ const now = store.canvas.artifacts[id];
137
+ if (now && now !== before.artifacts[id]) handler(now);
138
+ }
139
+ }
132
140
  function editedArtifactId(event) {
133
141
  switch (event.type) {
134
142
  case "canvas.create":
@@ -168,26 +176,34 @@ function createCanvasStore() {
168
176
  const artifact = id ? state.canvas.artifacts[id] : void 0;
169
177
  if (artifact) state.onUserEdit?.(artifact);
170
178
  },
171
- undo: () => set((state) => {
172
- if (!state.undoStack.length) return state;
173
- const previous = state.undoStack[state.undoStack.length - 1];
174
- return {
175
- canvas: previous,
176
- undoStack: state.undoStack.slice(0, -1),
177
- redoStack: [...state.redoStack, state.canvas].slice(-50),
178
- selections: []
179
- };
180
- }),
181
- redo: () => set((state) => {
182
- if (!state.redoStack.length) return state;
183
- const next = state.redoStack[state.redoStack.length - 1];
184
- return {
185
- canvas: next,
186
- redoStack: state.redoStack.slice(0, -1),
187
- undoStack: [...state.undoStack, state.canvas].slice(-50),
188
- selections: []
189
- };
190
- }),
179
+ undo: () => {
180
+ const before = get().canvas;
181
+ set((state) => {
182
+ if (!state.undoStack.length) return state;
183
+ const previous = state.undoStack[state.undoStack.length - 1];
184
+ return {
185
+ canvas: previous,
186
+ undoStack: state.undoStack.slice(0, -1),
187
+ redoStack: [...state.redoStack, state.canvas].slice(-50),
188
+ selections: []
189
+ };
190
+ });
191
+ notifyTimeTravel(get(), before);
192
+ },
193
+ redo: () => {
194
+ const before = get().canvas;
195
+ set((state) => {
196
+ if (!state.redoStack.length) return state;
197
+ const next = state.redoStack[state.redoStack.length - 1];
198
+ return {
199
+ canvas: next,
200
+ redoStack: state.redoStack.slice(0, -1),
201
+ undoStack: [...state.undoStack, state.canvas].slice(-50),
202
+ selections: []
203
+ };
204
+ });
205
+ notifyTimeTravel(get(), before);
206
+ },
191
207
  addUserMessage: (text) => set((state) => ({
192
208
  messages: [...state.messages, { id: `user_${state.messages.length}`, role: "user", text }],
193
209
  error: null
@@ -1,4 +1,4 @@
1
- import { useCanvasStore } from './chunk-KKLWKR5G.js';
1
+ import { useCanvasStore } from './chunk-S54GJDSJ.js';
2
2
  import { useCallback } from 'react';
3
3
 
4
4
  function useArtifactPatch(id) {
package/dist/index.d.ts CHANGED
@@ -82,6 +82,16 @@ interface TableData {
82
82
  */
83
83
  sheet?: Array<Record<string, unknown>>;
84
84
  }
85
+ /**
86
+ * A PDF shown in the browser's native viewer. `src` is a `data:application/pdf`
87
+ * URL (self-contained, the common case for agent/file-sourced PDFs), a `blob:`
88
+ * URL, or an `https:` URL the host is allowed to frame.
89
+ */
90
+ interface PdfData {
91
+ src: string;
92
+ /** Optional original filename, used for the download attribute. */
93
+ filename?: string;
94
+ }
85
95
  /** A freely-positioned element on a "blank" slide (percent geometry, 0–100). */
86
96
  interface SlideElement {
87
97
  id: string;
@@ -100,6 +110,9 @@ interface SlideElement {
100
110
  shape?: "rect" | "ellipse" | "line";
101
111
  /** Fill (rect/ellipse) or stroke (line) color for a shape. */
102
112
  fill?: string;
113
+ /** Editor-only grouping: elements sharing a `group` id select and move as
114
+ * one. Purely additive — exporters and the presenter ignore it. */
115
+ group?: string;
103
116
  }
104
117
  interface Slide {
105
118
  /** title · content (bullets) · section · image · two-column · blank (free canvas). */
@@ -117,6 +130,12 @@ interface Slide {
117
130
  background?: string;
118
131
  /** Slide text color (hex). */
119
132
  textColor?: string;
133
+ /** Theme accent color (hex) — used by quick layouts and new shapes for rules,
134
+ * section numbers, and stats. Set by the theme presets. */
135
+ accent?: string;
136
+ /** Font stack for the slide's text (system fonts only, no external loads).
137
+ * Set by the theme presets; cascades to every element. */
138
+ fontFamily?: string;
120
139
  /** Speaker notes (not shown on the slide; exported to the .pptx notes pane). */
121
140
  notes?: string;
122
141
  /** Content padding as a percent of the slide width (a safe margin around the
@@ -141,7 +160,10 @@ type TableArtifact = Artifact<TableData> & {
141
160
  type SlidesArtifact = Artifact<SlidesData> & {
142
161
  type: "slides";
143
162
  };
144
- type KnownArtifact = HtmlArtifact | DocumentArtifact | ChartArtifact | TableArtifact | SlidesArtifact;
163
+ type PdfArtifact = Artifact<PdfData> & {
164
+ type: "pdf";
165
+ };
166
+ type KnownArtifact = HtmlArtifact | DocumentArtifact | ChartArtifact | TableArtifact | SlidesArtifact | PdfArtifact;
145
167
 
146
168
  /**
147
169
  * Canvas Wire Protocol v1 — event envelopes. Mirror of
@@ -569,6 +591,8 @@ declare function ArtifactCard({ artifactId }: {
569
591
  artifactId: string;
570
592
  }): react.JSX.Element | null;
571
593
 
594
+ declare function PdfRenderer$1({ artifact }: RendererProps<PdfData>): react.JSX.Element;
595
+
572
596
  declare function SlidesRenderer$1({ artifact }: RendererProps<SlidesData>): react.JSX.Element;
573
597
 
574
598
  declare function TableRenderer$1({ artifact }: RendererProps<TableData>): react.JSX.Element;
@@ -583,6 +607,7 @@ declare const ChartRenderer: react.LazyExoticComponent<typeof ChartRenderer$1>;
583
607
  declare const DocumentRenderer: react.LazyExoticComponent<typeof DocumentRenderer$1>;
584
608
  declare const TableRenderer: react.LazyExoticComponent<typeof TableRenderer$1>;
585
609
  declare const SlidesRenderer: react.LazyExoticComponent<typeof SlidesRenderer$1>;
610
+ declare const PdfRenderer: react.LazyExoticComponent<typeof PdfRenderer$1>;
586
611
 
587
612
  /**
588
613
  * The batteries-included renderers. `html` is the base substrate (sandboxed
@@ -670,7 +695,7 @@ declare function printToPdf(html: string): void;
670
695
  */
671
696
 
672
697
  /** Extensions we can turn into an artifact, for `accept="…"` and drop filtering. */
673
- declare const IMPORTABLE_EXTENSIONS: readonly [".csv", ".md", ".markdown", ".txt", ".html", ".htm", ".json", ".xlsx"];
698
+ declare const IMPORTABLE_EXTENSIONS: readonly [".csv", ".md", ".markdown", ".txt", ".html", ".htm", ".json", ".xlsx", ".pdf", ".hwpx", ".hwp"];
674
699
  /** True when the file has an extension we know how to import. */
675
700
  declare const canImport: (file: File) => boolean;
676
701
  /**
@@ -695,4 +720,4 @@ declare function useCanvasImport(): {
695
720
  canImport: (file: File) => boolean;
696
721
  };
697
722
 
698
- export { type Artifact, ArtifactCard, type ArtifactRegistry, type ArtifactRenderer, type ArtifactStatus, Canvas, type CanvasAppend, type CanvasClose, type CanvasCreate, type CanvasEvent, type CanvasNodePatch, type CanvasPatch, type CanvasProps, CanvasProvider, type CanvasProviderProps, CanvasRegistryProvider, type CanvasReplace, type CanvasState, type CanvasStatus, type CanvasStore, type ChartArtifact, type ChartData, type ChartOptions, ChartRenderer, type ChartSeries, type ChatEvent, type ChatMessage, type ChatRequest, type DocumentArtifact, type DocumentData, DocumentRenderer, type DoneEvent, type ElementSelection, type ErrorEvent, ExportMenu, type FileExport, type HtmlArtifact, type HtmlData, HtmlRenderer, IMPORTABLE_EXTENSIONS, INSPECTOR_MARK, type IframeCommand, type KnownArtifact, type MessageDelta, type MessageEnd, type MockStreamOptions, type RendererProps, STYLE_PROPS, type Scenario, SelectionBar, type Slide, type SlideElement, type SlidesArtifact, type SlidesData, SlidesRenderer, type StreamEvent, type StreamOptions, StylePanel, type TableArtifact, type TableColumn, type TableData, TableRenderer, type ToolEnd, type ToolStart, type UseCanvasStreamOptions, type UserEditHandler, builtinRenderers, canImport, createCanvasStore, dataExporters, downloadBlob, emptyCanvasState, importFile, isCanvasEvent, isChatEvent, mergePatch, mergeRegistries, mockStream, parseCsv, parseSSE, printToPdf, reduceCanvas, scenarios, slidesToPrintHtml, slugify, streamChat, toStandaloneHtml, useArtifactPatch, useCanvasImport, useCanvasReplay, useCanvasStore, useCanvasStoreApi, useCanvasStream, useRenderer, withInspector };
723
+ export { type Artifact, ArtifactCard, type ArtifactRegistry, type ArtifactRenderer, type ArtifactStatus, Canvas, type CanvasAppend, type CanvasClose, type CanvasCreate, type CanvasEvent, type CanvasNodePatch, type CanvasPatch, type CanvasProps, CanvasProvider, type CanvasProviderProps, CanvasRegistryProvider, type CanvasReplace, type CanvasState, type CanvasStatus, type CanvasStore, type ChartArtifact, type ChartData, type ChartOptions, ChartRenderer, type ChartSeries, type ChatEvent, type ChatMessage, type ChatRequest, type DocumentArtifact, type DocumentData, DocumentRenderer, type DoneEvent, type ElementSelection, type ErrorEvent, ExportMenu, type FileExport, type HtmlArtifact, type HtmlData, HtmlRenderer, IMPORTABLE_EXTENSIONS, INSPECTOR_MARK, type IframeCommand, type KnownArtifact, type MessageDelta, type MessageEnd, type MockStreamOptions, type PdfArtifact, type PdfData, PdfRenderer, type RendererProps, STYLE_PROPS, type Scenario, SelectionBar, type Slide, type SlideElement, type SlidesArtifact, type SlidesData, SlidesRenderer, type StreamEvent, type StreamOptions, StylePanel, type TableArtifact, type TableColumn, type TableData, TableRenderer, type ToolEnd, type ToolStart, type UseCanvasStreamOptions, type UserEditHandler, builtinRenderers, canImport, createCanvasStore, dataExporters, downloadBlob, emptyCanvasState, importFile, isCanvasEvent, isChatEvent, mergePatch, mergeRegistries, mockStream, parseCsv, parseSSE, printToPdf, reduceCanvas, scenarios, slidesToPrintHtml, slugify, streamChat, toStandaloneHtml, useArtifactPatch, useCanvasImport, useCanvasReplay, useCanvasStore, useCanvasStoreApi, useCanvasStream, useRenderer, withInspector };