Fifth atomic checkpoint of F2.3. Moves the table-specific DOM render
helpers from engine/render.ts to extensions/table/render.ts.
Moved:
- renderTable(block, path, helpers) — replaces the table branch in
renderWordsBlock + internal renderWordsTableRow + renderWordsTableCell
- renderTablePlainText(block, helpers) — replaces the table branch in
renderBlockPlainText
The extension defines structural render-element types
(TableRenderElement, TableRenderText, TableRenderNode) assignment-
compatible with the engine's WordsRenderElement/Node. The engine
passes its constants (WORDS_PATH_ATTR, WORDS_NODE_ATTR) + helpers
(encodePath, renderInlines, getInlineText) via a TableRenderHelpers
object, keeping the extension independent of engine/dom.ts.
The engine's renderWordsBlock dispatcher branch for `block.type === 'table'`
now delegates to `renderTable(block, path, { ... })` in a single call.
~50 LoC of helper functions removed from the engine.
Extension barrel updated to expose:
- renderTable / renderTablePlainText
- TableRenderElement / TableRenderNode / TableRenderText / TableRenderHelpers / RenderPath types
Verification:
- 143/143 tests pass in src/uix/soma/components/words
- 0 new TypeScript errors
F2.3 progress: a + b + c + d + e done. Remaining: f (path), g
(normalize), h (operations — largest at ~400-500 LoC), i (registry
wire-up), j (verify). Operations remains the heaviest piece — likely
its own dedicated session.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
active-uix
parent
5797e69b88
commit
d839273536
@ -0,0 +1,153 @@
|
|||||||
|
/**
|
||||||
|
* Table extension — DOM render hooks.
|
||||||
|
*
|
||||||
|
* Owns the conversion of WordsTableBlock / WordsTableRow / WordsTableCell
|
||||||
|
* to the engine's intermediate render-element shape. The engine's render
|
||||||
|
* dispatcher calls into here for `block.type === 'table'`.
|
||||||
|
*
|
||||||
|
* Render-internal helpers (encodeWordsPath, WORDS_NODE_ATTR, etc.) come
|
||||||
|
* from the engine via constructor parameters to keep this module
|
||||||
|
* decoupled from `engine/dom.ts` constants.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { WordsInline } from '../../engine/document';
|
||||||
|
import type { WordsTableBlock, WordsTableCell, WordsTableRow } from './types';
|
||||||
|
|
||||||
|
// Render-element types kept structural to avoid importing the engine
|
||||||
|
// shape. The engine's WordsRenderElement is assignment-compatible.
|
||||||
|
export interface TableRenderElement {
|
||||||
|
readonly kind: 'element';
|
||||||
|
readonly tag:
|
||||||
|
| 'table'
|
||||||
|
| 'tbody'
|
||||||
|
| 'tr'
|
||||||
|
| 'td'
|
||||||
|
| 'th'
|
||||||
|
| string;
|
||||||
|
readonly attrs: Readonly<Record<string, string | undefined>>;
|
||||||
|
readonly children: ReadonlyArray<TableRenderNode>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TableRenderText {
|
||||||
|
readonly kind: 'text';
|
||||||
|
readonly text: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TableRenderNode = TableRenderElement | TableRenderText;
|
||||||
|
|
||||||
|
export type RenderPath = readonly number[];
|
||||||
|
|
||||||
|
export interface TableRenderHelpers {
|
||||||
|
/** Path attr key (engine's `WORDS_PATH_ATTR`). */
|
||||||
|
readonly pathAttr: string;
|
||||||
|
/** Node attr key (engine's `WORDS_NODE_ATTR`). */
|
||||||
|
readonly nodeAttr: string;
|
||||||
|
/** Engine path encoder (engine's `encodeWordsPath`). */
|
||||||
|
readonly encodePath: (path: RenderPath) => string;
|
||||||
|
/** Engine inline renderer for the cell contents. */
|
||||||
|
readonly renderInlines: (
|
||||||
|
inlines: readonly WordsInline[],
|
||||||
|
path: RenderPath
|
||||||
|
) => ReadonlyArray<TableRenderNode>;
|
||||||
|
/** Engine plain-text helper for cell text serialization. */
|
||||||
|
readonly getInlineText: (inline: WordsInline) => string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a table block to the engine's intermediate element tree.
|
||||||
|
*
|
||||||
|
* Mirrors what `renderWordsBlock(block, path)` produced for tables
|
||||||
|
* before F2.3e — same DOM shape (`<table><tbody><tr><td|th>`), same
|
||||||
|
* attributes, same path encoding. Pure function: takes the block + a
|
||||||
|
* path + helpers, returns a render-element.
|
||||||
|
*/
|
||||||
|
export function renderTable(
|
||||||
|
block: WordsTableBlock,
|
||||||
|
path: RenderPath,
|
||||||
|
helpers: TableRenderHelpers
|
||||||
|
): TableRenderElement {
|
||||||
|
return {
|
||||||
|
kind: 'element',
|
||||||
|
tag: 'table',
|
||||||
|
attrs: {
|
||||||
|
[helpers.nodeAttr]: 'table',
|
||||||
|
[helpers.pathAttr]: helpers.encodePath(path),
|
||||||
|
'data-words-block': 'table',
|
||||||
|
...(block.striped ? { 'data-words-table-striped': '' } : {}),
|
||||||
|
...(block.compact ? { 'data-words-table-compact': '' } : {})
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
kind: 'element',
|
||||||
|
tag: 'tbody',
|
||||||
|
attrs: {},
|
||||||
|
children: block.children.map((row, rowIndex) =>
|
||||||
|
renderTableRow(row, [...path, rowIndex], helpers)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderTableRow(
|
||||||
|
row: WordsTableRow,
|
||||||
|
path: RenderPath,
|
||||||
|
helpers: TableRenderHelpers
|
||||||
|
): TableRenderElement {
|
||||||
|
return {
|
||||||
|
kind: 'element',
|
||||||
|
tag: 'tr',
|
||||||
|
attrs: {
|
||||||
|
[helpers.nodeAttr]: 'table-row',
|
||||||
|
[helpers.pathAttr]: helpers.encodePath(path)
|
||||||
|
},
|
||||||
|
children: row.children.map((cell, cellIndex) =>
|
||||||
|
renderTableCell(cell, [...path, cellIndex], helpers)
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderTableCell(
|
||||||
|
cell: WordsTableCell,
|
||||||
|
path: RenderPath,
|
||||||
|
helpers: TableRenderHelpers
|
||||||
|
): TableRenderElement {
|
||||||
|
return {
|
||||||
|
kind: 'element',
|
||||||
|
tag: cell.header ? 'th' : 'td',
|
||||||
|
attrs: {
|
||||||
|
[helpers.nodeAttr]: 'table-cell',
|
||||||
|
[helpers.pathAttr]: helpers.encodePath(path),
|
||||||
|
...(cell.header ? { 'data-words-cell-header': '' } : {}),
|
||||||
|
...(cell.textAlign && cell.textAlign !== 'left'
|
||||||
|
? {
|
||||||
|
'data-words-cell-align': cell.textAlign,
|
||||||
|
style: `text-align: ${cell.textAlign}`
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
...(cell.verticalAlign && cell.verticalAlign !== 'top'
|
||||||
|
? { 'data-words-cell-vertical': cell.verticalAlign }
|
||||||
|
: {}),
|
||||||
|
...(cell.tone && cell.tone !== 'default' ? { 'data-words-cell-tone': cell.tone } : {})
|
||||||
|
},
|
||||||
|
children: helpers.renderInlines(cell.children, path)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a table block to plain text — cells tab-separated, rows
|
||||||
|
* newline-separated. Used by the engine's plain-text serializer for
|
||||||
|
* features like clipboard copy of a single block. Pure function.
|
||||||
|
*/
|
||||||
|
export function renderTablePlainText(
|
||||||
|
block: WordsTableBlock,
|
||||||
|
helpers: Pick<TableRenderHelpers, 'getInlineText'>
|
||||||
|
): string {
|
||||||
|
return block.children
|
||||||
|
.map((row) =>
|
||||||
|
row.children
|
||||||
|
.map((cell) => cell.children.map(helpers.getInlineText).join(''))
|
||||||
|
.join('\t')
|
||||||
|
)
|
||||||
|
.join('\n');
|
||||||
|
}
|
||||||
Loading…
Reference in new issue