@myxo-victor/chexjs 6.0.0
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/ChexJs/.htaccess +9 -0
- package/ChexJs/Chex.js +712 -0
- package/ChexJs/Chex.php +354 -0
- package/ChexJs/Chex_notify.php +68 -0
- package/ChexJs/LICENSE +22 -0
- package/ChexJs/README.md +121 -0
- package/ChexJs/api/read.txt +4 -0
- package/ChexJs/app/api/api.txt +4 -0
- package/ChexJs/app/components/main.js +4 -0
- package/ChexJs/app/index.css +12 -0
- package/ChexJs/app/index.html +28 -0
- package/ChexJs/app/logic/app.js +4 -0
- package/ChexJs/components/demo.js +45 -0
- package/ChexJs/components/main.js +609 -0
- package/ChexJs/images/logo.png +0 -0
- package/ChexJs/index.css +118 -0
- package/ChexJs/index.html +44 -0
- package/ChexJs/libs/modal.js +617 -0
- package/ChexJs/libs/orbit.js +217 -0
- package/ChexJs/libs/racket.js +374 -0
- package/ChexJs/libs/rinx.js +88 -0
- package/ChexJs/libs/scrollEcho.js +212 -0
- package/ChexJs/libs/skeleton.js +341 -0
- package/ChexJs/libs/smooth.js +647 -0
- package/ChexJs/logic/app.js +36 -0
- package/ChexJs/notification_handler.txt +1 -0
- package/ChexJs/sw.js +67 -0
- package/package.json +12 -0
|
@@ -0,0 +1,617 @@
|
|
|
1
|
+
/* ==========================================================
|
|
2
|
+
FLEXMODAL: Self-Styling Zero-Dependency Modal Engine
|
|
3
|
+
========================================================== */
|
|
4
|
+
class FlexModal {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.backdrop = null;
|
|
7
|
+
this.container = null;
|
|
8
|
+
this.focusableElements = [];
|
|
9
|
+
this.previouslyFocusedElement = null;
|
|
10
|
+
this.resolvePromise = null;
|
|
11
|
+
|
|
12
|
+
this._boundKeyDown = this._handleKeyDown.bind(this);
|
|
13
|
+
this._boundOverlayClick = this._handleOverlayClick.bind(this);
|
|
14
|
+
|
|
15
|
+
// Dynamic self-styling injection
|
|
16
|
+
this._injectStyles();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Pure CSS Stylesheet Injector (Zero CSS dependencies)
|
|
20
|
+
_injectStyles() {
|
|
21
|
+
const STYLE_ID = 'flexmodal-embedded-styles';
|
|
22
|
+
if (document.getElementById(STYLE_ID)) return;
|
|
23
|
+
|
|
24
|
+
const stylesheet = document.createElement('style');
|
|
25
|
+
stylesheet.id = STYLE_ID;
|
|
26
|
+
stylesheet.textContent = `
|
|
27
|
+
.flexmodal-backdrop {
|
|
28
|
+
position: fixed;
|
|
29
|
+
top: 0;
|
|
30
|
+
left: 0;
|
|
31
|
+
width: 100%;
|
|
32
|
+
height: 100%;
|
|
33
|
+
background-color: rgba(15, 23, 42, 0.6);
|
|
34
|
+
backdrop-filter: blur(4px);
|
|
35
|
+
-webkit-backdrop-filter: blur(4px);
|
|
36
|
+
z-index: 99999;
|
|
37
|
+
display: flex;
|
|
38
|
+
align-items: center;
|
|
39
|
+
justify-content: center;
|
|
40
|
+
padding: 16px;
|
|
41
|
+
opacity: 0;
|
|
42
|
+
transition: opacity 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
|
43
|
+
box-sizing: border-box;
|
|
44
|
+
}
|
|
45
|
+
.flexmodal-backdrop.is-active {
|
|
46
|
+
opacity: 1;
|
|
47
|
+
}
|
|
48
|
+
.flexmodal-container {
|
|
49
|
+
background: #ffffff;
|
|
50
|
+
border-radius: 16px;
|
|
51
|
+
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
|
52
|
+
width: 100%;
|
|
53
|
+
max-width: 500px;
|
|
54
|
+
max-height: 90vh;
|
|
55
|
+
overflow-y: auto;
|
|
56
|
+
border: 1px solid #e2e8f0;
|
|
57
|
+
transform: scale(0.95) translateY(12px);
|
|
58
|
+
opacity: 0;
|
|
59
|
+
transition: transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.25s ease;
|
|
60
|
+
box-sizing: border-box;
|
|
61
|
+
}
|
|
62
|
+
.flexmodal-backdrop.is-active .flexmodal-container {
|
|
63
|
+
transform: scale(1) translateY(0);
|
|
64
|
+
opacity: 1;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* Slide-Over Right Panel variation */
|
|
68
|
+
.flexmodal-backdrop.slide-over {
|
|
69
|
+
justify-content: flex-end;
|
|
70
|
+
padding: 0;
|
|
71
|
+
}
|
|
72
|
+
.flexmodal-backdrop.slide-over .flexmodal-container {
|
|
73
|
+
height: 100%;
|
|
74
|
+
max-height: 100%;
|
|
75
|
+
border-radius: 0;
|
|
76
|
+
border-left: 1px solid #e2e8f0;
|
|
77
|
+
border-top: none;
|
|
78
|
+
border-bottom: none;
|
|
79
|
+
width: 100%;
|
|
80
|
+
max-width: 400px;
|
|
81
|
+
transform: translateX(100%);
|
|
82
|
+
transition: transform 0.28s cubic-bezier(0.16, 1, 0.3, 1);
|
|
83
|
+
}
|
|
84
|
+
.flexmodal-backdrop.slide-over.is-active .flexmodal-container {
|
|
85
|
+
transform: translateX(0);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/* Inner UI Styles */
|
|
89
|
+
.flexmodal-body {
|
|
90
|
+
padding: 24px;
|
|
91
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
92
|
+
color: #1e293b;
|
|
93
|
+
line-height: 1.5;
|
|
94
|
+
box-sizing: border-box;
|
|
95
|
+
}
|
|
96
|
+
.flexmodal-row {
|
|
97
|
+
display: flex;
|
|
98
|
+
gap: 16px;
|
|
99
|
+
align-items: flex-start;
|
|
100
|
+
}
|
|
101
|
+
.flexmodal-column {
|
|
102
|
+
flex: 1;
|
|
103
|
+
}
|
|
104
|
+
.flexmodal-title {
|
|
105
|
+
font-size: 18px;
|
|
106
|
+
font-weight: 700;
|
|
107
|
+
margin: 0 0 6px 0;
|
|
108
|
+
color: #0f172a;
|
|
109
|
+
}
|
|
110
|
+
.flexmodal-subtitle {
|
|
111
|
+
font-size: 14px;
|
|
112
|
+
color: #64748b;
|
|
113
|
+
margin: 0;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/* Icons */
|
|
117
|
+
.flexmodal-icon-box {
|
|
118
|
+
display: flex;
|
|
119
|
+
align-items: center;
|
|
120
|
+
justify-content: center;
|
|
121
|
+
width: 48px;
|
|
122
|
+
height: 48px;
|
|
123
|
+
border-radius: 50%;
|
|
124
|
+
flex-shrink: 0;
|
|
125
|
+
}
|
|
126
|
+
.flexmodal-icon-box.informational {
|
|
127
|
+
background-color: #e0f2fe;
|
|
128
|
+
color: #0369a1;
|
|
129
|
+
}
|
|
130
|
+
.flexmodal-icon-box.promise {
|
|
131
|
+
background-color: #dcfce7;
|
|
132
|
+
color: #15803d;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/* Header & Close Action */
|
|
136
|
+
.flexmodal-head-row {
|
|
137
|
+
display: flex;
|
|
138
|
+
justify-content: space-between;
|
|
139
|
+
align-items: center;
|
|
140
|
+
border-bottom: 1px solid #f1f5f9;
|
|
141
|
+
padding-bottom: 16px;
|
|
142
|
+
margin-bottom: 16px;
|
|
143
|
+
}
|
|
144
|
+
.flexmodal-head-title {
|
|
145
|
+
font-size: 18px;
|
|
146
|
+
font-weight: 700;
|
|
147
|
+
margin: 0;
|
|
148
|
+
color: #0f172a;
|
|
149
|
+
}
|
|
150
|
+
.flexmodal-close-icon {
|
|
151
|
+
background: transparent;
|
|
152
|
+
border: none;
|
|
153
|
+
padding: 4px;
|
|
154
|
+
border-radius: 6px;
|
|
155
|
+
cursor: pointer;
|
|
156
|
+
color: #94a3b8;
|
|
157
|
+
display: flex;
|
|
158
|
+
align-items: center;
|
|
159
|
+
justify-content: center;
|
|
160
|
+
transition: background 0.15s, color 0.15s;
|
|
161
|
+
}
|
|
162
|
+
.flexmodal-close-icon:hover {
|
|
163
|
+
background-color: #f1f5f9;
|
|
164
|
+
color: #334155;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/* Buttons & Footer */
|
|
168
|
+
.flexmodal-footer {
|
|
169
|
+
display: flex;
|
|
170
|
+
justify-content: flex-end;
|
|
171
|
+
gap: 12px;
|
|
172
|
+
margin-top: 24px;
|
|
173
|
+
padding-top: 16px;
|
|
174
|
+
border-top: 1px solid #f1f5f9;
|
|
175
|
+
}
|
|
176
|
+
.flexmodal-btn {
|
|
177
|
+
font-family: inherit;
|
|
178
|
+
font-size: 14px;
|
|
179
|
+
font-weight: 600;
|
|
180
|
+
padding: 10px 18px;
|
|
181
|
+
border-radius: 8px;
|
|
182
|
+
cursor: pointer;
|
|
183
|
+
transition: background 0.15s, border-color 0.15s;
|
|
184
|
+
border: none;
|
|
185
|
+
outline: none;
|
|
186
|
+
}
|
|
187
|
+
.flexmodal-btn:focus-visible {
|
|
188
|
+
outline: 2px solid #4f46e5;
|
|
189
|
+
outline-offset: 2px;
|
|
190
|
+
}
|
|
191
|
+
.flexmodal-btn-primary {
|
|
192
|
+
background-color: #4f46e5;
|
|
193
|
+
color: #ffffff;
|
|
194
|
+
}
|
|
195
|
+
.flexmodal-btn-primary:hover {
|
|
196
|
+
background-color: #4338ca;
|
|
197
|
+
}
|
|
198
|
+
.flexmodal-btn-success {
|
|
199
|
+
background-color: #16a34a;
|
|
200
|
+
color: #ffffff;
|
|
201
|
+
}
|
|
202
|
+
.flexmodal-btn-success:hover {
|
|
203
|
+
background-color: #15803d;
|
|
204
|
+
}
|
|
205
|
+
.flexmodal-btn-secondary {
|
|
206
|
+
background-color: #ffffff;
|
|
207
|
+
border: 1px solid #cbd5e1;
|
|
208
|
+
color: #475569;
|
|
209
|
+
}
|
|
210
|
+
.flexmodal-btn-secondary:hover {
|
|
211
|
+
background-color: #f8fafc;
|
|
212
|
+
border-color: #94a3b8;
|
|
213
|
+
}
|
|
214
|
+
.flexmodal-btn-full {
|
|
215
|
+
width: 100%;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/* Containers for tabs */
|
|
219
|
+
.flexmodal-tabs-container {
|
|
220
|
+
margin-top: 16px;
|
|
221
|
+
display: flex;
|
|
222
|
+
flex-direction: column;
|
|
223
|
+
gap: 10px;
|
|
224
|
+
}
|
|
225
|
+
`;
|
|
226
|
+
document.head.appendChild(stylesheet);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Static entry shortcut
|
|
230
|
+
static open(options) {
|
|
231
|
+
const modal = new FlexModal();
|
|
232
|
+
return modal.open(options);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
open(options = {}) {
|
|
236
|
+
this.options = Object.assign({
|
|
237
|
+
type: 'informational', // informational, promise, slide-over, dynamic
|
|
238
|
+
title: 'Alert Notification',
|
|
239
|
+
subtitle: '',
|
|
240
|
+
button1: 'OK',
|
|
241
|
+
button2: 'Cancel',
|
|
242
|
+
tabs: null, // HTML string OR HTMLElement node
|
|
243
|
+
html: '', // Raw HTML for dynamic mode
|
|
244
|
+
closeOnOverlayClick: true,
|
|
245
|
+
closeOnEscape: true
|
|
246
|
+
}, options);
|
|
247
|
+
|
|
248
|
+
this.previouslyFocusedElement = document.activeElement;
|
|
249
|
+
|
|
250
|
+
// Build the dynamic elements
|
|
251
|
+
this._prepareDOM();
|
|
252
|
+
|
|
253
|
+
// Attach keyboard / overlay binders
|
|
254
|
+
if (this.options.closeOnOverlayClick) {
|
|
255
|
+
this.backdrop.addEventListener('click', this._boundOverlayClick);
|
|
256
|
+
}
|
|
257
|
+
if (this.options.closeOnEscape) {
|
|
258
|
+
document.addEventListener('keydown', this._boundKeyDown);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Trigger visual transitions
|
|
262
|
+
requestAnimationFrame(() => {
|
|
263
|
+
this.backdrop.classList.add('is-active');
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
// Capture focus loop
|
|
267
|
+
this._updateFocusableElements();
|
|
268
|
+
if (this.focusableElements.length > 0) {
|
|
269
|
+
this.focusableElements[0].focus();
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Auto update any lucide-icons if present in host app
|
|
273
|
+
if (window.lucide) {
|
|
274
|
+
window.lucide.createIcons();
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return new Promise((resolve) => {
|
|
278
|
+
this.resolvePromise = resolve;
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
_prepareDOM() {
|
|
283
|
+
this.backdrop = document.createElement('div');
|
|
284
|
+
this.backdrop.className = `flexmodal-backdrop ${this.options.type === 'slide-over' ? 'slide-over' : ''}`;
|
|
285
|
+
this.backdrop.setAttribute('role', 'dialog');
|
|
286
|
+
this.backdrop.setAttribute('aria-modal', 'true');
|
|
287
|
+
|
|
288
|
+
this.container = document.createElement('div');
|
|
289
|
+
this.container.className = 'flexmodal-container';
|
|
290
|
+
this.backdrop.appendChild(this.container);
|
|
291
|
+
|
|
292
|
+
// Assemble the body markup with pure CSS styles
|
|
293
|
+
const bodyWrapper = document.createElement('div');
|
|
294
|
+
bodyWrapper.className = 'flexmodal-body';
|
|
295
|
+
|
|
296
|
+
if (this.options.type === 'slide-over') {
|
|
297
|
+
// Right Slide Drawer Panel
|
|
298
|
+
const headRow = document.createElement('div');
|
|
299
|
+
headRow.className = 'flexmodal-head-row';
|
|
300
|
+
|
|
301
|
+
const headTitle = document.createElement('h3');
|
|
302
|
+
headTitle.className = 'flexmodal-head-title';
|
|
303
|
+
headTitle.textContent = this.options.title;
|
|
304
|
+
|
|
305
|
+
const closeBtn = document.createElement('button');
|
|
306
|
+
closeBtn.className = 'flexmodal-close-icon';
|
|
307
|
+
closeBtn.onclick = () => this.close(false);
|
|
308
|
+
closeBtn.appendChild(this._svgCloseIcon());
|
|
309
|
+
|
|
310
|
+
headRow.appendChild(headTitle);
|
|
311
|
+
headRow.appendChild(closeBtn);
|
|
312
|
+
|
|
313
|
+
const contentBox = document.createElement('div');
|
|
314
|
+
contentBox.className = 'flexmodal-column';
|
|
315
|
+
|
|
316
|
+
if (this.options.subtitle) {
|
|
317
|
+
const subtitle = document.createElement('p');
|
|
318
|
+
subtitle.className = 'flexmodal-subtitle';
|
|
319
|
+
subtitle.textContent = this.options.subtitle;
|
|
320
|
+
contentBox.appendChild(subtitle);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const tabsTarget = document.createElement('div');
|
|
324
|
+
tabsTarget.id = 'flexmodal-tabs-target';
|
|
325
|
+
tabsTarget.className = 'flexmodal-tabs-container';
|
|
326
|
+
contentBox.appendChild(tabsTarget);
|
|
327
|
+
|
|
328
|
+
const footer = document.createElement('div');
|
|
329
|
+
footer.className = 'flexmodal-footer';
|
|
330
|
+
|
|
331
|
+
const footerBtn = document.createElement('button');
|
|
332
|
+
footerBtn.className = 'flexmodal-btn flexmodal-btn-primary flexmodal-btn-full';
|
|
333
|
+
footerBtn.onclick = () => this.close(true);
|
|
334
|
+
footerBtn.textContent = this.options.button1;
|
|
335
|
+
footer.appendChild(footerBtn);
|
|
336
|
+
|
|
337
|
+
bodyWrapper.appendChild(headRow);
|
|
338
|
+
bodyWrapper.appendChild(contentBox);
|
|
339
|
+
bodyWrapper.appendChild(footer);
|
|
340
|
+
|
|
341
|
+
} else if (this.options.type === 'dynamic') {
|
|
342
|
+
// Custom Dynamic Injected Canvas
|
|
343
|
+
const headRow = document.createElement('div');
|
|
344
|
+
headRow.className = 'flexmodal-head-row';
|
|
345
|
+
|
|
346
|
+
const headTitle = document.createElement('h3');
|
|
347
|
+
headTitle.className = 'flexmodal-head-title';
|
|
348
|
+
headTitle.textContent = this.options.title;
|
|
349
|
+
|
|
350
|
+
const closeBtn = document.createElement('button');
|
|
351
|
+
closeBtn.className = 'flexmodal-close-icon';
|
|
352
|
+
closeBtn.onclick = () => this.close(false);
|
|
353
|
+
closeBtn.appendChild(this._svgCloseIcon());
|
|
354
|
+
|
|
355
|
+
headRow.appendChild(headTitle);
|
|
356
|
+
headRow.appendChild(closeBtn);
|
|
357
|
+
|
|
358
|
+
const dynamicContentWrapper = document.createElement('div');
|
|
359
|
+
dynamicContentWrapper.style.margin = '16px 0';
|
|
360
|
+
if (typeof this.options.html === 'string') {
|
|
361
|
+
dynamicContentWrapper.innerHTML = this.options.html;
|
|
362
|
+
} else if (this.options.html instanceof HTMLElement) {
|
|
363
|
+
dynamicContentWrapper.appendChild(this.options.html);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const footer = document.createElement('div');
|
|
367
|
+
footer.className = 'flexmodal-footer';
|
|
368
|
+
|
|
369
|
+
const footerBtn = document.createElement('button');
|
|
370
|
+
footerBtn.className = 'flexmodal-btn flexmodal-btn-primary';
|
|
371
|
+
footerBtn.onclick = () => this.close(true);
|
|
372
|
+
footerBtn.textContent = this.options.button1;
|
|
373
|
+
footer.appendChild(footerBtn);
|
|
374
|
+
|
|
375
|
+
bodyWrapper.appendChild(headRow);
|
|
376
|
+
bodyWrapper.appendChild(dynamicContentWrapper);
|
|
377
|
+
bodyWrapper.appendChild(footer);
|
|
378
|
+
|
|
379
|
+
} else {
|
|
380
|
+
// Informational & Promise Standard Dialog Box
|
|
381
|
+
const isPromise = this.options.type === 'promise';
|
|
382
|
+
const iconClass = isPromise ? 'promise' : 'informational';
|
|
383
|
+
|
|
384
|
+
const row = document.createElement('div');
|
|
385
|
+
row.className = 'flexmodal-row';
|
|
386
|
+
|
|
387
|
+
const iconBox = document.createElement('div');
|
|
388
|
+
iconBox.className = `flexmodal-icon-box ${iconClass}`;
|
|
389
|
+
iconBox.appendChild(isPromise ? this._svgPromiseIcon() : this._svgAlertIcon());
|
|
390
|
+
|
|
391
|
+
const column = document.createElement('div');
|
|
392
|
+
column.className = 'flexmodal-column';
|
|
393
|
+
|
|
394
|
+
const title = document.createElement('h3');
|
|
395
|
+
title.className = 'flexmodal-title';
|
|
396
|
+
title.textContent = this.options.title;
|
|
397
|
+
column.appendChild(title);
|
|
398
|
+
|
|
399
|
+
if (this.options.subtitle) {
|
|
400
|
+
const subtitle = document.createElement('p');
|
|
401
|
+
subtitle.className = 'flexmodal-subtitle';
|
|
402
|
+
subtitle.textContent = this.options.subtitle;
|
|
403
|
+
column.appendChild(subtitle);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
row.appendChild(iconBox);
|
|
407
|
+
row.appendChild(column);
|
|
408
|
+
|
|
409
|
+
const footer = document.createElement('div');
|
|
410
|
+
footer.className = 'flexmodal-footer';
|
|
411
|
+
|
|
412
|
+
if (isPromise) {
|
|
413
|
+
const btnSecondary = document.createElement('button');
|
|
414
|
+
btnSecondary.className = 'flexmodal-btn flexmodal-btn-secondary';
|
|
415
|
+
btnSecondary.onclick = () => this.close(false);
|
|
416
|
+
btnSecondary.textContent = this.options.button2;
|
|
417
|
+
footer.appendChild(btnSecondary);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const btnPrimary = document.createElement('button');
|
|
421
|
+
btnPrimary.className = `flexmodal-btn ${isPromise ? 'flexmodal-btn-success' : 'flexmodal-btn-primary'}`;
|
|
422
|
+
btnPrimary.onclick = () => this.close(true);
|
|
423
|
+
btnPrimary.textContent = this.options.button1;
|
|
424
|
+
footer.appendChild(btnPrimary);
|
|
425
|
+
|
|
426
|
+
bodyWrapper.appendChild(row);
|
|
427
|
+
bodyWrapper.appendChild(footer);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
this.container.appendChild(bodyWrapper);
|
|
431
|
+
|
|
432
|
+
// Inject tabs element node if applicable
|
|
433
|
+
if (this.options.type === 'slide-over' && this.options.tabs) {
|
|
434
|
+
const target = this.container.querySelector('#flexmodal-tabs-target');
|
|
435
|
+
if (target) {
|
|
436
|
+
if (this.options.tabs instanceof HTMLElement) {
|
|
437
|
+
target.appendChild(this.options.tabs);
|
|
438
|
+
} else {
|
|
439
|
+
target.innerHTML = this.options.tabs;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
document.body.appendChild(this.backdrop);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/* Internal SVGs to ensure zero-dependencies */
|
|
448
|
+
_svgCloseIcon() {
|
|
449
|
+
const wrapper = document.createElement('div');
|
|
450
|
+
wrapper.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" 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>`;
|
|
451
|
+
return wrapper.firstChild;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
_svgAlertIcon() {
|
|
455
|
+
const wrapper = document.createElement('div');
|
|
456
|
+
wrapper.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" 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>`;
|
|
457
|
+
return wrapper.firstChild;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
_svgPromiseIcon() {
|
|
461
|
+
const wrapper = document.createElement('div');
|
|
462
|
+
wrapper.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" 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>`;
|
|
463
|
+
return wrapper.firstChild;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
close(resultVal = false) {
|
|
467
|
+
if (!this.backdrop) return;
|
|
468
|
+
|
|
469
|
+
this.backdrop.classList.remove('is-active');
|
|
470
|
+
|
|
471
|
+
// Tear down elements after transitions
|
|
472
|
+
setTimeout(() => {
|
|
473
|
+
this._destroy(resultVal);
|
|
474
|
+
}, 250);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
_destroy(resultVal) {
|
|
478
|
+
if (this.options.closeOnOverlayClick && this.backdrop) {
|
|
479
|
+
this.backdrop.removeEventListener('click', this._boundOverlayClick);
|
|
480
|
+
}
|
|
481
|
+
if (this.options.closeOnEscape) {
|
|
482
|
+
document.removeEventListener('keydown', this._boundKeyDown);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
if (this.backdrop && this.backdrop.parentNode) {
|
|
486
|
+
this.backdrop.parentNode.removeChild(this.backdrop);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
if (this.previouslyFocusedElement) {
|
|
490
|
+
this.previouslyFocusedElement.focus();
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
if (this.resolvePromise) {
|
|
494
|
+
this.resolvePromise(resultVal);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
_handleOverlayClick(e) {
|
|
499
|
+
if (e.target === this.backdrop) {
|
|
500
|
+
this.close(false);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
_handleKeyDown(e) {
|
|
505
|
+
if (e.key === 'Escape') {
|
|
506
|
+
this.close(false);
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
if (e.key === 'Tab') {
|
|
511
|
+
this._updateFocusableElements();
|
|
512
|
+
if (this.focusableElements.length === 0) {
|
|
513
|
+
e.preventDefault();
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
const first = this.focusableElements[0];
|
|
518
|
+
const last = this.focusableElements[this.focusableElements.length - 1];
|
|
519
|
+
|
|
520
|
+
if (e.shiftKey) { // Back Tab
|
|
521
|
+
if (document.activeElement === first) {
|
|
522
|
+
last.focus();
|
|
523
|
+
e.preventDefault();
|
|
524
|
+
}
|
|
525
|
+
} else { // Forward Tab
|
|
526
|
+
if (document.activeElement === last) {
|
|
527
|
+
first.focus();
|
|
528
|
+
e.preventDefault();
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
_updateFocusableElements() {
|
|
535
|
+
const query = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
|
|
536
|
+
this.focusableElements = Array.from(this.container.querySelectorAll(query))
|
|
537
|
+
.filter(el => !el.hasAttribute('disabled') && el.offsetParent !== null);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// Global hook injection
|
|
542
|
+
window.modal = FlexModal;
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
/*****************************
|
|
546
|
+
* How to use this library: Use anyone of your choice below to open a modal. The modal returns a Promise that resolves to true or false depending on the user's action.
|
|
547
|
+
*****************************
|
|
548
|
+
*/
|
|
549
|
+
|
|
550
|
+
//For informational modal:
|
|
551
|
+
//Use this when you want to show a clean message box to the user with a single action button.
|
|
552
|
+
/* modal.open({
|
|
553
|
+
type: 'informational',
|
|
554
|
+
title: 'Welcome to My App',
|
|
555
|
+
subtitle: 'In this app you can enjoy free gifts, I hope this finds you well!',
|
|
556
|
+
button1: 'Awesome, got it!'
|
|
557
|
+
}).then((dismissed) => {
|
|
558
|
+
console.log("Modal closed:", dismissed); // Returns true when button1 is clicked
|
|
559
|
+
});
|
|
560
|
+
*/
|
|
561
|
+
|
|
562
|
+
// For promise modal:
|
|
563
|
+
//Use this for dual-option operations (such as destructive actions). It returns a native JavaScript Promise resolving to true (primary button clicked) or false (secondary button clicked, backdrop clicked, or Escape pressed).
|
|
564
|
+
/*
|
|
565
|
+
modal.open({
|
|
566
|
+
type: 'promise',
|
|
567
|
+
title: 'Confirm Critical Settings',
|
|
568
|
+
subtitle: 'Are you sure you want to deploy these configuration files to production?',
|
|
569
|
+
button1: 'Deploy Now',
|
|
570
|
+
button2: 'Review Changes'
|
|
571
|
+
}).then((confirmed) => {
|
|
572
|
+
if (confirmed) {
|
|
573
|
+
console.log("Proceed with deployment...");
|
|
574
|
+
} else {
|
|
575
|
+
console.log("User cancelled execution.");
|
|
576
|
+
}
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
*/
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
//For slide-over modal:
|
|
583
|
+
//Use this for a right-side slide-over panel with optional tabbed content. It returns a native JavaScript Promise resolving to true (primary button clicked) or false (secondary button clicked, backdrop clicked, or Escape pressed).
|
|
584
|
+
/*
|
|
585
|
+
modal.open({
|
|
586
|
+
type: 'slide-over',
|
|
587
|
+
title: 'Settings Panel',
|
|
588
|
+
subtitle: 'Adjust your preferences below.',
|
|
589
|
+
button1: 'Save Changes',
|
|
590
|
+
button2: 'Cancel',
|
|
591
|
+
tabs: '<div>Tab 1 Content</div><div>Tab 2 Content</div>'
|
|
592
|
+
}).then((confirmed) => {
|
|
593
|
+
if (confirmed) {
|
|
594
|
+
console.log("Changes saved.");
|
|
595
|
+
} else {
|
|
596
|
+
console.log("User cancelled changes.");
|
|
597
|
+
}
|
|
598
|
+
});
|
|
599
|
+
*/
|
|
600
|
+
|
|
601
|
+
//For dynamic modal:
|
|
602
|
+
//Use this for a fully custom modal with your own HTML content. It returns a native JavaScript Promise resolving to true (primary button clicked) or false (secondary button clicked, backdrop clicked, or Escape pressed).
|
|
603
|
+
/*
|
|
604
|
+
modal.open({
|
|
605
|
+
type: 'dynamic',
|
|
606
|
+
title: 'Custom Modal',
|
|
607
|
+
html: '<p>This is a custom modal with your own HTML content.</p><input type="text" placeholder="Enter something...">',
|
|
608
|
+
button1: 'Submit'
|
|
609
|
+
}).then((confirmed) => {
|
|
610
|
+
if (confirmed) {
|
|
611
|
+
console.log("User submitted the form.");
|
|
612
|
+
} else {
|
|
613
|
+
console.log("User closed the modal without submitting.");
|
|
614
|
+
}
|
|
615
|
+
});
|
|
616
|
+
*/
|
|
617
|
+
|