@oml/markdown 0.7.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.
Files changed (112) hide show
  1. package/README.md +39 -0
  2. package/out/index.d.ts +2 -0
  3. package/out/index.js +4 -0
  4. package/out/index.js.map +1 -0
  5. package/out/md/index.d.ts +6 -0
  6. package/out/md/index.js +8 -0
  7. package/out/md/index.js.map +1 -0
  8. package/out/md/md-execution.d.ts +33 -0
  9. package/out/md/md-execution.js +3 -0
  10. package/out/md/md-execution.js.map +1 -0
  11. package/out/md/md-executor.d.ts +21 -0
  12. package/out/md/md-executor.js +498 -0
  13. package/out/md/md-executor.js.map +1 -0
  14. package/out/md/md-frontmatter.d.ts +4 -0
  15. package/out/md/md-frontmatter.js +48 -0
  16. package/out/md/md-frontmatter.js.map +1 -0
  17. package/out/md/md-registry.d.ts +7 -0
  18. package/out/md/md-registry.js +19 -0
  19. package/out/md/md-registry.js.map +1 -0
  20. package/out/md/md-runtime.d.ts +10 -0
  21. package/out/md/md-runtime.js +166 -0
  22. package/out/md/md-runtime.js.map +1 -0
  23. package/out/md/md-types.d.ts +40 -0
  24. package/out/md/md-types.js +3 -0
  25. package/out/md/md-types.js.map +1 -0
  26. package/out/md/md-yaml.d.ts +1 -0
  27. package/out/md/md-yaml.js +15 -0
  28. package/out/md/md-yaml.js.map +1 -0
  29. package/out/renderers/chart-renderer.d.ts +6 -0
  30. package/out/renderers/chart-renderer.js +392 -0
  31. package/out/renderers/chart-renderer.js.map +1 -0
  32. package/out/renderers/diagram-renderer.d.ts +7 -0
  33. package/out/renderers/diagram-renderer.js +2354 -0
  34. package/out/renderers/diagram-renderer.js.map +1 -0
  35. package/out/renderers/graph-renderer.d.ts +6 -0
  36. package/out/renderers/graph-renderer.js +1384 -0
  37. package/out/renderers/graph-renderer.js.map +1 -0
  38. package/out/renderers/index.d.ts +14 -0
  39. package/out/renderers/index.js +16 -0
  40. package/out/renderers/index.js.map +1 -0
  41. package/out/renderers/list-renderer.d.ts +6 -0
  42. package/out/renderers/list-renderer.js +252 -0
  43. package/out/renderers/list-renderer.js.map +1 -0
  44. package/out/renderers/matrix-renderer.d.ts +14 -0
  45. package/out/renderers/matrix-renderer.js +498 -0
  46. package/out/renderers/matrix-renderer.js.map +1 -0
  47. package/out/renderers/message-renderer.d.ts +6 -0
  48. package/out/renderers/message-renderer.js +14 -0
  49. package/out/renderers/message-renderer.js.map +1 -0
  50. package/out/renderers/registry.d.ts +9 -0
  51. package/out/renderers/registry.js +41 -0
  52. package/out/renderers/registry.js.map +1 -0
  53. package/out/renderers/renderer.d.ts +28 -0
  54. package/out/renderers/renderer.js +61 -0
  55. package/out/renderers/renderer.js.map +1 -0
  56. package/out/renderers/table-editor-renderer.d.ts +4 -0
  57. package/out/renderers/table-editor-renderer.js +9 -0
  58. package/out/renderers/table-editor-renderer.js.map +1 -0
  59. package/out/renderers/table-renderer.d.ts +95 -0
  60. package/out/renderers/table-renderer.js +1571 -0
  61. package/out/renderers/table-renderer.js.map +1 -0
  62. package/out/renderers/text-renderer.d.ts +7 -0
  63. package/out/renderers/text-renderer.js +219 -0
  64. package/out/renderers/text-renderer.js.map +1 -0
  65. package/out/renderers/tree-renderer.d.ts +4 -0
  66. package/out/renderers/tree-renderer.js +9 -0
  67. package/out/renderers/tree-renderer.js.map +1 -0
  68. package/out/renderers/types.d.ts +18 -0
  69. package/out/renderers/types.js +3 -0
  70. package/out/renderers/types.js.map +1 -0
  71. package/out/renderers/wikilink-utils.d.ts +6 -0
  72. package/out/renderers/wikilink-utils.js +100 -0
  73. package/out/renderers/wikilink-utils.js.map +1 -0
  74. package/out/static/browser-runtime.bundle.js +74155 -0
  75. package/out/static/browser-runtime.bundle.js.map +7 -0
  76. package/out/static/browser-runtime.d.ts +1 -0
  77. package/out/static/browser-runtime.js +218 -0
  78. package/out/static/browser-runtime.js.map +1 -0
  79. package/out/static/index.d.ts +1 -0
  80. package/out/static/index.js +3 -0
  81. package/out/static/index.js.map +1 -0
  82. package/out/static/runtime-assets.d.ts +2 -0
  83. package/out/static/runtime-assets.js +174 -0
  84. package/out/static/runtime-assets.js.map +1 -0
  85. package/package.json +74 -0
  86. package/src/index.ts +4 -0
  87. package/src/md/index.ts +8 -0
  88. package/src/md/md-execution.ts +51 -0
  89. package/src/md/md-executor.ts +598 -0
  90. package/src/md/md-frontmatter.ts +53 -0
  91. package/src/md/md-registry.ts +22 -0
  92. package/src/md/md-runtime.ts +191 -0
  93. package/src/md/md-types.ts +48 -0
  94. package/src/md/md-yaml.ts +17 -0
  95. package/src/renderers/chart-renderer.ts +473 -0
  96. package/src/renderers/diagram-renderer.ts +2520 -0
  97. package/src/renderers/graph-renderer.ts +1653 -0
  98. package/src/renderers/index.ts +16 -0
  99. package/src/renderers/list-renderer.ts +289 -0
  100. package/src/renderers/matrix-renderer.ts +616 -0
  101. package/src/renderers/message-renderer.ts +18 -0
  102. package/src/renderers/registry.ts +45 -0
  103. package/src/renderers/renderer.ts +84 -0
  104. package/src/renderers/table-editor-renderer.ts +8 -0
  105. package/src/renderers/table-renderer.ts +1868 -0
  106. package/src/renderers/text-renderer.ts +252 -0
  107. package/src/renderers/tree-renderer.ts +7 -0
  108. package/src/renderers/types.ts +22 -0
  109. package/src/renderers/wikilink-utils.ts +108 -0
  110. package/src/static/browser-runtime.ts +249 -0
  111. package/src/static/index.ts +3 -0
  112. package/src/static/runtime-assets.ts +175 -0
