# 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}}
|
| Description |
Qty |
Unit Price |
Disc. |
Amount |
{{#each items}}
{{name}} {{description}} |
{{qty}} |
{{formatCurrency unitPrice ../invoice.currency}} |
{{discountPct}}% |
{{formatCurrency (lineNet qty unitPrice discountPct) ../invoice.currency}} |
{{/each}}
|
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** — `` 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. |