@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.
- package/LICENSE +21 -0
- package/README.md +127 -0
- package/dist/components/ApprovalGateRenderer.d.ts +2 -0
- package/dist/components/ApprovalGateRenderer.d.ts.map +1 -0
- package/dist/components/ApprovalGateRenderer.js +43 -0
- package/dist/components/ApprovalGateRenderer.js.map +1 -0
- package/dist/components/ButtonRenderer.d.ts +2 -0
- package/dist/components/ButtonRenderer.d.ts.map +1 -0
- package/dist/components/ButtonRenderer.js +23 -0
- package/dist/components/ButtonRenderer.js.map +1 -0
- package/dist/components/CalloutRenderer.d.ts +2 -0
- package/dist/components/CalloutRenderer.d.ts.map +1 -0
- package/dist/components/CalloutRenderer.js +33 -0
- package/dist/components/CalloutRenderer.js.map +1 -0
- package/dist/components/ChartRenderer.d.ts +3 -0
- package/dist/components/ChartRenderer.d.ts.map +1 -0
- package/dist/components/ChartRenderer.js +52 -0
- package/dist/components/ChartRenderer.js.map +1 -0
- package/dist/components/CustomRenderer.d.ts +8 -0
- package/dist/components/CustomRenderer.d.ts.map +1 -0
- package/dist/components/CustomRenderer.js +51 -0
- package/dist/components/CustomRenderer.js.map +1 -0
- package/dist/components/FormRenderer.d.ts +8 -0
- package/dist/components/FormRenderer.d.ts.map +1 -0
- package/dist/components/FormRenderer.js +149 -0
- package/dist/components/FormRenderer.js.map +1 -0
- package/dist/components/MdmaBlockLoading.d.ts +11 -0
- package/dist/components/MdmaBlockLoading.d.ts.map +1 -0
- package/dist/components/MdmaBlockLoading.js +26 -0
- package/dist/components/MdmaBlockLoading.js.map +1 -0
- package/dist/components/TableRenderer.d.ts +2 -0
- package/dist/components/TableRenderer.d.ts.map +1 -0
- package/dist/components/TableRenderer.js +66 -0
- package/dist/components/TableRenderer.js.map +1 -0
- package/dist/components/TasklistRenderer.d.ts +7 -0
- package/dist/components/TasklistRenderer.d.ts.map +1 -0
- package/dist/components/TasklistRenderer.js +80 -0
- package/dist/components/TasklistRenderer.js.map +1 -0
- package/dist/components/ThinkingRenderer.d.ts +3 -0
- package/dist/components/ThinkingRenderer.d.ts.map +1 -0
- package/dist/components/ThinkingRenderer.js +36 -0
- package/dist/components/ThinkingRenderer.js.map +1 -0
- package/dist/components/WebhookRenderer.d.ts +2 -0
- package/dist/components/WebhookRenderer.d.ts.map +1 -0
- package/dist/components/WebhookRenderer.js +33 -0
- package/dist/components/WebhookRenderer.js.map +1 -0
- package/dist/components/form/default-elements.d.ts +10 -0
- package/dist/components/form/default-elements.d.ts.map +1 -0
- package/dist/components/form/default-elements.js +198 -0
- package/dist/components/form/default-elements.js.map +1 -0
- package/dist/components/form/element-props.d.ts +58 -0
- package/dist/components/form/element-props.d.ts.map +1 -0
- package/dist/components/form/element-props.js +2 -0
- package/dist/components/form/element-props.js.map +1 -0
- package/dist/context/render-context.d.ts +40 -0
- package/dist/context/render-context.d.ts.map +1 -0
- package/dist/context/render-context.js +7 -0
- package/dist/context/render-context.js.map +1 -0
- package/dist/document/mount-block.d.ts +19 -0
- package/dist/document/mount-block.d.ts.map +1 -0
- package/dist/document/mount-block.js +31 -0
- package/dist/document/mount-block.js.map +1 -0
- package/dist/document/mount-document.d.ts +33 -0
- package/dist/document/mount-document.d.ts.map +1 -0
- package/dist/document/mount-document.js +177 -0
- package/dist/document/mount-document.js.map +1 -0
- package/dist/document/partial-yaml.d.ts +10 -0
- package/dist/document/partial-yaml.d.ts.map +1 -0
- package/dist/document/partial-yaml.js +48 -0
- package/dist/document/partial-yaml.js.map +1 -0
- package/dist/dom/el.d.ts +26 -0
- package/dist/dom/el.d.ts.map +1 -0
- package/dist/dom/el.js +59 -0
- package/dist/dom/el.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/markdown/render-mdast.d.ts +21 -0
- package/dist/markdown/render-mdast.d.ts.map +1 -0
- package/dist/markdown/render-mdast.js +89 -0
- package/dist/markdown/render-mdast.js.map +1 -0
- package/dist/renderers/renderer-props.d.ts +41 -0
- package/dist/renderers/renderer-props.d.ts.map +1 -0
- package/dist/renderers/renderer-props.js +31 -0
- package/dist/renderers/renderer-props.js.map +1 -0
- package/dist/renderers/renderer-registry.d.ts +14 -0
- package/dist/renderers/renderer-registry.d.ts.map +1 -0
- package/dist/renderers/renderer-registry.js +48 -0
- package/dist/renderers/renderer-registry.js.map +1 -0
- package/dist/theme/theme.d.ts +68 -0
- package/dist/theme/theme.d.ts.map +1 -0
- package/dist/theme/theme.js +120 -0
- package/dist/theme/theme.js.map +1 -0
- package/package.json +56 -0
- 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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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"}
|