(function (global, factory) { const LayerClass = factory(); function LayerFactory(options) { if (options && typeof options === 'object') { return LayerClass.$(options); } return new LayerClass(); } const copyStatic = (to, from) => { try { Object.getOwnPropertyNames(from).forEach((k) => { if (k === 'prototype' || k === 'name' || k === 'length') return; const desc = Object.getOwnPropertyDescriptor(from, k); if (!desc) return; Object.defineProperty(to, k, desc); }); } catch (e) { try { Object.assign(to, from); } catch {} } }; copyStatic(LayerFactory, LayerClass); LayerFactory.prototype = LayerClass.prototype; typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = LayerFactory : typeof define === 'function' && define.amd ? define(() => LayerFactory) : (global.Layer = LayerFactory); }(this, (function () { 'use strict'; const PREFIX = 'layer-'; const RING_TYPES = new Set(['success', 'error', 'warning', 'info', 'question']); const RING_BG_PARTS_SELECTOR = `.${PREFIX}success-circular-line-left, .${PREFIX}success-circular-line-right, .${PREFIX}success-fix`; const SVGANI_PREFIX = 'svg:'; const BG_SVG_PREFIX = 'svg('; const BG_CSS_PREFIX = 'css('; const THEME_VALUES = new Set(['auto', 'dark', 'light']); const normalizeTheme = (value) => { if (typeof value !== 'string') return 'auto'; const v = value.trim().toLowerCase(); return THEME_VALUES.has(v) ? v : 'auto'; }; const normalizeOptions = (options, text, icon) => { if (typeof options !== 'string') return options || {}; const o = { title: options }; if (text) o.text = text; if (icon) o.icon = icon; return o; }; const el = (tag, className, text) => { const node = document.createElement(tag); if (className) node.className = className; if (text !== undefined) node.textContent = text; return node; }; const svgEl = (tag) => document.createElementNS('http://www.w3.org/2000/svg', tag); let _xjsCssReady = null; const waitForStylesheet = (link) => new Promise((resolve) => { if (!link) return resolve(); if (link.sheet) return resolve(); let done = false; const finish = () => { if (done) return; done = true; resolve(); }; try { link.addEventListener('load', finish, { once: true }); } catch {} try { link.addEventListener('error', finish, { once: true }); } catch {} setTimeout(finish, 200); }); const ensureXjsCss = () => { try { if (_xjsCssReady) return _xjsCssReady; if (typeof document === 'undefined') return Promise.resolve(); if (!document.head) return Promise.resolve(); const existingLink = Array.from(document.querySelectorAll('link[rel="stylesheet"]')) .find((l) => /(^|\/)xjs\.css(\?|#|$)/.test((l.getAttribute('href') || '').trim())); if (existingLink) return (_xjsCssReady = waitForStylesheet(existingLink)); const id = 'xjs-css'; const existing = document.getElementById(id); if (existing) return (_xjsCssReady = waitForStylesheet(existing)); const scripts = Array.from(document.getElementsByTagName('script')); const scriptSrc = scripts .map((s) => s && s.src) .find((src) => /(^|\/)(xjs|layer)\.js(\?|#|$)/.test(String(src || ''))); let href = 'xjs.css'; if (scriptSrc) { href = String(scriptSrc) .replace(/(^|\/)xjs\.js(\?|#|$)/, '$1xjs.css$2') .replace(/(^|\/)layer\.js(\?|#|$)/, '$1xjs.css$2'); } const link = document.createElement('link'); link.id = id; link.rel = 'stylesheet'; link.href = href; _xjsCssReady = waitForStylesheet(link); document.head.appendChild(link); return _xjsCssReady; } catch { return Promise.resolve(); } }; let _svgAniReady = null; let _svgAniPrefetchScheduled = false; const isSvgAniIcon = (icon) => { if (typeof icon !== 'string') return false; const s = icon.trim(); return s.startsWith(SVGANI_PREFIX); }; const getSvgAniIconName = (icon) => { if (!isSvgAniIcon(icon)) return ''; let name = String(icon || '').trim().slice(SVGANI_PREFIX.length).trim(); if (name === 'loadding') name = 'loading'; return name; }; const normalizeSvgAniName = (raw) => { let name = String(raw || '').trim(); if (!name) return ''; if (name.startsWith(SVGANI_PREFIX)) name = getSvgAniIconName(name); if (name === 'loadding') name = 'loading'; return name; }; const getLayerScriptBase = () => { try { if (typeof document === 'undefined') return ''; const scripts = Array.from(document.getElementsByTagName('script')); const src = scripts .map((s) => s && s.src) .find((s) => /(^|\/)(xjs|layer)\.js(\?|#|$)/.test(String(s || ''))); if (!src) return ''; return String(src) .replace(/[#?].*$/, '') .replace(/\/[^\/]*$/, '/'); } catch { return ''; } }; const resolveSvgAniScriptUrl = () => { const base = getLayerScriptBase(); return base ? (base + 'svg/svg.js') : 'svg/svg.js'; }; const resolveSvgAniJsonUrl = (name) => { const raw = String(name || '').trim(); if (!raw) return ''; if (/^(https?:)?\/\//.test(raw)) return raw; if (raw.startsWith('/') || raw.startsWith('./') || raw.startsWith('../')) return raw; const base = getLayerScriptBase(); const file = raw.endsWith('.json') ? raw : (raw + '.json'); return (base ? base : '') + 'svg/' + file; }; const ensureSvgAniLib = () => { try { if (typeof window === 'undefined') return Promise.resolve(null); if (window.lottie) return Promise.resolve(window.lottie); if (typeof document === 'undefined' || !document.head) return Promise.resolve(null); if (_svgAniReady) return _svgAniReady; const existing = document.getElementById('layer-svgani-lib'); _svgAniReady = new Promise((resolve) => { const finish = () => resolve(window.lottie || null); if (existing) { try { existing.addEventListener('load', finish, { once: true }); } catch {} try { existing.addEventListener('error', finish, { once: true }); } catch {} setTimeout(finish, 1200); return; } const script = document.createElement('script'); script.id = 'layer-svgani-lib'; script.async = true; script.src = resolveSvgAniScriptUrl(); try { script.addEventListener('load', finish, { once: true }); } catch {} try { script.addEventListener('error', finish, { once: true }); } catch {} setTimeout(finish, 1800); document.head.appendChild(script); }); return _svgAniReady; } catch { return Promise.resolve(null); } }; const scheduleSvgAniPrefetch = () => { if (_svgAniPrefetchScheduled) return; _svgAniPrefetchScheduled = true; try { if (typeof window === 'undefined') return; const start = () => { setTimeout(() => { try { ensureSvgAniLib(); } catch {} }, 0); }; if (document.readyState === 'complete') start(); else window.addEventListener('load', start, { once: true }); } catch {} }; const waitForConnected = (el) => new Promise((resolve) => { if (!el) return resolve(false); if (el.isConnected) return resolve(true); let done = false; const finish = () => { if (done) return; done = true; resolve(!!el.isConnected); }; try { requestAnimationFrame(() => requestAnimationFrame(finish)); } catch { setTimeout(finish, 0); return; } setTimeout(finish, 120); }); class Layer { constructor() { this._cssReady = ensureXjsCss(); this.params = {}; this.dom = {}; this.promise = null; this.resolve = null; this.reject = null; this._onKeydown = null; this._mounted = null; this._isClosing = false; this._isReplace = false; this._flowSteps = null; this._flowIndex = 0; this._flowValues = []; this._flowBase = null; this._flowResolved = null; this._flowMountedList = []; this._flowAutoForm = null; this._backgroundSpec = null; } static get isProxy() { return true; } static run(options) { const instance = new Layer(); return instance.run(options); } static $(options) { const instance = new Layer(); if (options !== undefined) instance.config(options); return instance; } config(options = {}) { options = normalizeOptions(options, arguments[1], arguments[2]); this.params = { ...(this.params || {}), ...options }; return this; } step(options = {}) { if (!this._flowSteps) this._flowSteps = []; this._flowSteps.push(normalizeOptions(options, arguments[1], arguments[2])); return this; } steps(steps = []) { if (!Array.isArray(steps)) return this; if (!this._flowSteps) this._flowSteps = []; steps.forEach((s) => this.step(s)); return this; } static flow(steps = [], baseOptions = {}) { return Layer.$(baseOptions).steps(steps).run(); } 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 (!this._flowSteps || !this._flowSteps.length) { const didInit = this._initFlowFromStepContainer(merged); if (didInit) { this.params = { ...(this.params || {}), ...this._stripStepOptions(merged) }; } } if (this._flowSteps && this._flowSteps.length) { if (hasArgs) { this.params = { ...(this.params || {}), ...this._stripStepOptions(merged) }; } return this._fireFlow(); } return this._fire(merged); } _fire(options = {}) { options = normalizeOptions(options, arguments[1], arguments[2]); this.params = { title: '', text: '', icon: null, iconSize: null, iconLoop: null, confirmButtonText: 'OK', cancelButtonText: 'Cancel', showCancelButton: false, closeOnClickOutside: true, closeOnEsc: true, iconAnimation: true, popupAnimation: true, theme: 'auto', background: null, width: null, height: null, iwidth: null, iheight: null, nextIcon: null, prevIcon: null, dom: null, domMode: 'move', preConfirm: null, ...options }; this.promise = new Promise((resolve, reject) => { this.resolve = resolve; this.reject = reject; }); this._render(); return this.promise; } _fireFlow() { this._flowIndex = 0; this._flowValues = []; const base = { ...(this.params || {}) }; if (!('popupAnimation' in base)) base.popupAnimation = true; if (!('iconAnimation' in base)) base.iconAnimation = false; this._flowBase = base; this.promise = new Promise((resolve, reject) => { this.resolve = resolve; this.reject = reject; }); this._flowResolved = (this._flowSteps || []).map((_, i) => this._getFlowStepOptions(i)); const first = this._flowResolved[0] || this._getFlowStepOptions(0); this.params = first; this._render({ flow: true }); return this.promise; } static _getXjs() { const g = (typeof window !== 'undefined') ? window : (typeof globalThis !== 'undefined' ? globalThis : null); if (!g) return null; const x = g.$ || g.xjs || g.animal; return (typeof x === 'function') ? x : null; } _stripStepOptions(options) { const out = { ...(options || {}) }; try { delete out.step; } catch {} try { delete out.stepItem; } catch {} return out; } _parseStepSizeAttr(raw) { if (raw === undefined || raw === null) return null; const s = String(raw).trim(); if (!s) return null; if (/^-?\d+(\.\d+)?$/.test(s)) return parseFloat(s); return s; } _readStepAttr(item, container, names) { const list = Array.isArray(names) ? names : [names]; const readFrom = (el) => { if (!el || !el.getAttribute) return ''; for (let i = 0; i < list.length; i++) { const v = el.getAttribute(list[i]); if (v != null && String(v).trim() !== '') return String(v).trim(); } return ''; }; return readFrom(item) || readFrom(container) || ''; } _normalizeStepContainer(options) { const opts = options || {}; const raw = opts.step; if (!raw || typeof document === 'undefined') return null; const container = (typeof raw === 'string') ? document.querySelector(raw) : raw; if (!container || (typeof Element !== 'undefined' && !(container instanceof Element))) return null; const itemSelector = opts.stepItem; let items = []; if (typeof itemSelector === 'string' && itemSelector.trim()) { try { items = Array.from(container.querySelectorAll(itemSelector)); } catch {} } else { try { items = Array.from(container.children || []); } catch {} } return { container, items }; } _initFlowFromStepContainer(options) { const spec = this._normalizeStepContainer(options); if (!spec) return false; const { items, container } = spec; if (!items || !items.length) { try { console.warn('Layer step container has no items.'); } catch {} return false; } this._flowSteps = items.map((item) => { const step = { dom: item, __fromContainer: true }; try { const title = (item.getAttribute('data-step-title') || item.getAttribute('data-layer-title') || item.getAttribute('title') || '').trim(); if (title) step.title = title; } catch {} try { const background = this._readStepAttr(item, container, ['data-step-background', 'data-layer-background', 'data-background']); if (background) step.background = background; const icon = this._readStepAttr(item, container, ['data-step-icon', 'data-layer-icon', 'data-icon']); if (icon) step.icon = icon; const theme = this._readStepAttr(item, container, ['data-step-theme', 'data-layer-theme', 'data-theme']); if (theme) step.theme = theme; const width = this._readStepAttr(item, container, ['data-step-width', 'data-layer-width', 'data-width']); const height = this._readStepAttr(item, container, ['data-step-height', 'data-layer-height', 'data-height']); const iwidth = this._readStepAttr(item, container, ['data-step-iwidth', 'data-layer-iwidth', 'data-iwidth']); const iheight = this._readStepAttr(item, container, ['data-step-iheight', 'data-layer-iheight', 'data-iheight']); const w = this._parseStepSizeAttr(width); const h = this._parseStepSizeAttr(height); const iw = this._parseStepSizeAttr(iwidth); const ih = this._parseStepSizeAttr(iheight); if (w != null) step.width = w; if (h != null) step.height = h; if (iw != null) step.iwidth = iw; if (ih != null) step.iheight = ih; } catch {} return step; }); this._flowAutoForm = { enabled: true }; 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; 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); } 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; } 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); } 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(); const existing = document.querySelector(`.${PREFIX}overlay`); const wantReplace = !!(this.params && this.params.replace); this._isReplace = false; if (existing && wantReplace) { try { const prev = existing._layerInstance; if (prev && typeof prev._forceDestroy === 'function') prev._forceDestroy('replace'); } 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'); 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 {} } this.dom.overlay = el('div', `${PREFIX}overlay`); this.dom.overlay._layerInstance = this; } this.dom.popup = el('div', `${PREFIX}popup`); this.dom.overlay.appendChild(this.dom.popup); this._applyPopupBasics(this.params, { forceBackground: true }); if (meta && meta.flow) { this._renderFlowUI(); return; } if (this.params.icon) { this.dom.icon = this._createIcon(this.params.icon); this.dom.popup.appendChild(this.dom.icon); } if (this.params.title) { this.dom.title = el('h2', `${PREFIX}title`, this.params.title); this.dom.popup.appendChild(this.dom.title); } if (this.params.text || this.params.html || this.params.content || this.params.dom) { this.dom.content = el('div', `${PREFIX}content`); const domSpec = this._normalizeDomSpec(this.params); if (domSpec) { this._mountDomContent(domSpec); } else if (this.params.html) { this.dom.content.innerHTML = this.params.html; } else { this.dom.content.textContent = this.params.text; } this.dom.popup.appendChild(this.dom.content); } this._buildActions(); if (this.params.closeOnClickOutside) { this._bindOverlayClick((e) => { if (e.target === this.dom.overlay) this._close(null); }); } else { this._bindOverlayClick(null); } this._mountOverlay(); } _renderFlowUI() { this._destroySvgAniIcon(); const popup = this.dom.popup; if (!popup) return; this._clearPopup(); this._applyPopupBasics(this.params, { forceBackground: true }); this.dom.icon = null; if (this.params.icon) { this.dom.icon = this._createIcon(this.params.icon); popup.appendChild(this.dom.icon); } this.dom.title = el('h2', `${PREFIX}title`, this.params.title || ''); popup.appendChild(this.dom.title); this.dom.content = el('div', `${PREFIX}content`); this.dom.content.style.alignSelf = 'stretch'; this.dom.content.style.width = '100%'; this.dom.stepStack = el('div', `${PREFIX}step-stack`); this.dom.stepStack.style.position = 'relative'; this.dom.stepStack.style.width = '100%'; this.dom.stepPanes = []; this._flowMountedList = []; const steps = this._flowResolved || (this._flowSteps || []).map((_, i) => this._getFlowStepOptions(i)); steps.forEach((opt, i) => { const pane = el('div', `${PREFIX}step-pane`); pane.style.width = '100%'; pane.style.boxSizing = 'border-box'; pane.style.display = (i === this._flowIndex) ? '' : 'none'; const domSpec = this._normalizeDomSpec(opt); if (domSpec) { this._mountDomContentInto(pane, domSpec, { collectFlow: true }); } else if (opt.html) { pane.innerHTML = opt.html; } else if (opt.text) { pane.textContent = opt.text; } this.dom.stepStack.appendChild(pane); this.dom.stepPanes.push(pane); }); this.dom.content.appendChild(this.dom.stepStack); popup.appendChild(this.dom.content); this._buildActions({ flow: true }); if (this.params.closeOnClickOutside) { this._bindOverlayClick((e) => { if (e.target === this.dom.overlay) this._close(null, 'backdrop'); }); } else { this._bindOverlayClick(null); } this._mountOverlay(); } _updateFlowActions() { const actions = this.dom && this.dom.actions; if (!actions) return; while (actions.firstChild) actions.removeChild(actions.firstChild); this.dom.cancelBtn = null; const total = (this._flowSteps && this._flowSteps.length) ? this._flowSteps.length : 0; const isLast = total > 0 ? (this._flowIndex >= (total - 1)) : true; if (this.params.showCancelButton) { const prevIcon = (this._flowIndex > 0) ? this.params.prevIcon : null; this.dom.cancelBtn = el('button', `${PREFIX}button ${PREFIX}cancel`, ''); this._setButtonContent(this.dom.cancelBtn, this.params.cancelButtonText, prevIcon, 'start'); this.dom.cancelBtn.onclick = () => this._handleCancel(); actions.appendChild(this.dom.cancelBtn); } const nextIcon = (!isLast) ? this.params.nextIcon : null; this.dom.confirmBtn = el('button', `${PREFIX}button ${PREFIX}confirm`, ''); this._setButtonContent(this.dom.confirmBtn, this.params.confirmButtonText, nextIcon, 'end'); this.dom.confirmBtn.onclick = () => this._handleConfirm(); actions.appendChild(this.dom.confirmBtn); } _createIcon(type) { const rawType = String(type || '').trim(); const icon = el('div', `${PREFIX}icon ${rawType}`); const applyIconBoxSize = () => { if (!this.params) return; const w = this._normalizeSizeValue(this.params.iwidth); const h = this._normalizeSizeValue(this.params.iheight); if (w) icon.style.width = w; if (h) icon.style.height = h; }; const applyIconSize = (mode) => { if (!(this.params && this.params.iconSize)) return; try { const s = String(this.params.iconSize).trim(); if (!s) return; if (mode === 'font') { const m = s.match(/^(-?\d*\.?\d+)\s*(px|em|rem)$/i); if (m) { const n = parseFloat(m[1]); const unit = m[2]; if (Number.isFinite(n) && n > 0) { icon.style.fontSize = (n / 5) + unit; // icon is 5em wide/tall return; } } } icon.style.width = s; icon.style.height = s; } catch {} }; const appendRingParts = () => { icon.appendChild(el('div', `${PREFIX}success-circular-line-left`)); icon.appendChild(el('div', `${PREFIX}success-ring`)); icon.appendChild(el('div', `${PREFIX}success-fix`)); icon.appendChild(el('div', `${PREFIX}success-circular-line-right`)); }; const createInnerMarkSvg = () => { const svg = svgEl('svg'); svg.setAttribute('viewBox', '0 0 80 80'); svg.setAttribute('aria-hidden', 'true'); svg.setAttribute('focusable', 'false'); return svg; }; const addMarkPath = (svg, d, extraClass) => { const p = svgEl('path'); p.setAttribute('d', d); p.setAttribute('class', `${PREFIX}svg-mark ${PREFIX}svg-draw${extraClass ? ' ' + extraClass : ''}`); p.setAttribute('stroke', 'currentColor'); svg.appendChild(p); return p; }; const addDot = (svg, cx, cy) => { const dot = svgEl('circle'); dot.setAttribute('class', `${PREFIX}svg-dot`); dot.setAttribute('cx', String(cx)); dot.setAttribute('cy', String(cy)); dot.setAttribute('r', '3.2'); dot.setAttribute('fill', 'currentColor'); svg.appendChild(dot); return dot; }; const appendBuiltInInnerMark = (svg) => { if (rawType === 'error') { addMarkPath(svg, 'M28 28 L52 52', `${PREFIX}svg-error-left`); addMarkPath(svg, 'M52 28 L28 52', `${PREFIX}svg-error-right`); } else if (rawType === 'warning') { addMarkPath(svg, 'M40 20 L40 46', `${PREFIX}svg-warning-line`); addDot(svg, 40, 58); } else if (rawType === 'info') { addMarkPath(svg, 'M40 34 L40 56', `${PREFIX}svg-info-line`); addDot(svg, 40, 25); } else if (rawType === 'question') { addMarkPath(svg, 'M30 30 C30 23 35 19 42 19 C49 19 54 23 54 30 C54 36 50 39 46 41 C43 42 42 44 42 48 L42 52', `${PREFIX}svg-question`); addDot(svg, 42, 61); } }; if (isSvgAniIcon(rawType)) { const name = getSvgAniIconName(rawType); icon.className = `${PREFIX}icon ${PREFIX}icon-svgAni`; icon.dataset.svgani = name; const container = el('div', `${PREFIX}svgAni`); icon.appendChild(container); applyIconSize('box'); applyIconBoxSize(); scheduleSvgAniPrefetch(); this._initSvgAniIcon(icon, name); return icon; } if (rawType === 'success') { icon.appendChild(el('div', `${PREFIX}success-circular-line-left`)); const mark = el('div', `${PREFIX}success-mark`); mark.appendChild(el('span', `${PREFIX}success-line-tip`)); mark.appendChild(el('span', `${PREFIX}success-line-long`)); icon.appendChild(mark); icon.appendChild(el('div', `${PREFIX}success-ring`)); icon.appendChild(el('div', `${PREFIX}success-fix`)); icon.appendChild(el('div', `${PREFIX}success-circular-line-right`)); applyIconSize('font'); return icon; } if (rawType === 'error' || rawType === 'warning' || rawType === 'info' || rawType === 'question') { appendRingParts(); applyIconSize('font'); applyIconBoxSize(); const svg = createInnerMarkSvg(); appendBuiltInInnerMark(svg); icon.appendChild(svg); return icon; } applyIconSize('box'); applyIconBoxSize(); const svg = createInnerMarkSvg(); const ring = svgEl('circle'); ring.setAttribute('class', `${PREFIX}svg-ring ${PREFIX}svg-draw`); ring.setAttribute('cx', '40'); ring.setAttribute('cy', '40'); ring.setAttribute('r', '34'); ring.setAttribute('stroke', 'currentColor'); svg.appendChild(ring); icon.appendChild(svg); return icon; } _normalizeSizeValue(v) { if (v === undefined || v === null) return null; if (typeof v === 'number' && Number.isFinite(v)) return v + 'px'; if (typeof v === 'string') { const s = v.trim(); return s ? s : null; } return null; } _applyPopupSize(options) { const popup = this.dom && this.dom.popup; if (!popup) return; const w = this._normalizeSizeValue(options && options.width); const h = this._normalizeSizeValue(options && options.height); if (!w && !h) { popup.style.width = ''; popup.style.height = ''; popup.style.maxWidth = ''; popup.style.overflow = ''; return; } if (w) { popup.style.width = w; popup.style.maxWidth = 'none'; } else { popup.style.width = ''; popup.style.maxWidth = ''; } if (h) popup.style.height = h; else popup.style.height = ''; popup.style.overflow = 'auto'; } _applyPopupTheme(options) { const popup = this.dom && this.dom.popup; if (!popup) return; const theme = normalizeTheme(options && options.theme); try { popup.classList.remove(`${PREFIX}theme-auto`, `${PREFIX}theme-light`, `${PREFIX}theme-dark`); } catch {} try { popup.classList.add(`${PREFIX}theme-${theme}`); } catch {} } _normalizeBackgroundSpec(raw) { if (!raw || typeof raw !== 'string') return null; const s = raw.trim(); if (!s) return null; if (s.startsWith(BG_SVG_PREFIX) && s.endsWith(')')) { const name = normalizeSvgAniName(s.slice(BG_SVG_PREFIX.length, -1)); if (!name) return null; return { type: 'svg', name, key: `svg:${name}` }; } if (s.startsWith(SVGANI_PREFIX)) { const name = normalizeSvgAniName(s); if (!name) return null; return { type: 'svg', name, key: `svg:${name}` }; } if (/^url\(/i.test(s)) { return { type: 'image', value: s, key: s }; } if (s.startsWith(BG_CSS_PREFIX) && s.endsWith(')')) { const css = s.slice(BG_CSS_PREFIX.length, -1).trim(); if (!css) return null; return { type: 'css', css, key: `css:${css}` }; } return { type: 'css', css: s, key: `css:${s}` }; } _isSameBackgroundSpec(a, b) { if (!a && !b) return true; if (!a || !b) return false; return a.key === b.key; } _insertBackgroundEl(bgEl) { const popup = this.dom && this.dom.popup; if (!popup || !bgEl) return; if (popup.firstChild) popup.insertBefore(bgEl, popup.firstChild); else popup.appendChild(bgEl); } _createBackgroundEl(spec) { const bg = el('div', `${PREFIX}bg`); bg.dataset.bgKey = spec.key; if (spec.type === 'image') { bg.style.backgroundImage = spec.value; } else if (spec.type === 'css') { try { bg.style.cssText += ';' + spec.css; } catch {} } else if (spec.type === 'svg') { const container = el('div', `${PREFIX}svgAni`); bg.appendChild(container); this._initSvgAniBackground(bg, spec.name); } return bg; } _applyBackgroundForOptions(options, { force = false } = {}) { const popup = this.dom && this.dom.popup; if (!popup) return; const spec = this._normalizeBackgroundSpec(options && options.background); if (!spec) { if (this.dom.background) { this._destroySvgAniBackground(this.dom.background); try { this.dom.background.remove(); } catch {} } this.dom.background = null; this._backgroundSpec = null; popup.style.background = ''; return; } if (!force && this._isSameBackgroundSpec(this._backgroundSpec, spec)) return; if (this.dom.background) { this._destroySvgAniBackground(this.dom.background); try { this.dom.background.remove(); } catch {} } const bgEl = this._createBackgroundEl(spec); this.dom.background = bgEl; this._backgroundSpec = spec; popup.style.background = 'transparent'; this._insertBackgroundEl(bgEl); } async _transitionBackground(nextSpec, direction) { const popup = this.dom && this.dom.popup; if (!popup) return; if (this._isSameBackgroundSpec(this._backgroundSpec, nextSpec)) return; const fromEl = this.dom.background; let toEl = null; if (nextSpec) { toEl = this._createBackgroundEl(nextSpec); this._insertBackgroundEl(toEl); popup.style.background = 'transparent'; } else { popup.style.background = ''; } const isNext = direction !== 'prev'; const enterFrom = isNext ? 100 : -100; const exitTo = isNext ? -100 : 100; const duration = 320; if (toEl) { toEl.style.transform = `translateX(${enterFrom}%)`; toEl.style.opacity = '0.2'; } if (fromEl) { fromEl.style.transform = 'translateX(0%)'; fromEl.style.opacity = '1'; } let fromAnim = null; let toAnim = null; try { if (fromEl && fromEl.animate) { fromAnim = fromEl.animate( [ { transform: 'translateX(0%)', opacity: 1 }, { transform: `translateX(${exitTo}%)`, opacity: 0.1 } ], { duration, easing: 'cubic-bezier(0.2, 0.9, 0.2, 1)', fill: 'forwards' } ); } } catch {} try { if (toEl && toEl.animate) { toAnim = toEl.animate( [ { transform: `translateX(${enterFrom}%)`, opacity: 0.2 }, { transform: 'translateX(0%)', opacity: 1 } ], { duration, easing: 'cubic-bezier(0.2, 0.9, 0.2, 1)', fill: 'forwards' } ); } } catch {} try { await Promise.all([ fromAnim && fromAnim.finished ? fromAnim.finished.catch(() => {}) : Promise.resolve(), toAnim && toAnim.finished ? toAnim.finished.catch(() => {}) : Promise.resolve() ]); } catch {} if (fromEl) { this._destroySvgAniBackground(fromEl); try { fromEl.remove(); } catch {} } if (toEl) { toEl.style.transform = ''; toEl.style.opacity = ''; } this.dom.background = toEl; this._backgroundSpec = nextSpec || null; } _destroySvgAniBackground(bgEl) { if (!bgEl) return; const inst = bgEl._svgAniInstance; if (inst && typeof inst.destroy === 'function') { try { inst.destroy(); } catch {} } try { bgEl._svgAniInstance = null; } catch {} } _initSvgAniBackground(bgEl, name) { if (!bgEl) return; const container = bgEl.querySelector(`.${PREFIX}svgAni`); if (!container) return; const cleanName = normalizeSvgAniName(name); if (!cleanName) return; const url = resolveSvgAniJsonUrl(cleanName); if (!url) return; const showError = (msg) => { try { container.textContent = String(msg || ''); container.classList.add(`${PREFIX}svgAni-error`); } catch {} }; const loadData = () => new Promise((resolve, reject) => { try { if (typeof fetch === 'function') { fetch(url).then((res) => { if (!res || !res.ok) throw new Error('fetch failed'); return res.json(); }).then(resolve).catch(reject); return; } const xhr = new XMLHttpRequest(); xhr.open('GET', url, true); xhr.responseType = 'json'; xhr.onload = () => { if (xhr.status >= 200 && xhr.status < 300) { const data = xhr.response || (xhr.responseText ? JSON.parse(xhr.responseText) : null); resolve(data); } else { reject(new Error('xhr failed')); } }; xhr.onerror = () => reject(new Error('xhr failed')); xhr.send(); } catch (e) { reject(e); } }); const boot = async () => { const svgAniLib = await ensureSvgAniLib(); if (!svgAniLib) { showError('SvgAni lib not available'); return; } try { const data = await loadData(); if (!data) throw new Error('empty json'); try { container.textContent = ''; } catch {} const anim = svgAniLib.loadAnimation({ container, renderer: 'svg', loop: true, autoplay: true, animationData: data, rendererSettings: { preserveAspectRatio: 'xMidYMid slice' } }); bgEl._svgAniInstance = anim; } catch (e) { showError('SvgAni load failed'); } }; boot(); } _adjustRingBackgroundColor() { try { const icon = this.dom && this.dom.icon; const popup = this.dom && this.dom.popup; if (!icon || !popup) return; const bg = getComputedStyle(popup).backgroundColor; const parts = icon.querySelectorAll(RING_BG_PARTS_SELECTOR); parts.forEach((el) => { try { el.style.backgroundColor = bg; } catch {} }); } catch {} } _didOpen() { if (this.params.closeOnEsc) { this._onKeydown = (e) => { if (!e) return; if (e.key === 'Escape') this._close(null, 'esc'); }; document.addEventListener('keydown', this._onKeydown); } this._adjustRingBackgroundColor(); if (this.params.popupAnimation) { if (this.dom.popup) { try { const popup = this.dom.popup; try { popup.classList.add(`${PREFIX}popup-anim-slide`); } catch {} const rect = popup.getBoundingClientRect(); 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)); popup.style.transition = 'none'; popup.style.willChange = 'transform, opacity'; if (popup.animate) { const anim = popup.animate( [ { transform: `translateY(${y0}px) scale(0.92)`, opacity: 0 }, { transform: 'translateY(0px) scale(1)', opacity: 1 } ], { duration: 220, easing: 'cubic-bezier(0.2, 0.9, 0.2, 1)', fill: 'forwards' } ); anim.finished .catch(() => {}) .finally(() => { try { popup.style.willChange = ''; } catch {} }); } else { const X = Layer._getXjs(); if (X) { popup.style.opacity = '0'; popup.style.transform = `translateY(${y0}px) scale(0.92)`; X(popup).animate({ y: [y0, 0], scale: [0.92, 1], opacity: [0, 1], duration: 220, easing: 'ease-out' }); } setTimeout(() => { try { popup.style.willChange = ''; } catch {} }, 260); } } catch {} } } if (!this.params.popupAnimation && !this._isReplace && this.dom.popup) { try { const popup = this.dom.popup; popup.style.transition = 'none'; popup.style.willChange = 'transform, opacity'; if (popup.animate) { const anim = popup.animate( [ { transform: 'scale(0.8)', opacity: 0, offset: 0, easing: 'ease-out' }, { transform: 'scale(1.1)', opacity: 1, offset: 0.67, easing: 'ease-in-out' }, { transform: 'scale(1)', opacity: 1, offset: 1 } ], { duration: 240, fill: 'forwards' } ); anim.finished .catch(() => {}) .finally(() => { try { popup.style.willChange = ''; } catch {} try { popup.style.transition = ''; } catch {} try { popup.style.opacity = ''; } catch {} try { popup.style.transform = ''; } catch {} }); } else { const X = Layer._getXjs(); if (X) { popup.style.opacity = '0'; popup.style.transform = 'scale(0.8)'; X(popup).animate({ scale: [0.8, 1.1], opacity: [0, 1], duration: 160, easing: 'ease-out' }); setTimeout(() => { try { X(popup).animate({ scale: [1.1, 1], duration: 80, easing: 'ease-in-out' }); } catch {} }, 160); setTimeout(() => { try { popup.style.willChange = ''; } catch {} try { popup.style.transition = ''; } catch {} try { popup.style.opacity = ''; } catch {} try { popup.style.transform = ''; } catch {} }, 270); } else { setTimeout(() => { try { popup.style.willChange = ''; } catch {} try { popup.style.transition = ''; } catch {} }, 260); } } } catch {} } 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 {} } if (this.params.iconAnimation) { this._animateIcon(); } try { if (typeof this.params.didOpen === 'function') this.params.didOpen(this.dom.popup); } catch {} } _animateIcon() { const icon = this.dom.icon; if (!icon) return; const type = (this.params && this.params.icon) || ''; if (isSvgAniIcon(type)) return; if (RING_TYPES.has(type)) this._adjustRingBackgroundColor(); try { icon.classList.remove(`${PREFIX}icon-show`); } catch {} requestAnimationFrame(() => { try { icon.classList.add(`${PREFIX}icon-show`); } catch {} }); if (type === 'success') return; const X = Layer._getXjs(); if (!X) return; const svg = icon.querySelector('svg'); if (!svg) return; const marks = Array.from(svg.querySelectorAll(`.${PREFIX}svg-mark`)); const dot = svg.querySelector(`.${PREFIX}svg-dot`); const baseDelay = RING_TYPES.has(type) ? 520 : 0; if (type === 'error') { 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; const gap = 60; 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 { marks.forEach((m, i) => { try { X(m).draw({ duration: 420, easing: 'ease-out', delay: baseDelay + i * 60 }); } catch {} }); } if (dot) { try { dot.style.opacity = '0'; 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 } }); } else { X(dot).animate({ opacity: [0, 1], scale: [0.2, 1], duration: 320, delay: d, easing: { stiffness: 320, damping: 18 } }); } } catch {} } } _forceDestroy(reason = 'replace') { try { this._destroySvgAniIcon(); } catch {} try { this._destroySvgAniBackground(this.dom && this.dom.background); } catch {} try { if (this._flowSteps && this._flowSteps.length) this._unmountFlowMounted(); else this._unmountDomContent(); } catch {} if (this._onKeydown) { try { document.removeEventListener('keydown', this._onKeydown); } catch {} this._onKeydown = null; } try { if (this.resolve) { this.resolve({ isConfirmed: false, isDenied: false, isDismissed: true, dismiss: reason }); } } catch {} } _close(isConfirmed, reason) { if (this._isClosing) return; this._isClosing = true; try { this._destroySvgAniIcon(); } catch {} try { this._destroySvgAniBackground(this.dom && this.dom.background); } 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) { doUnmount(); } let customClose = false; 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; 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); } } catch {} if (this._onKeydown) { try { document.removeEventListener('keydown', this._onKeydown); } catch {} this._onKeydown = null; } try { if (typeof this.params.willClose === 'function') this.params.willClose(this.dom.popup); } catch {} try { if (typeof this.params.didClose === 'function') this.params.didClose(); } catch {} const value = (this._flowSteps && this._flowSteps.length) ? (this._flowValues || []) : undefined; let data; if (this._flowSteps && this._flowSteps.length && this._flowAutoForm && this._flowAutoForm.enabled) { const root = (this.dom && (this.dom.stepStack || this.dom.content || this.dom.popup)) || null; data = this._collectFormData(root); } if (isConfirmed === true) { const payload = { isConfirmed: true, isDenied: false, isDismissed: false, value }; if (data !== undefined) payload.data = data; this.resolve(payload); } else if (isConfirmed === false) { const payload = { isConfirmed: false, isDenied: false, isDismissed: true, dismiss: reason || 'cancel', value }; if (data !== undefined) payload.data = data; this.resolve(payload); } else { const payload = { isConfirmed: false, isDenied: false, isDismissed: true, dismiss: reason || 'backdrop', value }; if (data !== undefined) payload.data = data; this.resolve(payload); } } _assignFormValue(data, name, value, forceArray) { if (!data || !name) return; let key = name; let asArray = !!forceArray; if (key.endsWith('[]')) { key = key.slice(0, -2); asArray = true; } if (!(key in data)) { data[key] = asArray ? [value] : value; return; } if (Array.isArray(data[key])) { data[key].push(value); return; } data[key] = [data[key], value]; } _collectFormData(root) { const data = {}; if (!root || !root.querySelectorAll) return data; const fields = root.querySelectorAll('input, select, textarea'); fields.forEach((el) => { try { if (!el || el.disabled) return; const name = (el.getAttribute('name') || '').trim(); if (!name) return; const tag = (el.tagName || '').toLowerCase(); if (tag === 'select') { if (el.multiple) { const values = Array.from(el.options || []).filter((o) => o.selected).map((o) => o.value); values.forEach((v) => this._assignFormValue(data, name, v, true)); } else { this._assignFormValue(data, name, el.value, false); } return; } if (tag === 'textarea') { this._assignFormValue(data, name, el.value, false); return; } const type = (el.getAttribute('type') || 'text').toLowerCase(); if (type === 'radio') { if (el.checked) this._assignFormValue(data, name, el.value, false); return; } if (type === 'checkbox') { if (el.checked) this._assignFormValue(data, name, el.value, true); return; } if (type === 'file') { const files = el.files ? Array.from(el.files) : []; this._assignFormValue(data, name, files, true); return; } this._assignFormValue(data, name, el.value, false); } catch {} }); return data; } _normalizeDomSpec(params) { const p = params || {}; let dom = p.dom; let mode = p.domMode || 'move'; if (dom == null && p.content != null) { if (typeof p.content === 'object' && !(p.content instanceof Element)) { if (p.content && (p.content.dom != null || p.content.selector != null)) { dom = (p.content.dom != null) ? p.content.dom : p.content.selector; if (p.content.mode) mode = p.content.mode; if (p.content.clone === true) mode = 'clone'; } } else { dom = p.content; } } if (dom == null) return null; return { dom, mode }; } _mountDomContentInto(target, domSpec, opts = null) { const collectFlow = !!(opts && opts.collectFlow); const originalContent = this.dom.content; try { this.dom.content = target; } catch {} try { const before = this._mounted; this._mountDomContent(domSpec); const rec = this._mounted; if (collectFlow && rec && rec.kind === 'move') { this._flowMountedList.push(rec); this._mounted = before; // restore previous single record (usually null) } } finally { try { this.dom.content = originalContent; } catch {} } } _unmountFlowMounted() { const list = Array.isArray(this._flowMountedList) ? this._flowMountedList : []; this._flowMountedList = []; list.forEach((m) => { try { if (!m || m.kind !== 'move') return; const prev = this._mounted; this._mounted = m; this._unmountDomContent(); this._mounted = prev; } catch {} }); } _mountDomContent(domSpec) { try { if (!this.dom.content) return; this._unmountDomContent(); // ensure only one mount at a time const forceVisible = (el) => { if (!el) return { prevHidden: false, prevInlineDisplay: '' }; const prevHidden = !!el.hidden; const prevInlineDisplay = (el.style && typeof el.style.display === 'string') ? el.style.display : ''; try { el.hidden = false; } catch {} try { const cs = (typeof getComputedStyle === 'function') ? getComputedStyle(el) : null; const display = cs ? String(cs.display || '') : ''; if (display === 'none') el.style.display = 'block'; else if (el.style && el.style.display === 'none') el.style.display = 'block'; } catch {} return { prevHidden, prevInlineDisplay }; }; let node = domSpec.dom; if (typeof node === 'string') node = document.querySelector(node); if (!node) return; if (typeof HTMLTemplateElement !== 'undefined' && node instanceof HTMLTemplateElement) { const frag = node.content.cloneNode(true); this.dom.content.appendChild(frag); this._mounted = { kind: 'template' }; return; } if (!(node instanceof Element)) return; if (domSpec.mode === 'clone') { const clone = node.cloneNode(true); try { clone.hidden = false; } catch {} try { const cs = (typeof getComputedStyle === 'function') ? getComputedStyle(node) : null; const display = cs ? String(cs.display || '') : ''; if (display === 'none') clone.style.display = 'block'; } catch {} this.dom.content.appendChild(clone); this._mounted = { kind: 'clone' }; return; } const placeholder = document.createComment('layer-dom-placeholder'); const parent = node.parentNode; const nextSibling = node.nextSibling; if (!parent) { const clone = node.cloneNode(true); try { clone.hidden = false; } catch {} try { if (clone.style && clone.style.display === 'none') clone.style.display = ''; } catch {} this.dom.content.appendChild(clone); this._mounted = { kind: 'clone' }; return; } try { parent.insertBefore(placeholder, nextSibling); } catch {} const { prevHidden, prevInlineDisplay } = forceVisible(node); this.dom.content.appendChild(node); this._mounted = { kind: 'move', originalEl: node, placeholder, parent, nextSibling, prevHidden, prevInlineDisplay }; } catch { } } _unmountDomContent() { const m = this._mounted; if (!m) return; this._mounted = null; if (m.kind !== 'move') return; const node = m.originalEl; if (!node) return; try { node.hidden = !!m.prevHidden; } catch {} try { if (node.style && typeof m.prevInlineDisplay === 'string') node.style.display = m.prevInlineDisplay; } catch {} try { const ph = m.placeholder; if (ph && ph.parentNode) { ph.parentNode.insertBefore(node, ph); ph.parentNode.removeChild(ph); return; } } catch {} try { if (m.parent) m.parent.appendChild(node); } catch {} } _setButtonsDisabled(disabled) { try { if (this.dom && this.dom.confirmBtn) this.dom.confirmBtn.disabled = !!disabled; if (this.dom && this.dom.cancelBtn) this.dom.cancelBtn.disabled = !!disabled; } catch {} } _normalizeButtonIcon(icon) { if (!icon) return null; try { if (icon && icon.nodeType) return icon.cloneNode(true); } catch {} if (typeof icon === 'string') { const s = icon.trim(); if (!s) return null; if (s.indexOf('<') !== -1 && s.indexOf('>') !== -1) { const wrap = document.createElement('span'); wrap.innerHTML = s; return wrap; } return document.createTextNode(s); } return null; } _setButtonContent(btn, text, icon, iconPosition) { if (!btn) return; const labelText = (text === undefined || text === null) ? '' : String(text); if (!icon) { try { btn.textContent = labelText; } catch {} return; } while (btn.firstChild) btn.removeChild(btn.firstChild); const iconNode = this._normalizeButtonIcon(icon); const iconWrap = el('span', `${PREFIX}btn-icon`); if (iconNode) iconWrap.appendChild(iconNode); const label = el('span', `${PREFIX}btn-label`, labelText); const atEnd = iconPosition === 'end'; if (atEnd) { btn.appendChild(label); btn.appendChild(iconWrap); } else { btn.appendChild(iconWrap); btn.appendChild(label); } } async _handleConfirm() { if (this._flowSteps && this._flowSteps.length) { return this._flowNext(); } const pre = this.params && this.params.preConfirm; if (typeof pre === 'function') { try { this._setButtonsDisabled(true); const r = pre(this.dom && this.dom.popup); const v = (r && typeof r.then === 'function') ? await r : r; if (v === false) { this._setButtonsDisabled(false); return; } this._flowValues = [v]; } catch (e) { console.error(e); this._setButtonsDisabled(false); return; } } this._close(true, 'confirm'); } _handleCancel() { if (this._flowSteps && this._flowSteps.length) { if (this._flowIndex > 0) { this._flowPrev(); return; } } this._close(false, 'cancel'); } _getFlowStepOptions(index) { const step = (this._flowSteps && this._flowSteps[index]) ? this._flowSteps[index] : {}; const base = this._flowBase || {}; const merged = { ...base, ...step }; const isLast = index >= (this._flowSteps.length - 1); if (!('confirmButtonText' in step)) merged.confirmButtonText = isLast ? (base.confirmButtonText || 'OK') : 'Next'; if (index > 0) { if (!('showCancelButton' in step)) merged.showCancelButton = true; if (!('cancelButtonText' in step)) merged.cancelButtonText = 'Back'; } else { if (!('cancelButtonText' in step) && merged.showCancelButton) merged.cancelButtonText = merged.cancelButtonText || 'Cancel'; } 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; const chosenIcon = (explicitIcon !== undefined) ? explicitIcon : baseIcon; const isContainerStep = !!(step && step.__fromContainer); const allowContainerIcon = isContainerStep && explicitIcon !== undefined; if (!isSummary && !isLast) { if (allowContainerIcon) { merged.icon = chosenIcon; } else { merged.icon = (chosenIcon === 'question') ? 'question' : null; merged.iconAnimation = false; } } else if (!isSummary && isLast) { if (allowContainerIcon) { merged.icon = chosenIcon; } else { merged.icon = (chosenIcon === 'question') ? 'question' : null; merged.iconAnimation = false; } } else { if (!('icon' in step) && chosenIcon === undefined) merged.icon = null; } if (allowContainerIcon) { if (!('iconAnimation' in step) && !('iconAnimation' in base)) { merged.iconAnimation = true; } } return merged; } async _flowNext() { const idx = this._flowIndex; const total = this._flowSteps.length; const isLast = idx >= (total - 1); const pre = this.params && this.params.preConfirm; if (typeof pre === 'function') { try { this._setButtonsDisabled(true); const r = pre(this.dom && this.dom.popup, idx); const v = (r && typeof r.then === 'function') ? await r : r; if (v === false) { this._setButtonsDisabled(false); return; } this._flowValues[idx] = v; } catch (e) { console.error(e); this._setButtonsDisabled(false); return; } } if (isLast) { this._close(true, 'confirm'); return; } this._setButtonsDisabled(false); await this._flowGo(idx + 1, 'next'); } async _flowPrev() { const idx = this._flowIndex; if (idx <= 0) return; await this._flowGo(idx - 1, 'prev'); } async _flowGo(index, direction) { const next = (this._flowResolved && this._flowResolved[index]) ? this._flowResolved[index] : this._getFlowStepOptions(index); await this._transitionToFlow(index, next, direction); } async _transitionToFlow(nextIndex, nextOptions, direction) { const popup = this.dom && this.dom.popup; const content = this.dom && this.dom.content; const panes = this.dom && this.dom.stepPanes; if (!popup || !content || !panes || !panes.length) { this._flowIndex = nextIndex; this.params = nextOptions; this._render({ flow: true }); return; } const fromIndex = this._flowIndex; const fromPane = panes[fromIndex]; const toPane = panes[nextIndex]; if (!fromPane || !toPane) { this._flowIndex = nextIndex; this.params = nextOptions; this._render({ flow: true }); return; } const oldH = content.getBoundingClientRect().height; const prevDisplay = toPane.style.display; const prevPos = toPane.style.position; const prevVis = toPane.style.visibility; const prevPointer = toPane.style.pointerEvents; toPane.style.display = ''; toPane.style.position = 'absolute'; toPane.style.visibility = 'hidden'; toPane.style.pointerEvents = 'none'; toPane.style.left = '0'; toPane.style.right = '0'; const newH = toPane.getBoundingClientRect().height; toPane.style.position = prevPos; toPane.style.visibility = prevVis; toPane.style.pointerEvents = prevPointer; toPane.style.display = prevDisplay; // usually 'none' this._flowIndex = nextIndex; this.params = nextOptions; this._applyPopupTheme(this.params); const isNext = direction !== 'prev'; const enterFrom = isNext ? 100 : -100; const exitTo = isNext ? -100 : 100; const slideDuration = 320; let titleAnim = null; let titleCleanup = null; try { 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); const bgTransition = this._transitionBackground(nextBgSpec, direction); this._applyPopupSize(this.params); try { const wantsIcon = !!this.params.icon; if (!wantsIcon && this.dom.icon) { this._destroySvgAniIcon(); this.dom.icon.remove(); this.dom.icon = null; } else if (wantsIcon) { const same = this._isSameIconType(this.dom.icon, this.params.icon); if (!this.dom.icon || !same) { if (this.dom.icon) { this._destroySvgAniIcon(); this.dom.icon.remove(); } this.dom.icon = this._createIcon(this.params.icon); popup.insertBefore(this.dom.icon, this.dom.title || popup.firstChild); } } } catch {} this._updateFlowActions(); toPane.style.display = ''; toPane.style.position = 'absolute'; toPane.style.left = '0'; toPane.style.right = '0'; toPane.style.top = '0'; toPane.style.transform = `translateX(${enterFrom}%)`; toPane.style.opacity = '0'; toPane.style.pointerEvents = 'none'; fromPane.style.position = 'absolute'; fromPane.style.left = '0'; fromPane.style.right = '0'; fromPane.style.top = '0'; fromPane.style.transform = 'translateX(0%)'; fromPane.style.opacity = '1'; fromPane.style.pointerEvents = 'none'; content.style.height = oldH + 'px'; content.style.overflow = 'hidden'; 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 {} try { void toPane.offsetWidth; } catch {} await new Promise((resolve) => requestAnimationFrame(resolve)); try { if (fromPane.animate && toPane.animate) { fromAnim = fromPane.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' } ); toAnim = toPane.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' } ); } 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 {} }; 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 {} try { await Promise.all([ 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(), titleAnim && typeof titleAnim.then === 'function' ? titleAnim.catch(() => {}) : Promise.resolve() ]); } catch {} try { if (titleCleanup) titleCleanup(); } catch {} try { if (slideCleanup) slideCleanup(); } catch {} fromPane.style.display = 'none'; fromPane.style.position = ''; fromPane.style.left = ''; fromPane.style.right = ''; fromPane.style.top = ''; fromPane.style.transform = ''; fromPane.style.opacity = ''; fromPane.style.pointerEvents = ''; toPane.style.position = 'relative'; toPane.style.left = ''; toPane.style.right = ''; toPane.style.top = ''; toPane.style.transform = ''; toPane.style.opacity = ''; toPane.style.pointerEvents = ''; content.style.height = ''; content.style.overflow = ''; try { this._adjustRingBackgroundColor(); } catch {} } _isSameIconType(iconEl, type) { if (!iconEl) return false; const raw = String(type || '').trim(); if (isSvgAniIcon(raw)) { const name = getSvgAniIconName(raw); return iconEl.dataset && iconEl.dataset.svgani === name; } try { return iconEl.classList && iconEl.classList.contains(raw); } catch { return false; } } _destroySvgAniIcon() { const icon = this.dom && this.dom.icon; if (!icon) return; const inst = icon._svgAniInstance; if (inst && typeof inst.destroy === 'function') { try { inst.destroy(); } catch {} } try { icon._svgAniInstance = null; } catch {} } _initSvgAniIcon(iconEl, name) { if (!iconEl) return; const container = iconEl.querySelector(`.${PREFIX}svgAni`); if (!container) return; const cleanName = String(name || '').trim(); if (!cleanName) return; const url = resolveSvgAniJsonUrl(cleanName); if (!url) return; const wantsLoop = (this.params && typeof this.params.iconLoop === 'boolean') ? this.params.iconLoop : true; const wantsAutoplay = !(this.params && this.params.iconAnimation === false); const showError = (msg) => { try { container.textContent = String(msg || ''); container.classList.add(`${PREFIX}svgAni-error`); } catch {} }; const loadData = () => new Promise((resolve, reject) => { try { if (typeof fetch === 'function') { fetch(url).then((res) => { if (!res || !res.ok) throw new Error('fetch failed'); return res.json(); }).then(resolve).catch(reject); return; } const xhr = new XMLHttpRequest(); xhr.open('GET', url, true); xhr.responseType = 'json'; xhr.onload = () => { if (xhr.status >= 200 && xhr.status < 300) { const data = xhr.response || (xhr.responseText ? JSON.parse(xhr.responseText) : null); resolve(data); } else { reject(new Error('xhr failed')); } }; xhr.onerror = () => reject(new Error('xhr error')); xhr.send(); } catch (e) { reject(e); } }); (async () => { const svgAniLib = await ensureSvgAniLib(); if (!svgAniLib) { showError('SvgAni lib not loaded.'); return; } const connected = await waitForConnected(iconEl); if (!connected) return; let data = null; try { data = await loadData(); } catch {} if (!data) { showError('Failed to load animation.'); return; } if (!iconEl.isConnected) return; try { const anim = svgAniLib.loadAnimation({ container, renderer: 'svg', loop: wantsLoop, autoplay: wantsAutoplay, animationData: data }); iconEl._svgAniInstance = anim; if (!wantsAutoplay && anim && typeof anim.goToAndStop === 'function') { anim.goToAndStop(0, true); } } catch { showError('Failed to init animation.'); } })(); } _rerenderInside() { this._destroySvgAniIcon(); const popup = this._clearPopup(); if (!popup) return; this._applyPopupTheme(this.params); this.dom.icon = null; if (this.params.icon) { this.dom.icon = this._createIcon(this.params.icon); popup.appendChild(this.dom.icon); } this.dom.title = null; if (this.params.title) { this.dom.title = el('h2', `${PREFIX}title`, this.params.title); popup.appendChild(this.dom.title); } this.dom.content = null; if (this.params.text || this.params.html || this.params.content || this.params.dom) { this.dom.content = el('div', `${PREFIX}content`); const domSpec = this._normalizeDomSpec(this.params); if (domSpec) this._mountDomContent(domSpec); else if (this.params.html) this.dom.content.innerHTML = this.params.html; else this.dom.content.textContent = this.params.text; popup.appendChild(this.dom.content); } this.dom.actions = el('div', `${PREFIX}actions`); this.dom.cancelBtn = null; 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); } 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); popup.appendChild(this.dom.actions); try { if (typeof this.params.didOpen === 'function') this.params.didOpen(this.dom.popup); } catch {} } } return Layer; })));