(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 Object.assign(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; 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 { width: 5em; height: 5em; margin: 1.5em auto 1.2em; position: relative; display: grid; place-items: center; } /* SVG Icon (SweetAlert-like, animated via xjs.draw + spring) */ .${PREFIX}icon svg { width: 100%; height: 100%; display: block; overflow: visible; } .${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; } .${PREFIX}icon.success { color: #a5dc86; } .${PREFIX}icon.error { color: #f27474; } .${PREFIX}icon.warning { color: #f8bb86; } .${PREFIX}icon.info { color: #3fc3ee; } `; // 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, 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 svgNs = 'http://www.w3.org/2000/svg'; const svg = document.createElementNS(svgNs, 'svg'); svg.setAttribute('viewBox', '0 0 80 80'); svg.setAttribute('aria-hidden', 'true'); svg.setAttribute('focusable', 'false'); const ring = document.createElementNS(svgNs, '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); const addPath = (d, extraClass) => { const p = document.createElementNS(svgNs, '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; }; if (type === 'success') { addPath('M23 41 L34.5 53 L58 29', `${PREFIX}svg-success`); } else if (type === 'error') { addPath('M28 28 L52 52', `${PREFIX}svg-error-left`); addPath('M52 28 L28 52', `${PREFIX}svg-error-right`); } else if (type === 'warning') { addPath('M40 20 L40 46', `${PREFIX}svg-warning-line`); const dot = document.createElementNS(svgNs, 'circle'); dot.setAttribute('class', `${PREFIX}svg-dot`); dot.setAttribute('cx', '40'); dot.setAttribute('cy', '58'); dot.setAttribute('r', '3.2'); dot.setAttribute('fill', 'currentColor'); svg.appendChild(dot); } else if (type === 'info') { addPath('M40 34 L40 56', `${PREFIX}svg-info-line`); const dot = document.createElementNS(svgNs, 'circle'); dot.setAttribute('class', `${PREFIX}svg-dot`); dot.setAttribute('cx', '40'); dot.setAttribute('cy', '25'); dot.setAttribute('r', '3.2'); dot.setAttribute('fill', 'currentColor'); svg.appendChild(dot); } else { // Fallback to info-like ring only } icon.appendChild(svg); return icon; } _didOpen() { // Keyboard close (ESC) if (this.params.closeOnEsc) { this._onKeydown = (e) => { if (!e) return; if (e.key === 'Escape') this._close(null); }; document.addEventListener('keydown', this._onKeydown); } // Popup animation (optional) if (this.params.popupAnimation) { const X = Layer._getXjs(); if (X && this.dom.popup) { // Override the CSS scale transition with a spring-ish entrance. try { this.dom.popup.style.transition = 'none'; this.dom.popup.style.transform = 'scale(0.92)'; X(this.dom.popup).animate({ scale: [0.92, 1], y: [-6, 0], duration: 520, easing: { stiffness: 260, damping: 16 } }); } 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 {} } _animateIcon() { const icon = this.dom.icon; if (!icon) return; const X = Layer._getXjs(); if (!X) return; try { X(icon).animate({ opacity: [0, 1], scale: [0.68, 1], duration: 520, easing: { stiffness: 240, damping: 14 } }); } catch {} const svg = icon.querySelector('svg'); if (!svg) return; const type = (this.params && this.params.icon) || ''; const ring = svg.querySelector(`.${PREFIX}svg-ring`); const marks = Array.from(svg.querySelectorAll(`.${PREFIX}svg-mark`)); const dot = svg.querySelector(`.${PREFIX}svg-dot`); // draw ring first try { if (ring) X(ring).draw({ duration: 520, easing: 'ease-in-out', delay: 60 }); } catch {} if (type === 'error') { // two lines staggered const left = marks[0]; const right = marks[1]; try { if (left) X(left).draw({ duration: 360, easing: 'ease-out', delay: 180 }); } catch {} try { if (right) X(right).draw({ duration: 360, easing: 'ease-out', delay: 250 }); } catch {} return; } // single mark (success/warning/info) marks.forEach((m, i) => { try { X(m).draw({ duration: 420, easing: 'ease-out', delay: 180 + i * 60 }); } catch {} }); if (dot) { try { dot.style.opacity = '0'; X(dot).animate({ opacity: [0, 1], scale: [0.2, 1], duration: 320, delay: 280, easing: { stiffness: 320, damping: 18 } }); } catch {} } } _close(isConfirmed) { this.dom.overlay.classList.remove('show'); setTimeout(() => { if (this.dom.overlay && this.dom.overlay.parentNode) { this.dom.overlay.parentNode.removeChild(this.dom.overlay); } }, 300); 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 {} if (isConfirmed === true) { this.resolve({ isConfirmed: true, isDenied: false, isDismissed: false }); } else if (isConfirmed === false) { this.resolve({ isConfirmed: false, isDenied: false, isDismissed: true, dismiss: 'cancel' }); } else { this.resolve({ isConfirmed: false, isDenied: false, isDismissed: true, dismiss: 'backdrop' }); } } } return Layer; })));