alert90s 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,142 @@
1
+ # Alert90s
2
+
3
+ A "Neo Brutalism 90s" style JavaScript alert library. Completely standalone and dependency-free (CSS is injected automatically).
4
+
5
+ Heavily inspired by **SweetAlert2**. It supports many of the same API signatures (Promises, Inputs, PreConfirm, Timers, RTL), making it practically a drop-in UI replacement for existing projects that want a brutalist 90s makeover, just using the `Alert90s` object instead!
6
+
7
+ ## Installation
8
+
9
+ You can install it via npm:
10
+
11
+ ```bash
12
+ npm install alert90s
13
+ ```
14
+
15
+ Or just include the built file in your project:
16
+
17
+ ```html
18
+ <script src="path/to/alert90s.min.js"></script>
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ If using a bundler (Webpack, Vite, Rollup):
24
+
25
+ ```javascript
26
+ import Alert90s from "alert90s";
27
+
28
+ // Basic Message
29
+ Alert90s.show("Any fool can use a computer");
30
+ ```
31
+
32
+ If using via `<script>` tag, the library automatically registered on the global `window` object as `Alert90s` and `Swal`.
33
+
34
+ ## Examples
35
+
36
+ ### 1. Promises & Deny Buttons
37
+
38
+ ```javascript
39
+ Alert90s.fire({
40
+ title: "Do you want to save the changes?",
41
+ showDenyButton: true,
42
+ showCancelButton: true,
43
+ confirmButtonText: "Save",
44
+ denyButtonText: `Don't save`,
45
+ }).then((result) => {
46
+ if (result.isConfirmed) {
47
+ Alert90s.fire("Saved!", "", "success");
48
+ } else if (result.isDenied) {
49
+ Alert90s.fire("Changes are not saved", "", "info");
50
+ }
51
+ });
52
+ ```
53
+
54
+ ### 2. AJAX Requests & Inputs
55
+
56
+ ```javascript
57
+ Alert90s.fire({
58
+ title: "Submit your Github username",
59
+ input: "text",
60
+ inputAttributes: {
61
+ autocapitalize: "off",
62
+ },
63
+ showCancelButton: true,
64
+ confirmButtonText: "Look up",
65
+ showLoaderOnConfirm: true,
66
+ preConfirm: async (login) => {
67
+ try {
68
+ const response = await fetch(`https://api.github.com/users/${login}`);
69
+ if (!response.ok) {
70
+ return Alert90s.showValidationMessage(
71
+ `${JSON.stringify(await response.json())}`,
72
+ );
73
+ }
74
+ return response.json();
75
+ } catch (error) {
76
+ Alert90s.showValidationMessage(`Request failed: ${error}`);
77
+ }
78
+ },
79
+ allowOutsideClick: () => !Alert90s.isLoading(),
80
+ }).then((result) => {
81
+ if (result.isConfirmed) {
82
+ Alert90s.fire({ title: `Avatar`, imageUrl: result.value.avatar_url });
83
+ }
84
+ });
85
+ ```
86
+
87
+ ### 3. RTL Support
88
+
89
+ ```javascript
90
+ Alert90s.fire({
91
+ title: "هل تريد الاستمرار؟",
92
+ icon: "question",
93
+ iconHtml: "؟",
94
+ confirmButtonText: "نعم",
95
+ cancelButtonText: "لا",
96
+ showCancelButton: true,
97
+ showCloseButton: true,
98
+ dir: "rtl",
99
+ });
100
+ ```
101
+
102
+ ### 4. Custom Animate.css Animations
103
+
104
+ ```javascript
105
+ Alert90s.fire({
106
+ title: "Custom animation with Animate.css",
107
+ showClass: { popup: "animate__animated animate__fadeInUp" },
108
+ hideClass: { popup: "animate__animated animate__fadeOutDown" },
109
+ });
110
+ ```
111
+
112
+ ## Advanced Options
113
+
114
+ | Option | Type | Default | Description |
115
+ | ---------------- | -------- | -------- | ----------------------------------------------------------------- |
116
+ | title | String | '' | The title of the alert. Supports HTML. |
117
+ | text/message | String | '' | The message body of the alert. |
118
+ | html | String | '' | A custom HTML description for the alert. |
119
+ | icon | String | '' | Standard icons: `warning`, `error`, `info`, `success`, `question` |
120
+ | iconHtml | String | '' | Custom HTML string for the icon. |
121
+ | footer | String | '' | Custom HTML for the footer section. |
122
+ | imageUrl | String | '' | URL for an image to display. |
123
+ | input | String | null | Type of input: `text`, `password`, `email`, `textarea`, etc. |
124
+ | dir | String | 'auto' | Text direction. Set `rtl` for Arabic/Hebrew. |
125
+ | position | String | 'center' | `top`, `top-end`, `bottom-start`, etc. |
126
+ | timer | Number | null | Auto close timer in milliseconds. |
127
+ | timerProgressBar | Boolean | false | Display progress bar at the bottom. |
128
+ | draggable | Boolean | false | Allow moving the modal dragging its header. |
129
+ | showDenyButton | Boolean | false | Show the third middle deny button. |
130
+ | preConfirm | Function | null | Async function executed before confirm executes. |
131
+
132
+ ## Methods
133
+
134
+ - `Alert90s.fire(options)` or `Alert90s.show(options)`
135
+ - `Alert90s.showLoading()` / `Alert90s.hideLoading()`
136
+ - `Alert90s.showValidationMessage(message)` / `Alert90s.resetValidationMessage()`
137
+ - `Alert90s.getTimerLeft()`
138
+ - `Alert90s.getPopup()`
139
+
140
+ ## License
141
+
142
+ MIT
@@ -0,0 +1 @@
1
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).Alert90s=t()}(this,function(){"use strict";!function(e,t){void 0===t&&(t={});var o=t.insertAt;if("undefined"!=typeof document){var r=document.head||document.getElementsByTagName("head")[0],n=document.createElement("style");n.type="text/css","top"===o&&r.firstChild?r.insertBefore(n,r.firstChild):r.appendChild(n),n.styleSheet?n.styleSheet.cssText=e:n.appendChild(document.createTextNode(e))}}(".alert90s-overlay{animation:alert90s-fade-in .2s ease-out forwards;backdrop-filter:blur(4px);background-color:rgba(0,0,0,.6);box-sizing:border-box;display:flex;font-family:inherit;inset:0;padding:1rem;position:fixed;z-index:99999}.alert90s-overlay.alert90s-pos-center{align-items:center;justify-content:center}.alert90s-overlay.alert90s-pos-top{align-items:flex-start;justify-content:center}.alert90s-overlay.alert90s-pos-top-start{align-items:flex-start;justify-content:flex-start}.alert90s-overlay.alert90s-pos-top-end{align-items:flex-start;justify-content:flex-end}.alert90s-overlay.alert90s-pos-bottom{align-items:flex-end;justify-content:center}.alert90s-overlay.alert90s-pos-bottom-start{align-items:flex-end;justify-content:flex-start}.alert90s-overlay.alert90s-pos-bottom-end{align-items:flex-end;justify-content:flex-end}.alert90s-overlay.alert90s-pos-center-start{align-items:center;justify-content:flex-start}.alert90s-overlay.alert90s-pos-center-end{align-items:center;justify-content:flex-end}.alert90s-overlay *{box-sizing:border-box}.alert90s-box{align-items:center;animation:alert90s-pop-in .3s cubic-bezier(.175,.885,.32,1.275) forwards;background-color:#fff;border:4px solid #000;box-shadow:12px 12px 0 0 #000;display:flex;flex-direction:column;max-height:95vh;max-width:28rem;position:relative;text-align:center;width:100%}.alert90s-box[dir=rtl]{text-align:right}.alert90s-box.is-draggable{position:absolute}.alert90s-body{align-items:center;display:flex;flex-direction:column;overflow-y:auto;padding:3rem 2rem 2rem;scrollbar-width:none;width:100%}.alert90s-body::-webkit-scrollbar{display:none}.alert90s-header{align-items:center;background-color:#e2e8f0;border-bottom:4px solid #000;display:flex;height:2rem;justify-content:space-between;left:0;padding:0 .5rem;position:absolute;top:0;width:100%;z-index:10}.alert90s-header.draggable{cursor:grab}.alert90s-header.draggable:active{cursor:grabbing}.alert90s-header-left{display:flex;gap:.25rem}.alert90s-box[dir=rtl] .alert90s-header{flex-direction:row-reverse}.alert90s-header-dot{background-color:#fff;border:2px solid #000;height:.75rem;width:.75rem}.alert90s-header-right{align-items:center;display:flex;gap:.5rem;z-index:11}.alert90s-box[dir=rtl] .alert90s-header-right{flex-direction:row-reverse}.alert90s-header-title{font-size:10px;font-weight:700;letter-spacing:.1em;pointer-events:none;text-transform:uppercase}.alert90s-close-btn{align-items:center;background-color:#ef4444;border:2px solid #000;cursor:pointer;display:flex;height:1.25rem;justify-content:center;padding:0;transition:transform .1s,background-color .1s;width:1.25rem}.alert90s-close-btn:hover{background-color:#f87171;transform:scale(1.1)}.alert90s-close-btn span{color:#000;font-size:14px;font-weight:700;line-height:1}.alert90s-image-container{align-items:center;background:#fff;border:4px solid #000;box-shadow:4px 4px 0 0 #000;display:flex;justify-content:center;margin-bottom:1rem;overflow:hidden;width:100%}.alert90s-image-container img{display:block;height:auto;max-width:100%}.alert90s-icon{align-items:center;border:4px solid #000;border-radius:9999px;box-shadow:4px 4px 0 0 #000;display:flex;flex-shrink:0;height:4rem;justify-content:center;margin-bottom:1rem;width:4rem}.alert90s-icon img,.alert90s-icon svg{height:2rem;width:2rem}.alert90s-icon-custom{font-size:2.25rem;font-weight:700;line-height:1}.alert90s-icon.warning{background-color:#facc15}.alert90s-icon.danger,.alert90s-icon.error{background-color:#ef4444;color:#fff}.alert90s-icon.info,.alert90s-icon.question{background-color:#60a5fa}.alert90s-icon.success{background-color:#4ade80}.alert90s-title{color:#1a1a1a;font-size:1.5rem;font-weight:900;letter-spacing:.1em;line-height:1.2;margin:0 0 .5rem;text-transform:uppercase}.alert90s-message{color:#475569;font-weight:700}.alert90s-html,.alert90s-message{font-size:.875rem;margin:0 0 1.5rem;max-width:100%}.alert90s-html{color:#1a1a1a;line-height:1.5}.alert90s-box[dir=rtl] .alert90s-html{text-align:right}.alert90s-html a{color:#2563eb;font-weight:700;text-decoration:underline;text-decoration-thickness:2px}.alert90s-html a:hover{background-color:#bfdbfe}.alert90s-html b,.alert90s-html strong{font-weight:900}.alert90s-input-container{margin-bottom:1.5rem;width:100%}.alert90s-input{background-color:#f8fafc;border:4px solid #000;box-shadow:inset 4px 4px 0 0 rgba(0,0,0,.05);font-family:inherit;font-size:1rem;font-weight:700;outline:none;padding:.75rem;transition:background-color .2s;width:100%}.alert90s-input:focus{background-color:#fff;border-color:#2563eb}textarea.alert90s-input{min-height:100px;resize:vertical}.alert90s-validation-message{background-color:#fef2f2;border:4px solid #000;border-left:8px solid #ef4444;color:#ef4444;display:none;font-size:.875rem;font-weight:700;margin-bottom:1.5rem;padding:.75rem;text-align:left;width:100%}.alert90s-box[dir=rtl] .alert90s-validation-message{border-left-color:#000;border-left-width:4px;border-right-color:#ef4444;border-right-width:8px;text-align:right}.alert90s-actions{display:flex;flex-direction:column;gap:1rem;justify-content:center;width:100%}@media (min-width:640px){.alert90s-actions{flex-direction:row}}.alert90s-button{align-items:center;background-color:#fff;border:4px solid #000;box-shadow:4px 4px 0 0 #000;color:#000;cursor:pointer;display:flex;font-family:inherit;font-size:.875rem;font-weight:900;gap:.5rem;justify-content:center;letter-spacing:.1em;margin:0;padding:.75rem 1rem;text-transform:uppercase;transition:background-color .1s;width:100%}@media (min-width:640px){.alert90s-button{flex:1;min-width:100px;width:auto}}.alert90s-button:active{box-shadow:0 0 0 0 #000;transform:translate(4px,4px)}.alert90s-button:focus{outline:2px dashed #000;outline-offset:4px}.alert90s-button.cancel{background-color:#f87171}.alert90s-button.cancel:hover{background-color:#fca5a5}.alert90s-button.deny{background-color:#fb923c}.alert90s-button.deny:hover{background-color:#fdba74}.alert90s-button.confirm{background-color:#4ade80}.alert90s-button.confirm:hover{background-color:#86efac}.alert90s-footer{border-top:4px solid #000;color:#475569;font-size:.75rem;font-weight:700;margin-top:1.5rem;padding-top:1rem;width:100%}.alert90s-footer a{color:#2563eb;text-decoration:underline;text-decoration-thickness:2px}.alert90s-footer a:hover{background-color:#bfdbfe}.alert90s-progress-bar{animation:alert90s-progress linear forwards;background-color:#000;bottom:0;height:6px;left:0;position:absolute;width:100%}.alert90s-loader{align-items:center;display:flex;justify-content:center;padding:1rem 0}.alert90s-spinner{animation:alert90s-spin 1s linear infinite;border:4px solid #e2e8f0;border-radius:50%;border-top-color:#000;height:2rem;width:2rem}@keyframes alert90s-spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}@keyframes alert90s-progress{0%{width:100%}to{width:0}}@keyframes alert90s-fade-in{0%{opacity:0}to{opacity:1}}@keyframes alert90s-pop-in{0%{opacity:0;transform:scale(.95) translateY(10px)}to{opacity:1;transform:scale(1) translateY(0)}}@keyframes alert90s-fade-out{0%{opacity:1;transform:scale(1) translateY(0)}to{opacity:0;transform:scale(.95) translateY(10px)}}");class e{static getOptions(){return this.currentOptions||{}}static getPopup(){return this.currentPopup||null}static getTimerLeft(){if(!this.timerEnd)return;const e=this.timerEnd-Date.now();return e>0?e:0}static isLoading(){const e=this.getPopup();if(!e)return!1;const t=e.querySelector(".alert90s-actions"),o=e.querySelector(".alert90s-loader");return t&&"none"===t.style.display||!!o}static showLoading(){const e=this.getPopup();if(e){const t=e.querySelector(".alert90s-actions");t&&(t.style.display="none");let o=e.querySelector(".alert90s-loader");o||(o=document.createElement("div"),o.className="alert90s-loader",o.innerHTML='<div class="alert90s-spinner"></div>',t&&t.parentNode?t.parentNode.insertBefore(o,t):e.querySelector(".alert90s-body").appendChild(o))}}static hideLoading(){const e=this.getPopup();if(e){const t=e.querySelector(".alert90s-actions");t&&(t.style.display="");const o=e.querySelector(".alert90s-loader");o&&o.remove()}}static showValidationMessage(e){const t=this.getPopup();if(t){let o=t.querySelector(".alert90s-validation-message");if(!o){o=document.createElement("div"),o.className="alert90s-validation-message";const e=t.querySelector(".alert90s-actions");e&&e.parentNode?e.parentNode.insertBefore(o,e):t.querySelector(".alert90s-body").appendChild(o)}o.innerHTML=e,o.style.display="block"}}static resetValidationMessage(){const e=this.getPopup();if(e){const t=e.querySelector(".alert90s-validation-message");t&&(t.style.display="none")}}static fire(e={}){return this.show(e)}static show(t={}){return new Promise(o=>{"string"==typeof t&&(t={title:t}),this.currentOptions=t;const r={title:void 0!==t.title?t.title:"",text:t.message||t.text||"",html:t.html||"",icon:t.type||t.icon||"",iconHtml:t.iconHtml||"",showConfirmButton:!1!==t.showConfirmButton,showCancelButton:t.showCancelButton||!1,showDenyButton:t.showDenyButton||!1,showCloseButton:void 0===t.showCloseButton||t.showCloseButton,confirmButtonText:t.confirmText||t.confirmButtonText||"OK",cancelButtonText:t.cancelText||t.cancelButtonText||"Cancel",denyButtonText:t.denyText||t.denyButtonText||"No",confirmButtonColor:t.confirmButtonColor||"",cancelButtonColor:t.cancelButtonColor||"",denyButtonColor:t.denyButtonColor||"",confirmButtonAriaLabel:t.confirmButtonAriaLabel||"",cancelButtonAriaLabel:t.cancelButtonAriaLabel||"",denyButtonAriaLabel:t.denyButtonAriaLabel||"",focusConfirm:!1!==t.focusConfirm,footer:t.footer||"",imageUrl:t.imageUrl||"",imageWidth:t.imageWidth||null,imageHeight:t.imageHeight||null,imageAlt:t.imageAlt||"",input:t.input||null,inputAttributes:t.inputAttributes||{},showLoaderOnConfirm:t.showLoaderOnConfirm||!1,preConfirm:t.preConfirm||null,draggable:t.draggable||!1,position:t.position||"center",allowOutsideClick:void 0===t.allowOutsideClick||t.allowOutsideClick,dir:t.dir||"auto",showClass:t.showClass||{popup:"alert90s-pop-in"},hideClass:t.hideClass||{popup:"alert90s-fade-out"},timer:t.timer||null,timerProgressBar:t.timerProgressBar||!1,didOpen:t.didOpen||null,willClose:t.willClose||null},n=document.createElement("div");n.className=`alert90s-overlay alert90s-pos-${r.position}`,n.addEventListener("mousedown",e=>{if(e.target===n){("function"==typeof r.allowOutsideClick?r.allowOutsideClick():r.allowOutsideClick)&&f({isConfirmed:!1,isDenied:!1,isDismissed:!0,dismiss:"backdrop"})}});const i=document.createElement("div");i.className="alert90s-box","rtl"===document.dir||"rtl"===r.dir||r.position.includes("start")?i.setAttribute("dir","auto"===r.dir?document.dir||"ltr":r.dir):i.setAttribute("dir",r.dir),r.showClass.popup&&"alert90s-pop-in"!==r.showClass.popup&&(i.style.animation="none",i.className=`alert90s-box ${r.showClass.popup}`),r.draggable&&i.classList.add("is-draggable"),this.currentPopup=i;const s=document.createElement("div");s.className="alert90s-body";const a=document.createElement("div");a.className="alert90s-header",r.draggable&&a.classList.add("draggable");let l="";if(r.showCloseButton&&(l='\n <button class="alert90s-close-btn" id="alert90s-close" aria-label="Close">\n <span>&#10005;</span>\n </button>\n '),a.innerHTML=`\n <div class="alert90s-header-left">\n <div class="alert90s-header-dot"></div>\n <div class="alert90s-header-dot"></div>\n <div class="alert90s-header-dot"></div>\n </div>\n <div class="alert90s-header-right">\n <span class="alert90s-header-title">SYS.REQ</span>\n ${l}\n </div>\n `,r.draggable){let e,t,o,r,n=!1;a.addEventListener("mousedown",s=>{if(s.target.closest("#alert90s-close"))return;n=!0,e=s.clientX,t=s.clientY;const a=i.getBoundingClientRect();i.style.transform="none",i.style.animation="none",o=a.left,r=a.top,i.style.left=o+"px",i.style.top=r+"px",i.style.margin="0",document.body.style.userSelect="none"}),document.addEventListener("mousemove",s=>{n&&(i.style.left=o+(s.clientX-e)+"px",i.style.top=r+(s.clientY-t)+"px")}),document.addEventListener("mouseup",()=>{n&&(n=!1,document.body.style.userSelect="")})}let d=null;if(r.timer&&r.timerProgressBar&&(d=document.createElement("div"),d.className="alert90s-progress-bar",d.style.animationDuration=`${r.timer}ms`,i.appendChild(d)),r.imageUrl){const e=document.createElement("div");e.className="alert90s-image-container";const t=document.createElement("img");t.src=r.imageUrl,r.imageAlt&&(t.alt=r.imageAlt);let o="";r.imageWidth&&(o+=`width: ${r.imageWidth}px; max-width: 100%; `),r.imageHeight&&(o+=`height: ${r.imageHeight}px; `),o&&e.setAttribute("style",o),e.appendChild(t),s.appendChild(e)}if(r.icon){const e=document.createElement("div"),t="error"===r.icon?"danger":r.icon;e.className=`alert90s-icon ${t}`;let o="";r.iconHtml?o=`<div class="alert90s-icon-custom">${r.iconHtml}</div>`:"warning"===r.icon?o='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path><line x1="12" y1="9" x2="12" y2="13"></line><line x1="12" y1="17" x2="12.01" y2="17"></line></svg>':"danger"===r.icon||"error"===r.icon?o='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>':"info"===r.icon?o='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg>':"success"===r.icon?o='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path><polyline points="22 4 12 14.01 9 11.01"></polyline></svg>':"question"===r.icon&&(o='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path><line x1="12" y1="17" x2="12.01" y2="17"></line></svg>'),e.innerHTML=o,o&&s.appendChild(e)}if(r.title){const e=document.createElement("h2");e.className="alert90s-title",e.innerHTML=r.title,s.appendChild(e)}if(r.html){const e=document.createElement("div");e.className="alert90s-html",e.innerHTML=r.html,s.appendChild(e)}else if(r.text){const e=document.createElement("p");e.className="alert90s-message",e.textContent=r.text,s.appendChild(e)}let c=null;if(r.input){const e=document.createElement("div");if(e.className="alert90s-input-container","textarea"===r.input?c=document.createElement("textarea"):(c=document.createElement("input"),c.type=r.input),c.className="alert90s-input",r.inputAttributes)for(const[e,t]of Object.entries(r.inputAttributes))c.setAttribute(e,t);e.appendChild(c),s.appendChild(e)}const m=document.createElement("div");m.className="alert90s-actions";let u=null,p=null;const f=e=>{p&&clearTimeout(p),r.willClose&&r.willClose(),i.style.animation="none","alert90s-fade-out"===r.hideClass.popup?(i.style.animation="alert90s-fade-out 0.2s forwards",n.style.transition="opacity 0.2s",n.style.opacity="0"):i.className=`alert90s-box ${r.hideClass.popup}`,setTimeout(()=>{document.body.contains(n)&&document.body.removeChild(n),o(e)},200)},h=async()=>{let t=!0;if(c&&(t=c.value),r.preConfirm){e.resetValidationMessage(),r.showLoaderOnConfirm&&e.showLoading();try{const o=await Promise.resolve(r.preConfirm(t));if(!1===o)return void e.hideLoading();void 0!==o&&o!==t&&(t=o);const n=i.querySelector(".alert90s-validation-message");if(n&&"block"===n.style.display)return void e.hideLoading()}catch(t){return e.showValidationMessage(`Request failed: ${t}`),void e.hideLoading()}}f({isConfirmed:!0,isDenied:!1,isDismissed:!1,value:t})};if(r.showCancelButton){const e=document.createElement("button");e.className="alert90s-button cancel",e.innerHTML=r.cancelButtonText,r.cancelButtonAriaLabel&&e.setAttribute("aria-label",r.cancelButtonAriaLabel),r.cancelButtonColor&&(e.style.backgroundColor=r.cancelButtonColor),e.onclick=()=>f({isConfirmed:!1,isDenied:!1,isDismissed:!0,dismiss:"cancel"}),m.appendChild(e)}if(r.showDenyButton){const e=document.createElement("button");e.className="alert90s-button deny",e.innerHTML=r.denyButtonText,r.denyButtonAriaLabel&&e.setAttribute("aria-label",r.denyButtonAriaLabel),r.denyButtonColor&&(e.style.backgroundColor=r.denyButtonColor),e.onclick=()=>f({isConfirmed:!1,isDenied:!0,isDismissed:!1}),m.appendChild(e)}if(r.showConfirmButton){const e=document.createElement("button");e.className="alert90s-button confirm",e.innerHTML=r.confirmButtonText,r.confirmButtonAriaLabel&&e.setAttribute("aria-label",r.confirmButtonAriaLabel),r.confirmButtonColor&&(e.style.backgroundColor=r.confirmButtonColor),e.onclick=h,m.appendChild(e),u=e}if((r.showCancelButton||r.showDenyButton||r.showConfirmButton)&&s.appendChild(m),r.footer){const e=document.createElement("div");e.className="alert90s-footer",e.innerHTML=r.footer,s.appendChild(e)}if(i.appendChild(a),i.appendChild(s),n.appendChild(i),r.showCloseButton){const e=a.querySelector("#alert90s-close");e&&(e.onclick=()=>f({isConfirmed:!1,isDenied:!1,isDismissed:!0,dismiss:"close"}))}document.body.appendChild(n),r.focusConfirm&&u&&setTimeout(()=>{c?c.focus():u.focus()},50),r.didOpen&&setTimeout(()=>{r.didOpen()},0),r.timer&&(this.timerEnd=Date.now()+r.timer,p=setTimeout(()=>{f({isConfirmed:!1,isDenied:!1,isDismissed:!0,dismiss:"timer"})},r.timer))})}}return"undefined"!=typeof window&&(window.Alert90s=e),e});
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "alert90s",
3
+ "version": "1.0.1",
4
+ "description": "Neo Brutalism 90s style JS alert library",
5
+ "main": "dist/alert90s.min.js",
6
+ "files": [
7
+ "dist",
8
+ "src"
9
+ ],
10
+ "scripts": {
11
+ "build": "rollup -c"
12
+ },
13
+ "keywords": [
14
+ "alert",
15
+ "neo-brutalism",
16
+ "90s",
17
+ "modal",
18
+ "ui"
19
+ ],
20
+ "author": "",
21
+ "license": "MIT",
22
+ "devDependencies": {
23
+ "@rollup/plugin-terser": "^0.4.4",
24
+ "postcss": "^8.5.6",
25
+ "rollup": "^4.57.1",
26
+ "rollup-plugin-postcss": "^4.0.2"
27
+ }
28
+ }
package/src/alert.js ADDED
File without changes
package/src/index.js ADDED
@@ -0,0 +1,507 @@
1
+ import './styles.css';
2
+
3
+ class Alert90s {
4
+ static getOptions() {
5
+ return this.currentOptions || {};
6
+ }
7
+ static getPopup() {
8
+ return this.currentPopup || null;
9
+ }
10
+ static getTimerLeft() {
11
+ if (!this.timerEnd) return undefined;
12
+ const left = this.timerEnd - Date.now();
13
+ return left > 0 ? left : 0;
14
+ }
15
+ static isLoading() {
16
+ const popup = this.getPopup();
17
+ if (!popup) return false;
18
+ // Check if buttons are hidden and loader is present
19
+ const actions = popup.querySelector('.alert90s-actions');
20
+ const loader = popup.querySelector('.alert90s-loader');
21
+ return (actions && actions.style.display === 'none') || !!loader;
22
+ }
23
+ static showLoading() {
24
+ const popup = this.getPopup();
25
+ if (popup) {
26
+ const actions = popup.querySelector('.alert90s-actions');
27
+ if (actions) actions.style.display = 'none'; // hide buttons
28
+
29
+ let loader = popup.querySelector('.alert90s-loader');
30
+ if (!loader) {
31
+ loader = document.createElement('div');
32
+ loader.className = 'alert90s-loader';
33
+ loader.innerHTML = '<div class="alert90s-spinner"></div>';
34
+
35
+ // Insert loader where actions were
36
+ if (actions && actions.parentNode) {
37
+ actions.parentNode.insertBefore(loader, actions);
38
+ } else {
39
+ popup.querySelector('.alert90s-body').appendChild(loader);
40
+ }
41
+ }
42
+ }
43
+ }
44
+ static hideLoading() {
45
+ const popup = this.getPopup();
46
+ if (popup) {
47
+ const actions = popup.querySelector('.alert90s-actions');
48
+ if (actions) actions.style.display = ''; // restore buttons
49
+
50
+ const loader = popup.querySelector('.alert90s-loader');
51
+ if (loader) loader.remove();
52
+ }
53
+ }
54
+ static showValidationMessage(message) {
55
+ const popup = this.getPopup();
56
+ if (popup) {
57
+ let valEl = popup.querySelector('.alert90s-validation-message');
58
+ if (!valEl) {
59
+ valEl = document.createElement('div');
60
+ valEl.className = 'alert90s-validation-message';
61
+ // Insert validation message before actions
62
+ const actions = popup.querySelector('.alert90s-actions');
63
+ if (actions && actions.parentNode) {
64
+ actions.parentNode.insertBefore(valEl, actions);
65
+ } else {
66
+ popup.querySelector('.alert90s-body').appendChild(valEl);
67
+ }
68
+ }
69
+ valEl.innerHTML = message;
70
+ valEl.style.display = 'block';
71
+ }
72
+ }
73
+ static resetValidationMessage() {
74
+ const popup = this.getPopup();
75
+ if (popup) {
76
+ const valEl = popup.querySelector('.alert90s-validation-message');
77
+ if (valEl) valEl.style.display = 'none';
78
+ }
79
+ }
80
+
81
+ static fire(options = {}) {
82
+ return this.show(options);
83
+ }
84
+
85
+ static show(options = {}) {
86
+ return new Promise((resolve) => {
87
+ if (typeof options === 'string') {
88
+ options = { title: options };
89
+ }
90
+ this.currentOptions = options;
91
+
92
+ const config = {
93
+ title: options.title !== undefined ? options.title : '',
94
+ text: options.message || options.text || '',
95
+ html: options.html || '',
96
+ icon: options.type || options.icon || '',
97
+ iconHtml: options.iconHtml || '',
98
+
99
+ // Buttons
100
+ showConfirmButton: options.showConfirmButton !== false,
101
+ showCancelButton: options.showCancelButton || false,
102
+ showDenyButton: options.showDenyButton || false,
103
+ showCloseButton: options.showCloseButton !== undefined ? options.showCloseButton : true,
104
+
105
+ confirmButtonText: options.confirmText || options.confirmButtonText || 'OK',
106
+ cancelButtonText: options.cancelText || options.cancelButtonText || 'Cancel',
107
+ denyButtonText: options.denyText || options.denyButtonText || 'No',
108
+
109
+ confirmButtonColor: options.confirmButtonColor || '',
110
+ cancelButtonColor: options.cancelButtonColor || '',
111
+ denyButtonColor: options.denyButtonColor || '',
112
+
113
+ confirmButtonAriaLabel: options.confirmButtonAriaLabel || '',
114
+ cancelButtonAriaLabel: options.cancelButtonAriaLabel || '',
115
+ denyButtonAriaLabel: options.denyButtonAriaLabel || '',
116
+
117
+ focusConfirm: options.focusConfirm !== false,
118
+
119
+ // Advanced content
120
+ footer: options.footer || '',
121
+ imageUrl: options.imageUrl || '',
122
+ imageWidth: options.imageWidth || null,
123
+ imageHeight: options.imageHeight || null,
124
+ imageAlt: options.imageAlt || '',
125
+
126
+ // Input
127
+ input: options.input || null, // 'text', 'email', 'password', 'number', 'tel', 'url', 'textarea'
128
+ inputAttributes: options.inputAttributes || {},
129
+ showLoaderOnConfirm: options.showLoaderOnConfirm || false,
130
+ preConfirm: options.preConfirm || null,
131
+
132
+ // Behavior
133
+ draggable: options.draggable || false,
134
+ position: options.position || 'center',
135
+ allowOutsideClick: options.allowOutsideClick !== undefined ? options.allowOutsideClick : true,
136
+
137
+ // Display options
138
+ dir: options.dir || 'auto', // 'rtl', 'ltr', 'auto'
139
+
140
+ // Animations
141
+ showClass: options.showClass || { popup: 'alert90s-pop-in' },
142
+ hideClass: options.hideClass || { popup: 'alert90s-fade-out' },
143
+
144
+ // Timers
145
+ timer: options.timer || null,
146
+ timerProgressBar: options.timerProgressBar || false,
147
+
148
+ // Callbacks
149
+ didOpen: options.didOpen || null,
150
+ willClose: options.willClose || null,
151
+ };
152
+
153
+ // Create overlay
154
+ const overlay = document.createElement('div');
155
+ overlay.className = `alert90s-overlay alert90s-pos-${config.position}`;
156
+
157
+ // Handle allowOutsideClick
158
+ overlay.addEventListener('mousedown', (e) => {
159
+ if (e.target === overlay) {
160
+ const allow = typeof config.allowOutsideClick === 'function' ? config.allowOutsideClick() : config.allowOutsideClick;
161
+ if (allow) {
162
+ finish({ isConfirmed: false, isDenied: false, isDismissed: true, dismiss: 'backdrop' });
163
+ }
164
+ }
165
+ });
166
+
167
+ // Create Box
168
+ const box = document.createElement('div');
169
+ box.className = 'alert90s-box';
170
+
171
+ // RTL support
172
+ if (document.dir === 'rtl' || config.dir === 'rtl' || config.position.includes('start')) {
173
+ box.setAttribute('dir', config.dir === 'auto' ? (document.dir || 'ltr') : config.dir);
174
+ } else {
175
+ box.setAttribute('dir', config.dir);
176
+ }
177
+
178
+ // Apply custom show classes
179
+ if (config.showClass.popup) {
180
+ if (config.showClass.popup !== 'alert90s-pop-in') {
181
+ box.style.animation = 'none';
182
+ box.className = `alert90s-box ${config.showClass.popup}`;
183
+ }
184
+ }
185
+
186
+ if (config.draggable) {
187
+ box.classList.add('is-draggable');
188
+ }
189
+
190
+ this.currentPopup = box;
191
+
192
+ const bodyContainer = document.createElement('div');
193
+ bodyContainer.className = 'alert90s-body';
194
+
195
+ // Header
196
+ const header = document.createElement('div');
197
+ header.className = 'alert90s-header';
198
+ if (config.draggable) header.classList.add('draggable');
199
+
200
+ let closeButtonHtml = '';
201
+ if (config.showCloseButton) {
202
+ closeButtonHtml = `
203
+ <button class="alert90s-close-btn" id="alert90s-close" aria-label="Close">
204
+ <span>&#10005;</span>
205
+ </button>
206
+ `;
207
+ }
208
+
209
+ header.innerHTML = `
210
+ <div class="alert90s-header-left">
211
+ <div class="alert90s-header-dot"></div>
212
+ <div class="alert90s-header-dot"></div>
213
+ <div class="alert90s-header-dot"></div>
214
+ </div>
215
+ <div class="alert90s-header-right">
216
+ <span class="alert90s-header-title">SYS.REQ</span>
217
+ ${closeButtonHtml}
218
+ </div>
219
+ `;
220
+
221
+ // Draggable Logic
222
+ if (config.draggable) {
223
+ let isDragging = false;
224
+ let startX, startY, initialBoxX, initialBoxY;
225
+
226
+ header.addEventListener('mousedown', (e) => {
227
+ if (e.target.closest('#alert90s-close')) return;
228
+ isDragging = true;
229
+ startX = e.clientX;
230
+ startY = e.clientY;
231
+ const rect = box.getBoundingClientRect();
232
+ box.style.transform = 'none';
233
+ box.style.animation = 'none';
234
+ initialBoxX = rect.left;
235
+ initialBoxY = rect.top;
236
+ box.style.left = initialBoxX + 'px';
237
+ box.style.top = initialBoxY + 'px';
238
+ box.style.margin = '0';
239
+ document.body.style.userSelect = 'none';
240
+ });
241
+
242
+ document.addEventListener('mousemove', (e) => {
243
+ if (!isDragging) return;
244
+ box.style.left = (initialBoxX + (e.clientX - startX)) + 'px';
245
+ box.style.top = (initialBoxY + (e.clientY - startY)) + 'px';
246
+ });
247
+
248
+ document.addEventListener('mouseup', () => {
249
+ if (isDragging) {
250
+ isDragging = false;
251
+ document.body.style.userSelect = '';
252
+ }
253
+ });
254
+ }
255
+
256
+ // Progress Bar
257
+ let progressBarEl = null;
258
+ if (config.timer && config.timerProgressBar) {
259
+ progressBarEl = document.createElement('div');
260
+ progressBarEl.className = 'alert90s-progress-bar';
261
+ progressBarEl.style.animationDuration = `${config.timer}ms`;
262
+ box.appendChild(progressBarEl);
263
+ }
264
+
265
+ // Image
266
+ if (config.imageUrl) {
267
+ const imgContainer = document.createElement('div');
268
+ imgContainer.className = 'alert90s-image-container';
269
+ const img = document.createElement('img');
270
+ img.src = config.imageUrl;
271
+ if (config.imageAlt) img.alt = config.imageAlt;
272
+
273
+ let containerStyle = '';
274
+ if (config.imageWidth) containerStyle += `width: ${config.imageWidth}px; max-width: 100%; `;
275
+ if (config.imageHeight) containerStyle += `height: ${config.imageHeight}px; `;
276
+ if (containerStyle) imgContainer.setAttribute('style', containerStyle);
277
+
278
+ imgContainer.appendChild(img);
279
+ bodyContainer.appendChild(imgContainer);
280
+ }
281
+
282
+ // Icon
283
+ if (config.icon) {
284
+ const iconWrapper = document.createElement('div');
285
+ const typeClass = config.icon === 'error' ? 'danger' : config.icon;
286
+ iconWrapper.className = `alert90s-icon ${typeClass}`;
287
+
288
+ let iconHTML = '';
289
+ if (config.iconHtml) {
290
+ iconHTML = `<div class="alert90s-icon-custom">${config.iconHtml}</div>`;
291
+ } else if (config.icon === 'warning') {
292
+ iconHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path><line x1="12" y1="9" x2="12" y2="13"></line><line x1="12" y1="17" x2="12.01" y2="17"></line></svg>';
293
+ } else if (config.icon === 'danger' || config.icon === 'error') {
294
+ iconHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>';
295
+ } else if (config.icon === 'info') {
296
+ iconHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg>';
297
+ } else if (config.icon === 'success') {
298
+ iconHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path><polyline points="22 4 12 14.01 9 11.01"></polyline></svg>';
299
+ } else if (config.icon === 'question') {
300
+ iconHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path><line x1="12" y1="17" x2="12.01" y2="17"></line></svg>';
301
+ }
302
+
303
+ iconWrapper.innerHTML = iconHTML;
304
+ if (iconHTML) bodyContainer.appendChild(iconWrapper);
305
+ }
306
+
307
+ // Title
308
+ if (config.title) {
309
+ const titleEl = document.createElement('h2');
310
+ titleEl.className = 'alert90s-title';
311
+ titleEl.innerHTML = config.title;
312
+ bodyContainer.appendChild(titleEl);
313
+ }
314
+
315
+ // Content (Text/HTML)
316
+ if (config.html) {
317
+ const htmlEl = document.createElement('div');
318
+ htmlEl.className = 'alert90s-html';
319
+ htmlEl.innerHTML = config.html;
320
+ bodyContainer.appendChild(htmlEl);
321
+ } else if (config.text) {
322
+ const messageEl = document.createElement('p');
323
+ messageEl.className = 'alert90s-message';
324
+ messageEl.textContent = config.text;
325
+ bodyContainer.appendChild(messageEl);
326
+ }
327
+
328
+ // Input form
329
+ let inputEl = null;
330
+ if (config.input) {
331
+ const inputContainer = document.createElement('div');
332
+ inputContainer.className = 'alert90s-input-container';
333
+
334
+ if (config.input === 'textarea') {
335
+ inputEl = document.createElement('textarea');
336
+ } else {
337
+ inputEl = document.createElement('input');
338
+ inputEl.type = config.input;
339
+ }
340
+
341
+ inputEl.className = 'alert90s-input';
342
+
343
+ // Apply custom attributes
344
+ if (config.inputAttributes) {
345
+ for (const [key, val] of Object.entries(config.inputAttributes)) {
346
+ inputEl.setAttribute(key, val);
347
+ }
348
+ }
349
+
350
+ inputContainer.appendChild(inputEl);
351
+ bodyContainer.appendChild(inputContainer);
352
+ }
353
+
354
+ // Actions
355
+ const actions = document.createElement('div');
356
+ actions.className = 'alert90s-actions';
357
+
358
+ let confirmBtnRef = null;
359
+ let timerIntervalObj = null;
360
+
361
+ const finish = (resultObj) => {
362
+ if (timerIntervalObj) clearTimeout(timerIntervalObj);
363
+ if (config.willClose) config.willClose();
364
+
365
+ box.style.animation = 'none';
366
+
367
+ if (config.hideClass.popup === 'alert90s-fade-out') {
368
+ box.style.animation = 'alert90s-fade-out 0.2s forwards';
369
+ overlay.style.transition = 'opacity 0.2s';
370
+ overlay.style.opacity = '0';
371
+ } else {
372
+ box.className = `alert90s-box ${config.hideClass.popup}`;
373
+ }
374
+
375
+ setTimeout(() => {
376
+ if (document.body.contains(overlay)) {
377
+ document.body.removeChild(overlay);
378
+ }
379
+ resolve(resultObj);
380
+ }, 200);
381
+ };
382
+
383
+ // Handler for Confirm to support preConfirm and input
384
+ const handleConfirm = async () => {
385
+ let val = true;
386
+
387
+ if (inputEl) {
388
+ val = inputEl.value;
389
+ }
390
+
391
+ if (config.preConfirm) {
392
+ Alert90s.resetValidationMessage();
393
+ if (config.showLoaderOnConfirm) {
394
+ Alert90s.showLoading();
395
+ }
396
+ try {
397
+ const preConfirmResult = await Promise.resolve(config.preConfirm(val));
398
+ if (preConfirmResult === false) {
399
+ Alert90s.hideLoading();
400
+ return; // Prevent closing
401
+ }
402
+ if (preConfirmResult !== undefined && preConfirmResult !== val) {
403
+ val = preConfirmResult;
404
+ }
405
+ // Check if a validation error was shown during preConfirm
406
+ const valEl = box.querySelector('.alert90s-validation-message');
407
+ if (valEl && valEl.style.display === 'block') {
408
+ Alert90s.hideLoading();
409
+ return; // Prevent closing
410
+ }
411
+ } catch (error) {
412
+ Alert90s.showValidationMessage(`Request failed: ${error}`);
413
+ Alert90s.hideLoading();
414
+ return; // Prevent closing
415
+ }
416
+ }
417
+
418
+ finish({ isConfirmed: true, isDenied: false, isDismissed: false, value: val });
419
+ };
420
+
421
+ if (config.showCancelButton) {
422
+ const cancelBtn = document.createElement('button');
423
+ cancelBtn.className = 'alert90s-button cancel';
424
+ cancelBtn.innerHTML = config.cancelButtonText;
425
+ if (config.cancelButtonAriaLabel) cancelBtn.setAttribute('aria-label', config.cancelButtonAriaLabel);
426
+ if (config.cancelButtonColor) cancelBtn.style.backgroundColor = config.cancelButtonColor;
427
+ cancelBtn.onclick = () => finish({ isConfirmed: false, isDenied: false, isDismissed: true, dismiss: 'cancel' });
428
+ actions.appendChild(cancelBtn);
429
+ }
430
+
431
+ if (config.showDenyButton) {
432
+ const denyBtn = document.createElement('button');
433
+ denyBtn.className = 'alert90s-button deny';
434
+ denyBtn.innerHTML = config.denyButtonText;
435
+ if (config.denyButtonAriaLabel) denyBtn.setAttribute('aria-label', config.denyButtonAriaLabel);
436
+ if (config.denyButtonColor) denyBtn.style.backgroundColor = config.denyButtonColor;
437
+ denyBtn.onclick = () => finish({ isConfirmed: false, isDenied: true, isDismissed: false });
438
+ actions.appendChild(denyBtn);
439
+ }
440
+
441
+ if (config.showConfirmButton) {
442
+ const confirmBtn = document.createElement('button');
443
+ confirmBtn.className = 'alert90s-button confirm';
444
+ confirmBtn.innerHTML = config.confirmButtonText;
445
+ if (config.confirmButtonAriaLabel) confirmBtn.setAttribute('aria-label', config.confirmButtonAriaLabel);
446
+ if (config.confirmButtonColor) confirmBtn.style.backgroundColor = config.confirmButtonColor;
447
+ confirmBtn.onclick = handleConfirm;
448
+ actions.appendChild(confirmBtn);
449
+ confirmBtnRef = confirmBtn;
450
+ }
451
+
452
+ if (config.showCancelButton || config.showDenyButton || config.showConfirmButton) {
453
+ bodyContainer.appendChild(actions);
454
+ }
455
+
456
+ // Footer
457
+ if (config.footer) {
458
+ const footerEl = document.createElement('div');
459
+ footerEl.className = 'alert90s-footer';
460
+ footerEl.innerHTML = config.footer;
461
+ bodyContainer.appendChild(footerEl);
462
+ }
463
+
464
+ // Assemble
465
+ box.appendChild(header);
466
+ box.appendChild(bodyContainer);
467
+ overlay.appendChild(box);
468
+
469
+ if (config.showCloseButton) {
470
+ const closeBtn = header.querySelector('#alert90s-close');
471
+ if (closeBtn) {
472
+ closeBtn.onclick = () => finish({ isConfirmed: false, isDenied: false, isDismissed: true, dismiss: 'close' });
473
+ }
474
+ }
475
+
476
+ // Append to body
477
+ document.body.appendChild(overlay);
478
+
479
+ // Focus
480
+ if (config.focusConfirm && confirmBtnRef) {
481
+ setTimeout(() => {
482
+ // If input is present, sweetalert focuses input first
483
+ if (inputEl) inputEl.focus();
484
+ else confirmBtnRef.focus();
485
+ }, 50);
486
+ }
487
+
488
+ // User hooks & timers
489
+ if (config.didOpen) {
490
+ setTimeout(() => { config.didOpen(); }, 0);
491
+ }
492
+
493
+ if (config.timer) {
494
+ this.timerEnd = Date.now() + config.timer;
495
+ timerIntervalObj = setTimeout(() => {
496
+ finish({ isConfirmed: false, isDenied: false, isDismissed: true, dismiss: 'timer' });
497
+ }, config.timer);
498
+ }
499
+ });
500
+ }
501
+ }
502
+
503
+ export default Alert90s;
504
+ // Make it globally available on window if not using module bundler
505
+ if (typeof window !== 'undefined') {
506
+ window.Alert90s = Alert90s;
507
+ }
package/src/styles.css ADDED
@@ -0,0 +1,344 @@
1
+ .alert90s-overlay {
2
+ position: fixed;
3
+ inset: 0;
4
+ z-index: 99999;
5
+ display: flex;
6
+ padding: 1rem;
7
+ background-color: rgba(0, 0, 0, 0.6);
8
+ backdrop-filter: blur(4px);
9
+ animation: alert90s-fade-in 0.2s ease-out forwards;
10
+ font-family: inherit;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ /* Positions */
15
+ .alert90s-overlay.alert90s-pos-center { align-items: center; justify-content: center; }
16
+ .alert90s-overlay.alert90s-pos-top { align-items: flex-start; justify-content: center; }
17
+ .alert90s-overlay.alert90s-pos-top-start { align-items: flex-start; justify-content: flex-start; }
18
+ .alert90s-overlay.alert90s-pos-top-end { align-items: flex-start; justify-content: flex-end; }
19
+ .alert90s-overlay.alert90s-pos-bottom { align-items: flex-end; justify-content: center; }
20
+ .alert90s-overlay.alert90s-pos-bottom-start { align-items: flex-end; justify-content: flex-start; }
21
+ .alert90s-overlay.alert90s-pos-bottom-end { align-items: flex-end; justify-content: flex-end; }
22
+ .alert90s-overlay.alert90s-pos-center-start { align-items: center; justify-content: flex-start; }
23
+ .alert90s-overlay.alert90s-pos-center-end { align-items: center; justify-content: flex-end; }
24
+
25
+ .alert90s-overlay * {
26
+ box-sizing: border-box;
27
+ }
28
+ .alert90s-box {
29
+ width: 100%;
30
+ max-width: 28rem;
31
+ background-color: #ffffff;
32
+ border: 4px solid #000;
33
+ position: relative;
34
+ display: flex;
35
+ flex-direction: column;
36
+ align-items: center;
37
+ text-align: center;
38
+ animation: alert90s-pop-in 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards;
39
+ box-shadow: 12px 12px 0 0 #000;
40
+ max-height: 95vh;
41
+ }
42
+ /* RTL text alignment */
43
+ .alert90s-box[dir="rtl"] {
44
+ text-align: right;
45
+ }
46
+ .alert90s-box.is-draggable {
47
+ position: absolute; /* Needed for dragging logic */
48
+ }
49
+ .alert90s-body {
50
+ width: 100%;
51
+ padding: 3rem 2rem 2rem 2rem;
52
+ overflow-y: auto;
53
+ display: flex;
54
+ flex-direction: column;
55
+ align-items: center;
56
+ scrollbar-width: none;
57
+ }
58
+ .alert90s-body::-webkit-scrollbar {
59
+ display: none;
60
+ }
61
+ .alert90s-header {
62
+ position: absolute;
63
+ top: 0;
64
+ left: 0;
65
+ width: 100%;
66
+ height: 2rem;
67
+ display: flex;
68
+ justify-content: space-between;
69
+ align-items: center;
70
+ border-bottom: 4px solid #000;
71
+ background-color: #e2e8f0;
72
+ padding: 0 0.5rem;
73
+ z-index: 10;
74
+ }
75
+ .alert90s-header.draggable {
76
+ cursor: grab;
77
+ }
78
+ .alert90s-header.draggable:active {
79
+ cursor: grabbing;
80
+ }
81
+ .alert90s-header-left {
82
+ display: flex;
83
+ gap: 0.25rem;
84
+ }
85
+ .alert90s-box[dir="rtl"] .alert90s-header {
86
+ flex-direction: row-reverse;
87
+ }
88
+ .alert90s-header-dot {
89
+ width: 0.75rem;
90
+ height: 0.75rem;
91
+ border: 2px solid #000;
92
+ background-color: #fff;
93
+ }
94
+ .alert90s-header-right {
95
+ display: flex;
96
+ align-items: center;
97
+ gap: 0.5rem;
98
+ z-index: 11;
99
+ }
100
+ .alert90s-box[dir="rtl"] .alert90s-header-right {
101
+ flex-direction: row-reverse;
102
+ }
103
+ .alert90s-header-title {
104
+ font-size: 10px;
105
+ font-weight: bold;
106
+ letter-spacing: 0.1em;
107
+ text-transform: uppercase;
108
+ pointer-events: none;
109
+ }
110
+ .alert90s-close-btn {
111
+ width: 1.25rem;
112
+ height: 1.25rem;
113
+ border: 2px solid #000;
114
+ background-color: #ef4444;
115
+ display: flex;
116
+ align-items: center;
117
+ justify-content: center;
118
+ cursor: pointer;
119
+ padding: 0;
120
+ transition: transform 0.1s, background-color 0.1s;
121
+ }
122
+ .alert90s-close-btn:hover {
123
+ background-color: #f87171;
124
+ transform: scale(1.1);
125
+ }
126
+ .alert90s-close-btn span {
127
+ font-size: 14px;
128
+ color: #000;
129
+ font-weight: bold;
130
+ line-height: 1;
131
+ }
132
+
133
+ /* Image */
134
+ .alert90s-image-container {
135
+ width: 100%;
136
+ margin-bottom: 1rem;
137
+ border: 4px solid #000;
138
+ box-shadow: 4px 4px 0 0 #000;
139
+ background: #fff;
140
+ overflow: hidden;
141
+ display: flex;
142
+ justify-content: center;
143
+ align-items: center;
144
+ }
145
+ .alert90s-image-container img {
146
+ max-width: 100%;
147
+ height: auto;
148
+ display: block;
149
+ }
150
+
151
+ /* Icon */
152
+ .alert90s-icon {
153
+ width: 4rem;
154
+ height: 4rem;
155
+ border: 4px solid #000;
156
+ border-radius: 9999px;
157
+ display: flex;
158
+ align-items: center;
159
+ justify-content: center;
160
+ margin-bottom: 1rem;
161
+ box-shadow: 4px 4px 0 0 #000;
162
+ flex-shrink: 0;
163
+ }
164
+ .alert90s-icon svg, .alert90s-icon img { width: 2rem; height: 2rem; }
165
+ .alert90s-icon-custom {
166
+ font-size: 2.25rem;
167
+ font-weight: bold;
168
+ line-height: 1;
169
+ }
170
+ .alert90s-icon.warning { background-color: #facc15; }
171
+ .alert90s-icon.danger, .alert90s-icon.error { background-color: #ef4444; color: #fff; }
172
+ .alert90s-icon.info, .alert90s-icon.question { background-color: #60a5fa; }
173
+ .alert90s-icon.success { background-color: #4ade80; }
174
+
175
+ /* Typography */
176
+ .alert90s-title {
177
+ font-size: 1.5rem;
178
+ font-weight: 900;
179
+ text-transform: uppercase;
180
+ letter-spacing: 0.1em;
181
+ color: #1a1a1a;
182
+ margin: 0 0 0.5rem 0;
183
+ line-height: 1.2;
184
+ }
185
+ .alert90s-message {
186
+ font-size: 0.875rem;
187
+ font-weight: bold;
188
+ color: #475569;
189
+ margin: 0 0 1.5rem 0;
190
+ max-width: 100%;
191
+ }
192
+ .alert90s-html {
193
+ font-size: 0.875rem;
194
+ color: #1a1a1a;
195
+ margin: 0 0 1.5rem 0;
196
+ max-width: 100%;
197
+ line-height: 1.5;
198
+ }
199
+ .alert90s-box[dir="rtl"] .alert90s-html {
200
+ text-align: right;
201
+ }
202
+ .alert90s-html a { color: #2563eb; font-weight: bold; text-decoration: underline; text-decoration-thickness: 2px; }
203
+ .alert90s-html a:hover { background-color: #bfdbfe; }
204
+ .alert90s-html b, .alert90s-html strong { font-weight: 900; }
205
+
206
+ /* Input Elements */
207
+ .alert90s-input-container {
208
+ width: 100%;
209
+ margin-bottom: 1.5rem;
210
+ }
211
+ .alert90s-input {
212
+ width: 100%;
213
+ border: 4px solid #000;
214
+ font-family: inherit;
215
+ font-size: 1rem;
216
+ padding: 0.75rem;
217
+ font-weight: bold;
218
+ background-color: #f8fafc;
219
+ outline: none;
220
+ box-shadow: inset 4px 4px 0 0 rgba(0,0,0,0.05);
221
+ transition: background-color 0.2s;
222
+ }
223
+ .alert90s-input:focus {
224
+ background-color: #fff;
225
+ border-color: #2563eb;
226
+ }
227
+ textarea.alert90s-input {
228
+ resize: vertical;
229
+ min-height: 100px;
230
+ }
231
+ .alert90s-validation-message {
232
+ display: none;
233
+ width: 100%;
234
+ padding: 0.75rem;
235
+ background-color: #fef2f2;
236
+ border: 4px solid #000;
237
+ border-left-color: #ef4444;
238
+ border-left-width: 8px;
239
+ color: #ef4444;
240
+ font-weight: bold;
241
+ font-size: 0.875rem;
242
+ margin-bottom: 1.5rem;
243
+ text-align: left;
244
+ }
245
+ .alert90s-box[dir="rtl"] .alert90s-validation-message {
246
+ text-align: right;
247
+ border-left-width: 4px;
248
+ border-left-color: #000;
249
+ border-right-color: #ef4444;
250
+ border-right-width: 8px;
251
+ }
252
+
253
+ /* Actions */
254
+ .alert90s-actions {
255
+ display: flex;
256
+ flex-direction: column;
257
+ gap: 1rem;
258
+ width: 100%;
259
+ justify-content: center;
260
+ }
261
+ @media (min-width: 640px) {
262
+ .alert90s-actions { flex-direction: row; }
263
+ }
264
+
265
+ .alert90s-button {
266
+ width: 100%;
267
+ padding: 0.75rem 1rem;
268
+ border: 4px solid #000;
269
+ font-weight: 900;
270
+ text-transform: uppercase;
271
+ letter-spacing: 0.1em;
272
+ font-size: 0.875rem;
273
+ cursor: pointer;
274
+ transition: background-color 0.1s;
275
+ box-shadow: 4px 4px 0 0 #000;
276
+ font-family: inherit;
277
+ margin: 0;
278
+ background-color: #fff;
279
+ color: #000;
280
+ display: flex;
281
+ align-items: center;
282
+ justify-content: center;
283
+ gap: 0.5rem;
284
+ }
285
+ @media (min-width: 640px) {
286
+ .alert90s-button { width: auto; min-width: 100px; flex: 1; }
287
+ }
288
+ .alert90s-button:active { box-shadow: 0 0 0 0 #000; transform: translate(4px, 4px); }
289
+ .alert90s-button:focus { outline: 2px dashed #000; outline-offset: 4px; }
290
+
291
+ /* Default Button colors */
292
+ .alert90s-button.cancel { background-color: #f87171; }
293
+ .alert90s-button.cancel:hover { background-color: #fca5a5; }
294
+ .alert90s-button.deny { background-color: #fb923c; }
295
+ .alert90s-button.deny:hover { background-color: #fdba74; }
296
+ .alert90s-button.confirm { background-color: #4ade80; }
297
+ .alert90s-button.confirm:hover { background-color: #86efac; }
298
+
299
+
300
+ /* Footer */
301
+ .alert90s-footer {
302
+ width: 100%;
303
+ margin-top: 1.5rem;
304
+ padding-top: 1rem;
305
+ border-top: 4px solid #000;
306
+ font-size: 0.75rem;
307
+ font-weight: bold;
308
+ color: #475569;
309
+ }
310
+ .alert90s-footer a { color: #2563eb; text-decoration: underline; text-decoration-thickness: 2px; }
311
+ .alert90s-footer a:hover { background-color: #bfdbfe; }
312
+
313
+ /* Progress Bar */
314
+ .alert90s-progress-bar {
315
+ position: absolute;
316
+ bottom: 0;
317
+ left: 0;
318
+ height: 6px;
319
+ background-color: #000;
320
+ width: 100%;
321
+ animation: alert90s-progress linear forwards;
322
+ }
323
+
324
+ /* Loader */
325
+ .alert90s-loader {
326
+ display: flex;
327
+ justify-content: center;
328
+ align-items: center;
329
+ padding: 1rem 0;
330
+ }
331
+ .alert90s-spinner {
332
+ width: 2rem;
333
+ height: 2rem;
334
+ border: 4px solid #e2e8f0;
335
+ border-top: 4px solid #000;
336
+ border-radius: 50%;
337
+ animation: alert90s-spin 1s linear infinite;
338
+ }
339
+
340
+ @keyframes alert90s-spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
341
+ @keyframes alert90s-progress { from { width: 100%; } to { width: 0%; } }
342
+ @keyframes alert90s-fade-in { from { opacity: 0; } to { opacity: 1; } }
343
+ @keyframes alert90s-pop-in { 0% { transform: scale(0.95) translateY(10px); opacity: 0; } 100% { transform: scale(1) translateY(0); opacity: 1; } }
344
+ @keyframes alert90s-fade-out { 0% { transform: scale(1) translateY(0); opacity: 1; } 100% { transform: scale(0.95) translateY(10px); opacity: 0; } }