@jsenv/navi 0.12.6 → 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 +1646 -110
- package/dist/jsenv_navi.js.map +21 -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,
|
|
@@ -8555,6 +8564,1557 @@ const useNavStateBasic = (id, initialValue, { debug } = {}) => {
|
|
|
8555
8564
|
|
|
8556
8565
|
const useNavState = useNavStateBasic;
|
|
8557
8566
|
|
|
8567
|
+
const NEVER_SET = {};
|
|
8568
|
+
const useUrlSearchParam = (paramName) => {
|
|
8569
|
+
const documentUrl = documentUrlSignal.value;
|
|
8570
|
+
const searchParam = new URL(documentUrl).searchParams.get(paramName);
|
|
8571
|
+
const valueRef = useRef(NEVER_SET);
|
|
8572
|
+
const [value, setValue] = useState();
|
|
8573
|
+
if (valueRef.current !== searchParam) {
|
|
8574
|
+
valueRef.current = searchParam;
|
|
8575
|
+
setValue(searchParam);
|
|
8576
|
+
}
|
|
8577
|
+
|
|
8578
|
+
const setSearchParamValue = (newValue, { replace = true } = {}) => {
|
|
8579
|
+
const newUrlObject = new URL(window.location.href);
|
|
8580
|
+
newUrlObject.searchParams.set(paramName, newValue);
|
|
8581
|
+
const newUrl = newUrlObject.href;
|
|
8582
|
+
goTo(newUrl, { replace });
|
|
8583
|
+
};
|
|
8584
|
+
|
|
8585
|
+
return [value, setSearchParamValue];
|
|
8586
|
+
};
|
|
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
|
+
|
|
8558
10118
|
/**
|
|
8559
10119
|
* UITransition
|
|
8560
10120
|
*
|
|
@@ -8595,13 +10155,15 @@ const ContentKeyContext = createContext();
|
|
|
8595
10155
|
const UITransition = ({
|
|
8596
10156
|
children,
|
|
8597
10157
|
contentKey,
|
|
8598
|
-
sizeTransition,
|
|
10158
|
+
sizeTransition = true,
|
|
8599
10159
|
sizeTransitionDuration,
|
|
8600
10160
|
transitionType,
|
|
8601
10161
|
transitionDuration,
|
|
8602
10162
|
phaseTransitionType,
|
|
8603
10163
|
phaseTransitionDuration,
|
|
8604
|
-
|
|
10164
|
+
debugDetection,
|
|
10165
|
+
debugSize,
|
|
10166
|
+
debugBreakAfterClone,
|
|
8605
10167
|
...props
|
|
8606
10168
|
}) => {
|
|
8607
10169
|
const [contentKeyFromContext, setContentKeyFromContext] = useState();
|
|
@@ -8665,19 +10227,18 @@ const UITransition = ({
|
|
|
8665
10227
|
"data-content-transition-duration": transitionDuration ? transitionDuration : undefined,
|
|
8666
10228
|
"data-phase-transition": phaseTransitionType ? phaseTransitionType : undefined,
|
|
8667
10229
|
"data-phase-transition-duration": phaseTransitionDuration ? phaseTransitionDuration : undefined,
|
|
8668
|
-
"data-debug-
|
|
8669
|
-
|
|
10230
|
+
"data-debug-detection": debugDetection ? "" : undefined,
|
|
10231
|
+
"data-debug-size": debugSize ? "" : undefined,
|
|
10232
|
+
"data-debug-break-after-clone": debugBreakAfterClone,
|
|
10233
|
+
children: [jsxs("div", {
|
|
8670
10234
|
className: "ui_transition_outer_wrapper",
|
|
8671
|
-
children:
|
|
8672
|
-
className: "
|
|
8673
|
-
|
|
8674
|
-
|
|
8675
|
-
|
|
8676
|
-
|
|
8677
|
-
|
|
8678
|
-
className: "ui_transition_phase_overlay"
|
|
8679
|
-
})]
|
|
8680
|
-
})
|
|
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
|
+
})]
|
|
8681
10242
|
}), jsx("div", {
|
|
8682
10243
|
className: "ui_transition_content_overlay"
|
|
8683
10244
|
})]
|
|
@@ -12605,6 +14166,8 @@ const applyContentSpacingOnTextChildren = (children, contentSpacing) => {
|
|
|
12605
14166
|
installImportMetaCss(import.meta);import.meta.css = /* css */`
|
|
12606
14167
|
@layer navi {
|
|
12607
14168
|
.navi_link {
|
|
14169
|
+
--border-radius: 2px;
|
|
14170
|
+
--outline-color: var(--navi-focus-outline-color);
|
|
12608
14171
|
--color: rgb(0, 0, 238);
|
|
12609
14172
|
--color-visited: light-dark(#6a1b9a, #ab47bc);
|
|
12610
14173
|
--color-active: red;
|
|
@@ -12615,9 +14178,6 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
|
|
|
12615
14178
|
}
|
|
12616
14179
|
|
|
12617
14180
|
.navi_link {
|
|
12618
|
-
position: relative;
|
|
12619
|
-
border-radius: 2px;
|
|
12620
|
-
|
|
12621
14181
|
--x-color: var(--color);
|
|
12622
14182
|
--x-color-hover: var(--color-hover, var(--color));
|
|
12623
14183
|
--x-color-visited: var(--color-visited);
|
|
@@ -12626,8 +14186,11 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
|
|
|
12626
14186
|
--x-text-decoration-hover: var(--text-decoration-hover,);
|
|
12627
14187
|
--x-cursor: var(--cursor);
|
|
12628
14188
|
|
|
14189
|
+
position: relative;
|
|
12629
14190
|
color: var(--x-color);
|
|
12630
14191
|
text-decoration: var(--x-text-decoration);
|
|
14192
|
+
border-radius: var(--border-radius);
|
|
14193
|
+
outline-color: var(--outline-color);
|
|
12631
14194
|
cursor: var(--x-cursor);
|
|
12632
14195
|
}
|
|
12633
14196
|
/* Hover */
|
|
@@ -12640,6 +14203,10 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
|
|
|
12640
14203
|
position: relative;
|
|
12641
14204
|
z-index: 1; /* Ensure focus outline is above other elements */
|
|
12642
14205
|
}
|
|
14206
|
+
.navi_link[data-focus-visible] {
|
|
14207
|
+
outline-width: 2px;
|
|
14208
|
+
outline-style: solid;
|
|
14209
|
+
}
|
|
12643
14210
|
/* Visited */
|
|
12644
14211
|
.navi_link[data-visited] {
|
|
12645
14212
|
--x-color: var(--x-color-visited);
|
|
@@ -12673,9 +14240,18 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
|
|
|
12673
14240
|
}
|
|
12674
14241
|
`;
|
|
12675
14242
|
const LinkManagedByCSSVars = {
|
|
12676
|
-
|
|
12677
|
-
|
|
12678
|
-
|
|
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
|
+
}
|
|
12679
14255
|
};
|
|
12680
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"];
|
|
12681
14257
|
const LinkPseudoElements = ["::-navi-loader"];
|
|
@@ -14054,7 +15630,8 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
|
|
|
14054
15630
|
--width: 13px;
|
|
14055
15631
|
--height: 13px;
|
|
14056
15632
|
|
|
14057
|
-
--outline-color:
|
|
15633
|
+
--outline-color: var(--navi-focus-outline-color);
|
|
15634
|
+
--loader-color: var(--navi-loader-color);
|
|
14058
15635
|
--border-color: light-dark(#767676, #8e8e93);
|
|
14059
15636
|
--background-color: white;
|
|
14060
15637
|
--color: light-dark(#4476ff, #3b82f6);
|
|
@@ -14352,7 +15929,7 @@ const InputCheckboxBasic = props => {
|
|
|
14352
15929
|
children: [jsx(LoaderBackground, {
|
|
14353
15930
|
loading: innerLoading,
|
|
14354
15931
|
inset: -1,
|
|
14355
|
-
color: "var(--
|
|
15932
|
+
color: "var(--loader-color)"
|
|
14356
15933
|
}), renderCheckboxMemoized, jsx("div", {
|
|
14357
15934
|
className: "navi_checkbox_field",
|
|
14358
15935
|
children: jsx("svg", {
|
|
@@ -14443,7 +16020,8 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
|
|
|
14443
16020
|
--width: 13px;
|
|
14444
16021
|
--height: 13px;
|
|
14445
16022
|
|
|
14446
|
-
--outline-color:
|
|
16023
|
+
--outline-color: var(--navi-focus-outline-color);
|
|
16024
|
+
--loader-color: var(--navi-loader-color);
|
|
14447
16025
|
--border-color: light-dark(#767676, #8e8e93);
|
|
14448
16026
|
--background-color: white;
|
|
14449
16027
|
--color: light-dark(#4476ff, #3b82f6);
|
|
@@ -14785,7 +16363,7 @@ const InputRadioBasic = props => {
|
|
|
14785
16363
|
loading: innerLoading,
|
|
14786
16364
|
inset: -1,
|
|
14787
16365
|
targetSelector: ".navi_radio_field",
|
|
14788
|
-
color: "var(--
|
|
16366
|
+
color: "var(--loader-color)"
|
|
14789
16367
|
}), renderRadioMemoized, jsx("span", {
|
|
14790
16368
|
className: "navi_radio_field",
|
|
14791
16369
|
children: jsxs("svg", {
|
|
@@ -14830,7 +16408,8 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
|
|
|
14830
16408
|
--outer-width: calc(var(--border-width) + var(--outline-width));
|
|
14831
16409
|
|
|
14832
16410
|
/* Default */
|
|
14833
|
-
--outline-color:
|
|
16411
|
+
--outline-color: var(--navi-focus-outline-color);
|
|
16412
|
+
--loader-color: var(--navi-loader-color);
|
|
14834
16413
|
--border-color: light-dark(#767676, #8e8e93);
|
|
14835
16414
|
--background-color: white;
|
|
14836
16415
|
--color: currentColor;
|
|
@@ -15072,7 +16651,7 @@ const InputTextualBasic = props => {
|
|
|
15072
16651
|
...rest,
|
|
15073
16652
|
children: [jsx(LoaderBackground, {
|
|
15074
16653
|
loading: innerLoading,
|
|
15075
|
-
color: "var(--
|
|
16654
|
+
color: "var(--loader-color)",
|
|
15076
16655
|
inset: -1
|
|
15077
16656
|
}), renderInputMemoized]
|
|
15078
16657
|
});
|
|
@@ -15433,7 +17012,8 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
|
|
|
15433
17012
|
--border-width: 1px;
|
|
15434
17013
|
--border-radius: 2px;
|
|
15435
17014
|
/* default */
|
|
15436
|
-
--outline-color:
|
|
17015
|
+
--outline-color: var(--navi-focus-outline-color);
|
|
17016
|
+
--loader-color: var(--navi-loader-color);
|
|
15437
17017
|
--border-color: light-dark(#767676, #8e8e93);
|
|
15438
17018
|
--background-color: light-dark(#f3f4f6, #2d3748);
|
|
15439
17019
|
--color: currentColor;
|
|
@@ -15694,7 +17274,7 @@ const ButtonBasic = props => {
|
|
|
15694
17274
|
children: [jsx(LoaderBackground, {
|
|
15695
17275
|
loading: innerLoading,
|
|
15696
17276
|
inset: -1,
|
|
15697
|
-
color: "var(--
|
|
17277
|
+
color: "var(--loader-color)"
|
|
15698
17278
|
}), renderButtonContentMemoized]
|
|
15699
17279
|
});
|
|
15700
17280
|
};
|
|
@@ -20161,53 +21741,9 @@ const useSignalSync = (value, initialValue = value) => {
|
|
|
20161
21741
|
return signal;
|
|
20162
21742
|
};
|
|
20163
21743
|
|
|
20164
|
-
|
|
20165
|
-
.navi_font_sized_svg {
|
|
20166
|
-
display: flex;
|
|
20167
|
-
width: 1em;
|
|
20168
|
-
height: 1em;
|
|
20169
|
-
flex-shrink: 0;
|
|
20170
|
-
align-items: center;
|
|
20171
|
-
justify-self: center;
|
|
20172
|
-
line-height: 1em;
|
|
20173
|
-
}
|
|
20174
|
-
`;
|
|
20175
|
-
const FontSizedSvg = ({
|
|
20176
|
-
children,
|
|
20177
|
-
...rest
|
|
20178
|
-
}) => {
|
|
20179
|
-
return jsx(Box, {
|
|
20180
|
-
...rest,
|
|
20181
|
-
as: "span",
|
|
20182
|
-
baseClassName: "navi_font_sized_svg",
|
|
20183
|
-
children: children
|
|
20184
|
-
});
|
|
20185
|
-
};
|
|
21744
|
+
const FontSizedSvg = () => {};
|
|
20186
21745
|
|
|
20187
|
-
const IconAndText = ({
|
|
20188
|
-
icon,
|
|
20189
|
-
children,
|
|
20190
|
-
...rest
|
|
20191
|
-
}) => {
|
|
20192
|
-
if (typeof icon === "function") icon = icon({});
|
|
20193
|
-
return jsxs("span", {
|
|
20194
|
-
className: "icon_and_text",
|
|
20195
|
-
...rest,
|
|
20196
|
-
style: {
|
|
20197
|
-
display: "flex",
|
|
20198
|
-
alignItems: "center",
|
|
20199
|
-
gap: "0.1em",
|
|
20200
|
-
...rest.style
|
|
20201
|
-
},
|
|
20202
|
-
children: [jsx(FontSizedSvg, {
|
|
20203
|
-
className: "icon",
|
|
20204
|
-
children: icon
|
|
20205
|
-
}), jsx("span", {
|
|
20206
|
-
className: "text",
|
|
20207
|
-
children: children
|
|
20208
|
-
})]
|
|
20209
|
-
});
|
|
20210
|
-
};
|
|
21746
|
+
const IconAndText = () => {};
|
|
20211
21747
|
|
|
20212
21748
|
installImportMetaCss(import.meta);import.meta.css = /* css */`
|
|
20213
21749
|
.svg_mask_content * {
|
|
@@ -20550,5 +22086,5 @@ const useDependenciesDiff = (inputs) => {
|
|
|
20550
22086
|
return diffRef.current;
|
|
20551
22087
|
};
|
|
20552
22088
|
|
|
20553
|
-
export { ActionRenderer, ActiveKeyboardShortcuts, Box, Button, CharSlot, Checkbox, CheckboxList, Code, Col, Colgroup, Count, Details, Editable, ErrorBoundaryContext, FontSizedSvg, Form, Icon, IconAndText, Image, Input, Label, Layout, Link, LinkWithIcon, MessageBox, Paragraph, Radio, RadioList, Route, RouteLink, Routes, RowNumberCol, RowNumberTableCell, SINGLE_SPACE_CONSTRAINT, SVGMaskOverlay, Select, SelectionContext, SummaryMarker, Svg, Tab, TabList, Table, TableCell, Tbody, Text, Thead, Title, Tr, UITransition, actionIntegratedVia, addCustomMessage, createAction, createSelectionKeyboardShortcuts, createUniqueValueConstraint, enableDebugActions, enableDebugOnDocumentLoading, forwardActionRequested, goBack, goForward, goTo, installCustomConstraintValidation, isCellSelected, isColumnSelected, isRowSelected, openCallout, rawUrlPart, reload, removeCustomMessage, rerunActions, resource, setBaseUrl, setupRoutes, stopLoad, stringifyTableSelectionValue, updateActions, useActionData, useActionStatus, useCellsAndColumns, useDependenciesDiff, useDocumentState, useDocumentUrl, useEditionController, useFocusGroup, useKeyboardShortcuts, useNavState, useRouteStatus, useRunOnMount, useSelectableElement, useSelectionController, useSignalSync, useStateArray, valueInLocalStorage };
|
|
22089
|
+
export { ActionRenderer, ActiveKeyboardShortcuts, Box, Button, CharSlot, Checkbox, CheckboxList, Code, Col, Colgroup, Count, Details, Editable, ErrorBoundaryContext, FontSizedSvg, Form, Icon, IconAndText, Image, Input, Label, Layout, Link, LinkWithIcon, MessageBox, Paragraph, Radio, RadioList, Route, RouteLink, Routes, RowNumberCol, RowNumberTableCell, SINGLE_SPACE_CONSTRAINT, SVGMaskOverlay, Select, SelectionContext, SummaryMarker, Svg, Tab, TabList, Table, TableCell, Tbody, Text, Thead, Title, Tr, UITransition, actionIntegratedVia, addCustomMessage, createAction, createSelectionKeyboardShortcuts, createUniqueValueConstraint, enableDebugActions, enableDebugOnDocumentLoading, forwardActionRequested, goBack, goForward, goTo, installCustomConstraintValidation, isCellSelected, isColumnSelected, isRowSelected, openCallout, rawUrlPart, reload, removeCustomMessage, rerunActions, resource, setBaseUrl, setupRoutes, stopLoad, stringifyTableSelectionValue, updateActions, useActionData, useActionStatus, useCellsAndColumns, useDependenciesDiff, useDocumentState, useDocumentUrl, useEditionController, useFocusGroup, useKeyboardShortcuts, useNavState, useRouteStatus, useRunOnMount, useSelectableElement, useSelectionController, useSignalSync, useStateArray, useUrlSearchParam, valueInLocalStorage };
|
|
20554
22090
|
//# sourceMappingURL=jsenv_navi.js.map
|