@jsenv/navi 0.12.7 → 0.12.8
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/dist/jsenv_navi.js +1624 -109
- package/dist/jsenv_navi.js.map +19 -16
- package/dist/jsenv_navi_side_effects.js +3 -12
- package/dist/jsenv_navi_side_effects.js.map +2 -2
- package/package.json +3 -3
package/dist/jsenv_navi.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { installImportMetaCss } from "./jsenv_navi_side_effects.js";
|
|
2
|
-
import { createIterableWeakSet, createPubSub, createValueEffect, createStyleController, getVisuallyVisibleInfo, getFirstVisuallyVisibleAncestor, allowWheelThrough, visibleRectEffect, pickPositionRelativeTo, getBorderSizes, getPaddingSizes, activeElementSignal, canInterceptKeys,
|
|
2
|
+
import { createIterableWeakSet, createPubSub, createValueEffect, createStyleController, getVisuallyVisibleInfo, getFirstVisuallyVisibleAncestor, allowWheelThrough, resolveCSSColor, visibleRectEffect, pickPositionRelativeTo, getBorderSizes, getPaddingSizes, activeElementSignal, canInterceptKeys, createGroupTransitionController, getWidthWithoutTransition, getHeightWithoutTransition, createWidthTransition, createHeightTransition, getElementSignature, getTranslateX, getInnerWidth, createTranslateXTransition, getTranslateXWithoutTransition, getOpacity, createOpacityTransition, getOpacityWithoutTransition, normalizeStyle, mergeTwoStyles, appendStyles, normalizeStyles, resolveCSSSize, findBefore, findAfter, initFocusGroup, elementIsFocusable, pickLightOrDark, resolveColorLuminance, dragAfterThreshold, getScrollContainer, stickyAsRelativeCoords, createDragToMoveGestureController, getDropTargetInfo, setStyles, useActiveElement } from "@jsenv/dom";
|
|
3
3
|
import { prefixFirstAndIndentRemainingLines } from "@jsenv/humanize";
|
|
4
4
|
import { effect, signal, computed, batch, useSignal } from "@preact/signals";
|
|
5
5
|
import { useEffect, useRef, useCallback, useContext, useState, useLayoutEffect, useMemo, useErrorBoundary, useImperativeHandle, useId } from "preact/hooks";
|
|
@@ -614,9 +614,9 @@ const weakEffect = (values, callback) => {
|
|
|
614
614
|
return dispose;
|
|
615
615
|
};
|
|
616
616
|
|
|
617
|
-
let DEBUG$
|
|
617
|
+
let DEBUG$3 = false;
|
|
618
618
|
const enableDebugActions = () => {
|
|
619
|
-
DEBUG$
|
|
619
|
+
DEBUG$3 = true;
|
|
620
620
|
};
|
|
621
621
|
|
|
622
622
|
let dispatchActions = (params) => {
|
|
@@ -680,7 +680,7 @@ const prerunProtectionRegistry = (() => {
|
|
|
680
680
|
if (protection) {
|
|
681
681
|
clearTimeout(protection.timeoutId);
|
|
682
682
|
protectedActionMap.delete(action);
|
|
683
|
-
if (DEBUG$
|
|
683
|
+
if (DEBUG$3) {
|
|
684
684
|
const elapsed = Date.now() - protection.timestamp;
|
|
685
685
|
console.debug(`"${action}": GC protection removed after ${elapsed}ms`);
|
|
686
686
|
}
|
|
@@ -698,7 +698,7 @@ const prerunProtectionRegistry = (() => {
|
|
|
698
698
|
const timestamp = Date.now();
|
|
699
699
|
const timeoutId = setTimeout(() => {
|
|
700
700
|
unprotect(action);
|
|
701
|
-
if (DEBUG$
|
|
701
|
+
if (DEBUG$3) {
|
|
702
702
|
console.debug(
|
|
703
703
|
`"${action}": prerun protection expired after ${PROTECTION_DURATION}ms`,
|
|
704
704
|
);
|
|
@@ -707,7 +707,7 @@ const prerunProtectionRegistry = (() => {
|
|
|
707
707
|
|
|
708
708
|
protectedActionMap.set(action, { timeoutId, timestamp });
|
|
709
709
|
|
|
710
|
-
if (DEBUG$
|
|
710
|
+
if (DEBUG$3) {
|
|
711
711
|
console.debug(
|
|
712
712
|
`"${action}": protected from GC for ${PROTECTION_DURATION}ms`,
|
|
713
713
|
);
|
|
@@ -818,7 +818,7 @@ const updateActions = ({
|
|
|
818
818
|
|
|
819
819
|
const { runningSet, settledSet } = getActivationInfo();
|
|
820
820
|
|
|
821
|
-
if (DEBUG$
|
|
821
|
+
if (DEBUG$3) {
|
|
822
822
|
let argSource = `reason: \`${reason}\``;
|
|
823
823
|
if (isReplace) {
|
|
824
824
|
argSource += `, isReplace: true`;
|
|
@@ -942,7 +942,7 @@ ${lines.join("\n")}`,
|
|
|
942
942
|
}
|
|
943
943
|
}
|
|
944
944
|
}
|
|
945
|
-
if (DEBUG$
|
|
945
|
+
if (DEBUG$3) {
|
|
946
946
|
const lines = [
|
|
947
947
|
...(willResetSet.size
|
|
948
948
|
? [formatActionSet(willResetSet, "- will reset:")]
|
|
@@ -1038,7 +1038,7 @@ ${lines.join("\n")}`);
|
|
|
1038
1038
|
actionToPromotePrivateProperties.isPrerunSignal.value = false;
|
|
1039
1039
|
}
|
|
1040
1040
|
}
|
|
1041
|
-
if (DEBUG$
|
|
1041
|
+
if (DEBUG$3) {
|
|
1042
1042
|
console.groupEnd();
|
|
1043
1043
|
}
|
|
1044
1044
|
|
|
@@ -1149,7 +1149,7 @@ const createAction = (callback, rootOptions = {}) => {
|
|
|
1149
1149
|
if (!actionAbort) {
|
|
1150
1150
|
return false;
|
|
1151
1151
|
}
|
|
1152
|
-
if (DEBUG$
|
|
1152
|
+
if (DEBUG$3) {
|
|
1153
1153
|
console.log(`"${action}": aborting (reason: ${reason})`);
|
|
1154
1154
|
}
|
|
1155
1155
|
actionAbort(reason);
|
|
@@ -1421,7 +1421,7 @@ const createAction = (callback, rootOptions = {}) => {
|
|
|
1421
1421
|
if (isPrerun && (globalAbortSignal.aborted || abortSignal.aborted)) {
|
|
1422
1422
|
prerunProtectionRegistry.unprotect(action);
|
|
1423
1423
|
}
|
|
1424
|
-
if (DEBUG$
|
|
1424
|
+
if (DEBUG$3) {
|
|
1425
1425
|
console.log(`"${action}": aborted (reason: ${abortReason})`);
|
|
1426
1426
|
}
|
|
1427
1427
|
};
|
|
@@ -1494,7 +1494,7 @@ const createAction = (callback, rootOptions = {}) => {
|
|
|
1494
1494
|
onComplete?.(computedDataSignal.peek(), action);
|
|
1495
1495
|
completeSideEffect?.(action);
|
|
1496
1496
|
});
|
|
1497
|
-
if (DEBUG$
|
|
1497
|
+
if (DEBUG$3) {
|
|
1498
1498
|
console.log(`"${action}": completed`);
|
|
1499
1499
|
}
|
|
1500
1500
|
return computedDataSignal.peek();
|
|
@@ -1521,7 +1521,7 @@ const createAction = (callback, rootOptions = {}) => {
|
|
|
1521
1521
|
"never supposed to happen, abort error should be handled by the abort signal",
|
|
1522
1522
|
);
|
|
1523
1523
|
}
|
|
1524
|
-
if (DEBUG$
|
|
1524
|
+
if (DEBUG$3) {
|
|
1525
1525
|
console.log(
|
|
1526
1526
|
`"${action}": failed (error: ${e}, handled by ui: ${ui.hasRenderers})`,
|
|
1527
1527
|
);
|
|
@@ -1591,7 +1591,7 @@ const createAction = (callback, rootOptions = {}) => {
|
|
|
1591
1591
|
|
|
1592
1592
|
const performStop = ({ reason }) => {
|
|
1593
1593
|
abort(reason);
|
|
1594
|
-
if (DEBUG$
|
|
1594
|
+
if (DEBUG$3) {
|
|
1595
1595
|
console.log(`"${action}": stopping (reason: ${reason})`);
|
|
1596
1596
|
}
|
|
1597
1597
|
|
|
@@ -2089,10 +2089,11 @@ const openCallout = (
|
|
|
2089
2089
|
});
|
|
2090
2090
|
|
|
2091
2091
|
addLevelEffect(() => {
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2092
|
+
const levelColor = resolveCSSColor(
|
|
2093
|
+
`var(--${level}-color)`,
|
|
2094
|
+
calloutElement,
|
|
2095
2095
|
);
|
|
2096
|
+
anchorElement.style.setProperty("--callout-color", levelColor);
|
|
2096
2097
|
return () => {
|
|
2097
2098
|
anchorElement.style.removeProperty("--callout-color");
|
|
2098
2099
|
};
|
|
@@ -2200,7 +2201,13 @@ const ARROW_SPACING = 8;
|
|
|
2200
2201
|
import.meta.css = /* css */ `
|
|
2201
2202
|
@layer navi {
|
|
2202
2203
|
.navi_callout {
|
|
2204
|
+
--success-color: #4caf50;
|
|
2205
|
+
--info-color: #2196f3;
|
|
2206
|
+
--warning-color: #ff9800;
|
|
2207
|
+
--error-color: #f44336;
|
|
2208
|
+
|
|
2203
2209
|
--background-color: white;
|
|
2210
|
+
--icon-color: black;
|
|
2204
2211
|
--padding: 8px;
|
|
2205
2212
|
}
|
|
2206
2213
|
}
|
|
@@ -2218,8 +2225,8 @@ import.meta.css = /* css */ `
|
|
|
2218
2225
|
overflow: visible;
|
|
2219
2226
|
|
|
2220
2227
|
--x-border-color: var(--x-level-color);
|
|
2228
|
+
--x-background-color: var(--x-level-color);
|
|
2221
2229
|
--x-icon-color: var(--x-level-color);
|
|
2222
|
-
--x-background-color: var(--background-color);
|
|
2223
2230
|
}
|
|
2224
2231
|
|
|
2225
2232
|
.navi_callout_frame {
|
|
@@ -2230,16 +2237,6 @@ import.meta.css = /* css */ `
|
|
|
2230
2237
|
.navi_callout .navi_callout_border {
|
|
2231
2238
|
fill: var(--x-border-color);
|
|
2232
2239
|
}
|
|
2233
|
-
.navi_callout[data-level="info"] {
|
|
2234
|
-
--x-level-color: var(--navi-info-color);
|
|
2235
|
-
}
|
|
2236
|
-
.navi_callout[data-level="warning"] {
|
|
2237
|
-
--x-level-color: var(--navi-warning-color);
|
|
2238
|
-
}
|
|
2239
|
-
.navi_callout[data-level="error"] {
|
|
2240
|
-
--x-level-color: var(--navi-error-color);
|
|
2241
|
-
}
|
|
2242
|
-
|
|
2243
2240
|
.navi_callout_frame svg {
|
|
2244
2241
|
position: absolute;
|
|
2245
2242
|
inset: 0;
|
|
@@ -2248,7 +2245,6 @@ import.meta.css = /* css */ `
|
|
|
2248
2245
|
.navi_callout_background {
|
|
2249
2246
|
fill: var(--x-background-color);
|
|
2250
2247
|
}
|
|
2251
|
-
|
|
2252
2248
|
.navi_callout_box {
|
|
2253
2249
|
position: relative;
|
|
2254
2250
|
border-style: solid;
|
|
@@ -2316,6 +2312,19 @@ import.meta.css = /* css */ `
|
|
|
2316
2312
|
max-height: 200px;
|
|
2317
2313
|
overflow: auto;
|
|
2318
2314
|
}
|
|
2315
|
+
|
|
2316
|
+
.navi_callout[data-level="success"] {
|
|
2317
|
+
--x-level-color: var(--success-color);
|
|
2318
|
+
}
|
|
2319
|
+
.navi_callout[data-level="info"] {
|
|
2320
|
+
--x-level-color: var(--info-color);
|
|
2321
|
+
}
|
|
2322
|
+
.navi_callout[data-level="warning"] {
|
|
2323
|
+
--x-level-color: var(--warning-color);
|
|
2324
|
+
}
|
|
2325
|
+
.navi_callout[data-level="error"] {
|
|
2326
|
+
--x-level-color: var(--error-color);
|
|
2327
|
+
}
|
|
2319
2328
|
`;
|
|
2320
2329
|
|
|
2321
2330
|
// HTML template for the callout
|
|
@@ -8004,9 +8013,9 @@ const executeWithCleanup = (fn, cleanup) => {
|
|
|
8004
8013
|
}
|
|
8005
8014
|
};
|
|
8006
8015
|
|
|
8007
|
-
let DEBUG$
|
|
8016
|
+
let DEBUG$2 = false;
|
|
8008
8017
|
const enableDebugOnDocumentLoading = () => {
|
|
8009
|
-
DEBUG$
|
|
8018
|
+
DEBUG$2 = true;
|
|
8010
8019
|
};
|
|
8011
8020
|
|
|
8012
8021
|
const windowIsLoadingSignal = signal(true);
|
|
@@ -8026,13 +8035,13 @@ const [
|
|
|
8026
8035
|
removeFromDocumentLoadingRouteArraySignal,
|
|
8027
8036
|
] = arraySignal([]);
|
|
8028
8037
|
const routingWhile = (fn, routeNames = []) => {
|
|
8029
|
-
if (DEBUG$
|
|
8038
|
+
if (DEBUG$2 && routeNames.length > 0) {
|
|
8030
8039
|
console.debug(`routingWhile: Adding routes to loading state:`, routeNames);
|
|
8031
8040
|
}
|
|
8032
8041
|
addToDocumentLoadingRouteArraySignal(...routeNames);
|
|
8033
8042
|
return executeWithCleanup(fn, () => {
|
|
8034
8043
|
removeFromDocumentLoadingRouteArraySignal(...routeNames);
|
|
8035
|
-
if (DEBUG$
|
|
8044
|
+
if (DEBUG$2 && routeNames.length > 0) {
|
|
8036
8045
|
console.debug(
|
|
8037
8046
|
`routingWhile: Removed routes from loading state:`,
|
|
8038
8047
|
routeNames,
|
|
@@ -8049,7 +8058,7 @@ const [
|
|
|
8049
8058
|
removeFromDocumentLoadingActionArraySignal,
|
|
8050
8059
|
] = arraySignal([]);
|
|
8051
8060
|
const workingWhile = (fn, actionNames = []) => {
|
|
8052
|
-
if (DEBUG$
|
|
8061
|
+
if (DEBUG$2 && actionNames.length > 0) {
|
|
8053
8062
|
console.debug(
|
|
8054
8063
|
`workingWhile: Adding actions to loading state:`,
|
|
8055
8064
|
actionNames,
|
|
@@ -8058,7 +8067,7 @@ const workingWhile = (fn, actionNames = []) => {
|
|
|
8058
8067
|
addToDocumentLoadingActionArraySignal(...actionNames);
|
|
8059
8068
|
return executeWithCleanup(fn, () => {
|
|
8060
8069
|
removeFromDocumentLoadingActionArraySignal(...actionNames);
|
|
8061
|
-
if (DEBUG$
|
|
8070
|
+
if (DEBUG$2 && actionNames.length > 0) {
|
|
8062
8071
|
console.debug(
|
|
8063
8072
|
`routingWhile: Removed action from loading state:`,
|
|
8064
8073
|
actionNames,
|
|
@@ -8576,6 +8585,1536 @@ const useUrlSearchParam = (paramName) => {
|
|
|
8576
8585
|
return [value, setSearchParamValue];
|
|
8577
8586
|
};
|
|
8578
8587
|
|
|
8588
|
+
installImportMetaCss(import.meta);
|
|
8589
|
+
import.meta.css = /* css */ `
|
|
8590
|
+
.ui_transition_container[data-transition-running] {
|
|
8591
|
+
/* When transition are running we need to put overflow: hidden */
|
|
8592
|
+
/* Either because the transition slides */
|
|
8593
|
+
/* Or when size transition are disabled because we need to immediatly crop old content when it's bigger than new content */
|
|
8594
|
+
overflow: hidden;
|
|
8595
|
+
}
|
|
8596
|
+
|
|
8597
|
+
.ui_transition_container,
|
|
8598
|
+
.ui_transition_outer_wrapper,
|
|
8599
|
+
.ui_transition_slot,
|
|
8600
|
+
.ui_transition_phase_overlay,
|
|
8601
|
+
.ui_transition_content_overlay {
|
|
8602
|
+
display: flex;
|
|
8603
|
+
width: fit-content;
|
|
8604
|
+
min-width: 100%;
|
|
8605
|
+
height: fit-content;
|
|
8606
|
+
min-height: 100%;
|
|
8607
|
+
flex-direction: inherit;
|
|
8608
|
+
align-items: inherit;
|
|
8609
|
+
justify-content: inherit;
|
|
8610
|
+
border-radius: inherit;
|
|
8611
|
+
cursor: inherit;
|
|
8612
|
+
}
|
|
8613
|
+
|
|
8614
|
+
.ui_transition_slot {
|
|
8615
|
+
width: 100%;
|
|
8616
|
+
min-width: fit-content;
|
|
8617
|
+
height: 100%;
|
|
8618
|
+
min-height: fit-content;
|
|
8619
|
+
flex-direction: column;
|
|
8620
|
+
}
|
|
8621
|
+
|
|
8622
|
+
.ui_transition_container,
|
|
8623
|
+
.ui_transition_slot {
|
|
8624
|
+
position: relative;
|
|
8625
|
+
}
|
|
8626
|
+
|
|
8627
|
+
.ui_transition_phase_overlay,
|
|
8628
|
+
.ui_transition_content_overlay {
|
|
8629
|
+
position: absolute;
|
|
8630
|
+
inset: 0;
|
|
8631
|
+
pointer-events: none;
|
|
8632
|
+
}
|
|
8633
|
+
`;
|
|
8634
|
+
|
|
8635
|
+
const DEBUG$1 = {
|
|
8636
|
+
detection: false,
|
|
8637
|
+
size: false,
|
|
8638
|
+
content: false,
|
|
8639
|
+
transition_updates: false,
|
|
8640
|
+
};
|
|
8641
|
+
|
|
8642
|
+
const SIZE_TRANSITION_DURATION = 150; // Default size transition duration
|
|
8643
|
+
const SIZE_DIFF_EPSILON = 0.5; // Ignore size transition when difference below this (px)
|
|
8644
|
+
const CONTENT_TRANSITION = "cross-fade"; // Default content transition type
|
|
8645
|
+
const CONTENT_TRANSITION_DURATION = 300; // Default content transition duration
|
|
8646
|
+
const PHASE_TRANSITION = "cross-fade"; // Default phase transition type (only cross-fade supported)
|
|
8647
|
+
const PHASE_TRANSITION_DURATION = 300; // Default phase transition duration
|
|
8648
|
+
|
|
8649
|
+
const initUITransition = (container) => {
|
|
8650
|
+
if (!container.classList.contains("ui_transition_container")) {
|
|
8651
|
+
console.error("Element must have ui_transition_container class");
|
|
8652
|
+
return { cleanup: () => {} };
|
|
8653
|
+
}
|
|
8654
|
+
|
|
8655
|
+
const localDebug = {
|
|
8656
|
+
...DEBUG$1,
|
|
8657
|
+
detection: container.hasAttribute("data-debug-detection"),
|
|
8658
|
+
size: container.hasAttribute("data-debug-size"),
|
|
8659
|
+
content: container.hasAttribute("data-debug-content"),
|
|
8660
|
+
};
|
|
8661
|
+
const hasSomeDebugLogs =
|
|
8662
|
+
localDebug.detection || localDebug.size || localDebug.content;
|
|
8663
|
+
const debugClones = container.hasAttribute("data-debug-clones");
|
|
8664
|
+
const debugBreakAfterClone = container.getAttribute(
|
|
8665
|
+
"data-debug-break-after-clone",
|
|
8666
|
+
);
|
|
8667
|
+
const debug = (type, ...args) => {
|
|
8668
|
+
if (localDebug[type]) {
|
|
8669
|
+
console.debug(`[${type}]`, ...args);
|
|
8670
|
+
}
|
|
8671
|
+
};
|
|
8672
|
+
|
|
8673
|
+
const outerWrapper = container.querySelector(".ui_transition_outer_wrapper");
|
|
8674
|
+
const slot = container.querySelector(".ui_transition_slot");
|
|
8675
|
+
const phaseOverlay = outerWrapper.querySelector(
|
|
8676
|
+
".ui_transition_phase_overlay",
|
|
8677
|
+
);
|
|
8678
|
+
const contentOverlay = container.querySelector(
|
|
8679
|
+
".ui_transition_content_overlay",
|
|
8680
|
+
);
|
|
8681
|
+
if (!outerWrapper || !slot || !phaseOverlay || !contentOverlay) {
|
|
8682
|
+
console.error("Missing required ui-transition structure");
|
|
8683
|
+
return { cleanup: () => {} };
|
|
8684
|
+
}
|
|
8685
|
+
|
|
8686
|
+
const state = {
|
|
8687
|
+
isPaused: false,
|
|
8688
|
+
};
|
|
8689
|
+
const initialTransitionEnabled = container.hasAttribute(
|
|
8690
|
+
"data-initial-transition",
|
|
8691
|
+
);
|
|
8692
|
+
const transitionController = createGroupTransitionController();
|
|
8693
|
+
const setupTransition = ({
|
|
8694
|
+
isPhaseTransition = false,
|
|
8695
|
+
overlay,
|
|
8696
|
+
needsOldChildNodesClone,
|
|
8697
|
+
previousChildNodes,
|
|
8698
|
+
childNodes,
|
|
8699
|
+
slotInfo,
|
|
8700
|
+
attributeToRemove = [],
|
|
8701
|
+
}) => {
|
|
8702
|
+
let cleanup = () => {};
|
|
8703
|
+
let elementToImpact;
|
|
8704
|
+
|
|
8705
|
+
if (overlay.childNodes.length > 0) {
|
|
8706
|
+
elementToImpact = overlay;
|
|
8707
|
+
cleanup = () => {
|
|
8708
|
+
if (!debugClones) {
|
|
8709
|
+
overlay.innerHTML = "";
|
|
8710
|
+
}
|
|
8711
|
+
};
|
|
8712
|
+
debug(
|
|
8713
|
+
"content",
|
|
8714
|
+
`Continuing from current ${isPhaseTransition ? "phase" : "content"} transition element`,
|
|
8715
|
+
);
|
|
8716
|
+
} else if (needsOldChildNodesClone) {
|
|
8717
|
+
overlay.innerHTML = "";
|
|
8718
|
+
for (const previousChildNode of previousChildNodes) {
|
|
8719
|
+
const previousChildClone = previousChildNode.cloneNode(true);
|
|
8720
|
+
if (previousChildClone.nodeType !== Node.TEXT_NODE) {
|
|
8721
|
+
for (const attrToRemove of attributeToRemove) {
|
|
8722
|
+
previousChildClone.removeAttribute(attrToRemove);
|
|
8723
|
+
}
|
|
8724
|
+
previousChildClone.setAttribute("data-ui-transition-clone", "");
|
|
8725
|
+
}
|
|
8726
|
+
overlay.appendChild(previousChildClone);
|
|
8727
|
+
}
|
|
8728
|
+
elementToImpact = overlay;
|
|
8729
|
+
cleanup = () => {
|
|
8730
|
+
if (!debugClones) {
|
|
8731
|
+
overlay.innerHTML = "";
|
|
8732
|
+
}
|
|
8733
|
+
};
|
|
8734
|
+
debug(
|
|
8735
|
+
"content",
|
|
8736
|
+
`Cloned previous child for ${isPhaseTransition ? "phase" : "content"} transition:`,
|
|
8737
|
+
getElementSignature(previousChildNodes),
|
|
8738
|
+
);
|
|
8739
|
+
if (debugBreakAfterClone === slotInfo.contentKey) {
|
|
8740
|
+
debugger;
|
|
8741
|
+
}
|
|
8742
|
+
} else {
|
|
8743
|
+
overlay.innerHTML = "";
|
|
8744
|
+
debug(
|
|
8745
|
+
"content",
|
|
8746
|
+
`No old child to clone for ${isPhaseTransition ? "phase" : "content"} transition`,
|
|
8747
|
+
);
|
|
8748
|
+
}
|
|
8749
|
+
|
|
8750
|
+
// Determine which elements to return based on transition type:
|
|
8751
|
+
// - Phase transitions: operate on individual elements (cross-fade between specific elements)
|
|
8752
|
+
// - Content transitions: operate at container level (slide entire containers, outlive content phases)
|
|
8753
|
+
let oldElement;
|
|
8754
|
+
let newElement;
|
|
8755
|
+
if (isPhaseTransition) {
|
|
8756
|
+
// Phase transitions work on individual elements
|
|
8757
|
+
oldElement = elementToImpact;
|
|
8758
|
+
newElement = slot;
|
|
8759
|
+
} else {
|
|
8760
|
+
// Content transitions work at container level and can outlive content phase changes
|
|
8761
|
+
oldElement = previousChildNodes.length ? elementToImpact : null;
|
|
8762
|
+
newElement = childNodes.length ? slot : null;
|
|
8763
|
+
}
|
|
8764
|
+
|
|
8765
|
+
return {
|
|
8766
|
+
cleanup,
|
|
8767
|
+
oldElement,
|
|
8768
|
+
newElement,
|
|
8769
|
+
};
|
|
8770
|
+
};
|
|
8771
|
+
const [teardown, addTeardown] = createPubSub();
|
|
8772
|
+
const [publishPause, addPauseCallback] = createPubSub();
|
|
8773
|
+
const [publishResume, addResumeCallback] = createPubSub();
|
|
8774
|
+
|
|
8775
|
+
const [publishChange, subscribeChange] = createPubSub();
|
|
8776
|
+
let triggerChildSlotMutation;
|
|
8777
|
+
let previousSlotInfo;
|
|
8778
|
+
let slotInfo;
|
|
8779
|
+
let changeInfo;
|
|
8780
|
+
{
|
|
8781
|
+
const createSlotInfo = (childNodes, { contentKey, contentPhase }) => {
|
|
8782
|
+
const hasChild = childNodes.length > 0;
|
|
8783
|
+
let contentKeyFormatted;
|
|
8784
|
+
let contentName;
|
|
8785
|
+
if (hasChild) {
|
|
8786
|
+
if (contentKey) {
|
|
8787
|
+
contentKeyFormatted = `[data-content-key="${contentKey}"]`;
|
|
8788
|
+
} else {
|
|
8789
|
+
let onlyTextNodes = true;
|
|
8790
|
+
for (const child of childNodes) {
|
|
8791
|
+
if (child.nodeType !== Node.TEXT_NODE) {
|
|
8792
|
+
onlyTextNodes = false;
|
|
8793
|
+
break;
|
|
8794
|
+
}
|
|
8795
|
+
}
|
|
8796
|
+
contentKeyFormatted = onlyTextNodes ? "[text]" : "[unkeyed]";
|
|
8797
|
+
}
|
|
8798
|
+
contentName = contentPhase ? "content-phase" : "content";
|
|
8799
|
+
} else {
|
|
8800
|
+
contentKeyFormatted = "[empty]";
|
|
8801
|
+
contentName = "null";
|
|
8802
|
+
}
|
|
8803
|
+
|
|
8804
|
+
return {
|
|
8805
|
+
childNodes,
|
|
8806
|
+
contentKey,
|
|
8807
|
+
contentPhase,
|
|
8808
|
+
|
|
8809
|
+
hasChild: childNodes.length > 0,
|
|
8810
|
+
contentKeyFormatted,
|
|
8811
|
+
isContentPhase: Boolean(contentPhase),
|
|
8812
|
+
contentName,
|
|
8813
|
+
};
|
|
8814
|
+
};
|
|
8815
|
+
previousSlotInfo = createSlotInfo([], {
|
|
8816
|
+
contentKey: undefined,
|
|
8817
|
+
contentPhase: undefined,
|
|
8818
|
+
});
|
|
8819
|
+
slotInfo = previousSlotInfo;
|
|
8820
|
+
let isUpdating = false;
|
|
8821
|
+
triggerChildSlotMutation = (reason) => {
|
|
8822
|
+
if (isUpdating) {
|
|
8823
|
+
debug("detection", "Preventing recursive update");
|
|
8824
|
+
return;
|
|
8825
|
+
}
|
|
8826
|
+
try {
|
|
8827
|
+
const childNodes = Array.from(slot.childNodes);
|
|
8828
|
+
if (hasSomeDebugLogs) {
|
|
8829
|
+
const updateLabel =
|
|
8830
|
+
childNodes.length === 0
|
|
8831
|
+
? "cleared/empty"
|
|
8832
|
+
: childNodes.length === 1
|
|
8833
|
+
? getElementSignature(childNodes[0])
|
|
8834
|
+
: getElementSignature(slot);
|
|
8835
|
+
console.group(`UI Update: ${updateLabel} (reason: ${reason})`);
|
|
8836
|
+
}
|
|
8837
|
+
updateSlotChangeInfo(childNodes, reason);
|
|
8838
|
+
if (changeInfo.isStateChangeOnly) {
|
|
8839
|
+
} else {
|
|
8840
|
+
publishChange();
|
|
8841
|
+
previousSlotInfo = slotInfo;
|
|
8842
|
+
if (
|
|
8843
|
+
changeInfo.isInitialPopulationWithoutTransition ||
|
|
8844
|
+
changeInfo.becomesPopulated
|
|
8845
|
+
) {
|
|
8846
|
+
hasPopulatedOnce = true;
|
|
8847
|
+
}
|
|
8848
|
+
}
|
|
8849
|
+
} finally {
|
|
8850
|
+
isUpdating = false;
|
|
8851
|
+
if (hasSomeDebugLogs) {
|
|
8852
|
+
console.groupEnd();
|
|
8853
|
+
}
|
|
8854
|
+
}
|
|
8855
|
+
};
|
|
8856
|
+
|
|
8857
|
+
let hasPopulatedOnce = false; // track if we've already populated once (null → something)
|
|
8858
|
+
const updateSlotChangeInfo = (currentChildNodes, reason = "mutation") => {
|
|
8859
|
+
let childContentKey;
|
|
8860
|
+
let contentPhase;
|
|
8861
|
+
if (currentChildNodes.length === 0) {
|
|
8862
|
+
contentPhase = true; // empty treated as phase
|
|
8863
|
+
} else {
|
|
8864
|
+
for (const childNode of currentChildNodes) {
|
|
8865
|
+
if (childNode.nodeType === Node.TEXT_NODE) {
|
|
8866
|
+
continue;
|
|
8867
|
+
}
|
|
8868
|
+
if (childNode.hasAttribute("data-content-phase")) {
|
|
8869
|
+
const contentPhaseAttr =
|
|
8870
|
+
childNode.getAttribute("data-content-phase");
|
|
8871
|
+
contentPhase = contentPhaseAttr || true;
|
|
8872
|
+
}
|
|
8873
|
+
if (childNode.hasAttribute("data-content-key")) {
|
|
8874
|
+
childContentKey = childNode.getAttribute("data-content-key");
|
|
8875
|
+
}
|
|
8876
|
+
}
|
|
8877
|
+
}
|
|
8878
|
+
const slotContentKey = slot.getAttribute("data-content-key");
|
|
8879
|
+
if (childContentKey && slotContentKey) {
|
|
8880
|
+
console.warn(
|
|
8881
|
+
`Slot and slot child both have a [data-content-key]. Slot is ${slotContentKey} and child is ${childContentKey}, using the child.`,
|
|
8882
|
+
);
|
|
8883
|
+
}
|
|
8884
|
+
const contentKey = childContentKey || slotContentKey || undefined;
|
|
8885
|
+
slotInfo = createSlotInfo(currentChildNodes, {
|
|
8886
|
+
contentKey,
|
|
8887
|
+
contentPhase,
|
|
8888
|
+
});
|
|
8889
|
+
|
|
8890
|
+
const hadChild = previousSlotInfo.hasChild;
|
|
8891
|
+
const hasChild = currentChildNodes.length > 0;
|
|
8892
|
+
const becomesEmpty = hadChild && !hasChild;
|
|
8893
|
+
const becomesPopulated = !hadChild && hasChild;
|
|
8894
|
+
const isInitialPopulationWithoutTransition =
|
|
8895
|
+
becomesPopulated && !hasPopulatedOnce && !initialTransitionEnabled;
|
|
8896
|
+
const shouldDoContentTransition =
|
|
8897
|
+
contentKey &&
|
|
8898
|
+
previousSlotInfo.contentKey &&
|
|
8899
|
+
contentKey !== previousSlotInfo.contentKey;
|
|
8900
|
+
const previousIsContentPhase = !hadChild || previousSlotInfo.contentPhase;
|
|
8901
|
+
const currentIsContentPhase = !hasChild || contentPhase;
|
|
8902
|
+
const shouldDoPhaseTransition =
|
|
8903
|
+
!shouldDoContentTransition &&
|
|
8904
|
+
(becomesPopulated ||
|
|
8905
|
+
becomesEmpty ||
|
|
8906
|
+
(hadChild &&
|
|
8907
|
+
hasChild &&
|
|
8908
|
+
(previousIsContentPhase !== currentIsContentPhase ||
|
|
8909
|
+
(previousIsContentPhase && currentIsContentPhase))));
|
|
8910
|
+
const contentChange = hadChild && hasChild && shouldDoContentTransition;
|
|
8911
|
+
const phaseChange = hadChild && hasChild && shouldDoPhaseTransition;
|
|
8912
|
+
const isTransitionLess =
|
|
8913
|
+
!shouldDoContentTransition &&
|
|
8914
|
+
!shouldDoPhaseTransition &&
|
|
8915
|
+
!becomesPopulated &&
|
|
8916
|
+
!becomesEmpty;
|
|
8917
|
+
const shouldDoContentTransitionIncludingPopulation =
|
|
8918
|
+
shouldDoContentTransition ||
|
|
8919
|
+
(becomesPopulated && !shouldDoPhaseTransition);
|
|
8920
|
+
// nothing to transition if no previous and no current child
|
|
8921
|
+
// (Either it's the initial call or just content-key changes but there is no child yet)
|
|
8922
|
+
const isStateChangeOnly = !hadChild && !hasChild;
|
|
8923
|
+
if (isStateChangeOnly) {
|
|
8924
|
+
const prevKey = previousSlotInfo.contentKey;
|
|
8925
|
+
const keyIsTheSame = prevKey === contentKey;
|
|
8926
|
+
if (keyIsTheSame) {
|
|
8927
|
+
debug(
|
|
8928
|
+
"detection",
|
|
8929
|
+
`Childless change: no changes found -> do nothing and skip transitions`,
|
|
8930
|
+
);
|
|
8931
|
+
} else if (!prevKey && contentKey) {
|
|
8932
|
+
debug(
|
|
8933
|
+
"detection",
|
|
8934
|
+
`Childless change: ${contentKey} added -> registering it and skip transitions`,
|
|
8935
|
+
);
|
|
8936
|
+
} else if (prevKey && !contentKey) {
|
|
8937
|
+
debug(
|
|
8938
|
+
"detection",
|
|
8939
|
+
`Childless change: ${contentKey} removed -> registering it and skip transitions`,
|
|
8940
|
+
);
|
|
8941
|
+
} else {
|
|
8942
|
+
debug(
|
|
8943
|
+
"detection",
|
|
8944
|
+
`Childless change: content key updated from ${prevKey} to ${contentKey} -> registering it and skip transitions`,
|
|
8945
|
+
);
|
|
8946
|
+
}
|
|
8947
|
+
} else if (isInitialPopulationWithoutTransition) {
|
|
8948
|
+
debug(
|
|
8949
|
+
"detection",
|
|
8950
|
+
"Initial population detected -> skipping transitions (opt-in with [data-initial-transition])",
|
|
8951
|
+
);
|
|
8952
|
+
} else if (previousSlotInfo.contentKey !== slotInfo.contentKey) {
|
|
8953
|
+
let contentKeysSentence = `Content key: ${previousSlotInfo.contentKeyFormatted} → ${slotInfo.contentKeyFormatted}`;
|
|
8954
|
+
debug("detection", contentKeysSentence);
|
|
8955
|
+
} else if (previousSlotInfo.contentPhase !== slotInfo.contentPhase) {
|
|
8956
|
+
let contentPhasesSentence =
|
|
8957
|
+
slotInfo.contentPhase && previousSlotInfo.contentPhase
|
|
8958
|
+
? `Content phase: ${previousSlotInfo.contentPhase} → ${slotInfo.contentPhase}`
|
|
8959
|
+
: previousSlotInfo.contentPhase
|
|
8960
|
+
? `becomes content (content phase becomes undefined)`
|
|
8961
|
+
: `content phase becomes ${slotInfo.contentPhase}`;
|
|
8962
|
+
debug("detection", contentPhasesSentence);
|
|
8963
|
+
}
|
|
8964
|
+
|
|
8965
|
+
changeInfo = {
|
|
8966
|
+
reason,
|
|
8967
|
+
previousSlotInfo,
|
|
8968
|
+
becomesEmpty,
|
|
8969
|
+
becomesPopulated,
|
|
8970
|
+
isInitialPopulationWithoutTransition,
|
|
8971
|
+
shouldDoContentTransition,
|
|
8972
|
+
shouldDoPhaseTransition,
|
|
8973
|
+
contentChange,
|
|
8974
|
+
phaseChange,
|
|
8975
|
+
isTransitionLess,
|
|
8976
|
+
shouldDoContentTransitionIncludingPopulation,
|
|
8977
|
+
isStateChangeOnly,
|
|
8978
|
+
};
|
|
8979
|
+
};
|
|
8980
|
+
}
|
|
8981
|
+
|
|
8982
|
+
let onContentTransitionComplete;
|
|
8983
|
+
let hasSizeTransitions = container.hasAttribute("data-size-transition");
|
|
8984
|
+
{
|
|
8985
|
+
let naturalContentWidth = 0; // Natural size of actual content (not loading/error states)
|
|
8986
|
+
let naturalContentHeight = 0;
|
|
8987
|
+
let constrainedWidth = 0; // Current constrained dimensions (what outer wrapper is set to)
|
|
8988
|
+
let constrainedHeight = 0;
|
|
8989
|
+
let sizeTransition = null;
|
|
8990
|
+
|
|
8991
|
+
let pauseResizeObserver;
|
|
8992
|
+
{
|
|
8993
|
+
let resizeObserver = null;
|
|
8994
|
+
let isWithinResizeObserverTick = false;
|
|
8995
|
+
const pauseReasonSet = new Set();
|
|
8996
|
+
let state = "disconnected"; // "disconnected" | "paused" | "observing"
|
|
8997
|
+
let pendingResizeCount = 0;
|
|
8998
|
+
let resumeAnimationFrame;
|
|
8999
|
+
|
|
9000
|
+
pauseResizeObserver = (reason = "pause_requested") => {
|
|
9001
|
+
cancelAnimationFrame(resumeAnimationFrame);
|
|
9002
|
+
pauseReasonSet.add(reason);
|
|
9003
|
+
if (isWithinResizeObserverTick) {
|
|
9004
|
+
if (resizeObserver) {
|
|
9005
|
+
debug("size", `[resize observer] stop while "${reason}"`);
|
|
9006
|
+
stopResizeObserver();
|
|
9007
|
+
}
|
|
9008
|
+
} else {
|
|
9009
|
+
debug("size", `[resize observer] pause while "${reason}"`);
|
|
9010
|
+
// we keep the resize observer alive because we are not in a resize tick
|
|
9011
|
+
state = "paused";
|
|
9012
|
+
}
|
|
9013
|
+
const resume = () => {
|
|
9014
|
+
pauseReasonSet.delete(reason);
|
|
9015
|
+
if (pauseReasonSet.size > 0) {
|
|
9016
|
+
return;
|
|
9017
|
+
}
|
|
9018
|
+
resumeAnimationFrame = requestAnimationFrame(() => {
|
|
9019
|
+
debug("size", `[resize observer] resume after "${reason}"`);
|
|
9020
|
+
if (pendingResizeCount) {
|
|
9021
|
+
debug(
|
|
9022
|
+
"size",
|
|
9023
|
+
`[resize observer] was called while paused -> syncContentDimensions()`,
|
|
9024
|
+
);
|
|
9025
|
+
pendingResizeCount = 0;
|
|
9026
|
+
syncContentDimensions();
|
|
9027
|
+
state = "observing";
|
|
9028
|
+
}
|
|
9029
|
+
if (state === "disconnected") {
|
|
9030
|
+
debug(
|
|
9031
|
+
"size",
|
|
9032
|
+
`[resize observer] was disconnected -> reconnect it`,
|
|
9033
|
+
);
|
|
9034
|
+
startResizeObserver();
|
|
9035
|
+
}
|
|
9036
|
+
});
|
|
9037
|
+
};
|
|
9038
|
+
return resume;
|
|
9039
|
+
};
|
|
9040
|
+
const stopResizeObserver = () => {
|
|
9041
|
+
state = "disconnected";
|
|
9042
|
+
if (!resizeObserver) return;
|
|
9043
|
+
resizeObserver.disconnect();
|
|
9044
|
+
resizeObserver = null;
|
|
9045
|
+
};
|
|
9046
|
+
const startResizeObserver = () => {
|
|
9047
|
+
state = "observing";
|
|
9048
|
+
resizeObserver = new ResizeObserver(() => {
|
|
9049
|
+
if (!hasSizeTransitions) {
|
|
9050
|
+
return;
|
|
9051
|
+
}
|
|
9052
|
+
if (!slotInfo.hasChild || slotInfo.isContentPhase) {
|
|
9053
|
+
debug(
|
|
9054
|
+
"size",
|
|
9055
|
+
"[resize observer] size change ignored (no child or content-phase)",
|
|
9056
|
+
);
|
|
9057
|
+
return;
|
|
9058
|
+
}
|
|
9059
|
+
if (state === "paused") {
|
|
9060
|
+
pendingResizeCount++;
|
|
9061
|
+
const pauseReason =
|
|
9062
|
+
Array.from(pauseReasonSet).join(", ") ||
|
|
9063
|
+
"wait next frame to resume";
|
|
9064
|
+
debug(
|
|
9065
|
+
"size",
|
|
9066
|
+
`[resize observer] size change ignore (${pauseReason})`,
|
|
9067
|
+
);
|
|
9068
|
+
return;
|
|
9069
|
+
}
|
|
9070
|
+
if (localDebug.size) {
|
|
9071
|
+
console.group("[resize observer] size change detected");
|
|
9072
|
+
}
|
|
9073
|
+
isWithinResizeObserverTick = true;
|
|
9074
|
+
syncContentDimensions();
|
|
9075
|
+
if (localDebug.size) {
|
|
9076
|
+
console.groupEnd();
|
|
9077
|
+
}
|
|
9078
|
+
requestAnimationFrame(() => {
|
|
9079
|
+
isWithinResizeObserverTick = false;
|
|
9080
|
+
});
|
|
9081
|
+
});
|
|
9082
|
+
resizeObserver.observe(slot);
|
|
9083
|
+
};
|
|
9084
|
+
startResizeObserver();
|
|
9085
|
+
addTeardown(() => {
|
|
9086
|
+
stopResizeObserver();
|
|
9087
|
+
});
|
|
9088
|
+
}
|
|
9089
|
+
|
|
9090
|
+
const measureSlotSize = () => {
|
|
9091
|
+
return [
|
|
9092
|
+
getWidthWithoutTransition(slot),
|
|
9093
|
+
getHeightWithoutTransition(slot),
|
|
9094
|
+
];
|
|
9095
|
+
};
|
|
9096
|
+
const syncContentDimensions = () => {
|
|
9097
|
+
// check content dimensions to see if they changed and sync them
|
|
9098
|
+
const [currentWidth, currentHeight] = measureSlotSize();
|
|
9099
|
+
if (!slotInfo.isContentPhase) {
|
|
9100
|
+
updateNaturalContentSize(currentWidth, currentHeight);
|
|
9101
|
+
}
|
|
9102
|
+
if (sizeTransition) {
|
|
9103
|
+
updateToSize(currentWidth, currentHeight);
|
|
9104
|
+
} else {
|
|
9105
|
+
constrainedWidth = currentWidth;
|
|
9106
|
+
constrainedHeight = currentHeight;
|
|
9107
|
+
}
|
|
9108
|
+
};
|
|
9109
|
+
const applySizeConstraintsUntil = (width, height, reason) => {
|
|
9110
|
+
// we want to pause either because we have a diff and don't want to trigger the resize observer
|
|
9111
|
+
// or if we have no diff because we're about to do something that would trigger it (transition)
|
|
9112
|
+
const resumeResizeObserver = pauseResizeObserver(reason);
|
|
9113
|
+
debug("size", `Applying size constraints (${reason})`, {
|
|
9114
|
+
width: `${constrainedWidth} → ${width}`,
|
|
9115
|
+
height: `${constrainedHeight} → ${height}`,
|
|
9116
|
+
});
|
|
9117
|
+
outerWrapper.style.width = `${width}px`;
|
|
9118
|
+
outerWrapper.style.height = `${height}px`;
|
|
9119
|
+
constrainedWidth = width;
|
|
9120
|
+
constrainedHeight = height;
|
|
9121
|
+
// force content overlay to take the right size
|
|
9122
|
+
// (this way the content clone is not distorted by the new content size)
|
|
9123
|
+
contentOverlay.style.width = `${width}px`;
|
|
9124
|
+
contentOverlay.style.height = `${height}px`;
|
|
9125
|
+
const release = (reason) => {
|
|
9126
|
+
releaseSizeConstraints(reason);
|
|
9127
|
+
resumeResizeObserver(reason);
|
|
9128
|
+
};
|
|
9129
|
+
release.releaseResizeObserver = () => {
|
|
9130
|
+
resumeResizeObserver(reason);
|
|
9131
|
+
};
|
|
9132
|
+
return release;
|
|
9133
|
+
};
|
|
9134
|
+
const applySizeConstraints = (width, height, reason) => {
|
|
9135
|
+
applySizeConstraintsUntil(width, height, reason);
|
|
9136
|
+
};
|
|
9137
|
+
const releaseSizeConstraints = (reason) => {
|
|
9138
|
+
if (slotInfo.isContentPhase) {
|
|
9139
|
+
return;
|
|
9140
|
+
}
|
|
9141
|
+
debug("size", `Releasing constraints (${reason})`);
|
|
9142
|
+
const [beforeWidth, beforeHeight] = measureSlotSize();
|
|
9143
|
+
outerWrapper.style.width = "";
|
|
9144
|
+
outerWrapper.style.height = "";
|
|
9145
|
+
const [afterWidth, afterHeight] = measureSlotSize();
|
|
9146
|
+
debug("size", "Size after release:", {
|
|
9147
|
+
width: `${beforeWidth} → ${afterWidth}`,
|
|
9148
|
+
height: `${beforeHeight} → ${afterHeight}`,
|
|
9149
|
+
});
|
|
9150
|
+
updateNaturalContentSize(afterWidth, afterHeight);
|
|
9151
|
+
constrainedWidth = afterWidth;
|
|
9152
|
+
constrainedHeight = afterHeight;
|
|
9153
|
+
contentOverlay.style.width = ``;
|
|
9154
|
+
contentOverlay.style.height = ``;
|
|
9155
|
+
};
|
|
9156
|
+
const updateToSize = (targetWidth, targetHeight) => {
|
|
9157
|
+
if (
|
|
9158
|
+
constrainedWidth === targetWidth &&
|
|
9159
|
+
constrainedHeight === targetHeight
|
|
9160
|
+
) {
|
|
9161
|
+
return;
|
|
9162
|
+
}
|
|
9163
|
+
if (!hasSizeTransitions) {
|
|
9164
|
+
applySizeConstraints(
|
|
9165
|
+
targetWidth,
|
|
9166
|
+
targetHeight,
|
|
9167
|
+
"size update without transition",
|
|
9168
|
+
);
|
|
9169
|
+
return;
|
|
9170
|
+
}
|
|
9171
|
+
const widthDiff = Math.abs(targetWidth - constrainedWidth);
|
|
9172
|
+
const heightDiff = Math.abs(targetHeight - constrainedHeight);
|
|
9173
|
+
if (widthDiff <= SIZE_DIFF_EPSILON && heightDiff <= SIZE_DIFF_EPSILON) {
|
|
9174
|
+
applySizeConstraints(
|
|
9175
|
+
targetWidth,
|
|
9176
|
+
targetHeight,
|
|
9177
|
+
"skip transition (negligible diff)",
|
|
9178
|
+
);
|
|
9179
|
+
return;
|
|
9180
|
+
}
|
|
9181
|
+
const duration = parseInt(
|
|
9182
|
+
container.getAttribute("data-size-transition-duration") ||
|
|
9183
|
+
SIZE_TRANSITION_DURATION,
|
|
9184
|
+
);
|
|
9185
|
+
debug("size", "prepare transition:", {
|
|
9186
|
+
width: `${constrainedWidth} → ${targetWidth}`,
|
|
9187
|
+
height: `${constrainedHeight} → ${targetHeight}`,
|
|
9188
|
+
duration,
|
|
9189
|
+
});
|
|
9190
|
+
|
|
9191
|
+
const transitions = [];
|
|
9192
|
+
if (widthDiff === 0) ; else if (widthDiff <= SIZE_DIFF_EPSILON) {
|
|
9193
|
+
debug(
|
|
9194
|
+
"size",
|
|
9195
|
+
`Skip width transition (negligible diff ${widthDiff.toFixed(4)}px)`,
|
|
9196
|
+
);
|
|
9197
|
+
} else {
|
|
9198
|
+
transitions.push(
|
|
9199
|
+
createWidthTransition(outerWrapper, targetWidth, {
|
|
9200
|
+
setup: () =>
|
|
9201
|
+
notifyTransition(outerWrapper, {
|
|
9202
|
+
modelId: "ui_transition_width",
|
|
9203
|
+
canOverflow: true,
|
|
9204
|
+
id:
|
|
9205
|
+
targetWidth > constrainedWidth
|
|
9206
|
+
? "grow_to_new_width"
|
|
9207
|
+
: "shrink_to_new_width",
|
|
9208
|
+
}),
|
|
9209
|
+
duration,
|
|
9210
|
+
onUpdate: ({ value }) => {
|
|
9211
|
+
constrainedWidth = value;
|
|
9212
|
+
},
|
|
9213
|
+
}),
|
|
9214
|
+
);
|
|
9215
|
+
}
|
|
9216
|
+
if (heightDiff === 0) ; else if (heightDiff <= SIZE_DIFF_EPSILON) {
|
|
9217
|
+
debug(
|
|
9218
|
+
"size",
|
|
9219
|
+
`Skip height transition (negligible diff ${heightDiff.toFixed(4)}px)`,
|
|
9220
|
+
);
|
|
9221
|
+
} else {
|
|
9222
|
+
transitions.push(
|
|
9223
|
+
createHeightTransition(outerWrapper, targetHeight, {
|
|
9224
|
+
setup: () =>
|
|
9225
|
+
notifyTransition(outerWrapper, {
|
|
9226
|
+
modelId: "ui_transition_height",
|
|
9227
|
+
canOverflow: true,
|
|
9228
|
+
id:
|
|
9229
|
+
targetHeight > constrainedHeight
|
|
9230
|
+
? "grow_to_new_height"
|
|
9231
|
+
: "shrink_to_new_height",
|
|
9232
|
+
}),
|
|
9233
|
+
duration,
|
|
9234
|
+
onUpdate: ({ value }) => {
|
|
9235
|
+
constrainedHeight = value;
|
|
9236
|
+
},
|
|
9237
|
+
}),
|
|
9238
|
+
);
|
|
9239
|
+
}
|
|
9240
|
+
const release = applySizeConstraintsUntil(
|
|
9241
|
+
constrainedWidth,
|
|
9242
|
+
constrainedHeight,
|
|
9243
|
+
"size transitioning",
|
|
9244
|
+
);
|
|
9245
|
+
sizeTransition = transitionController.animate(transitions, {
|
|
9246
|
+
onCancel: () => {
|
|
9247
|
+
release.releaseResizeObserver("size transition cancelled");
|
|
9248
|
+
},
|
|
9249
|
+
onFinish: () => {
|
|
9250
|
+
release("size transition finished");
|
|
9251
|
+
},
|
|
9252
|
+
});
|
|
9253
|
+
sizeTransition.play();
|
|
9254
|
+
};
|
|
9255
|
+
const updateNaturalContentSize = (width, height) => {
|
|
9256
|
+
if (width === naturalContentWidth && height === naturalContentHeight) {
|
|
9257
|
+
return;
|
|
9258
|
+
}
|
|
9259
|
+
debug("size", "Updating natural content size:", {
|
|
9260
|
+
width: `${naturalContentWidth} → ${width}`,
|
|
9261
|
+
height: `${naturalContentHeight} → ${height}`,
|
|
9262
|
+
});
|
|
9263
|
+
naturalContentWidth = width;
|
|
9264
|
+
naturalContentHeight = height;
|
|
9265
|
+
};
|
|
9266
|
+
|
|
9267
|
+
// Initialize with current size
|
|
9268
|
+
[constrainedWidth, constrainedHeight] = measureSlotSize();
|
|
9269
|
+
|
|
9270
|
+
const updateSizeTransition = () => {
|
|
9271
|
+
hasSizeTransitions = container.hasAttribute("data-size-transition");
|
|
9272
|
+
const { isContentPhase } = slotInfo;
|
|
9273
|
+
const { isInitialPopulationWithoutTransition } = changeInfo;
|
|
9274
|
+
debug(
|
|
9275
|
+
"size",
|
|
9276
|
+
`updateSizeTransition(), current constrained size: ${constrainedWidth.toFixed(2)}x${constrainedHeight.toFixed(2)}`,
|
|
9277
|
+
);
|
|
9278
|
+
sizeTransition?.cancel();
|
|
9279
|
+
|
|
9280
|
+
// Initial population skip (first null → something): no content or size animations
|
|
9281
|
+
if (isInitialPopulationWithoutTransition) {
|
|
9282
|
+
const [newWidth, newHeight] = measureSlotSize();
|
|
9283
|
+
debug("size", `content size measured to: ${newWidth}x${newHeight}`);
|
|
9284
|
+
if (isContentPhase) {
|
|
9285
|
+
applySizeConstraints(
|
|
9286
|
+
newWidth,
|
|
9287
|
+
newHeight,
|
|
9288
|
+
"content phase initial population",
|
|
9289
|
+
);
|
|
9290
|
+
} else {
|
|
9291
|
+
updateNaturalContentSize(newWidth, newHeight);
|
|
9292
|
+
releaseSizeConstraints("initial population - skip transitions");
|
|
9293
|
+
}
|
|
9294
|
+
return;
|
|
9295
|
+
}
|
|
9296
|
+
|
|
9297
|
+
let targetWidth;
|
|
9298
|
+
let targetHeight;
|
|
9299
|
+
if (isContentPhase) {
|
|
9300
|
+
const shouldUseNewDimensions =
|
|
9301
|
+
naturalContentWidth === 0 && naturalContentHeight === 0;
|
|
9302
|
+
if (shouldUseNewDimensions) {
|
|
9303
|
+
// we don't have any natural content dimensions yet, we can use the content phase dimensions for now
|
|
9304
|
+
[targetWidth, targetHeight] = measureSlotSize();
|
|
9305
|
+
debug(
|
|
9306
|
+
"size",
|
|
9307
|
+
`content phase dimension measured to: ${targetWidth}x${targetHeight}`,
|
|
9308
|
+
);
|
|
9309
|
+
} else {
|
|
9310
|
+
// we don't care about the content phase dimension.
|
|
9311
|
+
// the content dimensions prevails
|
|
9312
|
+
targetWidth = naturalContentWidth;
|
|
9313
|
+
targetHeight = naturalContentHeight;
|
|
9314
|
+
debug(
|
|
9315
|
+
"size",
|
|
9316
|
+
`content phase using natural content size: ${naturalContentWidth}x${naturalContentHeight}`,
|
|
9317
|
+
);
|
|
9318
|
+
}
|
|
9319
|
+
} else {
|
|
9320
|
+
outerWrapper.style.width = "";
|
|
9321
|
+
outerWrapper.style.height = "";
|
|
9322
|
+
const [slotNaturalWidth, slotNaturalHeight] = measureSlotSize();
|
|
9323
|
+
outerWrapper.style.width = `${constrainedWidth}px`;
|
|
9324
|
+
outerWrapper.style.height = `${constrainedHeight}px`;
|
|
9325
|
+
updateNaturalContentSize(slotNaturalWidth, slotNaturalHeight);
|
|
9326
|
+
targetWidth = slotNaturalWidth;
|
|
9327
|
+
targetHeight = slotNaturalHeight;
|
|
9328
|
+
debug(
|
|
9329
|
+
"size",
|
|
9330
|
+
`content size measured to: ${slotNaturalWidth}x${slotNaturalHeight}`,
|
|
9331
|
+
);
|
|
9332
|
+
}
|
|
9333
|
+
|
|
9334
|
+
// If size transitions are disabled hold the previous size to avoid cropping during the content transition.
|
|
9335
|
+
if (!hasSizeTransitions) {
|
|
9336
|
+
debug(
|
|
9337
|
+
"size",
|
|
9338
|
+
`Holding previous size during content transition: ${constrainedWidth}x${constrainedHeight}`,
|
|
9339
|
+
);
|
|
9340
|
+
applySizeConstraints(
|
|
9341
|
+
constrainedWidth,
|
|
9342
|
+
constrainedHeight,
|
|
9343
|
+
"hold size for content transition",
|
|
9344
|
+
);
|
|
9345
|
+
sizeTransition?.cancel();
|
|
9346
|
+
onContentTransitionComplete = () => {
|
|
9347
|
+
onContentTransitionComplete = null;
|
|
9348
|
+
releaseSizeConstraints(
|
|
9349
|
+
"content transition completed - release size hold",
|
|
9350
|
+
);
|
|
9351
|
+
};
|
|
9352
|
+
return;
|
|
9353
|
+
}
|
|
9354
|
+
|
|
9355
|
+
if (
|
|
9356
|
+
targetWidth === constrainedWidth &&
|
|
9357
|
+
targetHeight === constrainedHeight
|
|
9358
|
+
) {
|
|
9359
|
+
sizeTransition?.cancel();
|
|
9360
|
+
debug("size", "No size change required");
|
|
9361
|
+
releaseSizeConstraints("no size change needed");
|
|
9362
|
+
return;
|
|
9363
|
+
}
|
|
9364
|
+
debug("size", "Size change needed:", {
|
|
9365
|
+
width: `${constrainedWidth} → ${targetWidth}`,
|
|
9366
|
+
height: `${constrainedHeight} → ${targetHeight}`,
|
|
9367
|
+
});
|
|
9368
|
+
updateToSize(targetWidth, targetHeight);
|
|
9369
|
+
};
|
|
9370
|
+
subscribeChange(updateSizeTransition);
|
|
9371
|
+
|
|
9372
|
+
addPauseCallback(() => {
|
|
9373
|
+
sizeTransition?.pause();
|
|
9374
|
+
});
|
|
9375
|
+
addResumeCallback(() => {
|
|
9376
|
+
sizeTransition?.play();
|
|
9377
|
+
});
|
|
9378
|
+
addTeardown(() => {
|
|
9379
|
+
sizeTransition?.cancel();
|
|
9380
|
+
});
|
|
9381
|
+
}
|
|
9382
|
+
|
|
9383
|
+
{
|
|
9384
|
+
let activeContentTransition = null;
|
|
9385
|
+
let activeContentTransitionType = null;
|
|
9386
|
+
let activePhaseTransition = null;
|
|
9387
|
+
let activePhaseTransitionType = null;
|
|
9388
|
+
|
|
9389
|
+
const updateContentTransitions = () => {
|
|
9390
|
+
const { childNodes, contentName: fromContentName } = slotInfo;
|
|
9391
|
+
const {
|
|
9392
|
+
previousSlotInfo,
|
|
9393
|
+
becomesEmpty,
|
|
9394
|
+
becomesPopulated,
|
|
9395
|
+
shouldDoContentTransition,
|
|
9396
|
+
shouldDoPhaseTransition,
|
|
9397
|
+
contentChange,
|
|
9398
|
+
phaseChange,
|
|
9399
|
+
isTransitionLess,
|
|
9400
|
+
shouldDoContentTransitionIncludingPopulation,
|
|
9401
|
+
} = changeInfo;
|
|
9402
|
+
const { hasChild: hadChild, contentName: toContentName } =
|
|
9403
|
+
previousSlotInfo;
|
|
9404
|
+
|
|
9405
|
+
const preserveOnlyContentTransition =
|
|
9406
|
+
isTransitionLess && activeContentTransition !== null;
|
|
9407
|
+
const previousChildNodes = previousSlotInfo.childNodes;
|
|
9408
|
+
|
|
9409
|
+
// Determine transition scenarios (hadChild/hasChild already computed above for logging)
|
|
9410
|
+
|
|
9411
|
+
/**
|
|
9412
|
+
* Content Phase Logic: Why empty slots are treated as content phases
|
|
9413
|
+
*
|
|
9414
|
+
* When there is no child element (React component returns null), it is considered
|
|
9415
|
+
* that the component does not render anything temporarily. This might be because:
|
|
9416
|
+
* - The component is loading but does not have a loading state
|
|
9417
|
+
* - The component has an error but does not have an error state
|
|
9418
|
+
* - The component is conceptually unloaded (underlying content was deleted/is not accessible)
|
|
9419
|
+
*
|
|
9420
|
+
* This represents a phase of the given content: having nothing to display.
|
|
9421
|
+
*
|
|
9422
|
+
* We support transitions between different contents via the ability to set
|
|
9423
|
+
* [data-content-key] on the ".ui_transition_slot". This is also useful when you want
|
|
9424
|
+
* all children of a React component to inherit the same data-content-key without
|
|
9425
|
+
* explicitly setting the attribute on each child element.
|
|
9426
|
+
*/
|
|
9427
|
+
|
|
9428
|
+
// Content key change when either slot or child has data-content-key and it changed
|
|
9429
|
+
// Content key change detection already computed in getSlotChangeInfo.
|
|
9430
|
+
// We rely on the shouldDoContentTransition value coming from changeInfo.
|
|
9431
|
+
|
|
9432
|
+
const decisions = [];
|
|
9433
|
+
if (shouldDoContentTransition) {
|
|
9434
|
+
decisions.push("CONTENT TRANSITION");
|
|
9435
|
+
}
|
|
9436
|
+
if (shouldDoPhaseTransition) {
|
|
9437
|
+
decisions.push("PHASE TRANSITION");
|
|
9438
|
+
}
|
|
9439
|
+
if (preserveOnlyContentTransition) {
|
|
9440
|
+
decisions.push("PRESERVE CONTENT TRANSITION");
|
|
9441
|
+
}
|
|
9442
|
+
if (decisions.length === 0) {
|
|
9443
|
+
decisions.push("NO TRANSITION");
|
|
9444
|
+
}
|
|
9445
|
+
|
|
9446
|
+
debug("content", `Decision: ${decisions.join(" + ")}`);
|
|
9447
|
+
if (preserveOnlyContentTransition) {
|
|
9448
|
+
const progress = (activeContentTransition.progress * 100).toFixed(1);
|
|
9449
|
+
debug(
|
|
9450
|
+
"content",
|
|
9451
|
+
`Preserving existing content transition (progress ${progress}%)`,
|
|
9452
|
+
);
|
|
9453
|
+
}
|
|
9454
|
+
|
|
9455
|
+
if (changeInfo.isInitialPopulationWithoutTransition) {
|
|
9456
|
+
return;
|
|
9457
|
+
}
|
|
9458
|
+
|
|
9459
|
+
// Handle content transitions (slide-left, cross-fade for content key changes)
|
|
9460
|
+
if (
|
|
9461
|
+
decisions.length === 1 &&
|
|
9462
|
+
decisions[0] === "NO TRANSITION" &&
|
|
9463
|
+
activeContentTransition === null &&
|
|
9464
|
+
activePhaseTransition === null
|
|
9465
|
+
) {
|
|
9466
|
+
// Skip creating any new transitions entirely
|
|
9467
|
+
onContentTransitionComplete?.();
|
|
9468
|
+
} else if (
|
|
9469
|
+
shouldDoContentTransitionIncludingPopulation &&
|
|
9470
|
+
!preserveOnlyContentTransition
|
|
9471
|
+
) {
|
|
9472
|
+
const animationProgress = activeContentTransition?.progress || 0;
|
|
9473
|
+
if (animationProgress > 0) {
|
|
9474
|
+
debug(
|
|
9475
|
+
"content",
|
|
9476
|
+
`Preserving content transition progress: ${(animationProgress * 100).toFixed(1)}%`,
|
|
9477
|
+
);
|
|
9478
|
+
}
|
|
9479
|
+
|
|
9480
|
+
const newTransitionType =
|
|
9481
|
+
container.getAttribute("data-content-transition") ||
|
|
9482
|
+
CONTENT_TRANSITION;
|
|
9483
|
+
const canContinueSmoothly =
|
|
9484
|
+
activeContentTransitionType === newTransitionType &&
|
|
9485
|
+
activeContentTransition;
|
|
9486
|
+
if (canContinueSmoothly) {
|
|
9487
|
+
debug(
|
|
9488
|
+
"content",
|
|
9489
|
+
"Continuing with same content transition type (restarting due to actual change)",
|
|
9490
|
+
);
|
|
9491
|
+
activeContentTransition.cancel();
|
|
9492
|
+
} else if (
|
|
9493
|
+
activeContentTransition &&
|
|
9494
|
+
activeContentTransitionType !== newTransitionType
|
|
9495
|
+
) {
|
|
9496
|
+
debug(
|
|
9497
|
+
"content",
|
|
9498
|
+
"Different content transition type, keeping both",
|
|
9499
|
+
`${activeContentTransitionType} → ${newTransitionType}`,
|
|
9500
|
+
);
|
|
9501
|
+
} else if (activeContentTransition) {
|
|
9502
|
+
debug("content", "Cancelling current content transition");
|
|
9503
|
+
activeContentTransition.cancel();
|
|
9504
|
+
}
|
|
9505
|
+
|
|
9506
|
+
const needsOldChildNodesClone =
|
|
9507
|
+
(contentChange || becomesEmpty) && hadChild;
|
|
9508
|
+
const duration = parseInt(
|
|
9509
|
+
container.getAttribute("data-content-transition-duration") ||
|
|
9510
|
+
CONTENT_TRANSITION_DURATION,
|
|
9511
|
+
);
|
|
9512
|
+
const type =
|
|
9513
|
+
container.getAttribute("data-content-transition") ||
|
|
9514
|
+
CONTENT_TRANSITION;
|
|
9515
|
+
|
|
9516
|
+
const setupContentTransition = () =>
|
|
9517
|
+
setupTransition({
|
|
9518
|
+
isPhaseTransition: false,
|
|
9519
|
+
overlay: contentOverlay,
|
|
9520
|
+
needsOldChildNodesClone,
|
|
9521
|
+
previousChildNodes,
|
|
9522
|
+
childNodes,
|
|
9523
|
+
slotInfo,
|
|
9524
|
+
attributeToRemove: ["data-content-key"],
|
|
9525
|
+
});
|
|
9526
|
+
|
|
9527
|
+
activeContentTransition = applyTransition(
|
|
9528
|
+
transitionController,
|
|
9529
|
+
setupContentTransition,
|
|
9530
|
+
{
|
|
9531
|
+
duration,
|
|
9532
|
+
type,
|
|
9533
|
+
animationProgress,
|
|
9534
|
+
isPhaseTransition: false,
|
|
9535
|
+
previousSlotInfo,
|
|
9536
|
+
slotInfo,
|
|
9537
|
+
onComplete: () => {
|
|
9538
|
+
activeContentTransition = null;
|
|
9539
|
+
activeContentTransitionType = null;
|
|
9540
|
+
onContentTransitionComplete?.();
|
|
9541
|
+
},
|
|
9542
|
+
debug,
|
|
9543
|
+
},
|
|
9544
|
+
);
|
|
9545
|
+
|
|
9546
|
+
if (activeContentTransition) {
|
|
9547
|
+
activeContentTransition.play();
|
|
9548
|
+
}
|
|
9549
|
+
activeContentTransitionType = type;
|
|
9550
|
+
} else if (!shouldDoContentTransition && !preserveOnlyContentTransition) {
|
|
9551
|
+
// Clean up content overlay if no content transition needed and nothing to preserve
|
|
9552
|
+
contentOverlay.innerHTML = "";
|
|
9553
|
+
activeContentTransition = null;
|
|
9554
|
+
activeContentTransitionType = null;
|
|
9555
|
+
}
|
|
9556
|
+
|
|
9557
|
+
// Handle phase transitions (cross-fade for content phase changes)
|
|
9558
|
+
if (shouldDoPhaseTransition) {
|
|
9559
|
+
const phaseTransitionType =
|
|
9560
|
+
container.getAttribute("data-phase-transition") || PHASE_TRANSITION;
|
|
9561
|
+
const phaseAnimationProgress = activePhaseTransition?.progress || 0;
|
|
9562
|
+
if (phaseAnimationProgress > 0) {
|
|
9563
|
+
debug(
|
|
9564
|
+
"content",
|
|
9565
|
+
`Preserving phase transition progress: ${(phaseAnimationProgress * 100).toFixed(1)}%`,
|
|
9566
|
+
);
|
|
9567
|
+
}
|
|
9568
|
+
|
|
9569
|
+
const canContinueSmoothly =
|
|
9570
|
+
activePhaseTransitionType === phaseTransitionType &&
|
|
9571
|
+
activePhaseTransition;
|
|
9572
|
+
|
|
9573
|
+
if (canContinueSmoothly) {
|
|
9574
|
+
debug("content", "Continuing with same phase transition type");
|
|
9575
|
+
activePhaseTransition.cancel();
|
|
9576
|
+
} else if (
|
|
9577
|
+
activePhaseTransition &&
|
|
9578
|
+
activePhaseTransitionType !== phaseTransitionType
|
|
9579
|
+
) {
|
|
9580
|
+
debug(
|
|
9581
|
+
"content",
|
|
9582
|
+
"Different phase transition type, keeping both",
|
|
9583
|
+
`${activePhaseTransitionType} → ${phaseTransitionType}`,
|
|
9584
|
+
);
|
|
9585
|
+
} else if (activePhaseTransition) {
|
|
9586
|
+
debug("content", "Cancelling current phase transition");
|
|
9587
|
+
activePhaseTransition.cancel();
|
|
9588
|
+
}
|
|
9589
|
+
|
|
9590
|
+
const needsOldPhaseClone =
|
|
9591
|
+
(becomesEmpty || becomesPopulated || phaseChange) && hadChild;
|
|
9592
|
+
const phaseDuration = parseInt(
|
|
9593
|
+
container.getAttribute("data-phase-transition-duration") ||
|
|
9594
|
+
PHASE_TRANSITION_DURATION,
|
|
9595
|
+
);
|
|
9596
|
+
|
|
9597
|
+
const setupPhaseTransition = () =>
|
|
9598
|
+
setupTransition({
|
|
9599
|
+
isPhaseTransition: true,
|
|
9600
|
+
overlay: phaseOverlay,
|
|
9601
|
+
needsOldChildNodesClone: needsOldPhaseClone,
|
|
9602
|
+
previousChildNodes,
|
|
9603
|
+
childNodes,
|
|
9604
|
+
slotInfo,
|
|
9605
|
+
attributeToRemove: ["data-content-key", "data-content-phase"],
|
|
9606
|
+
});
|
|
9607
|
+
|
|
9608
|
+
debug(
|
|
9609
|
+
"content",
|
|
9610
|
+
`Starting transition: ${fromContentName} → ${toContentName}`,
|
|
9611
|
+
);
|
|
9612
|
+
|
|
9613
|
+
activePhaseTransition = applyTransition(
|
|
9614
|
+
transitionController,
|
|
9615
|
+
setupPhaseTransition,
|
|
9616
|
+
{
|
|
9617
|
+
duration: phaseDuration,
|
|
9618
|
+
type: phaseTransitionType,
|
|
9619
|
+
animationProgress: phaseAnimationProgress,
|
|
9620
|
+
isPhaseTransition: true,
|
|
9621
|
+
previousSlotInfo,
|
|
9622
|
+
slotInfo,
|
|
9623
|
+
onComplete: () => {
|
|
9624
|
+
activePhaseTransition = null;
|
|
9625
|
+
activePhaseTransitionType = null;
|
|
9626
|
+
onContentTransitionComplete?.();
|
|
9627
|
+
debug("content", "Phase transition complete");
|
|
9628
|
+
},
|
|
9629
|
+
debug,
|
|
9630
|
+
},
|
|
9631
|
+
);
|
|
9632
|
+
|
|
9633
|
+
if (activePhaseTransition) {
|
|
9634
|
+
activePhaseTransition.play();
|
|
9635
|
+
}
|
|
9636
|
+
activePhaseTransitionType = phaseTransitionType;
|
|
9637
|
+
}
|
|
9638
|
+
};
|
|
9639
|
+
subscribeChange(updateContentTransitions);
|
|
9640
|
+
|
|
9641
|
+
addPauseCallback(() => {
|
|
9642
|
+
activeContentTransition?.pause();
|
|
9643
|
+
activePhaseTransition?.pause();
|
|
9644
|
+
});
|
|
9645
|
+
addResumeCallback(() => {
|
|
9646
|
+
activeContentTransition?.play();
|
|
9647
|
+
activePhaseTransition?.play();
|
|
9648
|
+
});
|
|
9649
|
+
addTeardown(() => {
|
|
9650
|
+
activeContentTransition?.cancel();
|
|
9651
|
+
activePhaseTransition?.cancel();
|
|
9652
|
+
});
|
|
9653
|
+
}
|
|
9654
|
+
|
|
9655
|
+
{
|
|
9656
|
+
const transitionSet = new Set();
|
|
9657
|
+
const updateTransitionOverflowAttribute = () => {
|
|
9658
|
+
if (transitionSet.size > 0) {
|
|
9659
|
+
container.setAttribute("data-transition-running", "");
|
|
9660
|
+
} else {
|
|
9661
|
+
container.removeAttribute("data-transition-running");
|
|
9662
|
+
}
|
|
9663
|
+
};
|
|
9664
|
+
const onTransitionStart = (event) => {
|
|
9665
|
+
transitionSet.add(event.detail.id);
|
|
9666
|
+
updateTransitionOverflowAttribute();
|
|
9667
|
+
};
|
|
9668
|
+
const onTransitionEnd = (event) => {
|
|
9669
|
+
transitionSet.delete(event.detail.id);
|
|
9670
|
+
updateTransitionOverflowAttribute();
|
|
9671
|
+
};
|
|
9672
|
+
container.addEventListener("ui_transition_start", onTransitionStart);
|
|
9673
|
+
container.addEventListener("ui_transition_end", onTransitionEnd);
|
|
9674
|
+
addTeardown(() => {
|
|
9675
|
+
container.removeEventListener("ui_transition_start", onTransitionStart);
|
|
9676
|
+
container.removeEventListener("ui_transition_end", onTransitionEnd);
|
|
9677
|
+
});
|
|
9678
|
+
}
|
|
9679
|
+
|
|
9680
|
+
// Run once at init to process current slot content
|
|
9681
|
+
triggerChildSlotMutation("init");
|
|
9682
|
+
{
|
|
9683
|
+
const mutationObserver = new MutationObserver((mutations) => {
|
|
9684
|
+
const reasonParts = [];
|
|
9685
|
+
|
|
9686
|
+
for (const mutation of mutations) {
|
|
9687
|
+
if (mutation.type === "childList") {
|
|
9688
|
+
const added = mutation.addedNodes.length;
|
|
9689
|
+
const removed = mutation.removedNodes.length;
|
|
9690
|
+
if (added && removed) {
|
|
9691
|
+
reasonParts.push(`addedNodes(${added}) removedNodes(${removed})`);
|
|
9692
|
+
} else if (added) {
|
|
9693
|
+
reasonParts.push(`addedNodes(${added})`);
|
|
9694
|
+
} else {
|
|
9695
|
+
reasonParts.push(`removedNodes(${removed})`);
|
|
9696
|
+
}
|
|
9697
|
+
continue;
|
|
9698
|
+
}
|
|
9699
|
+
if (mutation.type === "attributes") {
|
|
9700
|
+
const { attributeName } = mutation;
|
|
9701
|
+
if (
|
|
9702
|
+
attributeName === "data-content-key" ||
|
|
9703
|
+
attributeName === "data-content-phase"
|
|
9704
|
+
) {
|
|
9705
|
+
reasonParts.push(`[${attributeName}] change`);
|
|
9706
|
+
}
|
|
9707
|
+
}
|
|
9708
|
+
}
|
|
9709
|
+
|
|
9710
|
+
if (reasonParts.length === 0) {
|
|
9711
|
+
return;
|
|
9712
|
+
}
|
|
9713
|
+
const reason = reasonParts.join("+");
|
|
9714
|
+
triggerChildSlotMutation(reason);
|
|
9715
|
+
});
|
|
9716
|
+
mutationObserver.observe(slot, {
|
|
9717
|
+
childList: true,
|
|
9718
|
+
attributes: true,
|
|
9719
|
+
attributeFilter: ["data-content-key", "data-content-phase"],
|
|
9720
|
+
characterData: false,
|
|
9721
|
+
});
|
|
9722
|
+
addTeardown(() => {
|
|
9723
|
+
mutationObserver.disconnect();
|
|
9724
|
+
});
|
|
9725
|
+
}
|
|
9726
|
+
|
|
9727
|
+
return {
|
|
9728
|
+
slot,
|
|
9729
|
+
|
|
9730
|
+
cleanup: () => {
|
|
9731
|
+
teardown();
|
|
9732
|
+
},
|
|
9733
|
+
pause: () => {
|
|
9734
|
+
if (state.isPaused) {
|
|
9735
|
+
return;
|
|
9736
|
+
}
|
|
9737
|
+
publishPause();
|
|
9738
|
+
state.isPaused = true;
|
|
9739
|
+
},
|
|
9740
|
+
resume: () => {
|
|
9741
|
+
if (!state.isPaused) {
|
|
9742
|
+
return;
|
|
9743
|
+
}
|
|
9744
|
+
state.isPaused = false;
|
|
9745
|
+
publishResume();
|
|
9746
|
+
},
|
|
9747
|
+
getState: () => state,
|
|
9748
|
+
};
|
|
9749
|
+
};
|
|
9750
|
+
|
|
9751
|
+
const applyTransition = (
|
|
9752
|
+
transitionController,
|
|
9753
|
+
setupTransition,
|
|
9754
|
+
{
|
|
9755
|
+
type,
|
|
9756
|
+
duration,
|
|
9757
|
+
animationProgress = 0,
|
|
9758
|
+
isPhaseTransition,
|
|
9759
|
+
onComplete,
|
|
9760
|
+
previousSlotInfo,
|
|
9761
|
+
slotInfo,
|
|
9762
|
+
debug,
|
|
9763
|
+
},
|
|
9764
|
+
) => {
|
|
9765
|
+
let transitionType;
|
|
9766
|
+
if (type === "cross-fade") {
|
|
9767
|
+
transitionType = crossFade;
|
|
9768
|
+
} else if (type === "slide-left") {
|
|
9769
|
+
transitionType = slideLeft;
|
|
9770
|
+
} else {
|
|
9771
|
+
return null;
|
|
9772
|
+
}
|
|
9773
|
+
|
|
9774
|
+
const { cleanup, oldElement, newElement, onTeardown } = setupTransition();
|
|
9775
|
+
// Use precomputed content key states (expected to be provided by caller)
|
|
9776
|
+
const fromContentKey = previousSlotInfo.contentKeyFormatted;
|
|
9777
|
+
const toContentKey = slotInfo.contentKeyFormatted;
|
|
9778
|
+
|
|
9779
|
+
debug("content", "Setting up animation:", {
|
|
9780
|
+
type,
|
|
9781
|
+
from: fromContentKey,
|
|
9782
|
+
to: toContentKey,
|
|
9783
|
+
progress: `${(animationProgress * 100).toFixed(1)}%`,
|
|
9784
|
+
});
|
|
9785
|
+
|
|
9786
|
+
const remainingDuration = Math.max(100, duration * (1 - animationProgress));
|
|
9787
|
+
debug("content", `Animation duration: ${remainingDuration}ms`);
|
|
9788
|
+
|
|
9789
|
+
const transitions = transitionType.apply(oldElement, newElement, {
|
|
9790
|
+
duration: remainingDuration,
|
|
9791
|
+
startProgress: animationProgress,
|
|
9792
|
+
isPhaseTransition,
|
|
9793
|
+
debug,
|
|
9794
|
+
});
|
|
9795
|
+
|
|
9796
|
+
debug("content", `Created ${transitions.length} transition(s) for animation`);
|
|
9797
|
+
|
|
9798
|
+
if (transitions.length === 0) {
|
|
9799
|
+
debug("content", "No transitions to animate, cleaning up immediately");
|
|
9800
|
+
cleanup();
|
|
9801
|
+
onTeardown?.();
|
|
9802
|
+
onComplete?.();
|
|
9803
|
+
return null;
|
|
9804
|
+
}
|
|
9805
|
+
|
|
9806
|
+
const groupTransition = transitionController.animate(transitions, {
|
|
9807
|
+
onFinish: () => {
|
|
9808
|
+
groupTransition.cancel();
|
|
9809
|
+
cleanup();
|
|
9810
|
+
onTeardown?.();
|
|
9811
|
+
onComplete?.();
|
|
9812
|
+
},
|
|
9813
|
+
});
|
|
9814
|
+
|
|
9815
|
+
return groupTransition;
|
|
9816
|
+
};
|
|
9817
|
+
|
|
9818
|
+
const slideLeft = {
|
|
9819
|
+
id: "ui_transition_slide_left",
|
|
9820
|
+
name: "slide-left",
|
|
9821
|
+
apply: (
|
|
9822
|
+
oldElement,
|
|
9823
|
+
newElement,
|
|
9824
|
+
{ duration, startProgress = 0, isPhaseTransition = false, debug },
|
|
9825
|
+
) => {
|
|
9826
|
+
if (!oldElement && !newElement) {
|
|
9827
|
+
return [];
|
|
9828
|
+
}
|
|
9829
|
+
|
|
9830
|
+
if (!newElement) {
|
|
9831
|
+
// Content -> Empty (slide out left only)
|
|
9832
|
+
const currentPosition = getTranslateX(oldElement);
|
|
9833
|
+
const containerWidth = getInnerWidth(oldElement.parentElement);
|
|
9834
|
+
const from = currentPosition;
|
|
9835
|
+
const to = -containerWidth;
|
|
9836
|
+
debug("content", "Slide out to empty:", { from, to });
|
|
9837
|
+
|
|
9838
|
+
return [
|
|
9839
|
+
createTranslateXTransition(oldElement, to, {
|
|
9840
|
+
setup: () =>
|
|
9841
|
+
notifyTransition(newElement, {
|
|
9842
|
+
modelId: slideLeft.id,
|
|
9843
|
+
canOverflow: true,
|
|
9844
|
+
id: "slide_out_old_content",
|
|
9845
|
+
}),
|
|
9846
|
+
from,
|
|
9847
|
+
duration,
|
|
9848
|
+
startProgress,
|
|
9849
|
+
onUpdate: ({ value, timing }) => {
|
|
9850
|
+
debug("transition_updates", "Slide out progress:", value);
|
|
9851
|
+
if (timing === "end") {
|
|
9852
|
+
debug("content", "Slide out complete");
|
|
9853
|
+
}
|
|
9854
|
+
},
|
|
9855
|
+
}),
|
|
9856
|
+
];
|
|
9857
|
+
}
|
|
9858
|
+
|
|
9859
|
+
if (!oldElement) {
|
|
9860
|
+
// Empty -> Content (slide in from right)
|
|
9861
|
+
const containerWidth = getInnerWidth(newElement.parentElement);
|
|
9862
|
+
const from = containerWidth; // Start from right edge for slide-in effect
|
|
9863
|
+
const to = getTranslateXWithoutTransition(newElement);
|
|
9864
|
+
debug("content", "Slide in from empty:", { from, to });
|
|
9865
|
+
return [
|
|
9866
|
+
createTranslateXTransition(newElement, to, {
|
|
9867
|
+
setup: () =>
|
|
9868
|
+
notifyTransition(newElement, {
|
|
9869
|
+
modelId: slideLeft.id,
|
|
9870
|
+
canOverflow: true,
|
|
9871
|
+
id: "slide_in_new_content",
|
|
9872
|
+
}),
|
|
9873
|
+
from,
|
|
9874
|
+
duration,
|
|
9875
|
+
startProgress,
|
|
9876
|
+
onUpdate: ({ value, timing }) => {
|
|
9877
|
+
debug("transition_updates", "Slide in progress:", value);
|
|
9878
|
+
if (timing === "end") {
|
|
9879
|
+
debug("content", "Slide in complete");
|
|
9880
|
+
}
|
|
9881
|
+
},
|
|
9882
|
+
}),
|
|
9883
|
+
];
|
|
9884
|
+
}
|
|
9885
|
+
|
|
9886
|
+
// Content -> Content (slide left)
|
|
9887
|
+
// The old content (oldElement) slides OUT to the left
|
|
9888
|
+
// The new content (newElement) slides IN from the right
|
|
9889
|
+
|
|
9890
|
+
// Get positions for the slide animation
|
|
9891
|
+
const containerWidth = getInnerWidth(newElement.parentElement);
|
|
9892
|
+
const oldContentPosition = getTranslateX(oldElement);
|
|
9893
|
+
const currentNewPosition = getTranslateX(newElement);
|
|
9894
|
+
const naturalNewPosition = getTranslateXWithoutTransition(newElement);
|
|
9895
|
+
|
|
9896
|
+
// For smooth continuation: if newElement is mid-transition,
|
|
9897
|
+
// calculate new position to maintain seamless sliding
|
|
9898
|
+
let startNewPosition;
|
|
9899
|
+
if (currentNewPosition !== 0 && naturalNewPosition === 0) {
|
|
9900
|
+
startNewPosition = currentNewPosition + containerWidth;
|
|
9901
|
+
debug(
|
|
9902
|
+
"content",
|
|
9903
|
+
"Calculated seamless position:",
|
|
9904
|
+
`${currentNewPosition} + ${containerWidth} = ${startNewPosition}`,
|
|
9905
|
+
);
|
|
9906
|
+
} else {
|
|
9907
|
+
startNewPosition = naturalNewPosition || containerWidth;
|
|
9908
|
+
}
|
|
9909
|
+
|
|
9910
|
+
// For phase transitions, force new content to start from right edge for proper slide-in
|
|
9911
|
+
const effectiveFromPosition = isPhaseTransition
|
|
9912
|
+
? containerWidth
|
|
9913
|
+
: startNewPosition;
|
|
9914
|
+
|
|
9915
|
+
debug("content", "Slide transition:", {
|
|
9916
|
+
oldContent: `${oldContentPosition} → ${-containerWidth}`,
|
|
9917
|
+
newContent: `${effectiveFromPosition} → ${naturalNewPosition}`,
|
|
9918
|
+
});
|
|
9919
|
+
|
|
9920
|
+
const transitions = [];
|
|
9921
|
+
|
|
9922
|
+
// Slide old content out
|
|
9923
|
+
transitions.push(
|
|
9924
|
+
createTranslateXTransition(oldElement, -containerWidth, {
|
|
9925
|
+
setup: () =>
|
|
9926
|
+
notifyTransition(newElement, {
|
|
9927
|
+
modelId: slideLeft.id,
|
|
9928
|
+
canOverflow: true,
|
|
9929
|
+
id: "slide_out_old_content",
|
|
9930
|
+
}),
|
|
9931
|
+
from: oldContentPosition,
|
|
9932
|
+
duration,
|
|
9933
|
+
startProgress,
|
|
9934
|
+
onUpdate: ({ value }) => {
|
|
9935
|
+
debug("transition_updates", "Old content slide out:", value);
|
|
9936
|
+
},
|
|
9937
|
+
}),
|
|
9938
|
+
);
|
|
9939
|
+
|
|
9940
|
+
// Slide new content in
|
|
9941
|
+
transitions.push(
|
|
9942
|
+
createTranslateXTransition(newElement, naturalNewPosition, {
|
|
9943
|
+
setup: () =>
|
|
9944
|
+
notifyTransition(newElement, {
|
|
9945
|
+
modelId: slideLeft.id,
|
|
9946
|
+
canOverflow: true,
|
|
9947
|
+
id: "slide_in_new_content",
|
|
9948
|
+
}),
|
|
9949
|
+
from: effectiveFromPosition,
|
|
9950
|
+
duration,
|
|
9951
|
+
startProgress,
|
|
9952
|
+
onUpdate: ({ value, timing }) => {
|
|
9953
|
+
debug("transition_updates", "New content slide in:", value);
|
|
9954
|
+
if (timing === "end") {
|
|
9955
|
+
debug("content", "Slide complete");
|
|
9956
|
+
}
|
|
9957
|
+
},
|
|
9958
|
+
}),
|
|
9959
|
+
);
|
|
9960
|
+
|
|
9961
|
+
return transitions;
|
|
9962
|
+
},
|
|
9963
|
+
};
|
|
9964
|
+
|
|
9965
|
+
const crossFade = {
|
|
9966
|
+
id: "ui_transition_cross_fade",
|
|
9967
|
+
name: "cross-fade",
|
|
9968
|
+
apply: (
|
|
9969
|
+
oldElement,
|
|
9970
|
+
newElement,
|
|
9971
|
+
{ duration, startProgress = 0, isPhaseTransition = false, debug },
|
|
9972
|
+
) => {
|
|
9973
|
+
if (!oldElement && !newElement) {
|
|
9974
|
+
return [];
|
|
9975
|
+
}
|
|
9976
|
+
|
|
9977
|
+
if (!newElement) {
|
|
9978
|
+
// Content -> Empty (fade out only)
|
|
9979
|
+
const from = getOpacity(oldElement);
|
|
9980
|
+
const to = 0;
|
|
9981
|
+
debug("content", "Fade out to empty:", { from, to });
|
|
9982
|
+
return [
|
|
9983
|
+
createOpacityTransition(oldElement, to, {
|
|
9984
|
+
setup: () =>
|
|
9985
|
+
notifyTransition(newElement, {
|
|
9986
|
+
modelId: crossFade.id,
|
|
9987
|
+
canOverflow: true,
|
|
9988
|
+
id: "fade_out_old_content",
|
|
9989
|
+
}),
|
|
9990
|
+
from,
|
|
9991
|
+
duration,
|
|
9992
|
+
startProgress,
|
|
9993
|
+
onUpdate: ({ value, timing }) => {
|
|
9994
|
+
debug("transition_updates", "Content fade out:", value.toFixed(3));
|
|
9995
|
+
if (timing === "end") {
|
|
9996
|
+
debug("content", "Fade out complete");
|
|
9997
|
+
}
|
|
9998
|
+
},
|
|
9999
|
+
}),
|
|
10000
|
+
];
|
|
10001
|
+
}
|
|
10002
|
+
|
|
10003
|
+
if (!oldElement) {
|
|
10004
|
+
// Empty -> Content (fade in only)
|
|
10005
|
+
const from = 0;
|
|
10006
|
+
const to = getOpacityWithoutTransition(newElement);
|
|
10007
|
+
debug("content", "Fade in from empty:", { from, to });
|
|
10008
|
+
return [
|
|
10009
|
+
createOpacityTransition(newElement, to, {
|
|
10010
|
+
setup: () =>
|
|
10011
|
+
notifyTransition(newElement, {
|
|
10012
|
+
modelId: crossFade.id,
|
|
10013
|
+
canOverflow: true,
|
|
10014
|
+
id: "fade_in_new_content",
|
|
10015
|
+
}),
|
|
10016
|
+
from,
|
|
10017
|
+
duration,
|
|
10018
|
+
startProgress,
|
|
10019
|
+
onUpdate: ({ value, timing }) => {
|
|
10020
|
+
debug("transition_updates", "Fade in progress:", value.toFixed(3));
|
|
10021
|
+
if (timing === "end") {
|
|
10022
|
+
debug("content", "Fade in complete");
|
|
10023
|
+
}
|
|
10024
|
+
},
|
|
10025
|
+
}),
|
|
10026
|
+
];
|
|
10027
|
+
}
|
|
10028
|
+
|
|
10029
|
+
// Content -> Content (cross-fade)
|
|
10030
|
+
// Get current opacity for both elements
|
|
10031
|
+
const oldOpacity = getOpacity(oldElement);
|
|
10032
|
+
const newOpacity = getOpacity(newElement);
|
|
10033
|
+
const newNaturalOpacity = getOpacityWithoutTransition(newElement);
|
|
10034
|
+
|
|
10035
|
+
// For phase transitions, always start new content from 0 for clean visual transition
|
|
10036
|
+
// For content transitions, check for ongoing transitions to continue smoothly
|
|
10037
|
+
let effectiveFromOpacity;
|
|
10038
|
+
if (isPhaseTransition) {
|
|
10039
|
+
effectiveFromOpacity = 0; // Always start fresh for phase transitions (loading → content, etc.)
|
|
10040
|
+
} else {
|
|
10041
|
+
// For content transitions: if new element has ongoing opacity transition
|
|
10042
|
+
// (indicated by non-zero opacity when natural opacity is different),
|
|
10043
|
+
// start from current opacity to continue smoothly, otherwise start from 0
|
|
10044
|
+
const hasOngoingTransition =
|
|
10045
|
+
newOpacity !== newNaturalOpacity && newOpacity > 0;
|
|
10046
|
+
effectiveFromOpacity = hasOngoingTransition ? newOpacity : 0;
|
|
10047
|
+
}
|
|
10048
|
+
|
|
10049
|
+
debug("content", "Cross-fade transition:", {
|
|
10050
|
+
oldOpacity: `${oldOpacity} → 0`,
|
|
10051
|
+
newOpacity: `${effectiveFromOpacity} → ${newNaturalOpacity}`,
|
|
10052
|
+
isPhaseTransition,
|
|
10053
|
+
});
|
|
10054
|
+
|
|
10055
|
+
return [
|
|
10056
|
+
createOpacityTransition(oldElement, 0, {
|
|
10057
|
+
setup: () =>
|
|
10058
|
+
notifyTransition(newElement, {
|
|
10059
|
+
modelId: crossFade.id,
|
|
10060
|
+
canOverflow: true,
|
|
10061
|
+
id: "fade_out_old_content",
|
|
10062
|
+
}),
|
|
10063
|
+
from: oldOpacity,
|
|
10064
|
+
duration,
|
|
10065
|
+
startProgress,
|
|
10066
|
+
onUpdate: ({ value }) => {
|
|
10067
|
+
if (value > 0) {
|
|
10068
|
+
debug(
|
|
10069
|
+
"transition_updates",
|
|
10070
|
+
"Old content fade out:",
|
|
10071
|
+
value.toFixed(3),
|
|
10072
|
+
);
|
|
10073
|
+
}
|
|
10074
|
+
},
|
|
10075
|
+
}),
|
|
10076
|
+
createOpacityTransition(newElement, newNaturalOpacity, {
|
|
10077
|
+
setup: () =>
|
|
10078
|
+
notifyTransition(newElement, {
|
|
10079
|
+
modelId: crossFade.id,
|
|
10080
|
+
canOverflow: true,
|
|
10081
|
+
id: "fade_in_new_content",
|
|
10082
|
+
}),
|
|
10083
|
+
from: effectiveFromOpacity,
|
|
10084
|
+
duration,
|
|
10085
|
+
startProgress: isPhaseTransition ? 0 : startProgress, // Phase transitions: new content always starts fresh
|
|
10086
|
+
onUpdate: ({ value, timing }) => {
|
|
10087
|
+
debug("transition_updates", "New content fade in:", value.toFixed(3));
|
|
10088
|
+
if (timing === "end") {
|
|
10089
|
+
debug("content", "Cross-fade complete");
|
|
10090
|
+
}
|
|
10091
|
+
},
|
|
10092
|
+
}),
|
|
10093
|
+
];
|
|
10094
|
+
},
|
|
10095
|
+
};
|
|
10096
|
+
|
|
10097
|
+
const notifyTransition = (element, detail) => {
|
|
10098
|
+
dispatchUITransitionStartCustomEvent(element, detail);
|
|
10099
|
+
return () => {
|
|
10100
|
+
dispatchUITransitionEndCustomEvent(element, detail);
|
|
10101
|
+
};
|
|
10102
|
+
};
|
|
10103
|
+
const dispatchUITransitionStartCustomEvent = (element, detail) => {
|
|
10104
|
+
const customEvent = new CustomEvent("ui_transition_start", {
|
|
10105
|
+
bubbles: true,
|
|
10106
|
+
detail,
|
|
10107
|
+
});
|
|
10108
|
+
element.dispatchEvent(customEvent);
|
|
10109
|
+
};
|
|
10110
|
+
const dispatchUITransitionEndCustomEvent = (element, detail) => {
|
|
10111
|
+
const customEvent = new CustomEvent("ui_transition_end", {
|
|
10112
|
+
bubbles: true,
|
|
10113
|
+
detail,
|
|
10114
|
+
});
|
|
10115
|
+
element.dispatchEvent(customEvent);
|
|
10116
|
+
};
|
|
10117
|
+
|
|
8579
10118
|
/**
|
|
8580
10119
|
* UITransition
|
|
8581
10120
|
*
|
|
@@ -8616,13 +10155,15 @@ const ContentKeyContext = createContext();
|
|
|
8616
10155
|
const UITransition = ({
|
|
8617
10156
|
children,
|
|
8618
10157
|
contentKey,
|
|
8619
|
-
sizeTransition,
|
|
10158
|
+
sizeTransition = true,
|
|
8620
10159
|
sizeTransitionDuration,
|
|
8621
10160
|
transitionType,
|
|
8622
10161
|
transitionDuration,
|
|
8623
10162
|
phaseTransitionType,
|
|
8624
10163
|
phaseTransitionDuration,
|
|
8625
|
-
|
|
10164
|
+
debugDetection,
|
|
10165
|
+
debugSize,
|
|
10166
|
+
debugBreakAfterClone,
|
|
8626
10167
|
...props
|
|
8627
10168
|
}) => {
|
|
8628
10169
|
const [contentKeyFromContext, setContentKeyFromContext] = useState();
|
|
@@ -8686,19 +10227,18 @@ const UITransition = ({
|
|
|
8686
10227
|
"data-content-transition-duration": transitionDuration ? transitionDuration : undefined,
|
|
8687
10228
|
"data-phase-transition": phaseTransitionType ? phaseTransitionType : undefined,
|
|
8688
10229
|
"data-phase-transition-duration": phaseTransitionDuration ? phaseTransitionDuration : undefined,
|
|
8689
|
-
"data-debug-
|
|
8690
|
-
|
|
10230
|
+
"data-debug-detection": debugDetection ? "" : undefined,
|
|
10231
|
+
"data-debug-size": debugSize ? "" : undefined,
|
|
10232
|
+
"data-debug-break-after-clone": debugBreakAfterClone,
|
|
10233
|
+
children: [jsxs("div", {
|
|
8691
10234
|
className: "ui_transition_outer_wrapper",
|
|
8692
|
-
children:
|
|
8693
|
-
className: "
|
|
8694
|
-
|
|
8695
|
-
|
|
8696
|
-
|
|
8697
|
-
|
|
8698
|
-
|
|
8699
|
-
className: "ui_transition_phase_overlay"
|
|
8700
|
-
})]
|
|
8701
|
-
})
|
|
10235
|
+
children: [jsx("div", {
|
|
10236
|
+
className: "ui_transition_slot",
|
|
10237
|
+
"data-content-key": effectiveContentKey ? effectiveContentKey : undefined,
|
|
10238
|
+
children: children
|
|
10239
|
+
}), jsx("div", {
|
|
10240
|
+
className: "ui_transition_phase_overlay"
|
|
10241
|
+
})]
|
|
8702
10242
|
}), jsx("div", {
|
|
8703
10243
|
className: "ui_transition_content_overlay"
|
|
8704
10244
|
})]
|
|
@@ -12626,6 +14166,8 @@ const applyContentSpacingOnTextChildren = (children, contentSpacing) => {
|
|
|
12626
14166
|
installImportMetaCss(import.meta);import.meta.css = /* css */`
|
|
12627
14167
|
@layer navi {
|
|
12628
14168
|
.navi_link {
|
|
14169
|
+
--border-radius: 2px;
|
|
14170
|
+
--outline-color: var(--navi-focus-outline-color);
|
|
12629
14171
|
--color: rgb(0, 0, 238);
|
|
12630
14172
|
--color-visited: light-dark(#6a1b9a, #ab47bc);
|
|
12631
14173
|
--color-active: red;
|
|
@@ -12636,9 +14178,6 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
|
|
|
12636
14178
|
}
|
|
12637
14179
|
|
|
12638
14180
|
.navi_link {
|
|
12639
|
-
position: relative;
|
|
12640
|
-
border-radius: 2px;
|
|
12641
|
-
|
|
12642
14181
|
--x-color: var(--color);
|
|
12643
14182
|
--x-color-hover: var(--color-hover, var(--color));
|
|
12644
14183
|
--x-color-visited: var(--color-visited);
|
|
@@ -12647,8 +14186,11 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
|
|
|
12647
14186
|
--x-text-decoration-hover: var(--text-decoration-hover,);
|
|
12648
14187
|
--x-cursor: var(--cursor);
|
|
12649
14188
|
|
|
14189
|
+
position: relative;
|
|
12650
14190
|
color: var(--x-color);
|
|
12651
14191
|
text-decoration: var(--x-text-decoration);
|
|
14192
|
+
border-radius: var(--border-radius);
|
|
14193
|
+
outline-color: var(--outline-color);
|
|
12652
14194
|
cursor: var(--x-cursor);
|
|
12653
14195
|
}
|
|
12654
14196
|
/* Hover */
|
|
@@ -12661,6 +14203,10 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
|
|
|
12661
14203
|
position: relative;
|
|
12662
14204
|
z-index: 1; /* Ensure focus outline is above other elements */
|
|
12663
14205
|
}
|
|
14206
|
+
.navi_link[data-focus-visible] {
|
|
14207
|
+
outline-width: 2px;
|
|
14208
|
+
outline-style: solid;
|
|
14209
|
+
}
|
|
12664
14210
|
/* Visited */
|
|
12665
14211
|
.navi_link[data-visited] {
|
|
12666
14212
|
--x-color: var(--x-color-visited);
|
|
@@ -12694,9 +14240,18 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
|
|
|
12694
14240
|
}
|
|
12695
14241
|
`;
|
|
12696
14242
|
const LinkManagedByCSSVars = {
|
|
12697
|
-
|
|
12698
|
-
|
|
12699
|
-
|
|
14243
|
+
"outlineColor": "--outline-color",
|
|
14244
|
+
"borderRadius": "--border-radius",
|
|
14245
|
+
"color": "--color",
|
|
14246
|
+
"cursor": "--cursor",
|
|
14247
|
+
"textDecoration": "--text-decoration",
|
|
14248
|
+
":hover": {
|
|
14249
|
+
color: "--color-hover",
|
|
14250
|
+
textDecoration: "--text-decoration-hover"
|
|
14251
|
+
},
|
|
14252
|
+
":active": {
|
|
14253
|
+
color: "--color-active"
|
|
14254
|
+
}
|
|
12700
14255
|
};
|
|
12701
14256
|
const LinkPseudoClasses = [":hover", ":active", ":focus", ":focus-visible", ":read-only", ":disabled", ":visited", ":-navi-loading", ":-navi-internal-link", ":-navi-external-link", ":-navi-anchor-link", ":-navi-current-link"];
|
|
12702
14257
|
const LinkPseudoElements = ["::-navi-loader"];
|
|
@@ -14075,7 +15630,8 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
|
|
|
14075
15630
|
--width: 13px;
|
|
14076
15631
|
--height: 13px;
|
|
14077
15632
|
|
|
14078
|
-
--outline-color:
|
|
15633
|
+
--outline-color: var(--navi-focus-outline-color);
|
|
15634
|
+
--loader-color: var(--navi-loader-color);
|
|
14079
15635
|
--border-color: light-dark(#767676, #8e8e93);
|
|
14080
15636
|
--background-color: white;
|
|
14081
15637
|
--color: light-dark(#4476ff, #3b82f6);
|
|
@@ -14373,7 +15929,7 @@ const InputCheckboxBasic = props => {
|
|
|
14373
15929
|
children: [jsx(LoaderBackground, {
|
|
14374
15930
|
loading: innerLoading,
|
|
14375
15931
|
inset: -1,
|
|
14376
|
-
color: "var(--
|
|
15932
|
+
color: "var(--loader-color)"
|
|
14377
15933
|
}), renderCheckboxMemoized, jsx("div", {
|
|
14378
15934
|
className: "navi_checkbox_field",
|
|
14379
15935
|
children: jsx("svg", {
|
|
@@ -14464,7 +16020,8 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
|
|
|
14464
16020
|
--width: 13px;
|
|
14465
16021
|
--height: 13px;
|
|
14466
16022
|
|
|
14467
|
-
--outline-color:
|
|
16023
|
+
--outline-color: var(--navi-focus-outline-color);
|
|
16024
|
+
--loader-color: var(--navi-loader-color);
|
|
14468
16025
|
--border-color: light-dark(#767676, #8e8e93);
|
|
14469
16026
|
--background-color: white;
|
|
14470
16027
|
--color: light-dark(#4476ff, #3b82f6);
|
|
@@ -14806,7 +16363,7 @@ const InputRadioBasic = props => {
|
|
|
14806
16363
|
loading: innerLoading,
|
|
14807
16364
|
inset: -1,
|
|
14808
16365
|
targetSelector: ".navi_radio_field",
|
|
14809
|
-
color: "var(--
|
|
16366
|
+
color: "var(--loader-color)"
|
|
14810
16367
|
}), renderRadioMemoized, jsx("span", {
|
|
14811
16368
|
className: "navi_radio_field",
|
|
14812
16369
|
children: jsxs("svg", {
|
|
@@ -14851,7 +16408,8 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
|
|
|
14851
16408
|
--outer-width: calc(var(--border-width) + var(--outline-width));
|
|
14852
16409
|
|
|
14853
16410
|
/* Default */
|
|
14854
|
-
--outline-color:
|
|
16411
|
+
--outline-color: var(--navi-focus-outline-color);
|
|
16412
|
+
--loader-color: var(--navi-loader-color);
|
|
14855
16413
|
--border-color: light-dark(#767676, #8e8e93);
|
|
14856
16414
|
--background-color: white;
|
|
14857
16415
|
--color: currentColor;
|
|
@@ -15093,7 +16651,7 @@ const InputTextualBasic = props => {
|
|
|
15093
16651
|
...rest,
|
|
15094
16652
|
children: [jsx(LoaderBackground, {
|
|
15095
16653
|
loading: innerLoading,
|
|
15096
|
-
color: "var(--
|
|
16654
|
+
color: "var(--loader-color)",
|
|
15097
16655
|
inset: -1
|
|
15098
16656
|
}), renderInputMemoized]
|
|
15099
16657
|
});
|
|
@@ -15454,7 +17012,8 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
|
|
|
15454
17012
|
--border-width: 1px;
|
|
15455
17013
|
--border-radius: 2px;
|
|
15456
17014
|
/* default */
|
|
15457
|
-
--outline-color:
|
|
17015
|
+
--outline-color: var(--navi-focus-outline-color);
|
|
17016
|
+
--loader-color: var(--navi-loader-color);
|
|
15458
17017
|
--border-color: light-dark(#767676, #8e8e93);
|
|
15459
17018
|
--background-color: light-dark(#f3f4f6, #2d3748);
|
|
15460
17019
|
--color: currentColor;
|
|
@@ -15715,7 +17274,7 @@ const ButtonBasic = props => {
|
|
|
15715
17274
|
children: [jsx(LoaderBackground, {
|
|
15716
17275
|
loading: innerLoading,
|
|
15717
17276
|
inset: -1,
|
|
15718
|
-
color: "var(--
|
|
17277
|
+
color: "var(--loader-color)"
|
|
15719
17278
|
}), renderButtonContentMemoized]
|
|
15720
17279
|
});
|
|
15721
17280
|
};
|
|
@@ -20182,53 +21741,9 @@ const useSignalSync = (value, initialValue = value) => {
|
|
|
20182
21741
|
return signal;
|
|
20183
21742
|
};
|
|
20184
21743
|
|
|
20185
|
-
|
|
20186
|
-
.navi_font_sized_svg {
|
|
20187
|
-
display: flex;
|
|
20188
|
-
width: 1em;
|
|
20189
|
-
height: 1em;
|
|
20190
|
-
flex-shrink: 0;
|
|
20191
|
-
align-items: center;
|
|
20192
|
-
justify-self: center;
|
|
20193
|
-
line-height: 1em;
|
|
20194
|
-
}
|
|
20195
|
-
`;
|
|
20196
|
-
const FontSizedSvg = ({
|
|
20197
|
-
children,
|
|
20198
|
-
...rest
|
|
20199
|
-
}) => {
|
|
20200
|
-
return jsx(Box, {
|
|
20201
|
-
...rest,
|
|
20202
|
-
as: "span",
|
|
20203
|
-
baseClassName: "navi_font_sized_svg",
|
|
20204
|
-
children: children
|
|
20205
|
-
});
|
|
20206
|
-
};
|
|
21744
|
+
const FontSizedSvg = () => {};
|
|
20207
21745
|
|
|
20208
|
-
const IconAndText = ({
|
|
20209
|
-
icon,
|
|
20210
|
-
children,
|
|
20211
|
-
...rest
|
|
20212
|
-
}) => {
|
|
20213
|
-
if (typeof icon === "function") icon = icon({});
|
|
20214
|
-
return jsxs("span", {
|
|
20215
|
-
className: "icon_and_text",
|
|
20216
|
-
...rest,
|
|
20217
|
-
style: {
|
|
20218
|
-
display: "flex",
|
|
20219
|
-
alignItems: "center",
|
|
20220
|
-
gap: "0.1em",
|
|
20221
|
-
...rest.style
|
|
20222
|
-
},
|
|
20223
|
-
children: [jsx(FontSizedSvg, {
|
|
20224
|
-
className: "icon",
|
|
20225
|
-
children: icon
|
|
20226
|
-
}), jsx("span", {
|
|
20227
|
-
className: "text",
|
|
20228
|
-
children: children
|
|
20229
|
-
})]
|
|
20230
|
-
});
|
|
20231
|
-
};
|
|
21746
|
+
const IconAndText = () => {};
|
|
20232
21747
|
|
|
20233
21748
|
installImportMetaCss(import.meta);import.meta.css = /* css */`
|
|
20234
21749
|
.svg_mask_content * {
|