@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.
- package/NOTICE +19 -0
- package/README.md +2 -0
- package/_headers +2 -0
- package/all.js +24 -0
- package/clay-data.js +16 -0
- package/clay-dom.js +18 -0
- package/clay-events.js +28 -0
- package/clay-options.js +27 -0
- package/clay-ui.js +29 -0
- package/clay-utils.js +18 -0
- package/clay.js +17 -5
- package/package.json +11 -1
- package/sap.js +16 -0
- package/src/dom/all.js +440 -0
- package/src/dom/dom-helpers.js +218 -0
- package/src/dom/form-data.js +62 -0
- package/src/dom/insert-style-tag.js +109 -0
- package/src/dom/nearest.js +97 -0
- package/src/dom/pipe.js +1 -0
- package/src/events/index.js +22 -0
- package/src/events/onclickaway.js +27 -0
- package/src/events/onclickchildren.js +12 -0
- package/src/events/onclone.js +54 -0
- package/src/events/onmutation.js +54 -0
- package/src/events/onpagemutation.js +37 -0
- package/src/events/onrender.js +32 -0
- package/src/lib/mutation.js +26 -9
- package/src/lib/on-load.js +9 -0
- package/src/loader-logic.js +2 -1
- package/src/loader.js +2 -0
- package/src/options/options.js +253 -0
- package/src/plugins/demo.js +124 -0
- package/src/sync/live-sync.js +12 -1
- package/src/sync/merge-tags.js +36 -0
- package/src/ui/dialogs.js +206 -0
- package/src/ui/index.js +37 -0
- package/src/ui/modal.js +884 -0
- package/src/ui/toast.js +428 -0
- package/src/utils/copy-to-clipboard.js +27 -0
- package/src/utils/debounce.js +23 -0
- package/src/utils/index.js +10 -0
- package/src/utils/slugify.js +20 -0
- package/src/vendor/hyper-morph.vendor.js +2 -1
package/src/ui/modal.js
ADDED
|
@@ -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;
|