@b9g/crank 0.7.4 → 0.7.6

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/umd.js CHANGED
@@ -420,14 +420,14 @@
420
420
  * Typically, you use a helper function like createElement to create elements
421
421
  * rather than instatiating this class directly.
422
422
  */
423
- class Element {
423
+ let Element$1 = class Element {
424
424
  constructor(tag, props) {
425
425
  this.tag = tag;
426
426
  this.props = props;
427
427
  }
428
- }
428
+ };
429
429
  // See Element interface
430
- Element.prototype.$$typeof = ElementSymbol;
430
+ Element$1.prototype.$$typeof = ElementSymbol;
431
431
  function isElement(value) {
432
432
  return value != null && value.$$typeof === ElementSymbol;
433
433
  }
@@ -469,14 +469,14 @@
469
469
  else if (children.length === 1) {
470
470
  props.children = children[0];
471
471
  }
472
- return new Element(tag, props);
472
+ return new Element$1(tag, props);
473
473
  }
474
474
  /** Clones a given element, shallowly copying the props object. */
475
475
  function cloneElement(el) {
476
476
  if (!isElement(el)) {
477
477
  throw new TypeError(`Cannot clone non-element: ${String(el)}`);
478
478
  }
479
- return new Element(el.tag, { ...el.props });
479
+ return new Element$1(el.tag, { ...el.props });
480
480
  }
