@jarenjs/view 0.56.0 → 0.67.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/README.md +21 -0
- package/docs/VIEW-FORMAT.md +5 -0
- package/package.json +2 -2
- package/src/dom.js +3 -2
- package/src/helpers/theme.js +3 -2
- package/src/html.js +12 -1
package/README.md
CHANGED
|
@@ -176,6 +176,27 @@ When a producer cannot preserve the reference — it rebuilds its tree but knows
|
|
|
176
176
|
|
|
177
177
|
Children reconcile with a head/tail sweep plus a key map for the middle: keyed siblings move their real DOM nodes instead of recreating them; unkeyed siblings patch positionally. Event bindings are data stored on the node behind one shared proxy listener per event type — re-rendering rebinds by assignment, never through `addEventListener`.
|
|
178
178
|
|
|
179
|
+
## Exports
|
|
180
|
+
|
|
181
|
+
Every subpath a consumer can import, derived from the manifest by
|
|
182
|
+
`npm run docs:derive` (`npm run docs:check` fails when the two drift):
|
|
183
|
+
|
|
184
|
+
<!--fact:exports.view-->
|
|
185
|
+
| Import | Kind | Declarations |
|
|
186
|
+
|---|---|---|
|
|
187
|
+
| `@jarenjs/view` | JavaScript | declared |
|
|
188
|
+
| `@jarenjs/view/helpers` | JavaScript | declared |
|
|
189
|
+
| `@jarenjs/view/helpers/index` | JavaScript | declared |
|
|
190
|
+
| `@jarenjs/view/helpers/memo` | JavaScript | declared |
|
|
191
|
+
| `@jarenjs/view/helpers/metrics` | JavaScript | declared |
|
|
192
|
+
| `@jarenjs/view/helpers/svg` | JavaScript | declared |
|
|
193
|
+
| `@jarenjs/view/helpers/theme` | JavaScript | declared |
|
|
194
|
+
| `@jarenjs/view/helpers/url` | JavaScript | declared |
|
|
195
|
+
| `@jarenjs/view/schemas/jaren-vnode-safe.schema.json` | schema | — |
|
|
196
|
+
| `@jarenjs/view/schemas/jaren-vnode.schema.json` | schema | — |
|
|
197
|
+
| `@jarenjs/view/package.json` | metadata | — |
|
|
198
|
+
<!--/fact-->
|
|
199
|
+
|
|
179
200
|
## Development
|
|
180
201
|
|
|
181
202
|
Unit tests live in `test/view/` at the repository root (`npm run test:view`), including the minimal DOM stub they run against. See the repository [README](../../README.md) for the full suite documentation and [ROADMAP](../../docs/ROADMAP.md) for planned work: fragment roots, DOM-adopting hydration, and the memoized rule-output layer that turns JSLT sharing into cross-frame skipping.
|
package/docs/VIEW-FORMAT.md
CHANGED
|
@@ -225,6 +225,11 @@ patching renderer would build: text and attribute values escaped (`&`,
|
|
|
225
225
|
no output, boolean and style props serialized per §3. Serialization is
|
|
226
226
|
pure: no state, no DOM, safe in any runtime.
|
|
227
227
|
|
|
228
|
+
In trusted mode, a `textarea` carrying a `value` prop serializes that
|
|
229
|
+
value as escaped text content, overriding its children; nullish values
|
|
230
|
+
produce empty text. A leading newline is preserved through HTML parsing.
|
|
231
|
+
Safe mode keeps its attribute-only policy (§8).
|
|
232
|
+
|
|
228
233
|
Hydration in 0.1 is a client-side first render into the same container
|
|
229
234
|
(empty and rebuild). Adopting existing server-rendered DOM is a
|
|
230
235
|
roadmap item, not part of this contract.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jarenjs/view",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.67.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
7
7
|
"types": "./dist/types/index.d.ts",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"ssr"
|
|
54
54
|
],
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"@jarenjs/core": "^0.
|
|
56
|
+
"@jarenjs/core": "^0.67.0"
|
|
57
57
|
},
|
|
58
58
|
"scripts": {
|
|
59
59
|
"build": "npm run build:types",
|
package/src/dom.js
CHANGED
|
@@ -1104,8 +1104,9 @@ function patchChildren(ctx, parent, oldCh, newCh, ns) {
|
|
|
1104
1104
|
oldStart++; newStart++;
|
|
1105
1105
|
}
|
|
1106
1106
|
else if (isSameNode(oE, nE)) {
|
|
1107
|
-
|
|
1108
|
-
|
|
1107
|
+
// A widget may replace its host while keeping its vnode key/tag.
|
|
1108
|
+
// Future insertions must anchor to the live replacement.
|
|
1109
|
+
tailRef = patchNode(ctx, parent, oldDom[oldEnd], oE, nE, ns);
|
|
1109
1110
|
oldEnd--; newEnd--;
|
|
1110
1111
|
}
|
|
1111
1112
|
else if (isSameNode(oS, nE)) {
|
package/src/helpers/theme.js
CHANGED
|
@@ -51,11 +51,12 @@ export function resolveTheme(themes, prefix, nameOrOverrides = 'default', hostVa
|
|
|
51
51
|
let vars = null;
|
|
52
52
|
if (nameOrOverrides === 'host' && hostVars) nameOrOverrides = { vars: hostVars };
|
|
53
53
|
if (typeof nameOrOverrides === 'string') {
|
|
54
|
-
name = themes[nameOrOverrides] ? nameOrOverrides : 'default';
|
|
54
|
+
name = Object.hasOwn(themes, nameOrOverrides) && themes[nameOrOverrides] ? nameOrOverrides : 'default';
|
|
55
55
|
base = themes[name];
|
|
56
56
|
}
|
|
57
57
|
else if (nameOrOverrides && typeof nameOrOverrides === 'object') {
|
|
58
|
-
if (typeof nameOrOverrides.theme === 'string'
|
|
58
|
+
if (typeof nameOrOverrides.theme === 'string'
|
|
59
|
+
&& Object.hasOwn(themes, nameOrOverrides.theme) && themes[nameOrOverrides.theme]) {
|
|
59
60
|
name = nameOrOverrides.theme;
|
|
60
61
|
base = themes[name];
|
|
61
62
|
}
|
package/src/html.js
CHANGED
|
@@ -29,6 +29,9 @@ const VOID_ELEMENTS = new Set([
|
|
|
29
29
|
/** Props that never serialize to markup. */
|
|
30
30
|
const SKIP_PROPS = new Set(['key', 'on', 'memo']);
|
|
31
31
|
|
|
32
|
+
/** A controlled textarea's value is its text content in HTML markup. */
|
|
33
|
+
const TEXTAREA_SKIP_PROPS = new Set([...SKIP_PROPS, 'value']);
|
|
34
|
+
|
|
32
35
|
/** Widget-vnode props that configure the widget, not the host element. */
|
|
33
36
|
const WIDGET_SKIP_PROPS = new Set(['key', 'on', 'memo', 'name', 'props', 'tag']);
|
|
34
37
|
|
|
@@ -184,11 +187,19 @@ function renderNode(vnode, widgets, policy, onUnsafe) {
|
|
|
184
187
|
if (policy !== null && onUnsafe !== null && props.on !== undefined) {
|
|
185
188
|
onUnsafe({ kind: 'event', name: 'on' });
|
|
186
189
|
}
|
|
187
|
-
|
|
190
|
+
const controlledTextarea = policy === null && tag === 'textarea' && 'value' in props;
|
|
191
|
+
let out = '<' + tag + serializeProps(props,
|
|
192
|
+
controlledTextarea ? TEXTAREA_SKIP_PROPS : SKIP_PROPS, policy, onUnsafe);
|
|
188
193
|
if (VOID_ELEMENTS.has(tag)) {
|
|
189
194
|
return out + '>';
|
|
190
195
|
}
|
|
191
196
|
out += '>';
|
|
197
|
+
if (controlledTextarea) {
|
|
198
|
+
const value = props.value == null ? '' : String(props.value);
|
|
199
|
+
// HTML normalizes CR/CRLF to LF and consumes one leading LF in a
|
|
200
|
+
// textarea. Protect an authored newline with an extra LF.
|
|
201
|
+
return out + (/^[\r\n]/.test(value) ? '\n' : '') + escapeText(value) + '</' + tag + '>';
|
|
202
|
+
}
|
|
192
203
|
const children = childrenOf(vnode);
|
|
193
204
|
for (let i = 0; i < children.length; i++) {
|
|
194
205
|
out += renderNode(children[i], widgets, policy, onUnsafe);
|