(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') { return LayerClass.$(options); } 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) => { if (k === 'prototype' || k === 'name' || k === 'length') return; const desc = Object.getOwnPropertyDescriptor(from, k); if (!desc) return; 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 : typeof define === 'function' && define.amd ? define(() => LayerFactory) : (global.Layer = LayerFactory); }(this, (function () { 'use strict'; const PREFIX = 'layer-'; // Theme & Styles const css = ` .${PREFIX}overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.4); display: flex; justify-content: center; align-items: center; z-index: 1000; opacity: 0; transition: opacity 0.3s; } .${PREFIX}overlay.show { opacity: 1; } .${PREFIX}popup { background: #fff; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); width: 32em; max-width: 90%; padding: 1.5em; display: flex; flex-direction: column; align-items: center; position: relative; z-index: 1; transform: scale(0.92); transition: transform 0.26s ease; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; } .${PREFIX}overlay.show .${PREFIX}popup { transform: scale(1); } .${PREFIX}title { font-size: 1.8em; font-weight: 600; color: #333; margin: 0 0 0.5em; text-align: center; } .${PREFIX}content { font-size: 1.125em; color: #545454; margin-bottom: 1.5em; text-align: center; line-height: 1.5; } .${PREFIX}actions { display: flex; justify-content: center; gap: 1em; width: 100%; } .${PREFIX}button { border: none; border-radius: 4px; padding: 0.6em 1.2em; font-size: 1em; cursor: pointer; transition: background-color 0.2s, box-shadow 0.2s; color: #fff; } .${PREFIX}confirm { background-color: #3085d6; } .${PREFIX}confirm:hover { background-color: #2b77c0; } .${PREFIX}cancel { background-color: #aaa; } .${PREFIX}cancel:hover { background-color: #999; } .${PREFIX}icon { position: relative; box-sizing: content-box; width: 5em; height: 5em; margin: 0.5em auto 1.8em; border: 0.25em solid rgba(0, 0, 0, 0); border-radius: 50%; font-family: inherit; line-height: 5em; cursor: default; user-select: none; /* Ring sweep angles (match SweetAlert2 success feel) */ --${PREFIX}ring-start-rotate: -45deg; /* End is one full turn from start so it "sweeps then settles" */ --${PREFIX}ring-end-rotate: -405deg; } /* SVG mark content (kept), sits above the ring */ .${PREFIX}icon svg { width: 100%; height: 100%; display: block; overflow: visible; position: relative; z-index: 3; } .${PREFIX}svg-ring, .${PREFIX}svg-mark { fill: none; stroke-linecap: round; stroke-linejoin: round; } .${PREFIX}svg-ring { stroke-width: 4.5; opacity: 0.95; } .${PREFIX}svg-mark { stroke-width: 6; } .${PREFIX}svg-dot { transform-box: fill-box; transform-origin: center; } /* ========================================= "success-like" ring (shared by all icons) - we reuse the success ring pieces/rotation for every icon type - color is driven by currentColor ========================================= */ .${PREFIX}icon.success { border-color: #a5dc86; color: #a5dc86; } .${PREFIX}icon.error { border-color: #f27474; color: #f27474; } .${PREFIX}icon.warning { border-color: #f8bb86; color: #f8bb86; } .${PREFIX}icon.info { border-color: #3fc3ee; color: #3fc3ee; } .${PREFIX}icon.question { border-color: #b18cff; color: #b18cff; } /* Keep ring sweep logic identical across built-in icons (match success). */ .${PREFIX}icon .${PREFIX}success-ring { position: absolute; z-index: 2; top: -0.25em; left: -0.25em; box-sizing: content-box; width: 100%; height: 100%; border: 0.25em solid currentColor; opacity: 0.3; border-radius: 50%; } .${PREFIX}icon .${PREFIX}success-fix { position: absolute; z-index: 1; top: 0.5em; left: 1.625em; width: 0.4375em; height: 5.625em; transform: rotate(-45deg); background-color: #fff; /* adjusted at runtime to popup bg */ } .${PREFIX}icon .${PREFIX}success-circular-line-left, .${PREFIX}icon .${PREFIX}success-circular-line-right { position: absolute; width: 3.75em; height: 7.5em; border-radius: 50%; background-color: #fff; /* adjusted at runtime to popup bg */ } .${PREFIX}icon .${PREFIX}success-circular-line-left { top: -0.4375em; left: -2.0635em; transform: rotate(-45deg); transform-origin: 3.75em 3.75em; border-radius: 7.5em 0 0 7.5em; } .${PREFIX}icon .${PREFIX}success-circular-line-right { top: -0.6875em; left: 1.875em; transform: rotate(-45deg); transform-origin: 0 3.75em; border-radius: 0 7.5em 7.5em 0; } /* Clip ONLY the success tick (do not clip the ring sweep masks) */ .${PREFIX}icon .${PREFIX}success-mark { position: absolute; inset: 0; border-radius: 50%; overflow: hidden; z-index: 3; pointer-events: none; } .${PREFIX}icon .${PREFIX}success-line-tip, .${PREFIX}icon .${PREFIX}success-line-long { display: block; position: absolute; z-index: 1; /* inside success-mark; keep above its clipping bg */ height: 0.3125em; border-radius: 0.125em; background-color: currentColor; } .${PREFIX}icon .${PREFIX}success-line-tip { top: 2.875em; left: 0.8125em; width: 1.5625em; transform: rotate(45deg); } .${PREFIX}icon .${PREFIX}success-line-long { top: 2.375em; right: 0.5em; width: 2.9375em; transform: rotate(-45deg); } /* Triggered when icon has .${PREFIX}icon-show */ .${PREFIX}icon.${PREFIX}icon-show .${PREFIX}success-line-tip { animation: ${PREFIX}animate-success-line-tip 0.75s; } .${PREFIX}icon.${PREFIX}icon-show .${PREFIX}success-line-long { animation: ${PREFIX}animate-success-line-long 0.75s; } .${PREFIX}icon.${PREFIX}icon-show .${PREFIX}success-circular-line-right { animation: ${PREFIX}rotate-icon-circular-line 4.25s ease-in; } @keyframes ${PREFIX}animate-success-line-tip { 0% { top: 1.1875em; left: 0.0625em; width: 0; } 54% { top: 1.0625em; left: 0.125em; width: 0; } 70% { top: 2.1875em; left: -0.375em; width: 3.125em; } 84% { top: 3em; left: 1.3125em; width: 1.0625em; } 100% { top: 2.8125em; left: 0.8125em; width: 1.5625em; } } @keyframes ${PREFIX}animate-success-line-long { 0% { top: 3.375em; right: 2.875em; width: 0; } 65% { top: 3.375em; right: 2.875em; width: 0; } 84% { top: 2.1875em; right: 0; width: 3.4375em; } 100% { top: 2.375em; right: 0.5em; width: 2.9375em; } } @keyframes ${PREFIX}rotate-icon-circular-line { 0% { transform: rotate(var(--${PREFIX}ring-start-rotate)); } 5% { transform: rotate(var(--${PREFIX}ring-start-rotate)); } 12% { transform: rotate(var(--${PREFIX}ring-end-rotate)); } 100% { transform: rotate(var(--${PREFIX}ring-end-rotate)); } /* match 12% so it "sweeps then stops" */ } /* (Other icon shapes stay SVG-driven; only the ring animation is unified above) */ `; // Inject Styles const injectStyles = () => { if (document.getElementById(`${PREFIX}styles`)) return; const styleSheet = document.createElement('style'); styleSheet.id = `${PREFIX}styles`; styleSheet.textContent = css; document.head.appendChild(styleSheet); }; class Layer { constructor() { injectStyles(); this.params = {}; this.dom = {}; this.promise = null; this.resolve = null; this.reject = null; this._onKeydown = null; } // Constructor helper when called as function: const popup = Layer({...}) static get isProxy() { return true; } // Static entry point static fire(options) { const instance = new Layer(); return instance._fire(options); } // Chainable entry point (builder-style) // Example: // Layer.$({ title: 'Hi' }).fire().then(...) // Layer.$().config({ title: 'Hi' }).fire() static $(options) { const instance = new Layer(); if (options !== undefined) instance.config(options); return instance; } // Chainable config helper (does not render until `.fire()` is called) config(options = {}) { // Support the same shorthand as Layer.fire(title, text, icon) if (typeof options === 'string') { options = { title: options }; if (arguments[1]) options.text = arguments[1]; if (arguments[2]) options.icon = arguments[2]; } this.params = { ...(this.params || {}), ...options }; return this; } // Instance entry point (chainable) fire(options) { const merged = (options === undefined) ? (this.params || {}) : options; return this._fire(merged); } _fire(options = {}) { if (typeof options === 'string') { options = { title: options }; if (arguments[1]) options.text = arguments[1]; if (arguments[2]) options.icon = arguments[2]; } this.params = { title: '', text: '', icon: null, iconSize: null, // e.g. '6em' / '72px' confirmButtonText: 'OK', cancelButtonText: 'Cancel', showCancelButton: false, confirmButtonColor: '#3085d6', cancelButtonColor: '#aaa', closeOnClickOutside: true, closeOnEsc: true, iconAnimation: true, popupAnimation: true, ...options }; this.promise = new Promise((resolve, reject) => { this.resolve = resolve; this.reject = reject; }); this._render(); return this.promise; } static _getXjs() { // Prefer xjs, fallback to animal (compat) const g = (typeof window !== 'undefined') ? window : (typeof globalThis !== 'undefined' ? globalThis : null); if (!g) return null; const x = g.xjs || g.animal; return (typeof x === 'function') ? x : null; } _render() { // Remove existing if any const existing = document.querySelector(`.${PREFIX}overlay`); if (existing) existing.remove(); // Create Overlay this.dom.overlay = document.createElement('div'); this.dom.overlay.className = `${PREFIX}overlay`; // Create Popup this.dom.popup = document.createElement('div'); this.dom.popup.className = `${PREFIX}popup`; this.dom.overlay.appendChild(this.dom.popup); // 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 = document.createElement('h2'); this.dom.title.className = `${PREFIX}title`; this.dom.title.textContent = 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.dom.content = document.createElement('div'); this.dom.content.className = `${PREFIX}content`; if (this.params.content) { // DOM Element or Selector let el = this.params.content; if (typeof el === 'string') el = document.querySelector(el); if (el instanceof Element) this.dom.content.appendChild(el); } 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); } // Actions this.dom.actions = document.createElement('div'); this.dom.actions.className = `${PREFIX}actions`; // Cancel Button if (this.params.showCancelButton) { this.dom.cancelBtn = document.createElement('button'); this.dom.cancelBtn.className = `${PREFIX}button ${PREFIX}cancel`; this.dom.cancelBtn.textContent = this.params.cancelButtonText; this.dom.cancelBtn.style.backgroundColor = this.params.cancelButtonColor; this.dom.cancelBtn.onclick = () => this._close(false); this.dom.actions.appendChild(this.dom.cancelBtn); } // Confirm Button this.dom.confirmBtn = document.createElement('button'); this.dom.confirmBtn.className = `${PREFIX}button ${PREFIX}confirm`; this.dom.confirmBtn.textContent = this.params.confirmButtonText; this.dom.confirmBtn.style.backgroundColor = this.params.confirmButtonColor; this.dom.confirmBtn.onclick = () => this._close(true); this.dom.actions.appendChild(this.dom.confirmBtn); this.dom.popup.appendChild(this.dom.actions); // Event Listeners if (this.params.closeOnClickOutside) { this.dom.overlay.addEventListener('click', (e) => { if (e.target === this.dom.overlay) { this._close(null); // Dismiss } }); } document.body.appendChild(this.dom.overlay); // Animation requestAnimationFrame(() => { this.dom.overlay.classList.add('show'); this._didOpen(); }); } _createIcon(type) { const icon = document.createElement('div'); icon.className = `${PREFIX}icon ${type}`; const applyIconSize = (mode) => { if (!(this.params && this.params.iconSize)) return; try { const raw = this.params.iconSize; const s = String(raw).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) { 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; } } } // Fallback: directly size the box (works great for SVG icons) icon.style.width = s; icon.style.height = s; } catch {} }; const svgNs = 'http://www.w3.org/2000/svg'; if (type === 'success') { // SweetAlert2-compatible DOM structure (circle + tick) for exact style parity //
//