solid-tag-runtime 0.0.17 → 0.0.18
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/ARCHITECTURE.md +58 -4
- package/README.md +22 -3
- package/docs/api/html.md +25 -0
- package/docs/getting-started.md +18 -3
- package/docs/html-runtime.md +6 -0
- package/docs/rendering.md +4 -0
- package/docs/solid-render.md +227 -29
- package/docs/solid-runtime-setup.md +1 -1
- package/examples/render.html +7 -2
- package/html.d.ts +23 -0
- package/package.json +1 -1
- package/src/html/data-literal.js +270 -0
- package/src/html.js +196 -14
package/ARCHITECTURE.md
CHANGED
|
@@ -1344,11 +1344,11 @@ The shared render adapter lazily resolves the host application's own `solid-js`
|
|
|
1344
1344
|
3. `data-solid-runtime` selects a matching HTML controller whose configured root contains the element. Without the attribute, controller selection follows the same rule as unscoped script discovery: a containing scoped controller may participate when `acceptUnscoped:true`, while an unscoped controller naturally accepts it. Equally specific matches are ambiguous and require explicit scope. A controller outside the element's root is never selected merely because it is the only candidate.
|
|
1345
1345
|
4. The element remains in the DOM and is the light-DOM mount container. Wrapperless rendering belongs to bare `<script render>`.
|
|
1346
1346
|
5. Renderer configuration (`module`, `component`, `data-solid-runtime`) is never forwarded as component props.
|
|
1347
|
-
6. Declarative props
|
|
1348
|
-
7. `.props` carries arbitrary JavaScript references and overrides declarative
|
|
1347
|
+
6. Declarative prop layers are bulk `props`, individual `prop:*`, then programmatic `.props`. Kebab `prop:*` names normalize to camelCase. Empty/presence values are boolean `true`; plain values remain strings; one outer `{...}` pair opts an individual prop into safe typed-data parsing.
|
|
1348
|
+
7. Bulk `props` uses a safe JSON5-style object parser. Declarative parsing never evaluates JavaScript. `.props` carries arbitrary JavaScript references and overrides both declarative layers.
|
|
1349
1349
|
8. Prop changes update a reactive prop facade and do not remount the component, preserving local state.
|
|
1350
1350
|
9. Module/component/runtime identity changes increment a generation token, dispose the current owner, and remount. Stale async import successes and failures are ignored; they cannot mount or report an error against a newer identity.
|
|
1351
|
-
10. Initial light-DOM child nodes are captured once and exposed as reusable `props.children
|
|
1351
|
+
10. Initial light-DOM child nodes are captured once and exposed as reusable `props.children`. From 0.0.18 the element is hidden while pending by default so those children do not flash before activation; `show-until-ready` intentionally exposes a cloned fallback while pending. Named slots and dynamic child recapture remain out of scope.
|
|
1352
1352
|
11. Multiple elements may share one evaluated module namespace while owning independent Solid component roots.
|
|
1353
1353
|
12. Disconnect disposes the mounted root; reconnect mounts a fresh component instance from the retained declaration inputs.
|
|
1354
1354
|
13. Custom-element registration is global/idempotent per `CustomElementRegistry`, but controller creation does **not** eagerly upgrade existing `solid-render` elements. Initial `register()` / `observe()` first install the complete declarative script batch and then ensure the custom element is registered. `registerSolidRenderElement()` remains available for explicit/custom-registry use.
|
|
@@ -1356,7 +1356,7 @@ The shared render adapter lazily resolves the host application's own `solid-js`
|
|
|
1356
1356
|
|
|
1357
1357
|
Rendering lifecycle joins the existing HTML event stream through `render-mounting`, `render-mounted`, `render-disposing`, and `render-disposed`; failures are also reported through `html-error` with `render`/`solid-render` operations. Lifecycle subscriptions remain observational and cannot alter rendering.
|
|
1358
1358
|
|
|
1359
|
-
The
|
|
1359
|
+
The initial `solid-render` phase excluded named slots, Shadow DOM, typed declarative data, loading/fallback visibility policy, and expression evaluation. `0.0.18` adds safe typed data plus pending/fallback visibility while continuing to exclude named slots, Shadow DOM, arbitrary unprefixed prop forwarding, and JavaScript expression evaluation.
|
|
1360
1360
|
|
|
1361
1361
|
### 0.0.10 — unresolved scoped render diagnostics
|
|
1362
1362
|
|
|
@@ -1549,3 +1549,57 @@ Default `idleMs` is 50 ms. Invalid modes and negative/non-finite idle values are
|
|
|
1549
1549
|
Invariant: use continuous observation only when runtime declarations may enter the DOM through code outside the runtime's explicit APIs. Controlled dynamic module loading should remain observer-free after initial registration.
|
|
1550
1550
|
|
|
1551
1551
|
Regression coverage verifies continuous compatibility, bootstrap initial registration, late startup additions, unrelated mutation filtering, automatic disconnect, option validation, and re-observation after bootstrap.
|
|
1552
|
+
|
|
1553
|
+
### 0.0.18 — `<solid-render>` pending visibility and safe typed declarative props
|
|
1554
|
+
|
|
1555
|
+
Decision: make reusable `<solid-render>` instances visually stable during asynchronous activation and make declarative prop data expressive without turning HTML attributes into executable JavaScript.
|
|
1556
|
+
|
|
1557
|
+
Visibility lifecycle:
|
|
1558
|
+
|
|
1559
|
+
```text
|
|
1560
|
+
connected / identity changed
|
|
1561
|
+
↓
|
|
1562
|
+
pending
|
|
1563
|
+
↓
|
|
1564
|
+
module import + component mount
|
|
1565
|
+
↓
|
|
1566
|
+
ready
|
|
1567
|
+
|
|
1568
|
+
error path: pending → error
|
|
1569
|
+
```
|
|
1570
|
+
|
|
1571
|
+
Rules:
|
|
1572
|
+
|
|
1573
|
+
1. `<solid-render>` is hidden while `data-solid-render-state="pending"` by default.
|
|
1574
|
+
2. The HTML runtime installs one document-level visibility rule automatically; application CSS is not required. The rule also covers `solid-render:not(:defined)` once the HTML runtime has initialized, preventing the custom-element upgrade window from exposing fallback content.
|
|
1575
|
+
3. `show-until-ready` opts out of hiding and displays a clone of captured initial light-DOM children while pending.
|
|
1576
|
+
4. Error state is visible and restores captured initial content as fallback when no component mount exists.
|
|
1577
|
+
5. `renderState` exposes `pending | ready | error`; `hideUntilReady` is the programmatic visibility-policy property.
|
|
1578
|
+
6. Identity changes immediately re-enter `pending`; existing generation guards remain authoritative, so stale imports cannot reveal or mark ready a newer generation.
|
|
1579
|
+
7. Visibility management does not change light-DOM ownership, `props.children` capture, runtime routing, or the independent Solid owner for each instance.
|
|
1580
|
+
|
|
1581
|
+
Declarative prop layers:
|
|
1582
|
+
|
|
1583
|
+
```text
|
|
1584
|
+
props="{ ... }"
|
|
1585
|
+
↓ overridden by
|
|
1586
|
+
prop:*
|
|
1587
|
+
↓ overridden by
|
|
1588
|
+
element.props
|
|
1589
|
+
```
|
|
1590
|
+
|
|
1591
|
+
Parsing rules:
|
|
1592
|
+
|
|
1593
|
+
1. Bulk `props` must parse to a top-level object.
|
|
1594
|
+
2. The built-in parser accepts a safe JSON5-style data subset: unquoted keys, single/double quoted strings, arrays/objects, booleans, null, finite numbers, comments, and trailing commas.
|
|
1595
|
+
3. Plain individual `prop:*` attribute values remain HTML strings; empty/presence values remain boolean `true`.
|
|
1596
|
+
4. One outer `{...}` pair marks an individual prop as typed data. Examples: `{3}`, `{true}`, `{[1,2,3]}`, and `{{a:123}}`.
|
|
1597
|
+
5. Declarative parsing never uses `eval`, `new Function`, variable lookup, calls, member access, functions, constructors, or other executable JavaScript semantics. Unsupported expression syntax fails as a render/data error.
|
|
1598
|
+
6. Prop-only changes update the existing reactive prop facade and do not remount. Invalid later declarative updates preserve the existing component mount rather than partially applying data.
|
|
1599
|
+
7. An optional `parseProps(source, context)` controller hook may replace only declarative data decoding. `context.kind` distinguishes bulk `props` from one typed `prop:*`; module resolution, controller routing, and render ownership are unaffected.
|
|
1600
|
+
8. Programmatic `.props` remains the path for functions, signals/accessors, services, DOM nodes, class instances, Maps/Sets, and other identity-sensitive JavaScript values.
|
|
1601
|
+
|
|
1602
|
+
Implementation boundary: the safe parser lives under `src/html/` and has no external runtime dependency, preserving direct-browser/zero-build package usage. The core runtime remains completely unaware of these HTML/data semantics.
|
|
1603
|
+
|
|
1604
|
+
Regression coverage verifies pending/ready/error state, default hiding, visible fallback opt-out, fallback restoration on error, typed individual values, structured bulk values, precedence, reactive updates without remount, custom parser behavior, non-execution of JavaScript-like input, and automatic visibility-style installation.
|
|
1605
|
+
|
package/README.md
CHANGED
|
@@ -16,6 +16,23 @@ host Solid runtime
|
|
|
16
16
|
|
|
17
17
|
The core package is module-first and DOM-independent. Browser discovery, ownership, declarative rendering, and `<solid-render>` live in `solid-tag-runtime/html`. Optional Solid setup/provider helpers live in `solid-tag-runtime/solid`.
|
|
18
18
|
|
|
19
|
+
## 0.0.18 `<solid-render>` lifecycle and typed declarative props
|
|
20
|
+
|
|
21
|
+
`0.0.18` hides `<solid-render>` light-DOM content until the selected component is ready by default, exposes `pending` / `ready` / `error` through `data-solid-render-state`, and provides `show-until-ready` for intentional fallback content. The HTML runtime installs the pending-visibility rule automatically; applications do not need their own CSS.
|
|
22
|
+
|
|
23
|
+
Structured component data can now be written declaratively:
|
|
24
|
+
|
|
25
|
+
```html
|
|
26
|
+
<solid-render
|
|
27
|
+
module="/Counter.jsx"
|
|
28
|
+
props="{ initial: 100, options: { theme: 'dark' } }"
|
|
29
|
+
prop:step="{5}"
|
|
30
|
+
prop:items="{[1, 2, 3]}"
|
|
31
|
+
></solid-render>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Plain `prop:*` values remain HTML strings; one outer `{...}` pair opts an individual prop into safe typed-data parsing. Bulk `props` uses a safe JSON5-style object syntax. Neither form evaluates JavaScript. Precedence is `props < prop:* < element.props`, and prop-only changes remain reactive without remounting.
|
|
35
|
+
|
|
19
36
|
## 0.0.17 observation strategies
|
|
20
37
|
|
|
21
38
|
`0.0.17` adds explicit observation lifecycle strategies while keeping existing behavior backward compatible. `observe()` is still continuous by default. Applications that only need DOM discovery during startup can use `observe({ mode: "bootstrap" })`; it observes through DOM readiness and disconnects after a configurable quiet period of relevant runtime mutations.
|
|
@@ -192,6 +209,8 @@ See [Wrapperless delegation](./docs/wrapperless-delegation.md).
|
|
|
192
209
|
|
|
193
210
|
**In HTML, always use the explicit closing tag.** Do not write `<solid-render ... />`; custom elements are not HTML void elements and the self-closing slash is ignored by the HTML parser.
|
|
194
211
|
|
|
212
|
+
Initial light-DOM content is hidden until the component is ready by default. Use `show-until-ready` when those children are intentional loading/fallback content. See the [`<solid-render>` guide](./docs/solid-render.md) for structured `props`, typed `prop:*` literals, precedence, and render-state details.
|
|
213
|
+
|
|
195
214
|
## Persistent compile cache
|
|
196
215
|
|
|
197
216
|
The opt-in compile cache persists **pre-link compiler artifacts**. Runtime-specific linked URLs and evaluated module namespaces are never persisted.
|
|
@@ -242,9 +261,9 @@ When loading from an import map, map every used package subpath to the same rele
|
|
|
242
261
|
```json
|
|
243
262
|
{
|
|
244
263
|
"imports": {
|
|
245
|
-
"solid-tag-runtime": "https://esm.sh/solid-tag-runtime@0.0.
|
|
246
|
-
"solid-tag-runtime/html": "https://esm.sh/solid-tag-runtime@0.0.
|
|
247
|
-
"solid-tag-runtime/solid": "https://esm.sh/solid-tag-runtime@0.0.
|
|
264
|
+
"solid-tag-runtime": "https://esm.sh/solid-tag-runtime@0.0.18",
|
|
265
|
+
"solid-tag-runtime/html": "https://esm.sh/solid-tag-runtime@0.0.18/html",
|
|
266
|
+
"solid-tag-runtime/solid": "https://esm.sh/solid-tag-runtime@0.0.18/solid"
|
|
248
267
|
}
|
|
249
268
|
}
|
|
250
269
|
```
|
package/docs/api/html.md
CHANGED
|
@@ -25,6 +25,7 @@ Common options:
|
|
|
25
25
|
- `executeEntries`
|
|
26
26
|
- `executeRenders`
|
|
27
27
|
- `onError`
|
|
28
|
+
- `parseProps(source, context)` for optional custom `<solid-render>` declarative-data parsing
|
|
28
29
|
|
|
29
30
|
## Discovery and observation
|
|
30
31
|
|
|
@@ -95,3 +96,27 @@ For rendering semantics see [Declarative rendering](../rendering.md) and [`<soli
|
|
|
95
96
|
For bare `<script render>` ranges, automatic Solid delegated-event setup is available when the underlying runtime was created with `createSolidRuntime()` from `solid-tag-runtime/solid`.
|
|
96
97
|
|
|
97
98
|
The HTML adapter asks the runtime's private Solid integration to establish document-level delegation lazily, then keeps the declaration in its existing independent `createRoot() + insert()` lifecycle. Selector renders and `<solid-render>` retain their existing container semantics.
|
|
99
|
+
|
|
100
|
+
## `<solid-render>` state and props
|
|
101
|
+
|
|
102
|
+
`0.0.18` exposes these element properties/state surfaces:
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
renderer.renderState; // "pending" | "ready" | "error"
|
|
106
|
+
renderer.hideUntilReady; // true by default
|
|
107
|
+
renderer.props = { ... }; // strongest prop layer
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The DOM mirrors render state through `data-solid-render-state`. Pending content is hidden automatically unless `show-until-ready` is present.
|
|
111
|
+
|
|
112
|
+
Declarative data layers are:
|
|
113
|
+
|
|
114
|
+
```text
|
|
115
|
+
props="{ initial: 100 }"
|
|
116
|
+
< prop:step="{5}"
|
|
117
|
+
< renderer.props
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Plain `prop:*` values are strings. An outer `{...}` marker parses one individual value with the same safe JSON5-style data parser used by bulk `props`. No declarative form evaluates JavaScript.
|
|
121
|
+
|
|
122
|
+
A custom parser receives `(source, context)` where `context.kind` is `"props"` or `"prop"`; bulk `props` must return a top-level object.
|
package/docs/getting-started.md
CHANGED
|
@@ -97,6 +97,21 @@ Declarative source:
|
|
|
97
97
|
|
|
98
98
|
See [HTML runtime](./html-runtime.md), [declarative rendering](./rendering.md), and [`<solid-render>`](./solid-render.md).
|
|
99
99
|
|
|
100
|
+
|
|
101
|
+
## Reusable component instance
|
|
102
|
+
|
|
103
|
+
After the HTML controller registers declarations, an existing runtime module can be instantiated directly from HTML:
|
|
104
|
+
|
|
105
|
+
```html
|
|
106
|
+
<solid-render
|
|
107
|
+
module="/Counter.jsx"
|
|
108
|
+
props="{ initial: 100 }"
|
|
109
|
+
prop:step="{5}"
|
|
110
|
+
></solid-render>
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
`<solid-render>` is hidden while pending by default, so initial child content does not flash before the Solid mount is ready. Add `show-until-ready` when the initial children are intentional loading content. Plain `prop:*` values are strings; `{...}` opts an individual prop into safe typed-data parsing.
|
|
114
|
+
|
|
100
115
|
## Browser import maps
|
|
101
116
|
|
|
102
117
|
Map every used package subpath explicitly:
|
|
@@ -104,9 +119,9 @@ Map every used package subpath explicitly:
|
|
|
104
119
|
```json
|
|
105
120
|
{
|
|
106
121
|
"imports": {
|
|
107
|
-
"solid-tag-runtime": "https://esm.sh/solid-tag-runtime@0.0.
|
|
108
|
-
"solid-tag-runtime/html": "https://esm.sh/solid-tag-runtime@0.0.
|
|
109
|
-
"solid-tag-runtime/solid": "https://esm.sh/solid-tag-runtime@0.0.
|
|
122
|
+
"solid-tag-runtime": "https://esm.sh/solid-tag-runtime@0.0.18",
|
|
123
|
+
"solid-tag-runtime/html": "https://esm.sh/solid-tag-runtime@0.0.18/html",
|
|
124
|
+
"solid-tag-runtime/solid": "https://esm.sh/solid-tag-runtime@0.0.18/solid"
|
|
110
125
|
}
|
|
111
126
|
}
|
|
112
127
|
```
|
package/docs/html-runtime.md
CHANGED
|
@@ -152,3 +152,9 @@ A low-level `createRuntime()` controller behaves exactly as before and does not
|
|
|
152
152
|
When the runtime comes from `createSolidRuntime()` in `solid-tag-runtime/solid`, a bare `<script render>` can lazily request the document-level delegated-event setup required by Solid 2. Each marker-range declaration still owns an independent reactive root and disposer; the delegation host is infrastructure only and is not a shared reactive owner.
|
|
153
153
|
|
|
154
154
|
See [Solid runtime setup](./solid-runtime-setup.md) and [Wrapperless delegation](./wrapperless-delegation.md).
|
|
155
|
+
|
|
156
|
+
## `<solid-render>` declarative data and pending visibility
|
|
157
|
+
|
|
158
|
+
The HTML controller also owns the declarative-data parser configuration used by `<solid-render>`. The default parser treats bulk `props` as a safe JSON5-style object and treats `prop:name="{...}"` as an explicitly typed individual value. Plain individual attributes remain strings. Applications may override this data parser with `parseProps`, but parsing never changes module/runtime ownership.
|
|
159
|
+
|
|
160
|
+
When a controller is created in a browser document it installs one internal visibility rule for `<solid-render>`. Pending elements are hidden by default, including the pre-upgrade `:not(:defined)` window after the runtime has initialized. `show-until-ready` opts into visible fallback content. The custom element itself drives `data-solid-render-state="pending|ready|error"`.
|
package/docs/rendering.md
CHANGED
|
@@ -69,3 +69,7 @@ Combining `entry` and `render` on one declaration is rejected.
|
|
|
69
69
|
## Runtime scope diagnostics
|
|
70
70
|
|
|
71
71
|
A scoped render declaration is immediate work. If `data-solid-runtime="main"` cannot be handled by any registered controller covering that DOM root, the HTML adapter emits `html-warning` with code `unresolved-runtime-scope` and logs one warning.
|
|
72
|
+
|
|
73
|
+
## Reusable `<solid-render>` lifecycle
|
|
74
|
+
|
|
75
|
+
Reusable `<solid-render>` instances use container rendering rather than the marker-range wrapperless path. From `0.0.18`, they are hidden while pending by default and expose `data-solid-render-state="pending|ready|error"`. `show-until-ready` keeps captured initial children visible as fallback content. Structured `props` and typed `prop:*="{...}"` values are data-only and do not evaluate JavaScript. See [`<solid-render>`](./solid-render.md).
|
package/docs/solid-render.md
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
# `<solid-render>`
|
|
2
2
|
|
|
3
|
-
`<solid-render>` references an already-defined runtime module and mounts
|
|
3
|
+
`<solid-render>` references an already-defined runtime module and mounts one component instance into its own light DOM.
|
|
4
4
|
|
|
5
5
|
```html
|
|
6
6
|
<script type="solid-jsx" module="/Counter.jsx">
|
|
7
|
-
export default function Counter() {
|
|
8
|
-
return <button>
|
|
7
|
+
export default function Counter(props) {
|
|
8
|
+
return <button>{props.initial}</button>;
|
|
9
9
|
}
|
|
10
10
|
</script>
|
|
11
11
|
|
|
12
|
-
<solid-render
|
|
12
|
+
<solid-render
|
|
13
|
+
module="/Counter.jsx"
|
|
14
|
+
props="{ initial: 100 }"
|
|
15
|
+
></solid-render>
|
|
13
16
|
```
|
|
14
17
|
|
|
15
|
-
##
|
|
16
|
-
|
|
17
|
-
**Always use an explicit closing tag in HTML.**
|
|
18
|
+
## Always use an explicit closing tag
|
|
18
19
|
|
|
19
20
|
Correct:
|
|
20
21
|
|
|
@@ -28,7 +29,68 @@ Do not write:
|
|
|
28
29
|
<solid-render module="/Counter.jsx" />
|
|
29
30
|
```
|
|
30
31
|
|
|
31
|
-
Custom elements are not HTML void elements. The HTML parser ignores the XML-style self-closing slash, so following siblings may become children of `<solid-render>`.
|
|
32
|
+
Custom elements are not HTML void elements. The HTML parser ignores the XML-style self-closing slash, so following siblings may accidentally become children of `<solid-render>`.
|
|
33
|
+
|
|
34
|
+
## Hidden until ready by default
|
|
35
|
+
|
|
36
|
+
`0.0.18` prevents initial light-DOM content from flashing before the requested component is ready.
|
|
37
|
+
|
|
38
|
+
```html
|
|
39
|
+
<solid-render module="/Counter.jsx">
|
|
40
|
+
this text is captured, but does not flash before the component mounts
|
|
41
|
+
</solid-render>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The runtime installs the required visibility rule automatically; application CSS is not required.
|
|
45
|
+
|
|
46
|
+
The element exposes its current state:
|
|
47
|
+
|
|
48
|
+
```text
|
|
49
|
+
data-solid-render-state="pending"
|
|
50
|
+
data-solid-render-state="ready"
|
|
51
|
+
data-solid-render-state="error"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The normal lifecycle is:
|
|
55
|
+
|
|
56
|
+
```text
|
|
57
|
+
pending
|
|
58
|
+
↓
|
|
59
|
+
module import / component mount
|
|
60
|
+
↓
|
|
61
|
+
ready
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
On failure:
|
|
65
|
+
|
|
66
|
+
```text
|
|
67
|
+
pending → error
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Error state is visible, and captured initial content is restored as fallback content.
|
|
71
|
+
|
|
72
|
+
The visibility rule is installed as soon as the HTML runtime is created. For pages that need to avoid any paint before the runtime bootstrap itself executes, load/bootstrap the HTML runtime early in the document.
|
|
73
|
+
|
|
74
|
+
### Show fallback content while pending
|
|
75
|
+
|
|
76
|
+
If initial children are intentional loading content, opt out of hiding:
|
|
77
|
+
|
|
78
|
+
```html
|
|
79
|
+
<solid-render
|
|
80
|
+
module="/Account.jsx"
|
|
81
|
+
show-until-ready
|
|
82
|
+
>
|
|
83
|
+
Loading account…
|
|
84
|
+
</solid-render>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Programmatically:
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
renderer.hideUntilReady = false;
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The state attribute still transitions through `pending`, `ready`, and `error`; only the visibility policy changes.
|
|
32
94
|
|
|
33
95
|
## Named component
|
|
34
96
|
|
|
@@ -39,6 +101,8 @@ Custom elements are not HTML void elements. The HTML parser ignores the XML-styl
|
|
|
39
101
|
></solid-render>
|
|
40
102
|
```
|
|
41
103
|
|
|
104
|
+
Without `component`, `module.default` is used.
|
|
105
|
+
|
|
42
106
|
## Runtime selection
|
|
43
107
|
|
|
44
108
|
```html
|
|
@@ -50,48 +114,180 @@ Custom elements are not HTML void elements. The HTML parser ignores the XML-styl
|
|
|
50
114
|
|
|
51
115
|
The selected controller must match scope rules and contain the element within its configured root.
|
|
52
116
|
|
|
53
|
-
##
|
|
117
|
+
## Structured declarative props
|
|
118
|
+
|
|
119
|
+
Use `props` for a structured object:
|
|
54
120
|
|
|
55
121
|
```html
|
|
56
122
|
<solid-render
|
|
57
123
|
module="/UserCard.jsx"
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
124
|
+
props="{
|
|
125
|
+
user: {
|
|
126
|
+
name: 'Alice',
|
|
127
|
+
age: 32,
|
|
128
|
+
},
|
|
129
|
+
compact: true,
|
|
130
|
+
tags: ['admin', 'active'],
|
|
131
|
+
}"
|
|
61
132
|
></solid-render>
|
|
62
133
|
```
|
|
63
134
|
|
|
64
|
-
|
|
135
|
+
The built-in parser accepts safe JSON5-style data conveniences:
|
|
65
136
|
|
|
66
|
-
-
|
|
67
|
-
-
|
|
68
|
-
-
|
|
69
|
-
-
|
|
70
|
-
-
|
|
137
|
+
- unquoted object keys
|
|
138
|
+
- single- or double-quoted strings
|
|
139
|
+
- arrays and nested objects
|
|
140
|
+
- numbers, booleans, and `null`
|
|
141
|
+
- trailing commas
|
|
142
|
+
- line/block comments
|
|
71
143
|
|
|
72
|
-
|
|
144
|
+
Declarative props are **data only**. The parser never uses `eval()` or `new Function()` and does not resolve JavaScript variables, member access, calls, functions, or constructors.
|
|
73
145
|
|
|
74
|
-
|
|
75
|
-
|
|
146
|
+
This is rejected rather than executed:
|
|
147
|
+
|
|
148
|
+
```html
|
|
149
|
+
<solid-render props="{ value: getValue() }"></solid-render>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
The top-level `props` value must be an object.
|
|
153
|
+
|
|
154
|
+
## Individual `prop:*` values
|
|
155
|
+
|
|
156
|
+
Plain HTML attribute values remain strings:
|
|
157
|
+
|
|
158
|
+
```html
|
|
159
|
+
<solid-render
|
|
160
|
+
prop:first="123"
|
|
161
|
+
prop:second=123
|
|
162
|
+
></solid-render>
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Both values are the string `"123"`.
|
|
166
|
+
|
|
167
|
+
A present empty prop remains boolean `true`:
|
|
168
|
+
|
|
169
|
+
```html
|
|
170
|
+
<solid-render prop:compact></solid-render>
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
To opt one prop into typed data parsing, wrap it in one outer `{...}` pair:
|
|
174
|
+
|
|
175
|
+
```html
|
|
176
|
+
<solid-render
|
|
177
|
+
prop:count="{3}"
|
|
178
|
+
prop:enabled="{true}"
|
|
179
|
+
prop:missing="{null}"
|
|
180
|
+
prop:label="{'3'}"
|
|
181
|
+
prop:items="{[1, 2, 3]}"
|
|
182
|
+
prop:options="{{ theme: 'dark', step: 5 }}"
|
|
183
|
+
></solid-render>
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Effective values:
|
|
187
|
+
|
|
188
|
+
```js
|
|
189
|
+
{
|
|
190
|
+
count: 3,
|
|
191
|
+
enabled: true,
|
|
192
|
+
missing: null,
|
|
193
|
+
label: "3",
|
|
194
|
+
items: [1, 2, 3],
|
|
195
|
+
options: { theme: "dark", step: 5 },
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
The outer braces are a typed-data marker, not JavaScript expression syntax. Object values naturally use double braces because the inner braces belong to the object literal.
|
|
200
|
+
|
|
201
|
+
`prop:user-id` still normalizes to `userId`.
|
|
202
|
+
|
|
203
|
+
## Prop precedence
|
|
204
|
+
|
|
205
|
+
The effective prop layers are:
|
|
206
|
+
|
|
207
|
+
```text
|
|
208
|
+
props="..."
|
|
209
|
+
↓ overridden by
|
|
210
|
+
prop:*
|
|
211
|
+
↓ overridden by
|
|
212
|
+
element.props
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Example:
|
|
216
|
+
|
|
217
|
+
```html
|
|
218
|
+
<solid-render
|
|
219
|
+
module="/Counter.jsx"
|
|
220
|
+
props="{ initial: 100, step: 5 }"
|
|
221
|
+
prop:step="{10}"
|
|
222
|
+
></solid-render>
|
|
223
|
+
```
|
|
76
224
|
|
|
77
|
-
|
|
78
|
-
|
|
225
|
+
Then:
|
|
226
|
+
|
|
227
|
+
```ts
|
|
228
|
+
renderer.props = {
|
|
229
|
+
step: 20,
|
|
79
230
|
onSave,
|
|
80
|
-
service,
|
|
81
231
|
};
|
|
82
232
|
```
|
|
83
233
|
|
|
84
|
-
Programmatic
|
|
234
|
+
The effective `step` is `20`. Programmatic `.props` can contain arbitrary JavaScript values such as functions, signals, services, class instances, Maps/Sets, or identity-sensitive objects.
|
|
235
|
+
|
|
236
|
+
## Reactive prop updates
|
|
237
|
+
|
|
238
|
+
Changing any of these updates the existing mounted component without remounting:
|
|
239
|
+
|
|
240
|
+
```text
|
|
241
|
+
props attribute
|
|
242
|
+
prop:* attributes
|
|
243
|
+
element.props
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
For example:
|
|
247
|
+
|
|
248
|
+
```ts
|
|
249
|
+
renderer.setAttribute(
|
|
250
|
+
"props",
|
|
251
|
+
"{ initial: 200, options: { theme: 'light' } }",
|
|
252
|
+
);
|
|
85
253
|
|
|
86
|
-
|
|
254
|
+
renderer.setAttribute("prop:step", "{20}");
|
|
255
|
+
```
|
|
87
256
|
|
|
88
|
-
|
|
257
|
+
Local component state is preserved.
|
|
89
258
|
|
|
90
259
|
Changing `module`, `component`, or `data-solid-runtime` changes render identity and therefore disposes/remounts.
|
|
91
260
|
|
|
261
|
+
Invalid declarative data produces a `solid-render` error; it is never partially executed as JavaScript.
|
|
262
|
+
|
|
263
|
+
## Custom declarative parser
|
|
264
|
+
|
|
265
|
+
Applications with a domain-specific data syntax may override declarative parsing at the HTML-controller boundary:
|
|
266
|
+
|
|
267
|
+
```ts
|
|
268
|
+
const html = createHTMLRuntime(runtime, {
|
|
269
|
+
parseProps(source, context) {
|
|
270
|
+
if (context.kind === "props") {
|
|
271
|
+
return myObjectParser(source);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return myValueParser(source);
|
|
275
|
+
},
|
|
276
|
+
});
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Context distinguishes:
|
|
280
|
+
|
|
281
|
+
```text
|
|
282
|
+
kind: "props" bulk props attribute
|
|
283
|
+
kind: "prop" typed prop:* value
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
A custom bulk parser must still return an object. Parser customization changes data decoding only; it does not change runtime selection, module resolution, or component ownership.
|
|
287
|
+
|
|
92
288
|
## Children
|
|
93
289
|
|
|
94
|
-
Initial child DOM becomes `props.children`:
|
|
290
|
+
Initial child DOM is captured once and becomes `props.children`:
|
|
95
291
|
|
|
96
292
|
```html
|
|
97
293
|
<solid-render module="/Card.jsx">
|
|
@@ -99,7 +295,9 @@ Initial child DOM becomes `props.children`:
|
|
|
99
295
|
</solid-render>
|
|
100
296
|
```
|
|
101
297
|
|
|
102
|
-
|
|
298
|
+
By default those children are hidden while the element is pending, then instantiated through `props.children` when the component mounts. With `show-until-ready`, a clone is also displayed as fallback content while pending.
|
|
299
|
+
|
|
300
|
+
Named slots and dynamic child recapture are not part of the current API.
|
|
103
301
|
|
|
104
302
|
## Multiple instances
|
|
105
303
|
|
|
@@ -126,7 +126,7 @@ When provider fallback is needed and there is no versioned Solid anchor, this re
|
|
|
126
126
|
import { TESTED_SOLID_VERSION } from "solid-tag-runtime/solid";
|
|
127
127
|
```
|
|
128
128
|
|
|
129
|
-
For `0.0.14` through `0.0.
|
|
129
|
+
For `0.0.14` through `0.0.18`, the tested fallback line is Solid `2.0.0-rc.13`.
|
|
130
130
|
|
|
131
131
|
If an existing Solid mapping is explicitly versioned, that version becomes the family anchor for generated siblings. An explicitly unversioned existing mapping remains unversioned; the loader does not pretend it is pinned.
|
|
132
132
|
|
package/examples/render.html
CHANGED
|
@@ -37,14 +37,19 @@
|
|
|
37
37
|
<solid-render
|
|
38
38
|
data-solid-runtime="main"
|
|
39
39
|
module="/Counter.jsx"
|
|
40
|
-
|
|
40
|
+
props="{ label: 'Counter A' }"
|
|
41
|
+
prop:initial="{100}"
|
|
41
42
|
></solid-render>
|
|
42
43
|
|
|
43
44
|
<solid-render
|
|
44
45
|
data-solid-runtime="main"
|
|
45
46
|
module="/Counter.jsx"
|
|
46
47
|
prop:label="Counter B"
|
|
47
|
-
|
|
48
|
+
prop:initial="{200}"
|
|
49
|
+
show-until-ready
|
|
50
|
+
>
|
|
51
|
+
Loading Counter B…
|
|
52
|
+
</solid-render>
|
|
48
53
|
|
|
49
54
|
<!--
|
|
50
55
|
This bootstrap is ordinary JavaScript. The surrounding application/import
|
package/html.d.ts
CHANGED
|
@@ -18,8 +18,13 @@ export interface HTMLAttributeElementLike {
|
|
|
18
18
|
|
|
19
19
|
export interface HTMLModuleScriptElement extends HTMLAttributeElementLike {}
|
|
20
20
|
|
|
21
|
+
export type SolidRenderState = "pending" | "ready" | "error";
|
|
22
|
+
|
|
21
23
|
export interface SolidRenderElement extends HTMLAttributeElementLike, HTMLAppendTarget {
|
|
22
24
|
props: Record<string, unknown>;
|
|
25
|
+
readonly renderState: SolidRenderState;
|
|
26
|
+
/** Default true. Set false to expose captured fallback children while pending. */
|
|
27
|
+
hideUntilReady: boolean;
|
|
23
28
|
childNodes?: ArrayLike<any> | Iterable<any>;
|
|
24
29
|
attributes?: ArrayLike<{ name: string; value: string }> | Iterable<{ name: string; value: string }>;
|
|
25
30
|
replaceChildren?(...nodes: any[]): void;
|
|
@@ -32,6 +37,7 @@ export interface HTMLAppendTarget {
|
|
|
32
37
|
}
|
|
33
38
|
|
|
34
39
|
export interface HTMLDocumentLike extends HTMLModuleRoot, HTMLAppendTarget {
|
|
40
|
+
head?: HTMLAppendTarget;
|
|
35
41
|
body?: HTMLAppendTarget;
|
|
36
42
|
documentElement?: HTMLAppendTarget;
|
|
37
43
|
createElement?(tagName: string): any;
|
|
@@ -159,6 +165,8 @@ export interface RemoveHTMLElementResult {
|
|
|
159
165
|
|
|
160
166
|
export interface RegisterHTMLOptions extends DefineScriptOptions {
|
|
161
167
|
selector?: string;
|
|
168
|
+
/** Override safe declarative data parsing for `<solid-render props>` and typed `prop:*="{...}"`. */
|
|
169
|
+
parseProps?: HTMLDeclarativePropsParser;
|
|
162
170
|
executeEntries?: boolean;
|
|
163
171
|
/** Execute declarative render actions. Default: true. */
|
|
164
172
|
executeRenders?: boolean;
|
|
@@ -257,6 +265,19 @@ export interface AddHTMLModuleOptions extends DefineScriptOptions {
|
|
|
257
265
|
createElement?: (tagName: string) => HTMLModuleScriptElement;
|
|
258
266
|
}
|
|
259
267
|
|
|
268
|
+
export interface HTMLDeclarativePropsParseContext {
|
|
269
|
+
element: SolidRenderElement;
|
|
270
|
+
/** `props` parses a top-level object; `prop` parses one typed prop value. */
|
|
271
|
+
kind: "props" | "prop";
|
|
272
|
+
attribute: string;
|
|
273
|
+
key?: string;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export type HTMLDeclarativePropsParser = (
|
|
277
|
+
source: string,
|
|
278
|
+
context: HTMLDeclarativePropsParseContext,
|
|
279
|
+
) => unknown;
|
|
280
|
+
|
|
260
281
|
export interface HTMLRuntimeOptions extends ObserveHTMLOptions {
|
|
261
282
|
appendTo?: HTMLAppendTarget;
|
|
262
283
|
createElement?: (tagName: string) => HTMLModuleScriptElement;
|
|
@@ -323,6 +344,8 @@ export interface CustomElementRegistryLike {
|
|
|
323
344
|
export interface RegisterSolidRenderElementOptions {
|
|
324
345
|
customElements?: CustomElementRegistryLike;
|
|
325
346
|
HTMLElement?: new (...args: any[]) => any;
|
|
347
|
+
/** Document that receives the built-in pending-visibility rule. Defaults to global document. */
|
|
348
|
+
document?: HTMLDocumentLike;
|
|
326
349
|
}
|
|
327
350
|
|
|
328
351
|
export type HTMLObserverController = HTMLRuntimeController;
|
package/package.json
CHANGED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse a safe JSON5-style data literal without evaluating JavaScript.
|
|
3
|
+
*
|
|
4
|
+
* Supported conveniences intentionally cover declarative HTML data:
|
|
5
|
+
* - single/double quoted strings
|
|
6
|
+
* - unquoted object keys
|
|
7
|
+
* - line/block comments
|
|
8
|
+
* - trailing commas
|
|
9
|
+
* - arrays/objects
|
|
10
|
+
* - booleans/null/numbers
|
|
11
|
+
*
|
|
12
|
+
* Identifiers are accepted only as object keys or the literals true/false/null.
|
|
13
|
+
* Calls, property access, functions, constructors, variables, and expressions
|
|
14
|
+
* are rejected because this parser never executes JavaScript.
|
|
15
|
+
*/
|
|
16
|
+
export function parseHTMLDataLiteral(source) {
|
|
17
|
+
const input = String(source ?? "");
|
|
18
|
+
let index = 0;
|
|
19
|
+
|
|
20
|
+
const error = message => {
|
|
21
|
+
const position = index;
|
|
22
|
+
const context = input.slice(Math.max(0, position - 16), Math.min(input.length, position + 16));
|
|
23
|
+
const failure = new SyntaxError(`${message} at position ${position}${context ? ` near ${JSON.stringify(context)}` : ""}.`);
|
|
24
|
+
failure.position = position;
|
|
25
|
+
throw failure;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const peek = () => input[index];
|
|
29
|
+
const consume = expected => {
|
|
30
|
+
if (input[index] !== expected) error(`Expected ${JSON.stringify(expected)}`);
|
|
31
|
+
index += 1;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
function skipSpaceAndComments() {
|
|
35
|
+
while (index < input.length) {
|
|
36
|
+
const char = input[index];
|
|
37
|
+
if (/\s/.test(char)) {
|
|
38
|
+
index += 1;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (char === "/" && input[index + 1] === "/") {
|
|
42
|
+
index += 2;
|
|
43
|
+
while (index < input.length && input[index] !== "\n" && input[index] !== "\r") index += 1;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (char === "/" && input[index + 1] === "*") {
|
|
47
|
+
index += 2;
|
|
48
|
+
const end = input.indexOf("*/", index);
|
|
49
|
+
if (end < 0) error("Unterminated block comment");
|
|
50
|
+
index = end + 2;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function parseString() {
|
|
58
|
+
const quote = input[index++];
|
|
59
|
+
let result = "";
|
|
60
|
+
while (index < input.length) {
|
|
61
|
+
const char = input[index++];
|
|
62
|
+
if (char === quote) return result;
|
|
63
|
+
if (char === "\n" || char === "\r") error("Unterminated string literal");
|
|
64
|
+
if (char !== "\\") {
|
|
65
|
+
result += char;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (index >= input.length) error("Unterminated escape sequence");
|
|
69
|
+
const escape = input[index++];
|
|
70
|
+
switch (escape) {
|
|
71
|
+
case "'": result += "'"; break;
|
|
72
|
+
case '"': result += '"'; break;
|
|
73
|
+
case "\\": result += "\\"; break;
|
|
74
|
+
case "/": result += "/"; break;
|
|
75
|
+
case "b": result += "\b"; break;
|
|
76
|
+
case "f": result += "\f"; break;
|
|
77
|
+
case "n": result += "\n"; break;
|
|
78
|
+
case "r": result += "\r"; break;
|
|
79
|
+
case "t": result += "\t"; break;
|
|
80
|
+
case "v": result += "\v"; break;
|
|
81
|
+
case "0": {
|
|
82
|
+
if (/[0-9]/.test(input[index] ?? "")) error("Legacy octal escapes are not supported");
|
|
83
|
+
result += "\0";
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
case "x": {
|
|
87
|
+
const hex = input.slice(index, index + 2);
|
|
88
|
+
if (!/^[0-9a-fA-F]{2}$/.test(hex)) error("Invalid hexadecimal escape");
|
|
89
|
+
result += String.fromCharCode(Number.parseInt(hex, 16));
|
|
90
|
+
index += 2;
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
case "u": {
|
|
94
|
+
if (input[index] === "{") {
|
|
95
|
+
index += 1;
|
|
96
|
+
const close = input.indexOf("}", index);
|
|
97
|
+
if (close < 0) error("Invalid Unicode escape");
|
|
98
|
+
const hex = input.slice(index, close);
|
|
99
|
+
if (!/^[0-9a-fA-F]{1,6}$/.test(hex)) error("Invalid Unicode escape");
|
|
100
|
+
result += String.fromCodePoint(Number.parseInt(hex, 16));
|
|
101
|
+
index = close + 1;
|
|
102
|
+
} else {
|
|
103
|
+
const hex = input.slice(index, index + 4);
|
|
104
|
+
if (!/^[0-9a-fA-F]{4}$/.test(hex)) error("Invalid Unicode escape");
|
|
105
|
+
result += String.fromCharCode(Number.parseInt(hex, 16));
|
|
106
|
+
index += 4;
|
|
107
|
+
}
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
case "\n": break;
|
|
111
|
+
case "\r":
|
|
112
|
+
if (input[index] === "\n") index += 1;
|
|
113
|
+
break;
|
|
114
|
+
default:
|
|
115
|
+
result += escape;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
error("Unterminated string literal");
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function parseIdentifier() {
|
|
122
|
+
const start = index;
|
|
123
|
+
if (!/[A-Za-z_$]/.test(input[index] ?? "")) error("Expected identifier");
|
|
124
|
+
index += 1;
|
|
125
|
+
while (/[A-Za-z0-9_$]/.test(input[index] ?? "")) index += 1;
|
|
126
|
+
return input.slice(start, index);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function parseNumber() {
|
|
130
|
+
const start = index;
|
|
131
|
+
if (input[index] === "+" || input[index] === "-") index += 1;
|
|
132
|
+
|
|
133
|
+
if (input[index] === "0" && /[xX]/.test(input[index + 1] ?? "")) {
|
|
134
|
+
index += 2;
|
|
135
|
+
const hexStart = index;
|
|
136
|
+
while (/[0-9a-fA-F]/.test(input[index] ?? "")) index += 1;
|
|
137
|
+
if (index === hexStart) error("Invalid hexadecimal number");
|
|
138
|
+
const raw = input.slice(start, index);
|
|
139
|
+
const sign = raw.startsWith("-") ? -1 : 1;
|
|
140
|
+
const normalized = raw.replace(/^[+-]?0[xX]/, "");
|
|
141
|
+
return sign * Number.parseInt(normalized, 16);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
let digits = 0;
|
|
145
|
+
while (/[0-9]/.test(input[index] ?? "")) {
|
|
146
|
+
digits += 1;
|
|
147
|
+
index += 1;
|
|
148
|
+
}
|
|
149
|
+
if (input[index] === ".") {
|
|
150
|
+
index += 1;
|
|
151
|
+
while (/[0-9]/.test(input[index] ?? "")) {
|
|
152
|
+
digits += 1;
|
|
153
|
+
index += 1;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (digits === 0) error("Invalid number");
|
|
157
|
+
if (/[eE]/.test(input[index] ?? "")) {
|
|
158
|
+
index += 1;
|
|
159
|
+
if (input[index] === "+" || input[index] === "-") index += 1;
|
|
160
|
+
const exponentStart = index;
|
|
161
|
+
while (/[0-9]/.test(input[index] ?? "")) index += 1;
|
|
162
|
+
if (index === exponentStart) error("Invalid exponent");
|
|
163
|
+
}
|
|
164
|
+
const raw = input.slice(start, index);
|
|
165
|
+
const value = Number(raw);
|
|
166
|
+
if (!Number.isFinite(value)) error("Invalid finite number");
|
|
167
|
+
return value;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function defineDataProperty(target, key, value) {
|
|
171
|
+
Object.defineProperty(target, key, {
|
|
172
|
+
configurable: true,
|
|
173
|
+
enumerable: true,
|
|
174
|
+
writable: true,
|
|
175
|
+
value,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function parseObject() {
|
|
180
|
+
consume("{");
|
|
181
|
+
skipSpaceAndComments();
|
|
182
|
+
const object = {};
|
|
183
|
+
if (peek() === "}") {
|
|
184
|
+
index += 1;
|
|
185
|
+
return object;
|
|
186
|
+
}
|
|
187
|
+
while (index < input.length) {
|
|
188
|
+
skipSpaceAndComments();
|
|
189
|
+
let key;
|
|
190
|
+
const char = peek();
|
|
191
|
+
if (char === "'" || char === '"') key = parseString();
|
|
192
|
+
else if (/[A-Za-z_$]/.test(char ?? "")) key = parseIdentifier();
|
|
193
|
+
else if (/[+\-.0-9]/.test(char ?? "")) key = String(parseNumber());
|
|
194
|
+
else error("Expected object key");
|
|
195
|
+
|
|
196
|
+
skipSpaceAndComments();
|
|
197
|
+
consume(":");
|
|
198
|
+
skipSpaceAndComments();
|
|
199
|
+
defineDataProperty(object, key, parseValue());
|
|
200
|
+
skipSpaceAndComments();
|
|
201
|
+
if (peek() === "}") {
|
|
202
|
+
index += 1;
|
|
203
|
+
return object;
|
|
204
|
+
}
|
|
205
|
+
consume(",");
|
|
206
|
+
skipSpaceAndComments();
|
|
207
|
+
if (peek() === "}") {
|
|
208
|
+
index += 1;
|
|
209
|
+
return object;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
error("Unterminated object literal");
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function parseArray() {
|
|
216
|
+
consume("[");
|
|
217
|
+
skipSpaceAndComments();
|
|
218
|
+
const array = [];
|
|
219
|
+
if (peek() === "]") {
|
|
220
|
+
index += 1;
|
|
221
|
+
return array;
|
|
222
|
+
}
|
|
223
|
+
while (index < input.length) {
|
|
224
|
+
array.push(parseValue());
|
|
225
|
+
skipSpaceAndComments();
|
|
226
|
+
if (peek() === "]") {
|
|
227
|
+
index += 1;
|
|
228
|
+
return array;
|
|
229
|
+
}
|
|
230
|
+
consume(",");
|
|
231
|
+
skipSpaceAndComments();
|
|
232
|
+
if (peek() === "]") {
|
|
233
|
+
index += 1;
|
|
234
|
+
return array;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
error("Unterminated array literal");
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function parseValue() {
|
|
241
|
+
skipSpaceAndComments();
|
|
242
|
+
const char = peek();
|
|
243
|
+
if (char === "{") return parseObject();
|
|
244
|
+
if (char === "[") return parseArray();
|
|
245
|
+
if (char === "'" || char === '"') return parseString();
|
|
246
|
+
if (/[+\-.0-9]/.test(char ?? "")) return parseNumber();
|
|
247
|
+
if (/[A-Za-z_$]/.test(char ?? "")) {
|
|
248
|
+
const identifier = parseIdentifier();
|
|
249
|
+
if (identifier === "true") return true;
|
|
250
|
+
if (identifier === "false") return false;
|
|
251
|
+
if (identifier === "null") return null;
|
|
252
|
+
error(`Unsupported identifier ${JSON.stringify(identifier)}; declarative values cannot execute JavaScript`);
|
|
253
|
+
}
|
|
254
|
+
error("Expected data literal");
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
skipSpaceAndComments();
|
|
258
|
+
const value = parseValue();
|
|
259
|
+
skipSpaceAndComments();
|
|
260
|
+
if (index !== input.length) error("Unexpected trailing input");
|
|
261
|
+
return value;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export function parseHTMLPropsObject(source) {
|
|
265
|
+
const value = parseHTMLDataLiteral(source);
|
|
266
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
267
|
+
throw new TypeError("The `props` attribute must contain a top-level object literal.");
|
|
268
|
+
}
|
|
269
|
+
return value;
|
|
270
|
+
}
|
package/src/html.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ModuleResolutionError, SolidTagRuntimeError } from "./errors.js";
|
|
2
2
|
import { createEventDispatcher } from "./events.js";
|
|
3
3
|
import { HTMLRenderError, createDefaultHTMLRenderAdapter, resolveRenderComponent } from "./render.js";
|
|
4
|
+
import { parseHTMLDataLiteral, parseHTMLPropsObject } from "./html/data-literal.js";
|
|
4
5
|
|
|
5
6
|
const DEFAULT_SELECTOR = [
|
|
6
7
|
'script[type="solid-jsx"]',
|
|
@@ -12,6 +13,10 @@ const DEFAULT_SELECTOR = [
|
|
|
12
13
|
].join(",");
|
|
13
14
|
|
|
14
15
|
const RUNTIME_SCOPE_ATTRIBUTE = "data-solid-runtime";
|
|
16
|
+
const SOLID_RENDER_STATE_ATTRIBUTE = "data-solid-render-state";
|
|
17
|
+
const SOLID_RENDER_SHOW_UNTIL_READY_ATTRIBUTE = "show-until-ready";
|
|
18
|
+
const SOLID_RENDER_VISIBILITY_STYLE_ATTRIBUTE = "data-solid-tag-render-visibility";
|
|
19
|
+
const solidRenderVisibilityDocuments = new WeakSet();
|
|
15
20
|
const elementOwners = new WeakMap();
|
|
16
21
|
const defaultControllers = new WeakMap();
|
|
17
22
|
const activeControllers = new Set();
|
|
@@ -73,6 +78,7 @@ export function createHTMLRuntime(runtime, options = {}) {
|
|
|
73
78
|
createElement: options.createElement,
|
|
74
79
|
appendTo: options.appendTo,
|
|
75
80
|
renderAdapter: options.renderAdapter ?? createDefaultHTMLRenderAdapter(runtime),
|
|
81
|
+
parseProps: typeof options.parseProps === "function" ? options.parseProps : undefined,
|
|
76
82
|
bindings: new Map(),
|
|
77
83
|
renderElements: new Set(),
|
|
78
84
|
disposed: false,
|
|
@@ -134,6 +140,7 @@ export function createHTMLRuntime(runtime, options = {}) {
|
|
|
134
140
|
|
|
135
141
|
controller.api = api;
|
|
136
142
|
activeControllers.add(controller);
|
|
143
|
+
ensureSolidRenderVisibilityStyle(resolveControllerDocument(controller));
|
|
137
144
|
// Do not define/upgrade <solid-render> here. Existing declarative script
|
|
138
145
|
// modules must be installed first; otherwise connectedCallback() can import
|
|
139
146
|
// a module before register()/observe() has defined the preceding <script>.
|
|
@@ -667,6 +674,7 @@ export const htmlRuntimeScopeAttribute = RUNTIME_SCOPE_ATTRIBUTE;
|
|
|
667
674
|
* browser; programmatic-only applications may call it explicitly.
|
|
668
675
|
*/
|
|
669
676
|
export function registerSolidRenderElement(options = {}) {
|
|
677
|
+
ensureSolidRenderVisibilityStyle(options.document ?? globalThis.document);
|
|
670
678
|
const registry = options.customElements ?? globalThis.customElements;
|
|
671
679
|
const HTMLElementBase = options.HTMLElement ?? globalThis.HTMLElement;
|
|
672
680
|
if (!registry || typeof registry.define !== "function" || typeof registry.get !== "function" || typeof HTMLElementBase !== "function") {
|
|
@@ -680,7 +688,7 @@ export function registerSolidRenderElement(options = {}) {
|
|
|
680
688
|
|
|
681
689
|
class SolidRenderElement extends HTMLElementBase {
|
|
682
690
|
static get observedAttributes() {
|
|
683
|
-
return ["module", "component", RUNTIME_SCOPE_ATTRIBUTE];
|
|
691
|
+
return ["module", "component", RUNTIME_SCOPE_ATTRIBUTE, SOLID_RENDER_SHOW_UNTIL_READY_ATTRIBUTE];
|
|
684
692
|
}
|
|
685
693
|
|
|
686
694
|
constructor() {
|
|
@@ -696,13 +704,16 @@ export function registerSolidRenderElement(options = {}) {
|
|
|
696
704
|
propObserver: undefined,
|
|
697
705
|
pendingModuleResolution: undefined,
|
|
698
706
|
pendingCheckGeneration: undefined,
|
|
707
|
+
renderState: "pending",
|
|
699
708
|
});
|
|
700
709
|
}
|
|
701
710
|
|
|
702
711
|
connectedCallback() {
|
|
703
712
|
const state = getSolidRenderState(this);
|
|
704
713
|
state.connected = true;
|
|
714
|
+
setSolidRenderState(this, state, "pending");
|
|
705
715
|
captureSolidRenderChildren(this, state);
|
|
716
|
+
syncSolidRenderPendingContent(this, state);
|
|
706
717
|
startSolidRenderPropObserver(this, state);
|
|
707
718
|
void remountSolidRender(this, "connect");
|
|
708
719
|
}
|
|
@@ -720,9 +731,26 @@ export function registerSolidRenderElement(options = {}) {
|
|
|
720
731
|
if (oldValue === newValue) return;
|
|
721
732
|
const state = getSolidRenderState(this);
|
|
722
733
|
if (!state.connected) return;
|
|
734
|
+
if (name === SOLID_RENDER_SHOW_UNTIL_READY_ATTRIBUTE) {
|
|
735
|
+
syncSolidRenderPendingContent(this, state);
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
723
738
|
void remountSolidRender(this, `identity:${name}`);
|
|
724
739
|
}
|
|
725
740
|
|
|
741
|
+
get renderState() {
|
|
742
|
+
return getSolidRenderState(this).renderState;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
get hideUntilReady() {
|
|
746
|
+
return !this.hasAttribute?.(SOLID_RENDER_SHOW_UNTIL_READY_ATTRIBUTE);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
set hideUntilReady(value) {
|
|
750
|
+
if (value === false) this.setAttribute?.(SOLID_RENDER_SHOW_UNTIL_READY_ATTRIBUTE, "");
|
|
751
|
+
else this.removeAttribute?.(SOLID_RENDER_SHOW_UNTIL_READY_ATTRIBUTE);
|
|
752
|
+
}
|
|
753
|
+
|
|
726
754
|
get props() {
|
|
727
755
|
return getSolidRenderState(this).programmaticProps;
|
|
728
756
|
}
|
|
@@ -753,12 +781,78 @@ function getSolidRenderState(element) {
|
|
|
753
781
|
propObserver: undefined,
|
|
754
782
|
pendingModuleResolution: undefined,
|
|
755
783
|
pendingCheckGeneration: undefined,
|
|
784
|
+
renderState: "pending",
|
|
756
785
|
};
|
|
757
786
|
solidRenderInternals.set(element, state);
|
|
758
787
|
}
|
|
759
788
|
return state;
|
|
760
789
|
}
|
|
761
790
|
|
|
791
|
+
|
|
792
|
+
function resolveControllerDocument(controller) {
|
|
793
|
+
const root = controller?.root ?? globalThis.document;
|
|
794
|
+
if (root && typeof root.createElement === "function" && (root.head || root.documentElement || root.body)) return root;
|
|
795
|
+
return root?.ownerDocument ?? globalThis.document;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
function ensureSolidRenderVisibilityStyle(document) {
|
|
799
|
+
if (!document || (typeof document !== "object" && typeof document !== "function")) return;
|
|
800
|
+
if (solidRenderVisibilityDocuments.has(document)) return;
|
|
801
|
+
|
|
802
|
+
try {
|
|
803
|
+
if (document.querySelector?.(`style[${SOLID_RENDER_VISIBILITY_STYLE_ATTRIBUTE}]`)) {
|
|
804
|
+
solidRenderVisibilityDocuments.add(document);
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
if (typeof document.createElement !== "function") return;
|
|
808
|
+
const style = document.createElement("style");
|
|
809
|
+
style.setAttribute?.(SOLID_RENDER_VISIBILITY_STYLE_ATTRIBUTE, "");
|
|
810
|
+
style.textContent = [
|
|
811
|
+
`solid-render:not([${SOLID_RENDER_SHOW_UNTIL_READY_ATTRIBUTE}]):not(:defined),`,
|
|
812
|
+
`solid-render:not([${SOLID_RENDER_SHOW_UNTIL_READY_ATTRIBUTE}])[${SOLID_RENDER_STATE_ATTRIBUTE}="pending"] {`,
|
|
813
|
+
" visibility: hidden !important;",
|
|
814
|
+
"}",
|
|
815
|
+
].join("\n");
|
|
816
|
+
const target = document.head ?? document.documentElement ?? document.body;
|
|
817
|
+
if (typeof target?.append === "function") target.append(style);
|
|
818
|
+
else if (typeof target?.appendChild === "function") target.appendChild(style);
|
|
819
|
+
else return;
|
|
820
|
+
solidRenderVisibilityDocuments.add(document);
|
|
821
|
+
} catch {
|
|
822
|
+
// Styling is a progressive enhancement for pre-upgrade visibility. The
|
|
823
|
+
// custom element still manages pending/ready/error state in JavaScript.
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
function setSolidRenderState(element, state, nextState) {
|
|
828
|
+
state.renderState = nextState;
|
|
829
|
+
element.setAttribute?.(SOLID_RENDER_STATE_ATTRIBUTE, nextState);
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
function cloneSolidRenderFallbackNodes(state) {
|
|
833
|
+
const clone = state.childrenTemplate?.cloneNode?.(true);
|
|
834
|
+
if (!clone) return [];
|
|
835
|
+
if (clone.childNodes) return Array.from(clone.childNodes);
|
|
836
|
+
return [clone];
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
function restoreSolidRenderFallback(element, state) {
|
|
840
|
+
if (!state.childrenTemplate) {
|
|
841
|
+
element.replaceChildren?.();
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
element.replaceChildren?.(...cloneSolidRenderFallbackNodes(state));
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
function syncSolidRenderPendingContent(element, state) {
|
|
848
|
+
if (state.renderState !== "pending" || state.mount) return;
|
|
849
|
+
if (element.hasAttribute?.(SOLID_RENDER_SHOW_UNTIL_READY_ATTRIBUTE)) {
|
|
850
|
+
restoreSolidRenderFallback(element, state);
|
|
851
|
+
} else {
|
|
852
|
+
element.replaceChildren?.();
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
|
|
762
856
|
function captureSolidRenderChildren(element, state) {
|
|
763
857
|
if (state.captured) return;
|
|
764
858
|
state.captured = true;
|
|
@@ -784,7 +878,11 @@ function startSolidRenderPropObserver(element, state) {
|
|
|
784
878
|
if (typeof MutationObserverImpl !== "function") return;
|
|
785
879
|
|
|
786
880
|
const observer = new MutationObserverImpl(records => {
|
|
787
|
-
|
|
881
|
+
const relevant = Array.from(records ?? []).some(record => {
|
|
882
|
+
const name = String(record?.attributeName ?? "").toLowerCase();
|
|
883
|
+
return name === "props" || name.startsWith("prop:");
|
|
884
|
+
});
|
|
885
|
+
if (!relevant) return;
|
|
788
886
|
updateSolidRenderProps(element, state);
|
|
789
887
|
});
|
|
790
888
|
observer.observe(element, { attributes: true });
|
|
@@ -797,8 +895,10 @@ async function remountSolidRender(element, reason, options = {}) {
|
|
|
797
895
|
const generation = ++state.generation;
|
|
798
896
|
state.pendingModuleResolution = undefined;
|
|
799
897
|
state.pendingCheckGeneration = undefined;
|
|
898
|
+
setSolidRenderState(element, state, "pending");
|
|
800
899
|
await disposeSolidRenderMount(element, state, reason === "connect" ? "reconnect" : reason);
|
|
801
900
|
if (!state.connected || generation !== state.generation) return;
|
|
901
|
+
syncSolidRenderPendingContent(element, state);
|
|
802
902
|
|
|
803
903
|
let controller;
|
|
804
904
|
try {
|
|
@@ -812,7 +912,7 @@ async function remountSolidRender(element, reason, options = {}) {
|
|
|
812
912
|
}
|
|
813
913
|
const componentName = nonEmpty(element.getAttribute?.("component"));
|
|
814
914
|
const exportName = componentName ?? "default";
|
|
815
|
-
const props = buildSolidRenderProps(element, state);
|
|
915
|
+
const props = buildSolidRenderProps(element, state, controller);
|
|
816
916
|
|
|
817
917
|
emitHTML(controller, {
|
|
818
918
|
type: "render-mounting",
|
|
@@ -849,6 +949,7 @@ async function remountSolidRender(element, reason, options = {}) {
|
|
|
849
949
|
state.mount = mount;
|
|
850
950
|
state.pendingModuleResolution = undefined;
|
|
851
951
|
state.pendingCheckGeneration = undefined;
|
|
952
|
+
setSolidRenderState(element, state, "ready");
|
|
852
953
|
emitHTML(controller, {
|
|
853
954
|
type: "render-mounted",
|
|
854
955
|
element,
|
|
@@ -884,6 +985,8 @@ async function remountSolidRender(element, reason, options = {}) {
|
|
|
884
985
|
}
|
|
885
986
|
}
|
|
886
987
|
|
|
988
|
+
setSolidRenderState(element, state, "error");
|
|
989
|
+
if (!state.mount) restoreSolidRenderFallback(element, state);
|
|
887
990
|
if (controller) {
|
|
888
991
|
emitHTMLError(controller, "solid-render", error, {
|
|
889
992
|
element,
|
|
@@ -944,20 +1047,44 @@ async function disposeSolidRenderMount(element, state, reason = "dispose") {
|
|
|
944
1047
|
|
|
945
1048
|
function updateSolidRenderProps(element, state) {
|
|
946
1049
|
if (!state.mount?.updateProps) return;
|
|
947
|
-
state.
|
|
1050
|
+
let controller = state.controller;
|
|
1051
|
+
try {
|
|
1052
|
+
controller ??= resolveHTMLControllerForElement(element);
|
|
1053
|
+
const props = buildSolidRenderProps(element, state, controller);
|
|
1054
|
+
state.mount.updateProps(props);
|
|
1055
|
+
setSolidRenderState(element, state, "ready");
|
|
1056
|
+
} catch (error) {
|
|
1057
|
+
setSolidRenderState(element, state, "error");
|
|
1058
|
+
if (controller) {
|
|
1059
|
+
emitHTMLError(controller, "solid-render", error, {
|
|
1060
|
+
element,
|
|
1061
|
+
moduleId: nonEmpty(element.getAttribute?.("module")),
|
|
1062
|
+
origin: "solid-render",
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1065
|
+
dispatchSolidRenderError(element, error);
|
|
1066
|
+
}
|
|
948
1067
|
}
|
|
949
1068
|
|
|
950
|
-
function buildSolidRenderProps(element, state) {
|
|
1069
|
+
function buildSolidRenderProps(element, state, controller) {
|
|
951
1070
|
const declarative = {};
|
|
1071
|
+
const bulk = parseSolidRenderBulkProps(element, controller);
|
|
1072
|
+
copyPropRecord(declarative, bulk);
|
|
1073
|
+
|
|
952
1074
|
for (const attribute of Array.from(element.attributes ?? [])) {
|
|
953
|
-
const
|
|
1075
|
+
const rawName = String(attribute?.name ?? "");
|
|
1076
|
+
const name = rawName.toLowerCase();
|
|
954
1077
|
if (!name.startsWith("prop:")) continue;
|
|
955
1078
|
const key = kebabToCamel(name.slice(5));
|
|
956
1079
|
if (!key) continue;
|
|
957
|
-
declarative[key] =
|
|
1080
|
+
declarative[key] = parseSolidRenderAttributeProp(element, controller, key, attribute.value);
|
|
958
1081
|
}
|
|
959
1082
|
|
|
960
|
-
if (
|
|
1083
|
+
if (
|
|
1084
|
+
state.childrenTemplate
|
|
1085
|
+
&& !("children" in declarative)
|
|
1086
|
+
&& !("children" in (state.programmaticProps ?? {}))
|
|
1087
|
+
) {
|
|
961
1088
|
Object.defineProperty(declarative, "children", {
|
|
962
1089
|
configurable: true,
|
|
963
1090
|
enumerable: true,
|
|
@@ -968,14 +1095,69 @@ function buildSolidRenderProps(element, state) {
|
|
|
968
1095
|
}
|
|
969
1096
|
|
|
970
1097
|
const merged = Object.create(null);
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
1098
|
+
copyPropRecord(merged, declarative);
|
|
1099
|
+
copyPropRecord(merged, state.programmaticProps ?? {});
|
|
1100
|
+
return merged;
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
function copyPropRecord(target, source) {
|
|
1104
|
+
for (const key of Reflect.ownKeys(source ?? {})) {
|
|
1105
|
+
const descriptor = Object.getOwnPropertyDescriptor(source, key);
|
|
1106
|
+
if (descriptor) Object.defineProperty(target, key, descriptor);
|
|
1107
|
+
}
|
|
1108
|
+
return target;
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
function parseSolidRenderBulkProps(element, controller) {
|
|
1112
|
+
const source = element.getAttribute?.("props");
|
|
1113
|
+
if (source == null || String(source).trim() === "") return {};
|
|
1114
|
+
const value = parseSolidRenderData(controller, String(source), {
|
|
1115
|
+
element,
|
|
1116
|
+
kind: "props",
|
|
1117
|
+
attribute: "props",
|
|
1118
|
+
});
|
|
1119
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
1120
|
+
throw new HTMLRenderError("<solid-render> `props` must parse to a top-level object.", { element });
|
|
974
1121
|
}
|
|
975
|
-
|
|
976
|
-
|
|
1122
|
+
return value;
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
function parseSolidRenderAttributeProp(element, controller, key, source) {
|
|
1126
|
+
if (source === "") return true;
|
|
1127
|
+
const raw = String(source);
|
|
1128
|
+
const trimmed = raw.trim();
|
|
1129
|
+
if (!(trimmed.startsWith("{") && trimmed.endsWith("}"))) return raw;
|
|
1130
|
+
|
|
1131
|
+
const inner = trimmed.slice(1, -1).trim();
|
|
1132
|
+
if (!inner) {
|
|
1133
|
+
throw new HTMLRenderError(
|
|
1134
|
+
`<solid-render> prop:${key} uses an outer brace pair for typed data; the inner value cannot be empty.`,
|
|
1135
|
+
{ element },
|
|
1136
|
+
);
|
|
1137
|
+
}
|
|
1138
|
+
return parseSolidRenderData(controller, inner, {
|
|
1139
|
+
element,
|
|
1140
|
+
kind: "prop",
|
|
1141
|
+
attribute: `prop:${key}`,
|
|
1142
|
+
key,
|
|
1143
|
+
});
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
function parseSolidRenderData(controller, source, context) {
|
|
1147
|
+
try {
|
|
1148
|
+
if (typeof controller?.parseProps === "function") {
|
|
1149
|
+
return controller.parseProps(source, context);
|
|
1150
|
+
}
|
|
1151
|
+
return context.kind === "props"
|
|
1152
|
+
? parseHTMLPropsObject(source)
|
|
1153
|
+
: parseHTMLDataLiteral(source);
|
|
1154
|
+
} catch (cause) {
|
|
1155
|
+
const label = context.kind === "props" ? "props" : context.attribute;
|
|
1156
|
+
throw new HTMLRenderError(
|
|
1157
|
+
`<solid-render> could not parse ${label} as declarative data: ${cause?.message ?? cause}`,
|
|
1158
|
+
{ element: context.element, cause },
|
|
1159
|
+
);
|
|
977
1160
|
}
|
|
978
|
-
return merged;
|
|
979
1161
|
}
|
|
980
1162
|
|
|
981
1163
|
function kebabToCamel(value) {
|