@braincrew-lab/langchain-canvas 0.1.12 → 0.1.13

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,30 +1,32 @@
1
1
  import { useArtifactPatch } from './chunk-TERWLUW3.js';
2
2
  import './chunk-KKLWKR5G.js';
3
- import { useState, useMemo, useRef, useEffect } from 'react';
3
+ import { useState, useRef, useMemo, useEffect } from 'react';
4
4
  import * as echarts from 'echarts/core';
5
5
  import { BarChart, LineChart, PieChart } from 'echarts/charts';
6
6
  import { GridComponent, TooltipComponent, LegendComponent } from 'echarts/components';
7
7
  import { CanvasRenderer } from 'echarts/renderers';
8
- import { jsx, jsxs } from 'react/jsx-runtime';
8
+ import { jsxs, jsx } from 'react/jsx-runtime';
9
9
 
10
10
  echarts.use([BarChart, LineChart, PieChart, GridComponent, TooltipComponent, LegendComponent, CanvasRenderer]);
11
11
  var PALETTE = ["#6366f1", "#10b981", "#f59e0b", "#ef4444", "#0ea5e9", "#a855f7"];
12
12
  var seriesColor = (series, index) => series.color ?? PALETTE[index % PALETTE.length];
13
13
  var sliceColor = (options, index) => options?.colors?.[index] ?? PALETTE[index % PALETTE.length];
14
14
  var CHART_TYPES = ["bar", "line", "area", "pie"];
15
- function EChart({ option, height }) {
15
+ function EChart({ option, height, onInst }) {
16
16
  const boxRef = useRef(null);
17
17
  const instRef = useRef(null);
18
18
  useEffect(() => {
19
19
  if (!boxRef.current) return;
20
20
  const inst = echarts.init(boxRef.current, void 0, { renderer: "canvas" });
21
21
  instRef.current = inst;
22
+ onInst?.(inst);
22
23
  const ro = new ResizeObserver(() => inst.resize());
23
24
  ro.observe(boxRef.current);
24
25
  return () => {
25
26
  ro.disconnect();
26
27
  inst.dispose();
27
28
  instRef.current = null;
29
+ onInst?.(null);
28
30
  };
29
31
  }, []);
30
32
  useEffect(() => {
@@ -36,12 +38,28 @@ function ChartRenderer({ artifact }) {
36
38
  const { chart, rows, xKey, series, options, echartsOption } = artifact.data;
37
39
  const patch = useArtifactPatch(artifact.id);
38
40
  const [editing, setEditing] = useState(false);
41
+ const instRef = useRef(null);
42
+ const downloadPng = () => {
43
+ const inst = instRef.current;
44
+ if (!inst) return;
45
+ const url = inst.getDataURL({ type: "png", pixelRatio: 2, backgroundColor: "#ffffff" });
46
+ const a = document.createElement("a");
47
+ a.href = url;
48
+ a.download = `${(artifact.title || "chart").replace(/[^a-z0-9가-힣]+/gi, "-").replace(/^-+|-+$/g, "") || "chart"}.png`;
49
+ a.click();
50
+ };
39
51
  const option = useMemo(
40
52
  () => echartsOption ?? toEChartsOption(chart, rows, xKey, series, options),
41
53
  [echartsOption, chart, rows, xKey, series, options]
42
54
  );
43
55
  if (echartsOption) {
44
- return /* @__PURE__ */ jsx("div", { className: "cv-chart", children: /* @__PURE__ */ jsx(EChart, { option, height: 340 }) });
56
+ return /* @__PURE__ */ jsxs("div", { className: "cv-chart", children: [
57
+ /* @__PURE__ */ jsxs("div", { className: "cv-chart__toolbar cv-chrome", children: [
58
+ /* @__PURE__ */ jsx("span", { className: "cv-chart__spacer" }),
59
+ /* @__PURE__ */ jsx("button", { className: "cv-edit-btn", onClick: downloadPng, title: "Download as PNG", children: "\u2913 PNG" })
60
+ ] }),
61
+ /* @__PURE__ */ jsx(EChart, { option, height: 320, onInst: (i) => instRef.current = i })
62
+ ] });
45
63
  }
46
64
  if (rows.length === 0) {
47
65
  return /* @__PURE__ */ jsx("div", { className: "cv-chart cv-chart--empty", children: "Waiting for data\u2026" });
@@ -78,6 +96,7 @@ function ChartRenderer({ artifact }) {
78
96
  }
79
97
  ),
80
98
  /* @__PURE__ */ jsx("span", { className: "cv-chart__spacer" }),
99
+ /* @__PURE__ */ jsx("button", { className: "cv-edit-btn", onClick: downloadPng, title: "Download as PNG", children: "\u2913 PNG" }),
81
100
  /* @__PURE__ */ jsx("button", { className: `cv-edit-btn ${editing ? "is-primary" : ""}`, onClick: () => setEditing((v) => !v), children: editing ? "Done" : "Edit data" })
82
101
  ] }),
83
102
  editing && /* @__PURE__ */ jsxs("div", { className: "cv-chart__editor cv-chrome", children: [
@@ -102,7 +121,7 @@ function ChartRenderer({ artifact }) {
102
121
  ] }),
103
122
  /* @__PURE__ */ jsx(DataGrid, { rows, xKey, series, onCell: setCell, onAddRow: addRow, onRemoveRow: removeRow })
104
123
  ] }),
