# HTML Template Editor URL: /docs/web/components/html-template-editor WYSIWYG document editor for authoring Handlebars-aware HTML templates (quotes, invoices, reports). Word-style A4 page surface, four tabs (Visual / Code / Data / Preview), variable & helper chips, slash-style triggers, and a data-driven Table dialog where users discover JSON paths, configure columns, and write free-form sum / aggregate expressions per row. **Demo:** ```tsx 'use client'; import { useState } from 'react'; import { DEFAULT_HELPERS, HtmlTemplateEditor, numberToWordsTR, type HandlebarsVariable } from '@docyrus/ui/components/html-template-editor'; /* * Variables exposed to the side picker + the editor's `{{`-trigger * autocomplete. Listing them out gives the user a discoverable surface; * arbitrary Handlebars expressions still work without an entry here. */ const VARIABLES: HandlebarsVariable[] = [ { name: 'company.name', label: 'Company Name', category: 'Company' }, { name: 'company.addressLine1', label: 'Address Line 1', category: 'Company' }, { name: 'company.addressLine2', label: 'Address Line 2', category: 'Company' }, { name: 'company.email', label: 'Email', category: 'Company' }, { name: 'company.phone', label: 'Phone', category: 'Company' }, { name: 'company.taxId', label: 'Tax ID', category: 'Company' }, { name: 'customer.name', label: 'Customer Name', category: 'Bill To' }, { name: 'customer.company', label: 'Customer Company', category: 'Bill To' }, { name: 'customer.addressLine1', label: 'Address Line 1', category: 'Bill To' }, { name: 'customer.addressLine2', label: 'Address Line 2', category: 'Bill To' }, { name: 'customer.email', label: 'Email', category: 'Bill To' }, { name: 'invoice.number', label: 'Invoice Number', category: 'Invoice' }, { name: 'invoice.issueDate', label: 'Issue Date (ISO)', category: 'Invoice' }, { name: 'invoice.dueDate', label: 'Due Date (ISO)', category: 'Invoice' }, { name: 'invoice.currency', label: 'Currency', category: 'Invoice' }, { name: 'invoice.poNumber', label: 'PO Number', category: 'Invoice' }, { name: 'invoice.paymentTerms', label: 'Payment Terms', category: 'Invoice' }, { name: 'invoice.notes', label: 'Notes', category: 'Invoice' }, { name: 'payment.bankName', label: 'Bank Name', category: 'Payment' }, { name: 'payment.iban', label: 'IBAN', category: 'Payment' }, { name: 'payment.swift', label: 'SWIFT / BIC', category: 'Payment' }, { name: 'items', label: 'Line items (#each)', category: 'Items' }, { name: 'name', label: 'Item name (inside #each)', category: 'Items' }, { name: 'description', label: 'Description (inside #each)', category: 'Items' }, { name: 'qty', label: 'Qty (inside #each)', category: 'Items' }, { name: 'unitPrice', label: 'Unit Price (inside #each)', category: 'Items' }, { name: 'discountPct', label: 'Discount % (inside #each)', category: 'Items' }, { name: 'taxPct', label: 'Tax % (inside #each)', category: 'Items' }, { name: 'formatCurrency value invoice.currency', label: 'formatCurrency', category: 'Helpers' }, { name: 'formatDate invoice.issueDate "DD MMM YYYY"', label: 'formatDate', category: 'Helpers' }, { name: 'lineNet qty unitPrice discountPct', label: 'lineNet (per row)', category: 'Helpers' }, { name: 'sumLineNets items', label: 'sumLineNets (subtotal)', category: 'Helpers' }, { name: 'sumLineTaxes items', label: 'sumLineTaxes (tax)', category: 'Helpers' }, { name: 'sumGrandTotal items', label: 'sumGrandTotal (total)', category: 'Helpers' }, { name: 'numberToWordsTR (sumGrandTotal items)', label: 'numberToWordsTR (in words)', category: 'Helpers' } ]; /* * Full invoice dataset pre-loaded into the Data tab. Item rows keep the * canonical `qty` / `unitPrice` / `discountPct` / `taxPct` keys so the * built-in money helpers (`lineNet`, `sumLineNets`, `sumLineTaxes`, * `sumGrandTotal`) compute the totals without any extra wiring. */ const INITIAL_JSON = JSON.stringify( { company: { name: 'Northwind Studio Ltd.', addressLine1: '128 Maple Avenue, Suite 400', addressLine2: 'Portland, OR 97204, USA', email: 'billing@northwind.studio', phone: '+1 (503) 555-0142', taxId: 'US-93-1847265' }, customer: { name: 'Jordan Avery', company: 'Acme Corporation', addressLine1: '500 Industrial Parkway', addressLine2: 'Austin, TX 78704, USA', email: 'accounts@acme.com' }, invoice: { number: 'INV-2026-0042', issueDate: '2026-05-21', dueDate: '2026-06-20', currency: 'USD', poNumber: 'PO-77810', paymentTerms: 'Net 30', notes: 'Thank you for your business. Please reference the invoice number with your payment.' }, payment: { bankName: 'First National Bank', iban: 'US64 FNBK 0000 0123 4567 89', swift: 'FNBKUS33' }, items: [ { id: 'r1', name: 'Product Strategy Consulting', description: 'Discovery workshops & roadmap (8 hrs)', qty: 8, unitPrice: 150, discountPct: 0, taxPct: 8.25 }, { id: 'r2', name: 'Platform Implementation', description: 'Core setup, data migration & QA', qty: 1, unitPrice: 2500, discountPct: 5, taxPct: 8.25 }, { id: 'r3', name: 'UI Design System', description: 'Component library & design tokens', qty: 1, unitPrice: 1800, discountPct: 0, taxPct: 8.25 }, { id: 'r4', name: 'Annual Support Plan', description: 'Priority support, 12 months', qty: 12, unitPrice: 200, discountPct: 10, taxPct: 8.25 } ] }, null, 2 ); /* * A simple, self-contained invoice template. Every style is inline so the * markup renders identically in the Visual / Preview tabs, the rasterized * PDF tab, and any external Handlebars compile (e.g. server-side email/PDF * export) without depending on an external stylesheet. The line-item table * is driven by `{{#each items}}`; totals use the built-in money helpers. */ const INITIAL_TEMPLATE = `
{{company.name}}
{{company.addressLine1}}
{{company.addressLine2}}
{{company.email}} · {{company.phone}}
Tax ID: {{company.taxId}}
INVOICE
{{invoice.number}}
Issued {{formatDate invoice.issueDate "DD MMM YYYY"}}
Due {{formatDate invoice.dueDate "DD MMM YYYY"}}
Billed To
{{customer.name}}
{{customer.company}}
{{customer.addressLine1}}
{{customer.addressLine2}}
{{customer.email}}
Details
PO {{invoice.poNumber}}
Terms: {{invoice.paymentTerms}}
{{#each items}} {{/each}}
Description Qty Unit Price Disc. Amount
{{name}}
{{description}}
{{qty}} {{formatCurrency unitPrice ../invoice.currency}} {{discountPct}}% {{formatCurrency (lineNet qty unitPrice discountPct) ../invoice.currency}}
Subtotal {{formatCurrency (sumLineNets items) invoice.currency}}
Tax {{formatCurrency (sumLineTaxes items) invoice.currency}}
Total Due {{formatCurrency (sumGrandTotal items) invoice.currency}}
Payment Details
{{payment.bankName}} · IBAN {{payment.iban}} · SWIFT {{payment.swift}}
Notes — {{invoice.notes}}
Thank you for your business · {{company.name}}
`; export function HtmlTemplateEditorDemo() { const [value, setValue] = useState(INITIAL_TEMPLATE); const [data, setData] = useState(INITIAL_JSON); return ( ); } ``` ## Installation ```bash pnpm dlx @docyrus/cli add @docyrus/ui-html-template-editor ``` **Dependencies:** - [platejs](https://www.npmjs.com/package/platejs) - [handlebars](https://www.npmjs.com/package/handlebars) - [@uiw/react-codemirror](https://www.npmjs.com/package/@uiw/react-codemirror) - [@uiw/codemirror-extensions-langs](https://www.npmjs.com/package/@uiw/codemirror-extensions-langs) ## Usage The editor opens to a Word-style A4 page surface with a sticky toolbar above it. Four tabs across the top let users move between **Visual** (WYSIWYG), **Code** (raw Handlebars HTML), **Data** (JSON sample data) and **Preview** (compiled output). ```tsx import { useState } from 'react'; import { DEFAULT_HELPERS, HtmlTemplateEditor, type HandlebarsVariable } from '@docyrus/ui/components/html-template-editor'; const variables: HandlebarsVariable[] = [ { name: 'customer.name', label: 'Customer Name', category: 'Customer' }, { name: 'order.total', label: 'Order Total', category: 'Order' } ]; export function MyTemplateEditor() { const [html, setHtml] = useState(''); const [data, setData] = useState('{}'); return ( ); } ``` ### AI Assistant slot Pass a `renderAiAssistant` callback to mount any agent body inside a left-side drawer. The editor adds a Bot toggle button to the tabs row that opens/closes the drawer; the slot receives live editor state (`html`, `data`) so the agent can read the user's current draft and inject schema-aware context. The preferred wiring uses `useApplyHtmlTemplate` which owns the controlled state AND ships a fully-formed `applyHtmlTemplate` tool — the tool not only writes the new HTML into the editor but also runs `Handlebars.compile` against the current Data tab JSON and returns the compiled output preview, so the LLM can iterate to a working template without further user input. ```tsx import { EditorAgent } from '@docyrus/ui/components/editor-agent'; import { HtmlTemplateEditor, useApplyHtmlTemplate } from '@docyrus/ui/components/html-template-editor'; export function TemplateEditorWithAi({ client, user, agentId, dataSourceId, schema }) { const htmlTemplate = useApplyHtmlTemplate(); const [aiOpen, setAiOpen] = useState(false); return ( ( buildPromptContext({ schema, data: htmlTemplate.data, html: htmlTemplate.html })} clientTools={htmlTemplate.tools} /> )} /> ); } ``` The hook owns the html/data state, so the agent's `applyHtmlTemplate` tool pushes new content into the editor while the user's manual edits still flow through `onChange` / `onDataChange`. `editorContext` is invoked on every send — return a string that snapshots schema + current input + current template so the agent always sees fresh state. The matching backend agent must register a tool named `applyHtmlTemplate` whose input schema mirrors `{ html: string (required), data?: object, explanation?: string }`. ## Features - **A4 page surface** — Visual + Preview tabs render the document inside a 794×1123 px sheet (210×297 mm at 96 DPI) with proper margins, so the editor matches the final PDF 1:1. - **Sticky Word-style toolbar** — pinned above the page; follows the user when scrolling long documents. - **Variable & helper chips** — `{{customer.name}}` renders as a colored inline badge (color is derived from `category`). Block helpers like `{{#if}}`, `{{#each}}`, `{{#with}}`, `{{#unless}}` get their own chips; `{{/helper}}` and `{{else}}` complete the set. - **`{{`-trigger autocomplete** — type `{{` anywhere to open a filtered picker. `↑↓` navigates, `Enter`/`Tab` inserts. Each variable's `category` becomes a section heading. - **Auto-convert** — typing a complete `{{var}}`, `{{#helper expr}}`, `{{/helper}}`, or `{{else}}` and closing with `}}` converts the text to the corresponding chip automatically. - **Click-to-edit chips** — clicking any inserted chip opens an edit popover (above the chip via Radix collision detection). Variable chips swap their `name` from the same variables list. Block-helper chips edit their expression; `{{#each}}` and `{{#with}}` get a path picker built live from the Data tab JSON (array vs. object scan, suffix-match highlights the current selection). Marks (bold/italic/etc.) on the chip survive the swap. - **Robust table round-trip** — `{{#each}}…{{/each}}
` survives any chip edit. Block markers transport via HTML comments to bypass HTML foster-parenting, get hoisted out of table sections (slate can't hold inline-void as direct child of ``), ``/``/`` are unwrapped and `colSizes` is pre-seeded so the column count doesn't collapse to one. The serializer pattern-detects `[chip, table, chip]` adjacency and re-injects the chips around the body rows only (first `` is treated as ``), so `{{#each}}` never wraps the header. - **Four tabs**: Visual (WYSIWYG), Code (raw HTML in CodeMirror), Data (JSON sample data in CodeMirror), Preview (Handlebars-compiled output rendered in an A4-sized iframe). - **Data-driven Table dialog** — the toolbar `Table` button scans the Data tab JSON for array-of-object paths and walks the user through column selection, per-cell styling, per-column aggregate pills (Sum / Avg / Min / Max / Count) and free-form formula expressions like `qty * unitPrice * (1 - discountPct/100)`. Inserted tables stay live: serialization emits `{{#each }}` so the Preview tab iterates over real data. - **Safe expression evaluator** — `sumLineExpr` evaluates user-typed math via a small recursive-descent parser (no `eval` / `new Function`) so templates can be safely shared between users. - **Precision-safe currency math** — every financial helper rounds with EPSILON correction (`Math.round((n + Number.EPSILON) * 100) / 100`) so cumulative IEEE-754 drift can't move displayed totals by `0.01`. - **Default Handlebars helpers** — `formatCurrency`, `formatNumber`, `formatPercent`, `formatDate`, `multiply`, `add`, `subtract`, `divide`, `sumProperty`, `avgProperty`, `minProperty`, `maxProperty`, `countItems`, `lineNet`, `lineTotal`, `sumLineNets`, `sumLineTaxes`, `sumGrandTotal`, `sumLineExpr`, `eq`, `gt`, `lt` are registered at module load. - **`extraHelpers` prop** — register additional helpers (locale packs, domain formatters) without touching the package. - **Locale pack: `numberToWordsTR`** — opt-in Turkish number-to-words helper (`Sekiz Yüz Altmış Dokuz Bin Altı Yüz Kırk Türk Lirası`). Pass via `extraHelpers` only when needed. - **Built-in Plate kits** — Basic blocks, marks, lists, links, alignment, font color & size, columns, native tables, callouts. - **Read-only mode** — pass `readOnly` to render a non-editable view with chips visible but the toolbar hidden. ## Data-driven tables Click the toolbar **Table** button to open a configuration dialog backed by the Data tab JSON. The dialog walks the user through four steps; everything is configured by typing or clicking — no schema code required. ### 1. Pick a data path The dialog enumerates every array-of-objects path found in the Data tab JSON (up to 6 levels deep) and lists them as a tree. For example with this JSON: ```json { "customer": { "contacts": [ { "id": "c1", "name": "Aytekin", "phones": [{ "type": "Work", "number": "..." }] } ] }, "items": [{ "qty": 1, "unitPrice": 100, "discountPct": 5 }] } ``` …the picker surfaces `items`, `customer.contacts`, and `customer.contacts.0.phones` as selectable sources. ### 2. Configure columns Once a path is selected, the dialog auto-detects fields from the first row and lists them as toggleable rows. Each field carries: - **Visibility** — `id`-shaped fields default to hidden; the user opts them back in. - **Format** — `text` / `number` / `currency` / `percent` / `date` / `computed`. Smart inference picks `percent` for `*_pct` / `*_rate` keys, `currency` for `price`, `cost`, `total`-style keys, `date` for `*_at` / `*date*` keys. - **Alignment, weight, size, text & background color** — per-cell styling that flows into the serialized HTML. A live preview chip on each row shows what the cell will look like with the first sample row's data. ### 3. Toggle per-column aggregates Each column row carries pill toggles for the standard aggregates — **Toplam (Sum)**, **Ort. (Avg)**, **Min**, **Max**, **Adet (Count)**. Active pills add a row to the table's `` at serialize time. Currency / number columns get all five pills; text / date columns only get Count. ### 4. Write free-form total formulas Below the field list is a "Smart total" section where users type compound expressions like `qty * unitPrice * (1 - discountPct/100) * (1 + taxPct/100)` — anything the [expression parser](#expression-syntax) understands. Each saved total has a wide editable label textarea (anything — `×`, `−`, multi-line — goes) and a separate formula textarea. The serializer emits these as a right-aligned 2-column block below the main table, with the canonical net → tax → grand-total order if those shapes are detected. ### Editing inserted tables Each ad-hoc table renders an **Edit** button in its header. Clicking it re-opens the same dialog with the existing config (path, columns, formulas) pre-filled. Legacy schema-driven tables loaded from older templates also round-trip cleanly. ## Expression syntax User-typed formulas are evaluated by a small recursive-descent parser that supports: | Construct | Example | |-----------|---------| | Identifier (column key) | `qty`, `unitPrice`, `discountPct` | | Numeric literal | `100`, `0.5`, `.25` | | Binary operators | `+ - * / %` | | Parentheses | `(1 - discountPct/100)` | | Unary minus | `-amount` | The evaluator deliberately avoids `eval` / `new Function` — templates can be persisted and shared across users without becoming a code-injection vector. Anything that fails to parse evaluates to `0`, so a typo in the textarea doesn't blow up the preview. ```handlebars {{formatCurrency (sumLineExpr items "qty * unitPrice * (1 - discountPct/100) * (1 + taxPct/100)") "USD"}} ``` ## Bring your own helpers The editor only registers a small generic helper set by default. Locale-specific or domain-specific helpers should be passed via `extraHelpers`: ```tsx import { HtmlTemplateEditor, numberToWordsTR } from '@docyrus/ui/components/html-template-editor'; ``` Helpers are registered on first mount and become available globally via the singleton `Handlebars` import. From the template body: ```handlebars

Total: {{formatCurrency (sumLineExpr items "qty * unitPrice * (1 + taxPct/100)") order.currency}}

In words: {{numberToWordsTR (sumLineExpr items "qty * unitPrice * (1 + taxPct/100)")}}

``` ## Compile the template at runtime The component itself does NOT compile templates — it produces the template HTML. To render it with live data in your app (preview pane, PDF generation, server-side render): ```tsx import Handlebars from 'handlebars'; const output = Handlebars.compile(html)(data); ``` The Preview tab inside the editor uses this exact pattern against the `data` JSON the user provides. ## Advanced: schema-driven tables (legacy) For consumer apps that need fully pre-defined tables (e.g. fixed invoice template, hard-coded column compute functions), the editor still accepts a `tableSchemas` prop. Schema-driven `` blocks already present in loaded HTML continue to render and edit; new schemas can be passed without affecting the data-driven Table dialog. ```tsx import { type ComputedRow, type ComputedTableSchema } from '@docyrus/ui/components/html-template-editor'; function netOf(row: ComputedRow): number { return (Number(row.qty) || 0) * (Number(row.unitPrice) || 0) * (1 - (Number(row.discountPct) || 0) / 100); } const QUOTE_SCHEMA: ComputedTableSchema = { id: 'quote-line-items', label: 'Line Items', defaultCurrency: 'USD', columns: [ { key: 'name', label: 'Description', type: 'text' }, { key: 'qty', label: 'Qty', type: 'number', defaultValue: 1 }, { key: 'unitPrice', label: 'Unit Price', type: 'currency', defaultValue: 0 }, { key: 'discountPct', label: 'Discount', type: 'percent', defaultValue: 0 }, { key: 'lineTotal', label: 'Total', type: 'computed', compute: netOf } ], footer: [ { key: 'subtotal', label: 'Subtotal', compute: rows => rows.reduce((a, r) => a + netOf(r), 0) } ] }; ``` Inside the template HTML, instances are stored as `
` — the editor reconstructs the table from `schemaId` + `rows` on mount. ## Advanced: HandlebarsKit `HandlebarsKit` is a flat array of the five HBS-related Plate plugins (variable / block-open / block-close / else / normalizer). Use it when you want HBS chip behavior embedded inside a custom Plate editor: ```tsx import { HandlebarsKit } from '@docyrus/ui/components/html-template-editor'; const editor = usePlateEditor({ plugins: [...myPlugins, ...HandlebarsKit] }); ``` ## API Reference | Prop | Type | Default | Description | |------|------|---------|-------------| | `value` | `string` | `''` | Initial HBS HTML string | | `onChange` | `(value: string) => void` | — | Called with serialized HBS HTML on change (debounced 300 ms). After initial mount the editor re-pushes a serialized version so `
` shells receive their inner static table HTML. | | `data` | `string` | `'{\\n \\n}'` | Initial JSON sample data shown in the Data tab and used by the Preview tab's Handlebars compile. Also scanned by the Table dialog to surface array paths. | | `onDataChange` | `(data: string) => void` | — | Called when the user edits the JSON data. | | `variables` | `HandlebarsVariable[]` | `[]` | Variables shown in the side picker and the `{{`-trigger combobox. | | `helpers` | `HandlebarsBlockHelper[]` | `DEFAULT_HELPERS` | Block helpers shown in the picker and toolbar popover. | | `tableSchemas` | `ComputedTableSchema[]` | `[]` | Legacy schema-driven table definitions (see [Advanced: schema-driven tables](#advanced-schema-driven-tables-legacy)). | | `extraHelpers` | `Record unknown>` | — | Extra Handlebars helpers to register on mount (e.g. `numberToWordsTR`). | | `defaultTab` | `'visual' \| 'code' \| 'data' \| 'preview'` | `'visual'` | Tab shown on first render. | | `readOnly` | `boolean` | `false` | Disables editing; hides toolbar. | | `className` | `string` | — | Extra class on the root container. | | `placeholder` | `string` | `'Write your template…'` | Placeholder shown in the empty editor. | | `minHeight` | `string` | `'240px'` | CSS `min-height` of the editor / code view area. | | `aiAssistantOpen` | `boolean` | — | Controlled open state for the AI Assistant drawer. When provided the editor stops managing the open state internally — pair with `onAiAssistantOpenChange`. | | `onAiAssistantOpenChange` | `(open: boolean) => void` | — | Fired when the AI Assistant toolbar button toggles the drawer. | | `renderAiAssistant` | `(ctx: IHtmlTemplateAiAssistantRenderContext) => ReactNode` | — | Mounts a custom AI Assistant drawer body on the left of the editor. When set, the toolbar shows a Bot toggle that opens/closes the drawer; this render fn supplies the body (typically a `` or `` wrapper). Without it the AI button is hidden. | | `aiAssistantWidth` | `number` | `380` | Width in pixels the drawer animates to when open. | ## Type Exports | Type | Description | |------|-------------| | `HtmlTemplateEditorProps` | Props for `HtmlTemplateEditor` | | `HandlebarsVariable` | Variable definition passed to `variables` | | `HandlebarsBlockHelper` | Helper definition passed to `helpers` | | `ComputedTableSchema` | Full schema describing a legacy schema-driven table | | `ComputedColumn` | One column inside a schema | | `ComputedColumnType` | `'text' \| 'number' \| 'currency' \| 'percent' \| 'computed'` | | `ComputedColumnContext` | `{ currency, locale, rows, index }` passed to `column.compute` / `column.format` | | `ComputedFooter` | Footer aggregate row inside a schema | | `ComputedFooterContext` | `{ currency, locale, rows }` passed to `footer.compute` | | `ComputedRow` | Open dict `Record & { id: string }` | | `ComputedTableLabels` | i18n labels (title / addRow / emptyState / currencyLabel) | | `ComputedCurrencyOption` | `{ code, label, locale? }` entry for `schema.currencyOptions` | | `ComputedColumnConfig` | Ad-hoc column definition stored on the Plate node (key + label + format + per-cell styling) | | `ComputedColumnFormat` | `'text' \| 'number' \| 'currency' \| 'percent' \| 'date' \| 'computed'` | | `ComputedFooterConfig` | Ad-hoc footer entry: per-column aggregate or free-form `formula` | | `ComputedAggregate` | `'sum' \| 'average' \| 'count' \| 'min' \| 'max'` | | `ComputedFontWeight` | `'normal' \| 'bold'` | | `ComputedFontSize` | `'xs' \| 'sm' \| 'base' \| 'lg' \| 'xl'` | | `FormulaTerm` | Generic term-chain model `{ op, key }` used by legacy formula DSL | | `FormulaTermOp` | `'multiply' \| 'divide' \| 'multiply_complement' \| 'multiply_premium' \| 'multiply_pct'` | | `TComputedTableElement` | Plate element node — extended with `dataPath`, `label`, `columns`, `footer` for ad-hoc mode | | `ExtraHandlebarsHelper` | Signature for entries in `extraHelpers` | | `IHtmlTemplateAiAssistantRenderContext` | Argument passed to `renderAiAssistant` — `{ open, width, onClose, html, data }` | | `IUseApplyHtmlTemplateResult` | Return shape of `useApplyHtmlTemplate` — `{ html, setHtml, data, setData, tools }` | ## Type Reference ### HandlebarsVariable | Field | Type | Description | |-------|------|-------------| | `name` | `string` | Handlebars expression body (e.g. `customer.name`, `formatCurrency total order.currency`) | | `label` | `string?` | Human-readable label shown in the picker | | `description` | `string?` | Short description shown below the label | | `category` | `string?` | Groups variables in the picker popover; also drives chip color | ### HandlebarsBlockHelper | Field | Type | Description | |-------|------|-------------| | `name` | `string` | Helper name (`if`, `each`, …) | | `label` | `string?` | Human-readable label | | `description` | `string?` | Short description | | `defaultExpression` | `string?` | Pre-filled expression when inserting via the toolbar popover | ### ComputedColumnConfig | Field | Type | Description | |-------|------|-------------| | `key` | `string` | Field name on each row in the bound array | | `label` | `string` | Header cell text | | `format` | `ComputedColumnFormat` | Cell rendering / formatting type | | `visible` | `boolean?` | Initial visibility (default `true`; identifier-shaped keys default to `false`) | | `align` | `'left' \| 'right' \| 'center'?` | Cell alignment | | `width` | `string?` | CSS width hint | | `fontWeight` | `'normal' \| 'bold'?` | Text weight | | `fontSize` | `'xs' \| 'sm' \| 'base' \| 'lg' \| 'xl'?` | Tailwind text-size token | | `textColor` | `string?` | Tailwind class, hex, or CSS color string | | `backgroundColor` | `string?` | Tailwind class, hex, or CSS color string | | `formatPattern` | `string?` | Optional override format string (e.g. `'DD/MM/YYYY'`) | ### ComputedFooterConfig | Field | Type | Description | |-------|------|-------------| | `key` | `string` | Column key the entry sits under (drives cell placement) | | `label` | `string` | Footer row label cell text | | `aggregate` | `ComputedAggregate` | Standard aggregate when `formula` is not set | | `formula` | `string?` | Raw Handlebars sub-expression (`sumLineExpr items "qty * unitPrice"`) — overrides `aggregate` | | `formulaFormat` | `ComputedColumnFormat?` | Format wrapper hint for the formula output (defaults to the target column's format) | | `textColor` | `string?` | Optional row text color | | `backgroundColor` | `string?` | Optional row background color | ### ComputedColumn (legacy schema) | Field | Type | Description | |-------|------|-------------| | `key` | `string` | Field name on each row dict (`qty`, `unitPrice`, …) | | `label` | `string` | Header cell text | | `type` | `ComputedColumnType` | One of `text` / `number` / `currency` / `percent` / `computed` | | `defaultValue` | `unknown?` | Seeded into new rows | | `width` | `string?` | CSS width hint (`'72px'`, `'20%'`) | | `align` | `'left' \| 'right' \| 'center'?` | Cell alignment | | `step` | `number?` | `step` attr for numeric inputs | | `min` | `number?` | `min` attr for numeric inputs | | `max` | `number?` | `max` attr for numeric inputs | | `compute` | `(row, ctx) => number \| string?` | For `computed` columns: derive the value | | `format` | `(value, row, ctx) => string?` | Override the default formatter | | `toggleable` | `boolean?` | Show in the column-toggle dropdown | | `defaultVisible` | `boolean?` | Initial visibility (default `true`) | ### ComputedFooter (legacy schema) | Field | Type | Description | |-------|------|-------------| | `key` | `string` | Stable id (`subtotal`, `tax`, `grandTotal`) | | `label` | `string` | Footer label cell text | | `compute` | `(rows, ctx) => number \| string` | Aggregator over all rows | | `format` | `(value, ctx) => string?` | Display format override (default: currency) | | `emphasis` | `'normal' \| 'strong'?` | Visual weight (`strong` = bordered grand-total row) | ### IHtmlTemplateAiAssistantRenderContext | Field | Type | Description | |-------|------|-------------| | `open` | `boolean` | Whether the drawer is currently open | | `width` | `number` | Width in pixels the drawer animates to when open (mirrors `aiAssistantWidth`) | | `onClose` | `() => void` | Call from inside the slot to close the drawer | | `html` | `string` | Current template HTML in the editor | | `data` | `string` | Current JSON input string in the Data tab | ### ComputedTableSchema (legacy schema) | Field | Type | Description | |-------|------|-------------| | `id` | `string` | Stable id stored in node JSON to look up the schema at render time | | `label` | `string` | Short label shown in the insert dropdown | | `columns` | `ComputedColumn[]` | Column definitions | | `footer` | `ComputedFooter[]?` | Aggregate footer rows | | `defaultCurrency` | `string?` | Default currency for new instances | | `defaultLocale` | `string?` | Default locale for formatting | | `defaultRows` | `ComputedRow[]?` | Initial rows on first insert (else a single empty row) | | `labels` | `ComputedTableLabels?` | i18n strings (title / addRow / emptyState / currencyLabel) | | `currencyOptions` | `ComputedCurrencyOption[]?` | Entries for the in-table currency picker (omit to hide the picker) | ## Components | Component | Description | |-----------|-------------| | `HtmlTemplateEditor` | Main editor component | | `HandlebarsKit` | Array of all HBS Plate plugins for embedding in a custom editor | | `DEFAULT_HELPERS` | Default `if / unless / each / with` helper definitions | | `numberToWordsTR` | Optional Turkish number-to-words Handlebars helper (pass via `extraHelpers`) | | `useApplyHtmlTemplate` | Hook that owns `html` + `data` state and exposes the `applyHtmlTemplate` client-side tool for `` — the tool writes the agent's HTML AND compiles it against the current Data tab JSON, returning a compiled-output preview or a categorized error (data-empty / invalid-json / Handlebars compile failure). See the **AI Assistant slot** section above. |