@wireweave/core 2.7.1 → 3.0.0-beta.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.
package/dist/renderer.cjs CHANGED
@@ -28,11 +28,15 @@ __export(renderer_exports, {
28
28
  generateStyles: () => generateStyles,
29
29
  getIconData: () => getIconData,
30
30
  getTheme: () => getTheme,
31
+ layoutCanvas: () => layoutCanvas,
31
32
  lucideIcons: () => lucideIcons,
32
33
  render: () => render,
34
+ renderCanvas: () => renderCanvas,
33
35
  renderIconSvg: () => renderIconSvg,
36
+ renderPage: () => renderPage,
34
37
  renderToHtml: () => renderToHtml,
35
- renderToSvg: () => renderToSvg
38
+ renderToSvg: () => renderToSvg,
39
+ resolvePageDimensions: () => resolvePageDimensions
36
40
  });
37
41
  module.exports = __toCommonJS(renderer_exports);
38
42
 
@@ -1884,6 +1888,9 @@ function generateLayoutClasses(prefix) {
1884
1888
  padding: 16px;
1885
1889
  display: flex;
1886
1890
  flex-direction: column;
1891
+ /* Default vertical spacing between stacked content blocks.
1892
+ Inline gap from DSL (main gap=N) overrides this. */
1893
+ gap: 16px;
1887
1894
  min-height: 0;
1888
1895
  }
1889
1896
 
@@ -1925,7 +1932,7 @@ function generateLayoutClasses(prefix) {
1925
1932
  }
1926
1933
 
1927
1934
  .${prefix}-sidebar {
1928
- width: 256px;
1935
+ width: 224px;
1929
1936
  border-right: 1px solid var(--${prefix}-border);
1930
1937
  padding: 16px 16px 16px 20px;
1931
1938
  flex-shrink: 0;
@@ -49376,7 +49383,6 @@ function renderDropdown(node, ctx) {
49376
49383
  "data-action": dropdownItem.action
49377
49384
  };
49378
49385
  const interactiveAttrStr = ctx.buildAttrsString(interactiveAttrs);
49379
- const hrefAttr = dropdownItem.href ? ` href="${ctx.escapeHtml(dropdownItem.href)}"` : "";
49380
49386
  const disabledAttr = dropdownItem.disabled ? ' disabled="disabled"' : "";
49381
49387
  if (dropdownItem.href || dropdownItem.navigate) {
49382
49388
  const href = dropdownItem.href || dropdownItem.navigate || "#";
@@ -49480,13 +49486,14 @@ function renderBreadcrumb(node, ctx) {
49480
49486
  ]);
49481
49487
  const styles = ctx.buildCommonStyles(node);
49482
49488
  const styleAttr = styles ? ` style="${styles}"` : "";
49489
+ const separator = `<span class="${ctx.prefix}-breadcrumb-separator" aria-hidden="true">|</span>`;
49483
49490
  const items = node.items.map((item, idx) => {
49484
49491
  const isLast = idx === node.items.length - 1;
49485
49492
  if (typeof item === "string") {
49486
49493
  return isLast ? `<span class="${ctx.prefix}-breadcrumb-item" aria-current="page">${ctx.escapeHtml(item)}</span>` : `<a class="${ctx.prefix}-breadcrumb-item" href="#">${ctx.escapeHtml(item)}</a>`;
49487
49494
  }
49488
49495
  return isLast ? `<span class="${ctx.prefix}-breadcrumb-item" aria-current="page">${ctx.escapeHtml(item.label)}</span>` : `<a class="${ctx.prefix}-breadcrumb-item" href="${item.href || "#"}">${ctx.escapeHtml(item.label)}</a>`;
49489
- }).join(" / ");
49496
+ }).join(separator);
49490
49497
  return `<nav class="${classes}"${styleAttr} aria-label="Breadcrumb">${items}</nav>`;
49491
49498
  }
49492
49499
 
