@braincrew-lab/langchain-canvas 0.4.9 → 0.5.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,4 +1,4 @@
1
- import { resolveElements } from './chunk-6L3AL6W4.js';
1
+ import { deckPage, pageAspect, DEFAULT_SLIDE_PAGE_IN, resolveElements, fontScaleFor } from './chunk-SE6AP3A7.js';
2
2
  import { useAssetUrl } from './chunk-FTNRRJ3K.js';
3
3
  import './chunk-7T5DRR3F.js';
4
4
  import { useArtifactPatch } from './chunk-K2UZAYW2.js';
@@ -29,7 +29,7 @@ function snapAxis(pos, size, targets) {
29
29
  }
30
30
  return best ? { pos: pos + best.delta, guide: best.guide } : { pos, guide: null };
31
31
  }
32
- function FreeSlide({ elements, onChange, padding }) {
32
+ function FreeSlide({ elements, onChange, padding, fontScale = 1 }) {
33
33
  const slideRef = useRef(null);
34
34
  const assetUrl = useAssetUrl();
35
35
  const [els, setEls] = useState(elements);
@@ -174,7 +174,7 @@ function FreeSlide({ elements, onChange, padding }) {
174
174
  contentEditable: editingId === el.id,
175
175
  suppressContentEditableWarning: true,
176
176
  style: {
177
- fontSize: el.fontSize ?? 24,
177
+ fontSize: (el.fontSize ?? 24) * fontScale,
178
178
  fontWeight: el.bold ? 700 : 400,
179
179
  color: el.color,
180
180
  textAlign: el.align ?? "left"
@@ -234,6 +234,19 @@ function FreeSlide({ elements, onChange, padding }) {
234
234
  }
235
235
  );
236
236
  }
237
+ function useFontScale(page) {
238
+ const [node, setNode] = useState(null);
239
+ const [scale, setScale] = useState(1);
240
+ useEffect(() => {
241
+ if (!node) return;
242
+ const measure = () => setScale(fontScaleFor(node.clientWidth, page));
243
+ measure();
244
+ const observer = new ResizeObserver(measure);
245
+ observer.observe(node);
246
+ return () => observer.disconnect();
247
+ }, [node, page.widthIn, page.heightIn]);
248
+ return { ref: setNode, scale };
249
+ }
237
250
  var THEMES = [
238
251
  { id: "light", label: "Light", bg: "#ffffff", text: "#1f2328" },
239
252
  { id: "paper", label: "Paper", bg: "#f7f5ef", text: "#2b2a26" },
@@ -282,6 +295,11 @@ function SlidesRenderer({ artifact }) {
282
295
  const [presenting, setPresenting] = useState(false);
283
296
  const imgRef = useRef(null);
284
297
  const bgRef = useRef(null);
298
+ const page = deckPage(artifact.data);
299
+ const aspect = pageAspect(page);
300
+ const editBox = useFontScale(page);
301
+ const presentBox = useFontScale(page);
302
+ const thumbFont = 0.12 * (DEFAULT_SLIDE_PAGE_IN.widthIn / page.widthIn);
285
303
  useEffect(() => {
286
304
  if (!presenting) return;
287
305
  const onKey = (e) => {
@@ -374,11 +392,11 @@ function SlidesRenderer({ artifact }) {
374
392
  onDragEnd: () => setDragIndex(null),
375
393
  children: /* @__PURE__ */ jsxs("button", { className: "cv-deck__thumb", onClick: () => setIndex(i), children: [
376
394
  /* @__PURE__ */ jsx("span", { className: "cv-deck__thumb-n", children: i + 1 }),
377
- /* @__PURE__ */ jsx("div", { className: "cv-deck__thumb-slide", style: s.background ? { background: s.background } : void 0, children: /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: `${s.padding ?? 0}%` }, children: resolveElements(s).map(
395
+ /* @__PURE__ */ jsx("div", { className: "cv-deck__thumb-slide", style: { aspectRatio: aspect, ...s.background ? { background: s.background } : {} }, children: /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: `${s.padding ?? 0}%` }, children: resolveElements(s).map(
378
396
  (el) => el.type === "text" ? /* @__PURE__ */ jsx(
379
397
  "span",
380
398
  {
381
- style: { position: "absolute", left: `${el.x}%`, top: `${el.y}%`, width: `${el.w}%`, fontSize: (el.fontSize ?? 24) * 0.12, fontWeight: el.bold ? 700 : 400, color: el.color ?? s.textColor, overflow: "hidden", whiteSpace: "pre-wrap" },
399
+ style: { position: "absolute", left: `${el.x}%`, top: `${el.y}%`, width: `${el.w}%`, fontSize: (el.fontSize ?? 24) * thumbFont, fontWeight: el.bold ? 700 : 400, color: el.color ?? s.textColor, overflow: "hidden", whiteSpace: "pre-wrap" },
382
400
  children: el.text
383
401
  },
384
402
  el.id
@@ -458,7 +476,7 @@ function SlidesRenderer({ artifact }) {
458
476
  /* @__PURE__ */ jsx("button", { onClick: duplicateSlide, title: "Duplicate slide", children: "\u29C9" }),
459
477
  /* @__PURE__ */ jsx("button", { onClick: deleteSlide, title: "Delete slide", disabled: slides.length === 1, children: "\u{1F5D1}" })
460
478
  ] }),
461
- /* @__PURE__ */ jsx("div", { className: "cv-slide cv-slide--blank", style: slideStyle, children: /* @__PURE__ */ jsx(FreeSlide, { elements: resolveElements(slide), onChange: (elements) => update({ elements }), padding: slide.padding }) }),
479
+ /* @__PURE__ */ jsx("div", { className: "cv-slide cv-slide--blank", ref: editBox.ref, style: { aspectRatio: aspect, ...slideStyle }, children: /* @__PURE__ */ jsx(FreeSlide, { elements: resolveElements(slide), onChange: (elements) => update({ elements }), padding: slide.padding, fontScale: editBox.scale }) }),
462
480
  /* @__PURE__ */ jsxs("div", { className: "cv-deck__nav cv-chrome", children: [
463
481
  /* @__PURE__ */ jsx("button", { disabled: at === 0, onClick: () => setIndex(at - 1), "aria-label": "Previous slide", children: "\u2039" }),
464
482
  /* @__PURE__ */ jsxs("span", { children: [
@@ -479,8 +497,8 @@ function SlidesRenderer({ artifact }) {
479
497
  )
480
498
  ] }),
481
499
  presenting && /* @__PURE__ */ jsxs("div", { className: "cv-present", onClick: () => setIndex(Math.min(at + 1, slides.length - 1)), children: [
482
- /* @__PURE__ */ jsx("div", { className: "cv-present__slide cv-present__fade cv-slide cv-slide--blank", style: slideStyle, children: /* @__PURE__ */ jsx("div", { className: "cv-free", style: slide.padding ? { inset: `${slide.padding}%` } : void 0, children: resolveElements(slide).map(
483
- (el) => el.type === "text" ? /* @__PURE__ */ jsx("div", { className: "cv-free__el", style: { left: `${el.x}%`, top: `${el.y}%`, width: `${el.w}%`, height: `${el.h}%` }, children: /* @__PURE__ */ jsx("div", { className: "cv-free__text", style: { fontSize: el.fontSize ?? 24, fontWeight: el.bold ? 700 : 400, color: el.color, textAlign: el.align ?? "left", whiteSpace: "pre-wrap" }, children: el.text }) }, el.id) : el.type === "shape" ? /* @__PURE__ */ jsx("div", { className: "cv-free__el", style: { left: `${el.x}%`, top: `${el.y}%`, width: `${el.w}%`, height: `${el.h}%`, color: slide.textColor }, children: /* @__PURE__ */ jsx("div", { style: shapeStyle(el) }) }, el.id) : /* @__PURE__ */ jsx("div", { className: "cv-free__el", style: { left: `${el.x}%`, top: `${el.y}%`, width: `${el.w}%`, height: `${el.h}%` }, children: /* @__PURE__ */ jsx("img", { className: "cv-free__img", src: assetUrl(el.src), alt: "" }) }, el.id)
500
+ /* @__PURE__ */ jsx("div", { ref: presentBox.ref, className: "cv-present__slide cv-present__fade cv-slide cv-slide--blank", style: { aspectRatio: aspect, ...slideStyle }, children: /* @__PURE__ */ jsx("div", { className: "cv-free", style: slide.padding ? { inset: `${slide.padding}%` } : void 0, children: resolveElements(slide).map(
501
+ (el) => el.type === "text" ? /* @__PURE__ */ jsx("div", { className: "cv-free__el", style: { left: `${el.x}%`, top: `${el.y}%`, width: `${el.w}%`, height: `${el.h}%` }, children: /* @__PURE__ */ jsx("div", { className: "cv-free__text", style: { fontSize: (el.fontSize ?? 24) * presentBox.scale, fontWeight: el.bold ? 700 : 400, color: el.color, textAlign: el.align ?? "left", whiteSpace: "pre-wrap" }, children: el.text }) }, el.id) : el.type === "shape" ? /* @__PURE__ */ jsx("div", { className: "cv-free__el", style: { left: `${el.x}%`, top: `${el.y}%`, width: `${el.w}%`, height: `${el.h}%`, color: slide.textColor }, children: /* @__PURE__ */ jsx("div", { style: shapeStyle(el) }) }, el.id) : /* @__PURE__ */ jsx("div", { className: "cv-free__el", style: { left: `${el.x}%`, top: `${el.y}%`, width: `${el.w}%`, height: `${el.h}%` }, children: /* @__PURE__ */ jsx("img", { className: "cv-free__img", src: assetUrl(el.src), alt: "" }) }, el.id)
484
502
  ) }) }, at),
485
503
  slide.notes ? /* @__PURE__ */ jsx("div", { className: "cv-present__notes", onClick: (e) => e.stopPropagation(), children: slide.notes }) : null,
486
504
  /* @__PURE__ */ jsxs("div", { className: "cv-present__hint", children: [
@@ -21,4 +21,24 @@ function toElements(s) {
21
21
  }
22
22
  var resolveElements = (s) => s.elements?.length ? s.elements : toElements(s);
23
23
 
24
- export { resolveElements };
24
+ // src/client/slidePage.ts
25
+ var DEFAULT_SLIDE_PAGE_IN = { widthIn: 10, heightIn: 5.625 };
26
+ var PAGE_DPI = 96;
27
+ function deckPage(data) {
28
+ const page = data.page;
29
+ if (!page) return DEFAULT_SLIDE_PAGE_IN;
30
+ const { widthIn, heightIn } = page;
31
+ if (!Number.isFinite(widthIn) || !Number.isFinite(heightIn) || widthIn <= 0 || heightIn <= 0) {
32
+ return DEFAULT_SLIDE_PAGE_IN;
33
+ }
34
+ return { widthIn, heightIn };
35
+ }
36
+ function pageAspect(page) {
37
+ return `${page.widthIn} / ${page.heightIn}`;
38
+ }
39
+ function fontScaleFor(boxWidthPx, page) {
40
+ if (!Number.isFinite(boxWidthPx) || boxWidthPx <= 0) return 1;
41
+ return boxWidthPx / (page.widthIn * PAGE_DPI);
42
+ }
43
+
44
+ export { DEFAULT_SLIDE_PAGE_IN, deckPage, fontScaleFor, pageAspect, resolveElements };
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { projectSheetIntoRows, mergeRowsIntoSheet } from './chunk-IFNRLN4Y.js';
3
3
  import { loadOptional } from './chunk-YZZSJJMQ.js';
4
- import { resolveElements } from './chunk-6L3AL6W4.js';
4
+ import { deckPage, resolveElements } from './chunk-SE6AP3A7.js';
5
5
  export { useAssetUrl } from './chunk-FTNRRJ3K.js';
6
6
  import { inlineArtifactAssets, inlineHtmlAssets } from './chunk-7T5DRR3F.js';
7
7
  export { ASSET_REFERENCE_PREFIXES, fetchAssetDataUri, inlineArtifactAssets, inlineHtmlAssets, isAssetReference, normalizeAssetReference, resolveAssetUrl } from './chunk-7T5DRR3F.js';
@@ -2258,7 +2258,7 @@ function HtmlRenderer({ artifact: artifact2 }) {
2258
2258
  var ChartRenderer = lazy(() => import('./ChartRenderer-JGRJ23OB.js').then((m) => ({ default: m.ChartRenderer })));
2259
2259
  var DocumentRenderer = lazy(() => import('./DocumentRenderer-ZQDQMX7X.js').then((m) => ({ default: m.DocumentRenderer })));
2260
2260
  var TableRenderer = lazy(() => import('./TableRenderer-3ESF577F.js').then((m) => ({ default: m.TableRenderer })));
2261
- var SlidesRenderer = lazy(() => import('./SlidesRenderer-56R3TNWN.js').then((m) => ({ default: m.SlidesRenderer })));
2261
+ var SlidesRenderer = lazy(() => import('./SlidesRenderer-4TZNTSEL.js').then((m) => ({ default: m.SlidesRenderer })));
2262
2262
  var FileRenderer = lazy(() => import('./FileRenderer-3ZHNORZJ.js').then((m) => ({ default: m.FileRenderer })));
2263
2263
  var builtinRenderers = {
2264
2264
  html: HtmlRenderer,
@@ -2414,8 +2414,9 @@ async function documentToDocx(data) {
2414
2414
  async function slidesToPptx(data, _title) {
2415
2415
  const PptxGenJS = (await loadOptional("pptxgenjs", () => import('pptxgenjs'))).default;
2416
2416
  const pptx = new PptxGenJS();
2417
- const W = 10;
2418
- const H = 5.625;
2417
+ const { widthIn: W, heightIn: H } = deckPage(data);
2418
+ pptx.defineLayout({ name: "CV_PAGE", width: W, height: H });
2419
+ pptx.layout = "CV_PAGE";
2419
2420
  for (const slide of data.slides) {
2420
2421
  const s = pptx.addSlide();
2421
2422
  if (slide.background && /^#[0-9a-f]{3,8}$/i.test(slide.background)) s.background = { color: slide.background.replace("#", "") };
@@ -2444,6 +2445,9 @@ async function slidesToPptx(data, _title) {
2444
2445
  }
2445
2446
  function slidesToPrintHtml(data, title) {
2446
2447
  const slides2 = data.slides.length ? data.slides : [{ title: "Empty deck" }];
2448
+ const page = deckPage(data);
2449
+ const pw = Math.round(page.widthIn * 128);
2450
+ const ph = Math.round(page.heightIn * 128);
2447
2451
  const pages = slides2.map((slide) => {
2448
2452
  const bg = slide.background ?? "#ffffff";
2449
2453
  const fg = slide.textColor ?? "#1f2328";
@@ -2466,10 +2470,10 @@ function slidesToPrintHtml(data, title) {
2466
2470
  return `<section class="slide" style="background:${escapeAttr(bg)}">${inner}</section>`;
2467
2471
  }).join("");
2468
2472
  return `<!doctype html><html><head><meta charset="utf-8"><title>${escapeXml(title)}</title><style>
2469
- @page { size: 1280px 720px; margin: 0; }
2473
+ @page { size: ${pw}px ${ph}px; margin: 0; }
2470
2474
  * { margin: 0; box-sizing: border-box; }
2471
2475
  body { font-family: Inter, Arial, sans-serif; }
2472
- .slide { position: relative; width: 1280px; height: 720px; overflow: hidden; page-break-after: always; }
2476
+ .slide { position: relative; width: ${pw}px; height: ${ph}px; overflow: hidden; page-break-after: always; }
2473
2477
  .el { position: absolute; overflow: hidden; line-height: 1.25; }
2474
2478
  img.el { object-fit: contain; }
2475
2479
  </style></head><body>${pages}</body></html>`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@braincrew-lab/langchain-canvas",
3
- "version": "0.4.9",
3
+ "version": "0.5.0",
4
4
  "description": "A live canvas for LangChain agents — stream documents, charts, and rich artifacts into a React panel.",
5
5
  "license": "MIT",
6
6
  "author": "Brain Crew (https://github.com/braincrew-lab)",