105
- /* @__PURE__ */ jsx(EChart, { option, height: editing ? 260 : 340 })
124
+ /* @__PURE__ */ jsx(EChart, { option, height: editing ? 260 : 340, onInst: (i) => instRef.current = i })
106
125
  ] });
107
126
  }
108
127
  function DataGrid({
@@ -27,7 +27,7 @@ function snapAxis(pos, size, targets) {
27
27
  }
28
28
  return best ? { pos: pos + best.delta, guide: best.guide } : { pos, guide: null };
29
29
  }
30
- function FreeSlide({ elements, onChange }) {
30
+ function FreeSlide({ elements, onChange, padding }) {
31
31
  const slideRef = useRef(null);
32
32
  const [els, setEls] = useState(elements);
33
33
  const [selected, setSelected] = useState(null);
@@ -136,6 +136,7 @@ function FreeSlide({ elements, onChange }) {
136
136
  {
137
137
  className: "cv-free",
138
138
  ref: slideRef,
139
+ style: padding ? { inset: `${padding}%` } : void 0,
139
140
  onPointerMove: onMove,
140
141
  onPointerUp: onUp,
141
142
  onPointerLeave: onUp,
@@ -200,10 +201,10 @@ function FreeSlide({ elements, onChange }) {
200
201
  /* @__PURE__ */ jsx("button", { onClick: () => updateEl(el.id, { align: "center" }), title: "Align center", children: "\u2261" }),
201
202
  /* @__PURE__ */ jsx("button", { onClick: () => updateEl(el.id, { align: "right" }), title: "Align right", children: "\u27F9" })
202
203
  ] }),
203
- selected === el.id && el.type === "shape" && /* @__PURE__ */ jsx("div", { className: `cv-free__fmt ${el.y < 16 ? "cv-free__fmt--below" : ""}`, onPointerDown: (e) => e.stopPropagation(), onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsx("input", { type: "color", value: el.fill ?? "#5b5bd6", onChange: (e) => updateEl(el.id, { fill: e.target.value }), title: "Fill color" }) }),
204
204
  selected === el.id && /* @__PURE__ */ jsxs(Fragment, { children: [
205
205
  /* @__PURE__ */ jsx("span", { className: "cv-free__resize", onPointerDown: (e) => onDown(e, el, "resize") }),
206
206
  /* @__PURE__ */ jsxs("div", { className: `cv-free__ctl ${el.y < 16 ? "cv-free__ctl--below" : ""}`, onPointerDown: (e) => e.stopPropagation(), children: [
207
+ el.type === "shape" && /* @__PURE__ */ jsx("input", { className: "cv-free__ctl-fill", type: "color", value: el.fill ?? "#5b5bd6", onChange: (e) => updateEl(el.id, { fill: e.target.value }), onClick: (e) => e.stopPropagation(), title: "Fill color" }),
207
208
  /* @__PURE__ */ jsx("button", { onClick: (e) => {
208
209
  e.stopPropagation();
209
210
  duplicate(el);
@@ -369,7 +370,7 @@ function SlidesRenderer({ artifact }) {
369
370
  onDragEnd: () => setDragIndex(null),
370
371
  children: /* @__PURE__ */ jsxs("button", { className: "cv-deck__thumb", onClick: () => setIndex(i), children: [
371
372
  /* @__PURE__ */ jsx("span", { className: "cv-deck__thumb-n", children: i + 1 }),
372
- /* @__PURE__ */ jsx("div", { className: "cv-deck__thumb-slide", style: s.background ? { background: s.background } : void 0, children: resolveElements(s).map(
373
+ /* @__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(
373
374
  (el) => el.type === "text" ? /* @__PURE__ */ jsx(
374
375
  "span",
375
376
  {
@@ -378,7 +379,7 @@ function SlidesRenderer({ artifact }) {
378
379
  },
379
380
  el.id
380
381
  ) : el.type === "shape" ? /* @__PURE__ */ jsx("div", { style: { position: "absolute", left: `${el.x}%`, top: `${el.y}%`, width: `${el.w}%`, height: `${el.h}%`, color: s.textColor, ...shapeStyle(el) } }, el.id) : /* @__PURE__ */ jsx("img", { src: el.src, alt: "", style: { position: "absolute", left: `${el.x}%`, top: `${el.y}%`, width: `${el.w}%`, height: `${el.h}%`, objectFit: "contain" } }, el.id)
381
- ) })
382
+ ) }) })
382
383
  ] })
383
384
  },
384
385
  i
@@ -442,6 +443,10 @@ function SlidesRenderer({ artifact }) {
442
443
  ),
443
444
  /* @__PURE__ */ jsx("label", { className: "cv-deck__bg", title: "Background color", children: /* @__PURE__ */ jsx("input", { type: "color", value: /^#/.test(slide.background ?? "") ? slide.background : "#ffffff", onChange: (e) => update({ background: e.target.value }) }) }),
444
445
  /* @__PURE__ */ jsx("button", { onClick: () => bgRef.current?.click(), title: "Background image", children: "\u{1F5BC} BG" }),
446
+ /* @__PURE__ */ jsxs("label", { className: "cv-deck__pad", title: "Content padding (% of slide)", children: [
447
+ "Pad",
448
+ /* @__PURE__ */ jsx("input", { type: "number", min: 0, max: 20, value: slide.padding ?? 0, onChange: (e) => update({ padding: Number(e.target.value) || void 0 }) })
449
+ ] }),
445
450
  /* @__PURE__ */ jsx("span", { className: "cv-deck__spacer" }),
446
451
  /* @__PURE__ */ jsx("button", { className: "cv-deck__present", onClick: () => setPresenting(true), title: "Present (full screen)", children: "\u25B6 Present" }),
447
452
  /* @__PURE__ */ jsx("button", { onClick: () => moveSlide(-1), title: "Move up", disabled: at === 0, children: "\u25B2" }),
@@ -449,7 +454,7 @@ function SlidesRenderer({ artifact }) {
449
454
  /* @__PURE__ */ jsx("button", { onClick: duplicateSlide, title: "Duplicate slide", children: "\u29C9" }),
450
455
  /* @__PURE__ */ jsx("button", { onClick: deleteSlide, title: "Delete slide", disabled: slides.length === 1, children: "\u{1F5D1}" })
451
456
  ] }),
452
- /* @__PURE__ */ jsx("div", { className: "cv-slide cv-slide--blank", style: slideStyle, children: /* @__PURE__ */ jsx(FreeSlide, { elements: resolveElements(slide), onChange: (elements) => update({ elements }) }) }),
457
+ /* @__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 }) }),
453
458
  /* @__PURE__ */ jsxs("div", { className: "cv-deck__nav cv-chrome", children: [
454
459
  /* @__PURE__ */ jsx("button", { disabled: at === 0, onClick: () => setIndex(at - 1), "aria-label": "Previous slide", children: "\u2039" }),
455
460
  /* @__PURE__ */ jsxs("span", { children: [
@@ -470,7 +475,7 @@ function SlidesRenderer({ artifact }) {
470
475
  )
471
476
  ] }),
472
477
  presenting && /* @__PURE__ */ jsxs("div", { className: "cv-present", onClick: () => setIndex(Math.min(at + 1, slides.length - 1)), children: [
473
- /* @__PURE__ */ jsx("div", { className: "cv-present__slide cv-present__fade cv-slide cv-slide--blank", style: slideStyle, children: /* @__PURE__ */ jsx("div", { className: "cv-free", children: resolveElements(slide).map(
478
+ /* @__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(
474
479
  (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: el.src, alt: "" }) }, el.id)
475
480
  ) }) }, at),
476
481
  slide.notes ? /* @__PURE__ */ jsx("div", { className: "cv-present__notes", onClick: (e) => e.stopPropagation(), children: slide.notes }) : null,
@@ -2,7 +2,7 @@ import { loadOptional } from './chunk-YZZSJJMQ.js';
2
2
  import { useCanvasStore } from './chunk-KKLWKR5G.js';
3
3
  import { lazy, useMemo, useState, useRef, useEffect, useCallback, Suspense } from 'react';
4
4
  import '@fortune-sheet/react/dist/index.css';
5
- import { jsx, jsxs } from 'react/jsx-runtime';
5
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
6
6
 
7
7
  // src/io/formula.ts
8
8
  var EMPTY = /* @__PURE__ */ new Map();
@@ -203,10 +203,34 @@ function TableRenderer({ artifact }) {
203
203
  };
204
204
  }, [dataKey, hasFormulas]);
205
205
  const hasSheet = !!artifact.data.sheet?.length;
206
+ const [sortCol, setSortCol] = useState("");
207
+ const [sortDir, setSortDir] = useState(1);
208
+ const [filter, setFilter] = useState("");
209
+ const [appliedFilter, setAppliedFilter] = useState("");
210
+ useEffect(() => {
211
+ const t = setTimeout(() => setAppliedFilter(filter), 300);
212
+ return () => clearTimeout(t);
213
+ }, [filter]);
214
+ const viewRows = useMemo(() => {
215
+ let r = rows;
216
+ const q = appliedFilter.trim().toLowerCase();
217
+ if (q) r = r.filter((row) => columns.some((c) => String(row[c.key] ?? "").toLowerCase().includes(q)));
218
+ if (sortCol) {
219
+ r = [...r].sort((a, b) => {
220
+ const av = a[sortCol];
221
+ const bv = b[sortCol];
222
+ const cmp = typeof av === "number" && typeof bv === "number" ? av - bv : String(av ?? "").localeCompare(String(bv ?? ""));
223
+ return cmp * sortDir;
224
+ });
225
+ }
226
+ return r;
227
+ }, [rows, columns, appliedFilter, sortCol, sortDir]);
228
+ const viewActive = !!appliedFilter.trim() || !!sortCol;
229
+ const wbKey = `${dataKey}:${viewActive ? `view-s${sortCol}${sortDir}-f${appliedFilter}` : hasSheet ? "sheet" : "rows"}`;
206
230
  const initialData = useMemo(
207
- () => artifact.data.sheet?.length ? artifact.data.sheet : toWorkbook(columns, rows, formulas),
231
+ () => viewActive ? toWorkbook(columns, viewRows, formulas) : hasSheet ? artifact.data.sheet : toWorkbook(columns, rows, formulas),
208
232
  // eslint-disable-next-line react-hooks/exhaustive-deps
209
- [dataKey, formulasReady]
233
+ [wbKey, formulasReady]
210
234
  );
211
235
  const applyEvent = useCanvasStore((s) => s.applyUserEvent);
212
236
  const persistTimer = useRef(null);
@@ -241,9 +265,36 @@ function TableRenderer({ artifact }) {
241
265
  /* @__PURE__ */ jsxs("div", { className: "cv-sheet-tools", children: [
242
266
  /* @__PURE__ */ jsx("button", { type: "button", onClick: () => insert("column"), children: "\uFF0B Column" }),
243
267
  /* @__PURE__ */ jsx("button", { type: "button", onClick: () => insert("row"), children: "\uFF0B Row" }),
268
+ columns.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
269
+ /* @__PURE__ */ jsx("span", { className: "cv-sheet-tools__sep" }),
270
+ /* @__PURE__ */ jsxs(
271
+ "select",
272
+ {
273
+ className: "cv-sheet-tools__sort",
274
+ value: sortCol,
275
+ title: "Sort by column",
276
+ onChange: (e) => setSortCol(e.target.value),
277
+ children: [
278
+ /* @__PURE__ */ jsx("option", { value: "", children: "Sort\u2026" }),
279
+ columns.map((c) => /* @__PURE__ */ jsx("option", { value: c.key, children: c.label ?? c.key }, c.key))
280
+ ]
281
+ }
282
+ ),
283
+ sortCol && /* @__PURE__ */ jsx("button", { type: "button", title: sortDir === 1 ? "Ascending" : "Descending", onClick: () => setSortDir((d) => d === 1 ? -1 : 1), children: sortDir === 1 ? "\u25B2" : "\u25BC" }),
284
+ /* @__PURE__ */ jsx(
285
+ "input",
286
+ {
287
+ className: "cv-sheet-tools__filter",
288
+ value: filter,
289
+ placeholder: "Filter\u2026",
290
+ onChange: (e) => setFilter(e.target.value),
291
+ title: "Filter rows"
292
+ }
293
+ )
294
+ ] }),
244
295
  /* @__PURE__ */ jsx("span", { className: "cv-sheet-tools__hint", children: "Right-click a header for more, or drag to edit" })
245
296
  ] }),
246
- /* @__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 }, dataKey) }) })
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) }) })
247
298
  ] });
248
299
  }
249
300
 
package/dist/index.d.ts CHANGED
@@ -119,6 +119,9 @@ interface Slide {
119
119
  textColor?: string;
120
120
  /** Speaker notes (not shown on the slide; exported to the .pptx notes pane). */
121
121
  notes?: string;
122
+ /** Content padding as a percent of the slide width (a safe margin around the
123
+ * free canvas). Applied in the editor, present view, thumbnails, and export. */
124
+ padding?: number;
122
125
  }
123
126
  interface SlidesData {
124
127
  slides: Slide[];
package/dist/index.js CHANGED
@@ -2111,10 +2111,10 @@ function HtmlRenderer({ artifact: artifact2 }) {
2111
2111
  }
2112
2112
 
2113
2113
  // src/components/renderers/index.ts
2114
- var ChartRenderer = lazy(() => import('./ChartRenderer-CWV2OX3S.js').then((m) => ({ default: m.ChartRenderer })));
2114
+ var ChartRenderer = lazy(() => import('./ChartRenderer-FX6ZN4NC.js').then((m) => ({ default: m.ChartRenderer })));
2115
2115
  var DocumentRenderer = lazy(() => import('./DocumentRenderer-KJ6FTGKH.js').then((m) => ({ default: m.DocumentRenderer })));
2116
- var TableRenderer = lazy(() => import('./TableRenderer-ZF7MVA7I.js').then((m) => ({ default: m.TableRenderer })));
2117
- var SlidesRenderer = lazy(() => import('./SlidesRenderer-7ICJQHYT.js').then((m) => ({ default: m.SlidesRenderer })));
2116
+ var TableRenderer = lazy(() => import('./TableRenderer-Y4JFRSLU.js').then((m) => ({ default: m.TableRenderer })));
2117
+ var SlidesRenderer = lazy(() => import('./SlidesRenderer-SK5VQDIL.js').then((m) => ({ default: m.SlidesRenderer })));
2118
2118
  var builtinRenderers = {
2119
2119
  html: HtmlRenderer,
2120
2120
  document: DocumentRenderer,
@@ -2255,8 +2255,10 @@ async function slidesToPptx(data, _title) {
2255
2255
  const s = pptx.addSlide();
2256
2256
  if (slide.background && /^#[0-9a-f]{3,8}$/i.test(slide.background)) s.background = { color: slide.background.replace("#", "") };
2257
2257
  const tc = slide.textColor ? slide.textColor.replace("#", "") : void 0;
2258
+ const pad = (slide.padding ?? 0) / 100;
2259
+ const inset = (v) => pad + v / 100 * (1 - 2 * pad);
2258
2260
  for (const el of resolveElements(slide)) {
2259
- const box = { x: el.x / 100 * W, y: el.y / 100 * H, w: el.w / 100 * W, h: el.h / 100 * H };
2261
+ const box = { x: inset(el.x) * W, y: inset(el.y) * H, w: el.w / 100 * (1 - 2 * pad) * W, h: el.h / 100 * (1 - 2 * pad) * H };
2260
2262
  if (el.type === "text") {
2261
2263
  const color = el.color ? el.color.replace("#", "") : tc;
2262
2264
  s.addText(el.text ?? "", { ...box, fontSize: (el.fontSize ?? 24) * 0.75, bold: !!el.bold, align: el.align ?? "left", ...color ? { color } : {} });
@@ -2294,7 +2296,9 @@ function slidesToPrintHtml(data, title) {
2294
2296
  const src = safeSrc(el.src);
2295
2297
  return src ? `<img class="el" style="${box}" src="${escapeAttr(src)}"/>` : "";
2296
2298
  }).join("");
2297
- return `<section class="slide" style="background:${escapeAttr(bg)}">${els}</section>`;
2299
+ const pad = slide.padding ?? 0;
2300
+ const inner = pad ? `<div style="position:absolute;inset:${pad}%">${els}</div>` : els;
2301
+ return `<section class="slide" style="background:${escapeAttr(bg)}">${inner}</section>`;
2298
2302
  }).join("");
2299
2303
  return `<!doctype html><html><head><meta charset="utf-8"><title>${escapeXml(title)}</title><style>
2300
2304
  @page { size: 1280px 720px; margin: 0; }
package/dist/styles.css CHANGED
@@ -396,6 +396,13 @@
396
396
  border-radius: 6px; background: var(--cv-bg); color: var(--cv-text); cursor: pointer;
397
397
  }
398
398
  .cv-sheet-tools button:hover { border-color: var(--cv-accent, #6366f1); color: var(--cv-accent, #6366f1); }
399
+ .cv-sheet-tools__sep { width: 1px; height: 18px; background: var(--cv-border); margin: 0 2px; }
400
+ .cv-sheet-tools__sort, .cv-sheet-tools__filter {
401
+ font: inherit; font-size: 12.5px; padding: 4px 8px; border-radius: 6px;
402
+ border: 1px solid var(--cv-border); background: var(--cv-bg); color: var(--cv-text);
403
+ }
404
+ .cv-sheet-tools__filter { min-width: 96px; max-width: 160px; }
405
+ .cv-sheet-tools__sort:focus, .cv-sheet-tools__filter:focus { outline: none; border-color: var(--cv-accent); }
399
406
  .cv-sheet-tools__hint { margin-left: auto; font-size: 11px; color: var(--cv-muted); }
400
407
  .cv-sheet { position: relative; width: 100%; flex: 1; height: auto; min-height: 0; }
401
408
  .cv-sheet .fortune-container { height: 100% !important; width: 100% !important; }
@@ -611,6 +618,13 @@
611
618
  width: 30px; height: 30px; padding: 0;
612
619
  border: 1px solid var(--cv-border); border-radius: 7px; background: none; cursor: pointer;
613
620
  }
621
+ .cv-deck__pad {
622
+ display: inline-flex; align-items: center; gap: 5px; font-size: 12px; color: var(--cv-muted);
623
+ }
624
+ .cv-deck__pad input {
625
+ width: 48px; font: inherit; font-size: 12.5px; padding: 4px 6px;
626
+ border: 1px solid var(--cv-border); border-radius: 6px; background: var(--cv-bg); color: var(--cv-text);
627
+ }
614
628
  .cv-deck__present {
615
629
  width: auto !important; padding: 0 12px !important;
616
630
  background: var(--cv-accent) !important; color: #fff !important;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@braincrew-lab/langchain-canvas",
3
- "version": "0.1.12",
3
+ "version": "0.1.13",
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
  "type": "module",