@@ -0,0 +1,616 @@
1
+ // Copyright (c) 2026 Modelware. All rights reserved.
2
+
3
+ import { QueryMarkdownBlockRenderer } from './renderer.js';
4
+ import type { MdBlockExecutionResult } from './types.js';
5
+ import { appendTokenizedValueParts, createWikiLinkElement, isIriValue, shortLabelFromIri } from './wikilink-utils.js';
6
+
7
+ type MatrixStyleRuleKind = 'row' | 'column' | 'cell' | 'header';
8
+ type MatrixStyleRuleTarget = 'cell' | 'value';
9
+ type MatrixSelector = { kind: MatrixStyleRuleKind; condition?: string };
10
+ type CompiledMatrixStyleRule = {
11
+ selectors: MatrixSelector[];
12
+ target: MatrixStyleRuleTarget;
13
+ style: Record<string, string>;
14
+ };
15
+
16
+ type MatrixRowContext = {
17
+ index: number;
18
+ name: string;
19
+ };
20
+
21
+ type MatrixColumnContext = {
22
+ index: number;
23
+ name: string;
24
+ };
25
+
26
+ type MatrixCellContext = {
27
+ row: string;
28
+ col: string;
29
+ rowIndex: number;
30
+ colIndex: number;
31
+ value: string;
32
+ datatype?: string;
33
+ lang?: string;
34
+ };
35
+
36
+ type MatrixHeaderContext = {
37
+ name: string;
38
+ };
39
+
40
+ type MatrixSelectorContext =
41
+ | { kind: 'row'; context: MatrixRowContext }
42
+ | { kind: 'column'; context: MatrixColumnContext }
43
+ | { kind: 'cell'; context: MatrixCellContext }
44
+ | { kind: 'header'; context: MatrixHeaderContext };
45
+
46
+ type MatrixCellRecord = {
47
+ values: string[];
48
+ };
49
+
50
+ type MatrixModel = {
51
+ rowKeys: string[];
52
+ columnKeys: string[];
53
+ cells: Map<string, MatrixCellRecord>;
54
+ };
55
+
56
+ export class MatrixMarkdownBlockRenderer extends QueryMarkdownBlockRenderer {
57
+ canRender(result: MdBlockExecutionResult): boolean {
58
+ return result.status === 'ok'
59
+ && result.format === 'table'
60
+ && result.kind === 'matrix'
61
+ && !!result.payload;
62
+ }
63
+
64
+ render(result: MdBlockExecutionResult): HTMLElement {
65
+ const container = this.createResultContainer(result.status);
66
+ container.classList.add('oml-md-result-table');
67
+
68
+ const payloadColumns = result.payload?.columns ?? [];
69
+ const payloadRows = result.payload?.rows ?? [];
70
+ const rowIndex = payloadColumns.indexOf('row');
71
+ const columnIndex = payloadColumns.indexOf('column');
72
+ const valueIndex = payloadColumns.indexOf('value');
73
+ if (rowIndex < 0 || columnIndex < 0 || valueIndex < 0) {
74
+ container.appendChild(this.createMessageContainer(
75
+ "Matrix renderer requires SELECT variables named 'row', 'column', and 'value'."
76
+ ));
77
+ return container;
78
+ }
79
+
80
+ const stylesheet = compileMatrixStylesheet(result.options);
81
+ const rowColumnLabel = extractRowColumnLabel(result.options);
82
+ const model = this.buildMatrixModel(payloadRows, rowIndex, columnIndex, valueIndex);
83
+ container.appendChild(this.renderMatrix(model, stylesheet, rowColumnLabel));
84
+ return container;
85
+ }
86
+
87
+ private renderMatrix(
88
+ model: MatrixModel,
89
+ stylesheet: ReadonlyArray<CompiledMatrixStyleRule>,
90
+ rowColumnLabel: string
91
+ ): HTMLElement {
92
+ const root = document.createElement('div');
93
+ root.className = 'table-root graph-root matrix-root';
94
+
95
+ const controls = document.createElement('div');
96
+ controls.className = 'table-controls';
97
+ const controlsLeft = document.createElement('div');
98
+ controlsLeft.className = 'table-controls-left';
99
+ const controlsRight = document.createElement('div');
100
+ controlsRight.className = 'table-controls-right';
101
+
102
+ const searchInput = document.createElement('input');
103
+ searchInput.type = 'search';
104
+ searchInput.className = 'tree-filter';
105
+ searchInput.placeholder = 'Filter...';
106
+ controlsRight.appendChild(searchInput);
107
+
108
+ const downloadButton = createDownloadButton();
109
+ controlsRight.appendChild(downloadButton);
110
+
111
+ controls.appendChild(controlsLeft);
112
+ controls.appendChild(controlsRight);
113
+ root.appendChild(controls);
114
+
115
+ const wrapper = document.createElement('div');
116
+ wrapper.className = 'table-results-table-container hover-highlight';
117
+ const table = document.createElement('div');
118
+ table.className = 'table-results-table';
119
+ const headerRow = document.createElement('div');
120
+ headerRow.className = 'table-header-row';
121
+ const body = document.createElement('div');
122
+ body.className = 'table-body';
123
+ wrapper.appendChild(table);
124
+ root.appendChild(wrapper);
125
+
126
+ const footer = document.createElement('div');
127
+ footer.className = 'table-footer';
128
+ const info = document.createElement('div');
129
+ info.className = 'table-info-bar';
130
+ footer.appendChild(info);
131
+ root.appendChild(footer);
132
+
133
+ const state = {
134
+ search: '',
135
+ };
136
+
137
+ searchInput.addEventListener('input', () => {
138
+ state.search = searchInput.value.trim().toLowerCase();
139
+ renderMatrixPage();
140
+ });
141
+
142
+ downloadButton.addEventListener('click', () => {
143
+ const visibility = this.computeVisibility(model, state.search);
144
+ const csv = this.toCsv(model, visibility.visibleRows, visibility.visibleColumns, rowColumnLabel);
145
+ this.requestTextFileDownload(csv, 'matrix', 'csv');
146
+ });
147
+
148
+ const renderMatrixPage = () => {
149
+ const visibility = this.computeVisibility(model, state.search);
150
+ const visibleRows = visibility.visibleRows;
151
+ const visibleColumns = visibility.visibleColumns;
152
+
153
+ headerRow.innerHTML = '';
154
+ body.innerHTML = '';
155
+
156
+ const gridTemplateColumns = this.buildGridTemplate(visibleColumns.length + 1);
157
+ headerRow.style.gridTemplateColumns = gridTemplateColumns;
158
+
159
+ const topLeft = document.createElement('div');
160
+ topLeft.className = 'table-header-cell';
161
+ topLeft.textContent = rowColumnLabel;
162
+ this.applyStylesheet(topLeft, undefined, stylesheet, [
163
+ { kind: 'header', context: { name: rowColumnLabel } }
164
+ ]);
165
+ headerRow.appendChild(topLeft);
166
+
167
+ for (const [columnIndex, columnName] of visibleColumns.entries()) {
168
+ const th = document.createElement('div');
169
+ th.className = 'table-header-cell';
170
+ const label = this.renderHeaderLabel(columnName);
171
+ th.appendChild(label);
172
+ this.applyStylesheet(th, undefined, stylesheet, [
173
+ { kind: 'column', context: { index: columnIndex, name: columnName } }
174
+ ]);
175
+ headerRow.appendChild(th);
176
+ }
177
+
178
+ let rowCounter = 0;
179
+ for (const rowName of visibleRows) {
180
+ const rowElement = document.createElement('div');
181
+ rowElement.className = 'table-row';
182
+ rowElement.style.gridTemplateColumns = gridTemplateColumns;
183
+
184
+ const rowHeaderCell = document.createElement('div');
185
+ rowHeaderCell.className = 'table-cell';
186
+ const rowHeaderValue = this.renderValueParts([rowName]);
187
+ rowHeaderCell.appendChild(rowHeaderValue);
188
+ const rowContext: MatrixRowContext = { index: rowCounter, name: rowName };
189
+ this.applyStylesheet(rowHeaderCell, rowHeaderValue, stylesheet, [
190
+ { kind: 'row', context: rowContext },
191
+ { kind: 'cell', context: { row: rowName, col: 'row', rowIndex: rowCounter, colIndex: -1, value: rowName } }
192
+ ]);
193
+ rowElement.appendChild(rowHeaderCell);
194
+
195
+ for (const [columnIndex, columnName] of visibleColumns.entries()) {
196
+ const key = buildMatrixKey(rowName, columnName);
197
+ const record = model.cells.get(key);
198
+ const values = record?.values ?? [];
199
+ const displayValue = values.join(' ');
200
+ const columnContext: MatrixColumnContext = { index: columnIndex, name: columnName };
201
+
202
+ const td = document.createElement('div');
203
+ td.className = 'table-cell';
204
+ const valueElement = this.renderValueParts(values);
205
+ td.appendChild(valueElement);
206
+
207
+ const cellContext: MatrixCellContext = {
208
+ row: rowName,
209
+ col: columnName,
210
+ rowIndex: rowCounter,
211
+ colIndex: columnIndex,
212
+ value: displayValue,
213
+ datatype: undefined,
214
+ lang: undefined,
215
+ };
216
+ this.applyStylesheet(td, valueElement, stylesheet, [
217
+ { kind: 'row', context: rowContext },
218
+ { kind: 'column', context: columnContext },
219
+ { kind: 'cell', context: cellContext },
220
+ ]);
221
+ rowElement.appendChild(td);
222
+ }
223
+
224
+ body.appendChild(rowElement);
225
+ rowCounter += 1;
226
+ }
227
+
228
+ table.replaceChildren(headerRow, body);
229
+ info.textContent = `Showing rows ${visibleRows.length}/${model.rowKeys.length}, columns ${visibleColumns.length}/${model.columnKeys.length}`;
230
+ };
231
+
232
+ renderMatrixPage();
233
+ return root;
234
+ }
235
+
236
+ private buildGridTemplate(totalColumnCount: number): string {
237
+ const columns: string[] = [];
238
+ for (let index = 0; index < totalColumnCount; index += 1) {
239
+ columns.push('minmax(0, 1fr)');
240
+ }
241
+ return columns.join(' ');
242
+ }
243
+
244
+ private buildMatrixModel(rows: string[][], rowIndex: number, columnIndex: number, valueIndex: number): MatrixModel {
245
+ const rowKeys: string[] = [];
246
+ const columnKeys: string[] = [];
247
+ const seenRows = new Set<string>();
248
+ const seenColumns = new Set<string>();
249
+ const cells = new Map<string, MatrixCellRecord>();
250
+
251
+ for (const row of rows) {
252
+ const rowKey = row[rowIndex] ?? '';
253
+ const columnKey = row[columnIndex] ?? '';
254
+ const value = row[valueIndex] ?? '';
255
+ if (!rowKey || !columnKey) {
256
+ continue;
257
+ }
258
+ if (!seenRows.has(rowKey)) {
259
+ seenRows.add(rowKey);
260
+ rowKeys.push(rowKey);
261
+ }
262
+ if (!seenColumns.has(columnKey)) {
263
+ seenColumns.add(columnKey);
264
+ columnKeys.push(columnKey);
265
+ }
266
+ const key = buildMatrixKey(rowKey, columnKey);
267
+ const record = cells.get(key) ?? { values: [] };
268
+ record.values.push(value);
269
+ cells.set(key, record);
270
+ }
271
+
272
+ return { rowKeys, columnKeys, cells };
273
+ }
274
+
275
+ private renderValueParts(values: string[]): HTMLElement {
276
+ const value = document.createElement('span');
277
+ value.className = 'table-value';
278
+ const normalized = values.filter((entry) => entry.length > 0);
279
+ if (normalized.length === 0) {
280
+ value.textContent = '';
281
+ return value;
282
+ }
283
+ for (const rawPart of normalized) {
284
+ appendTokenizedValueParts(value, rawPart, 'table-value-part');
285
+ }
286
+ return value;
287
+ }
288
+
289
+ private renderHeaderLabel(raw: string): HTMLElement {
290
+ const label = document.createElement('span');
291
+ label.className = 'table-header-label';
292
+ label.style.fontWeight = '400';
293
+ if (!isIriValue(raw)) {
294
+ label.textContent = raw;
295
+ return label;
296
+ }
297
+ label.appendChild(createWikiLinkElement(raw, shortLabelFromIri(raw)));
298
+ return label;
299
+ }
300
+
301
+ private applyStylesheet(
302
+ cellElement: HTMLElement,
303
+ valueElement: HTMLElement | undefined,
304
+ rules: ReadonlyArray<CompiledMatrixStyleRule>,
305
+ contexts: ReadonlyArray<MatrixSelectorContext>
306
+ ): void {
307
+ for (const rule of rules) {
308
+ if (rule.target !== 'value') {
309
+ if (!matchesMatrixRule(rule, contexts)) {
310
+ continue;
311
+ }
312
+ for (const [property, cssValue] of Object.entries(rule.style)) {
313
+ cellElement.style.setProperty(property, cssValue);
314
+ }
315
+ continue;
316
+ }
317
+ if (!valueElement) {
318
+ continue;
319
+ }
320
+ const valueTargets = resolveValueTargets(valueElement);
321
+ for (const target of valueTargets) {
322
+ const valueOverride = target.dataset.value ?? target.textContent ?? '';
323
+ if (!matchesMatrixRule(rule, contexts, valueOverride)) {
324
+ continue;
325
+ }
326
+ for (const [property, cssValue] of Object.entries(rule.style)) {
327
+ target.style.setProperty(property, cssValue);
328
+ }
329
+ }
330
+ }
331
+ }
332
+
333
+ private computeVisibility(model: MatrixModel, search: string): { visibleRows: string[]; visibleColumns: string[] } {
334
+ if (!search) {
335
+ return {
336
+ visibleRows: model.rowKeys.slice(),
337
+ visibleColumns: model.columnKeys.slice(),
338
+ };
339
+ }
340
+
341
+ const allRows = model.rowKeys.slice();
342
+ const allColumns = model.columnKeys.slice();
343
+ const matchedRows = new Set<string>();
344
+ const matchedColumns = new Set<string>();
345
+ let rowLabelMatch = false;
346
+ let columnLabelMatch = false;
347
+ let cellMatch = false;
348
+
349
+ for (const rowName of model.rowKeys) {
350
+ if (rowName.toLowerCase().includes(search)) {
351
+ matchedRows.add(rowName);
352
+ rowLabelMatch = true;
353
+ }
354
+ }
355
+ for (const columnName of model.columnKeys) {
356
+ if (columnName.toLowerCase().includes(search)) {
357
+ matchedColumns.add(columnName);
358
+ columnLabelMatch = true;
359
+ }
360
+ }
361
+
362
+ for (const rowName of model.rowKeys) {
363
+ for (const columnName of model.columnKeys) {
364
+ const record = model.cells.get(buildMatrixKey(rowName, columnName));
365
+ if (!record) {
366
+ continue;
367
+ }
368
+ const cellText = record.values.join(' ').toLowerCase();
369
+ if (cellText.includes(search)) {
370
+ matchedRows.add(rowName);
371
+ matchedColumns.add(columnName);
372
+ cellMatch = true;
373
+ }
374
+ }
375
+ }
376
+
377
+ if (!rowLabelMatch && !columnLabelMatch && !cellMatch) {
378
+ return { visibleRows: [], visibleColumns: [] };
379
+ }
380
+ if (rowLabelMatch && !columnLabelMatch && !cellMatch) {
381
+ return {
382
+ visibleRows: allRows.filter((rowName) => matchedRows.has(rowName)),
383
+ visibleColumns: allColumns,
384
+ };
385
+ }
386
+ if (!rowLabelMatch && columnLabelMatch && !cellMatch) {
387
+ return {
388
+ visibleRows: allRows,
389
+ visibleColumns: allColumns.filter((columnName) => matchedColumns.has(columnName)),
390
+ };
391
+ }
392
+
393
+ const visibleRows = model.rowKeys.filter((rowName) => matchedRows.has(rowName));
394
+ const visibleColumns = model.columnKeys.filter((columnName) => matchedColumns.has(columnName));
395
+ return { visibleRows, visibleColumns };
396
+ }
397
+
398
+ private toCsv(model: MatrixModel, visibleRows: string[], visibleColumns: string[], rowColumnLabel: string): string {
399
+ const escapeCell = (value: string): string => `"${value.replace(/"/g, '""')}"`;
400
+ const lines: string[] = [];
401
+ lines.push([escapeCell(rowColumnLabel), ...visibleColumns.map(escapeCell)].join(','));
402
+ for (const rowName of visibleRows) {
403
+ const row: string[] = [escapeCell(rowName)];
404
+ for (const columnName of visibleColumns) {
405
+ const record = model.cells.get(buildMatrixKey(rowName, columnName));
406
+ row.push(escapeCell((record?.values ?? []).join(' ')));
407
+ }
408
+ lines.push(row.join(','));
409
+ }
410
+ return lines.join('\n');
411
+ }
412
+ }
413
+
414
+ function extractRowColumnLabel(options: Record<string, unknown> | undefined): string {
415
+ const raw = options?.rowColumnLabel;
416
+ if (typeof raw !== 'string') {
417
+ return 'row/column';
418
+ }
419
+ const trimmed = raw.trim();
420
+ return trimmed.length > 0 ? trimmed : 'row/column';
421
+ }
422
+
423
+ function buildMatrixKey(rowKey: string, columnKey: string): string {
424
+ return `${rowKey}\u0000${columnKey}`;
425
+ }
426
+
427
+ function createDownloadButton(): HTMLButtonElement {
428
+ const downloadButton = document.createElement('button');
429
+ downloadButton.className = 'tree-download-btn';
430
+ downloadButton.title = 'Download CSV';
431
+ downloadButton.setAttribute('aria-label', 'Download CSV');
432
+ const iconSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
433
+ iconSvg.setAttribute('viewBox', '0 0 24 24');
434
+ iconSvg.setAttribute('width', '20');
435
+ iconSvg.setAttribute('height', '20');
436
+ iconSvg.setAttribute('aria-hidden', 'true');
437
+ iconSvg.setAttribute('focusable', 'false');
438
+ iconSvg.style.fill = 'currentColor';
439
+ const iconPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
440
+ iconPath.setAttribute('d', 'M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z');
441
+ iconSvg.appendChild(iconPath);
442
+ downloadButton.appendChild(iconSvg);
443
+ return downloadButton;
444
+ }
445
+
446
+ function compileMatrixStylesheet(options: Record<string, unknown> | undefined): CompiledMatrixStyleRule[] {
447
+ const rawStylesheet = options?.stylesheet;
448
+ if (!Array.isArray(rawStylesheet)) {
449
+ return [];
450
+ }
451
+ const compiled: CompiledMatrixStyleRule[] = [];
452
+ for (const rawRule of rawStylesheet) {
453
+ if (!isRecord(rawRule)) {
454
+ continue;
455
+ }
456
+ const selectors = parseSelectors(rawRule.selector);
457
+ if (selectors.length === 0) {
458
+ continue;
459
+ }
460
+ const style = normalizeStyle(rawRule.style);
461
+ if (Object.keys(style).length === 0) {
462
+ continue;
463
+ }
464
+ const target = rawRule.target === 'value' ? 'value' : 'cell';
465
+ compiled.push({ selectors, target, style });
466
+ }
467
+ return compiled;
468
+ }
469
+
470
+ function parseSelectors(rawSelector: unknown): MatrixSelector[] {
471
+ if (typeof rawSelector !== 'string') {
472
+ return [];
473
+ }
474
+ const parts = splitSelectors(rawSelector);
475
+ const selectors: MatrixSelector[] = [];
476
+ for (const part of parts) {
477
+ const parsed = parseSelector(part);
478
+ if (parsed) {
479
+ selectors.push(parsed);
480
+ }
481
+ }
482
+ return selectors;
483
+ }
484
+
485
+ function splitSelectors(rawSelector: string): string[] {
486
+ const parts: string[] = [];
487
+ let current = '';
488
+ let bracketDepth = 0;
489
+ let quote: '"' | '\'' | undefined;
490
+ for (let index = 0; index < rawSelector.length; index += 1) {
491
+ const char = rawSelector[index];
492
+ if (quote) {
493
+ current += char;
494
+ if (char === quote && rawSelector[index - 1] !== '\\') {
495
+ quote = undefined;
496
+ }
497
+ continue;
498
+ }
499
+ if (char === '"' || char === '\'') {
500
+ quote = char;
501
+ current += char;
502
+ continue;
503
+ }
504
+ if (char === '[') {
505
+ bracketDepth += 1;
506
+ current += char;
507
+ continue;
508
+ }
509
+ if (char === ']' && bracketDepth > 0) {
510
+ bracketDepth -= 1;
511
+ current += char;
512
+ continue;
513
+ }
514
+ if (char === ',' && bracketDepth === 0) {
515
+ parts.push(current.trim());
516
+ current = '';
517
+ continue;
518
+ }
519
+ current += char;
520
+ }
521
+ if (current.trim()) {
522
+ parts.push(current.trim());
523
+ }
524
+ return parts;
525
+ }
526
+
527
+ function parseSelector(raw: string): MatrixSelector | undefined {
528
+ const trimmed = raw.trim();
529
+ if (!trimmed) {
530
+ return undefined;
531
+ }
532
+ const bracketStart = trimmed.indexOf('[');
533
+ if (bracketStart === -1) {
534
+ return toSelector(trimmed, undefined);
535
+ }
536
+ if (!trimmed.endsWith(']')) {
537
+ return undefined;
538
+ }
539
+ const kind = trimmed.slice(0, bracketStart).trim();
540
+ const condition = trimmed.slice(bracketStart + 1, -1).trim();
541
+ return toSelector(kind, condition || undefined);
542
+ }
543
+
544
+ function toSelector(kind: string, condition: string | undefined): MatrixSelector | undefined {
545
+ const normalized = kind.trim().toLowerCase();
546
+ if (normalized !== 'row' && normalized !== 'column' && normalized !== 'cell' && normalized !== 'header') {
547
+ return undefined;
548
+ }
549
+ return { kind: normalized, condition };
550
+ }
551
+
552
+ function normalizeStyle(rawStyle: unknown): Record<string, string> {
553
+ if (!isRecord(rawStyle)) {
554
+ return {};
555
+ }
556
+ const style: Record<string, string> = {};
557
+ for (const [property, rawValue] of Object.entries(rawStyle)) {
558
+ if (!property.trim()) {
559
+ continue;
560
+ }
561
+ if (rawValue === undefined || rawValue === null) {
562
+ continue;
563
+ }
564
+ style[property] = String(rawValue);
565
+ }
566
+ return style;
567
+ }
568
+
569
+ function matchesMatrixRule(
570
+ rule: CompiledMatrixStyleRule,
571
+ contexts: ReadonlyArray<MatrixSelectorContext>,
572
+ cellValueOverride?: string
573
+ ): boolean {
574
+ return rule.selectors.some((selector) => {
575
+ const contextEntry = contexts.find((entry) => entry.kind === selector.kind);
576
+ if (!contextEntry) {
577
+ return false;
578
+ }
579
+ if (selector.kind === 'cell' && cellValueOverride !== undefined) {
580
+ const cellContext = {
581
+ ...(contextEntry.context as MatrixCellContext),
582
+ value: cellValueOverride,
583
+ };
584
+ return evaluateCondition(selector.condition, cellContext);
585
+ }
586
+ return evaluateCondition(selector.condition, contextEntry.context);
587
+ });
588
+ }
589
+
590
+ function evaluateCondition(condition: string | undefined, context: object): boolean {
591
+ if (!condition) {
592
+ return true;
593
+ }
594
+ try {
595
+ const scope = Object.assign(Object.create(null), context) as Record<string, unknown>;
596
+ const keys = Object.keys(scope);
597
+ const values = keys.map((key) => scope[key]);
598
+ // eslint-disable-next-line no-new-func
599
+ const evaluator = new Function(...keys, `"use strict"; return (${condition});`);
600
+ return Boolean(evaluator(...values));
601
+ } catch {
602
+ return false;
603
+ }
604
+ }
605
+
606
+ function resolveValueTargets(valueElement: HTMLElement): HTMLElement[] {
607
+ const parts = Array.from(valueElement.querySelectorAll<HTMLElement>('.table-value-part'));
608
+ if (parts.length > 0) {
609
+ return parts;
610
+ }
611
+ return [valueElement];
612
+ }
613
+
614
+ function isRecord(value: unknown): value is Record<string, unknown> {
615
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
616
+ }
@@ -0,0 +1,18 @@
1
+ // Copyright (c) 2026 Modelware. All rights reserved.
2
+
3
+ import { BaseMarkdownBlockRenderer } from './renderer.js';
4
+ import type { MdBlockExecutionResult } from './types.js';
5
+
6
+ export class MessageMarkdownBlockRenderer extends BaseMarkdownBlockRenderer {
7
+ canRender(_result: MdBlockExecutionResult): boolean {
8
+ return true;
9
+ }
10
+
11
+ render(result: MdBlockExecutionResult): HTMLElement {
12
+ const container = this.createResultContainer(result.status);
13
+ const text = result.message ?? (result.status === 'unimplemented' ? 'Not implemented.' : 'Execution failed.');
14
+ container.appendChild(this.createMessageContainer(text));
15
+ return container;
16
+ }
17
+ }
18
+
@@ -0,0 +1,45 @@
1
+ // Copyright (c) 2026 Modelware. All rights reserved.
2
+
3
+ import { ChartMarkdownBlockRenderer } from './chart-renderer.js';
4
+ import { DiagramMarkdownBlockRenderer } from './diagram-renderer.js';
5
+ import { GraphMarkdownBlockRenderer } from './graph-renderer.js';
6
+ import { ListMarkdownBlockRenderer } from './list-renderer.js';
7
+ import { MatrixMarkdownBlockRenderer } from './matrix-renderer.js';
8
+ import { MessageMarkdownBlockRenderer } from './message-renderer.js';
9
+ import type { MarkdownBlockRenderer } from './renderer.js';
10
+ import { TableEditorMarkdownBlockRenderer } from './table-editor-renderer.js';
11
+ import { TableMarkdownBlockRenderer } from './table-renderer.js';
12
+ import { TextMarkdownBlockRenderer } from './text-renderer.js';
13
+ import { TreeMarkdownBlockRenderer } from './tree-renderer.js';
14
+ import type { MdBlockExecutionResult } from './types.js';
15
+
16
+ export class MarkdownRendererRegistry {
17
+ constructor(private readonly renderers: ReadonlyArray<MarkdownBlockRenderer>) {}
18
+
19
+ render(result: MdBlockExecutionResult): HTMLElement {
20
+ const renderer = this.renderers.find((candidate) => candidate.canRender(result));
21
+ if (!renderer) {
22
+ throw new Error(`No renderer registered for block '${result.blockId}'.`);
23
+ }
24
+ return renderer.render(result);
25
+ }
26
+ }
27
+
28
+ export function createCoreMarkdownRenderers(): MarkdownBlockRenderer[] {
29
+ return [
30
+ new MatrixMarkdownBlockRenderer(),
31
+ new TreeMarkdownBlockRenderer(),
32
+ new TableEditorMarkdownBlockRenderer(),
33
+ new TableMarkdownBlockRenderer(),
34
+ new ListMarkdownBlockRenderer(),
35
+ new TextMarkdownBlockRenderer(),
36
+ new GraphMarkdownBlockRenderer(),
37
+ new DiagramMarkdownBlockRenderer(),
38
+ new ChartMarkdownBlockRenderer(),
39
+ new MessageMarkdownBlockRenderer(),
40
+ ];
41
+ }
42
+
43
+ export function createMarkdownRendererRegistry(): MarkdownRendererRegistry {
44
+ return new MarkdownRendererRegistry(createCoreMarkdownRenderers());
45
+ }