@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 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.
@@ -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.56.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.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
- patchNode(ctx, parent, oldDom[oldEnd], oE, nE, ns);
1108
- tailRef = oldDom[oldEnd];
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)) {
@@ -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' && themes[nameOrOverrides.theme]) {
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
- let out = '<' + tag + serializeProps(props, SKIP_PROPS, policy, onUnsafe);
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);