layer.js 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. (function (global, factory) {
  2. typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  3. typeof define === 'function' && define.amd ? define(factory) :
  4. (global.Layer = factory());
  5. }(this, (function () {
  6. 'use strict';
  7. const PREFIX = 'layer-';
  8. // Theme & Styles
  9. const css = `
  10. .${PREFIX}overlay {
  11. position: fixed;
  12. top: 0;
  13. left: 0;
  14. width: 100%;
  15. height: 100%;
  16. background: rgba(0, 0, 0, 0.4);
  17. display: flex;
  18. justify-content: center;
  19. align-items: center;
  20. z-index: 1000;
  21. opacity: 0;
  22. transition: opacity 0.3s;
  23. }
  24. .${PREFIX}overlay.show {
  25. opacity: 1;
  26. }
  27. .${PREFIX}popup {
  28. background: #fff;
  29. border-radius: 8px;
  30. box-shadow: 0 4px 12px rgba(0,0,0,0.15);
  31. width: 32em;
  32. max-width: 90%;
  33. padding: 1.5em;
  34. display: flex;
  35. flex-direction: column;
  36. align-items: center;
  37. transform: scale(0.9);
  38. transition: transform 0.3s;
  39. font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  40. }
  41. .${PREFIX}overlay.show .${PREFIX}popup {
  42. transform: scale(1);
  43. }
  44. .${PREFIX}title {
  45. font-size: 1.8em;
  46. font-weight: 600;
  47. color: #333;
  48. margin: 0 0 0.5em;
  49. text-align: center;
  50. }
  51. .${PREFIX}content {
  52. font-size: 1.125em;
  53. color: #545454;
  54. margin-bottom: 1.5em;
  55. text-align: center;
  56. line-height: 1.5;
  57. }
  58. .${PREFIX}actions {
  59. display: flex;
  60. justify-content: center;
  61. gap: 1em;
  62. width: 100%;
  63. }
  64. .${PREFIX}button {
  65. border: none;
  66. border-radius: 4px;
  67. padding: 0.6em 1.2em;
  68. font-size: 1em;
  69. cursor: pointer;
  70. transition: background-color 0.2s, box-shadow 0.2s;
  71. color: #fff;
  72. }
  73. .${PREFIX}confirm {
  74. background-color: #3085d6;
  75. }
  76. .${PREFIX}confirm:hover {
  77. background-color: #2b77c0;
  78. }
  79. .${PREFIX}cancel {
  80. background-color: #aaa;
  81. }
  82. .${PREFIX}cancel:hover {
  83. background-color: #999;
  84. }
  85. .${PREFIX}icon {
  86. width: 5em;
  87. height: 5em;
  88. border: 0.25em solid transparent;
  89. border-radius: 50%;
  90. margin: 1.5em auto 1.2em;
  91. box-sizing: content-box;
  92. position: relative;
  93. }
  94. /* Success Icon */
  95. .${PREFIX}icon.success {
  96. border-color: #a5dc86;
  97. color: #a5dc86;
  98. }
  99. .${PREFIX}icon.success::before, .${PREFIX}icon.success::after {
  100. content: '';
  101. position: absolute;
  102. background: #fff;
  103. border-radius: 50%;
  104. }
  105. .${PREFIX}success-line-tip {
  106. width: 1.5em;
  107. height: 0.3em;
  108. background-color: #a5dc86;
  109. display: block;
  110. position: absolute;
  111. top: 2.85em;
  112. left: 0.85em;
  113. transform: rotate(45deg);
  114. border-radius: 0.15em;
  115. }
  116. .${PREFIX}success-line-long {
  117. width: 2.9em;
  118. height: 0.3em;
  119. background-color: #a5dc86;
  120. display: block;
  121. position: absolute;
  122. top: 2.4em;
  123. right: 0.5em;
  124. transform: rotate(-45deg);
  125. border-radius: 0.15em;
  126. }
  127. /* Error Icon */
  128. .${PREFIX}icon.error {
  129. border-color: #f27474;
  130. color: #f27474;
  131. }
  132. .${PREFIX}error-x-mark {
  133. position: relative;
  134. display: block;
  135. }
  136. .${PREFIX}error-line {
  137. position: absolute;
  138. height: 0.3em;
  139. width: 3em;
  140. background-color: #f27474;
  141. display: block;
  142. top: 2.3em;
  143. left: 1em;
  144. border-radius: 0.15em;
  145. }
  146. .${PREFIX}error-line.left {
  147. transform: rotate(45deg);
  148. }
  149. .${PREFIX}error-line.right {
  150. transform: rotate(-45deg);
  151. }
  152. /* Warning Icon */
  153. .${PREFIX}icon.warning {
  154. border-color: #facea8;
  155. color: #f8bb86;
  156. }
  157. .${PREFIX}warning-body {
  158. position: absolute;
  159. width: 0.3em;
  160. height: 2.9em;
  161. background-color: #f8bb86;
  162. left: 50%;
  163. top: 0.6em;
  164. margin-left: -0.15em;
  165. border-radius: 0.15em;
  166. }
  167. .${PREFIX}warning-dot {
  168. position: absolute;
  169. width: 0.3em;
  170. height: 0.3em;
  171. background-color: #f8bb86;
  172. left: 50%;
  173. bottom: 0.6em;
  174. margin-left: -0.15em;
  175. border-radius: 50%;
  176. }
  177. `;
  178. // Inject Styles
  179. const injectStyles = () => {
  180. if (document.getElementById(`${PREFIX}styles`)) return;
  181. const styleSheet = document.createElement('style');
  182. styleSheet.id = `${PREFIX}styles`;
  183. styleSheet.textContent = css;
  184. document.head.appendChild(styleSheet);
  185. };
  186. class Layer {
  187. constructor() {
  188. injectStyles();
  189. this.params = {};
  190. this.dom = {};
  191. this.promise = null;
  192. this.resolve = null;
  193. this.reject = null;
  194. }
  195. // Static entry point
  196. static fire(options) {
  197. const instance = new Layer();
  198. return instance._fire(options);
  199. }
  200. _fire(options = {}) {
  201. if (typeof options === 'string') {
  202. options = { title: options };
  203. if (arguments[1]) options.text = arguments[1];
  204. if (arguments[2]) options.icon = arguments[2];
  205. }
  206. this.params = {
  207. title: '',
  208. text: '',
  209. icon: null,
  210. confirmButtonText: 'OK',
  211. cancelButtonText: 'Cancel',
  212. showCancelButton: false,
  213. confirmButtonColor: '#3085d6',
  214. cancelButtonColor: '#aaa',
  215. closeOnClickOutside: true,
  216. ...options
  217. };
  218. this.promise = new Promise((resolve, reject) => {
  219. this.resolve = resolve;
  220. this.reject = reject;
  221. });
  222. this._render();
  223. return this.promise;
  224. }
  225. _render() {
  226. // Remove existing if any
  227. const existing = document.querySelector(`.${PREFIX}overlay`);
  228. if (existing) existing.remove();
  229. // Create Overlay
  230. this.dom.overlay = document.createElement('div');
  231. this.dom.overlay.className = `${PREFIX}overlay`;
  232. // Create Popup
  233. this.dom.popup = document.createElement('div');
  234. this.dom.popup.className = `${PREFIX}popup`;
  235. this.dom.overlay.appendChild(this.dom.popup);
  236. // Icon
  237. if (this.params.icon) {
  238. this.dom.icon = this._createIcon(this.params.icon);
  239. this.dom.popup.appendChild(this.dom.icon);
  240. }
  241. // Title
  242. if (this.params.title) {
  243. this.dom.title = document.createElement('h2');
  244. this.dom.title.className = `${PREFIX}title`;
  245. this.dom.title.textContent = this.params.title;
  246. this.dom.popup.appendChild(this.dom.title);
  247. }
  248. // Text
  249. if (this.params.text) {
  250. this.dom.content = document.createElement('div');
  251. this.dom.content.className = `${PREFIX}content`;
  252. this.dom.content.textContent = this.params.text;
  253. this.dom.popup.appendChild(this.dom.content);
  254. }
  255. // Actions
  256. this.dom.actions = document.createElement('div');
  257. this.dom.actions.className = `${PREFIX}actions`;
  258. // Cancel Button
  259. if (this.params.showCancelButton) {
  260. this.dom.cancelBtn = document.createElement('button');
  261. this.dom.cancelBtn.className = `${PREFIX}button ${PREFIX}cancel`;
  262. this.dom.cancelBtn.textContent = this.params.cancelButtonText;
  263. this.dom.cancelBtn.style.backgroundColor = this.params.cancelButtonColor;
  264. this.dom.cancelBtn.onclick = () => this._close(false);
  265. this.dom.actions.appendChild(this.dom.cancelBtn);
  266. }
  267. // Confirm Button
  268. this.dom.confirmBtn = document.createElement('button');
  269. this.dom.confirmBtn.className = `${PREFIX}button ${PREFIX}confirm`;
  270. this.dom.confirmBtn.textContent = this.params.confirmButtonText;
  271. this.dom.confirmBtn.style.backgroundColor = this.params.confirmButtonColor;
  272. this.dom.confirmBtn.onclick = () => this._close(true);
  273. this.dom.actions.appendChild(this.dom.confirmBtn);
  274. this.dom.popup.appendChild(this.dom.actions);
  275. // Event Listeners
  276. if (this.params.closeOnClickOutside) {
  277. this.dom.overlay.addEventListener('click', (e) => {
  278. if (e.target === this.dom.overlay) {
  279. this._close(null); // Dismiss
  280. }
  281. });
  282. }
  283. document.body.appendChild(this.dom.overlay);
  284. // Animation
  285. requestAnimationFrame(() => {
  286. this.dom.overlay.classList.add('show');
  287. });
  288. }
  289. _createIcon(type) {
  290. const icon = document.createElement('div');
  291. icon.className = `${PREFIX}icon ${type}`;
  292. if (type === 'success') {
  293. const tip = document.createElement('div');
  294. tip.className = `${PREFIX}success-line-tip`;
  295. const long = document.createElement('div');
  296. long.className = `${PREFIX}success-line-long`;
  297. icon.appendChild(tip);
  298. icon.appendChild(long);
  299. } else if (type === 'error') {
  300. const xMark = document.createElement('span');
  301. xMark.className = `${PREFIX}error-x-mark`;
  302. const left = document.createElement('span');
  303. left.className = `${PREFIX}error-line left`;
  304. const right = document.createElement('span');
  305. right.className = `${PREFIX}error-line right`;
  306. xMark.appendChild(left);
  307. xMark.appendChild(right);
  308. icon.appendChild(xMark);
  309. } else if (type === 'warning') {
  310. const body = document.createElement('div');
  311. body.className = `${PREFIX}warning-body`;
  312. const dot = document.createElement('div');
  313. dot.className = `${PREFIX}warning-dot`;
  314. icon.appendChild(body);
  315. icon.appendChild(dot);
  316. }
  317. return icon;
  318. }
  319. _close(isConfirmed) {
  320. this.dom.overlay.classList.remove('show');
  321. setTimeout(() => {
  322. if (this.dom.overlay && this.dom.overlay.parentNode) {
  323. this.dom.overlay.parentNode.removeChild(this.dom.overlay);
  324. }
  325. }, 300);
  326. if (isConfirmed === true) {
  327. this.resolve({ isConfirmed: true, isDenied: false, isDismissed: false });
  328. } else if (isConfirmed === false) {
  329. this.resolve({ isConfirmed: false, isDenied: false, isDismissed: true, dismiss: 'cancel' });
  330. } else {
  331. this.resolve({ isConfirmed: false, isDenied: false, isDismissed: true, dismiss: 'backdrop' });
  332. }
  333. }
  334. }
  335. return Layer;
  336. })));