|
|
@@ -460,6 +460,77 @@
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
+ _clearPopup() {
|
|
|
+ const popup = this.dom && this.dom.popup;
|
|
|
+ if (!popup) return null;
|
|
|
+ while (popup.firstChild) popup.removeChild(popup.firstChild);
|
|
|
+ return popup;
|
|
|
+ }
|
|
|
+
|
|
|
+ _bindOverlayClick(handler) {
|
|
|
+ const overlay = this.dom && this.dom.overlay;
|
|
|
+ if (!overlay) return;
|
|
|
+ try {
|
|
|
+ if (overlay._layerOnClick) {
|
|
|
+ overlay.removeEventListener('click', overlay._layerOnClick);
|
|
|
+ }
|
|
|
+ } catch {}
|
|
|
+ overlay._layerOnClick = null;
|
|
|
+ if (!handler) return;
|
|
|
+ overlay._layerOnClick = handler;
|
|
|
+ overlay.addEventListener('click', handler);
|
|
|
+ }
|
|
|
+
|
|
|
+ _mountOverlay() {
|
|
|
+ const overlay = this.dom && this.dom.overlay;
|
|
|
+ if (!overlay) return;
|
|
|
+ // Avoid first-open overlay "flash": wait for CSS before inserting into DOM,
|
|
|
+ // then show on a clean frame so opacity transitions are smooth.
|
|
|
+ const ready = this._cssReady && typeof this._cssReady.then === 'function' ? this._cssReady : Promise.resolve();
|
|
|
+ ready.then(() => {
|
|
|
+ try { overlay.style.visibility = ''; } catch {}
|
|
|
+ if (!overlay.parentNode) {
|
|
|
+ document.body.appendChild(overlay);
|
|
|
+ }
|
|
|
+ // Double-rAF gives the browser a chance to apply styles before animating.
|
|
|
+ requestAnimationFrame(() => {
|
|
|
+ requestAnimationFrame(() => {
|
|
|
+ overlay.classList.add('show');
|
|
|
+ this._didOpen();
|
|
|
+ });
|
|
|
+ });
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ _applyPopupBasics(options, { forceBackground = false } = {}) {
|
|
|
+ this._applyPopupSize(options);
|
|
|
+ this._applyPopupTheme(options);
|
|
|
+ this._applyBackgroundForOptions(options, { force: forceBackground });
|
|
|
+ }
|
|
|
+
|
|
|
+ _buildActions({ flow = false } = {}) {
|
|
|
+ this.dom.actions = el('div', `${PREFIX}actions`);
|
|
|
+ this.dom.popup.appendChild(this.dom.actions);
|
|
|
+ if (flow) {
|
|
|
+ this._updateFlowActions();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Cancel Button
|
|
|
+ if (this.params.showCancelButton) {
|
|
|
+ this.dom.cancelBtn = el('button', `${PREFIX}button ${PREFIX}cancel`, '');
|
|
|
+ this._setButtonContent(this.dom.cancelBtn, this.params.cancelButtonText);
|
|
|
+ this.dom.cancelBtn.onclick = () => this._handleCancel();
|
|
|
+ this.dom.actions.appendChild(this.dom.cancelBtn);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Confirm Button
|
|
|
+ this.dom.confirmBtn = el('button', `${PREFIX}button ${PREFIX}confirm`, '');
|
|
|
+ this._setButtonContent(this.dom.confirmBtn, this.params.confirmButtonText);
|
|
|
+ this.dom.confirmBtn.onclick = () => this._handleConfirm();
|
|
|
+ this.dom.actions.appendChild(this.dom.confirmBtn);
|
|
|
+ }
|
|
|
+
|
|
|
_render(meta = null) {
|
|
|
this._destroySvgAniIcon();
|
|
|
// Remove existing if any (but first, try to restore any mounted DOM from the previous instance)
|
|
|
@@ -515,11 +586,7 @@
|
|
|
// Create Popup
|
|
|
this.dom.popup = el('div', `${PREFIX}popup`);
|
|
|
this.dom.overlay.appendChild(this.dom.popup);
|
|
|
- this._applyPopupSize(this.params);
|
|
|
- this._applyPopupTheme(this.params);
|
|
|
-
|
|
|
- // Background (full popup)
|
|
|
- this._applyBackgroundForOptions(this.params, { force: true });
|
|
|
+ this._applyPopupBasics(this.params, { forceBackground: true });
|
|
|
|
|
|
// Flow mode: pre-mount all steps and switch by hide/show (DOM continuity, less jitter)
|
|
|
if (meta && meta.flow) {
|
|
|
@@ -557,63 +624,18 @@
|
|
|
}
|
|
|
|
|
|
// Actions
|
|
|
- this.dom.actions = el('div', `${PREFIX}actions`);
|
|
|
-
|
|
|
- // Cancel Button
|
|
|
- if (this.params.showCancelButton) {
|
|
|
- this.dom.cancelBtn = el('button', `${PREFIX}button ${PREFIX}cancel`, '');
|
|
|
- this._setButtonContent(this.dom.cancelBtn, this.params.cancelButtonText);
|
|
|
- this.dom.cancelBtn.onclick = () => this._handleCancel();
|
|
|
- this.dom.actions.appendChild(this.dom.cancelBtn);
|
|
|
- }
|
|
|
-
|
|
|
- // Confirm Button
|
|
|
- this.dom.confirmBtn = el('button', `${PREFIX}button ${PREFIX}confirm`, '');
|
|
|
- this._setButtonContent(this.dom.confirmBtn, this.params.confirmButtonText);
|
|
|
- this.dom.confirmBtn.onclick = () => this._handleConfirm();
|
|
|
- this.dom.actions.appendChild(this.dom.confirmBtn);
|
|
|
-
|
|
|
- this.dom.popup.appendChild(this.dom.actions);
|
|
|
+ this._buildActions();
|
|
|
|
|
|
// Event Listeners
|
|
|
if (this.params.closeOnClickOutside) {
|
|
|
- const onClick = (e) => {
|
|
|
- if (e.target === this.dom.overlay) {
|
|
|
- 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);
|
|
|
+ this._bindOverlayClick((e) => {
|
|
|
+ if (e.target === this.dom.overlay) this._close(null); // Dismiss
|
|
|
+ });
|
|
|
} else {
|
|
|
- try {
|
|
|
- if (this.dom.overlay._layerOnClick) {
|
|
|
- this.dom.overlay.removeEventListener('click', this.dom.overlay._layerOnClick);
|
|
|
- this.dom.overlay._layerOnClick = null;
|
|
|
- }
|
|
|
- } catch {}
|
|
|
+ this._bindOverlayClick(null);
|
|
|
}
|
|
|
|
|
|
- // Avoid first-open overlay "flash": wait for CSS before inserting into DOM,
|
|
|
- // then show on a clean frame so opacity transitions are smooth.
|
|
|
- const ready = this._cssReady && typeof this._cssReady.then === 'function' ? this._cssReady : Promise.resolve();
|
|
|
- ready.then(() => {
|
|
|
- try { this.dom.overlay.style.visibility = ''; } catch {}
|
|
|
- if (!this.dom.overlay.parentNode) {
|
|
|
- document.body.appendChild(this.dom.overlay);
|
|
|
- }
|
|
|
- // Double-rAF gives the browser a chance to apply styles before animating.
|
|
|
- requestAnimationFrame(() => {
|
|
|
- requestAnimationFrame(() => {
|
|
|
- this.dom.overlay.classList.add('show');
|
|
|
- this._didOpen();
|
|
|
- });
|
|
|
- });
|
|
|
- });
|
|
|
+ this._mountOverlay();
|
|
|
}
|
|
|
|
|
|
_renderFlowUI() {
|
|
|
@@ -623,13 +645,8 @@
|
|
|
if (!popup) return;
|
|
|
|
|
|
// Clear popup
|
|
|
- while (popup.firstChild) popup.removeChild(popup.firstChild);
|
|
|
-
|
|
|
- this._applyPopupSize(this.params);
|
|
|
- this._applyPopupTheme(this.params);
|
|
|
-
|
|
|
- // Background (full popup)
|
|
|
- this._applyBackgroundForOptions(this.params, { force: true });
|
|
|
+ this._clearPopup();
|
|
|
+ this._applyPopupBasics(this.params, { forceBackground: true });
|
|
|
|
|
|
// Icon (in flow: suppressed by default unless question or summary)
|
|
|
this.dom.icon = null;
|
|
|
@@ -676,48 +693,18 @@
|
|
|
popup.appendChild(this.dom.content);
|
|
|
|
|
|
// Actions
|
|
|
- this.dom.actions = el('div', `${PREFIX}actions`);
|
|
|
- popup.appendChild(this.dom.actions);
|
|
|
- this._updateFlowActions();
|
|
|
+ this._buildActions({ flow: true });
|
|
|
|
|
|
// Event Listeners
|
|
|
if (this.params.closeOnClickOutside) {
|
|
|
- const onClick = (e) => {
|
|
|
- if (e.target === this.dom.overlay) {
|
|
|
- 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);
|
|
|
+ this._bindOverlayClick((e) => {
|
|
|
+ if (e.target === this.dom.overlay) this._close(null, 'backdrop');
|
|
|
+ });
|
|
|
} else {
|
|
|
- try {
|
|
|
- if (this.dom.overlay._layerOnClick) {
|
|
|
- this.dom.overlay.removeEventListener('click', this.dom.overlay._layerOnClick);
|
|
|
- this.dom.overlay._layerOnClick = null;
|
|
|
- }
|
|
|
- } catch {}
|
|
|
+ this._bindOverlayClick(null);
|
|
|
}
|
|
|
|
|
|
- // Avoid first-open overlay "flash": wait for CSS before inserting into DOM,
|
|
|
- // then show on a clean frame so opacity transitions are smooth.
|
|
|
- const ready = this._cssReady && typeof this._cssReady.then === 'function' ? this._cssReady : Promise.resolve();
|
|
|
- ready.then(() => {
|
|
|
- try { this.dom.overlay.style.visibility = ''; } catch {}
|
|
|
- if (!this.dom.overlay.parentNode) {
|
|
|
- document.body.appendChild(this.dom.overlay);
|
|
|
- }
|
|
|
- requestAnimationFrame(() => {
|
|
|
- requestAnimationFrame(() => {
|
|
|
- this.dom.overlay.classList.add('show');
|
|
|
- this._didOpen();
|
|
|
- });
|
|
|
- });
|
|
|
- });
|
|
|
+ this._mountOverlay();
|
|
|
}
|
|
|
|
|
|
_updateFlowActions() {
|
|
|
@@ -2141,11 +2128,9 @@
|
|
|
_rerenderInside() {
|
|
|
this._destroySvgAniIcon();
|
|
|
// Update popup content without recreating overlay (used in flow transitions)
|
|
|
- const popup = this.dom && this.dom.popup;
|
|
|
+ const popup = this._clearPopup();
|
|
|
if (!popup) return;
|
|
|
|
|
|
- // Clear popup (but keep reference)
|
|
|
- while (popup.firstChild) popup.removeChild(popup.firstChild);
|
|
|
this._applyPopupTheme(this.params);
|
|
|
|
|
|
// Icon
|