@pyreon/runtime-dom 0.12.13 → 0.12.15
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 +13 -0
- package/lib/analysis/index.js.html +1 -1
- package/lib/index.js +167 -26
- package/lib/index.js.map +1 -1
- package/lib/types/index.d.ts.map +1 -1
- package/package.json +12 -6
- package/src/hydrate.ts +35 -5
- package/src/mount.ts +8 -2
- package/src/props.ts +101 -22
- package/src/tests/callback-ref-unmount.browser.test.ts +62 -0
- package/src/tests/callback-ref-unmount.test.ts +52 -0
- package/src/tests/dev-gate-pattern.test.ts +40 -0
- package/src/tests/dev-gate-treeshake.test.ts +262 -0
- package/src/tests/mount.test.ts +95 -5
- package/src/tests/props.test.ts +117 -0
- package/src/tests/runtime-dom.browser.test.ts +295 -0
- package/src/tests/ssr-xss-round-trip.browser.test.ts +93 -0
- package/src/tests/style-key-removal.browser.test.ts +54 -0
- package/src/tests/style-key-removal.test.ts +88 -0
- package/src/tests/transition-timeout-leak.test.ts +126 -0
- package/src/tests/verified-correct-probes.test.ts +56 -0
- package/src/transition-group.ts +80 -8
- package/src/transition.ts +46 -3
package/README.md
CHANGED
|
@@ -115,6 +115,19 @@ const App = () => (
|
|
|
115
115
|
|
|
116
116
|
`TransitionProps`, `TransitionGroupProps`, `KeepAliveProps`, `SanitizeFn`, `DevtoolsComponentEntry`, `PyreonDevtools`
|
|
117
117
|
|
|
118
|
+
## Dev-mode warnings — bundler tree-shake
|
|
119
|
+
|
|
120
|
+
Dev warnings are gated on `import.meta.env?.DEV`. Tree-shake behavior depends on both the source pattern and the consumer bundler:
|
|
121
|
+
|
|
122
|
+
| Source pattern | Vite prod | Raw esbuild prod | Test |
|
|
123
|
+
| --- | --- | --- | --- |
|
|
124
|
+
| `if (!import.meta.env?.DEV) return` (inline early-return) | tree-shaken | tree-shaken | `flow/src/tests/integration.test.ts` (esbuild) |
|
|
125
|
+
| `const __DEV__ = ...; if (__DEV__) ...` | tree-shaken | mostly tree-shaken | `runtime-dom/src/tests/dev-gate-treeshake.test.ts` (Vite) |
|
|
126
|
+
| `const __DEV__ = ...; __DEV__ && cond && warn(...)` (chained &&) | tree-shaken | runtime-gated only | `runtime-dom/.../dev-gate-treeshake.test.ts` (Vite + non-Vite runtime smoke) |
|
|
127
|
+
| `typeof process !== 'undefined'` | dead in browser | dead in browser | `pyreon/no-process-dev-gate` lint rule |
|
|
128
|
+
|
|
129
|
+
Vite is Pyreon's primary supported bundler. Non-Vite consumers (webpack, bunchee, raw esbuild) using the chained `&&` form may retain warning strings as data, but the runtime gate evaluates to `false` when `import.meta.env.DEV` is undefined — warnings don't fire. Only a small bundle-size cost.
|
|
130
|
+
|
|
118
131
|
## License
|
|
119
132
|
|
|
120
133
|
MIT
|
|
@@ -5386,7 +5386,7 @@ var drawChart = (function (exports) {
|
|
|
5386
5386
|
</script>
|
|
5387
5387
|
<script>
|
|
5388
5388
|
/*<!--*/
|
|
5389
|
-
const data = {"version":2,"tree":{"name":"root","children":[{"name":"index.js","children":[{"name":"src","children":[{"uid":"
|
|
5389
|
+
const data = {"version":2,"tree":{"name":"root","children":[{"name":"index.js","children":[{"name":"src","children":[{"uid":"391d1e60-1","name":"delegate.ts"},{"uid":"391d1e60-3","name":"hydration-debug.ts"},{"uid":"391d1e60-5","name":"devtools.ts"},{"uid":"391d1e60-7","name":"nodes.ts"},{"uid":"391d1e60-9","name":"props.ts"},{"uid":"391d1e60-11","name":"mount.ts"},{"uid":"391d1e60-13","name":"hydrate.ts"},{"uid":"391d1e60-15","name":"keep-alive.ts"},{"uid":"391d1e60-17","name":"template.ts"},{"uid":"391d1e60-19","name":"transition.ts"},{"uid":"391d1e60-21","name":"transition-group.ts"},{"uid":"391d1e60-23","name":"index.ts"}]}]}],"isRoot":true},"nodeParts":{"391d1e60-1":{"renderedLength":1990,"gzipLength":976,"brotliLength":0,"metaUid":"391d1e60-0"},"391d1e60-3":{"renderedLength":662,"gzipLength":370,"brotliLength":0,"metaUid":"391d1e60-2"},"391d1e60-5":{"renderedLength":6803,"gzipLength":2095,"brotliLength":0,"metaUid":"391d1e60-4"},"391d1e60-7":{"renderedLength":16207,"gzipLength":4334,"brotliLength":0,"metaUid":"391d1e60-6"},"391d1e60-9":{"renderedLength":7947,"gzipLength":3020,"brotliLength":0,"metaUid":"391d1e60-8"},"391d1e60-11":{"renderedLength":11649,"gzipLength":3717,"brotliLength":0,"metaUid":"391d1e60-10"},"391d1e60-13":{"renderedLength":8239,"gzipLength":2455,"brotliLength":0,"metaUid":"391d1e60-12"},"391d1e60-15":{"renderedLength":1473,"gzipLength":701,"brotliLength":0,"metaUid":"391d1e60-14"},"391d1e60-17":{"renderedLength":5319,"gzipLength":2069,"brotliLength":0,"metaUid":"391d1e60-16"},"391d1e60-19":{"renderedLength":4895,"gzipLength":1390,"brotliLength":0,"metaUid":"391d1e60-18"},"391d1e60-21":{"renderedLength":7942,"gzipLength":2070,"brotliLength":0,"metaUid":"391d1e60-20"},"391d1e60-23":{"renderedLength":769,"gzipLength":474,"brotliLength":0,"metaUid":"391d1e60-22"}},"nodeMetas":{"391d1e60-0":{"id":"/src/delegate.ts","moduleParts":{"index.js":"391d1e60-1"},"imported":[{"uid":"391d1e60-24"}],"importedBy":[{"uid":"391d1e60-22"},{"uid":"391d1e60-12"},{"uid":"391d1e60-8"}]},"391d1e60-2":{"id":"/src/hydration-debug.ts","moduleParts":{"index.js":"391d1e60-3"},"imported":[],"importedBy":[{"uid":"391d1e60-22"},{"uid":"391d1e60-12"}]},"391d1e60-4":{"id":"/src/devtools.ts","moduleParts":{"index.js":"391d1e60-5"},"imported":[],"importedBy":[{"uid":"391d1e60-22"},{"uid":"391d1e60-10"}]},"391d1e60-6":{"id":"/src/nodes.ts","moduleParts":{"index.js":"391d1e60-7"},"imported":[{"uid":"391d1e60-25"},{"uid":"391d1e60-24"}],"importedBy":[{"uid":"391d1e60-12"},{"uid":"391d1e60-10"}]},"391d1e60-8":{"id":"/src/props.ts","moduleParts":{"index.js":"391d1e60-9"},"imported":[{"uid":"391d1e60-25"},{"uid":"391d1e60-24"},{"uid":"391d1e60-0"}],"importedBy":[{"uid":"391d1e60-22"},{"uid":"391d1e60-12"},{"uid":"391d1e60-10"}]},"391d1e60-10":{"id":"/src/mount.ts","moduleParts":{"index.js":"391d1e60-11"},"imported":[{"uid":"391d1e60-25"},{"uid":"391d1e60-24"},{"uid":"391d1e60-4"},{"uid":"391d1e60-6"},{"uid":"391d1e60-8"}],"importedBy":[{"uid":"391d1e60-22"},{"uid":"391d1e60-12"},{"uid":"391d1e60-14"},{"uid":"391d1e60-16"},{"uid":"391d1e60-20"}]},"391d1e60-12":{"id":"/src/hydrate.ts","moduleParts":{"index.js":"391d1e60-13"},"imported":[{"uid":"391d1e60-25"},{"uid":"391d1e60-24"},{"uid":"391d1e60-0"},{"uid":"391d1e60-2"},{"uid":"391d1e60-10"},{"uid":"391d1e60-6"},{"uid":"391d1e60-8"}],"importedBy":[{"uid":"391d1e60-22"}]},"391d1e60-14":{"id":"/src/keep-alive.ts","moduleParts":{"index.js":"391d1e60-15"},"imported":[{"uid":"391d1e60-25"},{"uid":"391d1e60-24"},{"uid":"391d1e60-10"}],"importedBy":[{"uid":"391d1e60-22"}]},"391d1e60-16":{"id":"/src/template.ts","moduleParts":{"index.js":"391d1e60-17"},"imported":[{"uid":"391d1e60-24"},{"uid":"391d1e60-10"}],"importedBy":[{"uid":"391d1e60-22"}]},"391d1e60-18":{"id":"/src/transition.ts","moduleParts":{"index.js":"391d1e60-19"},"imported":[{"uid":"391d1e60-25"},{"uid":"391d1e60-24"}],"importedBy":[{"uid":"391d1e60-22"}]},"391d1e60-20":{"id":"/src/transition-group.ts","moduleParts":{"index.js":"391d1e60-21"},"imported":[{"uid":"391d1e60-25"},{"uid":"391d1e60-24"},{"uid":"391d1e60-10"}],"importedBy":[{"uid":"391d1e60-22"}]},"391d1e60-22":{"id":"/src/index.ts","moduleParts":{"index.js":"391d1e60-23"},"imported":[{"uid":"391d1e60-0"},{"uid":"391d1e60-12"},{"uid":"391d1e60-2"},{"uid":"391d1e60-14"},{"uid":"391d1e60-10"},{"uid":"391d1e60-8"},{"uid":"391d1e60-16"},{"uid":"391d1e60-18"},{"uid":"391d1e60-20"},{"uid":"391d1e60-4"}],"importedBy":[],"isEntry":true},"391d1e60-24":{"id":"@pyreon/reactivity","moduleParts":{},"imported":[],"importedBy":[{"uid":"391d1e60-0"},{"uid":"391d1e60-12"},{"uid":"391d1e60-14"},{"uid":"391d1e60-10"},{"uid":"391d1e60-8"},{"uid":"391d1e60-16"},{"uid":"391d1e60-18"},{"uid":"391d1e60-20"},{"uid":"391d1e60-6"}]},"391d1e60-25":{"id":"@pyreon/core","moduleParts":{},"imported":[],"importedBy":[{"uid":"391d1e60-12"},{"uid":"391d1e60-14"},{"uid":"391d1e60-10"},{"uid":"391d1e60-8"},{"uid":"391d1e60-18"},{"uid":"391d1e60-20"},{"uid":"391d1e60-6"}]}},"env":{"rollup":"4.23.0"},"options":{"gzip":true,"brotli":false,"sourcemap":false}};
|
|
5390
5390
|
|
|
5391
5391
|
const run = () => {
|
|
5392
5392
|
const width = window.innerWidth;
|
package/lib/index.js
CHANGED
|
@@ -981,7 +981,7 @@ function applyEventProp(el, key, value) {
|
|
|
981
981
|
if (__DEV__$3 && value != null) console.warn(`[Pyreon] Event handler "${key}" received a non-function value (${typeof value}). Expected a function. Did you mean ${key}={() => ...}?`);
|
|
982
982
|
return null;
|
|
983
983
|
}
|
|
984
|
-
const eventName = key[2]?.toLowerCase() + key.slice(3);
|
|
984
|
+
const eventName = (key[2]?.toLowerCase() + key.slice(3)).toLowerCase();
|
|
985
985
|
const handler = value;
|
|
986
986
|
if (DELEGATED_EVENTS.has(eventName)) {
|
|
987
987
|
const prop = delegatedPropName(eventName);
|
|
@@ -994,19 +994,38 @@ function applyEventProp(el, key, value) {
|
|
|
994
994
|
el.addEventListener(eventName, batched);
|
|
995
995
|
return () => el.removeEventListener(eventName, batched);
|
|
996
996
|
}
|
|
997
|
-
|
|
998
|
-
|
|
997
|
+
/**
|
|
998
|
+
* Sink for a single prop's CALLED value (always a primitive / object /
|
|
999
|
+
* `null` — never a function). Called both directly for static values and
|
|
1000
|
+
* from the reactive `renderEffect` for accessor-bound values.
|
|
1001
|
+
*
|
|
1002
|
+
* NOTE on architecture: extracting the special-cased sinks
|
|
1003
|
+
* (`innerHTML` / `dangerouslySetInnerHTML`) into this single dispatch
|
|
1004
|
+
* function ensures every prop kind goes through the same reactive
|
|
1005
|
+
* wrapping at `applyProp`'s entry. Previously each special case had its
|
|
1006
|
+
* own early-return branch that needed to remember to handle function
|
|
1007
|
+
* values; missing the dance once meant the closure was stringified and
|
|
1008
|
+
* set as literal text. The structural fix (one reactive-wrap, then
|
|
1009
|
+
* dispatch) eliminates the entire bug class.
|
|
1010
|
+
*/
|
|
1011
|
+
function applyStaticProp(el, key, value) {
|
|
1012
|
+
if (__DEV__$3 && typeof value === "function") console.warn(`[Pyreon] applyStaticProp received a function for "${key}". This likely means a new special-cased prop sink in applyProp() bypassed the reactive-wrap path. The closure would be stringified and set as a literal value. Verify the dispatch in applyProp().`);
|
|
999
1013
|
if (key === "innerHTML") {
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1014
|
+
const html = String(value ?? "");
|
|
1015
|
+
if (typeof el.setHTML === "function") el.setHTML(html);
|
|
1016
|
+
else el.innerHTML = sanitizeHtml(html);
|
|
1017
|
+
return;
|
|
1003
1018
|
}
|
|
1004
1019
|
if (key === "dangerouslySetInnerHTML") {
|
|
1005
|
-
el.innerHTML = value
|
|
1006
|
-
return
|
|
1020
|
+
el.innerHTML = value?.__html ?? "";
|
|
1021
|
+
return;
|
|
1007
1022
|
}
|
|
1008
|
-
if (typeof value === "function") return renderEffect(() => setStaticProp(el, key, value()));
|
|
1009
1023
|
setStaticProp(el, key, value);
|
|
1024
|
+
}
|
|
1025
|
+
function applyProp(el, key, value) {
|
|
1026
|
+
if (EVENT_RE.test(key)) return applyEventProp(el, key, value);
|
|
1027
|
+
if (typeof value === "function") return renderEffect(() => applyStaticProp(el, key, value()));
|
|
1028
|
+
applyStaticProp(el, key, value);
|
|
1010
1029
|
return null;
|
|
1011
1030
|
}
|
|
1012
1031
|
const URL_ATTRS = new Set([
|
|
@@ -1019,15 +1038,36 @@ const URL_ATTRS = new Set([
|
|
|
1019
1038
|
"data"
|
|
1020
1039
|
]);
|
|
1021
1040
|
const UNSAFE_URL_RE = /^\s*(?:javascript|data):/i;
|
|
1041
|
+
const _prevStyleKeys = /* @__PURE__ */ new WeakMap();
|
|
1022
1042
|
/** Apply a style prop (string or object). */
|
|
1023
1043
|
function applyStyleProp(el, value) {
|
|
1024
|
-
if (typeof value === "string")
|
|
1025
|
-
|
|
1044
|
+
if (typeof value === "string") {
|
|
1045
|
+
el.style.cssText = value;
|
|
1046
|
+
_prevStyleKeys.delete(el);
|
|
1047
|
+
return;
|
|
1048
|
+
}
|
|
1049
|
+
const prev = _prevStyleKeys.get(el);
|
|
1050
|
+
if (value == null) {
|
|
1051
|
+
if (prev) {
|
|
1052
|
+
for (const propName of prev) el.style.removeProperty(propName);
|
|
1053
|
+
_prevStyleKeys.delete(el);
|
|
1054
|
+
}
|
|
1055
|
+
return;
|
|
1056
|
+
}
|
|
1057
|
+
if (typeof value === "object") {
|
|
1026
1058
|
const obj = value;
|
|
1059
|
+
const next = /* @__PURE__ */ new Set();
|
|
1027
1060
|
for (const k in obj) {
|
|
1061
|
+
const propName = k.startsWith("--") ? k : toKebabCase(k);
|
|
1062
|
+
next.add(propName);
|
|
1028
1063
|
const css = normalizeStyleValue(k, obj[k]);
|
|
1029
|
-
el.style.setProperty(
|
|
1064
|
+
el.style.setProperty(propName, css);
|
|
1030
1065
|
}
|
|
1066
|
+
if (prev) {
|
|
1067
|
+
for (const propName of prev) if (!next.has(propName)) el.style.removeProperty(propName);
|
|
1068
|
+
}
|
|
1069
|
+
if (next.size === 0) _prevStyleKeys.delete(el);
|
|
1070
|
+
else _prevStyleKeys.set(el, next);
|
|
1031
1071
|
}
|
|
1032
1072
|
}
|
|
1033
1073
|
function applyClassProp(el, value) {
|
|
@@ -1299,13 +1339,15 @@ function mountElement(vnode, parent, anchor) {
|
|
|
1299
1339
|
};
|
|
1300
1340
|
const refToClean = ref;
|
|
1301
1341
|
return () => {
|
|
1302
|
-
if (refToClean
|
|
1342
|
+
if (refToClean) if (typeof refToClean === "function") refToClean(null);
|
|
1343
|
+
else refToClean.current = null;
|
|
1303
1344
|
if (propCleanup) propCleanup();
|
|
1304
1345
|
childCleanup();
|
|
1305
1346
|
};
|
|
1306
1347
|
}
|
|
1307
1348
|
return () => {
|
|
1308
|
-
if (ref
|
|
1349
|
+
if (ref) if (typeof ref === "function") ref(null);
|
|
1350
|
+
else ref.current = null;
|
|
1309
1351
|
if (propCleanup) propCleanup();
|
|
1310
1352
|
childCleanup();
|
|
1311
1353
|
const p = el.parentNode;
|
|
@@ -1500,7 +1542,9 @@ function hydrateReactiveChild(child, domNode, parent, anchor, path) {
|
|
|
1500
1542
|
const initial = runUntracked(child);
|
|
1501
1543
|
if (initial == null || initial === false) return [mountReactive(child, parent, insertMarker(parent, domNode, "pyreon"), mountChild), domNode];
|
|
1502
1544
|
if (typeof initial === "string" || typeof initial === "number" || typeof initial === "boolean") return hydrateReactiveText(child, domNode, parent, anchor, path);
|
|
1503
|
-
|
|
1545
|
+
const next = domNode ? nextReal(domNode) : null;
|
|
1546
|
+
if (domNode && domNode.parentNode) domNode.parentNode.removeChild(domNode);
|
|
1547
|
+
return [mountReactive(child, parent, insertMarker(parent, next, "pyreon"), mountChild), next];
|
|
1504
1548
|
}
|
|
1505
1549
|
/** Hydrate a reactive text binding against an existing text node. */
|
|
1506
1550
|
function hydrateReactiveText(child, domNode, parent, anchor, path) {
|
|
@@ -1543,6 +1587,18 @@ function hydrateChild(child, domNode, parent, anchor, path = "root") {
|
|
|
1543
1587
|
warnHydrationMismatch("text", "TextNode", domNode?.nodeType ?? "null", `${path} > text`);
|
|
1544
1588
|
return [mountChild(child, parent, anchor), domNode];
|
|
1545
1589
|
}
|
|
1590
|
+
if (child?.__isNative === true) {
|
|
1591
|
+
const native = child;
|
|
1592
|
+
const next = domNode ? nextReal(domNode) : null;
|
|
1593
|
+
if (domNode && domNode.parentNode) domNode.parentNode.replaceChild(native.el, domNode);
|
|
1594
|
+
else parent.insertBefore(native.el, anchor);
|
|
1595
|
+
const cleanup = () => {
|
|
1596
|
+
native.cleanup?.();
|
|
1597
|
+
const p = native.el.parentNode;
|
|
1598
|
+
if (p && p.isConnected !== false) p.removeChild(native.el);
|
|
1599
|
+
};
|
|
1600
|
+
return [cleanup, next];
|
|
1601
|
+
}
|
|
1546
1602
|
return hydrateVNode(child, domNode, parent, anchor, path);
|
|
1547
1603
|
}
|
|
1548
1604
|
function hydrateElement(vnode, domNode, parent, anchor, path = "root") {
|
|
@@ -1559,7 +1615,8 @@ function hydrateElement(vnode, domNode, parent, anchor, path = "root") {
|
|
|
1559
1615
|
if (ref) if (typeof ref === "function") ref(el);
|
|
1560
1616
|
else ref.current = el;
|
|
1561
1617
|
const cleanup = () => {
|
|
1562
|
-
if (ref
|
|
1618
|
+
if (ref) if (typeof ref === "function") ref(null);
|
|
1619
|
+
else ref.current = null;
|
|
1563
1620
|
for (const c of cleanups) c();
|
|
1564
1621
|
el.remove();
|
|
1565
1622
|
};
|
|
@@ -1906,38 +1963,63 @@ function Transition(props) {
|
|
|
1906
1963
|
};
|
|
1907
1964
|
const ref = createRef();
|
|
1908
1965
|
const isMounted = signal(runUntracked(props.show));
|
|
1966
|
+
let pendingEnterCancel = null;
|
|
1909
1967
|
let pendingLeaveCancel = null;
|
|
1910
1968
|
let initialized = false;
|
|
1911
1969
|
const applyEnter = (el) => {
|
|
1912
1970
|
pendingLeaveCancel?.();
|
|
1913
1971
|
pendingLeaveCancel = null;
|
|
1972
|
+
pendingEnterCancel?.();
|
|
1973
|
+
pendingEnterCancel = null;
|
|
1914
1974
|
props.onBeforeEnter?.(el);
|
|
1915
1975
|
el.classList.remove(cls.lf, cls.la, cls.lt);
|
|
1916
1976
|
el.classList.add(cls.ef, cls.ea);
|
|
1917
1977
|
requestAnimationFrame(() => {
|
|
1918
1978
|
el.classList.remove(cls.ef);
|
|
1919
1979
|
el.classList.add(cls.et);
|
|
1980
|
+
let safetyTimer = null;
|
|
1920
1981
|
const done = () => {
|
|
1921
1982
|
el.removeEventListener("transitionend", done);
|
|
1922
1983
|
el.removeEventListener("animationend", done);
|
|
1984
|
+
if (safetyTimer !== null) {
|
|
1985
|
+
clearTimeout(safetyTimer);
|
|
1986
|
+
safetyTimer = null;
|
|
1987
|
+
}
|
|
1988
|
+
pendingEnterCancel = null;
|
|
1923
1989
|
el.classList.remove(cls.ea, cls.et);
|
|
1924
1990
|
props.onAfterEnter?.(el);
|
|
1925
1991
|
};
|
|
1992
|
+
pendingEnterCancel = () => {
|
|
1993
|
+
el.removeEventListener("transitionend", done);
|
|
1994
|
+
el.removeEventListener("animationend", done);
|
|
1995
|
+
if (safetyTimer !== null) {
|
|
1996
|
+
clearTimeout(safetyTimer);
|
|
1997
|
+
safetyTimer = null;
|
|
1998
|
+
}
|
|
1999
|
+
el.classList.remove(cls.ef, cls.ea, cls.et);
|
|
2000
|
+
};
|
|
1926
2001
|
el.addEventListener("transitionend", done, { once: true });
|
|
1927
2002
|
el.addEventListener("animationend", done, { once: true });
|
|
1928
|
-
setTimeout(done, 5e3);
|
|
2003
|
+
safetyTimer = setTimeout(done, 5e3);
|
|
1929
2004
|
});
|
|
1930
2005
|
};
|
|
1931
2006
|
const applyLeave = (el) => {
|
|
2007
|
+
pendingEnterCancel?.();
|
|
2008
|
+
pendingEnterCancel = null;
|
|
1932
2009
|
props.onBeforeLeave?.(el);
|
|
1933
2010
|
el.classList.remove(cls.ef, cls.ea, cls.et);
|
|
1934
2011
|
el.classList.add(cls.lf, cls.la);
|
|
1935
2012
|
requestAnimationFrame(() => {
|
|
1936
2013
|
el.classList.remove(cls.lf);
|
|
1937
2014
|
el.classList.add(cls.lt);
|
|
2015
|
+
let safetyTimer = null;
|
|
1938
2016
|
const done = () => {
|
|
1939
2017
|
el.removeEventListener("transitionend", done);
|
|
1940
2018
|
el.removeEventListener("animationend", done);
|
|
2019
|
+
if (safetyTimer !== null) {
|
|
2020
|
+
clearTimeout(safetyTimer);
|
|
2021
|
+
safetyTimer = null;
|
|
2022
|
+
}
|
|
1941
2023
|
el.classList.remove(cls.la, cls.lt);
|
|
1942
2024
|
pendingLeaveCancel = null;
|
|
1943
2025
|
isMounted.set(false);
|
|
@@ -1946,11 +2028,15 @@ function Transition(props) {
|
|
|
1946
2028
|
pendingLeaveCancel = () => {
|
|
1947
2029
|
el.removeEventListener("transitionend", done);
|
|
1948
2030
|
el.removeEventListener("animationend", done);
|
|
2031
|
+
if (safetyTimer !== null) {
|
|
2032
|
+
clearTimeout(safetyTimer);
|
|
2033
|
+
safetyTimer = null;
|
|
2034
|
+
}
|
|
1949
2035
|
el.classList.remove(cls.lf, cls.la, cls.lt);
|
|
1950
2036
|
};
|
|
1951
2037
|
el.addEventListener("transitionend", done, { once: true });
|
|
1952
2038
|
el.addEventListener("animationend", done, { once: true });
|
|
1953
|
-
setTimeout(done, 5e3);
|
|
2039
|
+
safetyTimer = setTimeout(done, 5e3);
|
|
1954
2040
|
});
|
|
1955
2041
|
};
|
|
1956
2042
|
const handleVisibilityChange = (visible) => {
|
|
@@ -1977,6 +2063,8 @@ function Transition(props) {
|
|
|
1977
2063
|
handleVisibilityChange(visible);
|
|
1978
2064
|
});
|
|
1979
2065
|
onUnmount(() => {
|
|
2066
|
+
pendingEnterCancel?.();
|
|
2067
|
+
pendingEnterCancel = null;
|
|
1980
2068
|
pendingLeaveCancel?.();
|
|
1981
2069
|
pendingLeaveCancel = null;
|
|
1982
2070
|
});
|
|
@@ -2043,39 +2131,71 @@ function TransitionGroup(props) {
|
|
|
2043
2131
|
const entries = /* @__PURE__ */ new Map();
|
|
2044
2132
|
const ready = signal(false);
|
|
2045
2133
|
let firstRun = true;
|
|
2046
|
-
const applyEnter = (el) => {
|
|
2134
|
+
const applyEnter = (entry, el) => {
|
|
2047
2135
|
props.onBeforeEnter?.(el);
|
|
2048
2136
|
el.classList.remove(cls.lf, cls.la, cls.lt);
|
|
2049
2137
|
el.classList.add(cls.ef, cls.ea);
|
|
2050
2138
|
requestAnimationFrame(() => {
|
|
2051
2139
|
el.classList.remove(cls.ef);
|
|
2052
2140
|
el.classList.add(cls.et);
|
|
2141
|
+
let safetyTimer = null;
|
|
2053
2142
|
const done = () => {
|
|
2054
2143
|
el.removeEventListener("transitionend", done);
|
|
2055
2144
|
el.removeEventListener("animationend", done);
|
|
2145
|
+
if (safetyTimer !== null) {
|
|
2146
|
+
clearTimeout(safetyTimer);
|
|
2147
|
+
safetyTimer = null;
|
|
2148
|
+
}
|
|
2149
|
+
entry.cancelTransition = null;
|
|
2056
2150
|
el.classList.remove(cls.ea, cls.et);
|
|
2057
2151
|
props.onAfterEnter?.(el);
|
|
2058
2152
|
};
|
|
2153
|
+
entry.cancelTransition = () => {
|
|
2154
|
+
el.removeEventListener("transitionend", done);
|
|
2155
|
+
el.removeEventListener("animationend", done);
|
|
2156
|
+
if (safetyTimer !== null) {
|
|
2157
|
+
clearTimeout(safetyTimer);
|
|
2158
|
+
safetyTimer = null;
|
|
2159
|
+
}
|
|
2160
|
+
el.classList.remove(cls.ef, cls.ea, cls.et);
|
|
2161
|
+
};
|
|
2059
2162
|
el.addEventListener("transitionend", done, { once: true });
|
|
2060
2163
|
el.addEventListener("animationend", done, { once: true });
|
|
2164
|
+
safetyTimer = setTimeout(done, 5e3);
|
|
2061
2165
|
});
|
|
2062
2166
|
};
|
|
2063
|
-
const applyLeave = (el, onDone) => {
|
|
2167
|
+
const applyLeave = (entry, el, onDone) => {
|
|
2064
2168
|
props.onBeforeLeave?.(el);
|
|
2065
2169
|
el.classList.remove(cls.ef, cls.ea, cls.et);
|
|
2066
2170
|
el.classList.add(cls.lf, cls.la);
|
|
2067
2171
|
requestAnimationFrame(() => {
|
|
2068
2172
|
el.classList.remove(cls.lf);
|
|
2069
2173
|
el.classList.add(cls.lt);
|
|
2174
|
+
let safetyTimer = null;
|
|
2070
2175
|
const done = () => {
|
|
2071
2176
|
el.removeEventListener("transitionend", done);
|
|
2072
2177
|
el.removeEventListener("animationend", done);
|
|
2178
|
+
if (safetyTimer !== null) {
|
|
2179
|
+
clearTimeout(safetyTimer);
|
|
2180
|
+
safetyTimer = null;
|
|
2181
|
+
}
|
|
2182
|
+
entry.cancelTransition = null;
|
|
2073
2183
|
el.classList.remove(cls.la, cls.lt);
|
|
2074
2184
|
props.onAfterLeave?.(el);
|
|
2075
2185
|
onDone();
|
|
2076
2186
|
};
|
|
2187
|
+
entry.cancelTransition = () => {
|
|
2188
|
+
el.removeEventListener("transitionend", done);
|
|
2189
|
+
el.removeEventListener("animationend", done);
|
|
2190
|
+
if (safetyTimer !== null) {
|
|
2191
|
+
clearTimeout(safetyTimer);
|
|
2192
|
+
safetyTimer = null;
|
|
2193
|
+
}
|
|
2194
|
+
el.classList.remove(cls.lf, cls.la, cls.lt);
|
|
2195
|
+
};
|
|
2077
2196
|
el.addEventListener("transitionend", done, { once: true });
|
|
2078
2197
|
el.addEventListener("animationend", done, { once: true });
|
|
2198
|
+
safetyTimer = setTimeout(done, 5e3);
|
|
2079
2199
|
});
|
|
2080
2200
|
};
|
|
2081
2201
|
/** Start leave animation for removed items. */
|
|
@@ -2084,7 +2204,7 @@ function TransitionGroup(props) {
|
|
|
2084
2204
|
if (newKeys.has(key) || entry.leaving) continue;
|
|
2085
2205
|
entry.leaving = true;
|
|
2086
2206
|
const el = entry.ref.current;
|
|
2087
|
-
if (el) applyLeave(el, () => {
|
|
2207
|
+
if (el) applyLeave(entry, el, () => {
|
|
2088
2208
|
entry.cleanup();
|
|
2089
2209
|
entries.delete(key);
|
|
2090
2210
|
});
|
|
@@ -2113,25 +2233,42 @@ function TransitionGroup(props) {
|
|
|
2113
2233
|
ref: itemRef
|
|
2114
2234
|
}
|
|
2115
2235
|
} : rawVNode, container, null),
|
|
2116
|
-
leaving: false
|
|
2236
|
+
leaving: false,
|
|
2237
|
+
cancelTransition: null
|
|
2117
2238
|
};
|
|
2118
2239
|
entries.set(key, entry);
|
|
2119
2240
|
newEntries.push(entry);
|
|
2120
2241
|
}
|
|
2121
2242
|
return newEntries;
|
|
2122
2243
|
};
|
|
2123
|
-
const startMoveAnimation = (el) => {
|
|
2244
|
+
const startMoveAnimation = (entry, el) => {
|
|
2124
2245
|
requestAnimationFrame(() => {
|
|
2125
2246
|
el.classList.add(cls.mv);
|
|
2126
2247
|
el.style.transform = "";
|
|
2127
2248
|
el.style.transition = "";
|
|
2249
|
+
let safetyTimer = null;
|
|
2128
2250
|
const done = () => {
|
|
2129
2251
|
el.removeEventListener("transitionend", done);
|
|
2130
2252
|
el.removeEventListener("animationend", done);
|
|
2253
|
+
if (safetyTimer !== null) {
|
|
2254
|
+
clearTimeout(safetyTimer);
|
|
2255
|
+
safetyTimer = null;
|
|
2256
|
+
}
|
|
2257
|
+
entry.cancelTransition = null;
|
|
2258
|
+
el.classList.remove(cls.mv);
|
|
2259
|
+
};
|
|
2260
|
+
entry.cancelTransition = () => {
|
|
2261
|
+
el.removeEventListener("transitionend", done);
|
|
2262
|
+
el.removeEventListener("animationend", done);
|
|
2263
|
+
if (safetyTimer !== null) {
|
|
2264
|
+
clearTimeout(safetyTimer);
|
|
2265
|
+
safetyTimer = null;
|
|
2266
|
+
}
|
|
2131
2267
|
el.classList.remove(cls.mv);
|
|
2132
2268
|
};
|
|
2133
2269
|
el.addEventListener("transitionend", done, { once: true });
|
|
2134
2270
|
el.addEventListener("animationend", done, { once: true });
|
|
2271
|
+
safetyTimer = setTimeout(done, 5e3);
|
|
2135
2272
|
});
|
|
2136
2273
|
};
|
|
2137
2274
|
const flipEntry = (entry, oldPos) => {
|
|
@@ -2143,7 +2280,7 @@ function TransitionGroup(props) {
|
|
|
2143
2280
|
const el = entry.ref.current;
|
|
2144
2281
|
el.style.transform = `translate(${dx}px, ${dy}px)`;
|
|
2145
2282
|
el.style.transition = "none";
|
|
2146
|
-
startMoveAnimation(el);
|
|
2283
|
+
startMoveAnimation(entry, el);
|
|
2147
2284
|
};
|
|
2148
2285
|
/** Apply FLIP move animations for items that shifted position. */
|
|
2149
2286
|
const applyFlipMoves = (oldPositions) => {
|
|
@@ -2171,7 +2308,7 @@ function TransitionGroup(props) {
|
|
|
2171
2308
|
};
|
|
2172
2309
|
const animateNewEntries = (newEntries) => {
|
|
2173
2310
|
for (const entry of newEntries) queueMicrotask(() => {
|
|
2174
|
-
if (entry.ref.current) applyEnter(entry.ref.current);
|
|
2311
|
+
if (entry.ref.current) applyEnter(entry, entry.ref.current);
|
|
2175
2312
|
});
|
|
2176
2313
|
};
|
|
2177
2314
|
const e = effect(() => {
|
|
@@ -2194,7 +2331,11 @@ function TransitionGroup(props) {
|
|
|
2194
2331
|
});
|
|
2195
2332
|
onUnmount(() => {
|
|
2196
2333
|
e.dispose();
|
|
2197
|
-
for (const entry of entries.values())
|
|
2334
|
+
for (const entry of entries.values()) {
|
|
2335
|
+
entry.cancelTransition?.();
|
|
2336
|
+
entry.cancelTransition = null;
|
|
2337
|
+
entry.cleanup();
|
|
2338
|
+
}
|
|
2198
2339
|
entries.clear();
|
|
2199
2340
|
});
|
|
2200
2341
|
return h(tag, { ref: containerRef });
|