@b9g/crank 0.7.0 → 0.7.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.
- package/README.md +646 -138
- package/_css.cjs +80 -0
- package/_css.cjs.map +1 -0
- package/_css.d.ts +21 -0
- package/_css.js +76 -0
- package/_css.js.map +1 -0
- package/_utils.cjs +106 -0
- package/_utils.cjs.map +1 -0
- package/_utils.js +99 -0
- package/_utils.js.map +1 -0
- package/async.cjs +42 -39
- package/async.cjs.map +1 -1
- package/async.d.ts +10 -7
- package/async.js +42 -39
- package/async.js.map +1 -1
- package/crank.cjs +87 -141
- package/crank.cjs.map +1 -1
- package/crank.d.ts +255 -3
- package/crank.js +51 -105
- package/crank.js.map +1 -1
- package/dom.cjs +33 -19
- package/dom.cjs.map +1 -1
- package/dom.js +33 -19
- package/dom.js.map +1 -1
- package/html.cjs +5 -3
- package/html.cjs.map +1 -1
- package/html.js +5 -3
- package/html.js.map +1 -1
- package/jsx-runtime.cjs +0 -1
- package/jsx-runtime.cjs.map +1 -1
- package/jsx-runtime.js +0 -1
- package/jsx-runtime.js.map +1 -1
- package/jsx-tag.cjs +0 -1
- package/jsx-tag.cjs.map +1 -1
- package/jsx-tag.js +0 -1
- package/jsx-tag.js.map +1 -1
- package/package.json +3 -2
- package/standalone.cjs +0 -1
- package/standalone.cjs.map +1 -1
- package/standalone.js +0 -1
- package/standalone.js.map +1 -1
- package/umd.js +160 -28
- package/umd.js.map +1 -1
package/umd.js
CHANGED
|
@@ -510,6 +510,7 @@
|
|
|
510
510
|
const IsInForAwaitOfLoop = 1 << 14;
|
|
511
511
|
const NeedsToYield = 1 << 15;
|
|
512
512
|
const PropsAvailable = 1 << 16;
|
|
513
|
+
const IsSchedulingRefresh = 1 << 17;
|
|
513
514
|
function getFlag(ret, flag) {
|
|
514
515
|
return !!(ret.f & flag);
|
|
515
516
|
}
|
|
@@ -863,10 +864,10 @@
|
|
|
863
864
|
}
|
|
864
865
|
}
|
|
865
866
|
else if (ret) {
|
|
867
|
+
let candidateFound = false;
|
|
866
868
|
// we do not need to add the retainer to the graveyard if it is the
|
|
867
869
|
// fallback of another retainer
|
|
868
870
|
// search for the tag in fallback chain
|
|
869
|
-
let candidateFound = false;
|
|
870
871
|
for (let predecessor = ret, candidate = ret.fallback; candidate; predecessor = candidate, candidate = candidate.fallback) {
|
|
871
872
|
if (candidate.el.tag === child.tag) {
|
|
872
873
|
// If we find a retainer in the fallback chain with the same tag,
|
|
@@ -1701,6 +1702,9 @@
|
|
|
1701
1702
|
});
|
|
1702
1703
|
}
|
|
1703
1704
|
}
|
|
1705
|
+
if (getFlag(ctx.ret, IsScheduling)) {
|
|
1706
|
+
setFlag(ctx.ret, IsSchedulingRefresh);
|
|
1707
|
+
}
|
|
1704
1708
|
let diff;
|
|
1705
1709
|
const schedulePromises = [];
|
|
1706
1710
|
try {
|
|
@@ -2046,10 +2050,11 @@
|
|
|
2046
2050
|
if (getFlag(ctx.ret, IsInForOfLoop) &&
|
|
2047
2051
|
!getFlag(ctx.ret, NeedsToYield) &&
|
|
2048
2052
|
!getFlag(ctx.ret, IsUnmounted) &&
|
|
2049
|
-
!getFlag(ctx.ret,
|
|
2053
|
+
!getFlag(ctx.ret, IsSchedulingRefresh)) {
|
|
2050
2054
|
console.error(`Component <${getTagName(ctx.ret.el.tag)}> yielded/returned more than once in for...of loop`);
|
|
2051
2055
|
}
|
|
2052
2056
|
setFlag(ctx.ret, NeedsToYield, false);
|
|
2057
|
+
setFlag(ctx.ret, IsSchedulingRefresh, false);
|
|
2053
2058
|
if (iteration.done) {
|
|
2054
2059
|
setFlag(ctx.ret, IsSyncGen, false);
|
|
2055
2060
|
ctx.iterator = undefined;
|
|
@@ -2095,11 +2100,12 @@
|
|
|
2095
2100
|
if (getFlag(ctx.ret, IsInForOfLoop) &&
|
|
2096
2101
|
!getFlag(ctx.ret, NeedsToYield) &&
|
|
2097
2102
|
!getFlag(ctx.ret, IsUnmounted) &&
|
|
2098
|
-
!getFlag(ctx.ret,
|
|
2103
|
+
!getFlag(ctx.ret, IsSchedulingRefresh)) {
|
|
2099
2104
|
console.error(`Component <${getTagName(ctx.ret.el.tag)}> yielded/returned more than once in for...of loop`);
|
|
2100
2105
|
}
|
|
2101
2106
|
}
|
|
2102
2107
|
setFlag(ctx.ret, NeedsToYield, false);
|
|
2108
|
+
setFlag(ctx.ret, IsSchedulingRefresh, false);
|
|
2103
2109
|
if (iteration.done) {
|
|
2104
2110
|
setFlag(ctx.ret, IsAsyncGen, false);
|
|
2105
2111
|
ctx.iterator = undefined;
|
|
@@ -2331,18 +2337,17 @@
|
|
|
2331
2337
|
});
|
|
2332
2338
|
return getValue(ctx.ret);
|
|
2333
2339
|
}
|
|
2334
|
-
const wasScheduling = getFlag(ctx.ret, IsScheduling);
|
|
2335
2340
|
const values = commitChildren(ctx.adapter, ctx.host, ctx, ctx.scope, ctx.ret, ctx.index, schedulePromises, hydrationNodes);
|
|
2336
2341
|
if (getFlag(ctx.ret, IsUnmounted)) {
|
|
2337
2342
|
return;
|
|
2338
2343
|
}
|
|
2339
2344
|
addEventTargetDelegates(ctx.ctx, values);
|
|
2340
2345
|
// Execute schedule callbacks early to check for async deferral
|
|
2341
|
-
const
|
|
2346
|
+
const wasScheduling = getFlag(ctx.ret, IsScheduling);
|
|
2342
2347
|
let schedulePromises1;
|
|
2348
|
+
const callbacks = scheduleMap.get(ctx);
|
|
2343
2349
|
if (callbacks) {
|
|
2344
2350
|
scheduleMap.delete(ctx);
|
|
2345
|
-
// TODO: think about error handling for schedule callbacks
|
|
2346
2351
|
setFlag(ctx.ret, IsScheduling);
|
|
2347
2352
|
const result = ctx.adapter.read(unwrap(values));
|
|
2348
2353
|
for (const callback of callbacks) {
|
|
@@ -2353,7 +2358,7 @@
|
|
|
2353
2358
|
}
|
|
2354
2359
|
if (schedulePromises1 && !getFlag(ctx.ret, DidCommit)) {
|
|
2355
2360
|
const scheduleCallbacksP = Promise.all(schedulePromises1).then(() => {
|
|
2356
|
-
setFlag(ctx.ret, IsScheduling,
|
|
2361
|
+
setFlag(ctx.ret, IsScheduling, wasScheduling);
|
|
2357
2362
|
propagateComponent(ctx);
|
|
2358
2363
|
if (ctx.ret.fallback) {
|
|
2359
2364
|
unmount(ctx.adapter, ctx.host, ctx.parent, ctx.ret.fallback, false);
|
|
@@ -2393,6 +2398,37 @@
|
|
|
2393
2398
|
// if schedule callbacks call refresh() or async mounting is happening.
|
|
2394
2399
|
return getValue(ctx.ret, true);
|
|
2395
2400
|
}
|
|
2401
|
+
/**
|
|
2402
|
+
* Checks if a target retainer is active (contributing) in the host's retainer tree.
|
|
2403
|
+
* Performs a downward traversal from host to find if target is in the active path.
|
|
2404
|
+
*/
|
|
2405
|
+
function isRetainerActive(target, host) {
|
|
2406
|
+
const stack = [host];
|
|
2407
|
+
while (stack.length > 0) {
|
|
2408
|
+
const current = stack.pop();
|
|
2409
|
+
if (current === target) {
|
|
2410
|
+
return true;
|
|
2411
|
+
}
|
|
2412
|
+
// Add direct children to stack (skip if this is a host boundary)
|
|
2413
|
+
// Host boundaries are: DOM elements (string tags) or Portal, but NOT Fragment
|
|
2414
|
+
const isHostBoundary = current !== host &&
|
|
2415
|
+
((typeof current.el.tag === "string" && current.el.tag !== Fragment) ||
|
|
2416
|
+
current.el.tag === Portal);
|
|
2417
|
+
if (current.children && !isHostBoundary) {
|
|
2418
|
+
const children = wrap(current.children);
|
|
2419
|
+
for (const child of children) {
|
|
2420
|
+
if (child) {
|
|
2421
|
+
stack.push(child);
|
|
2422
|
+
}
|
|
2423
|
+
}
|
|
2424
|
+
}
|
|
2425
|
+
// Add fallback chains (only if current retainer is using fallback)
|
|
2426
|
+
if (current.fallback && !getFlag(current, DidDiff)) {
|
|
2427
|
+
stack.push(current.fallback);
|
|
2428
|
+
}
|
|
2429
|
+
}
|
|
2430
|
+
return false;
|
|
2431
|
+
}
|
|
2396
2432
|
/**
|
|
2397
2433
|
* Propagates component changes up to ancestors when rendering starts from a
|
|
2398
2434
|
* component via refresh() or multiple for await...of renders. This handles
|
|
@@ -2403,14 +2439,20 @@
|
|
|
2403
2439
|
const values = getChildValues(ctx.ret, ctx.index);
|
|
2404
2440
|
addEventTargetDelegates(ctx.ctx, values, (ctx1) => ctx1[_ContextState].host === ctx.host);
|
|
2405
2441
|
const host = ctx.host;
|
|
2442
|
+
const initiator = ctx.ret;
|
|
2443
|
+
// Check if initiator is active in the host's tree
|
|
2444
|
+
if (!isRetainerActive(initiator, host)) {
|
|
2445
|
+
return;
|
|
2446
|
+
}
|
|
2406
2447
|
const props = stripSpecialProps(host.el.props);
|
|
2448
|
+
const hostChildren = getChildValues(host, 0);
|
|
2407
2449
|
ctx.adapter.arrange({
|
|
2408
2450
|
tag: host.el.tag,
|
|
2409
2451
|
tagName: getTagName(host.el.tag),
|
|
2410
2452
|
node: host.value,
|
|
2411
2453
|
props,
|
|
2412
2454
|
oldProps: props,
|
|
2413
|
-
children:
|
|
2455
|
+
children: hostChildren,
|
|
2414
2456
|
});
|
|
2415
2457
|
flush(ctx.adapter, ctx.root, ctx);
|
|
2416
2458
|
}
|
|
@@ -2610,7 +2652,82 @@
|
|
|
2610
2652
|
commitComponent(parent, schedulePromises);
|
|
2611
2653
|
}
|
|
2612
2654
|
|
|
2655
|
+
/**
|
|
2656
|
+
* CSS utility functions for style property transformation.
|
|
2657
|
+
*
|
|
2658
|
+
* This module handles camelCase to kebab-case conversion and automatic
|
|
2659
|
+
* px unit conversion for numeric CSS values, making Crank more React-compatible.
|
|
2660
|
+
*/
|
|
2661
|
+
/**
|
|
2662
|
+
* Converts camelCase CSS property names to kebab-case.
|
|
2663
|
+
* Handles vendor prefixes correctly (WebkitTransform -> -webkit-transform).
|
|
2664
|
+
*/
|
|
2665
|
+
function camelToKebabCase(str) {
|
|
2666
|
+
// Handle vendor prefixes that start with capital letters (WebkitTransform -> -webkit-transform)
|
|
2667
|
+
if (/^[A-Z]/.test(str)) {
|
|
2668
|
+
return `-${str.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`).slice(1)}`;
|
|
2669
|
+
}
|
|
2670
|
+
// Handle normal camelCase (fontSize -> font-size)
|
|
2671
|
+
return str.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
|
|
2672
|
+
}
|
|
2673
|
+
/**
|
|
2674
|
+
* CSS properties that should remain unitless when given numeric values.
|
|
2675
|
+
* Based on React's list of unitless properties.
|
|
2676
|
+
*/
|
|
2677
|
+
const UNITLESS_PROPERTIES = new Set([
|
|
2678
|
+
"animation-iteration-count",
|
|
2679
|
+
"aspect-ratio",
|
|
2680
|
+
"border-image-outset",
|
|
2681
|
+
"border-image-slice",
|
|
2682
|
+
"border-image-width",
|
|
2683
|
+
"box-flex",
|
|
2684
|
+
"box-flex-group",
|
|
2685
|
+
"box-ordinal-group",
|
|
2686
|
+
"column-count",
|
|
2687
|
+
"columns",
|
|
2688
|
+
"flex",
|
|
2689
|
+
"flex-grow",
|
|
2690
|
+
"flex-positive",
|
|
2691
|
+
"flex-shrink",
|
|
2692
|
+
"flex-negative",
|
|
2693
|
+
"flex-order",
|
|
2694
|
+
"font-weight",
|
|
2695
|
+
"grid-area",
|
|
2696
|
+
"grid-column",
|
|
2697
|
+
"grid-column-end",
|
|
2698
|
+
"grid-column-span",
|
|
2699
|
+
"grid-column-start",
|
|
2700
|
+
"grid-row",
|
|
2701
|
+
"grid-row-end",
|
|
2702
|
+
"grid-row-span",
|
|
2703
|
+
"grid-row-start",
|
|
2704
|
+
"line-height",
|
|
2705
|
+
"opacity",
|
|
2706
|
+
"order",
|
|
2707
|
+
"orphans",
|
|
2708
|
+
"tab-size",
|
|
2709
|
+
"widows",
|
|
2710
|
+
"z-index",
|
|
2711
|
+
"zoom",
|
|
2712
|
+
]);
|
|
2713
|
+
/**
|
|
2714
|
+
* Formats CSS property values, automatically adding "px" to numeric values
|
|
2715
|
+
* for properties that are not unitless.
|
|
2716
|
+
*/
|
|
2717
|
+
function formatStyleValue(name, value) {
|
|
2718
|
+
if (typeof value === "number") {
|
|
2719
|
+
// If the property should remain unitless, keep the number as-is
|
|
2720
|
+
if (UNITLESS_PROPERTIES.has(name)) {
|
|
2721
|
+
return String(value);
|
|
2722
|
+
}
|
|
2723
|
+
// Otherwise, append "px" for numeric values
|
|
2724
|
+
return `${value}px`;
|
|
2725
|
+
}
|
|
2726
|
+
return String(value);
|
|
2727
|
+
}
|
|
2728
|
+
|
|
2613
2729
|
const SVG_NAMESPACE = "http://www.w3.org/2000/svg";
|
|
2730
|
+
const MATHML_NAMESPACE = "http://www.w3.org/1998/Math/MathML";
|
|
2614
2731
|
function isWritableProperty(element, name) {
|
|
2615
2732
|
// walk up the object's prototype chain to find the owner
|
|
2616
2733
|
let propOwner = element;
|
|
@@ -2660,6 +2777,9 @@
|
|
|
2660
2777
|
case "svg":
|
|
2661
2778
|
xmlns = SVG_NAMESPACE;
|
|
2662
2779
|
break;
|
|
2780
|
+
case "math":
|
|
2781
|
+
xmlns = MATHML_NAMESPACE;
|
|
2782
|
+
break;
|
|
2663
2783
|
}
|
|
2664
2784
|
return props.xmlns || xmlns;
|
|
2665
2785
|
},
|
|
@@ -2670,6 +2790,9 @@
|
|
|
2670
2790
|
else if (tag.toLowerCase() === "svg") {
|
|
2671
2791
|
xmlns = SVG_NAMESPACE;
|
|
2672
2792
|
}
|
|
2793
|
+
else if (tag.toLowerCase() === "math") {
|
|
2794
|
+
xmlns = MATHML_NAMESPACE;
|
|
2795
|
+
}
|
|
2673
2796
|
return xmlns
|
|
2674
2797
|
? document.createElementNS(xmlns, tag)
|
|
2675
2798
|
: document.createElement(tag);
|
|
@@ -2702,6 +2825,7 @@
|
|
|
2702
2825
|
}
|
|
2703
2826
|
const element = node;
|
|
2704
2827
|
const isSVG = xmlns === SVG_NAMESPACE;
|
|
2828
|
+
const isMathML = xmlns === MATHML_NAMESPACE;
|
|
2705
2829
|
for (let name in { ...oldProps, ...props }) {
|
|
2706
2830
|
let value = props[name];
|
|
2707
2831
|
const oldValue = oldProps ? oldProps[name] : undefined;
|
|
@@ -2782,26 +2906,30 @@
|
|
|
2782
2906
|
style.cssText = "";
|
|
2783
2907
|
}
|
|
2784
2908
|
for (const styleName in { ...oldValue, ...value }) {
|
|
2909
|
+
const cssName = camelToKebabCase(styleName);
|
|
2785
2910
|
const styleValue = value && value[styleName];
|
|
2786
2911
|
if (styleValue == null) {
|
|
2787
|
-
if (isHydrating && style.getPropertyValue(
|
|
2788
|
-
emitHydrationWarning(name, quietProps, null, style.getPropertyValue(
|
|
2912
|
+
if (isHydrating && style.getPropertyValue(cssName) !== "") {
|
|
2913
|
+
emitHydrationWarning(name, quietProps, null, style.getPropertyValue(cssName), element, `style.${styleName}`);
|
|
2789
2914
|
}
|
|
2790
|
-
style.removeProperty(
|
|
2915
|
+
style.removeProperty(cssName);
|
|
2791
2916
|
}
|
|
2792
|
-
else
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2917
|
+
else {
|
|
2918
|
+
const formattedValue = formatStyleValue(cssName, styleValue);
|
|
2919
|
+
if (style.getPropertyValue(cssName) !== formattedValue) {
|
|
2920
|
+
// TODO: hydration warnings for style props
|
|
2921
|
+
//if (isHydrating) {
|
|
2922
|
+
// emitHydrationWarning(
|
|
2923
|
+
// name,
|
|
2924
|
+
// quietProps,
|
|
2925
|
+
// formattedValue,
|
|
2926
|
+
// style.getPropertyValue(cssName),
|
|
2927
|
+
// element,
|
|
2928
|
+
// `style.${styleName}`,
|
|
2929
|
+
// );
|
|
2930
|
+
//}
|
|
2931
|
+
style.setProperty(cssName, formattedValue);
|
|
2932
|
+
}
|
|
2805
2933
|
}
|
|
2806
2934
|
}
|
|
2807
2935
|
}
|
|
@@ -2856,7 +2984,7 @@
|
|
|
2856
2984
|
.join(" "), hydratingClassName || "", element);
|
|
2857
2985
|
}
|
|
2858
2986
|
}
|
|
2859
|
-
else if (!isSVG) {
|
|
2987
|
+
else if (!isSVG && !isMathML) {
|
|
2860
2988
|
if (element.className !== value) {
|
|
2861
2989
|
if (isHydrating) {
|
|
2862
2990
|
emitHydrationWarning(name, quietProps, value, element.className, element);
|
|
@@ -2997,7 +3125,9 @@
|
|
|
2997
3125
|
if (typeof value === "string") {
|
|
2998
3126
|
const el = xmlns == null
|
|
2999
3127
|
? document.createElement("div")
|
|
3000
|
-
:
|
|
3128
|
+
: xmlns === SVG_NAMESPACE
|
|
3129
|
+
? document.createElementNS(xmlns, "svg")
|
|
3130
|
+
: document.createElementNS(xmlns, "math");
|
|
3001
3131
|
el.innerHTML = value;
|
|
3002
3132
|
nodes = Array.from(el.childNodes);
|
|
3003
3133
|
}
|
|
@@ -3098,7 +3228,9 @@
|
|
|
3098
3228
|
const cssStrings = [];
|
|
3099
3229
|
for (const [name, value] of Object.entries(style)) {
|
|
3100
3230
|
if (value != null) {
|
|
3101
|
-
|
|
3231
|
+
const cssName = camelToKebabCase(name);
|
|
3232
|
+
const cssValue = formatStyleValue(cssName, value);
|
|
3233
|
+
cssStrings.push(`${cssName}:${cssValue};`);
|
|
3102
3234
|
}
|
|
3103
3235
|
}
|
|
3104
3236
|
return cssStrings.join("");
|
|
@@ -3113,7 +3245,7 @@
|
|
|
3113
3245
|
if (typeof value === "string") {
|
|
3114
3246
|
attrs.push(`style="${escape(value)}"`);
|
|
3115
3247
|
}
|
|
3116
|
-
else if (typeof value === "object") {
|
|
3248
|
+
else if (typeof value === "object" && value !== null) {
|
|
3117
3249
|
attrs.push(`style="${escape(printStyleObject(value))}"`);
|
|
3118
3250
|
}
|
|
3119
3251
|
}
|