|
|
@@ -1,7 +1,5 @@
|
|
|
(function (global, factory) {
|
|
|
const LayerClass = factory();
|
|
|
- // Allow usage as `Layer({...})` or `new Layer()`
|
|
|
- // But Layer is a class. We can wrap it in a proxy or factory function.
|
|
|
|
|
|
function LayerFactory(options) {
|
|
|
if (options && typeof options === 'object') {
|
|
|
@@ -10,8 +8,6 @@
|
|
|
return new LayerClass();
|
|
|
}
|
|
|
|
|
|
- // Copy static methods (including non-enumerable class statics like `fire` / `$`)
|
|
|
- // Class static methods are non-enumerable by default, so Object.assign() would miss them.
|
|
|
const copyStatic = (to, from) => {
|
|
|
try {
|
|
|
Object.getOwnPropertyNames(from).forEach((k) => {
|
|
|
@@ -21,12 +17,10 @@
|
|
|
Object.defineProperty(to, k, desc);
|
|
|
});
|
|
|
} catch (e) {
|
|
|
- // Best-effort fallback
|
|
|
try { Object.assign(to, from); } catch {}
|
|
|
}
|
|
|
};
|
|
|
copyStatic(LayerFactory, LayerClass);
|
|
|
- // Also copy prototype for instanceof checks if needed (though tricky with factory)
|
|
|
LayerFactory.prototype = LayerClass.prototype;
|
|
|
|
|
|
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = LayerFactory :
|
|
|
@@ -66,10 +60,7 @@
|
|
|
|
|
|
const svgEl = (tag) => document.createElementNS('http://www.w3.org/2000/svg', tag);
|
|
|
|
|
|
- // Ensure library CSS is loaded (xjs.css)
|
|
|
- // - CSS is centralized in xjs.css (no runtime <style> injection).
|
|
|
- // - We still auto-load it for convenience/compat, since consumers may forget the <link>.
|
|
|
- let _xjsCssReady = null; // Promise<void>
|
|
|
+ let _xjsCssReady = null;
|
|
|
const waitForStylesheet = (link) => new Promise((resolve) => {
|
|
|
if (!link) return resolve();
|
|
|
if (link.sheet) return resolve();
|
|
|
@@ -81,7 +72,6 @@
|
|
|
};
|
|
|
try { link.addEventListener('load', finish, { once: true }); } catch {}
|
|
|
try { link.addEventListener('error', finish, { once: true }); } catch {}
|
|
|
- // Fallback timeout: avoid blocking forever if the load event is missed.
|
|
|
setTimeout(finish, 200);
|
|
|
});
|
|
|
const ensureXjsCss = () => {
|
|
|
@@ -118,12 +108,10 @@
|
|
|
document.head.appendChild(link);
|
|
|
return _xjsCssReady;
|
|
|
} catch {
|
|
|
- // ignore
|
|
|
return Promise.resolve();
|
|
|
}
|
|
|
};
|
|
|
|
|
|
- // SvgAni (svg.js) lazy loader for "svg:*" icon type
|
|
|
let _svgAniReady = null;
|
|
|
let _svgAniPrefetchScheduled = false;
|
|
|
const isSvgAniIcon = (icon) => {
|
|
|
@@ -243,57 +231,44 @@
|
|
|
this.resolve = null;
|
|
|
this.reject = null;
|
|
|
this._onKeydown = null;
|
|
|
- this._mounted = null; // { kind, originalEl, placeholder, parent, nextSibling, prevHidden, prevInlineDisplay }
|
|
|
+ this._mounted = null;
|
|
|
this._isClosing = false;
|
|
|
this._isReplace = false;
|
|
|
|
|
|
- // Step/flow support
|
|
|
- this._flowSteps = null; // Array<options>
|
|
|
+ this._flowSteps = null;
|
|
|
this._flowIndex = 0;
|
|
|
this._flowValues = [];
|
|
|
- this._flowBase = null; // base options merged into each step
|
|
|
- this._flowResolved = null; // resolved merged steps
|
|
|
- this._flowMountedList = []; // mounted DOM records for move-mode steps
|
|
|
- this._flowAutoForm = null; // { enabled: true } when using step container shorthand
|
|
|
+ this._flowBase = null;
|
|
|
+ this._flowResolved = null;
|
|
|
+ this._flowMountedList = [];
|
|
|
+ this._flowAutoForm = null;
|
|
|
this._backgroundSpec = null;
|
|
|
}
|
|
|
|
|
|
- // Constructor helper when called as function: const popup = Layer({...})
|
|
|
static get isProxy() { return true; }
|
|
|
|
|
|
- // Static entry point
|
|
|
static run(options) {
|
|
|
const instance = new Layer();
|
|
|
return instance.run(options);
|
|
|
}
|
|
|
- // Chainable entry point (builder-style)
|
|
|
- // Example:
|
|
|
- // Layer.$({ title: 'Hi' }).run().then(...)
|
|
|
- // Layer.$().config({ title: 'Hi' }).run()
|
|
|
static $(options) {
|
|
|
const instance = new Layer();
|
|
|
if (options !== undefined) instance.config(options);
|
|
|
return instance;
|
|
|
}
|
|
|
|
|
|
- // Chainable config helper (does not render until `.run()` is called)
|
|
|
config(options = {}) {
|
|
|
- // Support the same shorthand as Layer.run(title, text, icon)
|
|
|
options = normalizeOptions(options, arguments[1], arguments[2]);
|
|
|
this.params = { ...(this.params || {}), ...options };
|
|
|
return this;
|
|
|
}
|
|
|
|
|
|
- // Add a single step (chainable)
|
|
|
- // Usage:
|
|
|
- // Layer.$().step({ title:'A', dom:'#step1' }).step({ title:'B', dom:'#step2' }).run()
|
|
|
step(options = {}) {
|
|
|
if (!this._flowSteps) this._flowSteps = [];
|
|
|
this._flowSteps.push(normalizeOptions(options, arguments[1], arguments[2]));
|
|
|
return this;
|
|
|
}
|
|
|
|
|
|
- // Add multiple steps at once (chainable)
|
|
|
steps(steps = []) {
|
|
|
if (!Array.isArray(steps)) return this;
|
|
|
if (!this._flowSteps) this._flowSteps = [];
|
|
|
@@ -301,18 +276,15 @@
|
|
|
return this;
|
|
|
}
|
|
|
|
|
|
- // Convenience static helper: Layer.flow([steps], baseOptions?)
|
|
|
static flow(steps = [], baseOptions = {}) {
|
|
|
return Layer.$(baseOptions).steps(steps).run();
|
|
|
}
|
|
|
|
|
|
- // Instance entry point (chainable)
|
|
|
run(options) {
|
|
|
const hasArgs = (options !== undefined && options !== null);
|
|
|
const normalized = hasArgs ? normalizeOptions(options, arguments[1], arguments[2]) : null;
|
|
|
const merged = hasArgs ? normalized : (this.params || {});
|
|
|
|
|
|
- // If no explicit steps yet, allow shorthand: { step: '#container', stepItem: '.item' }
|
|
|
if (!this._flowSteps || !this._flowSteps.length) {
|
|
|
const didInit = this._initFlowFromStepContainer(merged);
|
|
|
if (didInit) {
|
|
|
@@ -320,10 +292,8 @@
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // Flow mode: if configured via .step()/.steps() or step container shorthand, ignore per-call options and use steps
|
|
|
if (this._flowSteps && this._flowSteps.length) {
|
|
|
if (hasArgs) {
|
|
|
- // allow providing base options at fire-time
|
|
|
this.params = { ...(this.params || {}), ...this._stripStepOptions(merged) };
|
|
|
}
|
|
|
return this._fireFlow();
|
|
|
@@ -339,8 +309,8 @@
|
|
|
title: '',
|
|
|
text: '',
|
|
|
icon: null,
|
|
|
- iconSize: null, // e.g. '6em' / '72px'
|
|
|
- iconLoop: null, // svgAni only: true/false/null (auto)
|
|
|
+ iconSize: null,
|
|
|
+ iconLoop: null,
|
|
|
confirmButtonText: 'OK',
|
|
|
cancelButtonText: 'Cancel',
|
|
|
showCancelButton: false,
|
|
|
@@ -354,19 +324,10 @@
|
|
|
height: null,
|
|
|
iwidth: null,
|
|
|
iheight: null,
|
|
|
- nextIcon: null, // flow-only: icon for Next button
|
|
|
- prevIcon: null, // flow-only: icon for Back button
|
|
|
- // Content:
|
|
|
- // - text: plain text
|
|
|
- // - html: innerHTML
|
|
|
- // - dom: selector / Element / <template> (preferred)
|
|
|
- // - content: backward compat for selector/Element OR advanced { dom, mode, clone }
|
|
|
+ nextIcon: null,
|
|
|
+ prevIcon: null,
|
|
|
dom: null,
|
|
|
- domMode: 'move', // 'move' (default) | 'clone'
|
|
|
- // Hook for confirming (also used in flow steps)
|
|
|
- // Return false to prevent close / next.
|
|
|
- // Return a value to be attached as `value`.
|
|
|
- // May return Promise.
|
|
|
+ domMode: 'move',
|
|
|
preConfirm: null,
|
|
|
...options
|
|
|
};
|
|
|
@@ -383,9 +344,6 @@
|
|
|
_fireFlow() {
|
|
|
this._flowIndex = 0;
|
|
|
this._flowValues = [];
|
|
|
- // In flow mode:
|
|
|
- // - keep iconAnimation off by default (avoids jitter)
|
|
|
- // - BUT allow popupAnimation by default so the first step has the same entrance feel
|
|
|
const base = { ...(this.params || {}) };
|
|
|
if (!('popupAnimation' in base)) base.popupAnimation = true;
|
|
|
if (!('iconAnimation' in base)) base.iconAnimation = false;
|
|
|
@@ -404,7 +362,6 @@
|
|
|
}
|
|
|
|
|
|
static _getXjs() {
|
|
|
- // Prefer $ (main), fallback to xjs/animal (compat)
|
|
|
const g = (typeof window !== 'undefined') ? window : (typeof globalThis !== 'undefined' ? globalThis : null);
|
|
|
if (!g) return null;
|
|
|
const x = g.$ || g.xjs || g.animal;
|
|
|
@@ -520,15 +477,12 @@
|
|
|
_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');
|
|
|
@@ -552,7 +506,6 @@
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- // Cancel Button
|
|
|
if (this.params.showCancelButton) {
|
|
|
this.dom.cancelBtn = el('button', `${PREFIX}button ${PREFIX}cancel`, '');
|
|
|
this._setButtonContent(this.dom.cancelBtn, this.params.cancelButtonText);
|
|
|
@@ -560,7 +513,6 @@
|
|
|
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();
|
|
|
@@ -569,7 +521,6 @@
|
|
|
|
|
|
_render(meta = null) {
|
|
|
this._destroySvgAniIcon();
|
|
|
- // Remove existing if any (but first, try to restore any mounted DOM from the previous instance)
|
|
|
const existing = document.querySelector(`.${PREFIX}overlay`);
|
|
|
const wantReplace = !!(this.params && this.params.replace);
|
|
|
this._isReplace = false;
|
|
|
@@ -596,7 +547,6 @@
|
|
|
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(() => {
|
|
|
@@ -614,39 +564,32 @@
|
|
|
} catch {}
|
|
|
try { existing.remove(); } catch {}
|
|
|
}
|
|
|
- // Create Overlay
|
|
|
this.dom.overlay = el('div', `${PREFIX}overlay`);
|
|
|
this.dom.overlay._layerInstance = this;
|
|
|
}
|
|
|
|
|
|
- // Create Popup
|
|
|
this.dom.popup = el('div', `${PREFIX}popup`);
|
|
|
this.dom.overlay.appendChild(this.dom.popup);
|
|
|
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) {
|
|
|
this._renderFlowUI();
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- // Icon
|
|
|
if (this.params.icon) {
|
|
|
this.dom.icon = this._createIcon(this.params.icon);
|
|
|
this.dom.popup.appendChild(this.dom.icon);
|
|
|
}
|
|
|
|
|
|
- // Title
|
|
|
if (this.params.title) {
|
|
|
this.dom.title = el('h2', `${PREFIX}title`, this.params.title);
|
|
|
this.dom.popup.appendChild(this.dom.title);
|
|
|
}
|
|
|
|
|
|
- // Content (Text / HTML / Element)
|
|
|
if (this.params.text || this.params.html || this.params.content || this.params.dom) {
|
|
|
this.dom.content = el('div', `${PREFIX}content`);
|
|
|
|
|
|
- // DOM content (preferred: dom, backward: content)
|
|
|
const domSpec = this._normalizeDomSpec(this.params);
|
|
|
if (domSpec) {
|
|
|
this._mountDomContent(domSpec);
|
|
|
@@ -659,13 +602,11 @@
|
|
|
this.dom.popup.appendChild(this.dom.content);
|
|
|
}
|
|
|
|
|
|
- // Actions
|
|
|
this._buildActions();
|
|
|
|
|
|
- // Event Listeners
|
|
|
if (this.params.closeOnClickOutside) {
|
|
|
this._bindOverlayClick((e) => {
|
|
|
- if (e.target === this.dom.overlay) this._close(null); // Dismiss
|
|
|
+ if (e.target === this.dom.overlay) this._close(null);
|
|
|
});
|
|
|
} else {
|
|
|
this._bindOverlayClick(null);
|
|
|
@@ -676,31 +617,24 @@
|
|
|
|
|
|
_renderFlowUI() {
|
|
|
this._destroySvgAniIcon();
|
|
|
- // Icon/title/content/actions are stable; steps are pre-mounted and toggled.
|
|
|
const popup = this.dom.popup;
|
|
|
if (!popup) return;
|
|
|
|
|
|
- // Clear popup
|
|
|
this._clearPopup();
|
|
|
this._applyPopupBasics(this.params, { forceBackground: true });
|
|
|
|
|
|
- // Icon (in flow: suppressed by default unless question or summary)
|
|
|
this.dom.icon = null;
|
|
|
if (this.params.icon) {
|
|
|
this.dom.icon = this._createIcon(this.params.icon);
|
|
|
popup.appendChild(this.dom.icon);
|
|
|
}
|
|
|
|
|
|
- // Title (always present for flow)
|
|
|
this.dom.title = el('h2', `${PREFIX}title`, this.params.title || '');
|
|
|
popup.appendChild(this.dom.title);
|
|
|
|
|
|
- // 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';
|
|
|
this.dom.stepStack.style.width = '100%';
|
|
|
@@ -714,7 +648,6 @@
|
|
|
pane.style.boxSizing = 'border-box';
|
|
|
pane.style.display = (i === this._flowIndex) ? '' : 'none';
|
|
|
|
|
|
- // Fill pane: dom/html/text
|
|
|
const domSpec = this._normalizeDomSpec(opt);
|
|
|
if (domSpec) {
|
|
|
this._mountDomContentInto(pane, domSpec, { collectFlow: true });
|
|
|
@@ -731,10 +664,8 @@
|
|
|
this.dom.content.appendChild(this.dom.stepStack);
|
|
|
popup.appendChild(this.dom.content);
|
|
|
|
|
|
- // Actions
|
|
|
this._buildActions({ flow: true });
|
|
|
|
|
|
- // Event Listeners
|
|
|
if (this.params.closeOnClickOutside) {
|
|
|
this._bindOverlayClick((e) => {
|
|
|
if (e.target === this.dom.overlay) this._close(null, 'backdrop');
|
|
|
@@ -785,7 +716,6 @@
|
|
|
try {
|
|
|
const s = String(this.params.iconSize).trim();
|
|
|
if (!s) return;
|
|
|
- // For SweetAlert2-style success icon, scale via font-size so all `em`-based parts remain proportional.
|
|
|
if (mode === 'font') {
|
|
|
const m = s.match(/^(-?\d*\.?\d+)\s*(px|em|rem)$/i);
|
|
|
if (m) {
|
|
|
@@ -797,14 +727,12 @@
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
- // Fallback: directly size the box (works great for SVG icons)
|
|
|
icon.style.width = s;
|
|
|
icon.style.height = s;
|
|
|
} catch {}
|
|
|
};
|
|
|
|
|
|
const appendRingParts = () => {
|
|
|
- // Use the same "success-like" ring parts for every built-in icon
|
|
|
icon.appendChild(el('div', `${PREFIX}success-circular-line-left`));
|
|
|
icon.appendChild(el('div', `${PREFIX}success-ring`));
|
|
|
icon.appendChild(el('div', `${PREFIX}success-fix`));
|
|
|
@@ -856,7 +784,6 @@
|
|
|
};
|
|
|
|
|
|
if (isSvgAniIcon(rawType)) {
|
|
|
- // SvgAni animation icon: svg:<name>
|
|
|
const name = getSvgAniIconName(rawType);
|
|
|
icon.className = `${PREFIX}icon ${PREFIX}icon-svgAni`;
|
|
|
icon.dataset.svgani = name;
|
|
|
@@ -870,12 +797,6 @@
|
|
|
}
|
|
|
|
|
|
if (rawType === 'success') {
|
|
|
- // SweetAlert2-compatible DOM structure (circle + tick) for exact style parity
|
|
|
- // <div class="...success-circular-line-left"></div>
|
|
|
- // <div class="...success-mark"><span class="...success-line-tip"></span><span class="...success-line-long"></span></div>
|
|
|
- // <div class="...success-ring"></div>
|
|
|
- // <div class="...success-fix"></div>
|
|
|
- // <div class="...success-circular-line-right"></div>
|
|
|
icon.appendChild(el('div', `${PREFIX}success-circular-line-left`));
|
|
|
const mark = el('div', `${PREFIX}success-mark`);
|
|
|
mark.appendChild(el('span', `${PREFIX}success-line-tip`));
|
|
|
@@ -890,20 +811,17 @@
|
|
|
}
|
|
|
|
|
|
if (rawType === 'error' || rawType === 'warning' || rawType === 'info' || rawType === 'question') {
|
|
|
- // Use the same "success-like" ring parts for every icon
|
|
|
appendRingParts();
|
|
|
|
|
|
applyIconSize('font');
|
|
|
applyIconBoxSize();
|
|
|
|
|
|
- // SVG only draws the inner symbol (no SVG ring)
|
|
|
const svg = createInnerMarkSvg();
|
|
|
appendBuiltInInnerMark(svg);
|
|
|
icon.appendChild(svg);
|
|
|
return icon;
|
|
|
}
|
|
|
|
|
|
- // Default to SVG icons for other/custom types
|
|
|
applyIconSize('box');
|
|
|
applyIconBoxSize();
|
|
|
|
|
|
@@ -917,8 +835,6 @@
|
|
|
ring.setAttribute('stroke', 'currentColor');
|
|
|
|
|
|
svg.appendChild(ring);
|
|
|
- // For custom types, we draw ring only by default (no inner mark).
|
|
|
-
|
|
|
icon.appendChild(svg);
|
|
|
|
|
|
return icon;
|
|
|
@@ -948,7 +864,6 @@
|
|
|
}
|
|
|
if (w) {
|
|
|
popup.style.width = w;
|
|
|
- // If width is explicitly set, remove default CSS max-width clamp.
|
|
|
popup.style.maxWidth = 'none';
|
|
|
} else {
|
|
|
popup.style.width = '';
|
|
|
@@ -993,7 +908,6 @@
|
|
|
if (!css) return null;
|
|
|
return { type: 'css', css, key: `css:${css}` };
|
|
|
}
|
|
|
- // Fallback: treat as raw css string
|
|
|
return { type: 'css', css: s, key: `css:${s}` };
|
|
|
}
|
|
|
|
|
|
@@ -1193,7 +1107,6 @@
|
|
|
loop: true,
|
|
|
autoplay: true,
|
|
|
animationData: data,
|
|
|
- // Fill background container even if aspect ratio differs
|
|
|
rendererSettings: { preserveAspectRatio: 'xMidYMid slice' }
|
|
|
});
|
|
|
bgEl._svgAniInstance = anim;
|
|
|
@@ -1219,7 +1132,6 @@
|
|
|
}
|
|
|
|
|
|
_didOpen() {
|
|
|
- // Keyboard close (ESC)
|
|
|
if (this.params.closeOnEsc) {
|
|
|
this._onKeydown = (e) => {
|
|
|
if (!e) return;
|
|
|
@@ -1228,30 +1140,22 @@
|
|
|
document.addEventListener('keydown', this._onKeydown);
|
|
|
}
|
|
|
|
|
|
- // Keep the "success-like" ring perfectly blended with popup bg
|
|
|
this._adjustRingBackgroundColor();
|
|
|
|
|
|
- // Popup animation (optional)
|
|
|
if (this.params.popupAnimation) {
|
|
|
if (this.dom.popup) {
|
|
|
- // Use WAAPI directly to guarantee the slide+fade effect is visible,
|
|
|
- // independent from xjs.animate implementation details.
|
|
|
try {
|
|
|
const popup = this.dom.popup;
|
|
|
try { popup.classList.add(`${PREFIX}popup-anim-slide`); } catch {}
|
|
|
|
|
|
const rect = popup.getBoundingClientRect();
|
|
|
- // Default entrance: from above center by "more than half" of popup height.
|
|
|
- // Clamp to viewport so very tall popups don't start far off-screen.
|
|
|
const vh = (typeof window !== 'undefined' && window && window.innerHeight) ? window.innerHeight : rect.height;
|
|
|
const baseH = Math.min(rect.height, vh);
|
|
|
const y0 = -Math.round(Math.max(18, baseH * 0.75));
|
|
|
|
|
|
- // Disable CSS transform transition so it won't fight with WAAPI.
|
|
|
popup.style.transition = 'none';
|
|
|
popup.style.willChange = 'transform, opacity';
|
|
|
|
|
|
- // If WAAPI is available, prefer it (most consistent).
|
|
|
if (popup.animate) {
|
|
|
const anim = popup.animate(
|
|
|
[
|
|
|
@@ -1270,7 +1174,6 @@
|
|
|
try { popup.style.willChange = ''; } catch {}
|
|
|
});
|
|
|
} else {
|
|
|
- // Fallback: try xjs.animate if WAAPI isn't available.
|
|
|
const X = Layer._getXjs();
|
|
|
if (X) {
|
|
|
popup.style.opacity = '0';
|
|
|
@@ -1290,7 +1193,6 @@
|
|
|
} catch {}
|
|
|
}
|
|
|
}
|
|
|
- // When popupAnimation is disabled, use a subtle scale-pop fade-in at current position.
|
|
|
if (!this.params.popupAnimation && !this._isReplace && this.dom.popup) {
|
|
|
try {
|
|
|
const popup = this.dom.popup;
|
|
|
@@ -1351,7 +1253,6 @@
|
|
|
}
|
|
|
} 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;
|
|
|
@@ -1371,12 +1272,10 @@
|
|
|
} catch {}
|
|
|
}
|
|
|
|
|
|
- // Icon SVG draw animation
|
|
|
if (this.params.iconAnimation) {
|
|
|
this._animateIcon();
|
|
|
}
|
|
|
|
|
|
- // User hook (SweetAlert-ish naming)
|
|
|
try {
|
|
|
if (typeof this.params.didOpen === 'function') this.params.didOpen(this.dom.popup);
|
|
|
} catch {}
|
|
|
@@ -1388,14 +1287,12 @@
|
|
|
const type = (this.params && this.params.icon) || '';
|
|
|
if (isSvgAniIcon(type)) return;
|
|
|
|
|
|
- // Ring animation (same as success) for all built-in icons
|
|
|
if (RING_TYPES.has(type)) this._adjustRingBackgroundColor();
|
|
|
try { icon.classList.remove(`${PREFIX}icon-show`); } catch {}
|
|
|
requestAnimationFrame(() => {
|
|
|
try { icon.classList.add(`${PREFIX}icon-show`); } catch {}
|
|
|
});
|
|
|
|
|
|
- // Success tick is CSS-driven; others still draw their SVG mark after the ring starts.
|
|
|
if (type === 'success') return;
|
|
|
|
|
|
const X = Layer._getXjs();
|
|
|
@@ -1407,13 +1304,9 @@
|
|
|
const marks = Array.from(svg.querySelectorAll(`.${PREFIX}svg-mark`));
|
|
|
const dot = svg.querySelector(`.${PREFIX}svg-dot`);
|
|
|
|
|
|
- // If this is a built-in icon, keep the SweetAlert-like order:
|
|
|
- // ring sweep first (~0.51s), then draw the inner mark.
|
|
|
const baseDelay = RING_TYPES.has(type) ? 520 : 0;
|
|
|
|
|
|
if (type === 'error') {
|
|
|
- // Draw order: left-top -> right-bottom, then right-top -> left-bottom
|
|
|
- // NOTE: A tiny delay (like 70ms) looks simultaneous; make it strictly sequential.
|
|
|
const a = svg.querySelector(`.${PREFIX}svg-error-left`) || marks[0]; // M28 28 L52 52
|
|
|
const b = svg.querySelector(`.${PREFIX}svg-error-right`) || marks[1]; // M52 28 L28 52
|
|
|
const dur = 320;
|
|
|
@@ -1421,7 +1314,6 @@
|
|
|
try { if (a) X(a).draw({ duration: dur, easing: 'ease-out', delay: baseDelay }); } catch {}
|
|
|
try { if (b) X(b).draw({ duration: dur, easing: 'ease-out', delay: baseDelay + dur + gap }); } catch {}
|
|
|
} else {
|
|
|
- // warning / info / question (single stroke) or custom SVG symbols
|
|
|
marks.forEach((m, i) => {
|
|
|
try { X(m).draw({ duration: 420, easing: 'ease-out', delay: baseDelay + i * 60 }); } catch {}
|
|
|
});
|
|
|
@@ -1430,7 +1322,6 @@
|
|
|
if (dot) {
|
|
|
try {
|
|
|
dot.style.opacity = '0';
|
|
|
- // Keep dot pop after the ring begins
|
|
|
const d = baseDelay + 140;
|
|
|
if (type === 'info') {
|
|
|
X(dot).animate({ opacity: [0, 1], y: [-8, 0], scale: [0.2, 1], duration: 420, delay: d, easing: { stiffness: 300, damping: 14 } });
|
|
|
@@ -1444,7 +1335,6 @@
|
|
|
_forceDestroy(reason = 'replace') {
|
|
|
try { this._destroySvgAniIcon(); } catch {}
|
|
|
try { this._destroySvgAniBackground(this.dom && this.dom.background); } catch {}
|
|
|
- // Restore mounted DOM (if any) and cleanup listeners; also resolve the promise so it doesn't hang.
|
|
|
try {
|
|
|
if (this._flowSteps && this._flowSteps.length) this._unmountFlowMounted();
|
|
|
else this._unmountDomContent();
|
|
|
@@ -1477,12 +1367,10 @@
|
|
|
} catch {}
|
|
|
};
|
|
|
if (!shouldDelayUnmount) {
|
|
|
- // Restore mounted DOM (moved into popup) before removing overlay
|
|
|
doUnmount();
|
|
|
}
|
|
|
|
|
|
let customClose = false;
|
|
|
- // Soft close for non-popupAnimation cases (avoid abrupt cut)
|
|
|
if (!this.params.popupAnimation) {
|
|
|
try {
|
|
|
const popup = this.dom.popup;
|
|
|
@@ -1493,7 +1381,6 @@
|
|
|
}
|
|
|
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(() => {
|
|
|
@@ -1622,8 +1509,6 @@
|
|
|
}
|
|
|
|
|
|
_normalizeDomSpec(params) {
|
|
|
- // Preferred: params.dom (selector/Element/template)
|
|
|
- // Backward: params.content (selector/Element) OR advanced object { dom, mode, clone }
|
|
|
const p = params || {};
|
|
|
let dom = p.dom;
|
|
|
let mode = p.domMode || 'move';
|
|
|
@@ -1645,16 +1530,13 @@
|
|
|
}
|
|
|
|
|
|
_mountDomContentInto(target, domSpec, opts = null) {
|
|
|
- // Like _mountDomContent, but mounts into the provided container.
|
|
|
const collectFlow = !!(opts && opts.collectFlow);
|
|
|
const originalContent = this.dom.content;
|
|
|
- // Temporarily redirect this.dom.content for reuse of internal logic.
|
|
|
try { this.dom.content = target; } catch {}
|
|
|
try {
|
|
|
const before = this._mounted;
|
|
|
this._mountDomContent(domSpec);
|
|
|
const rec = this._mounted;
|
|
|
- // If we mounted in move-mode, _mounted holds record; detach it from single-mode tracking.
|
|
|
if (collectFlow && rec && rec.kind === 'move') {
|
|
|
this._flowMountedList.push(rec);
|
|
|
this._mounted = before; // restore previous single record (usually null)
|
|
|
@@ -1665,13 +1547,11 @@
|
|
|
}
|
|
|
|
|
|
_unmountFlowMounted() {
|
|
|
- // Restore all moved DOM nodes for flow steps
|
|
|
const list = Array.isArray(this._flowMountedList) ? this._flowMountedList : [];
|
|
|
this._flowMountedList = [];
|
|
|
list.forEach((m) => {
|
|
|
try {
|
|
|
if (!m || m.kind !== 'move') return;
|
|
|
- // Reuse single unmount logic by swapping _mounted
|
|
|
const prev = this._mounted;
|
|
|
this._mounted = m;
|
|
|
this._unmountDomContent();
|
|
|
@@ -1693,8 +1573,6 @@
|
|
|
try {
|
|
|
const cs = (typeof getComputedStyle === 'function') ? getComputedStyle(el) : null;
|
|
|
const display = cs ? String(cs.display || '') : '';
|
|
|
- // If element is hidden via CSS (e.g. .hidden-dom{display:none}),
|
|
|
- // add an inline override so it becomes visible inside the popup.
|
|
|
if (display === 'none') el.style.display = 'block';
|
|
|
else if (el.style && el.style.display === 'none') el.style.display = 'block';
|
|
|
} catch {}
|
|
|
@@ -1705,7 +1583,6 @@
|
|
|
if (typeof node === 'string') node = document.querySelector(node);
|
|
|
if (!node) return;
|
|
|
|
|
|
- // <template> support: always clone template content
|
|
|
if (typeof HTMLTemplateElement !== 'undefined' && node instanceof HTMLTemplateElement) {
|
|
|
const frag = node.content.cloneNode(true);
|
|
|
this.dom.content.appendChild(frag);
|
|
|
@@ -1717,7 +1594,6 @@
|
|
|
|
|
|
if (domSpec.mode === 'clone') {
|
|
|
const clone = node.cloneNode(true);
|
|
|
- // If original is hidden (via attr or CSS), the clone may inherit; force show it in popup.
|
|
|
try { clone.hidden = false; } catch {}
|
|
|
try {
|
|
|
const cs = (typeof getComputedStyle === 'function') ? getComputedStyle(node) : null;
|
|
|
@@ -1729,12 +1605,10 @@
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- // Default: move into popup but restore on close
|
|
|
const placeholder = document.createComment('layer-dom-placeholder');
|
|
|
const parent = node.parentNode;
|
|
|
const nextSibling = node.nextSibling;
|
|
|
if (!parent) {
|
|
|
- // Detached node: moving would lose it when overlay is removed; clone instead.
|
|
|
const clone = node.cloneNode(true);
|
|
|
try { clone.hidden = false; } catch {}
|
|
|
try { if (clone.style && clone.style.display === 'none') clone.style.display = ''; } catch {}
|
|
|
@@ -1750,7 +1624,6 @@
|
|
|
this.dom.content.appendChild(node);
|
|
|
this._mounted = { kind: 'move', originalEl: node, placeholder, parent, nextSibling, prevHidden, prevInlineDisplay };
|
|
|
} catch {
|
|
|
- // ignore
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -1763,13 +1636,11 @@
|
|
|
const node = m.originalEl;
|
|
|
if (!node) return;
|
|
|
|
|
|
- // Restore hidden/display
|
|
|
try { node.hidden = !!m.prevHidden; } catch {}
|
|
|
try {
|
|
|
if (node.style && typeof m.prevInlineDisplay === 'string') node.style.display = m.prevInlineDisplay;
|
|
|
} catch {}
|
|
|
|
|
|
- // Move back to original position
|
|
|
try {
|
|
|
const ph = m.placeholder;
|
|
|
if (ph && ph.parentNode) {
|
|
|
@@ -1779,7 +1650,6 @@
|
|
|
}
|
|
|
} catch {}
|
|
|
|
|
|
- // Fallback: append to original parent
|
|
|
try {
|
|
|
if (m.parent) m.parent.appendChild(node);
|
|
|
} catch {}
|
|
|
@@ -1833,12 +1703,10 @@
|
|
|
}
|
|
|
|
|
|
async _handleConfirm() {
|
|
|
- // Flow next / finalize
|
|
|
if (this._flowSteps && this._flowSteps.length) {
|
|
|
return this._flowNext();
|
|
|
}
|
|
|
|
|
|
- // Single popup: support async preConfirm
|
|
|
const pre = this.params && this.params.preConfirm;
|
|
|
if (typeof pre === 'function') {
|
|
|
try {
|
|
|
@@ -1849,7 +1717,6 @@
|
|
|
this._setButtonsDisabled(false);
|
|
|
return;
|
|
|
}
|
|
|
- // store value in non-flow mode as single value
|
|
|
this._flowValues = [v];
|
|
|
} catch (e) {
|
|
|
console.error(e);
|
|
|
@@ -1863,7 +1730,6 @@
|
|
|
|
|
|
_handleCancel() {
|
|
|
if (this._flowSteps && this._flowSteps.length) {
|
|
|
- // default: if not first step, cancel acts as "back"
|
|
|
if (this._flowIndex > 0) {
|
|
|
this._flowPrev();
|
|
|
return;
|
|
|
@@ -1877,22 +1743,16 @@
|
|
|
const base = this._flowBase || {};
|
|
|
const merged = { ...base, ...step };
|
|
|
|
|
|
- // Default button texts for flow
|
|
|
const isLast = index >= (this._flowSteps.length - 1);
|
|
|
if (!('confirmButtonText' in step)) merged.confirmButtonText = isLast ? (base.confirmButtonText || 'OK') : 'Next';
|
|
|
|
|
|
- // Show cancel as Back after first step (unless step explicitly overrides)
|
|
|
if (index > 0) {
|
|
|
if (!('showCancelButton' in step)) merged.showCancelButton = true;
|
|
|
if (!('cancelButtonText' in step)) merged.cancelButtonText = 'Back';
|
|
|
} else {
|
|
|
- // First step default keeps base settings
|
|
|
if (!('cancelButtonText' in step) && merged.showCancelButton) merged.cancelButtonText = merged.cancelButtonText || 'Cancel';
|
|
|
}
|
|
|
|
|
|
- // Icon/animation policy for flow:
|
|
|
- // - During steps: no icon/animations by default (avoids distraction + layout jitter)
|
|
|
- // - Allow icon only if step explicitly uses `icon:'question'`, or step is marked as summary.
|
|
|
const isSummary = !!(step && (step.summary === true || step.isSummary === true));
|
|
|
const explicitIcon = ('icon' in step) ? step.icon : undefined;
|
|
|
const baseIcon = ('icon' in base) ? base.icon : undefined;
|
|
|
@@ -1908,7 +1768,6 @@
|
|
|
merged.iconAnimation = false;
|
|
|
}
|
|
|
} else if (!isSummary && isLast) {
|
|
|
- // last step: still suppress unless summary/question/container icon
|
|
|
if (allowContainerIcon) {
|
|
|
merged.icon = chosenIcon;
|
|
|
} else {
|
|
|
@@ -1916,7 +1775,6 @@
|
|
|
merged.iconAnimation = false;
|
|
|
}
|
|
|
} else {
|
|
|
- // summary step: allow icon; animation follows explicit config (default true only if provided elsewhere)
|
|
|
if (!('icon' in step) && chosenIcon === undefined) merged.icon = null;
|
|
|
}
|
|
|
|
|
|
@@ -1934,7 +1792,6 @@
|
|
|
const total = this._flowSteps.length;
|
|
|
const isLast = idx >= (total - 1);
|
|
|
|
|
|
- // preConfirm hook for current step
|
|
|
const pre = this.params && this.params.preConfirm;
|
|
|
if (typeof pre === 'function') {
|
|
|
try {
|
|
|
@@ -1994,10 +1851,8 @@
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- // Measure current content height
|
|
|
const oldH = content.getBoundingClientRect().height;
|
|
|
|
|
|
- // Prepare target pane for measurement without affecting layout
|
|
|
const prevDisplay = toPane.style.display;
|
|
|
const prevPos = toPane.style.position;
|
|
|
const prevVis = toPane.style.visibility;
|
|
|
@@ -2011,13 +1866,11 @@
|
|
|
|
|
|
const newH = toPane.getBoundingClientRect().height;
|
|
|
|
|
|
- // Restore pane styles (keep hidden until animation starts)
|
|
|
toPane.style.position = prevPos;
|
|
|
toPane.style.visibility = prevVis;
|
|
|
toPane.style.pointerEvents = prevPointer;
|
|
|
toPane.style.display = prevDisplay; // usually 'none'
|
|
|
|
|
|
- // Apply new options (title/buttons/icon policy) before showing the pane
|
|
|
this._flowIndex = nextIndex;
|
|
|
this.params = nextOptions;
|
|
|
this._applyPopupTheme(this.params);
|
|
|
@@ -2027,7 +1880,6 @@
|
|
|
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 {
|
|
|
@@ -2110,7 +1962,6 @@
|
|
|
const bgTransition = this._transitionBackground(nextBgSpec, direction);
|
|
|
this._applyPopupSize(this.params);
|
|
|
|
|
|
- // Icon updates (only if needed)
|
|
|
try {
|
|
|
const wantsIcon = !!this.params.icon;
|
|
|
if (!wantsIcon && this.dom.icon) {
|
|
|
@@ -2125,7 +1976,6 @@
|
|
|
this.dom.icon.remove();
|
|
|
}
|
|
|
this.dom.icon = this._createIcon(this.params.icon);
|
|
|
- // icon should be on top (before title)
|
|
|
popup.insertBefore(this.dom.icon, this.dom.title || popup.firstChild);
|
|
|
}
|
|
|
}
|
|
|
@@ -2133,7 +1983,6 @@
|
|
|
|
|
|
this._updateFlowActions();
|
|
|
|
|
|
- // Prepare panes for animation
|
|
|
toPane.style.display = '';
|
|
|
toPane.style.position = 'absolute';
|
|
|
toPane.style.left = '0';
|
|
|
@@ -2151,7 +2000,6 @@
|
|
|
fromPane.style.opacity = '1';
|
|
|
fromPane.style.pointerEvents = 'none';
|
|
|
|
|
|
- // Lock content height during transition
|
|
|
content.style.height = oldH + 'px';
|
|
|
content.style.overflow = 'hidden';
|
|
|
|
|
|
@@ -2165,7 +2013,6 @@
|
|
|
{ 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 {
|
|
|
@@ -2193,7 +2040,6 @@
|
|
|
try { fromPane.style.transition = ''; } catch {}
|
|
|
try { toPane.style.transition = ''; } catch {}
|
|
|
};
|
|
|
- // Force a layout so transitions apply reliably.
|
|
|
void fromPane.offsetWidth;
|
|
|
requestAnimationFrame(() => {
|
|
|
try {
|
|
|
@@ -2221,7 +2067,6 @@
|
|
|
try { if (titleCleanup) titleCleanup(); } catch {}
|
|
|
try { if (slideCleanup) slideCleanup(); } catch {}
|
|
|
|
|
|
- // Cleanup styles and hide old pane
|
|
|
fromPane.style.display = 'none';
|
|
|
fromPane.style.position = '';
|
|
|
fromPane.style.left = '';
|
|
|
@@ -2242,7 +2087,6 @@
|
|
|
content.style.height = '';
|
|
|
content.style.overflow = '';
|
|
|
|
|
|
- // Re-adjust ring background if icon exists (rare in flow)
|
|
|
try { this._adjustRingBackgroundColor(); } catch {}
|
|
|
}
|
|
|
|
|
|
@@ -2354,27 +2198,23 @@
|
|
|
|
|
|
_rerenderInside() {
|
|
|
this._destroySvgAniIcon();
|
|
|
- // Update popup content without recreating overlay (used in flow transitions)
|
|
|
const popup = this._clearPopup();
|
|
|
if (!popup) return;
|
|
|
|
|
|
this._applyPopupTheme(this.params);
|
|
|
|
|
|
- // Icon
|
|
|
this.dom.icon = null;
|
|
|
if (this.params.icon) {
|
|
|
this.dom.icon = this._createIcon(this.params.icon);
|
|
|
popup.appendChild(this.dom.icon);
|
|
|
}
|
|
|
|
|
|
- // Title
|
|
|
this.dom.title = null;
|
|
|
if (this.params.title) {
|
|
|
this.dom.title = el('h2', `${PREFIX}title`, this.params.title);
|
|
|
popup.appendChild(this.dom.title);
|
|
|
}
|
|
|
|
|
|
- // Content
|
|
|
this.dom.content = null;
|
|
|
if (this.params.text || this.params.html || this.params.content || this.params.dom) {
|
|
|
this.dom.content = el('div', `${PREFIX}content`);
|
|
|
@@ -2385,7 +2225,6 @@
|
|
|
popup.appendChild(this.dom.content);
|
|
|
}
|
|
|
|
|
|
- // Actions / buttons
|
|
|
this.dom.actions = el('div', `${PREFIX}actions`);
|
|
|
this.dom.cancelBtn = null;
|
|
|
if (this.params.showCancelButton) {
|
|
|
@@ -2402,7 +2241,6 @@
|
|
|
|
|
|
popup.appendChild(this.dom.actions);
|
|
|
|
|
|
- // Re-run open hooks for each step
|
|
|
try {
|
|
|
if (typeof this.params.didOpen === 'function') this.params.didOpen(this.dom.popup);
|
|
|
} catch {}
|