@arsedizioni/ars-utils 22.5.3 → 22.5.5
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/README.md +13 -5
- package/fesm2022/arsedizioni-ars-utils-ui.dialogs.mjs +6 -1
- package/fesm2022/arsedizioni-ars-utils-ui.dialogs.mjs.map +1 -1
- package/fesm2022/arsedizioni-ars-utils-ui.shell.mjs +172 -13
- package/fesm2022/arsedizioni-ars-utils-ui.shell.mjs.map +1 -1
- package/package.json +1 -1
- package/styles/ui.colors.scss +118 -88
- package/types/arsedizioni-ars-utils-ui.shell.d.ts +98 -6
- package/clipper.ui/ui/clipper.scss +0 -564
- package/fesm2022/arsedizioni-ars-utils-help.mjs +0 -530
- package/fesm2022/arsedizioni-ars-utils-help.mjs.map +0 -1
- package/fesm2022/arsedizioni-ars-utils-support.ui.mjs +0 -262
- package/fesm2022/arsedizioni-ars-utils-support.ui.mjs.map +0 -1
- package/fesm2022/arsedizioni-ars-utils-tinymce.mjs +0 -246
- package/fesm2022/arsedizioni-ars-utils-tinymce.mjs.map +0 -1
- package/help/package.json +0 -4
- package/support.ui/package.json +0 -4
- package/tinymce/README.md +0 -12
- package/tinymce/langs/it.js +0 -466
- package/tinymce/package.json +0 -4
- package/types/arsedizioni-ars-utils-help.d.ts +0 -220
- package/types/arsedizioni-ars-utils-support.ui.d.ts +0 -73
- package/types/arsedizioni-ars-utils-tinymce.d.ts +0 -63
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { timer, Subject } from 'rxjs';
|
|
2
2
|
import { tap, first } from 'rxjs/operators';
|
|
3
3
|
import * as i0 from '@angular/core';
|
|
4
|
-
import { signal, computed, ChangeDetectionStrategy, Component, output, viewChild, afterNextRender,
|
|
5
|
-
import { SafeHtmlPipe, SystemUtils } from '@arsedizioni/ars-utils/core';
|
|
4
|
+
import { inject, signal, computed, ChangeDetectionStrategy, Component, output, viewChild, afterNextRender, DestroyRef, HostListener, ApplicationRef, EnvironmentInjector, createComponent, Service } from '@angular/core';
|
|
5
|
+
import { ThemeService, SafeHtmlPipe, SystemUtils } from '@arsedizioni/ars-utils/core';
|
|
6
|
+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Shows the busy overlay only if the operation is still running after a debounce delay, so that
|
|
@@ -47,7 +48,32 @@ const SPINNER_CIRCUMFERENCE = 2 * Math.PI * 31.5;
|
|
|
47
48
|
* class on the host, never a `createComponent`, so the 40-odd calls a screen costs nothing.
|
|
48
49
|
*/
|
|
49
50
|
class ShellBusyComponent {
|
|
51
|
+
/**
|
|
52
|
+
* Keeps {@link dark} in step with the service, now and at every later change.
|
|
53
|
+
*
|
|
54
|
+
* `'auto'` is resolved through `getTheme()`, which is the only thing that knows what the
|
|
55
|
+
* operating system is currently on.
|
|
56
|
+
* @returns void
|
|
57
|
+
*/
|
|
58
|
+
followTheme() {
|
|
59
|
+
const resolve = (theme) => (theme === 'auto' ? this.themeService.getTheme() : theme) === 'dark';
|
|
60
|
+
this.dark.set(resolve(this.themeService.getTheme()));
|
|
61
|
+
this.themeService.changed
|
|
62
|
+
.pipe(takeUntilDestroyed())
|
|
63
|
+
.subscribe(theme => this.dark.set(resolve(theme)));
|
|
64
|
+
}
|
|
50
65
|
constructor() {
|
|
66
|
+
this.themeService = inject(ThemeService);
|
|
67
|
+
/**
|
|
68
|
+
* True when the panel must paint itself dark.
|
|
69
|
+
*
|
|
70
|
+
* Read from {@link ThemeService} and not from `prefers-color-scheme`, because the two do not
|
|
71
|
+
* always agree: the service is what the user chose, and it may be forcing dark on a machine
|
|
72
|
+
* that is on light. Following the OS instead would leave this panel the only light thing on a
|
|
73
|
+
* dark screen.
|
|
74
|
+
*/
|
|
75
|
+
this.dark = signal(false, /* @ts-ignore */
|
|
76
|
+
...(ngDevMode ? [{ debugName: "dark" }] : /* istanbul ignore next */ []));
|
|
51
77
|
/** Whether the overlay is on screen. Hidden with `display: none`, so it costs no layout. */
|
|
52
78
|
this.visible = signal(false, /* @ts-ignore */
|
|
53
79
|
...(ngDevMode ? [{ debugName: "visible" }] : /* istanbul ignore next */ []));
|
|
@@ -68,6 +94,7 @@ class ShellBusyComponent {
|
|
|
68
94
|
/** Length of the spinner arc still to be drawn, derived from {@link progress}. */
|
|
69
95
|
this.dashOffset = computed(() => SPINNER_CIRCUMFERENCE * (1 - Math.min(Math.max(this.progress(), 0), 100) / 100), /* @ts-ignore */
|
|
70
96
|
...(ngDevMode ? [{ debugName: "dashOffset" }] : /* istanbul ignore next */ []));
|
|
97
|
+
this.followTheme();
|
|
71
98
|
}
|
|
72
99
|
/**
|
|
73
100
|
* Updates the overlay state.
|
|
@@ -90,12 +117,12 @@ class ShellBusyComponent {
|
|
|
90
117
|
this.progressMode.set(progress > 0 ? 'determinate' : progressMode);
|
|
91
118
|
}
|
|
92
119
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: ShellBusyComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
93
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: ShellBusyComponent, isStandalone: true, selector: "ars-shell-busy", host: { properties: { "class.ars-hidden": "!visible()" } }, ngImport: i0, template: "<div class=\"scrim\"></div>\n\n@if (type() === 'hourglass' || type() === 'wait') {\n<div class=\"bare\">\n <svg class=\"hourglass\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n <path d=\"M6 2h12v6l-4 4 4 4v6H6v-6l4-4-4-4zm2 2v3.2l4 4 4-4V4zm0 16h8v-3.2l-4-4-4 4z\" />\n </svg>\n @if (type() === 'hourglass' && message()) {\n <div class=\"message\" [innerHTML]=\"message() | safeHtml\"></div>\n }\n</div>\n} @else {\n<div class=\"panel\" role=\"status\" aria-live=\"polite\">\n @if (message()) {\n <div class=\"message\" [innerHTML]=\"message() | safeHtml\"></div>\n }\n\n @if (type() === 'bar') {\n <div class=\"bar\" [class.indeterminate]=\"progressMode() === 'indeterminate'\">\n <div class=\"bar-fill\" [style.width.%]=\"progressMode() === 'determinate' ? progress() : null\"></div>\n </div>\n } @else if (type() === 'spinner') {\n <svg class=\"spinner\" [class.indeterminate]=\"progressMode() === 'indeterminate'\" viewBox=\"0 0 70 70\"\n aria-hidden=\"true\">\n <circle class=\"track\" cx=\"35\" cy=\"35\" r=\"31.5\"></circle>\n <circle class=\"arc\" cx=\"35\" cy=\"35\" r=\"31.5\" [style.stroke-dasharray]=\"circumference\"\n [style.stroke-dashoffset]=\"progressMode() === 'determinate' ? dashOffset() : null\"></circle>\n </svg>\n }\n\n @if (progress() > 0) {\n <div class=\"percent\"><b>{{ progress() }}%</b></div>\n }\n</div>\n}\n", styles: [":host{position:fixed;inset:0;z-index:var(--ars-busy-z-index, 1090);display:flex;align-items:center;justify-content:center;font-family:var(--mat-sys-body-medium-font, Roboto, sans-serif);color:var(--ars-color-text, var(--mat-sys-on-surface, #
|
|
120
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: ShellBusyComponent, isStandalone: true, selector: "ars-shell-busy", host: { properties: { "class.ars-hidden": "!visible()", "class.ars-dark": "dark()" } }, ngImport: i0, template: "<div class=\"scrim\"></div>\n\n@if (type() === 'hourglass' || type() === 'wait') {\n<div class=\"bare\">\n <svg class=\"hourglass\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n <path d=\"M6 2h12v6l-4 4 4 4v6H6v-6l4-4-4-4zm2 2v3.2l4 4 4-4V4zm0 16h8v-3.2l-4-4-4 4z\" />\n </svg>\n @if (type() === 'hourglass' && message()) {\n <div class=\"message\" [innerHTML]=\"message() | safeHtml\"></div>\n }\n</div>\n} @else {\n<div class=\"panel\" role=\"status\" aria-live=\"polite\">\n @if (message()) {\n <div class=\"message\" [innerHTML]=\"message() | safeHtml\"></div>\n }\n\n @if (type() === 'bar') {\n <div class=\"bar\" [class.indeterminate]=\"progressMode() === 'indeterminate'\">\n <div class=\"bar-fill\" [style.width.%]=\"progressMode() === 'determinate' ? progress() : null\"></div>\n </div>\n } @else if (type() === 'spinner') {\n <svg class=\"spinner\" [class.indeterminate]=\"progressMode() === 'indeterminate'\" viewBox=\"0 0 70 70\"\n aria-hidden=\"true\">\n <circle class=\"track\" cx=\"35\" cy=\"35\" r=\"31.5\"></circle>\n <circle class=\"arc\" cx=\"35\" cy=\"35\" r=\"31.5\" [style.stroke-dasharray]=\"circumference\"\n [style.stroke-dashoffset]=\"progressMode() === 'determinate' ? dashOffset() : null\"></circle>\n </svg>\n }\n\n @if (progress() > 0) {\n <div class=\"percent\"><b>{{ progress() }}%</b></div>\n }\n</div>\n}\n", styles: [":host{--_surface: var(--ars-dialog-background-color, var(--mat-sys-surface-container-high, #e6eae8));--_on-surface: var(--ars-color-text, var(--mat-sys-on-surface, #191c1b));--_scrim: var(--ars-busy-scrim-color, rgba(0, 0, 0, .42));--_primary: var(--ars-color-primary, var(--mat-sys-primary, #00a293));--_track: var(--ars-progress-track-color, var(--mat-sys-surface-container-highest, #d5dbd8));--_bare: var(--ars-busy-bare-color, #ffffff);color-scheme:light;margin:0;border:0;padding:0;background:none;color:inherit;overflow:visible;width:100%;height:100%;position:fixed;inset:0;z-index:var(--ars-busy-z-index, 1090);display:flex;align-items:center;justify-content:center;font-family:var(--mat-sys-body-medium-font, Roboto, sans-serif);color:var(--_on-surface)}:host(.ars-dark){--_surface: var(--ars-dialog-background-color, var(--mat-sys-surface-container-high, #282a29));--_on-surface: var(--ars-color-text, var(--mat-sys-on-surface, #e0e2e5));--_scrim: var(--ars-busy-scrim-color, rgba(0, 0, 0, .6));--_primary: var(--ars-color-primary, var(--mat-sys-primary, #53dbc9));--_track: var(--ars-progress-track-color, var(--mat-sys-surface-container-highest, #3a3d3c));color-scheme:dark}:host::backdrop{display:none}:host(.ars-hidden){display:none}.scrim{position:absolute;inset:0;background:var(--_scrim);-webkit-backdrop-filter:blur(1px);backdrop-filter:blur(1px)}.panel{position:relative;min-width:250px;max-width:calc(100% - 24px);margin:12px;padding:24px;border-radius:var(--ars-dialog-radius, 24px);background:var(--_surface);box-shadow:var(--ars-dialog-elevation, 0 4px 8px 0 rgba(0, 0, 0, .2), 0 6px 20px 0 rgba(0, 0, 0, .19));text-align:center}.bare{position:relative;display:flex;flex-direction:column;align-items:center;color:var(--_bare)}.message{font-size:larger;font-weight:600;padding-bottom:15px;text-align:center}.bare .message{padding-top:15px;padding-bottom:0}.percent{padding-top:8px;font-size:smaller}.bar{position:relative;max-width:350px;height:4px;margin:15px auto 0;overflow:hidden;border-radius:2px;background:var(--_track)}.bar-fill{position:absolute;inset:0 auto 0 0;width:0;border-radius:inherit;background:var(--_primary);transition:width .12s linear}.bar.indeterminate .bar-fill{width:40%;transition:none;animation:ars-busy-bar 1.6s cubic-bezier(.65,.02,.35,1) infinite}@keyframes ars-busy-bar{0%{left:-40%}to{left:100%}}.spinner{display:block;width:70px;height:70px;margin:15px auto 0;transform:rotate(-90deg)}.spinner circle{fill:none;stroke-width:7;stroke-linecap:round}.spinner .track{stroke:var(--_track)}.spinner .arc{stroke:var(--_primary);transition:stroke-dashoffset .12s linear}.spinner.indeterminate{animation:ars-busy-rotate 1.4s linear infinite}.spinner.indeterminate .arc{transition:none;stroke-dashoffset:0;animation:ars-busy-dash 1.4s ease-in-out infinite}@keyframes ars-busy-rotate{0%{transform:rotate(-90deg)}to{transform:rotate(270deg)}}@keyframes ars-busy-dash{0%{stroke-dasharray:15 183}50%{stroke-dasharray:145 53}to{stroke-dasharray:15 183}}.hourglass{width:80px;height:80px;fill:currentColor;animation:ars-busy-hourglass 3s linear infinite}@keyframes ars-busy-hourglass{0%{transform:rotate(360deg)}to{transform:rotate(0)}}@media(prefers-reduced-motion:reduce){.bar.indeterminate .bar-fill,.spinner.indeterminate,.spinner.indeterminate .arc,.hourglass{animation-duration:4s}}\n"], dependencies: [{ kind: "pipe", type: SafeHtmlPipe, name: "safeHtml" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
94
121
|
}
|
|
95
122
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: ShellBusyComponent, decorators: [{
|
|
96
123
|
type: Component,
|
|
97
|
-
args: [{ selector: 'ars-shell-busy', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [SafeHtmlPipe], host: { '[class.ars-hidden]': '!visible()' }, template: "<div class=\"scrim\"></div>\n\n@if (type() === 'hourglass' || type() === 'wait') {\n<div class=\"bare\">\n <svg class=\"hourglass\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n <path d=\"M6 2h12v6l-4 4 4 4v6H6v-6l4-4-4-4zm2 2v3.2l4 4 4-4V4zm0 16h8v-3.2l-4-4-4 4z\" />\n </svg>\n @if (type() === 'hourglass' && message()) {\n <div class=\"message\" [innerHTML]=\"message() | safeHtml\"></div>\n }\n</div>\n} @else {\n<div class=\"panel\" role=\"status\" aria-live=\"polite\">\n @if (message()) {\n <div class=\"message\" [innerHTML]=\"message() | safeHtml\"></div>\n }\n\n @if (type() === 'bar') {\n <div class=\"bar\" [class.indeterminate]=\"progressMode() === 'indeterminate'\">\n <div class=\"bar-fill\" [style.width.%]=\"progressMode() === 'determinate' ? progress() : null\"></div>\n </div>\n } @else if (type() === 'spinner') {\n <svg class=\"spinner\" [class.indeterminate]=\"progressMode() === 'indeterminate'\" viewBox=\"0 0 70 70\"\n aria-hidden=\"true\">\n <circle class=\"track\" cx=\"35\" cy=\"35\" r=\"31.5\"></circle>\n <circle class=\"arc\" cx=\"35\" cy=\"35\" r=\"31.5\" [style.stroke-dasharray]=\"circumference\"\n [style.stroke-dashoffset]=\"progressMode() === 'determinate' ? dashOffset() : null\"></circle>\n </svg>\n }\n\n @if (progress() > 0) {\n <div class=\"percent\"><b>{{ progress() }}%</b></div>\n }\n</div>\n}\n", styles: [":host{position:fixed;inset:0;z-index:var(--ars-busy-z-index, 1090);display:flex;align-items:center;justify-content:center;font-family:var(--mat-sys-body-medium-font, Roboto, sans-serif);color:var(--ars-color-text, var(--mat-sys-on-surface, #
|
|
98
|
-
}] });
|
|
124
|
+
args: [{ selector: 'ars-shell-busy', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [SafeHtmlPipe], host: { '[class.ars-hidden]': '!visible()', '[class.ars-dark]': 'dark()' }, template: "<div class=\"scrim\"></div>\n\n@if (type() === 'hourglass' || type() === 'wait') {\n<div class=\"bare\">\n <svg class=\"hourglass\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n <path d=\"M6 2h12v6l-4 4 4 4v6H6v-6l4-4-4-4zm2 2v3.2l4 4 4-4V4zm0 16h8v-3.2l-4-4-4 4z\" />\n </svg>\n @if (type() === 'hourglass' && message()) {\n <div class=\"message\" [innerHTML]=\"message() | safeHtml\"></div>\n }\n</div>\n} @else {\n<div class=\"panel\" role=\"status\" aria-live=\"polite\">\n @if (message()) {\n <div class=\"message\" [innerHTML]=\"message() | safeHtml\"></div>\n }\n\n @if (type() === 'bar') {\n <div class=\"bar\" [class.indeterminate]=\"progressMode() === 'indeterminate'\">\n <div class=\"bar-fill\" [style.width.%]=\"progressMode() === 'determinate' ? progress() : null\"></div>\n </div>\n } @else if (type() === 'spinner') {\n <svg class=\"spinner\" [class.indeterminate]=\"progressMode() === 'indeterminate'\" viewBox=\"0 0 70 70\"\n aria-hidden=\"true\">\n <circle class=\"track\" cx=\"35\" cy=\"35\" r=\"31.5\"></circle>\n <circle class=\"arc\" cx=\"35\" cy=\"35\" r=\"31.5\" [style.stroke-dasharray]=\"circumference\"\n [style.stroke-dashoffset]=\"progressMode() === 'determinate' ? dashOffset() : null\"></circle>\n </svg>\n }\n\n @if (progress() > 0) {\n <div class=\"percent\"><b>{{ progress() }}%</b></div>\n }\n</div>\n}\n", styles: [":host{--_surface: var(--ars-dialog-background-color, var(--mat-sys-surface-container-high, #e6eae8));--_on-surface: var(--ars-color-text, var(--mat-sys-on-surface, #191c1b));--_scrim: var(--ars-busy-scrim-color, rgba(0, 0, 0, .42));--_primary: var(--ars-color-primary, var(--mat-sys-primary, #00a293));--_track: var(--ars-progress-track-color, var(--mat-sys-surface-container-highest, #d5dbd8));--_bare: var(--ars-busy-bare-color, #ffffff);color-scheme:light;margin:0;border:0;padding:0;background:none;color:inherit;overflow:visible;width:100%;height:100%;position:fixed;inset:0;z-index:var(--ars-busy-z-index, 1090);display:flex;align-items:center;justify-content:center;font-family:var(--mat-sys-body-medium-font, Roboto, sans-serif);color:var(--_on-surface)}:host(.ars-dark){--_surface: var(--ars-dialog-background-color, var(--mat-sys-surface-container-high, #282a29));--_on-surface: var(--ars-color-text, var(--mat-sys-on-surface, #e0e2e5));--_scrim: var(--ars-busy-scrim-color, rgba(0, 0, 0, .6));--_primary: var(--ars-color-primary, var(--mat-sys-primary, #53dbc9));--_track: var(--ars-progress-track-color, var(--mat-sys-surface-container-highest, #3a3d3c));color-scheme:dark}:host::backdrop{display:none}:host(.ars-hidden){display:none}.scrim{position:absolute;inset:0;background:var(--_scrim);-webkit-backdrop-filter:blur(1px);backdrop-filter:blur(1px)}.panel{position:relative;min-width:250px;max-width:calc(100% - 24px);margin:12px;padding:24px;border-radius:var(--ars-dialog-radius, 24px);background:var(--_surface);box-shadow:var(--ars-dialog-elevation, 0 4px 8px 0 rgba(0, 0, 0, .2), 0 6px 20px 0 rgba(0, 0, 0, .19));text-align:center}.bare{position:relative;display:flex;flex-direction:column;align-items:center;color:var(--_bare)}.message{font-size:larger;font-weight:600;padding-bottom:15px;text-align:center}.bare .message{padding-top:15px;padding-bottom:0}.percent{padding-top:8px;font-size:smaller}.bar{position:relative;max-width:350px;height:4px;margin:15px auto 0;overflow:hidden;border-radius:2px;background:var(--_track)}.bar-fill{position:absolute;inset:0 auto 0 0;width:0;border-radius:inherit;background:var(--_primary);transition:width .12s linear}.bar.indeterminate .bar-fill{width:40%;transition:none;animation:ars-busy-bar 1.6s cubic-bezier(.65,.02,.35,1) infinite}@keyframes ars-busy-bar{0%{left:-40%}to{left:100%}}.spinner{display:block;width:70px;height:70px;margin:15px auto 0;transform:rotate(-90deg)}.spinner circle{fill:none;stroke-width:7;stroke-linecap:round}.spinner .track{stroke:var(--_track)}.spinner .arc{stroke:var(--_primary);transition:stroke-dashoffset .12s linear}.spinner.indeterminate{animation:ars-busy-rotate 1.4s linear infinite}.spinner.indeterminate .arc{transition:none;stroke-dashoffset:0;animation:ars-busy-dash 1.4s ease-in-out infinite}@keyframes ars-busy-rotate{0%{transform:rotate(-90deg)}to{transform:rotate(270deg)}}@keyframes ars-busy-dash{0%{stroke-dasharray:15 183}50%{stroke-dasharray:145 53}to{stroke-dasharray:15 183}}.hourglass{width:80px;height:80px;fill:currentColor;animation:ars-busy-hourglass 3s linear infinite}@keyframes ars-busy-hourglass{0%{transform:rotate(360deg)}to{transform:rotate(0)}}@media(prefers-reduced-motion:reduce){.bar.indeterminate .bar-fill,.spinner.indeterminate,.spinner.indeterminate .arc,.hourglass{animation-duration:4s}}\n"] }]
|
|
125
|
+
}], ctorParameters: () => [] });
|
|
99
126
|
|
|
100
127
|
/**
|
|
101
128
|
* The shell's message panel: a modal that looks like a Material dialog and owes nothing to
|
|
@@ -112,9 +139,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
|
|
|
112
139
|
* what lets an interceptor open it.
|
|
113
140
|
*/
|
|
114
141
|
class ShellMessageComponent {
|
|
142
|
+
/**
|
|
143
|
+
* Keeps {@link dark} in step with the service, now and at every later change.
|
|
144
|
+
*
|
|
145
|
+
* `'auto'` is resolved through `getTheme()`, which is the only thing that knows what the
|
|
146
|
+
* operating system is currently on.
|
|
147
|
+
* @returns void
|
|
148
|
+
*/
|
|
149
|
+
followTheme() {
|
|
150
|
+
const resolve = (theme) => (theme === 'auto' ? this.themeService.getTheme() : theme) === 'dark';
|
|
151
|
+
this.dark.set(resolve(this.themeService.getTheme()));
|
|
152
|
+
this.themeService.changed
|
|
153
|
+
.pipe(takeUntilDestroyed())
|
|
154
|
+
.subscribe(theme => this.dark.set(resolve(theme)));
|
|
155
|
+
}
|
|
115
156
|
constructor() {
|
|
116
157
|
/** Emitted when the user dismisses the message, whichever way they did it. */
|
|
117
158
|
this.closed = output();
|
|
159
|
+
this.themeService = inject(ThemeService);
|
|
118
160
|
this.panel = viewChild.required('panel', /* @ts-ignore */
|
|
119
161
|
...(ngDevMode ? [{ debugName: "panel" }] : /* istanbul ignore next */ []));
|
|
120
162
|
this.okButton = viewChild.required('ok', /* @ts-ignore */
|
|
@@ -128,6 +170,17 @@ class ShellMessageComponent {
|
|
|
128
170
|
this.copied = signal(false, /* @ts-ignore */
|
|
129
171
|
...(ngDevMode ? [{ debugName: "copied" }] : /* istanbul ignore next */ []));
|
|
130
172
|
this.closing = false;
|
|
173
|
+
/**
|
|
174
|
+
* True when the panel must paint itself dark.
|
|
175
|
+
*
|
|
176
|
+
* Read from {@link ThemeService} and not from `prefers-color-scheme`, because the two do not
|
|
177
|
+
* always agree: the service is what the user chose, and it may be forcing dark on a machine
|
|
178
|
+
* that is on light. Following the OS instead would leave this panel the only light thing on a
|
|
179
|
+
* dark screen.
|
|
180
|
+
*/
|
|
181
|
+
this.dark = signal(false, /* @ts-ignore */
|
|
182
|
+
...(ngDevMode ? [{ debugName: "dark" }] : /* istanbul ignore next */ []));
|
|
183
|
+
this.followTheme();
|
|
131
184
|
afterNextRender(() => this.okButton().nativeElement.focus());
|
|
132
185
|
inject(DestroyRef).onDestroy(() => {
|
|
133
186
|
clearTimeout(this.dismissTimer);
|
|
@@ -235,11 +288,11 @@ class ShellMessageComponent {
|
|
|
235
288
|
}
|
|
236
289
|
}
|
|
237
290
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: ShellMessageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
238
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: ShellMessageComponent, isStandalone: true, selector: "ars-shell-message", outputs: { closed: "closed" }, host: { listeners: { "document:keydown": "onKeydown($event)" } }, viewQueries: [{ propertyName: "panel", first: true, predicate: ["panel"], descendants: true, isSignal: true }, { propertyName: "okButton", first: true, predicate: ["ok"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"scrim\" (click)=\"close()\"></div>\n\n<div #panel class=\"panel\" role=\"alertdialog\" aria-modal=\"true\" [attr.aria-labelledby]=\"titleId\"\n [style.max-width.px]=\"data().width\">\n\n <h2 class=\"title\" [id]=\"titleId\" [innerHTML]=\"data().title | safeHtml\"></h2>\n\n <div class=\"content\">\n @if (data().message) {\n <div class=\"message\" [innerHTML]=\"data().message | safeHtml\"></div>\n }\n @if (data().details) {\n <div class=\"details-box\">\n <div class=\"details-separator\"></div>\n <p class=\"details-label\">Dettaglio:</p>\n <div class=\"details\">\n <div [innerHTML]=\"data().details | safeHtml\"></div>\n </div>\n </div>\n }\n </div>\n\n <div class=\"actions\">\n <button type=\"button\" class=\"icon-button\" (click)=\"copy()\"\n [title]=\"copied() ? 'Copiato negli appunti' : 'Copia negli appunti'\" aria-label=\"Copia negli appunti\">\n @if (copied()) {\n <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\"><path d=\"M9 16.2 4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4z\" /></svg>\n } @else {\n <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\"><path\n d=\"M16 1H4a2 2 0 0 0-2 2v14h2V3h12zm3 4H8a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h11a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2m0 16H8V7h11z\" /></svg>\n }\n </button>\n <button #ok type=\"button\" class=\"flat-button\" (click)=\"close()\">{{ data().okCaption }}</button>\n </div>\n</div>\n", styles: [":host{position:fixed;inset:0;z-index:var(--ars-shell-z-index, 1100);display:flex;align-items:center;justify-content:center;box-sizing:border-box}.
|
|
291
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: ShellMessageComponent, isStandalone: true, selector: "ars-shell-message", outputs: { closed: "closed" }, host: { listeners: { "document:keydown": "onKeydown($event)" }, properties: { "class.ars-dark": "dark()" } }, viewQueries: [{ propertyName: "panel", first: true, predicate: ["panel"], descendants: true, isSignal: true }, { propertyName: "okButton", first: true, predicate: ["ok"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"scrim\" (click)=\"close()\"></div>\n\n<div #panel class=\"panel\" role=\"alertdialog\" aria-modal=\"true\" [attr.aria-labelledby]=\"titleId\"\n [style.max-width.px]=\"data().width\">\n\n <h2 class=\"title\" [id]=\"titleId\" [innerHTML]=\"data().title | safeHtml\"></h2>\n\n <div class=\"content\">\n @if (data().message) {\n <div class=\"message\" [innerHTML]=\"data().message | safeHtml\"></div>\n }\n @if (data().details) {\n <div class=\"details-box\">\n <div class=\"details-separator\"></div>\n <p class=\"details-label\">Dettaglio:</p>\n <div class=\"details\">\n <div [innerHTML]=\"data().details | safeHtml\"></div>\n </div>\n </div>\n }\n </div>\n\n <div class=\"actions\">\n <button type=\"button\" class=\"icon-button\" (click)=\"copy()\"\n [title]=\"copied() ? 'Copiato negli appunti' : 'Copia negli appunti'\" aria-label=\"Copia negli appunti\">\n @if (copied()) {\n <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\"><path d=\"M9 16.2 4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4z\" /></svg>\n } @else {\n <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\"><path\n d=\"M16 1H4a2 2 0 0 0-2 2v14h2V3h12zm3 4H8a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h11a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2m0 16H8V7h11z\" /></svg>\n }\n </button>\n <button #ok type=\"button\" class=\"flat-button\" (click)=\"close()\">{{ data().okCaption }}</button>\n </div>\n</div>\n", styles: [":host{--_surface: var(--ars-dialog-background-color, var(--mat-sys-surface-container-high, #e6eae8));--_on-surface: var(--ars-color-text, var(--mat-sys-on-surface, #191c1b));--_secondary: var(--ars-color-secondary, var(--mat-sys-on-surface-variant, #4a635f));--_divider: var(--ars-color-divider, #757d87);--_scrim: var(--ars-scrim-color, rgba(0, 0, 0, .32));--_primary: var(--ars-color-primary, var(--mat-sys-primary, #00a293));--_on-primary: var(--ars-color-on-primary, var(--mat-sys-on-primary, #ffffff));--_hover: var(--ars-item-hover-background-color, rgba(0, 0, 0, .08));color-scheme:light;margin:0;border:0;padding:0;background:none;color:inherit;overflow:visible;width:100%;height:100%;position:fixed;inset:0;z-index:var(--ars-shell-z-index, 1100);display:flex;align-items:center;justify-content:center;box-sizing:border-box}:host(.ars-dark){--_surface: var(--ars-dialog-background-color, var(--mat-sys-surface-container-high, #282a29));--_on-surface: var(--ars-color-text, var(--mat-sys-on-surface, #e0e2e5));--_secondary: var(--ars-color-secondary, var(--mat-sys-on-surface-variant, #b1ccc6));--_scrim: var(--ars-scrim-color, rgba(0, 0, 0, .6));--_primary: var(--ars-color-primary, var(--mat-sys-primary, #53dbc9));--_on-primary: var(--ars-color-on-primary, var(--mat-sys-on-primary, #00382f));--_hover: var(--ars-item-hover-background-color, rgba(255, 255, 255, .08));color-scheme:dark}:host::backdrop{display:none}.scrim{position:absolute;inset:0;background:var(--_scrim);-webkit-backdrop-filter:blur(1px);backdrop-filter:blur(1px);animation:ars-shell-fade .15s cubic-bezier(0,0,.2,1)}.panel{position:relative;display:flex;flex-direction:column;width:100%;min-width:0;margin:12px;max-height:calc(100% - 24px);box-sizing:border-box;border-radius:var(--ars-dialog-radius, 28px);background:var(--_surface);color:var(--_on-surface);box-shadow:var(--ars-dialog-elevation, 0 4px 8px 3px rgba(0, 0, 0, .15), 0 1px 3px 0 rgba(0, 0, 0, .3));font-family:var(--mat-sys-body-medium-font, Roboto, sans-serif);text-align:left;animation:ars-shell-enter .15s cubic-bezier(0,0,.2,1)}.title{margin:0;padding:24px 24px 16px;font:var(--mat-sys-headline-small, 400 24px/32px Roboto, sans-serif);color:inherit}.content{display:flex;flex-direction:column;flex:1 1 auto;min-height:0;padding:0;font:var(--mat-sys-body-medium, 400 14px/20px Roboto, sans-serif)}.message{flex:0 1 auto;min-height:0;padding:0 24px 24px;overflow-y:auto;overscroll-behavior:contain}.details-box{display:contents}.details-separator{flex:0 0 auto;height:1px;margin:0 24px 8px;background:var(--_divider)}.details-label{flex:0 0 auto;margin:0 0 4px;padding:0 24px;font-size:.75em;text-transform:uppercase;color:var(--_secondary)}.details{flex:1 1000 auto;min-height:64px;max-height:350px;padding:4px 24px 8px;overflow-y:auto;overscroll-behavior:contain;background:transparent;font-size:.85em}.actions{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:16px 24px 24px}button{font-family:inherit;cursor:pointer;border:none}button:focus-visible{outline:3px solid var(--_primary);outline-offset:2px}.icon-button{display:inline-flex;align-items:center;justify-content:center;width:40px;height:40px;padding:0;border-radius:50%;background:transparent;color:inherit}.icon-button svg{width:24px;height:24px;fill:currentColor}.icon-button:hover{background:var(--_hover)}.flat-button{height:40px;padding:0 24px;border-radius:20px;background:var(--_primary);color:var(--_on-primary);font:var(--mat-sys-label-large, 500 14px/20px Roboto, sans-serif)}.flat-button:hover{filter:brightness(1.08)}@keyframes ars-shell-fade{0%{opacity:0}to{opacity:1}}@keyframes ars-shell-enter{0%{opacity:0;transform:scale(.7)}to{opacity:1;transform:none}}@media(prefers-reduced-motion:reduce){.scrim,.panel{animation:none}}\n"], dependencies: [{ kind: "pipe", type: SafeHtmlPipe, name: "safeHtml" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
239
292
|
}
|
|
240
293
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: ShellMessageComponent, decorators: [{
|
|
241
294
|
type: Component,
|
|
242
|
-
args: [{ selector: 'ars-shell-message', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [SafeHtmlPipe], template: "<div class=\"scrim\" (click)=\"close()\"></div>\n\n<div #panel class=\"panel\" role=\"alertdialog\" aria-modal=\"true\" [attr.aria-labelledby]=\"titleId\"\n [style.max-width.px]=\"data().width\">\n\n <h2 class=\"title\" [id]=\"titleId\" [innerHTML]=\"data().title | safeHtml\"></h2>\n\n <div class=\"content\">\n @if (data().message) {\n <div class=\"message\" [innerHTML]=\"data().message | safeHtml\"></div>\n }\n @if (data().details) {\n <div class=\"details-box\">\n <div class=\"details-separator\"></div>\n <p class=\"details-label\">Dettaglio:</p>\n <div class=\"details\">\n <div [innerHTML]=\"data().details | safeHtml\"></div>\n </div>\n </div>\n }\n </div>\n\n <div class=\"actions\">\n <button type=\"button\" class=\"icon-button\" (click)=\"copy()\"\n [title]=\"copied() ? 'Copiato negli appunti' : 'Copia negli appunti'\" aria-label=\"Copia negli appunti\">\n @if (copied()) {\n <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\"><path d=\"M9 16.2 4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4z\" /></svg>\n } @else {\n <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\"><path\n d=\"M16 1H4a2 2 0 0 0-2 2v14h2V3h12zm3 4H8a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h11a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2m0 16H8V7h11z\" /></svg>\n }\n </button>\n <button #ok type=\"button\" class=\"flat-button\" (click)=\"close()\">{{ data().okCaption }}</button>\n </div>\n</div>\n", styles: [":host{position:fixed;inset:0;z-index:var(--ars-shell-z-index, 1100);display:flex;align-items:center;justify-content:center;box-sizing:border-box}.
|
|
295
|
+
args: [{ selector: 'ars-shell-message', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [SafeHtmlPipe], host: { '[class.ars-dark]': 'dark()' }, template: "<div class=\"scrim\" (click)=\"close()\"></div>\n\n<div #panel class=\"panel\" role=\"alertdialog\" aria-modal=\"true\" [attr.aria-labelledby]=\"titleId\"\n [style.max-width.px]=\"data().width\">\n\n <h2 class=\"title\" [id]=\"titleId\" [innerHTML]=\"data().title | safeHtml\"></h2>\n\n <div class=\"content\">\n @if (data().message) {\n <div class=\"message\" [innerHTML]=\"data().message | safeHtml\"></div>\n }\n @if (data().details) {\n <div class=\"details-box\">\n <div class=\"details-separator\"></div>\n <p class=\"details-label\">Dettaglio:</p>\n <div class=\"details\">\n <div [innerHTML]=\"data().details | safeHtml\"></div>\n </div>\n </div>\n }\n </div>\n\n <div class=\"actions\">\n <button type=\"button\" class=\"icon-button\" (click)=\"copy()\"\n [title]=\"copied() ? 'Copiato negli appunti' : 'Copia negli appunti'\" aria-label=\"Copia negli appunti\">\n @if (copied()) {\n <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\"><path d=\"M9 16.2 4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4z\" /></svg>\n } @else {\n <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\"><path\n d=\"M16 1H4a2 2 0 0 0-2 2v14h2V3h12zm3 4H8a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h11a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2m0 16H8V7h11z\" /></svg>\n }\n </button>\n <button #ok type=\"button\" class=\"flat-button\" (click)=\"close()\">{{ data().okCaption }}</button>\n </div>\n</div>\n", styles: [":host{--_surface: var(--ars-dialog-background-color, var(--mat-sys-surface-container-high, #e6eae8));--_on-surface: var(--ars-color-text, var(--mat-sys-on-surface, #191c1b));--_secondary: var(--ars-color-secondary, var(--mat-sys-on-surface-variant, #4a635f));--_divider: var(--ars-color-divider, #757d87);--_scrim: var(--ars-scrim-color, rgba(0, 0, 0, .32));--_primary: var(--ars-color-primary, var(--mat-sys-primary, #00a293));--_on-primary: var(--ars-color-on-primary, var(--mat-sys-on-primary, #ffffff));--_hover: var(--ars-item-hover-background-color, rgba(0, 0, 0, .08));color-scheme:light;margin:0;border:0;padding:0;background:none;color:inherit;overflow:visible;width:100%;height:100%;position:fixed;inset:0;z-index:var(--ars-shell-z-index, 1100);display:flex;align-items:center;justify-content:center;box-sizing:border-box}:host(.ars-dark){--_surface: var(--ars-dialog-background-color, var(--mat-sys-surface-container-high, #282a29));--_on-surface: var(--ars-color-text, var(--mat-sys-on-surface, #e0e2e5));--_secondary: var(--ars-color-secondary, var(--mat-sys-on-surface-variant, #b1ccc6));--_scrim: var(--ars-scrim-color, rgba(0, 0, 0, .6));--_primary: var(--ars-color-primary, var(--mat-sys-primary, #53dbc9));--_on-primary: var(--ars-color-on-primary, var(--mat-sys-on-primary, #00382f));--_hover: var(--ars-item-hover-background-color, rgba(255, 255, 255, .08));color-scheme:dark}:host::backdrop{display:none}.scrim{position:absolute;inset:0;background:var(--_scrim);-webkit-backdrop-filter:blur(1px);backdrop-filter:blur(1px);animation:ars-shell-fade .15s cubic-bezier(0,0,.2,1)}.panel{position:relative;display:flex;flex-direction:column;width:100%;min-width:0;margin:12px;max-height:calc(100% - 24px);box-sizing:border-box;border-radius:var(--ars-dialog-radius, 28px);background:var(--_surface);color:var(--_on-surface);box-shadow:var(--ars-dialog-elevation, 0 4px 8px 3px rgba(0, 0, 0, .15), 0 1px 3px 0 rgba(0, 0, 0, .3));font-family:var(--mat-sys-body-medium-font, Roboto, sans-serif);text-align:left;animation:ars-shell-enter .15s cubic-bezier(0,0,.2,1)}.title{margin:0;padding:24px 24px 16px;font:var(--mat-sys-headline-small, 400 24px/32px Roboto, sans-serif);color:inherit}.content{display:flex;flex-direction:column;flex:1 1 auto;min-height:0;padding:0;font:var(--mat-sys-body-medium, 400 14px/20px Roboto, sans-serif)}.message{flex:0 1 auto;min-height:0;padding:0 24px 24px;overflow-y:auto;overscroll-behavior:contain}.details-box{display:contents}.details-separator{flex:0 0 auto;height:1px;margin:0 24px 8px;background:var(--_divider)}.details-label{flex:0 0 auto;margin:0 0 4px;padding:0 24px;font-size:.75em;text-transform:uppercase;color:var(--_secondary)}.details{flex:1 1000 auto;min-height:64px;max-height:350px;padding:4px 24px 8px;overflow-y:auto;overscroll-behavior:contain;background:transparent;font-size:.85em}.actions{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:16px 24px 24px}button{font-family:inherit;cursor:pointer;border:none}button:focus-visible{outline:3px solid var(--_primary);outline-offset:2px}.icon-button{display:inline-flex;align-items:center;justify-content:center;width:40px;height:40px;padding:0;border-radius:50%;background:transparent;color:inherit}.icon-button svg{width:24px;height:24px;fill:currentColor}.icon-button:hover{background:var(--_hover)}.flat-button{height:40px;padding:0 24px;border-radius:20px;background:var(--_primary);color:var(--_on-primary);font:var(--mat-sys-label-large, 500 14px/20px Roboto, sans-serif)}.flat-button:hover{filter:brightness(1.08)}@keyframes ars-shell-fade{0%{opacity:0}to{opacity:1}}@keyframes ars-shell-enter{0%{opacity:0;transform:scale(.7)}to{opacity:1;transform:none}}@media(prefers-reduced-motion:reduce){.scrim,.panel{animation:none}}\n"] }]
|
|
243
296
|
}], ctorParameters: () => [], propDecorators: { closed: [{ type: i0.Output, args: ["closed"] }], panel: [{ type: i0.ViewChild, args: ['panel', { isSignal: true }] }], okButton: [{ type: i0.ViewChild, args: ['ok', { isSignal: true }] }], onKeydown: [{
|
|
244
297
|
type: HostListener,
|
|
245
298
|
args: ['document:keydown', ['$event']]
|
|
@@ -261,12 +314,21 @@ const CLEAR_BUSY_GRACE = 500;
|
|
|
261
314
|
*
|
|
262
315
|
* ## Stacking
|
|
263
316
|
*
|
|
264
|
-
*
|
|
265
|
-
*
|
|
266
|
-
*
|
|
267
|
-
*
|
|
268
|
-
*
|
|
269
|
-
*
|
|
317
|
+
* Since Angular CDK 22 every overlay is opened as `popover="manual"`, so a Material dialog is not
|
|
318
|
+
* at z-index 1000 any more: it is in the browser top layer, which is painted above the whole
|
|
319
|
+
* document whatever the z-index of the document says. A number, however large, cannot get above
|
|
320
|
+
* it — so these overlays are promoted to the top layer as well, and the order there is the order
|
|
321
|
+
* of promotion: whatever went up last is on top.
|
|
322
|
+
*
|
|
323
|
+
* That gives the right answer by itself in the case that matters, an error raised while a dialog
|
|
324
|
+
* is open. The other three are arranged explicitly: `setBusy()` puts the message back on top
|
|
325
|
+
* after raising the busy overlay, `DialogService.open()` calls {@link raiseMessage} after opening
|
|
326
|
+
* a dialog, and it also takes the busy away immediately rather than with the grace period, so a
|
|
327
|
+
* dialog is never drawn under an overlay that is on its way out.
|
|
328
|
+
*
|
|
329
|
+
* The `--ars-shell-z-index` / `--ars-busy-z-index` pair (1100 / 1090, above the CDK's 1000) is
|
|
330
|
+
* still there and still correct: it is what orders the overlays on a browser without the Popover
|
|
331
|
+
* API, where the CDK does not use it either.
|
|
270
332
|
*/
|
|
271
333
|
class ShellService {
|
|
272
334
|
constructor() {
|
|
@@ -316,6 +378,7 @@ class ShellService {
|
|
|
316
378
|
const ref = this.messageRef;
|
|
317
379
|
this.messageRef = undefined;
|
|
318
380
|
this.messageKind = undefined;
|
|
381
|
+
this.leaveTopLayer(ref.location.nativeElement);
|
|
319
382
|
this.appRef.detachView(ref.hostView);
|
|
320
383
|
ref.destroy();
|
|
321
384
|
if (this.previousOverflow !== undefined) {
|
|
@@ -343,6 +406,9 @@ class ShellService {
|
|
|
343
406
|
if (this.messageRef && this.messageKind === kind) {
|
|
344
407
|
this.messageRef.instance.setData(data);
|
|
345
408
|
this.messageRef.changeDetectorRef.detectChanges();
|
|
409
|
+
// Back to the front: a dialog may have been opened since this panel went up, and the top
|
|
410
|
+
// layer orders by promotion, not by z-index.
|
|
411
|
+
this.raiseTopLayer(this.messageRef.location.nativeElement);
|
|
346
412
|
return this.messageHandle();
|
|
347
413
|
}
|
|
348
414
|
// A message of a different kind replaces the one on screen rather than stacking on it.
|
|
@@ -358,6 +424,7 @@ class ShellService {
|
|
|
358
424
|
document.body.style.overflow = 'hidden';
|
|
359
425
|
this.appRef.attachView(ref.hostView);
|
|
360
426
|
document.body.appendChild(ref.location.nativeElement);
|
|
427
|
+
this.enterTopLayer(ref.location.nativeElement);
|
|
361
428
|
this.messageRef = ref;
|
|
362
429
|
this.messageKind = kind;
|
|
363
430
|
return this.messageHandle();
|
|
@@ -408,6 +475,10 @@ class ShellService {
|
|
|
408
475
|
ref.instance.visible.set(true);
|
|
409
476
|
this.busyVisible = true;
|
|
410
477
|
ref.changeDetectorRef.detectChanges();
|
|
478
|
+
this.enterTopLayer(ref.location.nativeElement);
|
|
479
|
+
// The overlay has just been promoted, so it is the topmost element of the top layer: put the
|
|
480
|
+
// message back above it, which is what the 1100/1090 pair used to do on its own.
|
|
481
|
+
this.raiseMessage();
|
|
411
482
|
// The action always replaces the previous one. Keeping the first, as the previous
|
|
412
483
|
// implementation did, meant the overlay outlived the operation that was actually running.
|
|
413
484
|
this.busyActionSubscription?.unsubscribe();
|
|
@@ -497,6 +568,7 @@ class ShellService {
|
|
|
497
568
|
this.busyVisible = false;
|
|
498
569
|
this.busyRef?.instance.visible.set(false);
|
|
499
570
|
this.busyRef?.changeDetectorRef.detectChanges();
|
|
571
|
+
this.leaveTopLayer(this.busyRef?.location.nativeElement);
|
|
500
572
|
}
|
|
501
573
|
/**
|
|
502
574
|
* Returns the busy component, creating and attaching it the first time it is needed.
|
|
@@ -511,6 +583,93 @@ class ShellService {
|
|
|
511
583
|
this.busyRef = ref;
|
|
512
584
|
return ref;
|
|
513
585
|
}
|
|
586
|
+
// ---------------------------------------------------------------------------
|
|
587
|
+
// Top layer
|
|
588
|
+
// ---------------------------------------------------------------------------
|
|
589
|
+
/**
|
|
590
|
+
* Brings the message currently on screen back to the front of the top layer, if there is one.
|
|
591
|
+
*
|
|
592
|
+
* Called by `DialogService` after opening a dialog: an error the user has not dismissed yet
|
|
593
|
+
* must stay above whatever is opened underneath it.
|
|
594
|
+
* @returns void
|
|
595
|
+
*/
|
|
596
|
+
raiseMessage() {
|
|
597
|
+
this.raiseTopLayer(this.messageRef?.location.nativeElement);
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* Whether the browser can promote an element to the top layer through the Popover API.
|
|
601
|
+
*
|
|
602
|
+
* Angular CDK 22 opens every overlay as `popover="manual"`, so a Material dialog does not sit
|
|
603
|
+
* at z-index 1000 any more: it sits in the top layer, which is painted above the whole page
|
|
604
|
+
* whatever the z-index of the page says. The only way to put something above it is to be in
|
|
605
|
+
* the top layer too. Where the API is missing the overlays fall back to their z-index, which
|
|
606
|
+
* is also the CDK's own fallback and therefore still the right order.
|
|
607
|
+
* @returns `true` when `showPopover()` is available.
|
|
608
|
+
*/
|
|
609
|
+
supportsTopLayer() {
|
|
610
|
+
if (this.topLayerSupport === undefined) {
|
|
611
|
+
this.topLayerSupport = SystemUtils.isBrowser()
|
|
612
|
+
&& typeof HTMLElement !== 'undefined'
|
|
613
|
+
&& 'showPopover' in HTMLElement.prototype;
|
|
614
|
+
}
|
|
615
|
+
return this.topLayerSupport;
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* Promotes an overlay to the top layer, marking it as a manual popover the first time.
|
|
619
|
+
*
|
|
620
|
+
* `manual` and not `auto`: an `auto` popover is light-dismissed by a click anywhere and closed
|
|
621
|
+
* by Escape without telling anyone, which would take the panel off the screen behind the back
|
|
622
|
+
* of the component that owns it.
|
|
623
|
+
* @param el - The host element of the overlay. Ignored when undefined.
|
|
624
|
+
* @returns void
|
|
625
|
+
*/
|
|
626
|
+
enterTopLayer(el) {
|
|
627
|
+
if (!el || !this.supportsTopLayer())
|
|
628
|
+
return;
|
|
629
|
+
if (el.getAttribute('popover') !== 'manual')
|
|
630
|
+
el.setAttribute('popover', 'manual');
|
|
631
|
+
// Throws if the element is already showing, or is not connected yet.
|
|
632
|
+
try {
|
|
633
|
+
if (!el.matches(':popover-open'))
|
|
634
|
+
el.showPopover();
|
|
635
|
+
}
|
|
636
|
+
catch { /* already up */ }
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* Takes an overlay out of the top layer.
|
|
640
|
+
* @param el - The host element of the overlay. Ignored when undefined.
|
|
641
|
+
* @returns void
|
|
642
|
+
*/
|
|
643
|
+
leaveTopLayer(el) {
|
|
644
|
+
if (!el || !this.supportsTopLayer())
|
|
645
|
+
return;
|
|
646
|
+
try {
|
|
647
|
+
if (el.matches(':popover-open'))
|
|
648
|
+
el.hidePopover();
|
|
649
|
+
}
|
|
650
|
+
catch { /* already down */ }
|
|
651
|
+
}
|
|
652
|
+
/**
|
|
653
|
+
* Moves an overlay back to the front of the top layer.
|
|
654
|
+
*
|
|
655
|
+
* The top layer is a stack ordered by promotion, so the only way to raise an element already
|
|
656
|
+
* in it is to take it out and put it back.
|
|
657
|
+
* @param el - The host element of the overlay. Ignored when undefined.
|
|
658
|
+
* @returns void
|
|
659
|
+
*/
|
|
660
|
+
raiseTopLayer(el) {
|
|
661
|
+
if (!el || !this.supportsTopLayer())
|
|
662
|
+
return;
|
|
663
|
+
if (!el.matches(':popover-open')) {
|
|
664
|
+
this.enterTopLayer(el);
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
try {
|
|
668
|
+
el.hidePopover();
|
|
669
|
+
el.showPopover();
|
|
670
|
+
}
|
|
671
|
+
catch { /* the element is on its way out */ }
|
|
672
|
+
}
|
|
514
673
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: ShellService, deps: [], target: i0.ɵɵFactoryTarget.Service }); }
|
|
515
674
|
static { this.ɵprov = i0.ɵɵngDeclareService({ minVersion: "22.0.0", version: "22.1.3", ngImport: i0, type: ShellService }); }
|
|
516
675
|
}
|