@panphora/clayjs 0.1.0 → 0.2.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.
@@ -0,0 +1,884 @@
1
+ /*
2
+
3
+ theModal
4
+
5
+ // a pretty alternative to window.prompt
6
+
7
+ - set the content of the modal
8
+ - open it
9
+ - user confirms
10
+ - everything about the modal resets
11
+
12
+ themodal.html = content;
13
+ themodal.yes = content;
14
+ themodal.no = content;
15
+
16
+ themodal.disableFocus = true;
17
+ themodal.disableScroll = true;
18
+
19
+ themodal.onYes(content);
20
+ themodal.onNo(content);
21
+
22
+ themodal.open();
23
+ themodal.close();
24
+
25
+ */
26
+
27
+ /*
28
+
29
+ things you probably want to style
30
+
31
+ .micromodal {}
32
+ .micromodal .micromodal__container {}
33
+ .micromodal .micromodal__content {}
34
+ .micromodal .micromodal__heading {}
35
+ .micromodal .micromodal__input {}
36
+ .micromodal .micromodal__input:focus, .micromodal .micromodal__input:active {}
37
+ .micromodal .micromodal__buttons {}
38
+ .micromodal .micromodal__yes, .micromodal__no {}
39
+ .micromodal .micromodal__yes {}
40
+ .micromodal .micromodal__yes:focus, .micromodal__yes:hover {}
41
+ .micromodal .micromodal__no {}
42
+ .micromodal .micromodal__no:focus, .micromodal__no:hover {}
43
+ .micromodal .micromodal__close {}
44
+ .micromodal .micromodal__close:focus, .micromodal__close:hover {}
45
+
46
+ */
47
+
48
+
49
+
50
+
51
+
52
+
53
+ // MicroModal
54
+ // MIT License (c) 2017 Indrashish Ghosh
55
+ // MODIFIED: removed `this.activeElement.focus()` after modal is closed
56
+
57
+ const MicroModal = (() => {
58
+ 'use strict'
59
+
60
+ const FOCUSABLE_ELEMENTS = [
61
+ 'a[href]',
62
+ 'area[href]',
63
+ 'input:not([disabled]):not([type="hidden"]):not([aria-hidden])',
64
+ 'select:not([disabled]):not([aria-hidden])',
65
+ 'textarea:not([disabled]):not([aria-hidden])',
66
+ 'button:not([disabled]):not([aria-hidden])',
67
+ 'iframe',
68
+ 'object',
69
+ 'embed',
70
+ '[contenteditable]',
71
+ '[tabindex]:not([tabindex^="-"])'
72
+ ]
73
+
74
+ class Modal {
75
+ constructor ({
76
+ targetModal,
77
+ triggers = [],
78
+ onShow = () => { },
79
+ onClose = () => { },
80
+ openTrigger = 'data-micromodal-trigger',
81
+ closeTrigger = 'data-micromodal-close',
82
+ openClass = 'is-open',
83
+ disableScroll = false,
84
+ disableFocus = false,
85
+ awaitCloseAnimation = false,
86
+ awaitOpenAnimation = false,
87
+ debugMode = false
88
+ }) {
89
+ this.modal = document.getElementById(targetModal)
90
+
91
+ this.config = { debugMode, disableScroll, openTrigger, closeTrigger, openClass, onShow, onClose, awaitCloseAnimation, awaitOpenAnimation, disableFocus }
92
+
93
+ if (triggers.length > 0) this.registerTriggers(...triggers)
94
+
95
+ this.onClick = this.onClick.bind(this)
96
+ this.onKeydown = this.onKeydown.bind(this)
97
+ }
98
+
99
+ registerTriggers (...triggers) {
100
+ triggers.filter(Boolean).forEach(trigger => {
101
+ trigger.addEventListener('click', event => this.showModal(event))
102
+ })
103
+ }
104
+
105
+ showModal (event = null) {
106
+ this.activeElement = document.activeElement
107
+ this.modal.setAttribute('aria-hidden', 'false')
108
+ this.modal.classList.add(this.config.openClass)
109
+ this.scrollBehaviour('disable')
110
+ this.addEventListeners()
111
+
112
+ if (this.config.awaitOpenAnimation) {
113
+ const handler = () => {
114
+ this.modal.removeEventListener('animationend', handler, false)
115
+ this.setFocusToFirstNode()
116
+ }
117
+ this.modal.addEventListener('animationend', handler, false)
118
+ } else {
119
+ this.setFocusToFirstNode()
120
+ }
121
+
122
+ this.config.onShow(this.modal, this.activeElement, event)
123
+ }
124
+
125
+ closeModal (event = null) {
126
+ const modal = this.modal
127
+ this.modal.setAttribute('aria-hidden', 'true')
128
+ this.removeEventListeners()
129
+ this.scrollBehaviour('enable')
130
+ this.config.onClose(this.modal, this.activeElement, event)
131
+
132
+ if (this.config.awaitCloseAnimation) {
133
+ const openClass = this.config.openClass // <- old school ftw
134
+ this.modal.addEventListener('animationend', function handler () {
135
+ modal.classList.remove(openClass)
136
+ modal.removeEventListener('animationend', handler, false)
137
+ }, false)
138
+ } else {
139
+ modal.classList.remove(this.config.openClass)
140
+ }
141
+ }
142
+
143
+ closeModalById (targetModal) {
144
+ // Fall back to the stored modal reference when the node was removed from
145
+ // the DOM externally (e.g. a live-sync morph). Without this, getElementById
146
+ // returns null, closeModal() is skipped, and its cleanup never runs — the
147
+ // onClose callback, this.removeEventListeners() (which drops MicroModal's
148
+ // document keydown listener), and scroll restore are all stranded.
149
+ const modal = document.getElementById(targetModal)
150
+ if (modal) this.modal = modal
151
+ if (this.modal) this.closeModal()
152
+ }
153
+
154
+ scrollBehaviour (toggle) {
155
+ if (!this.config.disableScroll) return
156
+ const body = document.querySelector('body')
157
+ switch (toggle) {
158
+ case 'enable':
159
+ Object.assign(body.style, { overflow: '' })
160
+ break
161
+ case 'disable':
162
+ Object.assign(body.style, { overflow: 'hidden' })
163
+ break
164
+ default:
165
+ }
166
+ }
167
+
168
+ addEventListeners () {
169
+ this.modal.addEventListener('touchstart', this.onClick)
170
+ this.modal.addEventListener('click', this.onClick)
171
+ document.addEventListener('keydown', this.onKeydown)
172
+ }
173
+
174
+ removeEventListeners () {
175
+ if (this.modal) {
176
+ this.modal.removeEventListener('touchstart', this.onClick)
177
+ this.modal.removeEventListener('click', this.onClick)
178
+ }
179
+ document.removeEventListener('keydown', this.onKeydown)
180
+ }
181
+
182
+ onClick (event) {
183
+ if (
184
+ event.target.hasAttribute(this.config.closeTrigger) ||
185
+ event.target.parentNode.hasAttribute(this.config.closeTrigger)
186
+ ) {
187
+ event.preventDefault()
188
+ event.stopPropagation()
189
+ this.closeModal(event)
190
+ }
191
+ }
192
+
193
+ onKeydown (event) {
194
+ if (event.keyCode === 27) this.closeModal(event) // esc
195
+ if (event.keyCode === 9) this.retainFocus(event) // tab
196
+ }
197
+
198
+ getFocusableNodes () {
199
+ const nodes = this.modal.querySelectorAll(FOCUSABLE_ELEMENTS)
200
+ return Array(...nodes)
201
+ }
202
+
203
+ setFocusToFirstNode () {
204
+ if (this.config.disableFocus) return
205
+
206
+ const focusableNodes = this.getFocusableNodes()
207
+
208
+ if (focusableNodes.length === 0) return
209
+
210
+ const nodesWhichAreNotCloseTargets = focusableNodes.filter(node => {
211
+ return !node.hasAttribute(this.config.closeTrigger)
212
+ })
213
+
214
+ if (nodesWhichAreNotCloseTargets.length > 0) nodesWhichAreNotCloseTargets[0].focus()
215
+ if (nodesWhichAreNotCloseTargets.length === 0) focusableNodes[0].focus()
216
+ }
217
+
218
+ retainFocus (event) {
219
+ let focusableNodes = this.getFocusableNodes()
220
+
221
+ if (focusableNodes.length === 0) return
222
+
223
+ focusableNodes = focusableNodes.filter(node => {
224
+ return (node.offsetParent !== null)
225
+ })
226
+
227
+ if (!this.modal.contains(document.activeElement)) {
228
+ focusableNodes[0].focus()
229
+ } else {
230
+ const focusedItemIndex = focusableNodes.indexOf(document.activeElement)
231
+
232
+ if (event.shiftKey && focusedItemIndex === 0) {
233
+ focusableNodes[focusableNodes.length - 1].focus()
234
+ event.preventDefault()
235
+ }
236
+
237
+ if (!event.shiftKey && focusableNodes.length > 0 && focusedItemIndex === focusableNodes.length - 1) {
238
+ focusableNodes[0].focus()
239
+ event.preventDefault()
240
+ }
241
+ }
242
+ }
243
+ }
244
+
245
+
246
+ // Keep a reference to the opened modal
247
+ let activeModal = null
248
+
249
+ const generateTriggerMap = (triggers, triggerAttr) => {
250
+ const triggerMap = []
251
+
252
+ triggers.forEach(trigger => {
253
+ const targetModal = trigger.attributes[triggerAttr].value
254
+ if (triggerMap[targetModal] === undefined) triggerMap[targetModal] = []
255
+ triggerMap[targetModal].push(trigger)
256
+ })
257
+
258
+ return triggerMap
259
+ }
260
+
261
+ const validateModalPresence = id => {
262
+ if (!document.getElementById(id)) {
263
+ console.warn(`MicroModal: \u2757Seems like you have missed %c'${ id }'`, 'background-color: #f8f9fa;color: #50596c;font-weight: bold;', 'ID somewhere in your code. Refer example below to resolve it.')
264
+ console.warn('%cExample:', 'background-color: #f8f9fa;color: #50596c;font-weight: bold;', `<div class="modal" id="${ id }"></div>`)
265
+ return false
266
+ }
267
+ }
268
+
269
+ const validateTriggerPresence = triggers => {
270
+ if (triggers.length <= 0) {
271
+ console.warn('MicroModal: \u2757Please specify at least one %c\'micromodal-trigger\'', 'background-color: #f8f9fa;color: #50596c;font-weight: bold;', 'data attribute.')
272
+ console.warn('%cExample:', 'background-color: #f8f9fa;color: #50596c;font-weight: bold;', '<a href="#" data-micromodal-trigger="my-modal"></a>')
273
+ return false
274
+ }
275
+ }
276
+
277
+ const validateArgs = (triggers, triggerMap) => {
278
+ validateTriggerPresence(triggers)
279
+ if (!triggerMap) return true
280
+ for (const id in triggerMap) validateModalPresence(id)
281
+ return true
282
+ }
283
+
284
+ const init = config => {
285
+ const options = Object.assign({}, { openTrigger: 'data-micromodal-trigger' }, config)
286
+
287
+ const triggers = [...document.querySelectorAll(`[${ options.openTrigger }]`)]
288
+
289
+ const triggerMap = generateTriggerMap(triggers, options.openTrigger)
290
+
291
+ if (options.debugMode === true && validateArgs(triggers, triggerMap) === false) return
292
+
293
+ for (const key in triggerMap) {
294
+ const value = triggerMap[key]
295
+ options.targetModal = key
296
+ options.triggers = [...value]
297
+ activeModal = new Modal(options) // eslint-disable-line no-new
298
+ }
299
+ }
300
+
301
+ const show = (targetModal, config) => {
302
+ const options = config || {}
303
+ options.targetModal = targetModal
304
+
305
+ if (options.debugMode === true && validateModalPresence(targetModal) === false) return
306
+
307
+ if (activeModal) activeModal.removeEventListeners()
308
+
309
+ activeModal = new Modal(options) // eslint-disable-line no-new
310
+ activeModal.showModal()
311
+ }
312
+
313
+ const close = targetModal => {
314
+ if (!activeModal) return
315
+ targetModal ? activeModal.closeModalById(targetModal) : activeModal.closeModal()
316
+ activeModal = null
317
+ }
318
+
319
+ return { init, show, close }
320
+ })()
321
+
322
+
323
+
324
+ // themodal.js
325
+ // MIT License (c) 2023 David Miranda
326
+
327
+ const modalCss = `<style class="micromodal-css">
328
+ .micromodal {
329
+ display: none;
330
+ color: #fff;
331
+ font-family: var(--hyperclay-modal-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace);
332
+ font-size: var(--hyperclay-modal-font-size, 18px);
333
+ }
334
+
335
+ .micromodal button:not(.custom-button) {
336
+ background: none;
337
+ color: inherit;
338
+ border: none;
339
+ padding: 0;
340
+ margin: 0;
341
+ width: auto;
342
+ overflow: visible;
343
+ font: inherit;
344
+ line-height: inherit;
345
+ text-transform: none;
346
+ text-align: center;
347
+ text-decoration: none;
348
+ cursor: pointer;
349
+ -webkit-appearance: none;
350
+ -moz-appearance: none;
351
+ appearance: none;
352
+ outline: 0;
353
+ }
354
+
355
+ .micromodal .micromodal__hide {
356
+ display: none;
357
+ }
358
+
359
+ .micromodal.is-open {
360
+ display: block;
361
+ }
362
+
363
+ .micromodal__overlay {
364
+ position: fixed;
365
+ top: 0;
366
+ left: 0;
367
+ right: 0;
368
+ bottom: 0;
369
+ display: flex;
370
+ justify-content: center;
371
+ align-items: center;
372
+ background: rgba(0,0,0,.65);
373
+ }
374
+
375
+ .micromodal__container {
376
+ position: relative;
377
+ width: 100%;
378
+ min-width: 0;
379
+ max-width: min(550px, calc(100vw - 2rem));
380
+ max-height: calc(100vh - 4rem);
381
+ max-height: calc(100dvh - 2rem);
382
+ box-sizing: border-box;
383
+ overflow: hidden;
384
+ border: 2px solid #FFFFFF;
385
+ background-color: #11131E;
386
+ }
387
+
388
+ .micromodal__inner {
389
+ overflow-x: hidden;
390
+ overflow-y: auto;
391
+ /* Must subtract container's 4px border (2px top + 2px bottom) from max-height,
392
+ otherwise inner overflows past container since box-sizing: border-box
393
+ makes the container's content area smaller than its max-height */
394
+ max-height: calc(100dvh - 2rem - 4px);
395
+ padding: 26px 40px 40px 40px;
396
+ }
397
+
398
+ @media (min-width: 640px) {
399
+ .micromodal .micromodal__inner {
400
+ padding: 52px 64px 60px 64px;
401
+ }
402
+ }
403
+
404
+ .micromodal[aria-hidden="false"] .micromodal__overlay {
405
+ animation: microModalFadeIn .2s cubic-bezier(0.0, 0.0, 0.2, 1);
406
+ }
407
+
408
+ .micromodal[aria-hidden="false"] .micromodal__container {
409
+ animation: microModalSlideIn .2s cubic-bezier(0, 0, .2, 1);
410
+ }
411
+
412
+ .micromodal .micromodal__container,
413
+ .micromodal .micromodal__overlay {
414
+ will-change: transform;
415
+ }
416
+
417
+ @keyframes microModalFadeIn {
418
+ from { opacity: 0; }
419
+ to { opacity: 1; }
420
+ }
421
+
422
+ @keyframes microModalSlideIn {
423
+ from { transform: translateY(15%); }
424
+ to { transform: translateY(0); }
425
+ }
426
+
427
+ .micromodal .micromodal__content {
428
+ margin-bottom: 14px;
429
+ overflow-wrap: anywhere;
430
+ }
431
+
432
+ .micromodal .micromodal__heading {
433
+ display: flex;
434
+ flex-direction: column;
435
+ gap: 2px;
436
+ margin-bottom: 8px;
437
+ overflow-wrap: anywhere;
438
+ }
439
+
440
+ .micromodal .micromodal__input {
441
+ width: min(calc(100vw - 100px), 420px);
442
+ padding: 6px 6px 7px;
443
+ font-size: var(--hyperclay-modal-input-font-size, 16px);
444
+ color: #000;
445
+ background: #fff;
446
+ }
447
+
448
+ .micromodal .micromodal__input:focus, .micromodal .micromodal__input:active {
449
+ outline: 3px solid #6A73B6;
450
+ }
451
+
452
+ .micromodal button.micromodal__yes {
453
+ display: flex;
454
+ justify-content: center;
455
+ align-items: center;
456
+ width: 100%;
457
+ height: 39px;
458
+ line-height: 0;
459
+ border: 3px solid;
460
+ border-top-color: #94BA6F;
461
+ border-left-color: #94BA6F;
462
+ border-bottom-color: #1A3004;
463
+ border-right-color: #1A3004;
464
+ background-color: #49870B;
465
+ }
466
+
467
+ .micromodal button.micromodal__yes:focus,
468
+ .micromodal button.micromodal__yes:hover {
469
+ background-color: #549B0D;
470
+ }
471
+
472
+ .micromodal button.micromodal__yes:active {
473
+ border-top-color: #1A3004;
474
+ border-left-color: #1A3004;
475
+ border-bottom-color: #94BA6F;
476
+ border-right-color: #94BA6F;
477
+ }
478
+
479
+ .micromodal button.micromodal__secondary-btn {
480
+ display: flex;
481
+ justify-content: center;
482
+ align-items: center;
483
+ width: 100%;
484
+ height: 39px;
485
+ line-height: 0;
486
+ border: 3px solid;
487
+ border-top-color: #474C65;
488
+ border-left-color: #474C65;
489
+ border-bottom-color: #131725;
490
+ border-right-color: #131725;
491
+ background-color: #1D1F2F;
492
+ color: #E5E7EB;
493
+ font-family: inherit;
494
+ font-size: inherit;
495
+ font-weight: bold;
496
+ }
497
+
498
+ .micromodal button.micromodal__secondary-btn:focus,
499
+ .micromodal button.micromodal__secondary-btn:hover {
500
+ background-color: #232639;
501
+ }
502
+
503
+ .micromodal button.micromodal__secondary-btn:active {
504
+ border-top-color: #131725;
505
+ border-left-color: #131725;
506
+ border-bottom-color: #474C65;
507
+ border-right-color: #474C65;
508
+ }
509
+
510
+ .micromodal:has(.snippet-code-block) .micromodal__content {
511
+ margin-bottom: 0;
512
+ }
513
+
514
+ .micromodal .snippet-code-block {
515
+ background-color: #292E54;
516
+ padding: 1rem;
517
+ margin-bottom: 14px;
518
+ max-width: 420px;
519
+ overflow-x: auto;
520
+ }
521
+
522
+ .micromodal .snippet-code-block pre {
523
+ color: white;
524
+ font-family: var(--hyperclay-modal-code-font-family, var(--hyperclay-modal-font-family, monospace));
525
+ font-size: 0.875rem;
526
+ white-space: nowrap;
527
+ margin: 0;
528
+ }
529
+
530
+ .micromodal .snippet-warning {
531
+ padding: 0.75rem;
532
+ border: 2px solid #989742;
533
+ background-color: #1E1E11;
534
+ font-size: 0.875rem;
535
+ color: #FBF7B7;
536
+ max-width: 420px;
537
+ }
538
+
539
+ .micromodal .snippet-link-box {
540
+ font-size: 16px;
541
+ margin-top: 0.75rem;
542
+ text-align: right;
543
+ }
544
+
545
+ .micromodal .snippet-link-box a {
546
+ color: #F6F7F9;
547
+ text-decoration: none;
548
+ display: inline-flex;
549
+ align-items: center;
550
+ gap: 0.3rem;
551
+ padding: 0.35rem 0.45rem;
552
+ border: 1px solid #474C64;
553
+ line-height: 1;
554
+ }
555
+
556
+ .micromodal .snippet-link-box a:hover {
557
+ color: #E5E7EB;
558
+ border-color: #75798B;
559
+ }
560
+
561
+ .micromodal .snippet-link-box a svg {
562
+ width: 0.85rem;
563
+ height: 0.85rem;
564
+ flex-shrink: 0;
565
+ }
566
+
567
+ .micromodal button.micromodal__close {
568
+ clip-path: polygon(0% 4%, 0% 0%, 100% 0%, 100% 100%, 94% 100%);
569
+ position: absolute;
570
+ top: -1px;
571
+ right: -1px;
572
+ width: 68px;
573
+ }
574
+
575
+ .micromodal .micromodal__close-bg {
576
+ fill: #1D2032;
577
+ }
578
+
579
+ .micromodal .micromodal__close:hover .micromodal__close-bg {
580
+ fill: #212543;
581
+ }
582
+
583
+ .micromodal .micromodal__close-x {
584
+ fill: #fff;
585
+ }
586
+
587
+ .micromodal .micromodal__tell {
588
+ max-width: 440px;
589
+ margin-bottom: 28px;
590
+ }
591
+
592
+ .micromodal .micromodal__tell > * + * {
593
+ margin-top: 20px;
594
+ }
595
+
596
+ .micromodal .micromodal__tell-title {
597
+ font-size: var(--hyperclay-modal-title-font-size, 20px);
598
+ font-weight: bold;
599
+ }
600
+
601
+ .micromodal .micromodal__tell-content {
602
+ font-size: var(--hyperclay-modal-font-size, 16px);
603
+ font-weight: normal;
604
+ }
605
+
606
+ @media (min-width: 640px) {
607
+ .micromodal .micromodal__tell-title {
608
+ font-size: var(--hyperclay-modal-title-font-size, 22px);
609
+ }
610
+ .micromodal .micromodal__tell-content {
611
+ font-size: var(--hyperclay-modal-font-size, 18px);
612
+ }
613
+ }
614
+ </style>`;
615
+
616
+ const modalHtml = `<div class="micromodal" id="micromodal" aria-hidden="true">
617
+ <div class="micromodal__overlay" tabindex="-1">
618
+ <form class="micromodal__container" role="dialog" aria-modal="true">
619
+ <div class="micromodal__inner">
620
+ <div class="micromodal__content"></div>
621
+ <div class="micromodal__buttons">
622
+ <button class="micromodal__no" type="button"></button>
623
+ <button class="micromodal__yes" type="submit"></button>
624
+ </div>
625
+ </div>
626
+ <button class="micromodal__close" type="button" aria-label="Close modal"></button>
627
+ </form>
628
+ </div>
629
+ </div>`;
630
+
631
+ const themodal = (() => {
632
+ let html = "";
633
+ let yes = "";
634
+ let no = "";
635
+ let zIndex = "100";
636
+ let closeHtml = "";
637
+
638
+ let enableClickOutsideCloses = true;
639
+ let disableScroll = true;
640
+ let disableFocus = false;
641
+
642
+ let onYes = [];
643
+ let onNo = [];
644
+ let onOpen = [];
645
+
646
+ const themodalMain = {
647
+ isShowing: false,
648
+ open() {
649
+ // This modal's callbacks were registered (onYes pushed / onNo assigned)
650
+ // immediately before open(). Capture them for THIS open() call, then clear
651
+ // the shared arrays so the next modal starts clean. Every handler below
652
+ // fires ONLY these captured callbacks, so stacked modals can never drain
653
+ // each other's promises.
654
+ const myOnYes = onYes;
655
+ const myOnNo = onNo;
656
+ const myOnOpen = onOpen;
657
+ onYes = [];
658
+ onNo = [];
659
+ onOpen = [];
660
+
661
+ // The prompt promise must settle exactly once: resolve on yes, reject on
662
+ // any dismissal (no / close / backdrop / Esc / superseded). dismiss() runs
663
+ // the no-callbacks at most once; the yes path sets `settled` to block it.
664
+ let settled = false;
665
+ const dismiss = () => {
666
+ if (settled) return;
667
+ settled = true;
668
+ myOnNo.forEach(cb => cb());
669
+ };
670
+
671
+ // Singleton: only one modal on screen at a time. If one is still up (or
672
+ // left stale state behind, e.g. a live-sync morph removed its DOM), reject
673
+ // its promise and tear it down before opening the new one.
674
+ if (this.isShowing || document.querySelector('.micromodal-parent')) {
675
+ this._dismiss?.();
676
+ this._cleanupListeners?.();
677
+ document.querySelectorAll('.micromodal-parent').forEach(n => n.remove());
678
+ this.isShowing = false;
679
+ document.body.style.overflow = '';
680
+ }
681
+
682
+ // Expose this modal's dismiss so a later open()/close can settle it.
683
+ this._dismiss = dismiss;
684
+
685
+ document.body.insertAdjacentHTML("afterbegin", "<div clay='no-save no-snapshot' class='micromodal-parent'>" + modalCss + modalHtml + "</div>");
686
+
687
+ const modalOverlayElem = document.querySelector(".micromodal__overlay");
688
+ const modalContentElem = document.querySelector(".micromodal__content");
689
+ const modalButtonsElem = document.querySelector(".micromodal__buttons");
690
+ const modalYesElem = document.querySelector(".micromodal__yes");
691
+ const modalNoElem = document.querySelector(".micromodal__no");
692
+ const modalCloseElem = document.querySelector(".micromodal__close");
693
+
694
+ modalContentElem.innerHTML = html;
695
+ modalYesElem.innerHTML = yes;
696
+ modalNoElem.innerHTML = no;
697
+ modalOverlayElem.style.zIndex = zIndex;
698
+ modalCloseElem.innerHTML = closeHtml;
699
+
700
+ // MODIFIED so modal doesn't close if mousedown happened inside the modal
701
+ let mousedownOnBackdrop = false;
702
+
703
+ // MODIFIED so modal doesn't close if mousedown happened inside the modal
704
+ function handleMousedown(event) {
705
+ // Check if mousedown started on backdrop (overlay but not container)
706
+ mousedownOnBackdrop = event.target.closest(".micromodal__overlay") &&
707
+ !event.target.closest(".micromodal__container");
708
+ }
709
+
710
+ function handleClick(event) {
711
+ // Just close on no / close-button / backdrop; onClose runs dismiss().
712
+ if (event.target.closest(".micromodal__no") || event.target.closest(".micromodal__close")) {
713
+ MicroModal.close("micromodal");
714
+ // MODIFIED so modal doesn't close if mousedown happened inside the modal
715
+ } else if (enableClickOutsideCloses && mousedownOnBackdrop && !event.target.closest(".micromodal__container") && event.target.closest(".micromodal__overlay")) {
716
+ MicroModal.close("micromodal");
717
+ }
718
+
719
+ // Reset after handling
720
+ mousedownOnBackdrop = false;
721
+ }
722
+
723
+ function handleSubmit(event) {
724
+ if (event.target.closest("#micromodal")) {
725
+ event.preventDefault();
726
+
727
+ // Execute callbacks and check if any return false or throw errors
728
+ let shouldClose = true;
729
+
730
+ for (const cb of myOnYes) {
731
+ try {
732
+ const result = cb();
733
+ // If callback explicitly returns false, don't close
734
+ if (result === false) {
735
+ shouldClose = false;
736
+ break;
737
+ }
738
+ } catch (error) {
739
+ // If callback throws an error, don't close
740
+ shouldClose = false;
741
+ // Could optionally bubble the error up or handle it here
742
+ break;
743
+ }
744
+ }
745
+
746
+ // Only close if all callbacks succeeded. The yes callbacks already
747
+ // scheduled their resolve, so mark settled to stop onClose rejecting.
748
+ if (shouldClose) {
749
+ settled = true;
750
+ MicroModal.close("micromodal");
751
+ }
752
+ }
753
+ }
754
+
755
+ // MODIFIED so modal doesn't close if mousedown happened inside the modal
756
+ document.addEventListener("mousedown", handleMousedown);
757
+ document.addEventListener("click", handleClick);
758
+ document.addEventListener("submit", handleSubmit);
759
+
760
+ // Store cleanup so stale listeners can be removed if DOM is yanked externally
761
+ this._cleanupListeners = () => {
762
+ document.removeEventListener("mousedown", handleMousedown);
763
+ document.removeEventListener("click", handleClick);
764
+ document.removeEventListener("submit", handleSubmit);
765
+ };
766
+
767
+ function setButtonsVisibility () {
768
+ modalButtonsElem.classList.toggle("micromodal__hide", !yes && !no);
769
+ modalYesElem.classList.toggle("micromodal__hide", !yes);
770
+ modalNoElem.classList.toggle("micromodal__hide", !no);
771
+ }
772
+
773
+ setButtonsVisibility();
774
+
775
+ MicroModal.show("micromodal", {
776
+ disableScroll,
777
+ disableFocus: true, // we use our own
778
+ // reset everything on close
779
+ onClose: modal => {
780
+ // Settle the promise as rejected if it closed without a yes
781
+ // (no / close / backdrop / Esc). No-op once already resolved.
782
+ dismiss();
783
+
784
+ document.querySelector(".micromodal-parent")?.remove();
785
+
786
+ html = "";
787
+ yes = "";
788
+ no = "";
789
+ zIndex = "100";
790
+ closeHtml = "";
791
+
792
+ // reset to defaults
793
+ enableClickOutsideCloses = true;
794
+ disableScroll = true;
795
+ disableFocus = false;
796
+
797
+ // onYes/onNo/onOpen are owned by open() now (captured into this call's
798
+ // locals and cleared there), so they are deliberately not reset here:
799
+ // a late onClose must not wipe a newer modal's freshly-registered cbs.
800
+
801
+ this.isShowing = false;
802
+
803
+ // MODIFIED so modal doesn't close if mousedown happened inside the modal
804
+ document.removeEventListener("mousedown", handleMousedown);
805
+ document.removeEventListener("click", handleClick);
806
+ document.removeEventListener("submit", handleSubmit);
807
+ this._cleanupListeners = null;
808
+ this._dismiss = null;
809
+ }
810
+ });
811
+
812
+ this.isShowing = true;
813
+
814
+ myOnOpen.forEach(cb => cb());
815
+
816
+ if (!disableFocus) {
817
+ let firstInput = modalOverlayElem.querySelector(".micromodal__content :is(input,textarea,button):not(.micromodal__hide), .micromodal__buttons :is(input,textarea,button):not(.micromodal__hide)");
818
+ firstInput?.focus();
819
+ firstInput?.setSelectionRange?.(-1, -1);
820
+ }
821
+ },
822
+ close() {
823
+ // onClose runs the dismiss/reject path; just trigger the close.
824
+ MicroModal.close("micromodal");
825
+ },
826
+ get html() {
827
+ return html;
828
+ },
829
+ set html(newVal) {
830
+ html = newVal;
831
+ },
832
+ get closeHtml() {
833
+ return closeHtml;
834
+ },
835
+ set closeHtml(newVal) {
836
+ closeHtml = newVal;
837
+ },
838
+ get yes() {
839
+ return yes;
840
+ },
841
+ set yes(newVal) {
842
+ yes = newVal;
843
+ },
844
+ get no() {
845
+ return no;
846
+ },
847
+ set no(newVal) {
848
+ no = newVal;
849
+ },
850
+ get zIndex() {
851
+ return zIndex;
852
+ },
853
+ set zIndex(newVal) {
854
+ zIndex = newVal;
855
+ },
856
+ get disableFocus() {
857
+ return disableFocus;
858
+ },
859
+ set disableFocus(newVal) {
860
+ disableFocus = newVal;
861
+ },
862
+ get disableScroll() {
863
+ return disableScroll;
864
+ },
865
+ set disableScroll(newVal) {
866
+ disableScroll = newVal;
867
+ },
868
+
869
+ // Two registration forms are in use across callers: themodal.onYes(cb)
870
+ // (call → push, supports multiple) and themodal.onNo = cb (assign → replace).
871
+ // Expose both via get (returns a push fn) + set (replaces with one cb), so
872
+ // the assign form registers a callback instead of clobbering the method.
873
+ get onYes() { return (cb) => { onYes.push(cb); }; },
874
+ set onYes(cb) { onYes = [cb]; },
875
+ get onNo() { return (cb) => { onNo.push(cb); }; },
876
+ set onNo(cb) { onNo = [cb]; },
877
+ get onOpen() { return (cb) => { onOpen.push(cb); }; },
878
+ set onOpen(cb) { onOpen = [cb]; },
879
+ };
880
+
881
+ return themodalMain;
882
+ })();
883
+
884
+ export default themodal;