481
481
  function narrow(value) {
482
482
  if (typeof value === "boolean" || value == null) {
@@ -593,9 +593,15 @@
593
593
  function getChildValues(ret, startIndex) {
594
594
  const values = [];
595
595
  const lingerers = ret.lingerers;
596
- const children = wrap(ret.children);
596
+ const rawChildren = ret.children;
597
+ const isChildrenArray = Array.isArray(rawChildren);
598
+ const childrenLength = rawChildren === undefined
599
+ ? 0
600
+ : isChildrenArray
601
+ ? rawChildren.length
602
+ : 1;
597
603
  let currentIndex = startIndex;
598
- for (let i = 0; i < children.length; i++) {
604
+ for (let i = 0; i < childrenLength; i++) {
599
605
  if (lingerers != null && lingerers[i] != null) {
600
606
  const rets = lingerers[i];
601
607
  for (const ret of rets) {
@@ -616,7 +622,9 @@
616
622
  }
617
623
  }
618
624
  }
619
- const child = children[i];
625
+ const child = isChildrenArray
626
+ ? rawChildren[i]
627
+ : rawChildren;
620
628
  if (child) {
621
629
  const value = getValue(child, true, currentIndex);
622
630
  if (Array.isArray(value)) {
@@ -635,8 +643,8 @@
635
643
  }
636
644
  }
637
645
  }
638
- if (lingerers != null && lingerers.length > children.length) {
639
- for (let i = children.length; i < lingerers.length; i++) {
646
+ if (lingerers != null && lingerers.length > childrenLength) {
647
+ for (let i = childrenLength; i < lingerers.length; i++) {
640
648
  const rets = lingerers[i];
641
649
  if (rets != null) {
642
650
  for (const ret of rets) {
@@ -795,7 +803,155 @@
795
803
  }
796
804
  return adapter.read(unwrap(getChildValues(ret)));
797
805
  }
806
+ function diffChild(adapter, root, host, ctx, scope, parent, newChildren) {
807
+ let child = narrow(newChildren);
808
+ let ret = parent.children;
809
+ let graveyard;
810
+ let diff;
811
+ if (typeof child === "object") {
812
+ let childCopied = false;
813
+ // Check key match
814
+ const oldKey = typeof ret === "object" ? ret.el.props.key : undefined;
815
+ const newKey = child.props.key;
816
+ if (oldKey !== newKey) {
817
+ if (typeof ret === "object") {
818
+ (graveyard = graveyard || []).push(ret);
819
+ }
820
+ ret = undefined;
821
+ }
822
+ if (child.tag === Copy) {
823
+ childCopied = true;
824
+ }
825
+ else if (typeof ret === "object" &&
826
+ ret.el === child &&
827
+ getFlag(ret, DidCommit)) {
828
+ childCopied = true;
829
+ }
830
+ else {
831
+ if (ret && ret.el.tag === child.tag) {
832
+ ret.el = child;
833
+ if (child.props.copy && typeof child.props.copy !== "string") {
834
+ childCopied = true;
835
+ }
836
+ }
837
+ else if (ret) {
838
+ let candidateFound = false;
839
+ for (let predecessor = ret, candidate = ret.fallback; candidate; predecessor = candidate, candidate = candidate.fallback) {
840
+ if (candidate.el.tag === child.tag) {
841
+ const clone = cloneRetainer(candidate);
842
+ setFlag(clone, IsResurrecting);
843
+ predecessor.fallback = clone;
844
+ const fallback = ret;
845
+ ret = candidate;
846
+ ret.el = child;
847
+ ret.fallback = fallback;
848
+ setFlag(ret, DidDiff, false);
849
+ candidateFound = true;
850
+ break;
851
+ }
852
+ }
853
+ if (!candidateFound) {
854
+ const fallback = ret;
855
+ ret = new Retainer(child);
856
+ ret.fallback = fallback;
857
+ }
858
+ }
859
+ else {
860
+ ret = new Retainer(child);
861
+ }
862
+ if (childCopied && getFlag(ret, DidCommit)) ;
863
+ else if (child.tag === Raw || child.tag === Text) ;
864
+ else if (child.tag === Fragment) {
865
+ diff = diffChildren(adapter, root, host, ctx, scope, ret, ret.el.props.children);
866
+ }
867
+ else if (typeof child.tag === "function") {
868
+ diff = diffComponent(adapter, root, host, ctx, scope, ret);
869
+ }
870
+ else {
871
+ diff = diffHost(adapter, root, ctx, scope, ret);
872
+ }
873
+ }
874
+ if (typeof ret === "object") {
875
+ if (childCopied) {
876
+ setFlag(ret, IsCopied);
877
+ diff = getInflightDiff(ret);
878
+ }
879
+ else {
880
+ setFlag(ret, IsCopied, false);
881
+ }
882
+ }
883
+ }
884
+ else if (typeof child === "string") {
885
+ if (typeof ret === "object" && ret.el.tag === Text) {
886
+ ret.el.props.value = child;
887
+ }
888
+ else {
889
+ if (typeof ret === "object") {
890
+ (graveyard = graveyard || []).push(ret);
891
+ }
892
+ ret = new Retainer(createElement(Text, { value: child }));
893
+ }
894
+ }
895
+ else {
896
+ if (typeof ret === "object") {
897
+ (graveyard = graveyard || []).push(ret);
898
+ }
899
+ ret = undefined;
900
+ }
901
+ parent.children = ret;
902
+ if (isPromiseLike(diff)) {
903
+ const diff1 = diff.finally(() => {
904
+ setFlag(parent, DidDiff);
905
+ if (graveyard) {
906
+ if (parent.graveyard) {
907
+ for (let i = 0; i < graveyard.length; i++) {
908
+ parent.graveyard.push(graveyard[i]);
909
+ }
910
+ }
911
+ else {
912
+ parent.graveyard = graveyard;
913
+ }
914
+ }
915
+ });
916
+ let onNextDiffs;
917
+ const diff2 = (parent.pendingDiff = safeRace([
918
+ diff1,
919
+ new Promise((resolve) => (onNextDiffs = resolve)),
920
+ ]));
921
+ if (parent.onNextDiff) {
922
+ parent.onNextDiff(diff2);
923
+ }
924
+ parent.onNextDiff = onNextDiffs;
925
+ return diff2;
926
+ }
927
+ else {
928
+ setFlag(parent, DidDiff);
929
+ if (graveyard) {
930
+ if (parent.graveyard) {
931
+ for (let i = 0; i < graveyard.length; i++) {
932
+ parent.graveyard.push(graveyard[i]);
933
+ }
934
+ }
935
+ else {
936
+ parent.graveyard = graveyard;
937
+ }
938
+ }
939
+ if (parent.onNextDiff) {
940
+ parent.onNextDiff(diff);
941
+ parent.onNextDiff = undefined;
942
+ }
943
+ parent.pendingDiff = undefined;
944
+ }
945
+ }
798
946
  function diffChildren(adapter, root, host, ctx, scope, parent, newChildren) {
947
+ // Fast path for the common single non-keyed child case
948
+ if (!Array.isArray(newChildren) &&
949
+ (typeof newChildren !== "object" ||
950
+ newChildren === null ||
951
+ typeof newChildren[Symbol.iterator] !== "function") &&
952
+ !Array.isArray(parent.children)) {
953
+ return diffChild(adapter, root, host, ctx, scope, parent, newChildren);
954
+ }
799
955
  const oldRetained = wrap(parent.children);
800
956
  const newRetained = [];
801
957
  const newChildren1 = arrayify(newChildren);
@@ -1096,7 +1252,7 @@
1096
1252
  }
1097
1253
  }
1098
1254
  if (skippedHydrationNodes) {
1099
- skippedHydrationNodes.splice(0, wrap(value).length);
1255
+ skippedHydrationNodes.splice(0, value == null ? 0 : Array.isArray(value) ? value.length : 1);
1100
1256
  }
1101
1257
  if (!getFlag(ret, DidCommit)) {
1102
1258
  setFlag(ret, DidCommit);
@@ -1111,8 +1267,17 @@
1111
1267
  }
1112
1268
  function commitChildren(adapter, host, ctx, scope, root, parent, index, schedulePromises, hydrationNodes) {
1113
1269
  let values = [];
1114
- for (let i = 0, children = wrap(parent.children); i < children.length; i++) {
1115
- let child = children[i];
1270
+ const rawChildren = parent.children;
1271
+ const isChildrenArray = Array.isArray(rawChildren);
1272
+ const childrenLength = rawChildren === undefined
1273
+ ? 0
1274
+ : isChildrenArray
1275
+ ? rawChildren.length
1276
+ : 1;
1277
+ for (let i = 0; i < childrenLength; i++) {
1278
+ let child = isChildrenArray
1279
+ ? rawChildren[i]
1280
+ : rawChildren;
1116
1281
  let schedulePromises1;
1117
1282
  let isSchedulingFallback = false;
1118
1283
  while (child &&
@@ -1397,6 +1562,7 @@
1397
1562
  props,
1398
1563
  children,
1399
1564
  oldProps,
1565
+ scope,
1400
1566
  root,
1401
1567
  });
1402
1568
  }
@@ -1554,12 +1720,18 @@
1554
1720
  }
1555
1721
  ret.graveyard = undefined;
1556
1722
  }
1557
- for (let i = 0, children = wrap(ret.children); i < children.length; i++) {
1558
- const child = children[i];
1559
- if (typeof child === "object") {
1560
- unmount(adapter, host, ctx, root, child, isNested);
1723
+ const rawChildren = ret.children;
1724
+ if (Array.isArray(rawChildren)) {
1725
+ for (let i = 0; i < rawChildren.length; i++) {
1726
+ const child = rawChildren[i];
1727
+ if (typeof child === "object") {
1728
+ unmount(adapter, host, ctx, root, child, isNested);
1729
+ }
1561
1730
  }
1562
1731
  }
1732
+ else if (rawChildren !== undefined) {
1733
+ unmount(adapter, host, ctx, root, rawChildren, isNested);
1734
+ }
1563
1735
  }
1564
1736
  const provisionMaps = new WeakMap();
1565
1737
  const scheduleMap = new WeakMap();
@@ -2429,12 +2601,16 @@
2429
2601
  ((typeof current.el.tag === "string" && current.el.tag !== Fragment) ||
2430
2602
  current.el.tag === Portal);
2431
2603
  if (current.children && !isHostBoundary) {
2432
- const children = wrap(current.children);
2433
- for (const child of children) {
2434
- if (child) {
2435
- stack.push(child);
2604
+ if (Array.isArray(current.children)) {
2605
+ for (const child of current.children) {
2606
+ if (child) {
2607
+ stack.push(child);
2608
+ }
2436
2609
  }
2437
2610
  }
2611
+ else {
2612
+ stack.push(current.children);
2613
+ }
2438
2614
  }
2439
2615
  // Add fallback chains (only if current retainer is using fallback)
2440
2616
  if (current.fallback && !getFlag(current, DidDiff)) {
@@ -2458,6 +2634,11 @@
2458
2634
  if (!isRetainerActive(initiator, host)) {
2459
2635
  return;
2460
2636
  }
2637
+ // Check if host has been committed (has a node)
2638
+ // Fixes #334: refresh() called before component yields
2639
+ if (!getFlag(host, DidCommit)) {
2640
+ return;
2641
+ }
2461
2642
  const props = stripSpecialProps(host.el.props);
2462
2643
  const hostChildren = getChildValues(host, 0);
2463
2644
  ctx.adapter.arrange({
@@ -2467,6 +2648,7 @@
2467
2648
  props,
2468
2649
  oldProps: props,
2469
2650
  children: hostChildren,
2651
+ scope: host.scope,
2470
2652
  root: ctx.root,
2471
2653
  });
2472
2654
  flush(ctx.adapter, ctx.root, ctx);
@@ -2741,6 +2923,96 @@
2741
2923
  return String(value);
2742
2924
  }
2743
2925
 
2926
+ // React SVG camelCase → standard SVG attribute mapping.
2927
+ // Only includes entries where the React name differs from the SVG attribute name.
2928
+ // Canonical source: https://github.com/facebook/react/blob/main/packages/react-dom-bindings/src/shared/possibleStandardNames.js
2929
+ const REACT_SVG_PROPS = {
2930
+ accentHeight: "accent-height",
2931
+ alignmentBaseline: "alignment-baseline",
2932
+ arabicForm: "arabic-form",
2933
+ baselineShift: "baseline-shift",
2934
+ capHeight: "cap-height",
2935
+ clipPath: "clip-path",
2936
+ clipRule: "clip-rule",
2937
+ colorInterpolation: "color-interpolation",
2938
+ colorInterpolationFilters: "color-interpolation-filters",
2939
+ colorProfile: "color-profile",
2940
+ colorRendering: "color-rendering",
2941
+ dominantBaseline: "dominant-baseline",
2942
+ enableBackground: "enable-background",
2943
+ fillOpacity: "fill-opacity",
2944
+ fillRule: "fill-rule",
2945
+ floodColor: "flood-color",
2946
+ floodOpacity: "flood-opacity",
2947
+ fontFamily: "font-family",
2948
+ fontSize: "font-size",
2949
+ fontSizeAdjust: "font-size-adjust",
2950
+ fontStretch: "font-stretch",
2951
+ fontStyle: "font-style",
2952
+ fontVariant: "font-variant",
2953
+ fontWeight: "font-weight",
2954
+ glyphName: "glyph-name",
2955
+ glyphOrientationHorizontal: "glyph-orientation-horizontal",
2956
+ glyphOrientationVertical: "glyph-orientation-vertical",
2957
+ horizAdvX: "horiz-adv-x",
2958
+ horizOriginX: "horiz-origin-x",
2959
+ imageRendering: "image-rendering",
2960
+ letterSpacing: "letter-spacing",
2961
+ lightingColor: "lighting-color",
2962
+ markerEnd: "marker-end",
2963
+ markerMid: "marker-mid",
2964
+ markerStart: "marker-start",
2965
+ overlinePosition: "overline-position",
2966
+ overlineThickness: "overline-thickness",
2967
+ paintOrder: "paint-order",
2968
+ pointerEvents: "pointer-events",
2969
+ renderingIntent: "rendering-intent",
2970
+ shapeRendering: "shape-rendering",
2971
+ stopColor: "stop-color",
2972
+ stopOpacity: "stop-opacity",
2973
+ strikethroughPosition: "strikethrough-position",
2974
+ strikethroughThickness: "strikethrough-thickness",
2975
+ strokeDasharray: "stroke-dasharray",
2976
+ strokeDashoffset: "stroke-dashoffset",
2977
+ strokeLinecap: "stroke-linecap",
2978
+ strokeLinejoin: "stroke-linejoin",
2979
+ strokeMiterlimit: "stroke-miterlimit",
2980
+ strokeOpacity: "stroke-opacity",
2981
+ strokeWidth: "stroke-width",
2982
+ textAnchor: "text-anchor",
2983
+ textDecoration: "text-decoration",
2984
+ textRendering: "text-rendering",
2985
+ transformOrigin: "transform-origin",
2986
+ underlinePosition: "underline-position",
2987
+ underlineThickness: "underline-thickness",
2988
+ unicodeBidi: "unicode-bidi",
2989
+ unicodeRange: "unicode-range",
2990
+ unitsPerEm: "units-per-em",
2991
+ vAlphabetic: "v-alphabetic",
2992
+ vHanging: "v-hanging",
2993
+ vIdeographic: "v-ideographic",
2994
+ vMathematical: "v-mathematical",
2995
+ vectorEffect: "vector-effect",
2996
+ vertAdvY: "vert-adv-y",
2997
+ vertOriginX: "vert-origin-x",
2998
+ vertOriginY: "vert-origin-y",
2999
+ wordSpacing: "word-spacing",
3000
+ writingMode: "writing-mode",
3001
+ xHeight: "x-height",
3002
+ // xlink/xml namespace attributes (deprecated in SVG 2 but still used)
3003
+ xlinkActuate: "xlink:actuate",
3004
+ xlinkArcrole: "xlink:arcrole",
3005
+ xlinkHref: "xlink:href",
3006
+ xlinkRole: "xlink:role",
3007
+ xlinkShow: "xlink:show",
3008
+ xlinkTitle: "xlink:title",
3009
+ xlinkType: "xlink:type",
3010
+ xmlBase: "xml:base",
3011
+ xmlLang: "xml:lang",
3012
+ xmlSpace: "xml:space",
3013
+ xmlnsXlink: "xmlns:xlink",
3014
+ };
3015
+
2744
3016
  const SVG_NAMESPACE = "http://www.w3.org/2000/svg";
2745
3017
  const MATHML_NAMESPACE = "http://www.w3.org/1998/Math/MathML";
2746
3018
  function getRootDocument(root) {
@@ -2798,19 +3070,325 @@
2798
3070
  }
2799
3071
  }
2800
3072
  }
3073
+ function patchProp(element, name, value, oldValue, props, isSVG, isMathML, copyProps, quietProps, isHydrating) {
3074
+ if (copyProps != null && copyProps.has(name)) {
3075
+ return;
3076
+ }
3077
+ // handle prop:name or attr:name properties
3078
+ const colonIndex = name.indexOf(":");
3079
+ if (colonIndex !== -1) {
3080
+ const [ns, name1] = [name.slice(0, colonIndex), name.slice(colonIndex + 1)];
3081
+ switch (ns) {
3082
+ case "prop":
3083
+ element[name1] = value;
3084
+ return;
3085
+ case "attr":
3086
+ if (value == null || value === false) {
3087
+ if (isHydrating && element.hasAttribute(name1)) {
3088
+ emitHydrationWarning(name, quietProps, value, element.getAttribute(name1), element);
3089
+ }
3090
+ element.removeAttribute(name1);
3091
+ return;
3092
+ }
3093
+ else if (value === true) {
3094
+ if (isHydrating && !element.hasAttribute(name1)) {
3095
+ emitHydrationWarning(name, quietProps, value, null, element);
3096
+ }
3097
+ element.setAttribute(name1, "");
3098
+ return;
3099
+ }
3100
+ if (typeof value !== "string") {
3101
+ value = String(value);
3102
+ }
3103
+ if (isHydrating && element.getAttribute(name1) !== value) {
3104
+ emitHydrationWarning(name, quietProps, value, element.getAttribute(name1), element);
3105
+ }
3106
+ element.setAttribute(name1, value);
3107
+ return;
3108
+ }
3109
+ }
3110
+ switch (name) {
3111
+ // TODO: fix hydration warnings for the style prop
3112
+ case "style": {
3113
+ const style = element.style;
3114
+ if (value == null || value === false) {
3115
+ if (isHydrating && style.cssText !== "") {
3116
+ emitHydrationWarning(name, quietProps, value, style.cssText, element);
3117
+ }
3118
+ element.removeAttribute("style");
3119
+ }
3120
+ else if (value === true) {
3121
+ if (isHydrating && style.cssText !== "") {
3122
+ emitHydrationWarning(name, quietProps, "", style.cssText, element);
3123
+ }
3124
+ element.setAttribute("style", "");
3125
+ }
3126
+ else if (typeof value === "string") {
3127
+ if (style.cssText !== value) {
3128
+ // TODO: Fix hydration warnings for styles
3129
+ //if (isHydrating) {
3130
+ // emitHydrationWarning(
3131
+ // name,
3132
+ // quietProps,
3133
+ // value,
3134
+ // style.cssText,
3135
+ // element,
3136
+ // );
3137
+ //}
3138
+ style.cssText = value;
3139
+ }
3140
+ }
3141
+ else {
3142
+ if (typeof oldValue === "string") {
3143
+ // if the old value was a string, we need to clear the style
3144
+ // TODO: only clear the styles enumerated in the old value
3145
+ style.cssText = "";
3146
+ }
3147
+ // First pass: remove styles present in oldValue but not in value
3148
+ if (oldValue) {
3149
+ for (const styleName in oldValue) {
3150
+ if (value && styleName in value)
3151
+ continue;
3152
+ const cssName = camelToKebabCase(styleName);
3153
+ if (isHydrating && style.getPropertyValue(cssName) !== "") {
3154
+ emitHydrationWarning(name, quietProps, null, style.getPropertyValue(cssName), element, `style.${styleName}`);
3155
+ }
3156
+ style.removeProperty(cssName);
3157
+ }
3158
+ }
3159
+ // Second pass: apply all styles from value
3160
+ if (value) {
3161
+ for (const styleName in value) {
3162
+ const cssName = camelToKebabCase(styleName);
3163
+ const styleValue = value[styleName];
3164
+ if (styleValue == null) {
3165
+ if (isHydrating && style.getPropertyValue(cssName) !== "") {
3166
+ emitHydrationWarning(name, quietProps, null, style.getPropertyValue(cssName), element, `style.${styleName}`);
3167
+ }
3168
+ style.removeProperty(cssName);
3169
+ }
3170
+ else {
3171
+ const formattedValue = formatStyleValue(cssName, styleValue);
3172
+ if (style.getPropertyValue(cssName) !== formattedValue) {
3173
+ // TODO: hydration warnings for style props
3174
+ //if (isHydrating) {
3175
+ // emitHydrationWarning(
3176
+ // name,
3177
+ // quietProps,
3178
+ // formattedValue,
3179
+ // style.getPropertyValue(cssName),
3180
+ // element,
3181
+ // `style.${styleName}`,
3182
+ // );
3183
+ //}
3184
+ style.setProperty(cssName, formattedValue);
3185
+ }
3186
+ }
3187
+ }
3188
+ }
3189
+ }
3190
+ break;
3191
+ }
3192
+ case "class":
3193
+ case "className":
3194
+ if (name === "className" && "class" in props)
3195
+ break;
3196
+ if (value === true) {
3197
+ if (isHydrating && element.getAttribute("class") !== "") {
3198
+ emitHydrationWarning(name, quietProps, "", element.getAttribute("class"), element);
3199
+ }
3200
+ element.setAttribute("class", "");
3201
+ }
3202
+ else if (value == null) {
3203
+ if (isHydrating && element.hasAttribute("class")) {
3204
+ emitHydrationWarning(name, quietProps, value, element.getAttribute("class"), element);
3205
+ }
3206
+ element.removeAttribute("class");
3207
+ }
3208
+ else if (typeof value === "object") {
3209
+ // class={{"included-class": true, "excluded-class": false}} syntax
3210
+ if (typeof oldValue === "string") {
3211
+ // if the old value was a string, we need to clear all classes
3212
+ element.setAttribute("class", "");
3213
+ }
3214
+ let shouldIssueWarning = false;
3215
+ const hydratingClasses = isHydrating
3216
+ ? new Set(Array.from(element.classList))
3217
+ : undefined;
3218
+ const hydratingClassName = isHydrating
3219
+ ? element.getAttribute("class")
3220
+ : undefined;
3221
+ // Two passes: removes first, then adds. This ensures that
3222
+ // overlapping classes in different keys are handled correctly.
3223
+ // e.g. {"a b": false, "b c": true} should result in "b c"
3224
+ // Remove pass: iterate oldValue for classes to remove
3225
+ if (oldValue) {
3226
+ for (const classNames in oldValue) {
3227
+ if (value && value[classNames])
3228
+ continue;
3229
+ const classes = classNames.split(/\s+/).filter(Boolean);
3230
+ element.classList.remove(...classes);
3231
+ }
3232
+ }
3233
+ // Add pass: iterate value for classes to add
3234
+ if (value) {
3235
+ for (const classNames in value) {
3236
+ if (!value[classNames])
3237
+ continue;
3238
+ const classes = classNames.split(/\s+/).filter(Boolean);
3239
+ element.classList.add(...classes);
3240
+ for (const className of classes) {
3241
+ if (hydratingClasses && hydratingClasses.has(className)) {
3242
+ hydratingClasses.delete(className);
3243
+ }
3244
+ else if (isHydrating) {
3245
+ shouldIssueWarning = true;
3246
+ }
3247
+ }
3248
+ }
3249
+ }
3250
+ if (shouldIssueWarning ||
3251
+ (hydratingClasses && hydratingClasses.size > 0)) {
3252
+ emitHydrationWarning(name, quietProps, Object.keys(value)
3253
+ .filter((k) => value[k])
3254
+ .join(" "), hydratingClassName || "", element);
3255
+ }
3256
+ }
3257
+ else if (!isSVG && !isMathML) {
3258
+ if (element.className !== value) {
3259
+ if (isHydrating) {
3260
+ emitHydrationWarning(name, quietProps, value, element.className, element);
3261
+ }
3262
+ element.className = value;
3263
+ }
3264
+ }
3265
+ else if (element.getAttribute("class") !== value) {
3266
+ if (isHydrating) {
3267
+ emitHydrationWarning(name, quietProps, value, element.getAttribute("class"), element);
3268
+ }
3269
+ element.setAttribute("class", value);
3270
+ }
3271
+ break;
3272
+ case "innerHTML":
3273
+ if (value !== oldValue) {
3274
+ if (isHydrating) {
3275
+ emitHydrationWarning(name, quietProps, value, element.innerHTML, element);
3276
+ }
3277
+ element.innerHTML = value;
3278
+ }
3279
+ break;
3280
+ case "dangerouslySetInnerHTML": {
3281
+ const htmlValue = value && typeof value === "object" && "__html" in value
3282
+ ? (value.__html ?? "")
3283
+ : "";
3284
+ const oldHtmlValue = oldValue && typeof oldValue === "object" && "__html" in oldValue
3285
+ ? (oldValue.__html ?? "")
3286
+ : "";
3287
+ if (htmlValue !== oldHtmlValue) {
3288
+ element.innerHTML = htmlValue;
3289
+ }
3290
+ break;
3291
+ }
3292
+ case "htmlFor":
3293
+ if ("for" in props)
3294
+ break;
3295
+ if (value == null || value === false) {
3296
+ element.removeAttribute("for");
3297
+ }
3298
+ else {
3299
+ element.setAttribute("for", String(value === true ? "" : value));
3300
+ }
3301
+ break;
3302
+ default: {
3303
+ if (name[0] === "o" &&
3304
+ name[1] === "n" &&
3305
+ name[2] === name[2].toUpperCase() &&
3306
+ typeof value === "function") {
3307
+ // Support React-style event names (onClick, onChange, etc.)
3308
+ name = name.toLowerCase();
3309
+ }
3310
+ // Support React-style SVG attribute names (strokeWidth, etc.)
3311
+ if (isSVG && name in REACT_SVG_PROPS) {
3312
+ name = REACT_SVG_PROPS[name];
3313
+ }
3314
+ // try to set the property directly
3315
+ if (name in element &&
3316
+ // boolean properties will coerce strings, but sometimes they map to
3317
+ // enumerated attributes, where truthy strings ("false", "no") map to
3318
+ // falsy properties, so we force using setAttribute.
3319
+ !(typeof value === "string" &&
3320
+ typeof element[name] === "boolean") &&
3321
+ isWritableProperty(element, name)) {
3322
+ // For URL properties like src and href, the DOM property returns the
3323
+ // resolved absolute URL. We need to resolve the prop value the same way
3324
+ // to compare correctly.
3325
+ let domValue = element[name];
3326
+ let propValue = value;
3327
+ if ((name === "src" || name === "href") &&
3328
+ typeof value === "string" &&
3329
+ typeof domValue === "string") {
3330
+ try {
3331
+ propValue = new URL(value, element.baseURI).href;
3332
+ }
3333
+ catch {
3334
+ // Invalid URL, use original value for comparison
3335
+ }
3336
+ }
3337
+ if (propValue !== domValue || oldValue === undefined) {
3338
+ if (isHydrating &&
3339
+ typeof element[name] === "string" &&
3340
+ element[name] !== value) {
3341
+ emitHydrationWarning(name, quietProps, value, element[name], element);
3342
+ }
3343
+ // if the property is writable, assign it directly
3344
+ element[name] = value;
3345
+ }
3346
+ return;
3347
+ }
3348
+ if (value === true) {
3349
+ value = "";
3350
+ }
3351
+ else if (value == null || value === false) {
3352
+ if (isHydrating && element.hasAttribute(name)) {
3353
+ emitHydrationWarning(name, quietProps, value, element.getAttribute(name), element);
3354
+ }
3355
+ element.removeAttribute(name);
3356
+ return;
3357
+ }
3358
+ else if (typeof value !== "string") {
3359
+ value = String(value);
3360
+ }
3361
+ if (element.getAttribute(name) !== value) {
3362
+ if (isHydrating) {
3363
+ emitHydrationWarning(name, quietProps, value, element.getAttribute(name), element);
3364
+ }
3365
+ element.setAttribute(name, value);
3366
+ }
3367
+ }
3368
+ }
3369
+ }
2801
3370
  const adapter = {
2802
- scope({ scope: xmlns, tag, props, }) {
3371
+ scope({ scope: xmlns, tag, props, root, }) {
2803
3372
  switch (tag) {
2804
- case Portal:
2805
- // TODO: read the namespace from the portal root element
2806
- xmlns = undefined;
3373
+ case Portal: {
3374
+ const ns = root instanceof Element ? root.namespaceURI : null;
3375
+ xmlns =
3376
+ ns === SVG_NAMESPACE
3377
+ ? SVG_NAMESPACE
3378
+ : ns === MATHML_NAMESPACE
3379
+ ? MATHML_NAMESPACE
3380
+ : undefined;
2807
3381
  break;
3382
+ }
2808
3383
  case "svg":
2809
3384
  xmlns = SVG_NAMESPACE;
2810
3385
  break;
2811
3386
  case "math":
2812
3387
  xmlns = MATHML_NAMESPACE;
2813
3388
  break;
3389
+ case "foreignObject":
3390
+ xmlns = undefined;
3391
+ break;
2814
3392
  }
2815
3393
  return props.xmlns || xmlns;
2816
3394
  },
@@ -2824,6 +3402,9 @@
2824
3402
  else if (tag.toLowerCase() === "math") {
2825
3403
  xmlns = MATHML_NAMESPACE;
2826
3404
  }
3405
+ else if (tag === "foreignObject") {
3406
+ xmlns = SVG_NAMESPACE;
3407
+ }
2827
3408
  const doc = getRootDocument(root);
2828
3409
  return xmlns ? doc.createElementNS(xmlns, tag) : doc.createElement(tag);
2829
3410
  },
@@ -2847,7 +3428,7 @@
2847
3428
  }
2848
3429
  return Array.from(node.childNodes);
2849
3430
  },
2850
- patch({ tagName, node, props, oldProps, scope: xmlns, copyProps, quietProps, isHydrating, }) {
3431
+ patch({ tag, tagName, node, props, oldProps, scope: xmlns, copyProps, quietProps, isHydrating, }) {
2851
3432
  if (node.nodeType !== Node.ELEMENT_NODE) {
2852
3433
  throw new TypeError(`Cannot patch node: ${String(node)}`);
2853
3434
  }
@@ -2855,268 +3436,26 @@
2855
3436
  console.error(`Both "class" and "className" set in props for <${tagName}>. Use one or the other.`);
2856
3437
  }
2857
3438
  const element = node;
2858
- const isSVG = xmlns === SVG_NAMESPACE;
3439
+ const isSVG = xmlns === SVG_NAMESPACE || tag === "foreignObject";
2859
3440
  const isMathML = xmlns === MATHML_NAMESPACE;
2860
- for (let name in { ...oldProps, ...props }) {
2861
- let value = props[name];
2862
- const oldValue = oldProps ? oldProps[name] : undefined;
2863
- {
2864
- if (copyProps != null && copyProps.has(name)) {
3441
+ // First pass: iterate oldProps to handle removals
3442
+ if (oldProps) {
3443
+ for (let name in oldProps) {
3444
+ if (name in props)
2865
3445
  continue;
2866
- }
2867
- // handle prop:name or attr:name properties
2868
- const colonIndex = name.indexOf(":");
2869
- if (colonIndex !== -1) {
2870
- const [ns, name1] = [
2871
- name.slice(0, colonIndex),
2872
- name.slice(colonIndex + 1),
2873
- ];
2874
- switch (ns) {
2875
- case "prop":
2876
- node[name1] = value;
2877
- continue;
2878
- case "attr":
2879
- if (value == null || value === false) {
2880
- if (isHydrating && element.hasAttribute(name1)) {
2881
- emitHydrationWarning(name, quietProps, value, element.getAttribute(name1), element);
2882
- }
2883
- element.removeAttribute(name1);
2884
- }
2885
- else if (value === true) {
2886
- if (isHydrating && !element.hasAttribute(name1)) {
2887
- emitHydrationWarning(name, quietProps, value, null, element);
2888
- }
2889
- element.setAttribute(name1, "");
2890
- }
2891
- else if (typeof value !== "string") {
2892
- value = String(value);
2893
- }
2894
- if (isHydrating && element.getAttribute(name1) !== value) {
2895
- emitHydrationWarning(name, quietProps, value, element.getAttribute(name1), element);
2896
- }
2897
- element.setAttribute(name1, String(value));
2898
- continue;
2899
- }
2900
- }
2901
- }
2902
- switch (name) {
2903
- // TODO: fix hydration warnings for the style prop
2904
- case "style": {
2905
- const style = element.style;
2906
- if (value == null || value === false) {
2907
- if (isHydrating && style.cssText !== "") {
2908
- emitHydrationWarning(name, quietProps, value, style.cssText, element);
2909
- }
2910
- element.removeAttribute("style");
2911
- }
2912
- else if (value === true) {
2913
- if (isHydrating && style.cssText !== "") {
2914
- emitHydrationWarning(name, quietProps, "", style.cssText, element);
2915
- }
2916
- element.setAttribute("style", "");
2917
- }
2918
- else if (typeof value === "string") {
2919
- if (style.cssText !== value) {
2920
- // TODO: Fix hydration warnings for styles
2921
- //if (isHydrating) {
2922
- // emitHydrationWarning(
2923
- // name,
2924
- // quietProps,
2925
- // value,
2926
- // style.cssText,
2927
- // element,
2928
- // );
2929
- //}
2930
- style.cssText = value;
2931
- }
2932
- }
2933
- else {
2934
- if (typeof oldValue === "string") {
2935
- // if the old value was a string, we need to clear the style
2936
- // TODO: only clear the styles enumerated in the old value
2937
- style.cssText = "";
2938
- }
2939
- for (const styleName in { ...oldValue, ...value }) {
2940
- const cssName = camelToKebabCase(styleName);
2941
- const styleValue = value && value[styleName];
2942
- if (styleValue == null) {
2943
- if (isHydrating && style.getPropertyValue(cssName) !== "") {
2944
- emitHydrationWarning(name, quietProps, null, style.getPropertyValue(cssName), element, `style.${styleName}`);
2945
- }
2946
- style.removeProperty(cssName);
2947
- }
2948
- else {
2949
- const formattedValue = formatStyleValue(cssName, styleValue);
2950
- if (style.getPropertyValue(cssName) !== formattedValue) {
2951
- // TODO: hydration warnings for style props
2952
- //if (isHydrating) {
2953
- // emitHydrationWarning(
2954
- // name,
2955
- // quietProps,
2956
- // formattedValue,
2957
- // style.getPropertyValue(cssName),
2958
- // element,
2959
- // `style.${styleName}`,
2960
- // );
2961
- //}
2962
- style.setProperty(cssName, formattedValue);
2963
- }
2964
- }
2965
- }
2966
- }
2967
- break;
2968
- }
2969
- case "class":
2970
- case "className":
2971
- if (value === true) {
2972
- if (isHydrating && element.getAttribute("class") !== "") {
2973
- emitHydrationWarning(name, quietProps, "", element.getAttribute("class"), element);
2974
- }
2975
- element.setAttribute("class", "");
2976
- }
2977
- else if (value == null) {
2978
- if (isHydrating && element.hasAttribute("class")) {
2979
- emitHydrationWarning(name, quietProps, value, element.getAttribute("class"), element);
2980
- }
2981
- element.removeAttribute("class");
2982
- }
2983
- else if (typeof value === "object") {
2984
- // class={{"included-class": true, "excluded-class": false}} syntax
2985
- if (typeof oldValue === "string") {
2986
- // if the old value was a string, we need to clear all classes
2987
- element.setAttribute("class", "");
2988
- }
2989
- let shouldIssueWarning = false;
2990
- const hydratingClasses = isHydrating
2991
- ? new Set(Array.from(element.classList))
2992
- : undefined;
2993
- const hydratingClassName = isHydrating
2994
- ? element.getAttribute("class")
2995
- : undefined;
2996
- const allClassNames = { ...oldValue, ...value };
2997
- // Two passes: removes first, then adds. This ensures that
2998
- // overlapping classes in different keys are handled correctly.
2999
- // e.g. {"a b": false, "b c": true} should result in "b c"
3000
- for (const classNames in allClassNames) {
3001
- if (!(value && value[classNames])) {
3002
- const classes = classNames.split(/\s+/).filter(Boolean);
3003
- element.classList.remove(...classes);
3004
- }
3005
- }
3006
- for (const classNames in allClassNames) {
3007
- if (value && value[classNames]) {
3008
- const classes = classNames.split(/\s+/).filter(Boolean);
3009
- element.classList.add(...classes);
3010
- for (const className of classes) {
3011
- if (hydratingClasses && hydratingClasses.has(className)) {
3012
- hydratingClasses.delete(className);
3013
- }
3014
- else if (isHydrating) {
3015
- shouldIssueWarning = true;
3016
- }
3017
- }
3018
- }
3019
- }
3020
- if (shouldIssueWarning ||
3021
- (hydratingClasses && hydratingClasses.size > 0)) {
3022
- emitHydrationWarning(name, quietProps, Object.keys(value)
3023
- .filter((k) => value[k])
3024
- .join(" "), hydratingClassName || "", element);
3025
- }
3026
- }
3027
- else if (!isSVG && !isMathML) {
3028
- if (element.className !== value) {
3029
- if (isHydrating) {
3030
- emitHydrationWarning(name, quietProps, value, element.className, element);
3031
- }
3032
- element.className = value;
3033
- }
3034
- }
3035
- else if (element.getAttribute("class") !== value) {
3036
- if (isHydrating) {
3037
- emitHydrationWarning(name, quietProps, value, element.getAttribute("class"), element);
3038
- }
3039
- element.setAttribute("class", value);
3040
- }
3041
- break;
3042
- case "innerHTML":
3043
- if (value !== oldValue) {
3044
- if (isHydrating) {
3045
- emitHydrationWarning(name, quietProps, value, element.innerHTML, element);
3046
- }
3047
- element.innerHTML = value;
3048
- }
3049
- break;
3050
- default: {
3051
- if (name[0] === "o" &&
3052
- name[1] === "n" &&
3053
- name[2] === name[2].toUpperCase() &&
3054
- typeof value === "function") {
3055
- // Support React-style event names (onClick, onChange, etc.)
3056
- name = name.toLowerCase();
3057
- }
3058
- // try to set the property directly
3059
- if (name in element &&
3060
- // boolean properties will coerce strings, but sometimes they map to
3061
- // enumerated attributes, where truthy strings ("false", "no") map to
3062
- // falsy properties, so we force using setAttribute.
3063
- !(typeof value === "string" &&
3064
- typeof element[name] === "boolean") &&
3065
- isWritableProperty(element, name)) {
3066
- // For URL properties like src and href, the DOM property returns the
3067
- // resolved absolute URL. We need to resolve the prop value the same way
3068
- // to compare correctly.
3069
- let domValue = element[name];
3070
- let propValue = value;
3071
- if ((name === "src" || name === "href") &&
3072
- typeof value === "string" &&
3073
- typeof domValue === "string") {
3074
- try {
3075
- propValue = new URL(value, element.baseURI).href;
3076
- }
3077
- catch {
3078
- // Invalid URL, use original value for comparison
3079
- }
3080
- }
3081
- if (propValue !== domValue || oldValue === undefined) {
3082
- if (isHydrating &&
3083
- typeof element[name] === "string" &&
3084
- element[name] !== value) {
3085
- emitHydrationWarning(name, quietProps, value, element[name], element);
3086
- }
3087
- // if the property is writable, assign it directly
3088
- element[name] = value;
3089
- }
3090
- continue;
3091
- }
3092
- if (value === true) {
3093
- value = "";
3094
- }
3095
- else if (value == null || value === false) {
3096
- if (isHydrating && element.hasAttribute(name)) {
3097
- emitHydrationWarning(name, quietProps, value, element.getAttribute(name), element);
3098
- }
3099
- element.removeAttribute(name);
3100
- continue;
3101
- }
3102
- else if (typeof value !== "string") {
3103
- value = String(value);
3104
- }
3105
- if (element.getAttribute(name) !== value) {
3106
- if (isHydrating) {
3107
- emitHydrationWarning(name, quietProps, value, element.getAttribute(name), element);
3108
- }
3109
- element.setAttribute(name, value);
3110
- }
3111
- }
3446
+ patchProp(element, name, undefined, oldProps[name], props, isSVG, isMathML, copyProps, quietProps, isHydrating);
3112
3447
  }
3113
3448
  }
3449
+ // Second pass: iterate props to handle additions and updates
3450
+ for (let name in props) {
3451
+ patchProp(element, name, props[name], oldProps ? oldProps[name] : undefined, props, isSVG, isMathML, copyProps, quietProps, isHydrating);
3452
+ }
3114
3453
  },
3115
3454
  arrange({ tag, node, props, children, }) {
3116
3455
  if (tag === Portal && (node == null || typeof node.nodeType !== "number")) {
3117
3456
  throw new TypeError(`<Portal> root is not a node. Received: ${String(node)}`);
3118
3457
  }
3119
- if (!("innerHTML" in props)) {
3458
+ if (!("innerHTML" in props) && !("dangerouslySetInnerHTML" in props)) {
3120
3459
  let oldChild = node.firstChild;
3121
3460
  for (let i = 0; i < children.length; i++) {
3122
3461
  const newChild = children[i];
@@ -3292,12 +3631,20 @@
3292
3631
  }
3293
3632
  return cssStrings.join("");
3294
3633
  }
3295
- function printAttrs(props) {
3634
+ function printAttrs(props, isSVG) {
3296
3635
  const attrs = [];
3297
3636
  for (let [name, value] of Object.entries(props)) {
3298
- if (name === "innerHTML" || name.startsWith("prop:")) {
3637
+ if (name === "innerHTML" ||
3638
+ name === "dangerouslySetInnerHTML" ||
3639
+ name.startsWith("prop:")) {
3299
3640
  continue;
3300
3641
  }
3642
+ else if (name === "htmlFor") {
3643
+ if ("for" in props || value == null || value === false) {
3644
+ continue;
3645
+ }
3646
+ attrs.push(`for="${escape(String(value === true ? "" : value))}"`);
3647
+ }
3301
3648
  else if (name === "style") {
3302
3649
  if (typeof value === "string") {
3303
3650
  attrs.push(`style="${escape(value)}"`);
@@ -3330,6 +3677,9 @@
3330
3677
  if (name.startsWith("attr:")) {
3331
3678
  name = name.slice("attr:".length);
3332
3679
  }
3680
+ else if (isSVG && name in REACT_SVG_PROPS) {
3681
+ name = REACT_SVG_PROPS[name];
3682
+ }
3333
3683
  if (typeof value === "string") {
3334
3684
  attrs.push(`${escape(name)}="${escape(value)}"`);
3335
3685
  }
@@ -3352,6 +3702,20 @@
3352
3702
  return result;
3353
3703
  }
3354
3704
  const impl = {
3705
+ scope({ scope, tag, }) {
3706
+ if (tag === Portal) {
3707
+ return undefined;
3708
+ }
3709
+ switch (tag) {
3710
+ case "svg":
3711
+ return "svg";
3712
+ case "math":
3713
+ return "math";
3714
+ case "foreignObject":
3715
+ return undefined;
3716
+ }
3717
+ return scope;
3718
+ },
3355
3719
  create() {
3356
3720
  return { value: "" };
3357
3721
  },
@@ -3372,14 +3736,14 @@
3372
3736
  return value.value || "";
3373
3737
  }
3374
3738
  },
3375
- arrange({ tag, tagName, node, props, children, }) {
3739
+ arrange({ tag, tagName, node, props, children, scope, }) {
3376
3740
  if (tag === Portal) {
3377
3741
  return;
3378
3742
  }
3379
3743
  else if (typeof tag !== "string") {
3380
3744
  throw new Error(`Unknown tag: ${tagName}`);
3381
3745
  }
3382
- const attrs = printAttrs(props);
3746
+ const attrs = printAttrs(props, scope === "svg" || tag === "foreignObject");
3383
3747
  const open = `<${tag}${attrs.length ? " " : ""}${attrs}>`;
3384
3748
  let result;
3385
3749
  if (voidTags.has(tag)) {
@@ -3387,7 +3751,11 @@
3387
3751
  }
3388
3752
  else {
3389
3753
  const close = `</${tag}>`;
3390
- const contents = "innerHTML" in props ? props["innerHTML"] : join(children);
3754
+ const contents = "innerHTML" in props
3755
+ ? props["innerHTML"]
3756
+ : "dangerouslySetInnerHTML" in props
3757
+ ? (props["dangerouslySetInnerHTML"]?.__html ?? "")
3758
+ : join(children);
3391
3759
  result = `${open}${contents}${close}`;
3392
3760
  }
3393
3761
  node.value = result;
@@ -3409,7 +3777,7 @@
3409
3777
 
3410
3778
  exports.Context = Context;
3411
3779
  exports.Copy = Copy;
3412
- exports.Element = Element;
3780
+ exports.Element = Element$1;
3413
3781
  exports.Fragment = Fragment;
3414
3782
  exports.Portal = Portal;
3415
3783
  exports.Raw = Raw;