@@ -49893,7 +49900,10 @@ var HtmlRenderer = class extends BaseRenderer {
49893
49900
  const uiContent = this.renderChildren(uiChildren);
49894
49901
  const title = node.title ? `<title>${this.escapeHtml(node.title)}</title>
49895
49902
  ` : "";
49896
- const commonStyles = this.buildCommonStyles(node);
49903
+ const { x: _canvasX, y: _canvasY, ...nodeWithoutCanvasPos } = node;
49904
+ void _canvasX;
49905
+ void _canvasY;
49906
+ const commonStyles = this.buildCommonStyles(nodeWithoutCanvasPos);
49897
49907
  let viewportStyle = `position: relative; width: ${viewport.width}px; height: ${viewport.height}px; overflow: hidden`;
49898
49908
  if (this.context.options.background) {
49899
49909
  viewportStyle += `; background: ${this.context.options.background}`;
@@ -50312,8 +50322,99 @@ function createHtmlRenderer(options) {
50312
50322
  return new HtmlRenderer(options);
50313
50323
  }
50314
50324
 
50325
+ // src/renderer/page-renderer.ts
50326
+ function renderPage(page, options = {}) {
50327
+ const renderer = new HtmlRenderer(options);
50328
+ const { html, css } = renderer.render({
50329
+ type: "Document",
50330
+ children: [page]
50331
+ });
50332
+ const { width, height } = resolvePageDimensions(page);
50333
+ return { html, css, width, height };
50334
+ }
50335
+ function resolvePageDimensions(page) {
50336
+ const pageAny = page;
50337
+ const explicitW = page.w ?? pageAny.width;
50338
+ const explicitH = page.h ?? pageAny.height;
50339
+ if (typeof explicitW === "number" && typeof explicitH === "number") {
50340
+ return { width: explicitW, height: explicitH };
50341
+ }
50342
+ const viewport = resolveViewport(page.viewport, page.device);
50343
+ return { width: viewport.width, height: viewport.height };
50344
+ }
50345
+
50346
+ // src/renderer/canvas-renderer.ts
50347
+ var DEFAULT_GAP = 64;
50348
+ var DEFAULT_PREFIX = "wf";
50349
+ function layoutCanvas(pages, gap = DEFAULT_GAP) {
50350
+ const placed = [];
50351
+ let cursorX = 0;
50352
+ let maxRight = 0;
50353
+ let maxBottom = 0;
50354
+ for (const page of pages) {
50355
+ const { width, height } = resolvePageDimensions(page);
50356
+ const explicitX = typeof page.x === "number" ? page.x : void 0;
50357
+ const explicitY = typeof page.y === "number" ? page.y : void 0;
50358
+ const x = explicitX ?? cursorX;
50359
+ const y = explicitY ?? 0;
50360
+ placed.push({ page, x, y, w: width, h: height });
50361
+ if (explicitX === void 0) {
50362
+ cursorX = x + width + gap;
50363
+ }
50364
+ maxRight = Math.max(maxRight, x + width);
50365
+ maxBottom = Math.max(maxBottom, y + height);
50366
+ }
50367
+ return { placed, width: maxRight, height: maxBottom };
50368
+ }
50369
+ function renderCanvas(doc, options = {}) {
50370
+ const gap = options.gap ?? DEFAULT_GAP;
50371
+ const prefix = options.classPrefix ?? DEFAULT_PREFIX;
50372
+ const includeStyles = options.includeStyles !== false;
50373
+ const theme = options.theme === "dark" ? darkTheme : defaultTheme;
50374
+ const css = includeStyles ? generateStyles(theme, prefix) : "";
50375
+ if (doc.children.length === 0) {
50376
+ return {
50377
+ html: `<div class="${prefix}-canvas" data-empty="true"></div>`,
50378
+ css,
50379
+ width: 0,
50380
+ height: 0
50381
+ };
50382
+ }
50383
+ const { placed, width, height } = layoutCanvas(doc.children, gap);
50384
+ const pageRenderer = new HtmlRenderer({ ...options, includeStyles: false });
50385
+ const boards = placed.map((p) => {
50386
+ const { html: pageHtml } = pageRenderer.render({
50387
+ type: "Document",
50388
+ children: [p.page]
50389
+ });
50390
+ return wrapBoard(pageHtml, p, prefix);
50391
+ }).join("\n");
50392
+ const canvasStyle = `position: relative; width: ${width}px; height: ${height}px;`;
50393
+ const html = `<div class="${prefix}-canvas" style="${canvasStyle}" data-page-count="${placed.length}">
50394
+ ${boards}
50395
+ </div>`;
50396
+ return { html, css, width, height };
50397
+ }
50398
+ function wrapBoard(pageHtml, placed, prefix) {
50399
+ const { x, y, w, h, page } = placed;
50400
+ const positionStyle = `position: absolute; left: ${x}px; top: ${y}px; width: ${w}px; height: ${h}px;`;
50401
+ const titleAttr = page.title ? ` data-page-title="${escapeAttr(page.title)}"` : "";
50402
+ const dataAttrs = ` data-page-x="${x}" data-page-y="${y}" data-page-w="${w}" data-page-h="${h}"${titleAttr}`;
50403
+ return `<div class="${prefix}-canvas-board" style="${positionStyle}"${dataAttrs}>
50404
+ ${pageHtml}
50405
+ </div>`;
50406
+ }
50407
+ function escapeAttr(s) {
50408
+ return s.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
50409
+ }
50410
+
50315
50411
  // src/renderer/index.ts
50316
50412
  function render(document, options = {}) {
50413
+ const isMultiPage = document.children.length > 1;
50414
+ if (isMultiPage) {
50415
+ const result = renderCanvas(document, options);
50416
+ return { html: result.html, css: result.css };
50417
+ }
50317
50418
  const renderer = createHtmlRenderer(options);
50318
50419
  return renderer.render(document);
50319
50420
  }
@@ -50349,24 +50450,33 @@ ${html}
50349
50450
  </html>`;
50350
50451
  }
50351
50452
  function renderToSvg(document, options = {}) {
50352
- const firstPage = document.children[0];
50453
+ const isMultiPage = document.children.length > 1;
50353
50454
  let width = options.width ?? 800;
50354
50455
  let height = options.height ?? 600;
50355
- if (firstPage && options.width === void 0 && options.height === void 0) {
50356
- const pageAny = firstPage;
50357
- const explicitW = firstPage.w ?? pageAny.width;
50358
- const explicitH = firstPage.h ?? pageAny.height;
50359
- if (typeof explicitW === "number" && typeof explicitH === "number") {
50360
- width = explicitW;
50361
- height = explicitH;
50362
- } else if (firstPage.viewport !== void 0 || firstPage.device !== void 0) {
50363
- const viewport = resolveViewport(firstPage.viewport, firstPage.device);
50364
- width = viewport.width;
50365
- height = viewport.height;
50456
+ let html;
50457
+ let css;
50458
+ if (isMultiPage) {
50459
+ const canvas = renderCanvas(document, {
50460
+ theme: options.theme ?? "light"
50461
+ });
50462
+ if (options.width === void 0 && options.height === void 0) {
50463
+ width = canvas.width;
50464
+ height = canvas.height;
50465
+ }
50466
+ html = canvas.html;
50467
+ css = canvas.css;
50468
+ } else {
50469
+ const firstPage = document.children[0];
50470
+ if (firstPage && options.width === void 0 && options.height === void 0) {
50471
+ const dims = resolvePageDimensions(firstPage);
50472
+ width = dims.width;
50473
+ height = dims.height;
50366
50474
  }
50475
+ const result = render(document, { theme: options.theme ?? "light" });
50476
+ html = result.html;
50477
+ css = result.css;
50367
50478
  }
50368
50479
  const background = options.background ?? "#ffffff";
50369
- const { html, css } = render(document, { theme: options.theme ?? "light" });
50370
50480
  const svg = `<?xml version="1.0" encoding="UTF-8"?>
50371
50481
  <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
50372
50482
  viewBox="0 0 ${width} ${height}" width="${width}" height="${height}">
@@ -50400,11 +50510,15 @@ ${css}
50400
50510
  generateStyles,
50401
50511
  getIconData,
50402
50512
  getTheme,
50513
+ layoutCanvas,
50403
50514
  lucideIcons,
50404
50515
  render,
50516
+ renderCanvas,
50405
50517
  renderIconSvg,
50518
+ renderPage,
50406
50519
  renderToHtml,
50407
- renderToSvg
50520
+ renderToSvg,
50521
+ resolvePageDimensions
50408
50522
  });
