hyperclayjs 1.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/LICENSE +21 -0
- package/README.md +360 -0
- package/README.template.md +276 -0
- package/communication/behaviorCollector.js +230 -0
- package/communication/sendMessage.js +48 -0
- package/communication/uploadFile.js +348 -0
- package/core/adminContenteditable.js +36 -0
- package/core/adminInputs.js +58 -0
- package/core/adminOnClick.js +31 -0
- package/core/adminResources.js +33 -0
- package/core/adminSystem.js +15 -0
- package/core/editmode.js +8 -0
- package/core/editmodeSystem.js +18 -0
- package/core/enablePersistentFormInputValues.js +62 -0
- package/core/isAdminOfCurrentResource.js +13 -0
- package/core/optionVisibilityRuleGenerator.js +160 -0
- package/core/savePage.js +196 -0
- package/core/savePageCore.js +236 -0
- package/core/setPageTypeOnDocumentElement.js +23 -0
- package/custom-attributes/ajaxElements.js +94 -0
- package/custom-attributes/autosize.js +17 -0
- package/custom-attributes/domHelpers.js +175 -0
- package/custom-attributes/events.js +15 -0
- package/custom-attributes/inputHelpers.js +11 -0
- package/custom-attributes/onclickaway.js +27 -0
- package/custom-attributes/onclone.js +35 -0
- package/custom-attributes/onpagemutation.js +20 -0
- package/custom-attributes/onrender.js +30 -0
- package/custom-attributes/preventEnter.js +13 -0
- package/custom-attributes/sortable.js +76 -0
- package/dom-utilities/All.js +412 -0
- package/dom-utilities/getDataFromForm.js +60 -0
- package/dom-utilities/insertStyleTag.js +28 -0
- package/dom-utilities/onDomReady.js +7 -0
- package/dom-utilities/onLoad.js +7 -0
- package/hyperclay.js +465 -0
- package/module-dependency-graph.json +612 -0
- package/package.json +95 -0
- package/string-utilities/copy-to-clipboard.js +35 -0
- package/string-utilities/emmet-html.js +54 -0
- package/string-utilities/query.js +1 -0
- package/string-utilities/slugify.js +21 -0
- package/ui/info.js +39 -0
- package/ui/prompts.js +179 -0
- package/ui/theModal.js +677 -0
- package/ui/toast.js +273 -0
- package/utilities/cookie.js +45 -0
- package/utilities/debounce.js +12 -0
- package/utilities/mutation.js +403 -0
- package/utilities/nearest.js +97 -0
- package/utilities/pipe.js +1 -0
- package/utilities/throttle.js +21 -0
- package/vendor/Sortable.js +3351 -0
- package/vendor/idiomorph.min.js +8 -0
- package/vendor/tailwind-base.css +1471 -0
- package/vendor/tailwind-play.js +169 -0
package/ui/theModal.js
ADDED
|
@@ -0,0 +1,677 @@
|
|
|
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
|
+
this.modal = document.getElementById(targetModal)
|
|
145
|
+
if (this.modal) this.closeModal()
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
scrollBehaviour (toggle) {
|
|
149
|
+
if (!this.config.disableScroll) return
|
|
150
|
+
const body = document.querySelector('body')
|
|
151
|
+
switch (toggle) {
|
|
152
|
+
case 'enable':
|
|
153
|
+
Object.assign(body.style, { overflow: '' })
|
|
154
|
+
break
|
|
155
|
+
case 'disable':
|
|
156
|
+
Object.assign(body.style, { overflow: 'hidden' })
|
|
157
|
+
break
|
|
158
|
+
default:
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
addEventListeners () {
|
|
163
|
+
this.modal.addEventListener('touchstart', this.onClick)
|
|
164
|
+
this.modal.addEventListener('click', this.onClick)
|
|
165
|
+
document.addEventListener('keydown', this.onKeydown)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
removeEventListeners () {
|
|
169
|
+
this.modal.removeEventListener('touchstart', this.onClick)
|
|
170
|
+
this.modal.removeEventListener('click', this.onClick)
|
|
171
|
+
document.removeEventListener('keydown', this.onKeydown)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
onClick (event) {
|
|
175
|
+
if (
|
|
176
|
+
event.target.hasAttribute(this.config.closeTrigger) ||
|
|
177
|
+
event.target.parentNode.hasAttribute(this.config.closeTrigger)
|
|
178
|
+
) {
|
|
179
|
+
event.preventDefault()
|
|
180
|
+
event.stopPropagation()
|
|
181
|
+
this.closeModal(event)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
onKeydown (event) {
|
|
186
|
+
if (event.keyCode === 27) this.closeModal(event) // esc
|
|
187
|
+
if (event.keyCode === 9) this.retainFocus(event) // tab
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
getFocusableNodes () {
|
|
191
|
+
const nodes = this.modal.querySelectorAll(FOCUSABLE_ELEMENTS)
|
|
192
|
+
return Array(...nodes)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
setFocusToFirstNode () {
|
|
196
|
+
if (this.config.disableFocus) return
|
|
197
|
+
|
|
198
|
+
const focusableNodes = this.getFocusableNodes()
|
|
199
|
+
|
|
200
|
+
if (focusableNodes.length === 0) return
|
|
201
|
+
|
|
202
|
+
const nodesWhichAreNotCloseTargets = focusableNodes.filter(node => {
|
|
203
|
+
return !node.hasAttribute(this.config.closeTrigger)
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
if (nodesWhichAreNotCloseTargets.length > 0) nodesWhichAreNotCloseTargets[0].focus()
|
|
207
|
+
if (nodesWhichAreNotCloseTargets.length === 0) focusableNodes[0].focus()
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
retainFocus (event) {
|
|
211
|
+
let focusableNodes = this.getFocusableNodes()
|
|
212
|
+
|
|
213
|
+
if (focusableNodes.length === 0) return
|
|
214
|
+
|
|
215
|
+
focusableNodes = focusableNodes.filter(node => {
|
|
216
|
+
return (node.offsetParent !== null)
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
if (!this.modal.contains(document.activeElement)) {
|
|
220
|
+
focusableNodes[0].focus()
|
|
221
|
+
} else {
|
|
222
|
+
const focusedItemIndex = focusableNodes.indexOf(document.activeElement)
|
|
223
|
+
|
|
224
|
+
if (event.shiftKey && focusedItemIndex === 0) {
|
|
225
|
+
focusableNodes[focusableNodes.length - 1].focus()
|
|
226
|
+
event.preventDefault()
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (!event.shiftKey && focusableNodes.length > 0 && focusedItemIndex === focusableNodes.length - 1) {
|
|
230
|
+
focusableNodes[0].focus()
|
|
231
|
+
event.preventDefault()
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
// Keep a reference to the opened modal
|
|
239
|
+
let activeModal = null
|
|
240
|
+
|
|
241
|
+
const generateTriggerMap = (triggers, triggerAttr) => {
|
|
242
|
+
const triggerMap = []
|
|
243
|
+
|
|
244
|
+
triggers.forEach(trigger => {
|
|
245
|
+
const targetModal = trigger.attributes[triggerAttr].value
|
|
246
|
+
if (triggerMap[targetModal] === undefined) triggerMap[targetModal] = []
|
|
247
|
+
triggerMap[targetModal].push(trigger)
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
return triggerMap
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const validateModalPresence = id => {
|
|
254
|
+
if (!document.getElementById(id)) {
|
|
255
|
+
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.')
|
|
256
|
+
console.warn('%cExample:', 'background-color: #f8f9fa;color: #50596c;font-weight: bold;', `<div class="modal" id="${ id }"></div>`)
|
|
257
|
+
return false
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const validateTriggerPresence = triggers => {
|
|
262
|
+
if (triggers.length <= 0) {
|
|
263
|
+
console.warn('MicroModal: \u2757Please specify at least one %c\'micromodal-trigger\'', 'background-color: #f8f9fa;color: #50596c;font-weight: bold;', 'data attribute.')
|
|
264
|
+
console.warn('%cExample:', 'background-color: #f8f9fa;color: #50596c;font-weight: bold;', '<a href="#" data-micromodal-trigger="my-modal"></a>')
|
|
265
|
+
return false
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const validateArgs = (triggers, triggerMap) => {
|
|
270
|
+
validateTriggerPresence(triggers)
|
|
271
|
+
if (!triggerMap) return true
|
|
272
|
+
for (const id in triggerMap) validateModalPresence(id)
|
|
273
|
+
return true
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const init = config => {
|
|
277
|
+
const options = Object.assign({}, { openTrigger: 'data-micromodal-trigger' }, config)
|
|
278
|
+
|
|
279
|
+
const triggers = [...document.querySelectorAll(`[${ options.openTrigger }]`)]
|
|
280
|
+
|
|
281
|
+
const triggerMap = generateTriggerMap(triggers, options.openTrigger)
|
|
282
|
+
|
|
283
|
+
if (options.debugMode === true && validateArgs(triggers, triggerMap) === false) return
|
|
284
|
+
|
|
285
|
+
for (const key in triggerMap) {
|
|
286
|
+
const value = triggerMap[key]
|
|
287
|
+
options.targetModal = key
|
|
288
|
+
options.triggers = [...value]
|
|
289
|
+
activeModal = new Modal(options) // eslint-disable-line no-new
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const show = (targetModal, config) => {
|
|
294
|
+
const options = config || {}
|
|
295
|
+
options.targetModal = targetModal
|
|
296
|
+
|
|
297
|
+
if (options.debugMode === true && validateModalPresence(targetModal) === false) return
|
|
298
|
+
|
|
299
|
+
if (activeModal) activeModal.removeEventListeners()
|
|
300
|
+
|
|
301
|
+
activeModal = new Modal(options) // eslint-disable-line no-new
|
|
302
|
+
activeModal.showModal()
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const close = targetModal => {
|
|
306
|
+
targetModal ? activeModal.closeModalById(targetModal) : activeModal.closeModal()
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return { init, show, close }
|
|
310
|
+
})()
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
// themodal.js
|
|
315
|
+
// MIT License (c) 2023 David Miranda
|
|
316
|
+
|
|
317
|
+
const modalCss = `<style class="micromodal-css">
|
|
318
|
+
.micromodal {
|
|
319
|
+
display: none;
|
|
320
|
+
color: #fff;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
.micromodal button:not(.custom-button) {
|
|
324
|
+
background: none;
|
|
325
|
+
color: inherit;
|
|
326
|
+
border: none;
|
|
327
|
+
padding: 0;
|
|
328
|
+
margin: 0;
|
|
329
|
+
width: auto;
|
|
330
|
+
overflow: visible;
|
|
331
|
+
font: inherit;
|
|
332
|
+
line-height: inherit;
|
|
333
|
+
text-transform: none;
|
|
334
|
+
text-align: center;
|
|
335
|
+
text-decoration: none;
|
|
336
|
+
cursor: pointer;
|
|
337
|
+
-webkit-appearance: none;
|
|
338
|
+
-moz-appearance: none;
|
|
339
|
+
appearance: none;
|
|
340
|
+
outline: 0;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
.micromodal .micromodal__hide {
|
|
344
|
+
display: none;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
.micromodal.is-open {
|
|
348
|
+
display: block;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
.micromodal__overlay {
|
|
352
|
+
position: fixed;
|
|
353
|
+
top: 0;
|
|
354
|
+
left: 0;
|
|
355
|
+
right: 0;
|
|
356
|
+
bottom: 0;
|
|
357
|
+
display: flex;
|
|
358
|
+
justify-content: center;
|
|
359
|
+
align-items: center;
|
|
360
|
+
background: rgba(0,0,0,.65);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
.micromodal__container {
|
|
364
|
+
position: relative;
|
|
365
|
+
box-sizing: border-box;
|
|
366
|
+
overflow-y: auto;
|
|
367
|
+
padding: 26px 40px 40px 40px;
|
|
368
|
+
border: 2px solid #FFFFFF;
|
|
369
|
+
background-color: #11131E;
|
|
370
|
+
overflow: visible;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
@media (min-width: 640px) {
|
|
374
|
+
.micromodal .micromodal__container {
|
|
375
|
+
padding: 52px 64px 60px 64px;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
.micromodal[aria-hidden="false"] .micromodal__overlay {
|
|
380
|
+
animation: microModalFadeIn .2s cubic-bezier(0.0, 0.0, 0.2, 1);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
.micromodal[aria-hidden="false"] .micromodal__container {
|
|
384
|
+
animation: microModalSlideIn .2s cubic-bezier(0, 0, .2, 1);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
.micromodal .micromodal__container,
|
|
388
|
+
.micromodal .micromodal__overlay {
|
|
389
|
+
will-change: transform;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
@keyframes microModalFadeIn {
|
|
393
|
+
from { opacity: 0; }
|
|
394
|
+
to { opacity: 1; }
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
@keyframes microModalSlideIn {
|
|
398
|
+
from { transform: translateY(15%); }
|
|
399
|
+
to { transform: translateY(0); }
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
.micromodal .micromodal__content {
|
|
403
|
+
margin-bottom: 14px;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
.micromodal .micromodal__heading {
|
|
407
|
+
display: flex;
|
|
408
|
+
flex-direction: column;
|
|
409
|
+
gap: 2px;
|
|
410
|
+
margin-bottom: 8px;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
.micromodal .micromodal__input {
|
|
414
|
+
width: clamp(300px, calc(100vw - 100px), 420px);
|
|
415
|
+
padding: 6px 6px 7px;
|
|
416
|
+
font-size: 18px;
|
|
417
|
+
color: #000;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
.micromodal .micromodal__input:focus, .micromodal .micromodal__input:active {
|
|
421
|
+
outline: 3px solid #6A73B6;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
.micromodal button.micromodal__yes {
|
|
425
|
+
display: flex;
|
|
426
|
+
justify-content: center;
|
|
427
|
+
align-items: center;
|
|
428
|
+
width: 100%;
|
|
429
|
+
height: 39px;
|
|
430
|
+
border: 3px solid;
|
|
431
|
+
border-top-color: #94BA6F;
|
|
432
|
+
border-left-color: #94BA6F;
|
|
433
|
+
border-bottom-color: #1A3004;
|
|
434
|
+
border-right-color: #1A3004;
|
|
435
|
+
background-color: #49870B;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
.micromodal button.micromodal__yes:focus,
|
|
439
|
+
.micromodal button.micromodal__yes:hover {
|
|
440
|
+
background-color: #549B0D;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
.micromodal button.micromodal__yes:active {
|
|
444
|
+
border-top-color: #1A3004;
|
|
445
|
+
border-left-color: #1A3004;
|
|
446
|
+
border-bottom-color: #94BA6F;
|
|
447
|
+
border-right-color: #94BA6F;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
.micromodal button.micromodal__close {
|
|
451
|
+
clip-path: polygon(0% 4%, 0% 0%, 100% 0%, 100% 100%, 94% 100%);
|
|
452
|
+
position: absolute;
|
|
453
|
+
top: -1px;
|
|
454
|
+
right: -1px;
|
|
455
|
+
width: 68px;
|
|
456
|
+
}
|
|
457
|
+
</style>`;
|
|
458
|
+
|
|
459
|
+
const modalHtml = `<div class="micromodal" id="micromodal" aria-hidden="true">
|
|
460
|
+
<div class="micromodal__overlay" tabindex="-1">
|
|
461
|
+
<form class="micromodal__container" role="dialog" aria-modal="true">
|
|
462
|
+
<div class="micromodal__content"></div>
|
|
463
|
+
<div class="micromodal__buttons">
|
|
464
|
+
<button class="micromodal__no" type="button"></button>
|
|
465
|
+
<button class="micromodal__yes" type="submit"></button>
|
|
466
|
+
</div>
|
|
467
|
+
<button class="micromodal__close" type="button" aria-label="Close modal"></button>
|
|
468
|
+
</form>
|
|
469
|
+
</div>
|
|
470
|
+
</div>`;
|
|
471
|
+
|
|
472
|
+
const themodal = (() => {
|
|
473
|
+
let html = "";
|
|
474
|
+
let yes = "";
|
|
475
|
+
let no = "";
|
|
476
|
+
let zIndex = "100";
|
|
477
|
+
let closeHtml = "";
|
|
478
|
+
|
|
479
|
+
let enableClickOutsideCloses = true;
|
|
480
|
+
let disableScroll = true;
|
|
481
|
+
let disableFocus = false;
|
|
482
|
+
|
|
483
|
+
let onYes = [];
|
|
484
|
+
let onNo = [];
|
|
485
|
+
let onOpen = [];
|
|
486
|
+
|
|
487
|
+
const themodalMain = {
|
|
488
|
+
isShowing: false,
|
|
489
|
+
open() {
|
|
490
|
+
document.body.insertAdjacentHTML("afterbegin", "<div save-ignore class='micromodal-parent'>" + modalCss + modalHtml + "</div>");
|
|
491
|
+
|
|
492
|
+
const modalOverlayElem = document.querySelector(".micromodal__overlay");
|
|
493
|
+
const modalContentElem = document.querySelector(".micromodal__content");
|
|
494
|
+
const modalButtonsElem = document.querySelector(".micromodal__buttons");
|
|
495
|
+
const modalYesElem = document.querySelector(".micromodal__yes");
|
|
496
|
+
const modalNoElem = document.querySelector(".micromodal__no");
|
|
497
|
+
const modalCloseElem = document.querySelector(".micromodal__close");
|
|
498
|
+
|
|
499
|
+
modalContentElem.innerHTML = html;
|
|
500
|
+
modalYesElem.innerHTML = yes;
|
|
501
|
+
modalNoElem.innerHTML = no;
|
|
502
|
+
modalOverlayElem.style.zIndex = zIndex;
|
|
503
|
+
modalCloseElem.innerHTML = closeHtml;
|
|
504
|
+
|
|
505
|
+
// MODIFIED so modal doesn't close if mousedown happened inside the modal
|
|
506
|
+
let mousedownOnBackdrop = false;
|
|
507
|
+
|
|
508
|
+
// MODIFIED so modal doesn't close if mousedown happened inside the modal
|
|
509
|
+
function handleMousedown(event) {
|
|
510
|
+
// Check if mousedown started on backdrop (overlay but not container)
|
|
511
|
+
mousedownOnBackdrop = event.target.closest(".micromodal__overlay") &&
|
|
512
|
+
!event.target.closest(".micromodal__container");
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function handleClick(event) {
|
|
516
|
+
if (event.target.closest(".micromodal__no") || event.target.closest(".micromodal__close")) {
|
|
517
|
+
onNo.forEach(cb => cb());
|
|
518
|
+
MicroModal.close("micromodal");
|
|
519
|
+
// MODIFIED so modal doesn't close if mousedown happened inside the modal
|
|
520
|
+
} else if (enableClickOutsideCloses && mousedownOnBackdrop && !event.target.closest(".micromodal__container") && event.target.closest(".micromodal__overlay")) {
|
|
521
|
+
onNo.forEach(cb => cb());
|
|
522
|
+
MicroModal.close("micromodal");
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// Reset after handling
|
|
526
|
+
mousedownOnBackdrop = false;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
function handleSubmit(event) {
|
|
530
|
+
if (event.target.closest("#micromodal")) {
|
|
531
|
+
event.preventDefault();
|
|
532
|
+
|
|
533
|
+
// Execute callbacks and check if any return false or throw errors
|
|
534
|
+
let shouldClose = true;
|
|
535
|
+
|
|
536
|
+
for (const cb of onYes) {
|
|
537
|
+
try {
|
|
538
|
+
const result = cb();
|
|
539
|
+
// If callback explicitly returns false, don't close
|
|
540
|
+
if (result === false) {
|
|
541
|
+
shouldClose = false;
|
|
542
|
+
break;
|
|
543
|
+
}
|
|
544
|
+
} catch (error) {
|
|
545
|
+
// If callback throws an error, don't close
|
|
546
|
+
shouldClose = false;
|
|
547
|
+
// Could optionally bubble the error up or handle it here
|
|
548
|
+
break;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// Only close if all callbacks succeeded
|
|
553
|
+
if (shouldClose) {
|
|
554
|
+
MicroModal.close("micromodal");
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// MODIFIED so modal doesn't close if mousedown happened inside the modal
|
|
560
|
+
document.addEventListener("mousedown", handleMousedown);
|
|
561
|
+
document.addEventListener("click", handleClick);
|
|
562
|
+
document.addEventListener("submit", handleSubmit);
|
|
563
|
+
|
|
564
|
+
function setButtonsVisibility () {
|
|
565
|
+
modalButtonsElem.classList.toggle("micromodal__hide", !yes && !no);
|
|
566
|
+
modalYesElem.classList.toggle("micromodal__hide", !yes);
|
|
567
|
+
modalNoElem.classList.toggle("micromodal__hide", !no);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
setButtonsVisibility();
|
|
571
|
+
|
|
572
|
+
MicroModal.show("micromodal", {
|
|
573
|
+
disableScroll,
|
|
574
|
+
disableFocus: true, // we use our own
|
|
575
|
+
// reset everything on close
|
|
576
|
+
onClose: modal => {
|
|
577
|
+
document.querySelector(".micromodal-parent")?.remove();
|
|
578
|
+
|
|
579
|
+
html = "";
|
|
580
|
+
yes = "";
|
|
581
|
+
no = "";
|
|
582
|
+
zIndex = "100";
|
|
583
|
+
closeHtml = "";
|
|
584
|
+
|
|
585
|
+
// reset to defaults
|
|
586
|
+
enableClickOutsideCloses = true;
|
|
587
|
+
disableScroll = true;
|
|
588
|
+
disableFocus = false;
|
|
589
|
+
|
|
590
|
+
onYes = [];
|
|
591
|
+
onNo = [];
|
|
592
|
+
onOpen = [];
|
|
593
|
+
|
|
594
|
+
this.isShowing = false;
|
|
595
|
+
|
|
596
|
+
// MODIFIED so modal doesn't close if mousedown happened inside the modal
|
|
597
|
+
document.removeEventListener("mousedown", handleMousedown);
|
|
598
|
+
document.removeEventListener("click", handleClick);
|
|
599
|
+
document.removeEventListener("submit", handleSubmit);
|
|
600
|
+
}
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
this.isShowing = true;
|
|
604
|
+
|
|
605
|
+
if (!disableFocus) {
|
|
606
|
+
let firstInput = modalOverlayElem.querySelector(".micromodal__content :is(input,textarea,button):not(.micromodal__hide), .micromodal__buttons :is(input,textarea,button):not(.micromodal__hide)");
|
|
607
|
+
firstInput?.focus();
|
|
608
|
+
firstInput?.setSelectionRange?.(-1, -1);
|
|
609
|
+
}
|
|
610
|
+
},
|
|
611
|
+
close() {
|
|
612
|
+
onNo.forEach(cb => cb());
|
|
613
|
+
MicroModal.close("micromodal");
|
|
614
|
+
},
|
|
615
|
+
get html() {
|
|
616
|
+
return html;
|
|
617
|
+
},
|
|
618
|
+
set html(newVal) {
|
|
619
|
+
html = newVal;
|
|
620
|
+
},
|
|
621
|
+
get closeHtml() {
|
|
622
|
+
return closeHtml;
|
|
623
|
+
},
|
|
624
|
+
set closeHtml(newVal) {
|
|
625
|
+
closeHtml = newVal;
|
|
626
|
+
},
|
|
627
|
+
get yes() {
|
|
628
|
+
return yes;
|
|
629
|
+
},
|
|
630
|
+
set yes(newVal) {
|
|
631
|
+
yes = newVal;
|
|
632
|
+
},
|
|
633
|
+
get no() {
|
|
634
|
+
return no;
|
|
635
|
+
},
|
|
636
|
+
set no(newVal) {
|
|
637
|
+
no = newVal;
|
|
638
|
+
},
|
|
639
|
+
get zIndex() {
|
|
640
|
+
return zIndex;
|
|
641
|
+
},
|
|
642
|
+
set zIndex(newVal) {
|
|
643
|
+
zIndex = newVal;
|
|
644
|
+
},
|
|
645
|
+
get disableFocus() {
|
|
646
|
+
return disableFocus;
|
|
647
|
+
},
|
|
648
|
+
set disableFocus(newVal) {
|
|
649
|
+
disableFocus = newVal;
|
|
650
|
+
},
|
|
651
|
+
get disableScroll() {
|
|
652
|
+
return disableScroll;
|
|
653
|
+
},
|
|
654
|
+
set disableScroll(newVal) {
|
|
655
|
+
disableScroll = newVal;
|
|
656
|
+
},
|
|
657
|
+
|
|
658
|
+
onYes: (cb) => {
|
|
659
|
+
onYes.push(cb);
|
|
660
|
+
},
|
|
661
|
+
onNo: (cb) => {
|
|
662
|
+
onNo.push(cb);
|
|
663
|
+
},
|
|
664
|
+
onOpen: (cb) => {
|
|
665
|
+
onOpen.push(cb);
|
|
666
|
+
},
|
|
667
|
+
};
|
|
668
|
+
|
|
669
|
+
return themodalMain;
|
|
670
|
+
})();
|
|
671
|
+
|
|
672
|
+
// Export to window for global access
|
|
673
|
+
export function exportToWindow() {
|
|
674
|
+
window.themodal = themodal;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
export default themodal;
|