@mobile-reality/mdma-renderer-vanilla 0.2.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 (96) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +127 -0
  3. package/dist/components/ApprovalGateRenderer.d.ts +2 -0
  4. package/dist/components/ApprovalGateRenderer.d.ts.map +1 -0
  5. package/dist/components/ApprovalGateRenderer.js +43 -0
  6. package/dist/components/ApprovalGateRenderer.js.map +1 -0
  7. package/dist/components/ButtonRenderer.d.ts +2 -0
  8. package/dist/components/ButtonRenderer.d.ts.map +1 -0
  9. package/dist/components/ButtonRenderer.js +23 -0
  10. package/dist/components/ButtonRenderer.js.map +1 -0
  11. package/dist/components/CalloutRenderer.d.ts +2 -0
  12. package/dist/components/CalloutRenderer.d.ts.map +1 -0
  13. package/dist/components/CalloutRenderer.js +33 -0
  14. package/dist/components/CalloutRenderer.js.map +1 -0
  15. package/dist/components/ChartRenderer.d.ts +3 -0
  16. package/dist/components/ChartRenderer.d.ts.map +1 -0
  17. package/dist/components/ChartRenderer.js +52 -0
  18. package/dist/components/ChartRenderer.js.map +1 -0
  19. package/dist/components/CustomRenderer.d.ts +8 -0
  20. package/dist/components/CustomRenderer.d.ts.map +1 -0
  21. package/dist/components/CustomRenderer.js +51 -0
  22. package/dist/components/CustomRenderer.js.map +1 -0
  23. package/dist/components/FormRenderer.d.ts +8 -0
  24. package/dist/components/FormRenderer.d.ts.map +1 -0
  25. package/dist/components/FormRenderer.js +149 -0
  26. package/dist/components/FormRenderer.js.map +1 -0
  27. package/dist/components/MdmaBlockLoading.d.ts +11 -0
  28. package/dist/components/MdmaBlockLoading.d.ts.map +1 -0
  29. package/dist/components/MdmaBlockLoading.js +26 -0
  30. package/dist/components/MdmaBlockLoading.js.map +1 -0
  31. package/dist/components/TableRenderer.d.ts +2 -0
  32. package/dist/components/TableRenderer.d.ts.map +1 -0
  33. package/dist/components/TableRenderer.js +66 -0
  34. package/dist/components/TableRenderer.js.map +1 -0
  35. package/dist/components/TasklistRenderer.d.ts +7 -0
  36. package/dist/components/TasklistRenderer.d.ts.map +1 -0
  37. package/dist/components/TasklistRenderer.js +80 -0
  38. package/dist/components/TasklistRenderer.js.map +1 -0
  39. package/dist/components/ThinkingRenderer.d.ts +3 -0
  40. package/dist/components/ThinkingRenderer.d.ts.map +1 -0
  41. package/dist/components/ThinkingRenderer.js +36 -0
  42. package/dist/components/ThinkingRenderer.js.map +1 -0
  43. package/dist/components/WebhookRenderer.d.ts +2 -0
  44. package/dist/components/WebhookRenderer.d.ts.map +1 -0
  45. package/dist/components/WebhookRenderer.js +33 -0
  46. package/dist/components/WebhookRenderer.js.map +1 -0
  47. package/dist/components/form/default-elements.d.ts +10 -0
  48. package/dist/components/form/default-elements.d.ts.map +1 -0
  49. package/dist/components/form/default-elements.js +198 -0
  50. package/dist/components/form/default-elements.js.map +1 -0
  51. package/dist/components/form/element-props.d.ts +58 -0
  52. package/dist/components/form/element-props.d.ts.map +1 -0
  53. package/dist/components/form/element-props.js +2 -0
  54. package/dist/components/form/element-props.js.map +1 -0
  55. package/dist/context/render-context.d.ts +40 -0
  56. package/dist/context/render-context.d.ts.map +1 -0
  57. package/dist/context/render-context.js +7 -0
  58. package/dist/context/render-context.js.map +1 -0
  59. package/dist/document/mount-block.d.ts +19 -0
  60. package/dist/document/mount-block.d.ts.map +1 -0
  61. package/dist/document/mount-block.js +31 -0
  62. package/dist/document/mount-block.js.map +1 -0
  63. package/dist/document/mount-document.d.ts +33 -0
  64. package/dist/document/mount-document.d.ts.map +1 -0
  65. package/dist/document/mount-document.js +177 -0
  66. package/dist/document/mount-document.js.map +1 -0
  67. package/dist/document/partial-yaml.d.ts +10 -0
  68. package/dist/document/partial-yaml.d.ts.map +1 -0
  69. package/dist/document/partial-yaml.js +48 -0
  70. package/dist/document/partial-yaml.js.map +1 -0
  71. package/dist/dom/el.d.ts +26 -0
  72. package/dist/dom/el.d.ts.map +1 -0
  73. package/dist/dom/el.js +59 -0
  74. package/dist/dom/el.js.map +1 -0
  75. package/dist/index.d.ts +23 -0
  76. package/dist/index.d.ts.map +1 -0
  77. package/dist/index.js +22 -0
  78. package/dist/index.js.map +1 -0
  79. package/dist/markdown/render-mdast.d.ts +21 -0
  80. package/dist/markdown/render-mdast.d.ts.map +1 -0
  81. package/dist/markdown/render-mdast.js +89 -0
  82. package/dist/markdown/render-mdast.js.map +1 -0
  83. package/dist/renderers/renderer-props.d.ts +41 -0
  84. package/dist/renderers/renderer-props.d.ts.map +1 -0
  85. package/dist/renderers/renderer-props.js +31 -0
  86. package/dist/renderers/renderer-props.js.map +1 -0
  87. package/dist/renderers/renderer-registry.d.ts +14 -0
  88. package/dist/renderers/renderer-registry.d.ts.map +1 -0
  89. package/dist/renderers/renderer-registry.js +48 -0
  90. package/dist/renderers/renderer-registry.js.map +1 -0
  91. package/dist/theme/theme.d.ts +68 -0
  92. package/dist/theme/theme.d.ts.map +1 -0
  93. package/dist/theme/theme.js +120 -0
  94. package/dist/theme/theme.js.map +1 -0
  95. package/package.json +56 -0
  96. package/styles.css +1045 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Marcin Sadowski
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,127 @@
1
+ # @mobile-reality/mdma-renderer-vanilla
2
+
3
+ Render MDMA documents with **no framework** — plain DOM, no React, no Vue, no build step required.
4
+ Same components, same `.mdma-*` class names, and the same `styles.css` as
5
+ [`renderer-react`](../renderer-react) and [`renderer-vue`](../renderer-vue), so a theme is portable
6
+ across all three.
7
+
8
+ Reach for this when the host has no framework: a static page, a `<script>` tag, a CMS template, a
9
+ web-component wrapper, or an app whose framework MDMA shouldn't dictate. If you already have React
10
+ or Vue, use those renderers — they'll be less code for you.
11
+
12
+ ```sh
13
+ pnpm add @mobile-reality/mdma-renderer-vanilla
14
+ ```
15
+
16
+ ## Quick start
17
+
18
+ ```ts
19
+ import { unified } from 'unified';
20
+ import remarkParse from 'remark-parse';
21
+ import remarkGfm from 'remark-gfm';
22
+ import { remarkMdma } from '@mobile-reality/mdma-parser';
23
+ import { AttachableRegistry, createDocumentStore } from '@mobile-reality/mdma-runtime';
24
+ import { registerAllCoreAttachables } from '@mobile-reality/mdma-attachables-core';
25
+ import { mountMdmaDocument } from '@mobile-reality/mdma-renderer-vanilla';
26
+ import '@mobile-reality/mdma-renderer-vanilla/styles.css';
27
+
28
+ const processor = unified().use(remarkParse).use(remarkGfm).use(remarkMdma, {});
29
+ const ast = await processor.run(processor.parse(markdown), markdown);
30
+
31
+ const registry = new AttachableRegistry();
32
+ registerAllCoreAttachables(registry);
33
+ const store = createDocumentStore(ast, { registry });
34
+
35
+ const doc = mountMdmaDocument(document.getElementById('app')!, { ast, store, theme: 'auto' });
36
+ ```
37
+
38
+ `mountMdmaDocument` returns a handle:
39
+
40
+ ```ts
41
+ doc.el; // the .mdma-document root
42
+ doc.update({ ast: next }); // feed a re-parse (see Streaming)
43
+ doc.destroy(); // unsubscribe from the store and detach
44
+ ```
45
+
46
+ It subscribes to the store itself, so a dispatch from any component re-renders the document without
47
+ you wiring anything.
48
+
49
+ ## Streaming
50
+
51
+ This is the part a framework normally does for you, and the reason this package is more than a pile
52
+ of `document.createElement` calls.
53
+
54
+ A streamed model reply re-parses on **every chunk**, so `update({ ast })` gets a brand-new AST many
55
+ times a second — while the user may already be typing into a form the document drew. There is no
56
+ virtual DOM here. Instead each renderer owns its element and is re-fed props, so it decides what to
57
+ touch:
58
+
59
+ - **Form inputs are updated in place**, and `setInputValue` refuses to write to a focused field —
60
+ your half-typed value and caret position survive every re-parse.
61
+ - **Nodes are only moved when the order actually changed.** Re-appending a node that is already in
62
+ position would detach it, and detaching blurs a focused input.
63
+ - **A block that has parsed once is kept.** Mid-stream a complete fence can briefly revert to a
64
+ pending code node; without the cache the document would flicker back to a loading skeleton.
65
+ - **Blocks are keyed by id *and* type**, so a truncated type (`approval-gat` → `approval-gate`)
66
+ re-mounts into the right renderer instead of updating the wrong one.
67
+ - **A `thinking` block streams its content live** rather than sitting behind a skeleton.
68
+
69
+ Components with nothing worth preserving simply rebuild; `tests/streaming.test.ts` pins all of it.
70
+
71
+ ## Customization
72
+
73
+ ```ts
74
+ mountMdmaDocument(container, {
75
+ ast,
76
+ store,
77
+ theme: 'dark',
78
+ customizations: {
79
+ components: {
80
+ chart: MyChartRenderer, // replace a built-in renderer
81
+ form: { elements: { input: GlassInput } }, // or just one sub-element
82
+ },
83
+ customVariants: { 'graph-3d': Graph3D }, // draw `type: custom` blocks
84
+ dataSources: { countries: [{ label: 'Poland', value: 'pl' }] },
85
+ },
86
+ });
87
+ ```
88
+
89
+ A renderer is a function returning an instance — no class, no lifecycle to learn:
90
+
91
+ ```ts
92
+ import { el, type MdmaBlockRenderer } from '@mobile-reality/mdma-renderer-vanilla';
93
+
94
+ const MyChartRenderer: MdmaBlockRenderer = (initial) => {
95
+ const root = el('div', { class: 'my-chart' });
96
+ const instance = {
97
+ el: root,
98
+ update(props) { /* re-feed, mutate what changed */ },
99
+ destroy() { /* optional cleanup */ },
100
+ };
101
+ return instance;
102
+ };
103
+ ```
104
+
105
+ For the common case where nothing needs preserving, `stateless(props => element)` writes the
106
+ instance for you, and `withState(init, (props, state) => element)` keeps view state (a revealed PII
107
+ cell, a fired webhook) across rebuilds.
108
+
109
+ ## Theming
110
+
111
+ Identical to the other web renderers: `theme` takes `'light' | 'dark' | 'auto'` or a full
112
+ `MdmaTheme` token object, applied as `data-theme` or inline `--mdma-*` variables. `styles.css` is
113
+ byte-identical to the React package's and a test enforces that, so the three renderers can never
114
+ drift apart visually.
115
+
116
+ ## What's here
117
+
118
+ All ten core component types — `form`, `button`, `tasklist`, `table`, `callout`, `approval-gate`,
119
+ `webhook`, `chart`, `thinking`, `custom` — plus Markdown rendering, loading skeletons, element
120
+ overrides, and theming.
121
+
122
+ Two deliberate limits, matching the framework renderers: `chart` draws a table rather than a real
123
+ chart (override it with a charting library), and `webhook` routes the trigger rather than performing
124
+ the HTTP call.
125
+
126
+ Raw HTML in Markdown is rendered as **text**, never parsed into live markup — model output must not
127
+ become executable DOM.
@@ -0,0 +1,2 @@
1
+ export declare const ApprovalGateRenderer: import("../index.js").MdmaBlockRenderer;
2
+ //# sourceMappingURL=ApprovalGateRenderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ApprovalGateRenderer.d.ts","sourceRoot":"","sources":["../../src/components/ApprovalGateRenderer.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,oBAAoB,yCAsD/B,CAAC"}
@@ -0,0 +1,43 @@
1
+ import { el } from '../dom/el.js';
2
+ import { stateless } from '../renderers/renderer-props.js';
3
+ export const ApprovalGateRenderer = stateless(({ component, componentState, dispatch }) => {
4
+ if (component.type !== 'approval-gate')
5
+ return el('div');
6
+ const status = componentState?.values.status ?? 'pending';
7
+ return el('div', {
8
+ class: `mdma-approval-gate mdma-approval-gate--${status}`,
9
+ dataset: { 'component-id': component.id },
10
+ }, [
11
+ el('h3', { class: 'mdma-approval-gate-title' }, [component.title]),
12
+ component.description &&
13
+ el('p', { class: 'mdma-approval-gate-description' }, [component.description]),
14
+ el('div', { class: 'mdma-approval-gate-status' }, ['Status: ', el('strong', {}, [status])]),
15
+ status === 'pending' &&
16
+ el('div', { class: 'mdma-approval-gate-actions' }, [
17
+ el('button', {
18
+ type: 'button',
19
+ class: 'mdma-button mdma-button--primary',
20
+ on: {
21
+ click: () => dispatch({
22
+ type: 'APPROVAL_GRANTED',
23
+ componentId: component.id,
24
+ actor: { id: 'current-user' },
25
+ }),
26
+ },
27
+ }, ['Approve']),
28
+ el('button', {
29
+ type: 'button',
30
+ class: 'mdma-button mdma-button--danger',
31
+ on: {
32
+ click: () => dispatch({
33
+ type: 'APPROVAL_DENIED',
34
+ componentId: component.id,
35
+ actor: { id: 'current-user' },
36
+ reason: '',
37
+ }),
38
+ },
39
+ }, ['Deny']),
40
+ ]),
41
+ ]);
42
+ });
43
+ //# sourceMappingURL=ApprovalGateRenderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ApprovalGateRenderer.js","sourceRoot":"","sources":["../../src/components/ApprovalGateRenderer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAClC,OAAO,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAE3D,MAAM,CAAC,MAAM,oBAAoB,GAAG,SAAS,CAAC,CAAC,EAAE,SAAS,EAAE,cAAc,EAAE,QAAQ,EAAE,EAAE,EAAE;IACxF,IAAI,SAAS,CAAC,IAAI,KAAK,eAAe;QAAE,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC;IAEzD,MAAM,MAAM,GAAI,cAAc,EAAE,MAAM,CAAC,MAAiB,IAAI,SAAS,CAAC;IAEtE,OAAO,EAAE,CACP,KAAK,EACL;QACE,KAAK,EAAE,0CAA0C,MAAM,EAAE;QACzD,OAAO,EAAE,EAAE,cAAc,EAAE,SAAS,CAAC,EAAE,EAAE;KAC1C,EACD;QACE,EAAE,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,0BAA0B,EAAE,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QAClE,SAAS,CAAC,WAAW;YACnB,EAAE,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,gCAAgC,EAAE,EAAE,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;QAC/E,EAAE,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,2BAA2B,EAAE,EAAE,CAAC,UAAU,EAAE,EAAE,CAAC,QAAQ,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QAC3F,MAAM,KAAK,SAAS;YAClB,EAAE,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,4BAA4B,EAAE,EAAE;gBACjD,EAAE,CACA,QAAQ,EACR;oBACE,IAAI,EAAE,QAAQ;oBACd,KAAK,EAAE,kCAAkC;oBACzC,EAAE,EAAE;wBACF,KAAK,EAAE,GAAG,EAAE,CACV,QAAQ,CAAC;4BACP,IAAI,EAAE,kBAAkB;4BACxB,WAAW,EAAE,SAAS,CAAC,EAAE;4BACzB,KAAK,EAAE,EAAE,EAAE,EAAE,cAAc,EAAE;yBAC9B,CAAC;qBACL;iBACF,EACD,CAAC,SAAS,CAAC,CACZ;gBACD,EAAE,CACA,QAAQ,EACR;oBACE,IAAI,EAAE,QAAQ;oBACd,KAAK,EAAE,iCAAiC;oBACxC,EAAE,EAAE;wBACF,KAAK,EAAE,GAAG,EAAE,CACV,QAAQ,CAAC;4BACP,IAAI,EAAE,iBAAiB;4BACvB,WAAW,EAAE,SAAS,CAAC,EAAE;4BACzB,KAAK,EAAE,EAAE,EAAE,EAAE,cAAc,EAAE;4BAC7B,MAAM,EAAE,EAAE;yBACX,CAAC;qBACL;iBACF,EACD,CAAC,MAAM,CAAC,CACT;aACF,CAAC;KACL,CACF,CAAC;AACJ,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare const ButtonRenderer: import("../index.js").MdmaBlockRenderer;
2
+ //# sourceMappingURL=ButtonRenderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ButtonRenderer.d.ts","sourceRoot":"","sources":["../../src/components/ButtonRenderer.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,cAAc,yCAsBzB,CAAC"}
@@ -0,0 +1,23 @@
1
+ import { el } from '../dom/el.js';
2
+ import { stateless } from '../renderers/renderer-props.js';
3
+ export const ButtonRenderer = stateless(({ component, dispatch }) => {
4
+ if (component.type !== 'button')
5
+ return el('div');
6
+ return el('button', {
7
+ type: 'button',
8
+ class: `mdma-button mdma-button--${component.variant ?? 'primary'}`,
9
+ dataset: { 'component-id': component.id },
10
+ on: {
11
+ click: () => {
12
+ if (!component.onAction)
13
+ return;
14
+ dispatch({
15
+ type: 'ACTION_TRIGGERED',
16
+ componentId: component.id,
17
+ actionId: component.onAction,
18
+ });
19
+ },
20
+ },
21
+ }, [component.text]);
22
+ });
23
+ //# sourceMappingURL=ButtonRenderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ButtonRenderer.js","sourceRoot":"","sources":["../../src/components/ButtonRenderer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAClC,OAAO,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAE3D,MAAM,CAAC,MAAM,cAAc,GAAG,SAAS,CAAC,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE,EAAE;IAClE,IAAI,SAAS,CAAC,IAAI,KAAK,QAAQ;QAAE,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC;IAElD,OAAO,EAAE,CACP,QAAQ,EACR;QACE,IAAI,EAAE,QAAQ;QACd,KAAK,EAAE,4BAA4B,SAAS,CAAC,OAAO,IAAI,SAAS,EAAE;QACnE,OAAO,EAAE,EAAE,cAAc,EAAE,SAAS,CAAC,EAAE,EAAE;QACzC,EAAE,EAAE;YACF,KAAK,EAAE,GAAG,EAAE;gBACV,IAAI,CAAC,SAAS,CAAC,QAAQ;oBAAE,OAAO;gBAChC,QAAQ,CAAC;oBACP,IAAI,EAAE,kBAAkB;oBACxB,WAAW,EAAE,SAAS,CAAC,EAAE;oBACzB,QAAQ,EAAE,SAAS,CAAC,QAAQ;iBAC7B,CAAC,CAAC;YACL,CAAC;SACF;KACF,EACD,CAAC,SAAS,CAAC,IAAI,CAAC,CACjB,CAAC;AACJ,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare const CalloutRenderer: import("../index.js").MdmaBlockRenderer;
2
+ //# sourceMappingURL=CalloutRenderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CalloutRenderer.d.ts","sourceRoot":"","sources":["../../src/components/CalloutRenderer.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,eAAe,yCAsC1B,CAAC"}
@@ -0,0 +1,33 @@
1
+ import { el } from '../dom/el.js';
2
+ import { stateless } from '../renderers/renderer-props.js';
3
+ export const CalloutRenderer = stateless(({ component, componentState, dispatch }) => {
4
+ if (component.type !== 'callout')
5
+ return el('div');
6
+ // Dismissal is document state, so a dismissed callout stays dismissed across a
7
+ // re-parse of the same document. An empty span keeps the slot without markup.
8
+ if (componentState?.values.dismissed)
9
+ return el('span', { class: 'mdma-callout--dismissed' });
10
+ return el('div', {
11
+ class: `mdma-callout mdma-callout--${component.variant ?? 'info'}`,
12
+ dataset: { 'component-id': component.id },
13
+ role: 'alert',
14
+ }, [
15
+ component.title && el('strong', { class: 'mdma-callout-title' }, [component.title]),
16
+ el('p', { class: 'mdma-callout-content' }, [component.content]),
17
+ component.dismissible &&
18
+ el('button', {
19
+ type: 'button',
20
+ class: 'mdma-callout-dismiss',
21
+ 'aria-label': 'Dismiss',
22
+ on: {
23
+ click: () => dispatch({
24
+ type: 'FIELD_CHANGED',
25
+ componentId: component.id,
26
+ field: 'dismissed',
27
+ value: true,
28
+ }),
29
+ },
30
+ }, ['×']),
31
+ ]);
32
+ });
33
+ //# sourceMappingURL=CalloutRenderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CalloutRenderer.js","sourceRoot":"","sources":["../../src/components/CalloutRenderer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAClC,OAAO,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAE3D,MAAM,CAAC,MAAM,eAAe,GAAG,SAAS,CAAC,CAAC,EAAE,SAAS,EAAE,cAAc,EAAE,QAAQ,EAAE,EAAE,EAAE;IACnF,IAAI,SAAS,CAAC,IAAI,KAAK,SAAS;QAAE,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC;IAEnD,+EAA+E;IAC/E,8EAA8E;IAC9E,IAAI,cAAc,EAAE,MAAM,CAAC,SAAS;QAAE,OAAO,EAAE,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,yBAAyB,EAAE,CAAC,CAAC;IAE9F,OAAO,EAAE,CACP,KAAK,EACL;QACE,KAAK,EAAE,8BAA8B,SAAS,CAAC,OAAO,IAAI,MAAM,EAAE;QAClE,OAAO,EAAE,EAAE,cAAc,EAAE,SAAS,CAAC,EAAE,EAAE;QACzC,IAAI,EAAE,OAAO;KACd,EACD;QACE,SAAS,CAAC,KAAK,IAAI,EAAE,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,oBAAoB,EAAE,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QACnF,EAAE,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,sBAAsB,EAAE,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QAC/D,SAAS,CAAC,WAAW;YACnB,EAAE,CACA,QAAQ,EACR;gBACE,IAAI,EAAE,QAAQ;gBACd,KAAK,EAAE,sBAAsB;gBAC7B,YAAY,EAAE,SAAS;gBACvB,EAAE,EAAE;oBACF,KAAK,EAAE,GAAG,EAAE,CACV,QAAQ,CAAC;wBACP,IAAI,EAAE,eAAe;wBACrB,WAAW,EAAE,SAAS,CAAC,EAAE;wBACzB,KAAK,EAAE,WAAW;wBAClB,KAAK,EAAE,IAAI;qBACZ,CAAC;iBACL;aACF,EACD,CAAC,GAAG,CAAC,CACN;KACJ,CACF,CAAC;AACJ,CAAC,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ /** Draws chart data as a table. Override with a charting library via customizations. */
2
+ export declare const ChartRenderer: import("../index.js").MdmaBlockRenderer;
3
+ //# sourceMappingURL=ChartRenderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChartRenderer.d.ts","sourceRoot":"","sources":["../../src/components/ChartRenderer.ts"],"names":[],"mappings":"AA8BA,wFAAwF;AACxF,eAAO,MAAM,aAAa,yCA8CxB,CAAC"}
@@ -0,0 +1,52 @@
1
+ import { el } from '../dom/el.js';
2
+ import { stateless } from '../renderers/renderer-props.js';
3
+ function parseCsvData(raw) {
4
+ const lines = raw
5
+ .trim()
6
+ .split('\n')
7
+ .filter((line) => line.trim() !== '');
8
+ if (lines.length === 0)
9
+ return { headers: [], rows: [] };
10
+ const headers = lines[0].split(',').map((header) => header.trim());
11
+ const rows = lines.slice(1).map((line) => {
12
+ const values = line.split(',').map((value) => value.trim());
13
+ const row = {};
14
+ headers.forEach((header, index) => {
15
+ const value = values[index] ?? '';
16
+ const numeric = Number(value);
17
+ row[header] = value !== '' && !Number.isNaN(numeric) ? numeric : value;
18
+ });
19
+ return row;
20
+ });
21
+ return { headers, rows };
22
+ }
23
+ /** Draws chart data as a table. Override with a charting library via customizations. */
24
+ export const ChartRenderer = stateless(({ component, resolveBinding }) => {
25
+ if (component.type !== 'chart')
26
+ return el('div');
27
+ const raw = component.data;
28
+ let data = { headers: [], rows: [] };
29
+ if (typeof raw === 'string' && raw.startsWith('{{')) {
30
+ const resolved = resolveBinding(raw);
31
+ if (typeof resolved === 'string')
32
+ data = parseCsvData(resolved);
33
+ }
34
+ else if (typeof raw === 'string') {
35
+ data = parseCsvData(raw);
36
+ }
37
+ const label = component.label && el('div', { class: 'mdma-chart-label' }, [component.label]);
38
+ if (data.rows.length === 0) {
39
+ return el('div', { class: 'mdma-chart mdma-chart--empty', dataset: { 'component-id': component.id } }, [label, el('div', { class: 'mdma-chart-empty' }, ['No chart data'])]);
40
+ }
41
+ return el('div', { class: 'mdma-chart', dataset: { 'component-id': component.id } }, [
42
+ label,
43
+ el('div', { class: 'mdma-chart-variant' }, [`${component.variant ?? 'line'} chart`]),
44
+ el('table', { class: 'mdma-chart-data' }, [
45
+ el('thead', {}, [
46
+ el('tr', {}, data.headers.map((header) => el('th', {}, [header]))),
47
+ ]),
48
+ el('tbody', {}, data.rows.map((row) => el('tr', {}, data.headers.map((header) => el('td', {}, [String(row[header] ?? '')]))))),
49
+ ]),
50
+ ]);
51
+ });
52
+ //# sourceMappingURL=ChartRenderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChartRenderer.js","sourceRoot":"","sources":["../../src/components/ChartRenderer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import type { MdmaBlockRenderer } from '../renderers/renderer-props.js';
2
+ /**
3
+ * Draws a `custom` component with the variant registered under its `name`. An
4
+ * unregistered name degrades to an inline notice rather than crashing, mirroring
5
+ * how an unknown component type is handled.
6
+ */
7
+ export declare const CustomRenderer: MdmaBlockRenderer;
8
+ //# sourceMappingURL=CustomRenderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CustomRenderer.d.ts","sourceRoot":"","sources":["../../src/components/CustomRenderer.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,iBAAiB,EAGlB,MAAM,gCAAgC,CAAC;AAkBxC;;;;GAIG;AACH,eAAO,MAAM,cAAc,EAAE,iBAoC5B,CAAC"}
@@ -0,0 +1,51 @@
1
+ import { el } from '../dom/el.js';
2
+ const variantPropsOf = (component, props) => ({
3
+ component,
4
+ props: component.props,
5
+ componentState: props.componentState,
6
+ dispatch: props.dispatch,
7
+ resolveBinding: props.resolveBinding,
8
+ });
9
+ const unknown = (component) => el('div', { class: 'mdma-unknown-component', dataset: { 'component-id': component.id } }, [
10
+ `Unknown custom component: ${component.name}`,
11
+ ]);
12
+ /**
13
+ * Draws a `custom` component with the variant registered under its `name`. An
14
+ * unregistered name degrades to an inline notice rather than crashing, mirroring
15
+ * how an unknown component type is handled.
16
+ */
17
+ export const CustomRenderer = (initial) => {
18
+ let variant;
19
+ const create = (props) => {
20
+ if (props.component.type !== 'custom')
21
+ return el('div');
22
+ const component = props.component;
23
+ const factory = props.context.customVariants?.[component.name];
24
+ if (!factory) {
25
+ variant = undefined;
26
+ return unknown(component);
27
+ }
28
+ variant = factory(variantPropsOf(component, props));
29
+ return variant.el;
30
+ };
31
+ const instance = {
32
+ el: create(initial),
33
+ update(next) {
34
+ if (next.component.type !== 'custom')
35
+ return;
36
+ const component = next.component;
37
+ const factory = next.context.customVariants?.[component.name];
38
+ // A live variant keeps its own element and is re-fed props, so a custom
39
+ // component with internal state (a canvas, a chart) survives a re-render.
40
+ if (variant && factory) {
41
+ variant.update(variantPropsOf(component, next));
42
+ return;
43
+ }
44
+ const replacement = create(next);
45
+ instance.el.replaceWith(replacement);
46
+ instance.el = replacement;
47
+ },
48
+ };
49
+ return instance;
50
+ };
51
+ //# sourceMappingURL=CustomRenderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CustomRenderer.js","sourceRoot":"","sources":["../../src/components/CustomRenderer.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAOlC,MAAM,cAAc,GAAG,CACrB,SAA0B,EAC1B,KAA6B,EACT,EAAE,CAAC,CAAC;IACxB,SAAS;IACT,KAAK,EAAE,SAAS,CAAC,KAAK;IACtB,cAAc,EAAE,KAAK,CAAC,cAAc;IACpC,QAAQ,EAAE,KAAK,CAAC,QAAQ;IACxB,cAAc,EAAE,KAAK,CAAC,cAAc;CACrC,CAAC,CAAC;AAEH,MAAM,OAAO,GAAG,CAAC,SAA0B,EAAE,EAAE,CAC7C,EAAE,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,wBAAwB,EAAE,OAAO,EAAE,EAAE,cAAc,EAAE,SAAS,CAAC,EAAE,EAAE,EAAE,EAAE;IACxF,6BAA6B,SAAS,CAAC,IAAI,EAAE;CAC9C,CAAC,CAAC;AAEL;;;;GAIG;AACH,MAAM,CAAC,MAAM,cAAc,GAAsB,CAAC,OAAO,EAAE,EAAE;IAC3D,IAAI,OAAwD,CAAC;IAE7D,MAAM,MAAM,GAAG,CAAC,KAA6B,EAAe,EAAE;QAC5D,IAAI,KAAK,CAAC,SAAS,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC;QACxD,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,CAAC;QAClC,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,cAAc,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QAC/D,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO,GAAG,SAAS,CAAC;YACpB,OAAO,OAAO,CAAC,SAAS,CAAC,CAAC;QAC5B,CAAC;QACD,OAAO,GAAG,OAAO,CAAC,cAAc,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC;QACpD,OAAO,OAAO,CAAC,EAAE,CAAC;IACpB,CAAC,CAAC;IAEF,MAAM,QAAQ,GAAqB;QACjC,EAAE,EAAE,MAAM,CAAC,OAAO,CAAC;QACnB,MAAM,CAAC,IAAI;YACT,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,KAAK,QAAQ;gBAAE,OAAO;YAC7C,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;YACjC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,cAAc,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;YAE9D,wEAAwE;YACxE,0EAA0E;YAC1E,IAAI,OAAO,IAAI,OAAO,EAAE,CAAC;gBACvB,OAAO,CAAC,MAAM,CAAC,cAAc,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC;gBAChD,OAAO;YACT,CAAC;YAED,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;YACjC,QAAQ,CAAC,EAAE,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC;YACrC,QAAQ,CAAC,EAAE,GAAG,WAAW,CAAC;QAC5B,CAAC;KACF,CAAC;IAEF,OAAO,QAAQ,CAAC;AAClB,CAAC,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { MdmaBlockRenderer } from '../renderers/renderer-props.js';
2
+ /**
3
+ * The one renderer that must update in place. A streamed document re-parses on
4
+ * every chunk, and rebuilding the inputs each time would wipe whatever the user
5
+ * is typing — so the field elements persist and are only re-fed their props.
6
+ */
7
+ export declare const FormRenderer: MdmaBlockRenderer;
8
+ //# sourceMappingURL=FormRenderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FormRenderer.d.ts","sourceRoot":"","sources":["../../src/components/FormRenderer.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,iBAAiB,EAGlB,MAAM,gCAAgC,CAAC;AAkKxC;;;;GAIG;AACH,eAAO,MAAM,YAAY,EAAE,iBA6B1B,CAAC"}
@@ -0,0 +1,149 @@
1
+ import { resolveElementOverride } from '../context/render-context.js';
2
+ import { el } from '../dom/el.js';
3
+ import { DefaultCheckbox, DefaultFile, DefaultInput, DefaultSelect, DefaultSensitiveIndicator, DefaultSubmitButton, DefaultTextarea, } from './form/default-elements.js';
4
+ /** Any change here alters the form's structure, so the fields are rebuilt rather than updated. */
5
+ const shapeOf = (component) => [
6
+ component.label ?? '',
7
+ component.onSubmit ?? '',
8
+ ...component.fields.map((field) => `${field.name}:${field.type}:${field.label}:${field.required}:${field.sensitive}`),
9
+ ].join('|');
10
+ function optionsFor(field, props) {
11
+ if (typeof field.options === 'string') {
12
+ return props.context.dataSources?.[field.options] ?? [];
13
+ }
14
+ return field.options ?? [];
15
+ }
16
+ function elementPropsFor(field, component, props, getProps) {
17
+ const values = props.componentState?.values ?? {};
18
+ const shared = {
19
+ id: `${component.id}-${field.name}`,
20
+ name: field.name,
21
+ label: field.label,
22
+ required: field.required,
23
+ sensitive: field.sensitive,
24
+ };
25
+ const change = (value) => getProps().dispatch({
26
+ type: 'FIELD_CHANGED',
27
+ componentId: component.id,
28
+ field: field.name,
29
+ value,
30
+ });
31
+ if (field.type === 'checkbox') {
32
+ return {
33
+ id: shared.id,
34
+ name: field.name,
35
+ label: field.label,
36
+ sensitive: field.sensitive,
37
+ checked: Boolean(values[field.name]),
38
+ onChange: (checked) => change(checked),
39
+ };
40
+ }
41
+ if (field.type === 'file') {
42
+ return {
43
+ ...shared,
44
+ value: Array.isArray(values[field.name]) ? values[field.name] : [],
45
+ onChange: (files) => change(files),
46
+ };
47
+ }
48
+ if (field.type === 'select') {
49
+ return {
50
+ ...shared,
51
+ type: 'select',
52
+ value: String(values[field.name] ?? ''),
53
+ options: optionsFor(field, props),
54
+ onChange: (value) => change(value),
55
+ };
56
+ }
57
+ if (field.type === 'textarea') {
58
+ return {
59
+ ...shared,
60
+ value: String(values[field.name] ?? ''),
61
+ onChange: (value) => change(value),
62
+ };
63
+ }
64
+ return {
65
+ ...shared,
66
+ type: field.type,
67
+ value: String(values[field.name] ?? ''),
68
+ onChange: (value) => change(value),
69
+ };
70
+ }
71
+ function elementFor(field, props) {
72
+ const override = (name) => resolveElementOverride(props.context, 'form', name);
73
+ if (field.type === 'select')
74
+ return override('select') ?? DefaultSelect;
75
+ if (field.type === 'checkbox')
76
+ return override('checkbox') ?? DefaultCheckbox;
77
+ if (field.type === 'textarea')
78
+ return override('textarea') ?? DefaultTextarea;
79
+ if (field.type === 'file')
80
+ return override('file') ?? DefaultFile;
81
+ return override('input') ?? DefaultInput;
82
+ }
83
+ function build(props, component, getProps) {
84
+ const fields = new Map();
85
+ const rows = component.fields.map((field) => {
86
+ const create = elementFor(field, props);
87
+ const instance = create(elementPropsFor(field, component, props, getProps));
88
+ fields.set(field.name, instance);
89
+ const indicator = field.sensitive
90
+ ? (resolveElementOverride(props.context, 'form', 'sensitiveIndicator') ??
91
+ DefaultSensitiveIndicator)({ name: field.name, label: field.label })
92
+ : undefined;
93
+ return el('div', { class: `mdma-form-field ${field.sensitive ? 'mdma-form-field--sensitive' : ''}`.trim() }, [
94
+ el('label', { for: `${component.id}-${field.name}` }, [field.label, indicator?.el]),
95
+ instance.el,
96
+ ]);
97
+ });
98
+ let submit;
99
+ if (component.onSubmit) {
100
+ const create = (resolveElementOverride(props.context, 'form', 'submitButton') ??
101
+ DefaultSubmitButton);
102
+ submit = create({
103
+ label: 'Submit',
104
+ onClick: () => getProps().dispatch({
105
+ type: 'ACTION_TRIGGERED',
106
+ componentId: component.id,
107
+ actionId: component.onSubmit,
108
+ }),
109
+ });
110
+ }
111
+ const root = el('div', { class: 'mdma-form', dataset: { 'component-id': component.id } }, [
112
+ component.label && el('h3', { class: 'mdma-form-label' }, [component.label]),
113
+ ...rows,
114
+ submit?.el,
115
+ ]);
116
+ return { el: root, fields, submit, shape: shapeOf(component) };
117
+ }
118
+ const emptyBuild = () => ({ el: el('div'), fields: new Map(), shape: '' });
119
+ /**
120
+ * The one renderer that must update in place. A streamed document re-parses on
121
+ * every chunk, and rebuilding the inputs each time would wipe whatever the user
122
+ * is typing — so the field elements persist and are only re-fed their props.
123
+ */
124
+ export const FormRenderer = (initial) => {
125
+ let props = initial;
126
+ const getProps = () => props;
127
+ let built = initial.component.type === 'form' ? build(initial, initial.component, getProps) : emptyBuild();
128
+ const instance = {
129
+ el: built.el,
130
+ update(next) {
131
+ props = next;
132
+ if (next.component.type !== 'form')
133
+ return;
134
+ if (shapeOf(next.component) !== built.shape) {
135
+ const rebuilt = build(next, next.component, getProps);
136
+ built.el.replaceWith(rebuilt.el);
137
+ built = rebuilt;
138
+ instance.el = rebuilt.el;
139
+ return;
140
+ }
141
+ for (const field of next.component.fields) {
142
+ const element = built.fields.get(field.name);
143
+ element?.update(elementPropsFor(field, next.component, next, getProps));
144
+ }
145
+ },
146
+ };
147
+ return instance;
148
+ };
149
+ //# sourceMappingURL=FormRenderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FormRenderer.js","sourceRoot":"","sources":["../../src/components/FormRenderer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /** Pull a component type hint out of partial YAML (e.g. "type: form"). */
2
+ export declare function extractTypeHint(yaml?: string): string | null;
3
+ /**
4
+ * Skeleton for an `mdma` fence that hasn't finished streaming (or failed
5
+ * validation). The hint comes from the partial YAML so the placeholder can name
6
+ * what is arriving instead of showing a generic spinner.
7
+ */
8
+ export declare function renderBlockLoading(node: {
9
+ value?: string;
10
+ }): HTMLElement;
11
+ //# sourceMappingURL=MdmaBlockLoading.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MdmaBlockLoading.d.ts","sourceRoot":"","sources":["../../src/components/MdmaBlockLoading.ts"],"names":[],"mappings":"AAEA,0EAA0E;AAC1E,wBAAgB,eAAe,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAI5D;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,WAAW,CAYxE"}