what-react 0.11.0 → 0.11.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +2 -2
  2. package/src/runtime.js +45 -5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "what-react",
3
- "version": "0.11.0",
3
+ "version": "0.11.2",
4
4
  "description": "React compatibility layer for What Framework — real React semantics (value hooks, re-renders, context) on a dedicated compat runtime",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -27,7 +27,7 @@
27
27
  "compatibility"
28
28
  ],
29
29
  "peerDependencies": {
30
- "what-core": "^0.11.0"
30
+ "what-core": "^0.11.2"
31
31
  },
32
32
  "author": "ZVN DEV (https://zvndev.com)",
33
33
  "license": "MIT",
package/src/runtime.js CHANGED
@@ -467,6 +467,38 @@ function mountRNode(v, kind, container, svg, owner) {
467
467
 
468
468
  const SVG_NS = 'http://www.w3.org/2000/svg';
469
469
 
470
+ // A portal target may live inside an <svg> (recharts 3.x z-index layers are
471
+ // SVG <g> portal targets). Children portaled into an SVG container must be
472
+ // created in the SVG namespace, not HTML.
473
+ function targetSvg(target) {
474
+ return !!target && target.namespaceURI === SVG_NS && target.tagName !== 'foreignObject';
475
+ }
476
+
477
+ // camelCase React SVG prop → correct kebab/colon SVG attribute name.
478
+ // Covers the presentation/text/clip attributes charting libs (recharts) emit.
479
+ const SVG_ATTR_MAP = {
480
+ strokeWidth: 'stroke-width', strokeDasharray: 'stroke-dasharray',
481
+ strokeDashoffset: 'stroke-dashoffset', strokeLinecap: 'stroke-linecap',
482
+ strokeLinejoin: 'stroke-linejoin', strokeMiterlimit: 'stroke-miterlimit',
483
+ strokeOpacity: 'stroke-opacity', fillOpacity: 'fill-opacity',
484
+ fillRule: 'fill-rule', clipPath: 'clip-path', clipRule: 'clip-rule',
485
+ stopColor: 'stop-color', stopOpacity: 'stop-opacity',
486
+ textAnchor: 'text-anchor', dominantBaseline: 'dominant-baseline',
487
+ alignmentBaseline: 'alignment-baseline', baselineShift: 'baseline-shift',
488
+ colorInterpolation: 'color-interpolation',
489
+ colorInterpolationFilters: 'color-interpolation-filters',
490
+ floodColor: 'flood-color', floodOpacity: 'flood-opacity',
491
+ letterSpacing: 'letter-spacing', wordSpacing: 'word-spacing',
492
+ pointerEvents: 'pointer-events', shapeRendering: 'shape-rendering',
493
+ vectorEffect: 'vector-effect', paintOrder: 'paint-order',
494
+ markerStart: 'marker-start', markerMid: 'marker-mid', markerEnd: 'marker-end',
495
+ // Font + rendering attrs recharts emits on <text>/axis/tick/legend elements.
496
+ fontSize: 'font-size', fontFamily: 'font-family', fontWeight: 'font-weight',
497
+ fontStyle: 'font-style', fontVariant: 'font-variant', fontStretch: 'font-stretch',
498
+ textRendering: 'text-rendering', imageRendering: 'image-rendering',
499
+ writingMode: 'writing-mode', lightingColor: 'lighting-color',
500
+ };
501
+
470
502
  function mountElement(v, container, svg, owner) {
471
503
  const tag = v.tag;
472
504
  const childSvg = (svg || tag === 'svg') && tag !== 'foreignObject';
@@ -551,7 +583,7 @@ function mountPortal(v, container, owner) {
551
583
  console.warn('[what-react] createPortal: target container not found');
552
584
  return rn;
553
585
  }
554
- rn.children = patchChildren(target, [], normalizeChildren(v.children), null, false, owner);
586
+ rn.children = patchChildren(target, [], normalizeChildren(v.children), null, targetSvg(target), owner);
555
587
  return rn;
556
588
  }
557
589
 
@@ -705,13 +737,13 @@ function patchPortal(rn, v, owner) {
705
737
  const target = v.props && v.props.container;
706
738
  if (target === rn.container) {
707
739
  if (target) {
708
- rn.children = patchChildren(target, rn.children, normalizeChildren(v.children), null, false, owner);
740
+ rn.children = patchChildren(target, rn.children, normalizeChildren(v.children), null, targetSvg(target), owner);
709
741
  }
710
742
  } else {
711
743
  for (const child of rn.children) unmountRNode(child, true);
712
744
  rn.container = target || null;
713
745
  rn.children = target
714
- ? patchChildren(target, [], normalizeChildren(v.children), null, false, owner)
746
+ ? patchChildren(target, [], normalizeChildren(v.children), null, targetSvg(target), owner)
715
747
  : [];
716
748
  }
717
749
  rn.vnode = v;
@@ -1130,8 +1162,16 @@ export function setProperty(el, name, value, oldValue, svg) {
1130
1162
  el.setAttributeNS('http://www.w3.org/1999/xlink', 'href', value);
1131
1163
  return;
1132
1164
  }
1133
- if (value == null || value === false) el.removeAttribute(name);
1134
- else el.setAttribute(name, value === true ? '' : value);
1165
+ // React accepts camelCase SVG presentation props (strokeWidth, fontSize, …)
1166
+ // and the spec expects kebab-case attribute names. Unlike HTML elements,
1167
+ // SVG-namespaced elements preserve the exact case passed to setAttribute —
1168
+ // so "strokeWidth" stays "strokeWidth", not "strokewidth". That camelCase
1169
+ // name is not a valid SVG presentation attribute, so the renderer silently
1170
+ // ignores it. Map the known ones to their correct kebab-case equivalents.
1171
+ const mapped = SVG_ATTR_MAP[name];
1172
+ const attr = mapped || name;
1173
+ if (value == null || value === false) el.removeAttribute(attr);
1174
+ else el.setAttribute(attr, value === true ? '' : value);
1135
1175
  return;
1136
1176
  }
1137
1177