|
|
@@ -697,6 +697,9 @@
|
|
|
|
|
|
// Content container
|
|
|
this.dom.content = el('div', `${PREFIX}content`);
|
|
|
+ // In flow, stretch content so slide distance is visible.
|
|
|
+ this.dom.content.style.alignSelf = 'stretch';
|
|
|
+ this.dom.content.style.width = '100%';
|
|
|
// Stack container: keep panes absolute so we can cross-fade without layout thrash
|
|
|
this.dom.stepStack = el('div', `${PREFIX}step-stack`);
|
|
|
this.dom.stepStack.style.position = 'relative';
|
|
|
@@ -2019,9 +2022,88 @@
|
|
|
this.params = nextOptions;
|
|
|
this._applyPopupTheme(this.params);
|
|
|
|
|
|
- // Update icon/title/buttons without recreating DOM
|
|
|
+ const isNext = direction !== 'prev';
|
|
|
+ const enterFrom = isNext ? 100 : -100;
|
|
|
+ const exitTo = isNext ? -100 : 100;
|
|
|
+ const slideDuration = 320;
|
|
|
+
|
|
|
+ // Update title with directional slide (keep in sync with panes)
|
|
|
+ let titleAnim = null;
|
|
|
+ let titleCleanup = null;
|
|
|
try {
|
|
|
- if (this.dom.title) this.dom.title.textContent = this.params.title || '';
|
|
|
+ const titleEl = this.dom && this.dom.title;
|
|
|
+ if (titleEl) {
|
|
|
+ const nextTitle = this.params.title || '';
|
|
|
+ const prevTitle = titleEl.textContent || '';
|
|
|
+ if (prevTitle !== nextTitle) {
|
|
|
+ const clone = titleEl.cloneNode(true);
|
|
|
+ try { clone.textContent = prevTitle; } catch {}
|
|
|
+ try {
|
|
|
+ const popupRect = popup.getBoundingClientRect();
|
|
|
+ const titleRect = titleEl.getBoundingClientRect();
|
|
|
+ clone.style.position = 'absolute';
|
|
|
+ clone.style.left = '0';
|
|
|
+ clone.style.right = '0';
|
|
|
+ clone.style.top = Math.max(0, titleRect.top - popupRect.top) + 'px';
|
|
|
+ clone.style.width = '100%';
|
|
|
+ clone.style.pointerEvents = 'none';
|
|
|
+ clone.style.willChange = 'transform, opacity';
|
|
|
+ popup.appendChild(clone);
|
|
|
+ } catch {}
|
|
|
+
|
|
|
+ titleEl.textContent = nextTitle;
|
|
|
+ try {
|
|
|
+ titleEl.style.willChange = 'transform, opacity';
|
|
|
+ titleEl.style.transform = `translateX(${enterFrom}%)`;
|
|
|
+ titleEl.style.opacity = '0';
|
|
|
+ } catch {}
|
|
|
+
|
|
|
+ if (titleEl.animate && clone.animate) {
|
|
|
+ const inAnim = titleEl.animate(
|
|
|
+ [
|
|
|
+ { transform: `translateX(${enterFrom}%)`, opacity: 0.2 },
|
|
|
+ { transform: 'translateX(0%)', opacity: 1 }
|
|
|
+ ],
|
|
|
+ { duration: slideDuration, easing: 'cubic-bezier(0.2, 0.9, 0.2, 1)', fill: 'forwards' }
|
|
|
+ );
|
|
|
+ const outAnim = clone.animate(
|
|
|
+ [
|
|
|
+ { transform: 'translateX(0%)', opacity: 1 },
|
|
|
+ { transform: `translateX(${exitTo}%)`, opacity: 0.1 }
|
|
|
+ ],
|
|
|
+ { duration: slideDuration, easing: 'cubic-bezier(0.2, 0.9, 0.2, 1)', fill: 'forwards' }
|
|
|
+ );
|
|
|
+ titleAnim = Promise.all([
|
|
|
+ inAnim.finished ? inAnim.finished.catch(() => {}) : Promise.resolve(),
|
|
|
+ outAnim.finished ? outAnim.finished.catch(() => {}) : Promise.resolve()
|
|
|
+ ]);
|
|
|
+ } else {
|
|
|
+ const ease = 'cubic-bezier(0.2, 0.9, 0.2, 1)';
|
|
|
+ const transition = `transform ${slideDuration}ms ${ease}, opacity ${slideDuration}ms ${ease}`;
|
|
|
+ try { titleEl.style.transition = transition; } catch {}
|
|
|
+ try { clone.style.transition = transition; } catch {}
|
|
|
+ requestAnimationFrame(() => {
|
|
|
+ try {
|
|
|
+ titleEl.style.transform = 'translateX(0%)';
|
|
|
+ titleEl.style.opacity = '1';
|
|
|
+ clone.style.transform = `translateX(${exitTo}%)`;
|
|
|
+ clone.style.opacity = '0.1';
|
|
|
+ } catch {}
|
|
|
+ });
|
|
|
+ titleAnim = new Promise((resolve) => setTimeout(resolve, slideDuration + 40));
|
|
|
+ }
|
|
|
+
|
|
|
+ titleCleanup = () => {
|
|
|
+ try { if (clone && clone.parentNode) clone.parentNode.removeChild(clone); } catch {}
|
|
|
+ try { titleEl.style.willChange = ''; } catch {}
|
|
|
+ try { titleEl.style.transition = ''; } catch {}
|
|
|
+ try { titleEl.style.transform = ''; } catch {}
|
|
|
+ try { titleEl.style.opacity = ''; } catch {}
|
|
|
+ };
|
|
|
+ } else {
|
|
|
+ titleEl.textContent = nextTitle;
|
|
|
+ }
|
|
|
+ }
|
|
|
} catch {}
|
|
|
|
|
|
const nextBgSpec = this._normalizeBackgroundSpec(this.params && this.params.background);
|
|
|
@@ -2051,11 +2133,6 @@
|
|
|
|
|
|
this._updateFlowActions();
|
|
|
|
|
|
- // Switch panes with directional slide (next: left, prev: right)
|
|
|
- const isNext = direction !== 'prev';
|
|
|
- const enterFrom = isNext ? 100 : -100;
|
|
|
- const exitTo = isNext ? -100 : 100;
|
|
|
-
|
|
|
// Prepare panes for animation
|
|
|
toPane.style.display = '';
|
|
|
toPane.style.position = 'absolute';
|
|
|
@@ -2078,16 +2155,19 @@
|
|
|
content.style.height = oldH + 'px';
|
|
|
content.style.overflow = 'hidden';
|
|
|
|
|
|
- const slideDuration = 320;
|
|
|
let heightAnim = null;
|
|
|
let fromAnim = null;
|
|
|
let toAnim = null;
|
|
|
+ let slideCleanup = null;
|
|
|
try {
|
|
|
heightAnim = content.animate(
|
|
|
[{ height: oldH + 'px' }, { height: newH + 'px' }],
|
|
|
{ duration: 220, easing: 'cubic-bezier(0.2, 0.9, 0.2, 1)', fill: 'forwards' }
|
|
|
);
|
|
|
} catch {}
|
|
|
+ // Ensure the new pane is in layout before starting slide (fixes missing transitions on some browsers).
|
|
|
+ try { void toPane.offsetWidth; } catch {}
|
|
|
+ await new Promise((resolve) => requestAnimationFrame(resolve));
|
|
|
try {
|
|
|
if (fromPane.animate && toPane.animate) {
|
|
|
fromAnim = fromPane.animate(
|
|
|
@@ -2104,6 +2184,28 @@
|
|
|
],
|
|
|
{ duration: slideDuration, easing: 'cubic-bezier(0.2, 0.9, 0.2, 1)', fill: 'forwards' }
|
|
|
);
|
|
|
+ } else {
|
|
|
+ const ease = 'cubic-bezier(0.2, 0.9, 0.2, 1)';
|
|
|
+ const transition = `transform ${slideDuration}ms ${ease}, opacity ${slideDuration}ms ${ease}`;
|
|
|
+ fromPane.style.transition = transition;
|
|
|
+ toPane.style.transition = transition;
|
|
|
+ slideCleanup = () => {
|
|
|
+ try { fromPane.style.transition = ''; } catch {}
|
|
|
+ try { toPane.style.transition = ''; } catch {}
|
|
|
+ };
|
|
|
+ // Force a layout so transitions apply reliably.
|
|
|
+ void fromPane.offsetWidth;
|
|
|
+ requestAnimationFrame(() => {
|
|
|
+ try {
|
|
|
+ fromPane.style.transform = `translateX(${exitTo}%)`;
|
|
|
+ fromPane.style.opacity = '0.1';
|
|
|
+ toPane.style.transform = 'translateX(0%)';
|
|
|
+ toPane.style.opacity = '1';
|
|
|
+ } catch {}
|
|
|
+ });
|
|
|
+ const fallback = new Promise((resolve) => setTimeout(resolve, slideDuration + 40));
|
|
|
+ fromAnim = fallback;
|
|
|
+ toAnim = fallback;
|
|
|
}
|
|
|
} catch {}
|
|
|
|
|
|
@@ -2112,9 +2214,12 @@
|
|
|
heightAnim && heightAnim.finished ? heightAnim.finished.catch(() => {}) : Promise.resolve(),
|
|
|
fromAnim && fromAnim.finished ? fromAnim.finished.catch(() => {}) : Promise.resolve(),
|
|
|
toAnim && toAnim.finished ? toAnim.finished.catch(() => {}) : Promise.resolve(),
|
|
|
- bgTransition && typeof bgTransition.then === 'function' ? bgTransition.catch(() => {}) : Promise.resolve()
|
|
|
+ bgTransition && typeof bgTransition.then === 'function' ? bgTransition.catch(() => {}) : Promise.resolve(),
|
|
|
+ titleAnim && typeof titleAnim.then === 'function' ? titleAnim.catch(() => {}) : Promise.resolve()
|
|
|
]);
|
|
|
} catch {}
|
|
|
+ try { if (titleCleanup) titleCleanup(); } catch {}
|
|
|
+ try { if (slideCleanup) slideCleanup(); } catch {}
|
|
|
|
|
|
// Cleanup styles and hide old pane
|
|
|
fromPane.style.display = 'none';
|