|
@@ -124,6 +124,7 @@
|
|
|
this._onKeydown = null;
|
|
this._onKeydown = null;
|
|
|
this._mounted = null; // { kind, originalEl, placeholder, parent, nextSibling, prevHidden, prevInlineDisplay }
|
|
this._mounted = null; // { kind, originalEl, placeholder, parent, nextSibling, prevHidden, prevInlineDisplay }
|
|
|
this._isClosing = false;
|
|
this._isClosing = false;
|
|
|
|
|
+ this._isReplace = false;
|
|
|
|
|
|
|
|
// Step/flow support
|
|
// Step/flow support
|
|
|
this._flowSteps = null; // Array<options>
|
|
this._flowSteps = null; // Array<options>
|
|
@@ -273,18 +274,54 @@
|
|
|
_render(meta = null) {
|
|
_render(meta = null) {
|
|
|
// Remove existing if any (but first, try to restore any mounted DOM from the previous instance)
|
|
// Remove existing if any (but first, try to restore any mounted DOM from the previous instance)
|
|
|
const existing = document.querySelector(`.${PREFIX}overlay`);
|
|
const existing = document.querySelector(`.${PREFIX}overlay`);
|
|
|
- if (existing) {
|
|
|
|
|
|
|
+ const wantReplace = !!(this.params && this.params.replace);
|
|
|
|
|
+ this._isReplace = false;
|
|
|
|
|
+ if (existing && wantReplace) {
|
|
|
try {
|
|
try {
|
|
|
const prev = existing._layerInstance;
|
|
const prev = existing._layerInstance;
|
|
|
if (prev && typeof prev._forceDestroy === 'function') prev._forceDestroy('replace');
|
|
if (prev && typeof prev._forceDestroy === 'function') prev._forceDestroy('replace');
|
|
|
} catch {}
|
|
} catch {}
|
|
|
- try { existing.remove(); } catch {}
|
|
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (existing._layerCloseTimer) {
|
|
|
|
|
+ clearTimeout(existing._layerCloseTimer);
|
|
|
|
|
+ existing._layerCloseTimer = null;
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch {}
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (existing._layerOnClick) {
|
|
|
|
|
+ existing.removeEventListener('click', existing._layerOnClick);
|
|
|
|
|
+ existing._layerOnClick = null;
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch {}
|
|
|
|
|
+ try {
|
|
|
|
|
+ while (existing.firstChild) existing.removeChild(existing.firstChild);
|
|
|
|
|
+ } catch {}
|
|
|
|
|
+ try {
|
|
|
|
|
+ existing.style.visibility = '';
|
|
|
|
|
+ existing.classList.add('show');
|
|
|
|
|
+ // Reset any inline fade state from the previous close
|
|
|
|
|
+ existing.style.transition = 'none';
|
|
|
|
|
+ existing.style.opacity = '1';
|
|
|
|
|
+ requestAnimationFrame(() => {
|
|
|
|
|
+ try { existing.style.transition = ''; } catch {}
|
|
|
|
|
+ });
|
|
|
|
|
+ } catch {}
|
|
|
|
|
+ this.dom.overlay = existing;
|
|
|
|
|
+ this.dom.overlay._layerInstance = this;
|
|
|
|
|
+ this._isReplace = true;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ if (existing) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const prev = existing._layerInstance;
|
|
|
|
|
+ if (prev && typeof prev._forceDestroy === 'function') prev._forceDestroy('replace');
|
|
|
|
|
+ } catch {}
|
|
|
|
|
+ try { existing.remove(); } catch {}
|
|
|
|
|
+ }
|
|
|
|
|
+ // Create Overlay
|
|
|
|
|
+ this.dom.overlay = el('div', `${PREFIX}overlay`);
|
|
|
|
|
+ this.dom.overlay._layerInstance = this;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // Create Overlay
|
|
|
|
|
- this.dom.overlay = el('div', `${PREFIX}overlay`);
|
|
|
|
|
- this.dom.overlay._layerInstance = this;
|
|
|
|
|
-
|
|
|
|
|
// Create Popup
|
|
// Create Popup
|
|
|
this.dom.popup = el('div', `${PREFIX}popup`);
|
|
this.dom.popup = el('div', `${PREFIX}popup`);
|
|
|
this.dom.overlay.appendChild(this.dom.popup);
|
|
this.dom.overlay.appendChild(this.dom.popup);
|
|
@@ -345,11 +382,25 @@
|
|
|
|
|
|
|
|
// Event Listeners
|
|
// Event Listeners
|
|
|
if (this.params.closeOnClickOutside) {
|
|
if (this.params.closeOnClickOutside) {
|
|
|
- this.dom.overlay.addEventListener('click', (e) => {
|
|
|
|
|
|
|
+ const onClick = (e) => {
|
|
|
if (e.target === this.dom.overlay) {
|
|
if (e.target === this.dom.overlay) {
|
|
|
this._close(null); // Dismiss
|
|
this._close(null); // Dismiss
|
|
|
}
|
|
}
|
|
|
- });
|
|
|
|
|
|
|
+ };
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (this.dom.overlay._layerOnClick) {
|
|
|
|
|
+ this.dom.overlay.removeEventListener('click', this.dom.overlay._layerOnClick);
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch {}
|
|
|
|
|
+ this.dom.overlay._layerOnClick = onClick;
|
|
|
|
|
+ this.dom.overlay.addEventListener('click', onClick);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (this.dom.overlay._layerOnClick) {
|
|
|
|
|
+ this.dom.overlay.removeEventListener('click', this.dom.overlay._layerOnClick);
|
|
|
|
|
+ this.dom.overlay._layerOnClick = null;
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch {}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Avoid first-open overlay "flash": wait for CSS before inserting into DOM,
|
|
// Avoid first-open overlay "flash": wait for CSS before inserting into DOM,
|
|
@@ -429,11 +480,25 @@
|
|
|
|
|
|
|
|
// Event Listeners
|
|
// Event Listeners
|
|
|
if (this.params.closeOnClickOutside) {
|
|
if (this.params.closeOnClickOutside) {
|
|
|
- this.dom.overlay.addEventListener('click', (e) => {
|
|
|
|
|
|
|
+ const onClick = (e) => {
|
|
|
if (e.target === this.dom.overlay) {
|
|
if (e.target === this.dom.overlay) {
|
|
|
this._close(null, 'backdrop');
|
|
this._close(null, 'backdrop');
|
|
|
}
|
|
}
|
|
|
- });
|
|
|
|
|
|
|
+ };
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (this.dom.overlay._layerOnClick) {
|
|
|
|
|
+ this.dom.overlay.removeEventListener('click', this.dom.overlay._layerOnClick);
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch {}
|
|
|
|
|
+ this.dom.overlay._layerOnClick = onClick;
|
|
|
|
|
+ this.dom.overlay.addEventListener('click', onClick);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (this.dom.overlay._layerOnClick) {
|
|
|
|
|
+ this.dom.overlay.removeEventListener('click', this.dom.overlay._layerOnClick);
|
|
|
|
|
+ this.dom.overlay._layerOnClick = null;
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch {}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Avoid first-open overlay "flash": wait for CSS before inserting into DOM,
|
|
// Avoid first-open overlay "flash": wait for CSS before inserting into DOM,
|
|
@@ -687,6 +752,25 @@
|
|
|
} catch {}
|
|
} catch {}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+ // Replace mode: if popupAnimation is off, do a soft fade+scale to avoid a hard cut.
|
|
|
|
|
+ if (!this.params.popupAnimation && this._isReplace && this.dom.popup) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const popup = this.dom.popup;
|
|
|
|
|
+ popup.style.transition = 'none';
|
|
|
|
|
+ popup.style.opacity = '0';
|
|
|
|
|
+ popup.style.transform = 'scale(0.98)';
|
|
|
|
|
+ requestAnimationFrame(() => {
|
|
|
|
|
+ popup.style.transition = 'opacity 0.22s ease, transform 0.22s ease';
|
|
|
|
|
+ popup.style.opacity = '1';
|
|
|
|
|
+ popup.style.transform = 'scale(1)';
|
|
|
|
|
+ setTimeout(() => {
|
|
|
|
|
+ try { popup.style.transition = ''; } catch {}
|
|
|
|
|
+ try { popup.style.opacity = ''; } catch {}
|
|
|
|
|
+ try { popup.style.transform = ''; } catch {}
|
|
|
|
|
+ }, 260);
|
|
|
|
|
+ });
|
|
|
|
|
+ } catch {}
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
// Icon SVG draw animation
|
|
// Icon SVG draw animation
|
|
|
if (this.params.iconAnimation) {
|
|
if (this.params.iconAnimation) {
|
|
@@ -759,7 +843,10 @@
|
|
|
|
|
|
|
|
_forceDestroy(reason = 'replace') {
|
|
_forceDestroy(reason = 'replace') {
|
|
|
// Restore mounted DOM (if any) and cleanup listeners; also resolve the promise so it doesn't hang.
|
|
// Restore mounted DOM (if any) and cleanup listeners; also resolve the promise so it doesn't hang.
|
|
|
- try { this._unmountDomContent(); } catch {}
|
|
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (this._flowSteps && this._flowSteps.length) this._unmountFlowMounted();
|
|
|
|
|
+ else this._unmountDomContent();
|
|
|
|
|
+ } catch {}
|
|
|
if (this._onKeydown) {
|
|
if (this._onKeydown) {
|
|
|
try { document.removeEventListener('keydown', this._onKeydown); } catch {}
|
|
try { document.removeEventListener('keydown', this._onKeydown); } catch {}
|
|
|
this._onKeydown = null;
|
|
this._onKeydown = null;
|
|
@@ -775,18 +862,65 @@
|
|
|
if (this._isClosing) return;
|
|
if (this._isClosing) return;
|
|
|
this._isClosing = true;
|
|
this._isClosing = true;
|
|
|
|
|
|
|
|
- // Restore mounted DOM (moved into popup) before removing overlay
|
|
|
|
|
- try {
|
|
|
|
|
- if (this._flowSteps && this._flowSteps.length) this._unmountFlowMounted();
|
|
|
|
|
- else this._unmountDomContent();
|
|
|
|
|
- } catch {}
|
|
|
|
|
|
|
+ const shouldDelayUnmount = !!(
|
|
|
|
|
+ (this._mounted && this._mounted.kind === 'move') ||
|
|
|
|
|
+ (this._flowSteps && this._flowSteps.length && this._flowMountedList && this._flowMountedList.length)
|
|
|
|
|
+ ) || !this.params.popupAnimation;
|
|
|
|
|
+ const doUnmount = () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (this._flowSteps && this._flowSteps.length) this._unmountFlowMounted();
|
|
|
|
|
+ else this._unmountDomContent();
|
|
|
|
|
+ } catch {}
|
|
|
|
|
+ };
|
|
|
|
|
+ if (!shouldDelayUnmount) {
|
|
|
|
|
+ // Restore mounted DOM (moved into popup) before removing overlay
|
|
|
|
|
+ doUnmount();
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- this.dom.overlay.classList.remove('show');
|
|
|
|
|
- setTimeout(() => {
|
|
|
|
|
- if (this.dom.overlay && this.dom.overlay.parentNode) {
|
|
|
|
|
- this.dom.overlay.parentNode.removeChild(this.dom.overlay);
|
|
|
|
|
|
|
+ let customClose = false;
|
|
|
|
|
+ // Soft close for non-popupAnimation cases (avoid abrupt cut)
|
|
|
|
|
+ if (!this.params.popupAnimation) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const popup = this.dom.popup;
|
|
|
|
|
+ if (popup) {
|
|
|
|
|
+ popup.style.transition = 'opacity 0.22s ease';
|
|
|
|
|
+ popup.style.opacity = '0';
|
|
|
|
|
+ popup.style.transform = '';
|
|
|
|
|
+ }
|
|
|
|
|
+ if (this.dom.overlay) {
|
|
|
|
|
+ const overlay = this.dom.overlay;
|
|
|
|
|
+ // Use inline opacity to avoid class-based jumps
|
|
|
|
|
+ overlay.style.transition = 'opacity 0.22s ease';
|
|
|
|
|
+ overlay.style.opacity = '1';
|
|
|
|
|
+ requestAnimationFrame(() => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (overlay._layerInstance !== this) return;
|
|
|
|
|
+ overlay.style.opacity = '0';
|
|
|
|
|
+ } catch {}
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ customClose = true;
|
|
|
|
|
+ } catch {}
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!customClose) {
|
|
|
|
|
+ this.dom.overlay.classList.remove('show');
|
|
|
|
|
+ }
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (this.dom.overlay) {
|
|
|
|
|
+ const overlay = this.dom.overlay;
|
|
|
|
|
+ const delay = customClose ? 240 : 300;
|
|
|
|
|
+ overlay._layerCloseTimer = setTimeout(() => {
|
|
|
|
|
+ if (shouldDelayUnmount) {
|
|
|
|
|
+ doUnmount();
|
|
|
|
|
+ }
|
|
|
|
|
+ if (overlay._layerInstance !== this) return;
|
|
|
|
|
+ if (overlay.parentNode) {
|
|
|
|
|
+ overlay.parentNode.removeChild(overlay);
|
|
|
|
|
+ }
|
|
|
|
|
+ }, delay);
|
|
|
}
|
|
}
|
|
|
- }, 300);
|
|
|
|
|
|
|
+ } catch {}
|
|
|
|
|
|
|
|
if (this._onKeydown) {
|
|
if (this._onKeydown) {
|
|
|
try { document.removeEventListener('keydown', this._onKeydown); } catch {}
|
|
try { document.removeEventListener('keydown', this._onKeydown); } catch {}
|