50409
50523
  /**
50410
50524
  * Lucide Icons Data
@@ -1,4 +1,4 @@
1
- import { a1 as WireframeDocument, A as AnyNode, P as PageNode } from './types-CQDDIeyC.cjs';
1
+ import { a1 as WireframeDocument, A as AnyNode, P as PageNode } from './types-hEr_afgw.cjs';
2
2
 
3
3
  /**
4
4
  * Renderer type definitions for wireweave
@@ -25,6 +25,25 @@ interface RenderResult {
25
25
  /** Generated CSS styles */
26
26
  css: string;
27
27
  }
28
+ /**
29
+ * Result of rendering a single page in isolation (`renderPage`).
30
+ * Carries the resolved pixel dimensions so callers (export pipelines, hosts)
31
+ * can size their wrapper without re-resolving viewport/device themselves.
32
+ */
33
+ interface PageRenderResult extends RenderResult {
34
+ width: number;
35
+ height: number;
36
+ }
37
+ interface CanvasOptions extends RenderOptions {
38
+ /** Gap between auto-laid-out pages, in pixels. Default `64`. */
39
+ gap?: number;
40
+ }
41
+ interface CanvasRenderResult extends RenderResult {
42
+ /** Total canvas width — bounding box of all pages. */
43
+ width: number;
44
+ /** Total canvas height — bounding box of all pages. */
45
+ height: number;
46
+ }
28
47
  interface SvgRenderOptions {
29
48
  /** Width of the SVG viewport */
30
49
  width?: number;
@@ -351,6 +370,90 @@ declare function generateStyles(theme: ThemeConfig, prefix?: string): string;
351
370
  */
352
371
  declare function generateComponentStyles(_theme: ThemeConfig, prefix?: string): string;
353
372
 
373
+ /**
374
+ * Page renderer — single-Page primitive.
375
+ *
376
+ * `renderPage` is the *pure* unit of this renderer: its output depends only
377
+ * on the page itself, never on sibling pages or canvas-level options. This
378
+ * is what export pipelines (1 page = 1 file) consume, and it is also the
379
+ * building block `renderCanvas` composes when laying multiple pages out.
380
+ */
381
+
382
+ /**
383
+ * Render a single page to self-contained HTML + CSS.
384
+ *
385
+ * The page's HTML output is identical to what `render(document)` produces
386
+ * for a document containing only this page — we route through `HtmlRenderer`
387
+ * so the existing renderer pipeline (themes, prefix, indentation, minify)
388
+ * applies uniformly.
389
+ */
390
+ declare function renderPage(page: PageNode, options?: RenderOptions): PageRenderResult;
391
+ /**
392
+ * Resolve a page's pixel dimensions from explicit `w`/`h` or viewport/device.
393
+ *
394
+ * Mirrors the precedence used inside `HtmlRenderer.renderPage`:
395
+ * 1. Explicit numeric `w` and `h` both set → use them.
396
+ * 2. Otherwise resolve via viewport string / device preset.
397
+ *
398
+ * Non-numeric `w`/`h` (e.g. keyword `'full'` or `ValueWithUnit`) fall through
399
+ * to the viewport — those are layout-internal hints, not canvas dimensions.
400
+ */
401
+ declare function resolvePageDimensions(page: PageNode): {
402
+ width: number;
403
+ height: number;
404
+ };
405
+
406
+ /**
407
+ * Canvas renderer — multi-page composition.
408
+ *
409
+ * `renderCanvas` lays out one or more pages on a single bounded canvas. The
410
+ * DSL stays page-centric — coordinates come from each page's optional
411
+ * `at(x, y)` attribute (resolved into `Page.x` / `Page.y` by the parser).
412
+ * Pages without coordinates auto-flow horizontally with `gap` between them.
413
+ *
414
+ * Output is intentionally minimal: a bounded `<div class="wf-canvas">` of the
415
+ * exact layout extent, containing absolutely-positioned `<div class="wf-canvas-board">`
416
+ * wrappers around each page's HTML. No chrome, no decoration, no grid, no
417
+ * labels. Hosts (dashboard editor, markdown plugin, VSCode preview) wrap this
418
+ * output in their own viewport / grid / pan-zoom layers.
419
+ *
420
+ * `layoutCanvas` is exposed as a pure utility so hosts that want to compose
421
+ * the canvas DOM themselves can reuse the placement math.
422
+ */
423
+
424
+ interface PlacedPage {
425
+ page: PageNode;
426
+ x: number;
427
+ y: number;
428
+ w: number;
429
+ h: number;
430
+ }
431
+ /**
432
+ * Compute each page's position on the canvas and the bounding canvas size.
433
+ *
434
+ * Per-page mode:
435
+ * - `Page.x` / `Page.y` set → placed at exactly those coordinates.
436
+ * - Either missing → auto-flow: continues from the running cursor in a
437
+ * single horizontal row, advancing by `width + gap` after each
438
+ * auto-placed page. The cursor is unaffected by explicitly-placed pages.
439
+ *
440
+ * Mixed documents are allowed (ux-rules will warn) but explicit pages may
441
+ * overlap auto pages — that is the author's responsibility.
442
+ */
443
+ declare function layoutCanvas(pages: PageNode[], gap?: number): {
444
+ placed: PlacedPage[];
445
+ width: number;
446
+ height: number;
447
+ };
448
+ /**
449
+ * Render a multi-page document to a single bounded canvas of HTML + CSS.
450
+ *
451
+ * Output: `<div class="wf-canvas" style="width:Wpx; height:Hpx; position:relative">`
452
+ * containing one `<div class="wf-canvas-board">` per page, absolutely
453
+ * positioned at the layout coordinates.
454
+ */
455
+ declare function renderCanvas(doc: WireframeDocument, options?: CanvasOptions): CanvasRenderResult;
456
+
354
457
  /**
355
458
  * Lucide Icons Data
356
459
  *
@@ -388,13 +491,19 @@ strokeWidth?: number, className?: string, styleAttr?: string): string;
388
491
  */
389
492
 
390
493
  /**
391
- * Render AST to HTML and CSS
494
+ * Render AST to HTML and CSS.
392
495
  *
393
- * @param document - Parsed wireframe document
394
- * @param options - Render options
395
- * @returns Object containing HTML and CSS strings
496
+ * Two modes, decided automatically by page count:
497
+ * 1. Multiple pages canvas mode (bounded layout, absolutely-positioned
498
+ * boards). Hosts wrap this in their own viewport / grid / pan-zoom layer.
499
+ * 2. Single page → legacy mode (page only, no canvas wrapper) for
500
+ * backward compatibility with single-page consumers (markdown-plugin,
501
+ * dashboard previews, vscode-extension).
502
+ *
503
+ * Use `renderPage(page)` for explicit single-page export, or
504
+ * `renderCanvas(doc)` for explicit multi-page composition.
396
505
  */
397
- declare function render(document: WireframeDocument, options?: RenderOptions): RenderResult;
506
+ declare function render(document: WireframeDocument, options?: RenderOptions | CanvasOptions): RenderResult;
398
507
  /**
399
508
  * Render AST to standalone HTML with embedded CSS
400
509
  *
@@ -409,10 +518,14 @@ declare function renderToHtml(document: WireframeDocument, options?: RenderOptio
409
518
  * This approach embeds HTML+CSS inside SVG using foreignObject,
410
519
  * which allows CSS flexbox/grid layouts to work properly.
411
520
  *
521
+ * Multi-page documents are auto-rendered as a bounded canvas, with the
522
+ * SVG viewBox sized to the canvas bounding box. Single-page documents
523
+ * keep their legacy single-page sizing.
524
+ *
412
525
  * @param document - Parsed wireframe document
413
526
  * @param options - SVG render options
414
527
  * @returns Object containing SVG string and dimensions
415
528
  */
416
529
  declare function renderToSvg(document: WireframeDocument, options?: SvgRenderOptions): SvgRenderResult;
417
530
 
418
- export { HtmlRenderer, type IconData, type IconElement, type RenderContext, type RenderOptions, type RenderResult, type SvgRenderOptions, type SvgRenderResult, type ThemeColors, type ThemeConfig, createHtmlRenderer, darkTheme, defaultTheme, generateComponentStyles, generateStyles, getIconData, getTheme, lucideIcons, render, renderIconSvg, renderToHtml, renderToSvg };
531
+ export { type CanvasOptions, type CanvasRenderResult, HtmlRenderer, type IconData, type IconElement, type PageRenderResult, type PlacedPage, type RenderContext, type RenderOptions, type RenderResult, type SvgRenderOptions, type SvgRenderResult, type ThemeColors, type ThemeConfig, createHtmlRenderer, darkTheme, defaultTheme, generateComponentStyles, generateStyles, getIconData, getTheme, layoutCanvas, lucideIcons, render, renderCanvas, renderIconSvg, renderPage, renderToHtml, renderToSvg, resolvePageDimensions };
@@ -1,4 +1,4 @@
1
- import { a1 as WireframeDocument, A as AnyNode, P as PageNode } from './types-CQDDIeyC.js';
1
+ import { a1 as WireframeDocument, A as AnyNode, P as PageNode } from './types-hEr_afgw.js';
2
2
 
3
3
  /**
4
4
  * Renderer type definitions for wireweave
@@ -25,6 +25,25 @@ interface RenderResult {
25
25
  /** Generated CSS styles */
26
26
  css: string;
27
27
  }
