@webqit/webflo 0.20.31 → 0.20.33
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/package.json
CHANGED
|
@@ -35,6 +35,9 @@ export class WebfloClient extends AppRuntime {
|
|
|
35
35
|
#background;
|
|
36
36
|
get background() { return this.#background; }
|
|
37
37
|
|
|
38
|
+
#viewport;
|
|
39
|
+
get viewport() { return this.#viewport; }
|
|
40
|
+
|
|
38
41
|
get isClientSide() { return true; }
|
|
39
42
|
|
|
40
43
|
constructor(bootstrap, host) {
|
|
@@ -56,6 +59,68 @@ export class WebfloClient extends AppRuntime {
|
|
|
56
59
|
phase: 0
|
|
57
60
|
};
|
|
58
61
|
this.#background = new StarPort({ handshake: 1, autoClose: false });
|
|
62
|
+
|
|
63
|
+
// ---------------------
|
|
64
|
+
// Dynamic viewport styling
|
|
65
|
+
|
|
66
|
+
const oskToken = 'interactive-widget=resizes-content';
|
|
67
|
+
const hasOsk = (content) => content?.includes(oskToken);
|
|
68
|
+
const removeOsk = (content) => {
|
|
69
|
+
if (content?.includes('interactive-widget')) {
|
|
70
|
+
return content
|
|
71
|
+
.split(',')
|
|
72
|
+
.filter((s) => !s.includes('interactive-widget'))
|
|
73
|
+
.map((s) => s.trim())
|
|
74
|
+
.join(', ');
|
|
75
|
+
}
|
|
76
|
+
return content;
|
|
77
|
+
};
|
|
78
|
+
const addOsk = (content) => {
|
|
79
|
+
if (content?.includes('interactive-widget')) {
|
|
80
|
+
return content
|
|
81
|
+
.split(',')
|
|
82
|
+
.map((s) => s.includes('interactive-widget') ? oskToken : s.trim())
|
|
83
|
+
.join(', ');
|
|
84
|
+
}
|
|
85
|
+
return content + ', ' + oskToken;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const viewportMeta = document.querySelector('meta[name="viewport"]');
|
|
89
|
+
const viewportMetaInitialContent = viewportMeta?.content;
|
|
90
|
+
const themeColorMeta = document.querySelector('meta[name="theme-color"]');
|
|
91
|
+
const renderViewportMetas = (entry) => {
|
|
92
|
+
viewportMeta?.setAttribute('content', entry.osk ? addOsk(viewportMetaInitialContent) : removeOsk(viewportMetaInitialContent));
|
|
93
|
+
themeColorMeta?.setAttribute('content', entry.themeColor);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const initial = {
|
|
97
|
+
themeColor: themeColorMeta?.content,
|
|
98
|
+
osk: hasOsk(viewportMetaInitialContent),
|
|
99
|
+
};
|
|
100
|
+
const viewportStack = [initial];
|
|
101
|
+
|
|
102
|
+
this.#viewport = {
|
|
103
|
+
push(entryId, { themeColor = viewportStack[0].themeColor, osk = viewportStack[0].osk }) {
|
|
104
|
+
if (typeof entryId !== 'string' || !entryId?.trim()) {
|
|
105
|
+
throw new Error('entryId cannot be ommited');
|
|
106
|
+
}
|
|
107
|
+
if (viewportStack.find((e) => e.entryId === entryId)) return;
|
|
108
|
+
viewportStack.unshift({ entryId, themeColor, osk });
|
|
109
|
+
renderViewportMetas(viewportStack[0]);
|
|
110
|
+
},
|
|
111
|
+
pop(entryId) {
|
|
112
|
+
if (typeof entryId !== 'string' || !entryId?.trim()) {
|
|
113
|
+
throw new Error('entryId cannot be ommited');
|
|
114
|
+
}
|
|
115
|
+
const index = viewportStack.findIndex((e) => e.entryId === entryId);
|
|
116
|
+
if (index === -1) return;
|
|
117
|
+
viewportStack.splice(index, 1);
|
|
118
|
+
renderViewportMetas(viewportStack[0]);
|
|
119
|
+
},
|
|
120
|
+
current() {
|
|
121
|
+
return viewportStack[0];
|
|
122
|
+
}
|
|
123
|
+
};
|
|
59
124
|
}
|
|
60
125
|
|
|
61
126
|
async initialize() {
|
|
@@ -600,6 +665,7 @@ export class WebfloClient extends AppRuntime {
|
|
|
600
665
|
state: {},
|
|
601
666
|
data: $response.body,
|
|
602
667
|
env: 'client',
|
|
668
|
+
viewport: this.viewport,
|
|
603
669
|
navigator: this.navigator,
|
|
604
670
|
location: this.location,
|
|
605
671
|
network: this.network, // request, redirect, error, status, remote
|
|
@@ -42,12 +42,12 @@ export class WebfloRootClientA extends WebfloClient {
|
|
|
42
42
|
async initialize() {
|
|
43
43
|
// INITIALIZATIONS
|
|
44
44
|
const instanceController = await super.initialize();
|
|
45
|
-
|
|
45
|
+
|
|
46
46
|
// Bind network status handlers
|
|
47
47
|
const onlineHandler = () => Observer.set(this.network, 'status', window.navigator.onLine);
|
|
48
48
|
window.addEventListener('online', onlineHandler, { signal: instanceController.signal });
|
|
49
49
|
window.addEventListener('offline', onlineHandler, { signal: instanceController.signal });
|
|
50
|
-
|
|
50
|
+
|
|
51
51
|
// Window opener pinging
|
|
52
52
|
if (window.opener) {
|
|
53
53
|
const beforeunloadHandler = () => window.opener.postMessage('close');
|
|
@@ -93,7 +93,7 @@ export class WebfloRootClientA extends WebfloClient {
|
|
|
93
93
|
scopeObj.data = JSON.parse(this.host.querySelector(`script[rel="hydration"][type="application/json"]`)?.textContent?.trim() || 'null');
|
|
94
94
|
} catch (e) { }
|
|
95
95
|
scopeObj.response = new LiveResponse(scopeObj.data, { headers: { 'Content-Type': 'application/json' } });
|
|
96
|
-
|
|
96
|
+
|
|
97
97
|
for (const name of ['X-Message-Port', 'X-Webflo-Dev-Mode']) {
|
|
98
98
|
const metaElement = this.host.querySelector(`meta[name="${name}"]`);
|
|
99
99
|
if (!metaElement) continue;
|
|
@@ -136,7 +136,7 @@ export class WebfloRootClientA extends WebfloClient {
|
|
|
136
136
|
try { window.history.pushState({}, '', newHref); } catch (e) { }
|
|
137
137
|
};
|
|
138
138
|
const instanceController = super.controlClassic/*IMPORTANT*/(locationCallback);
|
|
139
|
-
|
|
139
|
+
|
|
140
140
|
// ONPOPSTATE
|
|
141
141
|
const popstateHandler = (e) => {
|
|
142
142
|
if (this.isHashChange(location)) {
|
|
@@ -158,7 +158,7 @@ export class WebfloRootClientA extends WebfloClient {
|
|
|
158
158
|
this.navigate(location.href, {}, detail);
|
|
159
159
|
};
|
|
160
160
|
window.addEventListener('popstate', popstateHandler, { signal: instanceController.signal });
|
|
161
|
-
|
|
161
|
+
|
|
162
162
|
return instanceController;
|
|
163
163
|
}
|
|
164
164
|
|
|
@@ -18,6 +18,8 @@ export class WebfloSubClient extends WebfloClient {
|
|
|
18
18
|
|
|
19
19
|
get capabilities() { return this.#superRuntime.capabilities; }
|
|
20
20
|
|
|
21
|
+
get viewport() { return this.#superRuntime.viewport; }
|
|
22
|
+
|
|
21
23
|
get withViewTransitions() { return this.host.hasAttribute('viewtransitions'); }
|
|
22
24
|
|
|
23
25
|
constructor(superRuntime, host) {
|
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
// ---------------- ToastElement
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
class ToastElement extends HTMLElement {
|
|
4
|
+
|
|
5
|
+
set type(value) {
|
|
6
|
+
if ([undefined, null].includes(value)) {
|
|
7
|
+
this.removeAttribute('type');
|
|
8
|
+
} else this.setAttribute('type', value);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
get type() { return this.getAttribute('type'); }
|
|
12
|
+
|
|
13
|
+
get contentHTML() { return ''; }
|
|
14
|
+
|
|
15
|
+
get css() { return ''; }
|
|
4
16
|
|
|
5
17
|
#childToast = null;
|
|
6
18
|
|
|
@@ -21,12 +33,6 @@ export class ToastElement extends HTMLElement {
|
|
|
21
33
|
}
|
|
22
34
|
}
|
|
23
35
|
|
|
24
|
-
connectedCallback() {
|
|
25
|
-
if (!this.popover) {
|
|
26
|
-
this.popover = 'auto';
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
36
|
render({ content, context }, childToast = null, recursion = 1) {
|
|
31
37
|
if (context && recursion > 0) {
|
|
32
38
|
const directChildToast = document.createElement(this.tagName);
|
|
@@ -54,18 +60,12 @@ export class ToastElement extends HTMLElement {
|
|
|
54
60
|
this.innerHTML = content.message;
|
|
55
61
|
}
|
|
56
62
|
|
|
57
|
-
|
|
58
|
-
if (
|
|
59
|
-
this.
|
|
60
|
-
}
|
|
63
|
+
connectedCallback() {
|
|
64
|
+
if (!this.popover) {
|
|
65
|
+
this.popover = 'auto';
|
|
66
|
+
}
|
|
61
67
|
}
|
|
62
68
|
|
|
63
|
-
get type() { return this.getAttribute('type'); }
|
|
64
|
-
|
|
65
|
-
get contentHTML() { return ''; }
|
|
66
|
-
|
|
67
|
-
get css() { return ''; }
|
|
68
|
-
|
|
69
69
|
constructor() {
|
|
70
70
|
super();
|
|
71
71
|
this.attachShadow({ mode: 'open' });
|
|
@@ -315,7 +315,7 @@ export class ToastElement extends HTMLElement {
|
|
|
315
315
|
|
|
316
316
|
// ---------------- ModalElement
|
|
317
317
|
|
|
318
|
-
|
|
318
|
+
class ModalMinmaxEvent extends Event {
|
|
319
319
|
|
|
320
320
|
#ratio;
|
|
321
321
|
get ratio() { return this.#ratio; }
|
|
@@ -326,64 +326,108 @@ export class ModalMinmaxEvent extends Event {
|
|
|
326
326
|
}
|
|
327
327
|
}
|
|
328
328
|
|
|
329
|
-
|
|
329
|
+
class ModalElement extends HTMLElement {
|
|
330
330
|
|
|
331
|
-
|
|
332
|
-
const viewElement = this.shadowRoot.querySelector('.view');
|
|
333
|
-
const beaderBoxElement = this.shadowRoot.querySelector('.header-box');
|
|
334
|
-
const beaderBarElement = this.shadowRoot.querySelector('.header-bar');
|
|
335
|
-
const footerBarElement = this.shadowRoot.querySelector('.footer-bar');
|
|
336
|
-
requestAnimationFrame(() => {
|
|
337
|
-
viewElement.style.setProperty('--header-box-height', beaderBoxElement.offsetHeight + 'px');
|
|
338
|
-
viewElement.style.setProperty('--header-bar-height', beaderBarElement.offsetHeight + 'px');
|
|
339
|
-
viewElement.style.setProperty('--footer-bar-height', footerBarElement.offsetHeight + 'px');
|
|
340
|
-
if (this.classList.contains('_container')) return;
|
|
341
|
-
viewElement.style.setProperty('--view-width', viewElement.clientWidth/* instead of offsetHeight; safari reasons */ + 'px');
|
|
342
|
-
viewElement.style.setProperty('--view-height', viewElement.clientHeight/* instead of offsetHeight; safari reasons */ + 'px');
|
|
343
|
-
});
|
|
344
|
-
}
|
|
331
|
+
#onminmaxHandler = null;
|
|
345
332
|
|
|
346
|
-
|
|
347
|
-
if (
|
|
348
|
-
this.
|
|
333
|
+
set onminmax(handler) {
|
|
334
|
+
if (this.#onminmaxHandler) {
|
|
335
|
+
this.removeEventListener('onminmax', this.#onminmaxHandler);
|
|
349
336
|
}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
if (
|
|
353
|
-
|
|
337
|
+
if (typeof handler === 'function') {
|
|
338
|
+
this.addEventListener('minmax', this.#onminmaxHandler);
|
|
339
|
+
} else if (handler !== null && handler !== undefined) {
|
|
340
|
+
throw new Error('onminmax must be null or a function');
|
|
354
341
|
}
|
|
342
|
+
this.#onminmaxHandler = handler;
|
|
343
|
+
}
|
|
355
344
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
345
|
+
get onminmax() { return this.#onminmaxHandler; }
|
|
346
|
+
|
|
347
|
+
set type(value) {
|
|
348
|
+
if ([undefined, null].includes(value)) {
|
|
349
|
+
this.removeAttribute('type');
|
|
350
|
+
} else this.setAttribute('type', value);
|
|
359
351
|
}
|
|
360
352
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
353
|
+
get type() { return this.getAttribute('type'); }
|
|
354
|
+
|
|
355
|
+
get headerBoxHTML() { return ''; }
|
|
356
|
+
|
|
357
|
+
get headerHTML() { return ''; }
|
|
358
|
+
|
|
359
|
+
get mainHTML() { return ''; }
|
|
360
|
+
|
|
361
|
+
get contentHTML() { return ''; }
|
|
362
|
+
|
|
363
|
+
get footerHTML() { return ''; }
|
|
364
|
+
|
|
365
|
+
get css() { return ''; }
|
|
366
|
+
|
|
367
|
+
#viewElement;
|
|
368
|
+
#sentinelElement;
|
|
369
|
+
#spacingElement;
|
|
370
|
+
#headerElement;
|
|
371
|
+
#headerBoxElement;
|
|
372
|
+
#footerElement;
|
|
373
|
+
|
|
374
|
+
updateScrollViewDimensions() {
|
|
375
|
+
requestAnimationFrame(() => {
|
|
376
|
+
let viewWidth, viewHeight;
|
|
377
|
+
|
|
378
|
+
const swipeDismiss = this.classList.contains('_swipe-dismiss');
|
|
379
|
+
const minmaxScroll = !!window.getComputedStyle(this).getPropertyValue('--modal-minmax-length');
|
|
380
|
+
|
|
381
|
+
if (swipeDismiss || minmaxScroll) {
|
|
382
|
+
requestAnimationFrame(() => {
|
|
383
|
+
let left = 0, top = 0;
|
|
384
|
+
if (!this.matches('._left._horz, ._top:not(._horz)')) {
|
|
385
|
+
if (this.classList.contains('_horz')) {
|
|
386
|
+
viewWidth = this.#viewElement.clientWidth/* instead of offsetHeight; safari reasons */;
|
|
387
|
+
left = viewWidth - this.#spacingElement.clientWidth;
|
|
388
|
+
} else {
|
|
389
|
+
viewHeight = this.#viewElement.clientHeight/* instead of offsetHeight; safari reasons */;
|
|
390
|
+
top = viewHeight - this.#spacingElement.clientHeight;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
if (this.#viewElement.scrollTop < top || this.#viewElement.scrollLeft < left) {
|
|
394
|
+
this.#viewElement.scrollTo({ top, left });
|
|
395
|
+
}
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
this.#viewElement.style.setProperty('--header-box-height', this.#headerBoxElement.offsetHeight + 'px');
|
|
400
|
+
this.#viewElement.style.setProperty('--header-max-height', this.#headerElement.offsetHeight + 'px');
|
|
401
|
+
this.#viewElement.style.setProperty('--footer-max-height', this.#footerElement.offsetHeight + 'px');
|
|
402
|
+
|
|
403
|
+
if (this.classList.contains('_container')) return;
|
|
404
|
+
if (viewWidth === undefined) viewWidth = this.#viewElement.clientWidth;
|
|
405
|
+
if (viewHeight === undefined) viewHeight = this.#viewElement.clientHeight;
|
|
406
|
+
|
|
407
|
+
this.#viewElement.style.setProperty('--view-width', viewWidth + 'px');
|
|
408
|
+
this.#viewElement.style.setProperty('--view-height', viewHeight + 'px');
|
|
409
|
+
});
|
|
364
410
|
}
|
|
365
411
|
|
|
366
412
|
#unbindMinmaxWorker = null;
|
|
367
413
|
|
|
368
414
|
bindMinmaxWorker() {
|
|
369
415
|
const swipeDismiss = this.classList.contains('_swipe-dismiss');
|
|
370
|
-
const minmaxEvents = this.classList.contains('_minmax');
|
|
416
|
+
const minmaxEvents = this.classList.contains('_minmax-events');
|
|
371
417
|
|
|
372
418
|
if (!swipeDismiss && !minmaxEvents) return;
|
|
373
419
|
|
|
374
|
-
const viewElement = this.shadowRoot.querySelector('.view');
|
|
375
|
-
const sentinelElement = this.shadowRoot.querySelector('.sentinel');
|
|
376
|
-
const spacingElement = viewElement.querySelector('.spacing');
|
|
377
|
-
|
|
378
420
|
const options = {
|
|
379
|
-
root: viewElement,
|
|
421
|
+
root: this.#viewElement,
|
|
380
422
|
threshold: [0, 1]
|
|
381
423
|
};
|
|
382
424
|
|
|
383
425
|
const observer = new IntersectionObserver((entries) => {
|
|
426
|
+
if (!this.#userScrolled) return;
|
|
427
|
+
|
|
384
428
|
for (const entry of entries) {
|
|
385
429
|
// Minmax events
|
|
386
|
-
if (entry.target === spacingElement) {
|
|
430
|
+
if (entry.target === this.#spacingElement) {
|
|
387
431
|
const event = new ModalMinmaxEvent(1 - entry.intersectionRatio);
|
|
388
432
|
this.dispatchEvent(event);
|
|
389
433
|
|
|
@@ -394,65 +438,80 @@ export class ModalElement extends HTMLElement {
|
|
|
394
438
|
}
|
|
395
439
|
|
|
396
440
|
// For auto-closing
|
|
397
|
-
if (entry.target === sentinelElement
|
|
441
|
+
if (entry.target === this.#sentinelElement
|
|
442
|
+
&& entry.isIntersecting
|
|
443
|
+
&& entry.intersectionRatio >= 0.8) {
|
|
398
444
|
this.hidePopover();
|
|
399
|
-
setTimeout(() => spacingElement.scrollIntoView(), 300);
|
|
400
445
|
}
|
|
401
446
|
}
|
|
402
447
|
}, options);
|
|
403
448
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
#onminmaxHandler = null;
|
|
449
|
+
setTimeout(() => {
|
|
450
|
+
if (minmaxEvents) observer.observe(this.#spacingElement);
|
|
451
|
+
if (swipeDismiss) observer.observe(this.#sentinelElement);
|
|
452
|
+
}, 200);
|
|
410
453
|
|
|
411
|
-
|
|
412
|
-
if (this.#onminmaxHandler) {
|
|
413
|
-
this.removeEventListener('onminmax', this.#onminmaxHandler);
|
|
414
|
-
}
|
|
415
|
-
if (typeof handler === 'function') {
|
|
416
|
-
this.addEventListener('minmax', this.#onminmaxHandler);
|
|
417
|
-
} else if (handler !== null && handler !== undefined) {
|
|
418
|
-
throw new Error('onminmax must be null or a function');
|
|
419
|
-
}
|
|
420
|
-
this.#onminmaxHandler = handler;
|
|
454
|
+
this.#unbindMinmaxWorker = () => observer.disconnect();
|
|
421
455
|
}
|
|
422
456
|
|
|
423
|
-
|
|
457
|
+
#userScrolled = false;
|
|
458
|
+
#unbindDimensionsWorker;
|
|
424
459
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
}
|
|
460
|
+
#bindDimensionsWorker() {
|
|
461
|
+
this.#userScrolled = false;
|
|
462
|
+
const handleUserScroll = () => this.#userScrolled = true;
|
|
463
|
+
this.#viewElement.addEventListener('scroll', handleUserScroll);
|
|
430
464
|
|
|
431
|
-
get type() { return this.getAttribute('type'); }
|
|
432
465
|
|
|
433
|
-
|
|
466
|
+
this.updateScrollViewDimensions();
|
|
467
|
+
const handleResize = () => this.updateScrollViewDimensions();
|
|
468
|
+
window.addEventListener('resize', handleResize);
|
|
434
469
|
|
|
435
|
-
|
|
470
|
+
this.#unbindDimensionsWorker?.();
|
|
471
|
+
this.#unbindDimensionsWorker = () => {
|
|
472
|
+
window.removeEventListener('resize', handleResize);
|
|
473
|
+
this.#viewElement.removeEventListener('scroll', handleUserScroll);
|
|
474
|
+
};
|
|
475
|
+
}
|
|
436
476
|
|
|
437
|
-
|
|
477
|
+
connectedCallback() {
|
|
478
|
+
if (!this.popover) {
|
|
479
|
+
this.popover = 'manual';
|
|
480
|
+
}
|
|
481
|
+
if (this.hasAttribute('open')) {
|
|
482
|
+
this.showPopover();
|
|
483
|
+
}
|
|
484
|
+
}
|
|
438
485
|
|
|
439
|
-
|
|
486
|
+
disconnectedCallback() {
|
|
487
|
+
this.#unbindDimensionsWorker?.();
|
|
488
|
+
this.#unbindDimensionsWorker = null;
|
|
489
|
+
this.#unbindMinmaxWorker?.();
|
|
490
|
+
this.#unbindMinmaxWorker = null;
|
|
491
|
+
}
|
|
440
492
|
|
|
441
|
-
get
|
|
493
|
+
static get observedAttributes() {
|
|
494
|
+
return ['class'];
|
|
495
|
+
}
|
|
442
496
|
|
|
443
|
-
|
|
497
|
+
attributeChangedCallback(name, old, _new) {
|
|
498
|
+
if (name === 'class') this.#bindDimensionsWorker();
|
|
499
|
+
}
|
|
444
500
|
|
|
445
501
|
constructor() {
|
|
446
502
|
super();
|
|
447
503
|
this.attachShadow({ mode: 'open' });
|
|
448
504
|
|
|
449
505
|
this.addEventListener('toggle', (e) => {
|
|
450
|
-
if (e.newState
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
506
|
+
if (e.newState === 'open') {
|
|
507
|
+
this.#bindDimensionsWorker();
|
|
508
|
+
this.bindMinmaxWorker();
|
|
509
|
+
} else if (e.newState === 'closed') {
|
|
510
|
+
this.#unbindDimensionsWorker?.();
|
|
511
|
+
this.#unbindDimensionsWorker = null;
|
|
512
|
+
this.#unbindMinmaxWorker?.();
|
|
513
|
+
this.#unbindMinmaxWorker = null;
|
|
514
|
+
}
|
|
456
515
|
});
|
|
457
516
|
|
|
458
517
|
this.shadowRoot.innerHTML = `
|
|
@@ -480,7 +539,7 @@ export class ModalElement extends HTMLElement {
|
|
|
480
539
|
<div class="_content" style="flex-grow: 1">
|
|
481
540
|
<slot
|
|
482
541
|
name="header"
|
|
483
|
-
onslotchange="this.closest('.view').style.setProperty('--header-
|
|
542
|
+
onslotchange="this.closest('.view').style.setProperty('--header-max-height', this.closest('header').offsetHeight + 'px');"
|
|
484
543
|
>${this.headerHTML}</slot>
|
|
485
544
|
</div>
|
|
486
545
|
</div>
|
|
@@ -489,31 +548,24 @@ export class ModalElement extends HTMLElement {
|
|
|
489
548
|
</button>
|
|
490
549
|
</div>
|
|
491
550
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
551
|
+
</header>
|
|
552
|
+
|
|
553
|
+
<div class="scrollport-anchor">
|
|
554
|
+
<div class="scrollport">
|
|
555
|
+
<div class="scrollbar-track">
|
|
556
|
+
<div class="scrollbar-thumb"></div>
|
|
497
557
|
</div>
|
|
498
558
|
</div>
|
|
499
|
-
</
|
|
559
|
+
</div>
|
|
500
560
|
|
|
501
561
|
${this.mainHTML || `<div class="main" part="main">${this.contentHTML || `<slot></slot>`
|
|
502
562
|
}</div>`}
|
|
503
563
|
|
|
504
564
|
<footer part="footer">
|
|
505
|
-
<div class="scrollport-anchor">
|
|
506
|
-
<div class="scrollport">
|
|
507
|
-
<div class="scrollbar-track">
|
|
508
|
-
<div class="scrollbar-thumb"></div>
|
|
509
|
-
</div>
|
|
510
|
-
</div>
|
|
511
|
-
</div>
|
|
512
|
-
|
|
513
565
|
<div class="footer-bar" part="footer-bar">
|
|
514
566
|
<slot
|
|
515
567
|
name="footer"
|
|
516
|
-
onslotchange="this.classList.toggle('has-slotted', !!this.assignedElements().length); this.closest('.view').style.setProperty('--footer-
|
|
568
|
+
onslotchange="this.classList.toggle('has-slotted', !!this.assignedElements().length); this.closest('.view').style.setProperty('--footer-max-height', this.closest('footer').offsetHeight + 'px');"
|
|
517
569
|
>${this.footerHTML}</slot>
|
|
518
570
|
</div>
|
|
519
571
|
</footer>
|
|
@@ -586,7 +638,6 @@ export class ModalElement extends HTMLElement {
|
|
|
586
638
|
|
|
587
639
|
--expanse-length: var(--modal-expanse-length, 0px);
|
|
588
640
|
--minmax-length: var(--modal-minmax-length, 0px);
|
|
589
|
-
--swipe-dismiss-length: var(--modal-swipe-dismiss-length, 0px);
|
|
590
641
|
|
|
591
642
|
--scrollbar-thumb-color: var(--modal-scrollbar-thumb-color, black);
|
|
592
643
|
--scrollbar-thumb-width: var(--modal-scrollbar-thumb-width, 4px);
|
|
@@ -603,6 +654,14 @@ export class ModalElement extends HTMLElement {
|
|
|
603
654
|
--entry-transform: translateY(var(--translation));
|
|
604
655
|
--exit-transform: translateY(calc(var(--translation) * var(--exit-factor)));
|
|
605
656
|
}
|
|
657
|
+
|
|
658
|
+
:host(._swipe-dismiss) .view {
|
|
659
|
+
--swipe-dismiss-length: var(--modal-swipe-dismiss-length, calc(var(--view-height) - var(--minmax-length)));
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
:host(._horz._swipe-dismiss) .view {
|
|
663
|
+
--swipe-dismiss-length: var(--modal-swipe-dismiss-length, calc(var(--view-width) - var(--minmax-length)));
|
|
664
|
+
}
|
|
606
665
|
|
|
607
666
|
/* transform reversal */
|
|
608
667
|
|
|
@@ -620,11 +679,14 @@ export class ModalElement extends HTMLElement {
|
|
|
620
679
|
/* -------- internal, dynamic props (view) -------- */
|
|
621
680
|
|
|
622
681
|
.view {
|
|
682
|
+
--header-max-height: 1.6rem;
|
|
623
683
|
--header-box-height: 0px;
|
|
624
|
-
--
|
|
625
|
-
--footer-bar-height: 0px;
|
|
684
|
+
--footer-max-height: 0px;
|
|
626
685
|
|
|
627
|
-
--
|
|
686
|
+
--header-min-height: calc(var(--header-max-height) - var(--header-box-height));
|
|
687
|
+
--footer-min-height: var(--footer-max-height);
|
|
688
|
+
|
|
689
|
+
--view-inner-height: calc(var(--view-height) - var(--header-min-height) - var(--footer-min-height));
|
|
628
690
|
--total-minmax-length: calc(var(--minmax-length) + var(--swipe-dismiss-length));
|
|
629
691
|
|
|
630
692
|
--y-scroll-effect-exclude: var(--total-minmax-length);
|
|
@@ -645,6 +707,11 @@ export class ModalElement extends HTMLElement {
|
|
|
645
707
|
--view-width: 100cqw;
|
|
646
708
|
}
|
|
647
709
|
|
|
710
|
+
:host(._container._horz) .view {
|
|
711
|
+
--view-height: 100cqh;
|
|
712
|
+
--view-width: calc(100cqw - var(--expanse-length));
|
|
713
|
+
}
|
|
714
|
+
|
|
648
715
|
/* transform reversal */
|
|
649
716
|
|
|
650
717
|
:host(:is(._top:not(._horz), ._left._horz)) .view {
|
|
@@ -721,9 +788,8 @@ export class ModalElement extends HTMLElement {
|
|
|
721
788
|
|
|
722
789
|
/* flex orientation */
|
|
723
790
|
|
|
724
|
-
:host
|
|
791
|
+
:host,
|
|
725
792
|
.view {
|
|
726
|
-
display: flex;
|
|
727
793
|
flex-direction: column;
|
|
728
794
|
align-items: stretch;
|
|
729
795
|
}
|
|
@@ -745,7 +811,8 @@ export class ModalElement extends HTMLElement {
|
|
|
745
811
|
/* spacing */
|
|
746
812
|
|
|
747
813
|
:host>.spacing,
|
|
748
|
-
.view>.spacing
|
|
814
|
+
.view>.spacing,
|
|
815
|
+
.view>.sentinel {
|
|
749
816
|
position: relative;
|
|
750
817
|
display: block;
|
|
751
818
|
flex-shrink: 0;
|
|
@@ -766,17 +833,15 @@ export class ModalElement extends HTMLElement {
|
|
|
766
833
|
:host(:not(._horz)) .view>.spacing { height: var(--minmax-length); }
|
|
767
834
|
:host(._horz) .view>.spacing { width: var(--minmax-length); }
|
|
768
835
|
|
|
769
|
-
:host(:not(.
|
|
770
|
-
:host(.
|
|
771
|
-
|
|
772
|
-
:host(._horz:not(._left)) .view>.spacing { margin-left: var(--swipe-dismiss-length); }
|
|
773
|
-
:host(._horz._left) .view>.spacing { margin-right: var(--swipe-dismiss-length); }
|
|
836
|
+
:host(:not(._horz)) .view>.sentinel { height: var(--swipe-dismiss-length); }
|
|
837
|
+
:host(._horz) .view>.sentinel { width: var(--swipe-dismiss-length); }
|
|
774
838
|
|
|
775
839
|
/* ----------- */
|
|
776
840
|
|
|
777
841
|
.view {
|
|
778
842
|
position: relative;
|
|
779
843
|
flex-grow: 1;
|
|
844
|
+
display: flex;
|
|
780
845
|
|
|
781
846
|
pointer-events: none;
|
|
782
847
|
|
|
@@ -830,16 +895,13 @@ export class ModalElement extends HTMLElement {
|
|
|
830
895
|
position: relative;
|
|
831
896
|
flex-grow: 1;
|
|
832
897
|
|
|
833
|
-
min-height: 100%;
|
|
834
|
-
min-width: 100%;
|
|
835
|
-
|
|
836
898
|
pointer-events: auto;
|
|
837
899
|
|
|
838
900
|
display: flex;
|
|
839
901
|
flex-direction: column;
|
|
840
902
|
}
|
|
841
903
|
|
|
842
|
-
:host(._swipe-dismiss) .container {
|
|
904
|
+
:host(._swipe-dismiss-fadeout) .container {
|
|
843
905
|
animation-timing-function: linear;
|
|
844
906
|
animation-fill-mode: both;
|
|
845
907
|
animation-name: appear;
|
|
@@ -847,7 +909,7 @@ export class ModalElement extends HTMLElement {
|
|
|
847
909
|
animation-range: 0 var(--swipe-dismiss-length);
|
|
848
910
|
}
|
|
849
911
|
|
|
850
|
-
:host(._swipe-dismiss:is(._top:not(._horz), ._left._horz)) .container {
|
|
912
|
+
:host(._swipe-dismiss-fadeout:is(._top:not(._horz), ._left._horz)) .container {
|
|
851
913
|
animation-name: disappear;
|
|
852
914
|
animation-range: calc(100% - var(--swipe-dismiss-length)) 100%;
|
|
853
915
|
}
|
|
@@ -857,7 +919,7 @@ export class ModalElement extends HTMLElement {
|
|
|
857
919
|
header {
|
|
858
920
|
position: sticky;
|
|
859
921
|
top: calc(var(--header-box-height) * -1);
|
|
860
|
-
z-index:
|
|
922
|
+
z-index: 2;
|
|
861
923
|
|
|
862
924
|
display: flex;
|
|
863
925
|
flex-direction: column;
|
|
@@ -867,6 +929,8 @@ export class ModalElement extends HTMLElement {
|
|
|
867
929
|
|
|
868
930
|
border-top-left-radius: var(--radius-top-left);
|
|
869
931
|
border-top-right-radius: var(--radius-top-right);
|
|
932
|
+
|
|
933
|
+
order: 1;
|
|
870
934
|
}
|
|
871
935
|
|
|
872
936
|
:host(:not(._horz)) header {
|
|
@@ -959,6 +1023,8 @@ export class ModalElement extends HTMLElement {
|
|
|
959
1023
|
|
|
960
1024
|
color: var(--footer-color-default);
|
|
961
1025
|
background: var(--footer-background);
|
|
1026
|
+
|
|
1027
|
+
order: 5;
|
|
962
1028
|
}
|
|
963
1029
|
|
|
964
1030
|
:host([type="info"]) footer {
|
|
@@ -983,23 +1049,26 @@ export class ModalElement extends HTMLElement {
|
|
|
983
1049
|
|
|
984
1050
|
/* ----------- */
|
|
985
1051
|
|
|
986
|
-
|
|
1052
|
+
.view {
|
|
987
1053
|
scroll-snap-type: y mandatory;
|
|
988
1054
|
}
|
|
989
1055
|
|
|
990
|
-
:host(._horz
|
|
1056
|
+
:host(._horz) .view {
|
|
991
1057
|
scroll-snap-type: x mandatory;
|
|
992
1058
|
}
|
|
993
1059
|
|
|
994
|
-
.view>.spacing
|
|
1060
|
+
.view>.spacing,
|
|
1061
|
+
.view>.sentinel {
|
|
995
1062
|
scroll-snap-align: var(--scroll-snap-start);
|
|
996
1063
|
}
|
|
997
1064
|
|
|
998
1065
|
.main {
|
|
999
1066
|
flex-grow: 1;
|
|
1000
|
-
scroll-margin-top: var(--header-
|
|
1001
|
-
scroll-margin-bottom: var(--footer-
|
|
1067
|
+
scroll-margin-top: var(--header-min-height);
|
|
1068
|
+
scroll-margin-bottom: var(--footer-min-height);
|
|
1002
1069
|
scroll-snap-align: var(--scroll-snap-start);
|
|
1070
|
+
|
|
1071
|
+
order: 3;
|
|
1003
1072
|
}
|
|
1004
1073
|
|
|
1005
1074
|
:host(:is(._top, ._left._horz)) .main {
|
|
@@ -1021,42 +1090,108 @@ export class ModalElement extends HTMLElement {
|
|
|
1021
1090
|
/* ----------- */
|
|
1022
1091
|
|
|
1023
1092
|
.scrollport-anchor {
|
|
1024
|
-
|
|
1093
|
+
order: 2;
|
|
1094
|
+
|
|
1095
|
+
position: sticky;
|
|
1096
|
+
top: var(--header-min-height);
|
|
1097
|
+
bottom: var(--footer-min-height);
|
|
1098
|
+
left: 0;
|
|
1099
|
+
right: 0;
|
|
1100
|
+
display: flex;
|
|
1101
|
+
flex-direction: column;
|
|
1102
|
+
|
|
1025
1103
|
height: 0;
|
|
1104
|
+
width: var(--view-width);
|
|
1026
1105
|
}
|
|
1027
1106
|
|
|
1028
|
-
:host(:
|
|
1029
|
-
|
|
1030
|
-
|
|
1107
|
+
:host(:is(._left._horz, ._top:not(._horz))) .scrollport-anchor {
|
|
1108
|
+
justify-content: end;
|
|
1109
|
+
order: 4;
|
|
1031
1110
|
}
|
|
1032
1111
|
|
|
1033
1112
|
.scrollport {
|
|
1034
|
-
position:
|
|
1035
|
-
|
|
1036
|
-
left: 0;
|
|
1037
|
-
right: 0;
|
|
1038
|
-
|
|
1039
|
-
container-type: size;
|
|
1113
|
+
position: relative;
|
|
1114
|
+
|
|
1040
1115
|
height: var(--view-inner-height);
|
|
1041
1116
|
width: var(--view-width);
|
|
1117
|
+
flex-shrink: 0;
|
|
1042
1118
|
|
|
1043
1119
|
pointer-events: none;
|
|
1044
1120
|
}
|
|
1045
1121
|
|
|
1046
|
-
|
|
1122
|
+
:host(._top:not(._horz)) .scrollport {
|
|
1123
|
+
height: calc(var(--view-inner-height) - var(--header-box-height));
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
/* -- scroll unfold -- */
|
|
1127
|
+
|
|
1128
|
+
:host(._scroll-unfold) .scrollport {
|
|
1129
|
+
display: flex;
|
|
1130
|
+
flex-direction: column;
|
|
1131
|
+
justify-content: space-between;
|
|
1132
|
+
align-items: stretch;
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
:host(._scroll-unfold._horz) .scrollport {
|
|
1136
|
+
flex-direction: row;
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
:host(._scroll-unfold) .scrollport::before,
|
|
1140
|
+
:host(._scroll-unfold) .scrollport::after {
|
|
1141
|
+
position: sticky;
|
|
1142
|
+
display: block;
|
|
1143
|
+
content: "";
|
|
1144
|
+
opacity: 0;
|
|
1145
|
+
|
|
1146
|
+
background: var(--background);
|
|
1147
|
+
|
|
1148
|
+
mask-repeat: no-repeat;
|
|
1149
|
+
mask-size: 100% 100%;
|
|
1150
|
+
|
|
1151
|
+
animation-timing-function: linear;
|
|
1152
|
+
animation-fill-mode: forwards;
|
|
1153
|
+
animation-name: appear;
|
|
1154
|
+
animation-timeline: --view-scroll;
|
|
1155
|
+
|
|
1156
|
+
animation-range: var(--scrollbar-progress-range);
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
:host(._scroll-unfold:not(._horz)) .scrollport::before,
|
|
1160
|
+
:host(._scroll-unfold:not(._horz)) .scrollport::after {
|
|
1161
|
+
top: var(--header-min-height);
|
|
1162
|
+
height: 25%;
|
|
1163
|
+
|
|
1164
|
+
mask-image: linear-gradient(to bottom, black 0%, transparent 100%);
|
|
1165
|
+
-webkit-mask-image: linear-gradient(to bottom, black 0%, transparent 100%);
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
:host(._scroll-unfold:not(._horz)) .scrollport::after {
|
|
1169
|
+
bottom: var(--footer-min-height);
|
|
1047
1170
|
top: auto;
|
|
1048
|
-
|
|
1049
|
-
|
|
1171
|
+
opacity: 1;
|
|
1172
|
+
animation-name: disappear;
|
|
1173
|
+
transform: scaleY(-1);
|
|
1050
1174
|
}
|
|
1051
1175
|
|
|
1052
|
-
:host(.
|
|
1053
|
-
|
|
1176
|
+
:host(._scroll-unfold._horz) .scrollport::before,
|
|
1177
|
+
:host(._scroll-unfold._horz) .scrollport::after {
|
|
1178
|
+
left: 0;
|
|
1179
|
+
width: 25%;
|
|
1180
|
+
|
|
1181
|
+
mask-image: linear-gradient(to right, black 0%, transparent 100%);
|
|
1182
|
+
-webkit-mask-image: linear-gradient(to right, black 0%, transparent 100%);
|
|
1054
1183
|
}
|
|
1055
1184
|
|
|
1056
|
-
:host(.
|
|
1057
|
-
|
|
1185
|
+
:host(._scroll-unfold._horz) .scrollport::after {
|
|
1186
|
+
right: 0;
|
|
1187
|
+
left: auto;
|
|
1188
|
+
opacity: 1;
|
|
1189
|
+
animation-name: disappear;
|
|
1190
|
+
transform: scaleX(-1);
|
|
1058
1191
|
}
|
|
1059
1192
|
|
|
1193
|
+
/* -- scrollbar -- */
|
|
1194
|
+
|
|
1060
1195
|
:host(._scrollbars) .scrollbar-track {
|
|
1061
1196
|
position: absolute;
|
|
1062
1197
|
display: block;
|
|
@@ -1067,6 +1202,8 @@ export class ModalElement extends HTMLElement {
|
|
|
1067
1202
|
right: 0;
|
|
1068
1203
|
padding: 6px;
|
|
1069
1204
|
|
|
1205
|
+
container-type: size;
|
|
1206
|
+
|
|
1070
1207
|
opacity: 0;
|
|
1071
1208
|
|
|
1072
1209
|
animation: appear linear;
|
|
@@ -1101,6 +1238,7 @@ export class ModalElement extends HTMLElement {
|
|
|
1101
1238
|
:host(._scrollbars._horz) .scrollbar-thumb {
|
|
1102
1239
|
height: var(--scrollbar-thumb-width);
|
|
1103
1240
|
width: var(--scrollbar-thumb-height);
|
|
1241
|
+
|
|
1104
1242
|
--scrollbar-thumb-start: translateX(0);
|
|
1105
1243
|
--scrollbar-thumb-length: translateX(calc(100cqw - 100%));
|
|
1106
1244
|
}
|
|
@@ -1182,7 +1320,7 @@ export class ModalElement extends HTMLElement {
|
|
|
1182
1320
|
|
|
1183
1321
|
.main {
|
|
1184
1322
|
color: var(--color-default);
|
|
1185
|
-
background
|
|
1323
|
+
background: var(--background);
|
|
1186
1324
|
}
|
|
1187
1325
|
|
|
1188
1326
|
.view:not(:has(footer slot:is(.has-slotted, :not(:empty)))) .main {
|
|
@@ -1224,12 +1362,19 @@ export class ModalElement extends HTMLElement {
|
|
|
1224
1362
|
${this.css}
|
|
1225
1363
|
</style>
|
|
1226
1364
|
`;
|
|
1365
|
+
|
|
1366
|
+
this.#viewElement = this.shadowRoot.querySelector('.view');
|
|
1367
|
+
this.#sentinelElement = this.#viewElement.querySelector('.sentinel');
|
|
1368
|
+
this.#spacingElement = this.#viewElement.querySelector('.spacing');
|
|
1369
|
+
this.#headerElement = this.#viewElement.querySelector('header');
|
|
1370
|
+
this.#headerBoxElement = this.#viewElement.querySelector('.header-box');
|
|
1371
|
+
this.#footerElement = this.#viewElement.querySelector('footer');
|
|
1227
1372
|
}
|
|
1228
1373
|
}
|
|
1229
1374
|
|
|
1230
1375
|
// ---------------- DialogElement
|
|
1231
1376
|
|
|
1232
|
-
|
|
1377
|
+
class DialogResponseEvent extends Event {
|
|
1233
1378
|
|
|
1234
1379
|
#data;
|
|
1235
1380
|
get data() { return this.#data; }
|
|
@@ -1240,7 +1385,7 @@ export class DialogResponseEvent extends Event {
|
|
|
1240
1385
|
}
|
|
1241
1386
|
}
|
|
1242
1387
|
|
|
1243
|
-
|
|
1388
|
+
class DialogElement extends ModalElement {
|
|
1244
1389
|
|
|
1245
1390
|
constructor() {
|
|
1246
1391
|
super();
|
|
@@ -1333,6 +1478,7 @@ export class DialogElement extends ModalElement {
|
|
|
1333
1478
|
}
|
|
1334
1479
|
|
|
1335
1480
|
.main {
|
|
1481
|
+
flex-shrink: 0;
|
|
1336
1482
|
display: flex;
|
|
1337
1483
|
flex-direction: column;
|
|
1338
1484
|
gap: 1rem;
|
|
@@ -1391,7 +1537,7 @@ export class DialogElement extends ModalElement {
|
|
|
1391
1537
|
|
|
1392
1538
|
// ---------------- PromptElement
|
|
1393
1539
|
|
|
1394
|
-
|
|
1540
|
+
class PromptElement extends DialogElement {
|
|
1395
1541
|
|
|
1396
1542
|
static get observedAttributes() {
|
|
1397
1543
|
return ['value', 'placeholder'].concat(super.observedAttributes || []);
|
|
@@ -1400,8 +1546,8 @@ export class PromptElement extends DialogElement {
|
|
|
1400
1546
|
attributeChangedCallback(name, old, _new) {
|
|
1401
1547
|
super.attributeChangedCallback?.(...arguments);
|
|
1402
1548
|
const input = this.shadowRoot.querySelector('input');
|
|
1403
|
-
if (name === 'value')
|
|
1404
|
-
if (name === 'placeholder')
|
|
1549
|
+
if (name === 'value') input.value = _new;
|
|
1550
|
+
if (name === 'placeholder') input.placeholder = _new;
|
|
1405
1551
|
}
|
|
1406
1552
|
|
|
1407
1553
|
set placeholder(value) {
|
|
@@ -1470,7 +1616,7 @@ export class PromptElement extends DialogElement {
|
|
|
1470
1616
|
|
|
1471
1617
|
// ---------------- ConfirmElement
|
|
1472
1618
|
|
|
1473
|
-
|
|
1619
|
+
class ConfirmElement extends DialogElement {
|
|
1474
1620
|
get actionTexts() { return ['No', 'Yes']; }
|
|
1475
1621
|
|
|
1476
1622
|
respondWith(response) { super.respondWith(!!response); }
|
|
@@ -1480,7 +1626,7 @@ export class ConfirmElement extends DialogElement {
|
|
|
1480
1626
|
|
|
1481
1627
|
// ---------------- AlertElement
|
|
1482
1628
|
|
|
1483
|
-
|
|
1629
|
+
class AlertElement extends DialogElement {
|
|
1484
1630
|
get actionTexts() { return ['', 'Got it']; }
|
|
1485
1631
|
}
|
|
1486
1632
|
|