wick-dom-observer 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/.github/workflows/main.yml +12 -0
- package/LICENSE +21 -0
- package/README.md +530 -0
- package/assets/log-observed-dissapear.png +0 -0
- package/assets/overview.png +0 -0
- package/assets/spinner.png +0 -0
- package/assets/timelines.png +0 -0
- package/assets/toast.png +0 -0
- package/cypress/e2e/modalTableDemo.cy.js +115 -0
- package/cypress/e2e/spinnerAndToast.cy.js +313 -0
- package/cypress/public/demo.html +847 -0
- package/cypress/public/modal-table-demo.html +454 -0
- package/cypress/support/commands.js +25 -0
- package/cypress/support/e2e.js +20 -0
- package/cypress.config.js +13 -0
- package/package.json +33 -0
- package/src/index.d.ts +144 -0
- package/src/index.js +2 -0
- package/src/utils.js +509 -0
- package/src/watcher.js +178 -0
|
@@ -0,0 +1,847 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
+
<title>clickSpinner test page</title>
|
|
8
|
+
<style>
|
|
9
|
+
:root {
|
|
10
|
+
--bg: #0b1220;
|
|
11
|
+
--bg-card: rgba(15, 23, 42, 0.88);
|
|
12
|
+
--text: #e2e8f0;
|
|
13
|
+
--muted: #94a3b8;
|
|
14
|
+
--primary: #6366f1;
|
|
15
|
+
--primary-2: #8b5cf6;
|
|
16
|
+
--accent: #22d3ee;
|
|
17
|
+
--ring: rgba(99, 102, 241, 0.45);
|
|
18
|
+
--border: rgba(148, 163, 184, 0.2);
|
|
19
|
+
--shadow: 0 10px 30px rgba(2, 6, 23, 0.45);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
* {
|
|
23
|
+
box-sizing: border-box;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
body {
|
|
27
|
+
margin: 0;
|
|
28
|
+
min-height: 100vh;
|
|
29
|
+
font-family: Inter, Segoe UI, Roboto, Arial, sans-serif;
|
|
30
|
+
color: var(--text);
|
|
31
|
+
background:
|
|
32
|
+
radial-gradient(circle at 10% 20%, rgba(34, 211, 238, 0.12), transparent 35%),
|
|
33
|
+
radial-gradient(circle at 90% 15%, rgba(139, 92, 246, 0.16), transparent 38%),
|
|
34
|
+
linear-gradient(160deg, #020617, #0f172a 55%, #111827);
|
|
35
|
+
display: grid;
|
|
36
|
+
place-items: center;
|
|
37
|
+
padding: 24px;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.page-grid {
|
|
41
|
+
width: min(1080px, 100%);
|
|
42
|
+
display: grid;
|
|
43
|
+
gap: 18px;
|
|
44
|
+
grid-template-columns: repeat(auto-fit, minmax(360px, 1fr));
|
|
45
|
+
align-items: start;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.panel {
|
|
49
|
+
width: min(820px, 100%);
|
|
50
|
+
background: var(--bg-card);
|
|
51
|
+
border: 1px solid var(--border);
|
|
52
|
+
border-radius: 18px;
|
|
53
|
+
box-shadow: var(--shadow);
|
|
54
|
+
backdrop-filter: blur(6px);
|
|
55
|
+
overflow: hidden;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.panel-header {
|
|
59
|
+
padding: 20px 24px 10px;
|
|
60
|
+
border-bottom: 1px solid var(--border);
|
|
61
|
+
background: linear-gradient(180deg, rgba(99, 102, 241, 0.09), transparent);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.title {
|
|
65
|
+
margin: 0;
|
|
66
|
+
font-size: 1.2rem;
|
|
67
|
+
font-weight: 700;
|
|
68
|
+
letter-spacing: 0.2px;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.subtitle {
|
|
72
|
+
margin: 8px 0 0;
|
|
73
|
+
color: var(--muted);
|
|
74
|
+
font-size: 0.93rem;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.panel-body {
|
|
78
|
+
padding: 22px 30px 24px;
|
|
79
|
+
display: grid;
|
|
80
|
+
gap: 16px;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
#spinnerPlaygroundBody {
|
|
84
|
+
position: relative;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.controls-group {
|
|
88
|
+
display: grid;
|
|
89
|
+
gap: 12px;
|
|
90
|
+
align-items: start;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.button-row {
|
|
94
|
+
display: flex;
|
|
95
|
+
flex-direction: column;
|
|
96
|
+
align-items: flex-start;
|
|
97
|
+
gap: 10px;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
button {
|
|
101
|
+
border: 1px solid transparent;
|
|
102
|
+
border-radius: 12px;
|
|
103
|
+
font-size: 0.95rem;
|
|
104
|
+
font-weight: 600;
|
|
105
|
+
padding: 10px 14px;
|
|
106
|
+
color: #f8fafc;
|
|
107
|
+
background: linear-gradient(135deg, var(--primary), var(--primary-2));
|
|
108
|
+
cursor: pointer;
|
|
109
|
+
transition: transform 120ms ease, box-shadow 120ms ease, opacity 120ms ease;
|
|
110
|
+
box-shadow: 0 8px 18px rgba(79, 70, 229, 0.35);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
button:hover {
|
|
114
|
+
transform: translateY(-1px);
|
|
115
|
+
box-shadow: 0 12px 20px rgba(79, 70, 229, 0.4);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
button:focus-visible {
|
|
119
|
+
outline: none;
|
|
120
|
+
box-shadow: 0 0 0 3px var(--ring), 0 12px 20px rgba(79, 70, 229, 0.4);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
#spinnerBtn5 {
|
|
124
|
+
background: linear-gradient(135deg, rgba(6, 95, 70, 0.95), rgba(22, 163, 74, 0.95));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
#spinnerBtn5.is-loading {
|
|
128
|
+
background: linear-gradient(135deg, rgba(153, 27, 27, 0.95), rgba(220, 38, 38, 0.95));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
#spinnerBtn6,
|
|
132
|
+
#toastBtn3 {
|
|
133
|
+
background: linear-gradient(135deg, #334155, #1f2937);
|
|
134
|
+
box-shadow: 0 8px 18px rgba(31, 41, 55, 0.4);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
#toastBtn1 {
|
|
138
|
+
box-shadow: 0 8px 18px rgba(6, 78, 59, 0.55);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.canvas-wrap {
|
|
142
|
+
border: 1px solid var(--border);
|
|
143
|
+
border-radius: 14px;
|
|
144
|
+
background: rgba(2, 6, 23, 0.4);
|
|
145
|
+
padding: 12px;
|
|
146
|
+
width: fit-content;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
#spinnerCanvas,
|
|
150
|
+
#toastCanvas {
|
|
151
|
+
display: block;
|
|
152
|
+
border-radius: 10px;
|
|
153
|
+
border: 1px solid rgba(148, 163, 184, 0.35);
|
|
154
|
+
background:
|
|
155
|
+
linear-gradient(135deg, rgba(56, 189, 248, 0.16), rgba(99, 102, 241, 0.16)),
|
|
156
|
+
#0f172a;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.hint {
|
|
160
|
+
margin: 0;
|
|
161
|
+
font-size: 0.86rem;
|
|
162
|
+
color: var(--muted);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.spinner-layer {
|
|
166
|
+
position: relative;
|
|
167
|
+
display: flex;
|
|
168
|
+
flex-direction: column;
|
|
169
|
+
gap: 8px;
|
|
170
|
+
min-height: 46px;
|
|
171
|
+
align-items: flex-start;
|
|
172
|
+
pointer-events: none;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.toast-layer {
|
|
176
|
+
position: relative;
|
|
177
|
+
display: flex;
|
|
178
|
+
flex-direction: column;
|
|
179
|
+
gap: 8px;
|
|
180
|
+
min-height: 46px;
|
|
181
|
+
align-items: flex-end;
|
|
182
|
+
pointer-events: none;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.spinner {
|
|
186
|
+
display: inline-flex;
|
|
187
|
+
align-items: center;
|
|
188
|
+
gap: 8px;
|
|
189
|
+
padding: 10px 14px;
|
|
190
|
+
border-radius: 12px;
|
|
191
|
+
border: 1px solid rgba(251, 191, 36, 0.75);
|
|
192
|
+
color: #fff7ed;
|
|
193
|
+
background: linear-gradient(135deg, rgba(120, 53, 15, 0.95), rgba(194, 65, 12, 0.95));
|
|
194
|
+
box-shadow: 0 10px 24px rgba(194, 65, 12, 0.45);
|
|
195
|
+
font-size: 0.95rem;
|
|
196
|
+
font-weight: 700;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.spinner.spinner-removed {
|
|
200
|
+
color: #111827;
|
|
201
|
+
border-color: rgba(250, 204, 21, 0.9);
|
|
202
|
+
background: linear-gradient(135deg, rgba(250, 204, 21, 0.95), rgba(234, 179, 8, 0.95));
|
|
203
|
+
box-shadow: 0 10px 24px rgba(234, 179, 8, 0.45);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.spinner.spinner-removed::before {
|
|
207
|
+
border: 2px solid rgba(17, 24, 39, 0.25);
|
|
208
|
+
border-top-color: #111827;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.spinner.spinner-overlay {
|
|
212
|
+
position: absolute;
|
|
213
|
+
inset: 0;
|
|
214
|
+
width: 100%;
|
|
215
|
+
height: 100%;
|
|
216
|
+
justify-content: center;
|
|
217
|
+
align-items: center;
|
|
218
|
+
border: 0;
|
|
219
|
+
border-radius: 0;
|
|
220
|
+
background: transparent;
|
|
221
|
+
box-shadow: none;
|
|
222
|
+
text-indent: -9999px;
|
|
223
|
+
overflow: hidden;
|
|
224
|
+
pointer-events: none;
|
|
225
|
+
z-index: 40;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.spinner.spinner-overlay::before {
|
|
229
|
+
width: 130px;
|
|
230
|
+
height: 130px;
|
|
231
|
+
border: 0;
|
|
232
|
+
background: conic-gradient(
|
|
233
|
+
from 0deg,
|
|
234
|
+
rgba(250, 204, 21, 0.14) 0deg,
|
|
235
|
+
rgba(250, 204, 21, 0.24) 140deg,
|
|
236
|
+
#fde047 250deg,
|
|
237
|
+
#f59e0b 320deg,
|
|
238
|
+
rgba(250, 204, 21, 0.14) 360deg
|
|
239
|
+
);
|
|
240
|
+
-webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 7px), #000 calc(100% - 6px));
|
|
241
|
+
mask: radial-gradient(farthest-side, transparent calc(100% - 7px), #000 calc(100% - 6px));
|
|
242
|
+
animation: spin 900ms linear infinite;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.spinner.spinner-spokes-orange {
|
|
246
|
+
position: absolute;
|
|
247
|
+
inset: 0;
|
|
248
|
+
width: 100%;
|
|
249
|
+
height: 100%;
|
|
250
|
+
min-width: 0;
|
|
251
|
+
padding: 0;
|
|
252
|
+
gap: 0;
|
|
253
|
+
border: 0;
|
|
254
|
+
background: transparent;
|
|
255
|
+
box-shadow: none;
|
|
256
|
+
color: transparent;
|
|
257
|
+
text-indent: -9999px;
|
|
258
|
+
overflow: visible;
|
|
259
|
+
display: flex;
|
|
260
|
+
justify-content: center;
|
|
261
|
+
align-items: center;
|
|
262
|
+
z-index: 35;
|
|
263
|
+
pointer-events: none;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
.spinner.spinner-spokes-orange::before {
|
|
267
|
+
content: "";
|
|
268
|
+
position: relative;
|
|
269
|
+
width: 220px;
|
|
270
|
+
height: 220px;
|
|
271
|
+
border-radius: 999px;
|
|
272
|
+
background: repeating-conic-gradient(
|
|
273
|
+
from -90deg,
|
|
274
|
+
rgba(251, 146, 60, 1) 0deg 16deg,
|
|
275
|
+
rgba(234, 88, 12, 0.22) 16deg 30deg
|
|
276
|
+
);
|
|
277
|
+
-webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 20px), #000 calc(100% - 19px));
|
|
278
|
+
mask: radial-gradient(farthest-side, transparent calc(100% - 20px), #000 calc(100% - 19px));
|
|
279
|
+
animation: spin 900ms linear infinite;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.spinner.spinner-inline {
|
|
283
|
+
padding: 4px 10px;
|
|
284
|
+
font-size: 0.82rem;
|
|
285
|
+
box-shadow: none;
|
|
286
|
+
border-color: rgba(251, 191, 36, 0.55);
|
|
287
|
+
min-width: auto;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.button-inline-spinner {
|
|
291
|
+
display: inline-block;
|
|
292
|
+
width: 16px;
|
|
293
|
+
height: 16px;
|
|
294
|
+
border-radius: 999px;
|
|
295
|
+
border: 2px solid rgba(255, 255, 255, 0.45);
|
|
296
|
+
border-top-color: #ffffff;
|
|
297
|
+
animation: spin 700ms linear infinite;
|
|
298
|
+
flex: 0 0 auto;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
.toast {
|
|
302
|
+
display: inline-flex;
|
|
303
|
+
align-items: center;
|
|
304
|
+
gap: 8px;
|
|
305
|
+
padding: 10px 14px;
|
|
306
|
+
border-radius: 12px;
|
|
307
|
+
border: 1px solid rgba(110, 231, 183, 0.75);
|
|
308
|
+
color: #ecfdf5;
|
|
309
|
+
background: linear-gradient(135deg, rgba(6, 95, 70, 0.95), rgba(22, 163, 74, 0.95));
|
|
310
|
+
box-shadow: 0 10px 24px rgba(22, 163, 74, 0.45);
|
|
311
|
+
font-size: 0.95rem;
|
|
312
|
+
font-weight: 700;
|
|
313
|
+
min-width: 220px;
|
|
314
|
+
justify-content: space-between;
|
|
315
|
+
pointer-events: auto;
|
|
316
|
+
transform-origin: right center;
|
|
317
|
+
animation: toastIn 220ms ease-out;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.toast[data-from="toastBtn1"] {
|
|
321
|
+
box-shadow: 0 10px 24px rgba(6, 78, 59, 0.55);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
.toast::before {
|
|
325
|
+
content: "";
|
|
326
|
+
width: 10px;
|
|
327
|
+
height: 10px;
|
|
328
|
+
border-radius: 999px;
|
|
329
|
+
background: #86efac;
|
|
330
|
+
box-shadow: 0 0 0 2px rgba(134, 239, 172, 0.2);
|
|
331
|
+
flex: 0 0 auto;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
.toast-message {
|
|
335
|
+
flex: 1 1 auto;
|
|
336
|
+
min-width: 0;
|
|
337
|
+
white-space: nowrap;
|
|
338
|
+
overflow: hidden;
|
|
339
|
+
text-overflow: ellipsis;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
.toast-close {
|
|
343
|
+
margin-left: 8px;
|
|
344
|
+
border: 0;
|
|
345
|
+
width: 22px;
|
|
346
|
+
height: 22px;
|
|
347
|
+
border-radius: 999px;
|
|
348
|
+
background: rgba(6, 78, 59, 0.8);
|
|
349
|
+
color: #ecfdf5;
|
|
350
|
+
font-size: 14px;
|
|
351
|
+
line-height: 1;
|
|
352
|
+
display: inline-flex;
|
|
353
|
+
align-items: center;
|
|
354
|
+
justify-content: center;
|
|
355
|
+
cursor: pointer;
|
|
356
|
+
box-shadow: none;
|
|
357
|
+
padding: 0;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
.toast-close:hover {
|
|
361
|
+
transform: none;
|
|
362
|
+
box-shadow: none;
|
|
363
|
+
background: rgba(6, 78, 59, 1);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
.toast-close:focus-visible {
|
|
367
|
+
box-shadow: 0 0 0 2px rgba(110, 231, 183, 0.5);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
.toast-closing {
|
|
371
|
+
animation: toastOut 180ms ease-in forwards;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
.toast-hidden {
|
|
375
|
+
display: none;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
.spinner::before {
|
|
379
|
+
content: "";
|
|
380
|
+
width: 12px;
|
|
381
|
+
height: 12px;
|
|
382
|
+
border-radius: 999px;
|
|
383
|
+
border: 2px solid rgba(255, 237, 213, 0.45);
|
|
384
|
+
border-top-color: #fde68a;
|
|
385
|
+
animation: spin 700ms linear infinite;
|
|
386
|
+
flex: 0 0 auto;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
.loading {
|
|
390
|
+
opacity: 1;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
.spinner-hidden {
|
|
394
|
+
position: absolute;
|
|
395
|
+
opacity: 0;
|
|
396
|
+
visibility: hidden;
|
|
397
|
+
transform: translateY(-4px);
|
|
398
|
+
transition: opacity 180ms ease, transform 180ms ease;
|
|
399
|
+
pointer-events: none;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
@keyframes spin {
|
|
403
|
+
to {
|
|
404
|
+
transform: rotate(360deg);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
@keyframes toastIn {
|
|
409
|
+
from {
|
|
410
|
+
opacity: 0;
|
|
411
|
+
transform: translateX(30px) scaleX(0.8);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
to {
|
|
415
|
+
opacity: 1;
|
|
416
|
+
transform: translateX(0) scaleX(1);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
@keyframes toastOut {
|
|
421
|
+
from {
|
|
422
|
+
opacity: 1;
|
|
423
|
+
transform: translateX(0) scaleX(1);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
to {
|
|
427
|
+
opacity: 0;
|
|
428
|
+
transform: translateX(24px) scaleX(0.86);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
</style>
|
|
432
|
+
</head>
|
|
433
|
+
|
|
434
|
+
<body>
|
|
435
|
+
<div class="page-grid">
|
|
436
|
+
<main class="panel">
|
|
437
|
+
<header class="panel-header">
|
|
438
|
+
<h1 class="title">Spinner Playground</h1>
|
|
439
|
+
<p class="subtitle">Use these controls for <code>clickAndWatchForElement()</code> tests</p>
|
|
440
|
+
</header>
|
|
441
|
+
|
|
442
|
+
<section class="panel-body" id="spinnerPlaygroundBody">
|
|
443
|
+
<div class="controls-group">
|
|
444
|
+
<div class="button-row">
|
|
445
|
+
<button id="spinnerBtn1">Spinner shows after 1000ms - removed after 1500ms</button>
|
|
446
|
+
<button id="spinnerBtn2">Spinner shows immediatelly - removed after 2500ms</button>
|
|
447
|
+
<button id="spinnerBtn3">Spinner shows after 500ms - hidden after 3500ms</button>
|
|
448
|
+
<!--<button id="spinnerBtn4">Spinner shows immediatelly - hidden after 1800ms</button>-->
|
|
449
|
+
<button id="spinnerBtn5">Spinner shows INSIDE BUTTON inmediatelly - text restored after 1800ms</button>
|
|
450
|
+
<!--<button id="spinnerBtn6">No spinner</button>-->
|
|
451
|
+
</div>
|
|
452
|
+
|
|
453
|
+
<div class="canvas-wrap">
|
|
454
|
+
<canvas id="spinnerCanvas" width="390" height="100"></canvas>
|
|
455
|
+
</div>
|
|
456
|
+
</div>
|
|
457
|
+
|
|
458
|
+
<div class="spinner-layer" aria-live="polite">
|
|
459
|
+
<div class="spinner-persistent spinner-hidden" data-from="spinnerBtn4" aria-hidden="true">Waiting</div>
|
|
460
|
+
</div>
|
|
461
|
+
<div class="spinner-persistent spinner-spokes-orange spinner-hidden" data-from="spinnerBtn3" aria-hidden="true">Loading</div>
|
|
462
|
+
|
|
463
|
+
<p class="hint">Save/Slow/Canvas show a temporary spinner. Hide Spinner becomes hidden. Inline Spinner replaces button text. NoSpinner does nothing.</p>
|
|
464
|
+
</section>
|
|
465
|
+
</main>
|
|
466
|
+
|
|
467
|
+
<main class="panel">
|
|
468
|
+
<header class="panel-header">
|
|
469
|
+
<h1 class="title">Toast Playground</h1>
|
|
470
|
+
<p class="subtitle">Use these controls for <code>clickAndWatchForElement()</code> tests</p>
|
|
471
|
+
</header>
|
|
472
|
+
|
|
473
|
+
<section class="panel-body">
|
|
474
|
+
<div class="controls-group">
|
|
475
|
+
<div class="button-row">
|
|
476
|
+
<button id="toastBtn1">Toast shows after 1800ms - removed after 1000ms</button>
|
|
477
|
+
<button id="toastBtn2">Toast shows immediatelly - hidden after 3000ms</button>
|
|
478
|
+
<button id="toastBtn3">No toast</button>
|
|
479
|
+
</div>
|
|
480
|
+
|
|
481
|
+
<div class="canvas-wrap">
|
|
482
|
+
<canvas id="toastCanvas" width="390" height="100"></canvas>
|
|
483
|
+
</div>
|
|
484
|
+
</div>
|
|
485
|
+
|
|
486
|
+
<div class="toast-layer" aria-live="polite">
|
|
487
|
+
<div class="toast toast-persistent toast-hidden" data-from="toastBtn2" aria-hidden="true">
|
|
488
|
+
<span class="toast-message">Toast shows immediatelly - hidden after 3000ms</span>
|
|
489
|
+
<button class="toast-close" type="button" aria-label="Close toast">×</button>
|
|
490
|
+
</div>
|
|
491
|
+
<div class="toast toast-persistent toast-hidden" data-from="toastCanvas" aria-hidden="true">
|
|
492
|
+
<span class="toast-message">Toast shows after 1000ms - removed after 110ms</span>
|
|
493
|
+
<button class="toast-close" type="button" aria-label="Close toast">×</button>
|
|
494
|
+
</div>
|
|
495
|
+
</div>
|
|
496
|
+
|
|
497
|
+
<p class="hint">Save/Slow/Canvas show a green toast that expands from the right and auto-dismisses (available X icon to close). No toast does nothing.</p>
|
|
498
|
+
</section>
|
|
499
|
+
</main>
|
|
500
|
+
</div>
|
|
501
|
+
|
|
502
|
+
<script>
|
|
503
|
+
(function () {
|
|
504
|
+
function addSpinner(id, lifetime, delay = 0, hideInsteadOfRemove = false) {
|
|
505
|
+
lifetime = lifetime !== undefined ? lifetime : 120
|
|
506
|
+
var layer = document.querySelector('.spinner-layer')
|
|
507
|
+
var spinnerPlayground = document.getElementById('spinnerPlaygroundBody') || layer
|
|
508
|
+
|
|
509
|
+
var startSpinner = function () {
|
|
510
|
+
if (hideInsteadOfRemove) {
|
|
511
|
+
var persistent = spinnerPlayground.querySelector('.spinner-persistent[data-from="' + id + '"]')
|
|
512
|
+
if (!persistent) {
|
|
513
|
+
persistent = document.createElement('div')
|
|
514
|
+
persistent.className = 'spinner-persistent spinner-hidden'
|
|
515
|
+
persistent.setAttribute('data-from', id)
|
|
516
|
+
persistent.setAttribute('aria-hidden', 'true')
|
|
517
|
+
persistent.textContent = id === 'spinnerBtn4' ? 'Waiting' : 'Loading'
|
|
518
|
+
if (id === 'spinnerBtn3') {
|
|
519
|
+
persistent.classList.add('spinner-spokes-orange')
|
|
520
|
+
spinnerPlayground.appendChild(persistent)
|
|
521
|
+
} else {
|
|
522
|
+
layer.appendChild(persistent)
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
if (persistent._hideTimer) {
|
|
527
|
+
clearTimeout(persistent._hideTimer)
|
|
528
|
+
persistent._hideTimer = null
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
if (id === 'spinnerBtn3') {
|
|
532
|
+
persistent.classList.add('spinner-spokes-orange')
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
persistent.classList.add('spinner')
|
|
536
|
+
persistent.classList.remove('spinner-hidden')
|
|
537
|
+
persistent.classList.add('loading')
|
|
538
|
+
persistent.setAttribute('aria-hidden', 'false')
|
|
539
|
+
persistent._hideTimer = setTimeout(function () {
|
|
540
|
+
persistent.classList.add('spinner-hidden')
|
|
541
|
+
persistent.classList.remove('spinner')
|
|
542
|
+
persistent.classList.remove('loading')
|
|
543
|
+
persistent.setAttribute('aria-hidden', 'true')
|
|
544
|
+
}, lifetime)
|
|
545
|
+
return
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
var created = document.createElement('div')
|
|
549
|
+
created.className = 'spinner spinner-removed loading'
|
|
550
|
+
var targetLayer = layer
|
|
551
|
+
if (id === 'spinnerBtn2') {
|
|
552
|
+
created.classList.add('spinner-overlay')
|
|
553
|
+
targetLayer = document.getElementById('spinnerPlaygroundBody') || layer
|
|
554
|
+
}
|
|
555
|
+
created.setAttribute('data-from', id)
|
|
556
|
+
created.textContent = 'Loading'
|
|
557
|
+
targetLayer.appendChild(created)
|
|
558
|
+
setTimeout(function () { created.remove() }, lifetime)
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
if (delay > 0) {
|
|
562
|
+
setTimeout(startSpinner, delay)
|
|
563
|
+
return
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
startSpinner()
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
function wireToastClose(el) {
|
|
570
|
+
if (!el || el._closeWired) return
|
|
571
|
+
var closeBtn = el.querySelector('.toast-close')
|
|
572
|
+
if (!closeBtn) return
|
|
573
|
+
closeBtn.addEventListener('click', function () {
|
|
574
|
+
dismissToast(el, el.classList.contains('toast-persistent'))
|
|
575
|
+
})
|
|
576
|
+
el._closeWired = true
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
function addToast(id, message, lifetime, delay, reuseExisting) {
|
|
580
|
+
lifetime = lifetime !== undefined ? lifetime : 3200
|
|
581
|
+
delay = delay !== undefined ? delay : 0
|
|
582
|
+
reuseExisting = reuseExisting !== undefined ? reuseExisting : false
|
|
583
|
+
var layer = document.querySelector('.toast-layer')
|
|
584
|
+
|
|
585
|
+
var el = null
|
|
586
|
+
if (reuseExisting) {
|
|
587
|
+
el = layer.querySelector('.toast-persistent[data-from="' + id + '"]')
|
|
588
|
+
if (!el) return
|
|
589
|
+
var msgEl = el.querySelector('.toast-message')
|
|
590
|
+
if (msgEl) {
|
|
591
|
+
msgEl.textContent = message || 'Success'
|
|
592
|
+
}
|
|
593
|
+
wireToastClose(el)
|
|
594
|
+
} else {
|
|
595
|
+
var stale = layer.querySelectorAll('.toast[data-from="' + id + '"]:not(.toast-persistent)')
|
|
596
|
+
stale.forEach(function (node) { node.remove() })
|
|
597
|
+
|
|
598
|
+
el = document.createElement('div')
|
|
599
|
+
el.className = 'toast'
|
|
600
|
+
el.setAttribute('data-from', id)
|
|
601
|
+
|
|
602
|
+
var msg = document.createElement('span')
|
|
603
|
+
msg.className = 'toast-message'
|
|
604
|
+
msg.textContent = message || 'Success'
|
|
605
|
+
|
|
606
|
+
var closeBtn = document.createElement('button')
|
|
607
|
+
closeBtn.className = 'toast-close'
|
|
608
|
+
closeBtn.setAttribute('type', 'button')
|
|
609
|
+
closeBtn.setAttribute('aria-label', 'Close toast')
|
|
610
|
+
closeBtn.textContent = '×'
|
|
611
|
+
|
|
612
|
+
el.appendChild(msg)
|
|
613
|
+
el.appendChild(closeBtn)
|
|
614
|
+
wireToastClose(el)
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
var showToast = function () {
|
|
618
|
+
if (el._hideTimer) {
|
|
619
|
+
clearTimeout(el._hideTimer)
|
|
620
|
+
el._hideTimer = null
|
|
621
|
+
}
|
|
622
|
+
el.classList.remove('toast-hidden')
|
|
623
|
+
el.classList.remove('toast-closing')
|
|
624
|
+
el.setAttribute('aria-hidden', 'false')
|
|
625
|
+
if (!el.parentNode) {
|
|
626
|
+
layer.appendChild(el)
|
|
627
|
+
}
|
|
628
|
+
el._dismissTimer = setTimeout(function () {
|
|
629
|
+
dismissToast(el, reuseExisting)
|
|
630
|
+
}, lifetime)
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
if (delay > 0) {
|
|
634
|
+
if (el._showTimer) {
|
|
635
|
+
clearTimeout(el._showTimer)
|
|
636
|
+
el._showTimer = null
|
|
637
|
+
}
|
|
638
|
+
el._showTimer = setTimeout(showToast, delay)
|
|
639
|
+
return
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
showToast()
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
function removeToastNode(el) {
|
|
646
|
+
if (!el) return
|
|
647
|
+
if (el._removeTimer) {
|
|
648
|
+
clearTimeout(el._removeTimer)
|
|
649
|
+
el._removeTimer = null
|
|
650
|
+
}
|
|
651
|
+
if (el.isConnected) {
|
|
652
|
+
el.remove()
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
function dismissToast(el, keepInDom) {
|
|
657
|
+
if (!el) return
|
|
658
|
+
var retractMs = 180
|
|
659
|
+
keepInDom = keepInDom !== undefined ? keepInDom : false
|
|
660
|
+
if (el._showTimer) {
|
|
661
|
+
clearTimeout(el._showTimer)
|
|
662
|
+
el._showTimer = null
|
|
663
|
+
}
|
|
664
|
+
if (el._dismissTimer) {
|
|
665
|
+
clearTimeout(el._dismissTimer)
|
|
666
|
+
el._dismissTimer = null
|
|
667
|
+
}
|
|
668
|
+
if (el._hideTimer) {
|
|
669
|
+
clearTimeout(el._hideTimer)
|
|
670
|
+
el._hideTimer = null
|
|
671
|
+
}
|
|
672
|
+
if (el.classList.contains('toast-closing')) {
|
|
673
|
+
if (!keepInDom && !el._removeTimer) {
|
|
674
|
+
el._removeTimer = setTimeout(function () {
|
|
675
|
+
removeToastNode(el)
|
|
676
|
+
}, 180)
|
|
677
|
+
}
|
|
678
|
+
return
|
|
679
|
+
}
|
|
680
|
+
el.classList.add('toast-closing')
|
|
681
|
+
if (keepInDom) {
|
|
682
|
+
el._hideTimer = setTimeout(function () {
|
|
683
|
+
el.classList.add('toast-hidden')
|
|
684
|
+
el.classList.remove('toast-closing')
|
|
685
|
+
el.setAttribute('aria-hidden', 'true')
|
|
686
|
+
}, retractMs)
|
|
687
|
+
return
|
|
688
|
+
}
|
|
689
|
+
var handledByAnimation = false
|
|
690
|
+
var onAnimationEnd = function () {
|
|
691
|
+
handledByAnimation = true
|
|
692
|
+
el.removeEventListener('animationend', onAnimationEnd)
|
|
693
|
+
removeToastNode(el)
|
|
694
|
+
}
|
|
695
|
+
el.addEventListener('animationend', onAnimationEnd)
|
|
696
|
+
el._removeTimer = setTimeout(function () {
|
|
697
|
+
if (!handledByAnimation) {
|
|
698
|
+
el.removeEventListener('animationend', onAnimationEnd)
|
|
699
|
+
}
|
|
700
|
+
removeToastNode(el)
|
|
701
|
+
}, retractMs + 30)
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
function showInlineSpinnerInButton(buttonId, lifetime, delay) {
|
|
705
|
+
var button = document.getElementById(buttonId)
|
|
706
|
+
if (!button) return
|
|
707
|
+
|
|
708
|
+
var visibleFor = lifetime !== undefined ? lifetime : 1200
|
|
709
|
+
var waitBeforeShow = delay !== undefined ? delay : 0
|
|
710
|
+
var originalText = button.getAttribute('data-original-text') || button.textContent
|
|
711
|
+
button.setAttribute('data-original-text', originalText)
|
|
712
|
+
|
|
713
|
+
setTimeout(function () {
|
|
714
|
+
var spinnerEl = document.createElement('span')
|
|
715
|
+
spinnerEl.className = 'button-inline-spinner'
|
|
716
|
+
spinnerEl.setAttribute('data-from', buttonId)
|
|
717
|
+
spinnerEl.setAttribute('aria-hidden', 'true')
|
|
718
|
+
var buttonRect = button.getBoundingClientRect()
|
|
719
|
+
button.style.width = buttonRect.width + 'px'
|
|
720
|
+
button.style.height = buttonRect.height + 'px'
|
|
721
|
+
|
|
722
|
+
button.classList.add('is-loading')
|
|
723
|
+
button.textContent = ''
|
|
724
|
+
button.appendChild(spinnerEl)
|
|
725
|
+
|
|
726
|
+
setTimeout(function () {
|
|
727
|
+
spinnerEl.remove()
|
|
728
|
+
button.textContent = originalText
|
|
729
|
+
button.classList.remove('is-loading')
|
|
730
|
+
button.style.width = ''
|
|
731
|
+
button.style.height = ''
|
|
732
|
+
}, visibleFor)
|
|
733
|
+
}, waitBeforeShow)
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
// SPINNERS EXAMPLES
|
|
737
|
+
// ------------------------------------------------------------
|
|
738
|
+
|
|
739
|
+
// Spinner after 1000ms - removed after 1500ms
|
|
740
|
+
document.getElementById('spinnerBtn1').addEventListener('click', function () {
|
|
741
|
+
addSpinner('spinnerBtn1', 1500, 1000)
|
|
742
|
+
})
|
|
743
|
+
|
|
744
|
+
// Spinner immediatelly - removed after 6000ms
|
|
745
|
+
document.getElementById('spinnerBtn2').addEventListener('click', function () {
|
|
746
|
+
addSpinner('spinnerBtn2', 2500)
|
|
747
|
+
})
|
|
748
|
+
|
|
749
|
+
// Spinner after 500ms - hidden after 3500ms
|
|
750
|
+
document.getElementById('spinnerBtn3').addEventListener('click', function () {
|
|
751
|
+
addSpinner('spinnerBtn3', 3500, 500, true)
|
|
752
|
+
})
|
|
753
|
+
|
|
754
|
+
// // Spinner immediately - hidden after 1800ms
|
|
755
|
+
// document.getElementById('spinnerBtn4').addEventListener('click', function () {
|
|
756
|
+
// addSpinner('spinnerBtn4', 1800, 0, true)
|
|
757
|
+
// })
|
|
758
|
+
|
|
759
|
+
// Spinner inside button immediately - text restored after 1800ms.
|
|
760
|
+
document.getElementById('spinnerBtn5').addEventListener('click', function () {
|
|
761
|
+
showInlineSpinnerInButton('spinnerBtn5', 1300, 0)
|
|
762
|
+
})
|
|
763
|
+
|
|
764
|
+
// // No spinner intentionally
|
|
765
|
+
// document.getElementById('spinnerBtn6').addEventListener('click', function () { })
|
|
766
|
+
|
|
767
|
+
// Spinner shows immediatelly and then removed after 20ms
|
|
768
|
+
document.getElementById('spinnerCanvas').addEventListener('click', function () {
|
|
769
|
+
addSpinner('spinnerCanvas', 20)
|
|
770
|
+
})
|
|
771
|
+
|
|
772
|
+
// TOASTS EXAMPLES
|
|
773
|
+
// ------------------------------------------------------------
|
|
774
|
+
|
|
775
|
+
// Toast after 1800ms - removed after 1000ms
|
|
776
|
+
document.getElementById('toastBtn1').addEventListener('click', function () {
|
|
777
|
+
addToast('toastBtn1', 'Toast after 1800ms - removed after 1000ms', 1000, 1800, false)
|
|
778
|
+
})
|
|
779
|
+
|
|
780
|
+
// Spinner immediately - hidden after 1800ms
|
|
781
|
+
document.getElementById('toastBtn2').addEventListener('click', function () {
|
|
782
|
+
addToast('toastBtn2', 'Toast immediatelly - hidden after 3000ms', 3000, 0, true)
|
|
783
|
+
})
|
|
784
|
+
|
|
785
|
+
// No toast intentionally
|
|
786
|
+
document.getElementById('toastBtn3').addEventListener('click', function () { })
|
|
787
|
+
|
|
788
|
+
// Toast shows after 30ms and then hidden after 110ms
|
|
789
|
+
document.getElementById('toastCanvas').addEventListener('click', function () {
|
|
790
|
+
addToast('toastCanvas', 'Toast after 30ms - hidden after 110ms', 110, 30, true)
|
|
791
|
+
})
|
|
792
|
+
|
|
793
|
+
function paintCanvasHint(canvasId, text) {
|
|
794
|
+
var canvas = document.getElementById(canvasId)
|
|
795
|
+
var ctx = canvas.getContext('2d')
|
|
796
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height)
|
|
797
|
+
ctx.fillStyle = '#1e2b4a'
|
|
798
|
+
ctx.fillRect(0, 0, canvas.width, canvas.height)
|
|
799
|
+
ctx.fillStyle = '#f8fafc'
|
|
800
|
+
ctx.font = '700 12px Segoe UI, Arial, sans-serif'
|
|
801
|
+
ctx.textAlign = 'center'
|
|
802
|
+
ctx.textBaseline = 'top'
|
|
803
|
+
ctx.strokeStyle = 'rgba(15, 23, 42, 0.7)'
|
|
804
|
+
ctx.lineWidth = 2
|
|
805
|
+
var words = String(text || '').split(' ')
|
|
806
|
+
var lines = []
|
|
807
|
+
var currentLine = words[0] || ''
|
|
808
|
+
var maxWidth = canvas.width - 20
|
|
809
|
+
var lineHeight = 16
|
|
810
|
+
|
|
811
|
+
for (var i = 1; i < words.length; i += 1) {
|
|
812
|
+
var candidate = currentLine + ' ' + words[i]
|
|
813
|
+
if (ctx.measureText(candidate).width <= maxWidth) {
|
|
814
|
+
currentLine = candidate
|
|
815
|
+
} else {
|
|
816
|
+
lines.push(currentLine)
|
|
817
|
+
currentLine = words[i]
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
lines.push(currentLine)
|
|
821
|
+
|
|
822
|
+
var totalHeight = lines.length * lineHeight
|
|
823
|
+
var startY = (canvas.height - totalHeight) / 2
|
|
824
|
+
|
|
825
|
+
for (var j = 0; j < lines.length; j += 1) {
|
|
826
|
+
var y = startY + j * lineHeight
|
|
827
|
+
ctx.strokeText(lines[j], canvas.width / 2, y)
|
|
828
|
+
ctx.fillText(lines[j], canvas.width / 2, y)
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
function syncSpinnerButtonWidths() {
|
|
833
|
+
var referenceBtn = document.getElementById('spinnerBtn2')
|
|
834
|
+
var targetBtn = document.getElementById('spinnerBtn5')
|
|
835
|
+
if (!referenceBtn || !targetBtn) return
|
|
836
|
+
targetBtn.style.width = referenceBtn.getBoundingClientRect().width + 'px'
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
paintCanvasHint('spinnerCanvas', 'Spinner shows immediatelly - removed after 20ms (Canvas)')
|
|
840
|
+
paintCanvasHint('toastCanvas', 'Toast shows after 30ms - hidden after 110ms (Canvas)')
|
|
841
|
+
syncSpinnerButtonWidths()
|
|
842
|
+
window.addEventListener('resize', syncSpinnerButtonWidths)
|
|
843
|
+
})()
|
|
844
|
+
</script>
|
|
845
|
+
</body>
|
|
846
|
+
|
|
847
|
+
</html>
|