28
+ /**
29
+ * Result of rendering a single page in isolation (`renderPage`).
30
+ * Carries the resolved pixel dimensions so callers (export pipelines, hosts)
31
+ * can size their wrapper without re-resolving viewport/device themselves.
32
+ */
33
+ interface PageRenderResult extends RenderResult {
34
+ width: number;
35
+ height: number;
36
+ }
37
+ interface CanvasOptions extends RenderOptions {
38
+ /** Gap between auto-laid-out pages, in pixels. Default `64`. */
39
+ gap?: number;
40
+ }
41
+ interface CanvasRenderResult extends RenderResult {
42
+ /** Total canvas width — bounding box of all pages. */
43
+ width: number;
44
+ /** Total canvas height — bounding box of all pages. */
45
+ height: number;
46
+ }
28
47
  interface SvgRenderOptions {
29
48
  /** Width of the SVG viewport */
30
49
  width?: number;
@@ -351,6 +370,90 @@ declare function generateStyles(theme: ThemeConfig, prefix?: string): string;
351
370
  */
352
371
  declare function generateComponentStyles(_theme: ThemeConfig, prefix?: string): string;
353
372
 
373
+ /**
374
+ * Page renderer — single-Page primitive.
375
+ *
376
+ * `renderPage` is the *pure* unit of this renderer: its output depends only
377
+ * on the page itself, never on sibling pages or canvas-level options. This
378
+ * is what export pipelines (1 page = 1 file) consume, and it is also the
379
+ * building block `renderCanvas` composes when laying multiple pages out.
380
+ */
381
+
382
+ /**
383
+ * Render a single page to self-contained HTML + CSS.
384
+ *
385
+ * The page's HTML output is identical to what `render(document)` produces
386
+ * for a document containing only this page — we route through `HtmlRenderer`
387
+ * so the existing renderer pipeline (themes, prefix, indentation, minify)
388
+ * applies uniformly.
389
+ */
390
+ declare function renderPage(page: PageNode, options?: RenderOptions): PageRenderResult;
391
+ /**
392
+ * Resolve a page's pixel dimensions from explicit `w`/`h` or viewport/device.
393
+ *
394
+ * Mirrors the precedence used inside `HtmlRenderer.renderPage`:
395
+ * 1. Explicit numeric `w` and `h` both set → use them.
396
+ * 2. Otherwise resolve via viewport string / device preset.
397
+ *
398
+ * Non-numeric `w`/`h` (e.g. keyword `'full'` or `ValueWithUnit`) fall through
399
+ * to the viewport — those are layout-internal hints, not canvas dimensions.
400
+ */
401
+ declare function resolvePageDimensions(page: PageNode): {
402
+ width: number;
403
+ height: number;
404
+ };
405
+
406
+ /**
407
+ * Canvas renderer — multi-page composition.
408
+ *
409
+ * `renderCanvas` lays out one or more pages on a single bounded canvas. The
410
+ * DSL stays page-centric — coordinates come from each page's optional
411
+ * `at(x, y)` attribute (resolved into `Page.x` / `Page.y` by the parser).
412
+ * Pages without coordinates auto-flow horizontally with `gap` between them.
413
+ *
414
+ * Output is intentionally minimal: a bounded `<div class="wf-canvas">` of the
415
+ * exact layout extent, containing absolutely-positioned `<div class="wf-canvas-board">`
416
+ * wrappers around each page's HTML. No chrome, no decoration, no grid, no
417
+ * labels. Hosts (dashboard editor, markdown plugin, VSCode preview) wrap this
418
+ * output in their own viewport / grid / pan-zoom layers.
419
+ *
420
+ * `layoutCanvas` is exposed as a pure utility so hosts that want to compose
421
+ * the canvas DOM themselves can reuse the placement math.
422
+ */
423
+
424
+ interface PlacedPage {
425
+ page: PageNode;
426
+ x: number;
427
+ y: number;
428
+ w: number;
429
+ h: number;
430
+ }
431
+ /**
432
+ * Compute each page's position on the canvas and the bounding canvas size.
433
+ *
434
+ * Per-page mode:
435
+ * - `Page.x` / `Page.y` set → placed at exactly those coordinates.
436
+ * - Either missing → auto-flow: continues from the running cursor in a
437
+ * single horizontal row, advancing by `width + gap` after each
438
+ * auto-placed page. The cursor is unaffected by explicitly-placed pages.
439
+ *
440
+ * Mixed documents are allowed (ux-rules will warn) but explicit pages may
441
+ * overlap auto pages — that is the author's responsibility.
442
+ */
443
+ declare function layoutCanvas(pages: PageNode[], gap?: number): {
444
+ placed: PlacedPage[];
445
+ width: number;
446
+ height: number;
447
+ };
448
+ /**
449
+ * Render a multi-page document to a single bounded canvas of HTML + CSS.
450
+ *
451
+ * Output: `<div class="wf-canvas" style="width:Wpx; height:Hpx; position:relative">`
452
+ * containing one `<div class="wf-canvas-board">` per page, absolutely
453
+ * positioned at the layout coordinates.
454
+ */
455
+ declare function renderCanvas(doc: WireframeDocument, options?: CanvasOptions): CanvasRenderResult;
456
+
354
457
  /**
355
458
  * Lucide Icons Data
356
459
  *
@@ -388,13 +491,19 @@ strokeWidth?: number, className?: string, styleAttr?: string): string;
388
491
  */
389
492
 
390
493
  /**
391
- * Render AST to HTML and CSS
494
+ * Render AST to HTML and CSS.
392
495
  *
393
- * @param document - Parsed wireframe document
394
- * @param options - Render options
395
- * @returns Object containing HTML and CSS strings
496
+ * Two modes, decided automatically by page count:
497
+ * 1. Multiple pages canvas mode (bounded layout, absolutely-positioned
498
+ * boards). Hosts wrap this in their own viewport / grid / pan-zoom layer.
499
+ * 2. Single page → legacy mode (page only, no canvas wrapper) for
500
+ * backward compatibility with single-page consumers (markdown-plugin,
501
+ * dashboard previews, vscode-extension).
502
+ *
503
+ * Use `renderPage(page)` for explicit single-page export, or
504
+ * `renderCanvas(doc)` for explicit multi-page composition.
396
505
  */
397
- declare function render(document: WireframeDocument, options?: RenderOptions): RenderResult;
506
+ declare function render(document: WireframeDocument, options?: RenderOptions | CanvasOptions): RenderResult;
398
507
  /**
399
508
  * Render AST to standalone HTML with embedded CSS
400
509
  *
@@ -409,10 +518,14 @@ declare function renderToHtml(document: WireframeDocument, options?: RenderOptio
409
518
  * This approach embeds HTML+CSS inside SVG using foreignObject,
410
519
  * which allows CSS flexbox/grid layouts to work properly.
411
520
  *
521
+ * Multi-page documents are auto-rendered as a bounded canvas, with the
522
+ * SVG viewBox sized to the canvas bounding box. Single-page documents
523
+ * keep their legacy single-page sizing.
524
+ *
412
525
  * @param document - Parsed wireframe document
413
526
  * @param options - SVG render options
414
527
  * @returns Object containing SVG string and dimensions
415
528
  */
416
529
  declare function renderToSvg(document: WireframeDocument, options?: SvgRenderOptions): SvgRenderResult;
417
530
 
418
- export { HtmlRenderer, type IconData, type IconElement, type RenderContext, type RenderOptions, type RenderResult, type SvgRenderOptions, type SvgRenderResult, type ThemeColors, type ThemeConfig, createHtmlRenderer, darkTheme, defaultTheme, generateComponentStyles, generateStyles, getIconData, getTheme, lucideIcons, render, renderIconSvg, renderToHtml, renderToSvg };
531
+ export { type CanvasOptions, type CanvasRenderResult, HtmlRenderer, type IconData, type IconElement, type PageRenderResult, type PlacedPage, type RenderContext, type RenderOptions, type RenderResult, type SvgRenderOptions, type SvgRenderResult, type ThemeColors, type ThemeConfig, createHtmlRenderer, darkTheme, defaultTheme, generateComponentStyles, generateStyles, getIconData, getTheme, layoutCanvas, lucideIcons, render, renderCanvas, renderIconSvg, renderPage, renderToHtml, renderToSvg, resolvePageDimensions };