@lifecorner/angular-ui-kit 3.27.2 → 3.28.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/fesm2022/lifecorner-angular-ui-kit-app-shell.mjs +102 -24
- package/fesm2022/lifecorner-angular-ui-kit-app-shell.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-internal-overlay.mjs +31 -2
- package/fesm2022/lifecorner-angular-ui-kit-internal-overlay.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-toast.mjs +12 -2
- package/fesm2022/lifecorner-angular-ui-kit-toast.mjs.map +1 -1
- package/package.json +1 -1
- package/types/lifecorner-angular-ui-kit-app-shell.d.ts +64 -0
- package/types/lifecorner-angular-ui-kit-internal-overlay.d.ts +21 -0
- package/types/lifecorner-angular-ui-kit-toast.d.ts +25 -0
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import * as i0 from "@angular/core";
|
|
2
2
|
import { ChangeDetectionStrategy, Component, DestroyRef, afterNextRender, computed, inject, input, viewChild } from "@angular/core";
|
|
3
|
+
import { OverlayInsetsService } from "@lifecorner/angular-ui-kit/internal/overlay";
|
|
3
4
|
const BAR_VAR = "--_lc-app-shell-bar";
|
|
5
|
+
const TOP_INSET = "env(safe-area-inset-top, 0px)";
|
|
6
|
+
const BOTTOM_INSET = "env(safe-area-inset-bottom, 0px)";
|
|
7
|
+
const borderBoxHeight = (entry, target) => entry.borderBoxSize?.[0]?.blockSize ?? target.getBoundingClientRect().height;
|
|
4
8
|
var AppShellComponent = class AppShellComponent {
|
|
5
9
|
horizon = input(false, ...ngDevMode ? [{ debugName: "horizon" }] : /* istanbul ignore next */ []);
|
|
6
10
|
navPlacement = input("below-header", ...ngDevMode ? [{ debugName: "navPlacement" }] : /* istanbul ignore next */ []);
|
|
@@ -11,21 +15,37 @@ var AppShellComponent = class AppShellComponent {
|
|
|
11
15
|
return classes.join(" ");
|
|
12
16
|
}, ...ngDevMode ? [{ debugName: "shellClasses" }] : /* istanbul ignore next */ []);
|
|
13
17
|
shell = viewChild.required("shell", ...ngDevMode ? [{ debugName: "shell" }] : /* istanbul ignore next */ []);
|
|
18
|
+
headerSlot = viewChild.required("headerSlot", ...ngDevMode ? [{ debugName: "headerSlot" }] : /* istanbul ignore next */ []);
|
|
14
19
|
barSlot = viewChild.required("barSlot", ...ngDevMode ? [{ debugName: "barSlot" }] : /* istanbul ignore next */ []);
|
|
15
20
|
constructor() {
|
|
16
21
|
const destroyRef = inject(DestroyRef);
|
|
22
|
+
const insets = inject(OverlayInsetsService);
|
|
17
23
|
afterNextRender(() => {
|
|
18
24
|
if (typeof ResizeObserver === "undefined") return;
|
|
19
25
|
const shell = this.shell().nativeElement;
|
|
26
|
+
const header = this.headerSlot().nativeElement;
|
|
20
27
|
const slot = this.barSlot().nativeElement;
|
|
21
|
-
|
|
28
|
+
insets.top.set(TOP_INSET);
|
|
29
|
+
insets.bottom.set(BOTTOM_INSET);
|
|
30
|
+
let bar = null;
|
|
31
|
+
const clear = () => {
|
|
32
|
+
shell.style.removeProperty(BAR_VAR);
|
|
33
|
+
insets.bottom.set(BOTTOM_INSET);
|
|
34
|
+
};
|
|
22
35
|
const sizes = new ResizeObserver((entries) => {
|
|
23
36
|
const entry = entries[entries.length - 1];
|
|
24
37
|
const height = entry.contentBoxSize?.[0]?.blockSize ?? entry.contentRect.height;
|
|
25
|
-
if (height > 0)
|
|
26
|
-
|
|
38
|
+
if (height > 0 && bar) {
|
|
39
|
+
shell.style.setProperty(BAR_VAR, `${height}px`);
|
|
40
|
+
const bottom = parseFloat(getComputedStyle(bar).bottom);
|
|
41
|
+
insets.bottom.set(`${(Number.isFinite(bottom) ? bottom : 0) + borderBoxHeight(entry, bar)}px`);
|
|
42
|
+
} else clear();
|
|
27
43
|
});
|
|
28
|
-
|
|
44
|
+
const headerSizes = new ResizeObserver((entries) => {
|
|
45
|
+
const height = borderBoxHeight(entries[entries.length - 1], header);
|
|
46
|
+
insets.top.set(height > 0 ? `${height}px` : TOP_INSET);
|
|
47
|
+
});
|
|
48
|
+
headerSizes.observe(header);
|
|
29
49
|
const pick = () => {
|
|
30
50
|
const next = slot.querySelector(".lc-bottom-nav") ?? slot.firstElementChild;
|
|
31
51
|
if (next === bar) return;
|
|
@@ -42,7 +62,10 @@ var AppShellComponent = class AppShellComponent {
|
|
|
42
62
|
});
|
|
43
63
|
destroyRef.onDestroy(() => {
|
|
44
64
|
sizes.disconnect();
|
|
65
|
+
headerSizes.disconnect();
|
|
45
66
|
changes?.disconnect();
|
|
67
|
+
insets.top.set(null);
|
|
68
|
+
insets.bottom.set(null);
|
|
46
69
|
});
|
|
47
70
|
});
|
|
48
71
|
}
|
|
@@ -76,21 +99,31 @@ var AppShellComponent = class AppShellComponent {
|
|
|
76
99
|
transformFunction: null
|
|
77
100
|
}
|
|
78
101
|
},
|
|
79
|
-
viewQueries: [
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
102
|
+
viewQueries: [
|
|
103
|
+
{
|
|
104
|
+
propertyName: "shell",
|
|
105
|
+
first: true,
|
|
106
|
+
predicate: ["shell"],
|
|
107
|
+
descendants: true,
|
|
108
|
+
isSignal: true
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
propertyName: "headerSlot",
|
|
112
|
+
first: true,
|
|
113
|
+
predicate: ["headerSlot"],
|
|
114
|
+
descendants: true,
|
|
115
|
+
isSignal: true
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
propertyName: "barSlot",
|
|
119
|
+
first: true,
|
|
120
|
+
predicate: ["barSlot"],
|
|
121
|
+
descendants: true,
|
|
122
|
+
isSignal: true
|
|
123
|
+
}
|
|
124
|
+
],
|
|
92
125
|
ngImport: i0,
|
|
93
|
-
template: "<div #shell [class]=\"shellClasses()\">\n <div class=\"lc-app-shell__header\"><ng-content select=\"[shell-header]\" /></div>\n <div class=\"lc-app-shell__nav\"><ng-content select=\"[shell-nav]\" /></div>\n <div class=\"lc-app-shell__body\"><ng-content /></div>\n @if (horizon()) {\n <div class=\"lc-app-shell__horizon lc-horizon\" aria-hidden=\"true\"><ng-content select=\"[shell-horizon-figure]\" /></div>\n }\n <div class=\"lc-app-shell__footer\"><ng-content select=\"[shell-footer]\" /></div>\n <div #barSlot class=\"lc-app-shell__bottom-nav\"><ng-content select=\"[shell-bottom-nav]\" /></div>\n</div>\n",
|
|
126
|
+
template: "<div #shell [class]=\"shellClasses()\">\n <div #headerSlot class=\"lc-app-shell__header\"><ng-content select=\"[shell-header]\" /></div>\n <div class=\"lc-app-shell__nav\"><ng-content select=\"[shell-nav]\" /></div>\n <div class=\"lc-app-shell__body\"><ng-content /></div>\n @if (horizon()) {\n <div class=\"lc-app-shell__horizon lc-horizon\" aria-hidden=\"true\"><ng-content select=\"[shell-horizon-figure]\" /></div>\n }\n <div class=\"lc-app-shell__footer\"><ng-content select=\"[shell-footer]\" /></div>\n <div #barSlot class=\"lc-app-shell__bottom-nav\"><ng-content select=\"[shell-bottom-nav]\" /></div>\n</div>\n",
|
|
94
127
|
styles: [":host{display:block}.lc-app-shell{--_nav-width: var(--lc-app-shell-nav-width, 240px);--_clearance: var( --lc-app-shell-bottom-clearance, max( calc(112px + env(safe-area-inset-bottom, 0px)) , calc(var(--_lc-app-shell-bar, 72px) + 40px + env(safe-area-inset-bottom, 0px))) );display:grid;grid-template-columns:minmax(0,1fr);grid-template-rows:auto auto 1fr;grid-template-areas:\"header\" \"body\" \"end\";min-height:100vh;min-height:100dvh;box-sizing:border-box;background-color:var(--lc-app-shell-bg, var(--color-background));background-image:var(--lc-app-shell-bg-image, var(--app-background-image, none))}.lc-app-shell__header{grid-area:header;min-width:0;padding-top:env(safe-area-inset-top,0px);padding-left:env(safe-area-inset-left,0px);padding-right:env(safe-area-inset-right,0px)}.lc-app-shell__body{grid-area:body;min-width:0;padding-left:env(safe-area-inset-left,0px);padding-right:env(safe-area-inset-right,0px)}.lc-app-shell__nav{display:none}.lc-app-shell__horizon{grid-area:end;min-width:0}.lc-app-shell__footer{grid-area:end;align-self:end;min-width:0;padding-left:env(safe-area-inset-left,0px);padding-right:env(safe-area-inset-right,0px)}.lc-app-shell--horizon{grid-template-rows:auto auto 1fr auto}.lc-app-shell--horizon .lc-app-shell__footer{position:relative;z-index:1;grid-row:4;grid-column:1/-1;background-color:var(--color-land-3)}.lc-app-shell__footer:empty,.lc-app-shell__header:empty{display:none}.lc-app-shell__bottom-nav{display:contents}@media(max-width:1023.98px){.lc-app-shell:not(.lc-app-shell--horizon):has(>.lc-app-shell__bottom-nav:not(:empty)){padding-bottom:var(--_clearance)}.lc-app-shell--horizon:has(>.lc-app-shell__bottom-nav:not(:empty))>.lc-app-shell__horizon{--_lc-horizon-land: calc(var(--lc-horizon-land, 112px) + max(0px, var(--_lc-app-shell-bar, 72px) - 72px))}}@media(min-width:1024px){.lc-app-shell{grid-template-columns:var(--_nav-width) minmax(0,1fr);grid-template-areas:\"header header\" \"nav body\" \"end end\"}.lc-app-shell:has(>.lc-app-shell__nav:empty){grid-template-columns:minmax(0,1fr);grid-template-areas:\"header\" \"body\" \"end\"}.lc-app-shell__nav{grid-area:nav;display:block;min-width:0;position:sticky;top:0;align-self:start;max-height:100vh;max-height:100dvh;overflow-y:auto;padding-left:env(safe-area-inset-left,0px)}.lc-app-shell__nav:empty{display:none}.lc-app-shell:not(:has(>.lc-app-shell__nav:empty)) .lc-app-shell__body{padding-left:0}.lc-app-shell__bottom-nav{display:none}.lc-app-shell--nav-full-height:not(:has(>.lc-app-shell__nav:empty)){grid-template-areas:\"nav header\" \"nav body\" \"end end\"}.lc-app-shell--nav-full-height:not(:has(>.lc-app-shell__nav:empty))>.lc-app-shell__header{padding-left:0}.lc-app-shell--nav-full-height:not(:has(>.lc-app-shell__nav:empty))>.lc-app-shell__nav{grid-row:1/-1;grid-column:1;box-sizing:border-box;height:100vh;height:100dvh;max-height:none;z-index:2;padding-top:env(safe-area-inset-top,0px);display:grid;grid-template-columns:minmax(0,1fr);grid-template-rows:minmax(100%,auto)}.lc-app-shell--nav-full-height:not(:has(>.lc-app-shell__nav:empty))>.lc-app-shell__footer{grid-column:1/-1;padding-left:var(--_nav-width)}}@media(min-width:1280px){.lc-app-shell:has(.lc-app-page__aside)>.lc-app-shell__header{--_aside-width: var(--lc-app-page-aside-width, 360px);--_hairline: var(--color-hairline, var(--color-border));background:linear-gradient(var(--_hairline),var(--_hairline)) right calc(var(--_aside-width) - 1px) top/1px 100% no-repeat,linear-gradient(var(--color-background-low),var(--color-background-low)) right top/var(--_aside-width) 100% no-repeat}}@media(min-width:1440px){.lc-app-shell{--_nav-width: var(--lc-app-shell-nav-width, 248px)}.lc-app-shell:has(.lc-app-page__aside)>.lc-app-shell__header{--_aside-width: var(--lc-app-page-aside-width, 400px)}}@media(min-width:1920px){.lc-app-shell{--_nav-width: var(--lc-app-shell-nav-width, 280px)}.lc-app-shell:has(.lc-app-page__aside)>.lc-app-shell__header{--_aside-width: var(--lc-app-page-aside-width, 460px)}}@media(forced-colors:active){.lc-app-shell__header{background:none}}\n"],
|
|
95
128
|
changeDetection: i0.ChangeDetectionStrategy.OnPush
|
|
96
129
|
});
|
|
@@ -106,7 +139,7 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
106
139
|
selector: "lc-app-shell",
|
|
107
140
|
standalone: true,
|
|
108
141
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
109
|
-
template: "<div #shell [class]=\"shellClasses()\">\n <div class=\"lc-app-shell__header\"><ng-content select=\"[shell-header]\" /></div>\n <div class=\"lc-app-shell__nav\"><ng-content select=\"[shell-nav]\" /></div>\n <div class=\"lc-app-shell__body\"><ng-content /></div>\n @if (horizon()) {\n <div class=\"lc-app-shell__horizon lc-horizon\" aria-hidden=\"true\"><ng-content select=\"[shell-horizon-figure]\" /></div>\n }\n <div class=\"lc-app-shell__footer\"><ng-content select=\"[shell-footer]\" /></div>\n <div #barSlot class=\"lc-app-shell__bottom-nav\"><ng-content select=\"[shell-bottom-nav]\" /></div>\n</div>\n",
|
|
142
|
+
template: "<div #shell [class]=\"shellClasses()\">\n <div #headerSlot class=\"lc-app-shell__header\"><ng-content select=\"[shell-header]\" /></div>\n <div class=\"lc-app-shell__nav\"><ng-content select=\"[shell-nav]\" /></div>\n <div class=\"lc-app-shell__body\"><ng-content /></div>\n @if (horizon()) {\n <div class=\"lc-app-shell__horizon lc-horizon\" aria-hidden=\"true\"><ng-content select=\"[shell-horizon-figure]\" /></div>\n }\n <div class=\"lc-app-shell__footer\"><ng-content select=\"[shell-footer]\" /></div>\n <div #barSlot class=\"lc-app-shell__bottom-nav\"><ng-content select=\"[shell-bottom-nav]\" /></div>\n</div>\n",
|
|
110
143
|
styles: [":host{display:block}.lc-app-shell{--_nav-width: var(--lc-app-shell-nav-width, 240px);--_clearance: var( --lc-app-shell-bottom-clearance, max( calc(112px + env(safe-area-inset-bottom, 0px)) , calc(var(--_lc-app-shell-bar, 72px) + 40px + env(safe-area-inset-bottom, 0px))) );display:grid;grid-template-columns:minmax(0,1fr);grid-template-rows:auto auto 1fr;grid-template-areas:\"header\" \"body\" \"end\";min-height:100vh;min-height:100dvh;box-sizing:border-box;background-color:var(--lc-app-shell-bg, var(--color-background));background-image:var(--lc-app-shell-bg-image, var(--app-background-image, none))}.lc-app-shell__header{grid-area:header;min-width:0;padding-top:env(safe-area-inset-top,0px);padding-left:env(safe-area-inset-left,0px);padding-right:env(safe-area-inset-right,0px)}.lc-app-shell__body{grid-area:body;min-width:0;padding-left:env(safe-area-inset-left,0px);padding-right:env(safe-area-inset-right,0px)}.lc-app-shell__nav{display:none}.lc-app-shell__horizon{grid-area:end;min-width:0}.lc-app-shell__footer{grid-area:end;align-self:end;min-width:0;padding-left:env(safe-area-inset-left,0px);padding-right:env(safe-area-inset-right,0px)}.lc-app-shell--horizon{grid-template-rows:auto auto 1fr auto}.lc-app-shell--horizon .lc-app-shell__footer{position:relative;z-index:1;grid-row:4;grid-column:1/-1;background-color:var(--color-land-3)}.lc-app-shell__footer:empty,.lc-app-shell__header:empty{display:none}.lc-app-shell__bottom-nav{display:contents}@media(max-width:1023.98px){.lc-app-shell:not(.lc-app-shell--horizon):has(>.lc-app-shell__bottom-nav:not(:empty)){padding-bottom:var(--_clearance)}.lc-app-shell--horizon:has(>.lc-app-shell__bottom-nav:not(:empty))>.lc-app-shell__horizon{--_lc-horizon-land: calc(var(--lc-horizon-land, 112px) + max(0px, var(--_lc-app-shell-bar, 72px) - 72px))}}@media(min-width:1024px){.lc-app-shell{grid-template-columns:var(--_nav-width) minmax(0,1fr);grid-template-areas:\"header header\" \"nav body\" \"end end\"}.lc-app-shell:has(>.lc-app-shell__nav:empty){grid-template-columns:minmax(0,1fr);grid-template-areas:\"header\" \"body\" \"end\"}.lc-app-shell__nav{grid-area:nav;display:block;min-width:0;position:sticky;top:0;align-self:start;max-height:100vh;max-height:100dvh;overflow-y:auto;padding-left:env(safe-area-inset-left,0px)}.lc-app-shell__nav:empty{display:none}.lc-app-shell:not(:has(>.lc-app-shell__nav:empty)) .lc-app-shell__body{padding-left:0}.lc-app-shell__bottom-nav{display:none}.lc-app-shell--nav-full-height:not(:has(>.lc-app-shell__nav:empty)){grid-template-areas:\"nav header\" \"nav body\" \"end end\"}.lc-app-shell--nav-full-height:not(:has(>.lc-app-shell__nav:empty))>.lc-app-shell__header{padding-left:0}.lc-app-shell--nav-full-height:not(:has(>.lc-app-shell__nav:empty))>.lc-app-shell__nav{grid-row:1/-1;grid-column:1;box-sizing:border-box;height:100vh;height:100dvh;max-height:none;z-index:2;padding-top:env(safe-area-inset-top,0px);display:grid;grid-template-columns:minmax(0,1fr);grid-template-rows:minmax(100%,auto)}.lc-app-shell--nav-full-height:not(:has(>.lc-app-shell__nav:empty))>.lc-app-shell__footer{grid-column:1/-1;padding-left:var(--_nav-width)}}@media(min-width:1280px){.lc-app-shell:has(.lc-app-page__aside)>.lc-app-shell__header{--_aside-width: var(--lc-app-page-aside-width, 360px);--_hairline: var(--color-hairline, var(--color-border));background:linear-gradient(var(--_hairline),var(--_hairline)) right calc(var(--_aside-width) - 1px) top/1px 100% no-repeat,linear-gradient(var(--color-background-low),var(--color-background-low)) right top/var(--_aside-width) 100% no-repeat}}@media(min-width:1440px){.lc-app-shell{--_nav-width: var(--lc-app-shell-nav-width, 248px)}.lc-app-shell:has(.lc-app-page__aside)>.lc-app-shell__header{--_aside-width: var(--lc-app-page-aside-width, 400px)}}@media(min-width:1920px){.lc-app-shell{--_nav-width: var(--lc-app-shell-nav-width, 280px)}.lc-app-shell:has(.lc-app-page__aside)>.lc-app-shell__header{--_aside-width: var(--lc-app-page-aside-width, 460px)}}@media(forced-colors:active){.lc-app-shell__header{background:none}}\n"]
|
|
111
144
|
}]
|
|
112
145
|
}],
|
|
@@ -132,6 +165,10 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
132
165
|
type: i0.ViewChild,
|
|
133
166
|
args: ["shell", { isSignal: true }]
|
|
134
167
|
}],
|
|
168
|
+
headerSlot: [{
|
|
169
|
+
type: i0.ViewChild,
|
|
170
|
+
args: ["headerSlot", { isSignal: true }]
|
|
171
|
+
}],
|
|
135
172
|
barSlot: [{
|
|
136
173
|
type: i0.ViewChild,
|
|
137
174
|
args: ["barSlot", { isSignal: true }]
|
|
@@ -146,10 +183,21 @@ var AppPageComponent = class AppPageComponent {
|
|
|
146
183
|
busy = input(false, ...ngDevMode ? [{ debugName: "busy" }] : /* istanbul ignore next */ []);
|
|
147
184
|
paddingX = input("default", ...ngDevMode ? [{ debugName: "paddingX" }] : /* istanbul ignore next */ []);
|
|
148
185
|
paddingTop = input("default", ...ngDevMode ? [{ debugName: "paddingTop" }] : /* istanbul ignore next */ []);
|
|
186
|
+
reserveAside = input(false, ...ngDevMode ? [{ debugName: "reserveAside" }] : /* istanbul ignore next */ []);
|
|
187
|
+
contentWidth = input("default", ...ngDevMode ? [{ debugName: "contentWidth" }] : /* istanbul ignore next */ []);
|
|
149
188
|
hasAside = computed(() => !!(this.asideLabelledby() || this.asideAriaLabel()), ...ngDevMode ? [{ debugName: "hasAside" }] : /* istanbul ignore next */ []);
|
|
189
|
+
wide = computed(() => this.contentWidth() === "wide" && !this.hasAside(), ...ngDevMode ? [{ debugName: "wide" }] : /* istanbul ignore next */ []);
|
|
190
|
+
reserved = computed(() => this.reserveAside() && !this.hasAside() && !this.wide(), ...ngDevMode ? [{ debugName: "reserved" }] : /* istanbul ignore next */ []);
|
|
191
|
+
wideMax = computed(() => {
|
|
192
|
+
if (!this.wide()) return null;
|
|
193
|
+
const max = this.contentMaxWidth();
|
|
194
|
+
if (max === "880px") return null;
|
|
195
|
+
return max === "none" ? "100vw" : max;
|
|
196
|
+
}, ...ngDevMode ? [{ debugName: "wideMax" }] : /* istanbul ignore next */ []);
|
|
150
197
|
pageClasses = computed(() => {
|
|
151
198
|
const classes = ["lc-app-page"];
|
|
152
|
-
if (this.hasAside()) classes.push("lc-app-page--aside");
|
|
199
|
+
if (this.hasAside() || this.reserveAside() || this.wide()) classes.push("lc-app-page--aside");
|
|
200
|
+
if (this.wide()) classes.push("lc-app-page--wide");
|
|
153
201
|
if (this.paddingX() === "header") classes.push("lc-app-page--pad-x-header");
|
|
154
202
|
if (this.paddingTop() !== "default") classes.push(`lc-app-page--pad-top-${this.paddingTop()}`);
|
|
155
203
|
return classes.join(" ");
|
|
@@ -217,11 +265,25 @@ var AppPageComponent = class AppPageComponent {
|
|
|
217
265
|
isSignal: true,
|
|
218
266
|
isRequired: false,
|
|
219
267
|
transformFunction: null
|
|
268
|
+
},
|
|
269
|
+
reserveAside: {
|
|
270
|
+
classPropertyName: "reserveAside",
|
|
271
|
+
publicName: "reserveAside",
|
|
272
|
+
isSignal: true,
|
|
273
|
+
isRequired: false,
|
|
274
|
+
transformFunction: null
|
|
275
|
+
},
|
|
276
|
+
contentWidth: {
|
|
277
|
+
classPropertyName: "contentWidth",
|
|
278
|
+
publicName: "contentWidth",
|
|
279
|
+
isSignal: true,
|
|
280
|
+
isRequired: false,
|
|
281
|
+
transformFunction: null
|
|
220
282
|
}
|
|
221
283
|
},
|
|
222
284
|
ngImport: i0,
|
|
223
|
-
template: "<div [class]=\"pageClasses()\" [style.--lc-app-page-content-max]=\"contentMaxWidth()\">\n <main class=\"lc-app-page__main\" [id]=\"mainId()\" [attr.aria-busy]=\"busy() ? 'true' : null\">\n <div class=\"lc-app-page__content\"><ng-content /></div>\n </main>\n @if (hasAside()) {\n <aside\n
|
|
224
|
-
styles: [":host{display:block;min-width:0}.lc-app-page{--_pad-x: var(--lc-app-page-padding-x, 24px);--_pad-top: var(--lc-app-page-padding-top, 24px);--_gap: var(--lc-app-page-gap, 24px);--_aside-width: var(--lc-app-page-aside-width, 360px);display:grid;grid-template-columns:minmax(0,1fr);min-height:100%}.lc-app-page__main{display:block;min-width:0;padding:var(--_pad-top) var(--_pad-x) 16px}.lc-app-page__content{display:flex;flex-direction:column;gap:var(--_gap);max-width:var(--lc-app-page-content-max, 880px);margin-inline:auto}.lc-app-page__aside{display:none}@media(min-width:640px){.lc-app-page{--_pad-x: var(--lc-app-page-padding-x, 32px);--_pad-top: var(--lc-app-page-padding-top, 32px)}}@media(min-width:1024px){.lc-app-page{--_pad-x: var(--lc-app-page-padding-x, 48px);--_pad-top: var(--lc-app-page-padding-top, 48px);--_gap: var(--lc-app-page-gap, 40px)}.lc-app-page__main{padding-bottom:32px}}@media(min-width:1280px){.lc-app-page{--_pad-x: var(--lc-app-page-padding-x, 56px);--_pad-top: var(--lc-app-page-padding-top, 56px)}.lc-app-page--aside{grid-template-columns:minmax(0,1fr) var(--_aside-width)}.lc-app-page__aside{display:block;min-width:0;box-sizing:border-box;padding:max(16px,var(--_pad-top) - 32px) 40px 40px;background:linear-gradient(180deg,var(--color-background-low),transparent);border-left:1px solid var(--color-hairline, var(--color-border));border-image:linear-gradient(180deg,var(--color-hairline, var(--color-border)) 60%,transparent) 1}}@media(min-width:1440px){.lc-app-page{--_pad-x: var(--lc-app-page-padding-x, 72px);--_pad-top: var(--lc-app-page-padding-top, 72px);--_aside-width: var(--lc-app-page-aside-width, 400px)}}@media(min-width:1920px){.lc-app-page{--_pad-x: var(--lc-app-page-padding-x, 96px);--_pad-top: var(--lc-app-page-padding-top, 112px);--_aside-width: var(--lc-app-page-aside-width, 460px)}}.lc-app-page.lc-app-page--pad-x-header{--_pad-x: var(--lc-app-page-padding-x, var(--lc-density-padding-lg, var(--spacing-6)))}@media(max-width:768px){.lc-app-page.lc-app-page--pad-x-header{--_pad-x: var(--lc-app-page-padding-x, var(--lc-density-padding-md, var(--spacing-4)))}}.lc-app-page.lc-app-page--pad-top-none{--_pad-top: var(--lc-app-page-padding-top, 0px)}.lc-app-page.lc-app-page--pad-top-sm{--_pad-top: var(--lc-app-page-padding-top, var(--spacing-2))}.lc-app-page.lc-app-page--pad-top-md{--_pad-top: var(--lc-app-page-padding-top, var(--spacing-4))}@media(forced-colors:active){.lc-app-page__aside{background:none;border-image:none;border-left-color:CanvasText}}\n"],
|
|
285
|
+
template: "<div [class]=\"pageClasses()\" [style.--lc-app-page-content-max]=\"contentMaxWidth()\" [style.--_lc-app-page-wide-max]=\"wideMax()\">\n <main class=\"lc-app-page__main\" [id]=\"mainId()\" [attr.aria-busy]=\"busy() ? 'true' : null\">\n <div class=\"lc-app-page__content\"><ng-content /></div>\n </main>\n <!-- One outer block, so a page without a right column keeps 3.27.2's single\n anchor (an @else branch adds one); the reserved column (AS-35) is empty,\n aria-hidden and no landmark. -->\n @if (hasAside() || reserved()) {\n @if (hasAside()) {\n <aside\n class=\"lc-app-page__aside\"\n [attr.aria-labelledby]=\"asideLabelledby() || null\"\n [attr.aria-label]=\"asideLabelledby() ? null : asideAriaLabel()\"\n >\n <ng-content select=\"[page-aside]\" />\n </aside>\n } @else {<div class=\"lc-app-page__aside lc-app-page__aside--reserved\" aria-hidden=\"true\"></div>}\n }\n</div>\n",
|
|
286
|
+
styles: [":host{display:block;min-width:0}.lc-app-page{--_pad-x: var(--lc-app-page-padding-x, 24px);--_pad-top: var(--lc-app-page-padding-top, 24px);--_gap: var(--lc-app-page-gap, 24px);--_aside-width: var(--lc-app-page-aside-width, 360px);display:grid;grid-template-columns:minmax(0,1fr);min-height:100%}.lc-app-page__main{display:block;min-width:0;padding:var(--_pad-top) var(--_pad-x) 16px}.lc-app-page__content{display:flex;flex-direction:column;gap:var(--_gap);max-width:var(--lc-app-page-content-max, 880px);margin-inline:auto}.lc-app-page__aside{display:none}@media(min-width:640px){.lc-app-page{--_pad-x: var(--lc-app-page-padding-x, 32px);--_pad-top: var(--lc-app-page-padding-top, 32px)}}@media(min-width:1024px){.lc-app-page{--_pad-x: var(--lc-app-page-padding-x, 48px);--_pad-top: var(--lc-app-page-padding-top, 48px);--_gap: var(--lc-app-page-gap, 40px)}.lc-app-page__main{padding-bottom:32px}}@media(min-width:1280px){.lc-app-page{--_pad-x: var(--lc-app-page-padding-x, 56px);--_pad-top: var(--lc-app-page-padding-top, 56px)}.lc-app-page--aside{grid-template-columns:minmax(0,1fr) var(--_aside-width)}.lc-app-page__aside{display:block;min-width:0;box-sizing:border-box;padding:max(16px,var(--_pad-top) - 32px) 40px 40px;background:linear-gradient(180deg,var(--color-background-low),transparent);border-left:1px solid var(--color-hairline, var(--color-border));border-image:linear-gradient(180deg,var(--color-hairline, var(--color-border)) 60%,transparent) 1}}@media(min-width:1440px){.lc-app-page{--_pad-x: var(--lc-app-page-padding-x, 72px);--_pad-top: var(--lc-app-page-padding-top, 72px);--_aside-width: var(--lc-app-page-aside-width, 400px)}}@media(min-width:1920px){.lc-app-page{--_pad-x: var(--lc-app-page-padding-x, 96px);--_pad-top: var(--lc-app-page-padding-top, 112px);--_aside-width: var(--lc-app-page-aside-width, 460px)}}.lc-app-page.lc-app-page--pad-x-header{--_pad-x: var(--lc-app-page-padding-x, var(--lc-density-padding-lg, var(--spacing-6)))}@media(max-width:768px){.lc-app-page.lc-app-page--pad-x-header{--_pad-x: var(--lc-app-page-padding-x, var(--lc-density-padding-md, var(--spacing-4)))}}.lc-app-page.lc-app-page--pad-top-none{--_pad-top: var(--lc-app-page-padding-top, 0px)}.lc-app-page.lc-app-page--pad-top-sm{--_pad-top: var(--lc-app-page-padding-top, var(--spacing-2))}.lc-app-page.lc-app-page--pad-top-md{--_pad-top: var(--lc-app-page-padding-top, var(--spacing-4))}@media(forced-colors:active){.lc-app-page__aside{background:none;border-image:none;border-left-color:CanvasText}}@media(min-width:1280px){:host ::ng-deep .lc-app-page--wide>.lc-app-page__main>.lc-app-page__content>[page-wide]{box-sizing:border-box;width:min(var(--_lc-app-page-wide-max, 928px),100% + var(--_aside-width));max-width:none}}\n"],
|
|
225
287
|
changeDetection: i0.ChangeDetectionStrategy.OnPush
|
|
226
288
|
});
|
|
227
289
|
};
|
|
@@ -236,8 +298,8 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
236
298
|
selector: "lc-app-page",
|
|
237
299
|
standalone: true,
|
|
238
300
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
239
|
-
template: "<div [class]=\"pageClasses()\" [style.--lc-app-page-content-max]=\"contentMaxWidth()\">\n <main class=\"lc-app-page__main\" [id]=\"mainId()\" [attr.aria-busy]=\"busy() ? 'true' : null\">\n <div class=\"lc-app-page__content\"><ng-content /></div>\n </main>\n @if (hasAside()) {\n <aside\n
|
|
240
|
-
styles: [":host{display:block;min-width:0}.lc-app-page{--_pad-x: var(--lc-app-page-padding-x, 24px);--_pad-top: var(--lc-app-page-padding-top, 24px);--_gap: var(--lc-app-page-gap, 24px);--_aside-width: var(--lc-app-page-aside-width, 360px);display:grid;grid-template-columns:minmax(0,1fr);min-height:100%}.lc-app-page__main{display:block;min-width:0;padding:var(--_pad-top) var(--_pad-x) 16px}.lc-app-page__content{display:flex;flex-direction:column;gap:var(--_gap);max-width:var(--lc-app-page-content-max, 880px);margin-inline:auto}.lc-app-page__aside{display:none}@media(min-width:640px){.lc-app-page{--_pad-x: var(--lc-app-page-padding-x, 32px);--_pad-top: var(--lc-app-page-padding-top, 32px)}}@media(min-width:1024px){.lc-app-page{--_pad-x: var(--lc-app-page-padding-x, 48px);--_pad-top: var(--lc-app-page-padding-top, 48px);--_gap: var(--lc-app-page-gap, 40px)}.lc-app-page__main{padding-bottom:32px}}@media(min-width:1280px){.lc-app-page{--_pad-x: var(--lc-app-page-padding-x, 56px);--_pad-top: var(--lc-app-page-padding-top, 56px)}.lc-app-page--aside{grid-template-columns:minmax(0,1fr) var(--_aside-width)}.lc-app-page__aside{display:block;min-width:0;box-sizing:border-box;padding:max(16px,var(--_pad-top) - 32px) 40px 40px;background:linear-gradient(180deg,var(--color-background-low),transparent);border-left:1px solid var(--color-hairline, var(--color-border));border-image:linear-gradient(180deg,var(--color-hairline, var(--color-border)) 60%,transparent) 1}}@media(min-width:1440px){.lc-app-page{--_pad-x: var(--lc-app-page-padding-x, 72px);--_pad-top: var(--lc-app-page-padding-top, 72px);--_aside-width: var(--lc-app-page-aside-width, 400px)}}@media(min-width:1920px){.lc-app-page{--_pad-x: var(--lc-app-page-padding-x, 96px);--_pad-top: var(--lc-app-page-padding-top, 112px);--_aside-width: var(--lc-app-page-aside-width, 460px)}}.lc-app-page.lc-app-page--pad-x-header{--_pad-x: var(--lc-app-page-padding-x, var(--lc-density-padding-lg, var(--spacing-6)))}@media(max-width:768px){.lc-app-page.lc-app-page--pad-x-header{--_pad-x: var(--lc-app-page-padding-x, var(--lc-density-padding-md, var(--spacing-4)))}}.lc-app-page.lc-app-page--pad-top-none{--_pad-top: var(--lc-app-page-padding-top, 0px)}.lc-app-page.lc-app-page--pad-top-sm{--_pad-top: var(--lc-app-page-padding-top, var(--spacing-2))}.lc-app-page.lc-app-page--pad-top-md{--_pad-top: var(--lc-app-page-padding-top, var(--spacing-4))}@media(forced-colors:active){.lc-app-page__aside{background:none;border-image:none;border-left-color:CanvasText}}\n"]
|
|
301
|
+
template: "<div [class]=\"pageClasses()\" [style.--lc-app-page-content-max]=\"contentMaxWidth()\" [style.--_lc-app-page-wide-max]=\"wideMax()\">\n <main class=\"lc-app-page__main\" [id]=\"mainId()\" [attr.aria-busy]=\"busy() ? 'true' : null\">\n <div class=\"lc-app-page__content\"><ng-content /></div>\n </main>\n <!-- One outer block, so a page without a right column keeps 3.27.2's single\n anchor (an @else branch adds one); the reserved column (AS-35) is empty,\n aria-hidden and no landmark. -->\n @if (hasAside() || reserved()) {\n @if (hasAside()) {\n <aside\n class=\"lc-app-page__aside\"\n [attr.aria-labelledby]=\"asideLabelledby() || null\"\n [attr.aria-label]=\"asideLabelledby() ? null : asideAriaLabel()\"\n >\n <ng-content select=\"[page-aside]\" />\n </aside>\n } @else {<div class=\"lc-app-page__aside lc-app-page__aside--reserved\" aria-hidden=\"true\"></div>}\n }\n</div>\n",
|
|
302
|
+
styles: [":host{display:block;min-width:0}.lc-app-page{--_pad-x: var(--lc-app-page-padding-x, 24px);--_pad-top: var(--lc-app-page-padding-top, 24px);--_gap: var(--lc-app-page-gap, 24px);--_aside-width: var(--lc-app-page-aside-width, 360px);display:grid;grid-template-columns:minmax(0,1fr);min-height:100%}.lc-app-page__main{display:block;min-width:0;padding:var(--_pad-top) var(--_pad-x) 16px}.lc-app-page__content{display:flex;flex-direction:column;gap:var(--_gap);max-width:var(--lc-app-page-content-max, 880px);margin-inline:auto}.lc-app-page__aside{display:none}@media(min-width:640px){.lc-app-page{--_pad-x: var(--lc-app-page-padding-x, 32px);--_pad-top: var(--lc-app-page-padding-top, 32px)}}@media(min-width:1024px){.lc-app-page{--_pad-x: var(--lc-app-page-padding-x, 48px);--_pad-top: var(--lc-app-page-padding-top, 48px);--_gap: var(--lc-app-page-gap, 40px)}.lc-app-page__main{padding-bottom:32px}}@media(min-width:1280px){.lc-app-page{--_pad-x: var(--lc-app-page-padding-x, 56px);--_pad-top: var(--lc-app-page-padding-top, 56px)}.lc-app-page--aside{grid-template-columns:minmax(0,1fr) var(--_aside-width)}.lc-app-page__aside{display:block;min-width:0;box-sizing:border-box;padding:max(16px,var(--_pad-top) - 32px) 40px 40px;background:linear-gradient(180deg,var(--color-background-low),transparent);border-left:1px solid var(--color-hairline, var(--color-border));border-image:linear-gradient(180deg,var(--color-hairline, var(--color-border)) 60%,transparent) 1}}@media(min-width:1440px){.lc-app-page{--_pad-x: var(--lc-app-page-padding-x, 72px);--_pad-top: var(--lc-app-page-padding-top, 72px);--_aside-width: var(--lc-app-page-aside-width, 400px)}}@media(min-width:1920px){.lc-app-page{--_pad-x: var(--lc-app-page-padding-x, 96px);--_pad-top: var(--lc-app-page-padding-top, 112px);--_aside-width: var(--lc-app-page-aside-width, 460px)}}.lc-app-page.lc-app-page--pad-x-header{--_pad-x: var(--lc-app-page-padding-x, var(--lc-density-padding-lg, var(--spacing-6)))}@media(max-width:768px){.lc-app-page.lc-app-page--pad-x-header{--_pad-x: var(--lc-app-page-padding-x, var(--lc-density-padding-md, var(--spacing-4)))}}.lc-app-page.lc-app-page--pad-top-none{--_pad-top: var(--lc-app-page-padding-top, 0px)}.lc-app-page.lc-app-page--pad-top-sm{--_pad-top: var(--lc-app-page-padding-top, var(--spacing-2))}.lc-app-page.lc-app-page--pad-top-md{--_pad-top: var(--lc-app-page-padding-top, var(--spacing-4))}@media(forced-colors:active){.lc-app-page__aside{background:none;border-image:none;border-left-color:CanvasText}}@media(min-width:1280px){:host ::ng-deep .lc-app-page--wide>.lc-app-page__main>.lc-app-page__content>[page-wide]{box-sizing:border-box;width:min(var(--_lc-app-page-wide-max, 928px),100% + var(--_aside-width));max-width:none}}\n"]
|
|
241
303
|
}]
|
|
242
304
|
}],
|
|
243
305
|
propDecorators: {
|
|
@@ -296,6 +358,22 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
296
358
|
alias: "paddingTop",
|
|
297
359
|
required: false
|
|
298
360
|
}]
|
|
361
|
+
}],
|
|
362
|
+
reserveAside: [{
|
|
363
|
+
type: i0.Input,
|
|
364
|
+
args: [{
|
|
365
|
+
isSignal: true,
|
|
366
|
+
alias: "reserveAside",
|
|
367
|
+
required: false
|
|
368
|
+
}]
|
|
369
|
+
}],
|
|
370
|
+
contentWidth: [{
|
|
371
|
+
type: i0.Input,
|
|
372
|
+
args: [{
|
|
373
|
+
isSignal: true,
|
|
374
|
+
alias: "contentWidth",
|
|
375
|
+
required: false
|
|
376
|
+
}]
|
|
299
377
|
}]
|
|
300
378
|
}
|
|
301
379
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecorner-angular-ui-kit-app-shell.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/app-shell/app-shell.component.ts","../../../../libs/angular-ui-kit/src/lib/app-shell/app-shell.component.html","../../../../libs/angular-ui-kit/src/lib/app-shell/app-page.component.ts","../../../../libs/angular-ui-kit/src/lib/app-shell/app-page.component.html"],"sourcesContent":["import {\n afterNextRender,\n ChangeDetectionStrategy,\n Component,\n computed,\n DestroyRef,\n ElementRef,\n inject,\n input,\n viewChild,\n} from '@angular/core';\n\n/**\n * Private custom property the shell writes on `.lc-app-shell` in the browser:\n * the bar's content height. Its CSS fallback is 72 px (4 + 64 + 4 at 100 % text).\n */\nconst BAR_VAR = '--_lc-app-shell-bar';\n\n/**\n * Where the side navigation sits from 1024 px: `'below-header'` (the header\n * spans the full width, the nav starts below it) or `'full-height'` (the nav\n * runs from the top to the bottom of the viewport, the header beside it).\n */\nexport type AppShellNavPlacement = 'below-header' | 'full-height';\n\n/**\n * App shell: the frame around every page of an app — header, side navigation,\n * page, bottom navigation bar, footer and the horizon — laid out for phones\n * first, with the DOCUMENT scrolling (not an inner container), so the iOS\n * status-bar tap, address-bar collapse and pull-to-refresh keep working.\n *\n * Slots (content projection by attribute):\n * - `[shell-header]` — the `lc-header`; scrolls away with the page. Gets the\n * top, left and right safe-area insets.\n * - `[shell-nav]` — the docked side navigation (`lc-sidenav mode=\"docked\"\n * [isOpen]=\"true\" width=\"100%\" [mobileBreakpoint]=\"0\"`), shown from\n * 1024 px in a column of 240 px (248 from 1440, 280 from 1920; override\n * with `--lc-app-shell-nav-width`) and sticky once the header has gone.\n * An empty slot takes no column.\n * - default — the page, usually an `lc-app-page` (or a `<router-outlet>`\n * whose pages use one). It spans to the window's right edge, so a page's\n * right column docks there.\n * - `[shell-bottom-nav]` — the `lc-bottom-nav`, shown below 1024 px. The shell\n * reserves its real height at the end of the page: at least 112 px (16 + 72\n * + 24) plus the safe-area inset, more when the bar grows with the text size\n * (measured in the browser; `--lc-app-shell-bottom-clearance` overrides it),\n * so it never covers content. With the horizon, the bar rests on the land\n * instead, and the land grows with a taller bar (3.10.1, AS-31).\n * - `[shell-footer]` — e.g. a compact `lc-footer`; after the page, or with\n * `horizon` in a row of its own below the horizon, painted in the land's\n * colour (`--color-land-3`): the land grows by the footer's height, so\n * `--lc-horizon-land` stays the land above the footer and no page overrides\n * it (3.9, LK-12).\n * - `[shell-horizon-figure]` — Edu (or any decorative figure) on the horizon\n * line, left of the sun: `lc-character`, `<img>`, `<picture>` or an element\n * holding an inline `<svg>`. Projected as a direct child of `.lc-horizon`,\n * sized and placed by the horizon (`--lc-horizon-figure-size` / `-gap`).\n * Decorative: the horizon is `aria-hidden`, so give an `<img>` `alt=\"\"` and\n * nothing focusable. Shown only when `horizon` is on (3.9, AS-29).\n *\n * `horizon` closes the page with the \"Weite\" horizon (`.lc-horizon` from the\n * kit's styles): full-bleed, below the side navigation and the page, filling\n * the rest of the screen — on Today, the sign-in pages and the shell pages.\n *\n * `navPlacement=\"full-height\"` (3.14) runs the nav column from the top to the\n * bottom of the viewport (sticky, 100dvh) with the header beside it, from\n * 1024 px; below 1024 px it renders like the default. The DOM and tab order\n * stay header → nav → page → footer, so in full-height the header's controls\n * come before a brand at the top of the nav (banner, then navigation, then\n * content). The land and the footer run under the nav; the footer's content\n * starts at the nav width.\n *\n * CSS hooks (set them on `lc-app-shell` or any ancestor):\n * - `--lc-app-shell-nav-width` — the nav column from 1024 px. Default\n * `240px` (`248px` from 1440, `280px` from 1920).\n * - `--lc-app-shell-bottom-clearance` — the space kept free for the bottom\n * bar below 1024 px. Default: the bar's height + 40 px + the inset, at\n * least `112px` + the inset.\n * - `--lc-app-shell-bg` — the page colour behind everything (the sky).\n * Default `var(--color-background)`.\n * - `--lc-app-shell-bg-image` — a layer over the page colour. Default\n * `var(--app-background-image, none)`. A tint, for example:\n * `linear-gradient(to bottom, var(--color-land-1), var(--color-land-2), var(--color-land-3))`.\n * The horizon's land still paints over it.\n *\n * Needs `<meta name=\"viewport\" content=\"width=device-width, initial-scale=1,\n * viewport-fit=cover\">` for the safe-area insets. Breakpoints are fixed\n * (1024 px side navigation, 1440 / 1920 px widths), so the layout works\n * before JavaScript runs (SSR, prerendering).\n *\n * @example\n * ```html\n * <lc-app-shell [horizon]=\"isToday()\">\n * <lc-header shell-header variant=\"transparent\" [sticky]=\"false\" … />\n * <lc-sidenav shell-nav mode=\"docked\" [isOpen]=\"true\" width=\"100%\" [mobileBreakpoint]=\"0\"\n * variant=\"transparent\" [items]=\"nav\" [activeRoute]=\"url()\" />\n * <router-outlet />\n * <lc-bottom-nav shell-bottom-nav variant=\"floating\" [items]=\"nav\" [activeRoute]=\"url()\" />\n * </lc-app-shell>\n * ```\n */\n@Component({\n selector: 'lc-app-shell',\n standalone: true,\n templateUrl: './app-shell.component.html',\n styleUrl: './app-shell.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class AppShellComponent {\n /**\n * Close the page with the horizon (`.lc-horizon`): full width, below the\n * side navigation and the page, filling the rest of the screen; the footer\n * (in a row of its own, which adds to the land) and the floating bar rest\n * on its land, and `[shell-horizon-figure]` stands on its line.\n * @default false\n */\n readonly horizon = input(false);\n\n /**\n * Where the side navigation sits from 1024 px. `'below-header'`: the header\n * spans the full width and the nav starts below it. `'full-height'`: the nav\n * runs from the top to the bottom of the viewport, with the header beside\n * it. Below 1024 px both render the same. The DOM and tab order don't change\n * (header, nav, page). CSS only, so it holds before JavaScript runs.\n * `'full-height'` expects a brand block at the top of the rail\n * (`lc-sidenav brandName` or `showLogo`, with a `logoSize` that matches the\n * header's `size`, e.g. both `md`: 64 px) and the header's `showLogo` off.\n * @default 'below-header'\n */\n readonly navPlacement = input<AppShellNavPlacement>('below-header');\n\n protected readonly shellClasses = computed(() => {\n const classes = ['lc-app-shell'];\n if (this.horizon()) classes.push('lc-app-shell--horizon');\n if (this.navPlacement() === 'full-height') classes.push('lc-app-shell--nav-full-height');\n return classes.join(' ');\n });\n\n private readonly shell = viewChild.required<ElementRef<HTMLElement>>('shell');\n private readonly barSlot = viewChild.required<ElementRef<HTMLElement>>('barSlot');\n\n constructor() {\n const destroyRef = inject(DestroyRef);\n // Browser only (afterNextRender never runs on the server): follow the\n // bar's content height, so the clearance and the land grow with a bar\n // that grows with the text size (AS-31). Without ResizeObserver, and\n // before this runs, the CSS fallback (72 px → 112 px clearance) holds.\n afterNextRender(() => {\n if (typeof ResizeObserver === 'undefined') return;\n const shell = this.shell().nativeElement;\n const slot = this.barSlot().nativeElement;\n const clear = () => shell.style.removeProperty(BAR_VAR);\n const sizes = new ResizeObserver((entries) => {\n const entry = entries[entries.length - 1];\n const height = entry.contentBoxSize?.[0]?.blockSize ?? entry.contentRect.height;\n // 0 when the bar is hidden (from 1024 px).\n if (height > 0) shell.style.setProperty(BAR_VAR, `${height}px`);\n else clear();\n });\n let bar: Element | null = null;\n // The bar may come later (`@if (items())` around it) or go again.\n const pick = () => {\n const next = slot.querySelector('.lc-bottom-nav') ?? slot.firstElementChild;\n if (next === bar) return;\n if (bar) sizes.unobserve(bar);\n bar = next;\n if (bar) sizes.observe(bar);\n else clear();\n };\n pick();\n const changes = typeof MutationObserver === 'undefined' ? null : new MutationObserver(pick);\n changes?.observe(slot, { childList: true, subtree: true });\n destroyRef.onDestroy(() => {\n sizes.disconnect();\n changes?.disconnect();\n });\n });\n }\n}\n","<div #shell [class]=\"shellClasses()\">\n <div class=\"lc-app-shell__header\"><ng-content select=\"[shell-header]\" /></div>\n <div class=\"lc-app-shell__nav\"><ng-content select=\"[shell-nav]\" /></div>\n <div class=\"lc-app-shell__body\"><ng-content /></div>\n @if (horizon()) {\n <div class=\"lc-app-shell__horizon lc-horizon\" aria-hidden=\"true\"><ng-content select=\"[shell-horizon-figure]\" /></div>\n }\n <div class=\"lc-app-shell__footer\"><ng-content select=\"[shell-footer]\" /></div>\n <div #barSlot class=\"lc-app-shell__bottom-nav\"><ng-content select=\"[shell-bottom-nav]\" /></div>\n</div>\n","import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';\n\n/**\n * Horizontal padding of `lc-app-page` (3.15).\n * - `default`: 24 · 32 · 48 · 56 · 72 · 96 px from 0 · 640 · 1024 · 1280 · 1440 · 1920 px.\n * - `header`: `lc-header`'s padding (16 px up to 768 px, 24 px above), so the\n * content lines up with the header title and the user menu.\n */\nexport type AppPagePaddingX = 'default' | 'header';\n\n/**\n * Space above `lc-app-page`'s content (3.15).\n * - `default`: 24 · 32 · 48 · 56 · 72 · 112 px by width.\n * - `none` 0, `sm` 8 px, `md` 16 px at every width.\n */\nexport type AppPagePaddingTop = 'default' | 'none' | 'sm' | 'md';\n\n/**\n * A page inside `lc-app-shell`: the `<main>` landmark with the page's content\n * centred at a reading width, and an optional right column docked to the\n * window's right edge (LK-6).\n *\n * - `<main [id]=\"mainId\">` holds the default slot as a column (24 px apart,\n * 40 px from 1024 px), centred and capped at `contentMaxWidth` (880 px of\n * content). Side padding 24 · 32 · 48 · 56 · 72 · 96 px at 360 · 640 ·\n * 1024 · 1280 · 1440 · 1920 px.\n * - `[page-aside]` is the right column: an `<aside>` AFTER `<main>` in the DOM,\n * shown from 1280 px at 360 px (400 from 1440, 460 from 1920; override\n * with `--lc-app-page-aside-width`), with a tint and a hairline that fade\n * downwards; inside `lc-app-shell` they start at the top of the window,\n * behind a transparent header. Below 1280 px it is hidden: show the page's narrow equivalent\n * inside main instead (e.g. a member row). It renders only with an\n * accessible name — `asideLabelledby` (the id of its heading) or\n * `asideAriaLabel`.\n * - `busy` marks the page as loading: `aria-busy=\"true\"` on `<main>` while a\n * skeleton stands in for the content (AS-12). No visual change.\n * - `paddingX=\"header\"` lines the content up with `lc-header` (3.15): its\n * title and its user menu. Combine it with `contentMaxWidth=\"none\"` (or a\n * page narrower than the cap); centred content doesn't align.\n * `paddingTop` sets the space above the content (`none`, `sm` 8 px, `md`\n * 16 px), e.g. `md` with `lc-page-header paddingTop=\"none\"` puts the `h1`\n * 16 px below the header.\n *\n * CSS hooks (set them on `lc-app-page` or any ancestor; each wins over the\n * inputs and holds one value at every width):\n * - `--lc-app-page-padding-x`: the side padding (default: `paddingX`).\n * Per breakpoint, set it inside your own media query:\n * `lc-app-page { --lc-app-page-padding-x: 16px }` and\n * `@media (min-width: 1024px) { lc-app-page { --lc-app-page-padding-x: 32px } }`.\n * - `--lc-app-page-padding-top`: the space above the content (default: `paddingTop`).\n * - `--lc-app-page-gap`: the space between the content's children (24 px, 40 px from 1024 px).\n * - `--lc-app-page-aside-width`: the right column (360 px, 400 from 1440, 460 from 1920).\n *\n * @example\n * ```html\n * <lc-app-page asideLabelledby=\"members-title\">\n * <lc-page-header … />\n * <lc-next-step-card … />\n * <section page-aside>\n * <h2 id=\"members-title\">Mitglieder</h2>\n * <lc-list [items]=\"members\" />\n * </section>\n * </lc-app-page>\n * ```\n */\n@Component({\n selector: 'lc-app-page',\n standalone: true,\n templateUrl: './app-page.component.html',\n styleUrl: './app-page.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class AppPageComponent {\n /** Id of the `<main>` element (the skip link's target). */\n readonly mainId = input('main');\n\n /**\n * Widest the page content gets (CSS length; `none` for no cap). The default\n * keeps lines readable on wide screens: the page gains air, not line length.\n */\n readonly contentMaxWidth = input('880px');\n\n /** Id of the heading that names the right column (`aria-labelledby`). */\n readonly asideLabelledby = input('');\n\n /** Accessible name of the right column when it has no visible heading. */\n readonly asideAriaLabel = input('');\n\n /**\n * Marks the page as loading: `aria-busy=\"true\"` on its `<main>` while\n * `true`, no attribute while `false`. Set it while a skeleton stands in for\n * the content. Screen readers may hold back changes inside a busy region\n * until it clears, so a loading message that must be heard belongs outside\n * `<main>` (for example a `.sr-only` `role=\"status\"` next to the page) or is\n * static text. No visual change.\n * @default false\n */\n readonly busy = input(false);\n\n /**\n * Horizontal page padding. `'default'`: 24/32/48/56/72/96 px from\n * 0/640/1024/1280/1440/1920 px. `'header'`: the same padding as `lc-header`\n * (16 px up to 768 px, 24 px above, from `--lc-density-padding-md`/`-lg`),\n * so the content lines up with the header title and the user menu. Combine\n * it with `contentMaxWidth=\"none\"` (or a page narrower than the cap).\n * `--lc-app-page-padding-x` wins over both.\n * @default 'default'\n */\n readonly paddingX = input<AppPagePaddingX>('default');\n\n /**\n * Space above the page content. `'default'`: 24/32/48/56/72/112 px by\n * width. `'none'` 0, `'sm'` 8 px, `'md'` 16 px at every width.\n * `--lc-app-page-padding-top` wins.\n * @default 'default'\n */\n readonly paddingTop = input<AppPagePaddingTop>('default');\n\n /** The right column renders only when it has an accessible name. */\n protected readonly hasAside = computed(() => !!(this.asideLabelledby() || this.asideAriaLabel()));\n\n protected readonly pageClasses = computed(() => {\n const classes = ['lc-app-page'];\n if (this.hasAside()) classes.push('lc-app-page--aside');\n if (this.paddingX() === 'header') classes.push('lc-app-page--pad-x-header');\n if (this.paddingTop() !== 'default') classes.push(`lc-app-page--pad-top-${this.paddingTop()}`);\n return classes.join(' ');\n });\n}\n","<div [class]=\"pageClasses()\" [style.--lc-app-page-content-max]=\"contentMaxWidth()\">\n <main class=\"lc-app-page__main\" [id]=\"mainId()\" [attr.aria-busy]=\"busy() ? 'true' : null\">\n <div class=\"lc-app-page__content\"><ng-content /></div>\n </main>\n @if (hasAside()) {\n <aside\n class=\"lc-app-page__aside\"\n [attr.aria-labelledby]=\"asideLabelledby() || null\"\n [attr.aria-label]=\"asideLabelledby() ? null : asideAriaLabel()\"\n >\n <ng-content select=\"[page-aside]\" />\n </aside>\n }\n</div>\n"],"mappings":";;AAgBA,MAAM,UAAU;AA4FhB,IAAa,oBAAb,MAAa,kBAAiB;CAQnB,UAAU,MAAM,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CAatB,eAAe,MAA4B,gBAAe,GAAA,YAAA,CAAA,EAAA,WAAA,eAAA,CAAA,+BAAA,CAAA,CAAA;CAEhD,eAAe,eAAc;EAC9C,MAAM,UAAU,CAAC,cAAc;EAC/B,IAAI,KAAK,QAAO,GAAI,QAAQ,KAAK,uBAAuB;EACxD,IAAI,KAAK,aAAY,MAAO,eAAe,QAAQ,KAAK,+BAA+B;EACvF,OAAO,QAAQ,KAAK,GAAG;CACzB,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,eAAA,CAAA,+BAAA,CAAA,CAAA;CAEe,QAAQ,UAAU,SAAkC,SAAQ,GAAA,YAAA,CAAA,EAAA,WAAA,QAAA,CAAA,+BAAA,CAAA,CAAA;CAC5D,UAAU,UAAU,SAAkC,WAAU,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CAEjF,cAAA;EACE,MAAM,aAAa,OAAO,UAAU;EAKpC,sBAAqB;GACnB,IAAI,OAAO,mBAAmB,aAAa;GAC3C,MAAM,QAAQ,KAAK,MAAK,CAAE,CAAC;GAC3B,MAAM,OAAO,KAAK,QAAO,CAAE,CAAC;GAC5B,MAAM,cAAc,MAAM,MAAM,eAAe,OAAO;GACtD,MAAM,QAAQ,IAAI,gBAAgB,YAAW;IAC3C,MAAM,QAAQ,QAAQ,QAAQ,SAAS;IACvC,MAAM,SAAS,MAAM,iBAAiB,EAAE,EAAE,aAAa,MAAM,YAAY;IAEzE,IAAI,SAAS,GAAG,MAAM,MAAM,YAAY,SAAS,GAAG,OAAM,GAAI;SACzD,MAAK;GACZ,CAAC;GACD,IAAI,MAAsB;GAE1B,MAAM,aAAY;IAChB,MAAM,OAAO,KAAK,cAAc,gBAAgB,KAAK,KAAK;IAC1D,IAAI,SAAS,KAAK;IAClB,IAAI,KAAK,MAAM,UAAU,GAAG;IAC5B,MAAM;IACN,IAAI,KAAK,MAAM,QAAQ,GAAG;SACrB,MAAK;GACZ;GACA,KAAI;GACJ,MAAM,UAAU,OAAO,qBAAqB,cAAc,OAAO,IAAI,iBAAiB,IAAI;GAC1F,SAAS,QAAQ,MAAM;IAAE,WAAW;IAAM,SAAS;GAAI,CAAE;GACzD,WAAW,gBAAe;IACxB,MAAM,WAAU;IAChB,SAAS,WAAU;GACrB,CAAC;EACH,CAAC;CACH;CArEW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAiB,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAjB,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAAiB,cAAA;EAAA,UAAA;EAAA,QAAA;GAAA,SAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,cAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;EAAA;EAAA,aAAA,CAAA;GAAA,cAAA;GAAA,OAAA;GAAA,WAAA,CAAA,OAAA;GAAA,aAAA;GAAA,UAAA;EAAA,GAAA;GAAA,cAAA;GAAA,OAAA;GAAA,WAAA,CAAA,SAAA;GAAA,aAAA;GAAA,UAAA;EAAA,CAAA;EAAA,UAAA;EAAA,UC5G9B;EAUA,QAAA,CAAA,29HAAA;EAAA,iBAAA,GAAA,wBAAA;CAAA,CAAA;;ADkGa,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAiB,YAAA,CAAA;EAP7B,MAAA;EACW,MAAA,CAAA;GAAA,UAAA;GAAc,YACZ;GAAI,iBAGC,wBAAwB;GAAM,UAAA;GAAA,QAAA,CAAA,29HAAA;EAAA,CAAA;CAgCsB,CAAA;CAAA,sBAAA,CAAA;CAAA,gBAAA;EAAA,SAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,cAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA;IAAA,UAAA;IAAA,OAAA;IAAA,UAAA;GAAA,CAAA;EAAA,CAAA;EAAA,OAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CAAA,SAAO,EAAA,UAAA,KAAA,CAAA;EAAA,CAAA;EAAA,SAAA,CAAA;GAAA,MAAA,GAAA;GAAA,MAAA,CACL,WAAS,EAAA,UAAA,KAAA,CAAA;EAAA,CAAA;CAAA;AAAA,CAAA;AEnElF,IAAa,mBAAb,MAAa,iBAAgB;CAElB,SAAS,MAAM,QAAO,GAAA,YAAA,CAAA,EAAA,WAAA,SAAA,CAAA,+BAAA,CAAA,CAAA;CAMtB,kBAAkB,MAAM,SAAQ,GAAA,YAAA,CAAA,EAAA,WAAA,kBAAA,CAAA,+BAAA,CAAA,CAAA;CAGhC,kBAAkB,MAAM,IAAG,GAAA,YAAA,CAAA,EAAA,WAAA,kBAAA,CAAA,+BAAA,CAAA,CAAA;CAG3B,iBAAiB,MAAM,IAAG,GAAA,YAAA,CAAA,EAAA,WAAA,iBAAA,CAAA,+BAAA,CAAA,CAAA;CAW1B,OAAO,MAAM,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,OAAA,CAAA,+BAAA,CAAA,CAAA;CAWnB,WAAW,MAAuB,WAAU,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;CAQ5C,aAAa,MAAyB,WAAU,GAAA,YAAA,CAAA,EAAA,WAAA,aAAA,CAAA,+BAAA,CAAA,CAAA;CAGtC,WAAW,eAAe,CAAC,EAAE,KAAK,gBAAe,KAAM,KAAK,eAAc,IAAI,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;CAE9E,cAAc,eAAc;EAC7C,MAAM,UAAU,CAAC,aAAa;EAC9B,IAAI,KAAK,SAAQ,GAAI,QAAQ,KAAK,oBAAoB;EACtD,IAAI,KAAK,SAAQ,MAAO,UAAU,QAAQ,KAAK,2BAA2B;EAC1E,IAAI,KAAK,WAAU,MAAO,WAAW,QAAQ,KAAK,wBAAwB,KAAK,WAAU,GAAI;EAC7F,OAAO,QAAQ,KAAK,GAAG;CACzB,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,cAAA,CAAA,+BAAA,CAAA,CAAA;CAvDS,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAgB,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAhB,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAAgB,cAAA;EAAA,UAAA;EAAA,QAAA;GAAA,QAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,iBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,iBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,gBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,MAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,UAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,YAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;EAAA;EAAA,UAAA;EAAA,UCxE7B;EAcA,QAAA,CAAA,m9EAAA;EAAA,iBAAA,GAAA,wBAAA;CAAA,CAAA;;AD0Da,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAgB,YAAA,CAAA;EAP5B,MAAA;EACW,MAAA,CAAA;GAAA,UAAA;GAAa,YACX;GAAI,iBAGC,wBAAwB;GAAM,UAAA;GAAA,QAAA,CAAA,m9EAAA;EAAA,CAAA"}
|
|
1
|
+
{"version":3,"file":"lifecorner-angular-ui-kit-app-shell.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/app-shell/app-shell.component.ts","../../../../libs/angular-ui-kit/src/lib/app-shell/app-shell.component.html","../../../../libs/angular-ui-kit/src/lib/app-shell/app-page.component.ts","../../../../libs/angular-ui-kit/src/lib/app-shell/app-page.component.html"],"sourcesContent":["import {\n afterNextRender,\n ChangeDetectionStrategy,\n Component,\n computed,\n DestroyRef,\n ElementRef,\n inject,\n input,\n viewChild,\n} from '@angular/core';\nimport { OverlayInsetsService } from '@lifecorner/angular-ui-kit/internal/overlay';\n\n/**\n * Private custom property the shell writes on `.lc-app-shell` in the browser:\n * the bar's content height. Its CSS fallback is 72 px (4 + 64 + 4 at 100 % text).\n */\nconst BAR_VAR = '--_lc-app-shell-bar';\n\n/** What a fixed overlay keeps free without a header or without a shown bar (AS-34). */\nconst TOP_INSET = 'env(safe-area-inset-top, 0px)';\nconst BOTTOM_INSET = 'env(safe-area-inset-bottom, 0px)';\n\n/** The border-box height of a ResizeObserver entry, with a layout read where the browser doesn't report it. */\nconst borderBoxHeight = (entry: ResizeObserverEntry, target: Element) =>\n entry.borderBoxSize?.[0]?.blockSize ?? target.getBoundingClientRect().height;\n\n/**\n * Where the side navigation sits from 1024 px: `'below-header'` (the header\n * spans the full width, the nav starts below it) or `'full-height'` (the nav\n * runs from the top to the bottom of the viewport, the header beside it).\n */\nexport type AppShellNavPlacement = 'below-header' | 'full-height';\n\n/**\n * App shell: the frame around every page of an app — header, side navigation,\n * page, bottom navigation bar, footer and the horizon — laid out for phones\n * first, with the DOCUMENT scrolling (not an inner container), so the iOS\n * status-bar tap, address-bar collapse and pull-to-refresh keep working.\n *\n * Slots (content projection by attribute):\n * - `[shell-header]` — the `lc-header`; scrolls away with the page. Gets the\n * top, left and right safe-area insets.\n * - `[shell-nav]` — the docked side navigation (`lc-sidenav mode=\"docked\"\n * [isOpen]=\"true\" width=\"100%\" [mobileBreakpoint]=\"0\"`), shown from\n * 1024 px in a column of 240 px (248 from 1440, 280 from 1920; override\n * with `--lc-app-shell-nav-width`) and sticky once the header has gone.\n * An empty slot takes no column.\n * - default — the page, usually an `lc-app-page` (or a `<router-outlet>`\n * whose pages use one). It spans to the window's right edge, so a page's\n * right column docks there.\n * - `[shell-bottom-nav]` — the `lc-bottom-nav`, shown below 1024 px. The shell\n * reserves its real height at the end of the page: at least 112 px (16 + 72\n * + 24) plus the safe-area inset, more when the bar grows with the text size\n * (measured in the browser; `--lc-app-shell-bottom-clearance` overrides it),\n * so it never covers content. With the horizon, the bar rests on the land\n * instead, and the land grows with a taller bar (3.10.1, AS-31).\n * - `[shell-footer]` — e.g. a compact `lc-footer`; after the page, or with\n * `horizon` in a row of its own below the horizon, painted in the land's\n * colour (`--color-land-3`): the land grows by the footer's height, so\n * `--lc-horizon-land` stays the land above the footer and no page overrides\n * it (3.9, LK-12).\n * - `[shell-horizon-figure]` — Edu (or any decorative figure) on the horizon\n * line, left of the sun: `lc-character`, `<img>`, `<picture>` or an element\n * holding an inline `<svg>`. Projected as a direct child of `.lc-horizon`,\n * sized and placed by the horizon (`--lc-horizon-figure-size` / `-gap`).\n * Decorative: the horizon is `aria-hidden`, so give an `<img>` `alt=\"\"` and\n * nothing focusable. Shown only when `horizon` is on (3.9, AS-29).\n *\n * `horizon` closes the page with the \"Weite\" horizon (`.lc-horizon` from the\n * kit's styles): full-bleed, below the side navigation and the page, filling\n * the rest of the screen — on Today, the sign-in pages and the shell pages.\n *\n * `navPlacement=\"full-height\"` (3.14) runs the nav column from the top to the\n * bottom of the viewport (sticky, 100dvh) with the header beside it, from\n * 1024 px; below 1024 px it renders like the default. The DOM and tab order\n * stay header → nav → page → footer, so in full-height the header's controls\n * come before a brand at the top of the nav (banner, then navigation, then\n * content). The land and the footer run under the nav; the footer's content\n * starts at the nav width.\n *\n * Toasts (3.28, AS-34): in the browser the shell tells `lc-toast-outlet` —\n * wherever the app puts it, also after `</lc-app-shell>` — where its header\n * ends and where the shown bar starts, so top toasts sit below the header and\n * bottom toasts 16 px above the bar (from 1024 px: above the safe-area\n * inset). `--lc-toast-outlet-offset-top` / `-bottom` override both.\n *\n * CSS hooks (set them on `lc-app-shell` or any ancestor):\n * - `--lc-app-shell-nav-width` — the nav column from 1024 px. Default\n * `240px` (`248px` from 1440, `280px` from 1920).\n * - `--lc-app-shell-bottom-clearance` — the space kept free for the bottom\n * bar below 1024 px. Default: the bar's height + 40 px + the inset, at\n * least `112px` + the inset.\n * - `--lc-app-shell-bg` — the page colour behind everything (the sky).\n * Default `var(--color-background)`.\n * - `--lc-app-shell-bg-image` — a layer over the page colour. Default\n * `var(--app-background-image, none)`. A tint, for example:\n * `linear-gradient(to bottom, var(--color-land-1), var(--color-land-2), var(--color-land-3))`.\n * The horizon's land still paints over it.\n *\n * Needs `<meta name=\"viewport\" content=\"width=device-width, initial-scale=1,\n * viewport-fit=cover\">` for the safe-area insets. Breakpoints are fixed\n * (1024 px side navigation, 1440 / 1920 px widths), so the layout works\n * before JavaScript runs (SSR, prerendering).\n *\n * @example\n * ```html\n * <lc-app-shell [horizon]=\"isToday()\">\n * <lc-header shell-header variant=\"transparent\" [sticky]=\"false\" … />\n * <lc-sidenav shell-nav mode=\"docked\" [isOpen]=\"true\" width=\"100%\" [mobileBreakpoint]=\"0\"\n * variant=\"transparent\" [items]=\"nav\" [activeRoute]=\"url()\" />\n * <router-outlet />\n * <lc-bottom-nav shell-bottom-nav variant=\"floating\" [items]=\"nav\" [activeRoute]=\"url()\" />\n * </lc-app-shell>\n * ```\n */\n@Component({\n selector: 'lc-app-shell',\n standalone: true,\n templateUrl: './app-shell.component.html',\n styleUrl: './app-shell.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class AppShellComponent {\n /**\n * Close the page with the horizon (`.lc-horizon`): full width, below the\n * side navigation and the page, filling the rest of the screen; the footer\n * (in a row of its own, which adds to the land) and the floating bar rest\n * on its land, and `[shell-horizon-figure]` stands on its line.\n * @default false\n */\n readonly horizon = input(false);\n\n /**\n * Where the side navigation sits from 1024 px. `'below-header'`: the header\n * spans the full width and the nav starts below it. `'full-height'`: the nav\n * runs from the top to the bottom of the viewport, with the header beside\n * it. Below 1024 px both render the same. The DOM and tab order don't change\n * (header, nav, page). CSS only, so it holds before JavaScript runs.\n * `'full-height'` expects a brand block at the top of the rail\n * (`lc-sidenav brandName` or `showLogo`, with a `logoSize` that matches the\n * header's `size`, e.g. both `md`: 64 px) and the header's `showLogo` off.\n * @default 'below-header'\n */\n readonly navPlacement = input<AppShellNavPlacement>('below-header');\n\n protected readonly shellClasses = computed(() => {\n const classes = ['lc-app-shell'];\n if (this.horizon()) classes.push('lc-app-shell--horizon');\n if (this.navPlacement() === 'full-height') classes.push('lc-app-shell--nav-full-height');\n return classes.join(' ');\n });\n\n private readonly shell = viewChild.required<ElementRef<HTMLElement>>('shell');\n private readonly headerSlot = viewChild.required<ElementRef<HTMLElement>>('headerSlot');\n private readonly barSlot = viewChild.required<ElementRef<HTMLElement>>('barSlot');\n\n constructor() {\n const destroyRef = inject(DestroyRef);\n const insets = inject(OverlayInsetsService);\n // Browser only (afterNextRender never runs on the server): follow the\n // bar's content height, so the clearance and the land grow with a bar\n // that grows with the text size (AS-31). Without ResizeObserver, and\n // before this runs, the CSS fallback (72 px → 112 px clearance) holds.\n // The same measurements tell a fixed overlay (lc-toast-outlet, AS-34)\n // what to keep free: the header's bottom edge and the bar's top edge.\n afterNextRender(() => {\n if (typeof ResizeObserver === 'undefined') return;\n const shell = this.shell().nativeElement;\n const header = this.headerSlot().nativeElement;\n const slot = this.barSlot().nativeElement;\n // Until a box is measured (a hidden bar or an empty header reports\n // nothing): the safe-area insets.\n insets.top.set(TOP_INSET);\n insets.bottom.set(BOTTOM_INSET);\n let bar: Element | null = null;\n const clear = () => {\n shell.style.removeProperty(BAR_VAR);\n insets.bottom.set(BOTTOM_INSET);\n };\n const sizes = new ResizeObserver((entries) => {\n const entry = entries[entries.length - 1];\n const height = entry.contentBoxSize?.[0]?.blockSize ?? entry.contentRect.height;\n // 0 when the bar is hidden (from 1024 px).\n if (height > 0 && bar) {\n shell.style.setProperty(BAR_VAR, `${height}px`);\n // Its top edge from the viewport's bottom: its own `bottom` (16 px\n // plus the inset when floating) plus its border box.\n const bottom = parseFloat(getComputedStyle(bar).bottom);\n insets.bottom.set(`${(Number.isFinite(bottom) ? bottom : 0) + borderBoxHeight(entry, bar)}px`);\n } else clear();\n });\n // The header wrapper carries the top inset (its padding), so its border\n // box ends at the header's bottom edge; 0 when the slot is empty.\n const headerSizes = new ResizeObserver((entries) => {\n const height = borderBoxHeight(entries[entries.length - 1], header);\n insets.top.set(height > 0 ? `${height}px` : TOP_INSET);\n });\n headerSizes.observe(header);\n // The bar may come later (`@if (items())` around it) or go again.\n const pick = () => {\n const next = slot.querySelector('.lc-bottom-nav') ?? slot.firstElementChild;\n if (next === bar) return;\n if (bar) sizes.unobserve(bar);\n bar = next;\n if (bar) sizes.observe(bar);\n else clear();\n };\n pick();\n const changes = typeof MutationObserver === 'undefined' ? null : new MutationObserver(pick);\n changes?.observe(slot, { childList: true, subtree: true });\n destroyRef.onDestroy(() => {\n sizes.disconnect();\n headerSizes.disconnect();\n changes?.disconnect();\n insets.top.set(null);\n insets.bottom.set(null);\n });\n });\n }\n}\n","<div #shell [class]=\"shellClasses()\">\n <div #headerSlot class=\"lc-app-shell__header\"><ng-content select=\"[shell-header]\" /></div>\n <div class=\"lc-app-shell__nav\"><ng-content select=\"[shell-nav]\" /></div>\n <div class=\"lc-app-shell__body\"><ng-content /></div>\n @if (horizon()) {\n <div class=\"lc-app-shell__horizon lc-horizon\" aria-hidden=\"true\"><ng-content select=\"[shell-horizon-figure]\" /></div>\n }\n <div class=\"lc-app-shell__footer\"><ng-content select=\"[shell-footer]\" /></div>\n <div #barSlot class=\"lc-app-shell__bottom-nav\"><ng-content select=\"[shell-bottom-nav]\" /></div>\n</div>\n","import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';\n\n/**\n * Horizontal padding of `lc-app-page` (3.15).\n * - `default`: 24 · 32 · 48 · 56 · 72 · 96 px from 0 · 640 · 1024 · 1280 · 1440 · 1920 px.\n * - `header`: `lc-header`'s padding (16 px up to 768 px, 24 px above), so the\n * content lines up with the header title and the user menu.\n */\nexport type AppPagePaddingX = 'default' | 'header';\n\n/**\n * Space above `lc-app-page`'s content (3.15).\n * - `default`: 24 · 32 · 48 · 56 · 72 · 112 px by width.\n * - `none` 0, `sm` 8 px, `md` 16 px at every width.\n */\nexport type AppPagePaddingTop = 'default' | 'none' | 'sm' | 'md';\n\n/**\n * Width of `lc-app-page`'s content from 1280 px (3.28, AS-36).\n * - `default`: the content column, centred, capped at `contentMaxWidth`.\n * - `wide`: the standard column of a page with a right column, the right\n * column left empty, and `[page-wide]` children running over it, up to\n * 928 px. Below 1280 px, and next to a named right column, as `default`.\n */\nexport type AppPageContentWidth = 'default' | 'wide';\n\n/**\n * A page inside `lc-app-shell`: the `<main>` landmark with the page's content\n * centred at a reading width, and an optional right column docked to the\n * window's right edge (LK-6).\n *\n * - `<main [id]=\"mainId\">` holds the default slot as a column (24 px apart,\n * 40 px from 1024 px), centred and capped at `contentMaxWidth` (880 px of\n * content). Side padding 24 · 32 · 48 · 56 · 72 · 96 px at 360 · 640 ·\n * 1024 · 1280 · 1440 · 1920 px.\n * - `[page-aside]` is the right column: an `<aside>` AFTER `<main>` in the DOM,\n * shown from 1280 px at 360 px (400 from 1440, 460 from 1920; override\n * with `--lc-app-page-aside-width`), with a tint and a hairline that fade\n * downwards; inside `lc-app-shell` they start at the top of the window,\n * behind a transparent header. Below 1280 px it is hidden: show the page's narrow equivalent\n * inside main instead (e.g. a member row). It renders only with an\n * accessible name — `asideLabelledby` (the id of its heading) or\n * `asideAriaLabel`.\n * - `reserveAside` keeps that column from 1280 px — width, tint and hairline —\n * with no content and no landmark (`aria-hidden=\"true\"`), so a page without\n * right-column content keeps the same column as a page with one (3.28,\n * AS-35). A named aside wins.\n * - `contentWidth=\"wide\"` (3.28, AS-36) lays the page out like a page with a\n * right column, leaves the column empty and untinted, and lets children\n * marked `[page-wide]` (e.g. a table's wrapper) run over it, up to 928 px;\n * the other children (the page header, the next-step card) keep the\n * standard column, so the title doesn't move between pages. Next to a named\n * aside, and below 1280 px, it renders like `'default'`.\n * - `busy` marks the page as loading: `aria-busy=\"true\"` on `<main>` while a\n * skeleton stands in for the content (AS-12). No visual change.\n * - `paddingX=\"header\"` lines the content up with `lc-header` (3.15): its\n * title and its user menu. Combine it with `contentMaxWidth=\"none\"` (or a\n * page narrower than the cap); centred content doesn't align.\n * `paddingTop` sets the space above the content (`none`, `sm` 8 px, `md`\n * 16 px), e.g. `md` with `lc-page-header paddingTop=\"none\"` puts the `h1`\n * 16 px below the header.\n *\n * CSS hooks (set them on `lc-app-page` or any ancestor; each wins over the\n * inputs and holds one value at every width):\n * - `--lc-app-page-padding-x`: the side padding (default: `paddingX`).\n * Per breakpoint, set it inside your own media query:\n * `lc-app-page { --lc-app-page-padding-x: 16px }` and\n * `@media (min-width: 1024px) { lc-app-page { --lc-app-page-padding-x: 32px } }`.\n * - `--lc-app-page-padding-top`: the space above the content (default: `paddingTop`).\n * - `--lc-app-page-gap`: the space between the content's children (24 px, 40 px from 1024 px).\n * - `--lc-app-page-aside-width`: the right column (360 px, 400 from 1440, 460 from 1920).\n *\n * @example\n * ```html\n * <lc-app-page asideLabelledby=\"members-title\">\n * <lc-page-header … />\n * <lc-next-step-card … />\n * <section page-aside>\n * <h2 id=\"members-title\">Mitglieder</h2>\n * <lc-list [items]=\"members\" />\n * </section>\n * </lc-app-page>\n * ```\n */\n@Component({\n selector: 'lc-app-page',\n standalone: true,\n templateUrl: './app-page.component.html',\n styleUrl: './app-page.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class AppPageComponent {\n /** Id of the `<main>` element (the skip link's target). */\n readonly mainId = input('main');\n\n /**\n * Widest the page content gets (CSS length; `none` for no cap). The default\n * keeps lines readable on wide screens: the page gains air, not line length.\n */\n readonly contentMaxWidth = input('880px');\n\n /** Id of the heading that names the right column (`aria-labelledby`). */\n readonly asideLabelledby = input('');\n\n /** Accessible name of the right column when it has no visible heading. */\n readonly asideAriaLabel = input('');\n\n /**\n * Marks the page as loading: `aria-busy=\"true\"` on its `<main>` while\n * `true`, no attribute while `false`. Set it while a skeleton stands in for\n * the content. Screen readers may hold back changes inside a busy region\n * until it clears, so a loading message that must be heard belongs outside\n * `<main>` (for example a `.sr-only` `role=\"status\"` next to the page) or is\n * static text. No visual change.\n * @default false\n */\n readonly busy = input(false);\n\n /**\n * Horizontal page padding. `'default'`: 24/32/48/56/72/96 px from\n * 0/640/1024/1280/1440/1920 px. `'header'`: the same padding as `lc-header`\n * (16 px up to 768 px, 24 px above, from `--lc-density-padding-md`/`-lg`),\n * so the content lines up with the header title and the user menu. Combine\n * it with `contentMaxWidth=\"none\"` (or a page narrower than the cap).\n * `--lc-app-page-padding-x` wins over both.\n * @default 'default'\n */\n readonly paddingX = input<AppPagePaddingX>('default');\n\n /**\n * Space above the page content. `'default'`: 24/32/48/56/72/112 px by\n * width. `'none'` 0, `'sm'` 8 px, `'md'` 16 px at every width.\n * `--lc-app-page-padding-top` wins.\n * @default 'default'\n */\n readonly paddingTop = input<AppPagePaddingTop>('default');\n\n /**\n * Keeps the right column from 1280 px (360 · 400 · 460 px,\n * `--lc-app-page-aside-width`) with its tint and hairline but no content\n * and no landmark (`aria-hidden=\"true\"`, no role), so pages without\n * right-column content share one column with pages that have one. A named\n * aside (`asideLabelledby` / `asideAriaLabel`) wins; `contentWidth=\"wide\"`\n * leaves the column empty and untinted. Below 1280 px nothing shows.\n * @default false\n */\n readonly reserveAside = input(false);\n\n /**\n * `'wide'`, from 1280 px: the page keeps the standard column of a page with\n * a right column, the column stays empty and untinted, and `[page-wide]`\n * children run over it, up to 928 px (or `contentMaxWidth` when it is not\n * `880px`; `none` means the whole column). The other children keep the\n * standard column. With a named aside, `'wide'` has no effect. Below\n * 1280 px `'wide'` renders like `'default'`.\n * @default 'default'\n */\n readonly contentWidth = input<AppPageContentWidth>('default');\n\n /** The right column renders only when it has an accessible name. */\n protected readonly hasAside = computed(() => !!(this.asideLabelledby() || this.asideAriaLabel()));\n\n /** `contentWidth=\"wide\"` takes effect only without a named aside, which it yields to. */\n protected readonly wide = computed(() => this.contentWidth() === 'wide' && !this.hasAside());\n\n /** The empty, unnamed column (`reserveAside`), unless a named aside or `wide` takes the space. */\n protected readonly reserved = computed(() => this.reserveAside() && !this.hasAside() && !this.wide());\n\n /**\n * The cap of `[page-wide]` children: `null` keeps the CSS's 928 px (also\n * for the default `contentMaxWidth`), `none` means the whole column.\n */\n protected readonly wideMax = computed(() => {\n if (!this.wide()) return null;\n const max = this.contentMaxWidth();\n if (max === '880px') return null;\n return max === 'none' ? '100vw' : max;\n });\n\n protected readonly pageClasses = computed(() => {\n const classes = ['lc-app-page'];\n if (this.hasAside() || this.reserveAside() || this.wide()) classes.push('lc-app-page--aside');\n if (this.wide()) classes.push('lc-app-page--wide');\n if (this.paddingX() === 'header') classes.push('lc-app-page--pad-x-header');\n if (this.paddingTop() !== 'default') classes.push(`lc-app-page--pad-top-${this.paddingTop()}`);\n return classes.join(' ');\n });\n}\n","<div [class]=\"pageClasses()\" [style.--lc-app-page-content-max]=\"contentMaxWidth()\" [style.--_lc-app-page-wide-max]=\"wideMax()\">\n <main class=\"lc-app-page__main\" [id]=\"mainId()\" [attr.aria-busy]=\"busy() ? 'true' : null\">\n <div class=\"lc-app-page__content\"><ng-content /></div>\n </main>\n <!-- One outer block, so a page without a right column keeps 3.27.2's single\n anchor (an @else branch adds one); the reserved column (AS-35) is empty,\n aria-hidden and no landmark. -->\n @if (hasAside() || reserved()) {\n @if (hasAside()) {\n <aside\n class=\"lc-app-page__aside\"\n [attr.aria-labelledby]=\"asideLabelledby() || null\"\n [attr.aria-label]=\"asideLabelledby() ? null : asideAriaLabel()\"\n >\n <ng-content select=\"[page-aside]\" />\n </aside>\n } @else {<div class=\"lc-app-page__aside lc-app-page__aside--reserved\" aria-hidden=\"true\"></div>}\n }\n</div>\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from "@angular/core";
|
|
2
|
-
import { Injectable } from "@angular/core";
|
|
2
|
+
import { Injectable, signal } from "@angular/core";
|
|
3
3
|
var OverlayStackService = class OverlayStackService {
|
|
4
4
|
stack = [];
|
|
5
5
|
claimed = /* @__PURE__ */ new WeakSet();
|
|
@@ -45,6 +45,35 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
45
45
|
args: [{ providedIn: "root" }]
|
|
46
46
|
}]
|
|
47
47
|
});
|
|
48
|
-
|
|
48
|
+
var OverlayInsetsService = class OverlayInsetsService {
|
|
49
|
+
top = signal(null, ...ngDevMode ? [{ debugName: "top" }] : /* istanbul ignore next */ []);
|
|
50
|
+
bottom = signal(null, ...ngDevMode ? [{ debugName: "bottom" }] : /* istanbul ignore next */ []);
|
|
51
|
+
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
52
|
+
minVersion: "12.0.0",
|
|
53
|
+
version: "22.2.2",
|
|
54
|
+
ngImport: i0,
|
|
55
|
+
type: OverlayInsetsService,
|
|
56
|
+
deps: [],
|
|
57
|
+
target: i0.ɵɵFactoryTarget.Injectable
|
|
58
|
+
});
|
|
59
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({
|
|
60
|
+
minVersion: "12.0.0",
|
|
61
|
+
version: "22.2.2",
|
|
62
|
+
ngImport: i0,
|
|
63
|
+
type: OverlayInsetsService,
|
|
64
|
+
providedIn: "root"
|
|
65
|
+
});
|
|
66
|
+
};
|
|
67
|
+
i0.ɵɵngDeclareClassMetadata({
|
|
68
|
+
minVersion: "12.0.0",
|
|
69
|
+
version: "22.2.2",
|
|
70
|
+
ngImport: i0,
|
|
71
|
+
type: OverlayInsetsService,
|
|
72
|
+
decorators: [{
|
|
73
|
+
type: Injectable,
|
|
74
|
+
args: [{ providedIn: "root" }]
|
|
75
|
+
}]
|
|
76
|
+
});
|
|
77
|
+
export { OverlayInsetsService, OverlayStackService };
|
|
49
78
|
|
|
50
79
|
//# sourceMappingURL=lifecorner-angular-ui-kit-internal-overlay.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecorner-angular-ui-kit-internal-overlay.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/internal/overlay/overlay-stack.service.ts"],"sourcesContent":["import { Injectable } from '@angular/core';\n\n/**\n * Keeps the open overlays (modal, drawer, popover, menu, tooltip, …) in\n * opening order so that a dismiss gesture — Escape, click outside — reaches\n * only the top-most one instead of closing every open layer at once.\n *\n * Every overlay pushes its id when it opens and removes it when it closes or\n * is destroyed. Internal to the library; not part of the public API.\n */\n@Injectable({ providedIn: 'root' })\nexport class OverlayStackService {\n private readonly stack: string[] = [];\n\n /**\n * Events already consumed by an overlay. All overlays listen on `document`,\n * where listeners fire in registration order — not z-order — so by the time a\n * lower overlay's listener runs, the top one may already have closed and left\n * the stack, making the lower one \"top\" for the very same event.\n */\n private readonly claimed = new WeakSet<Event>();\n\n /** Register `id` as the new top-most overlay (re-pushing moves it to the top). */\n push(id: string): void {\n this.remove(id);\n this.stack.push(id);\n }\n\n remove(id: string): void {\n const index = this.stack.indexOf(id);\n if (index !== -1) this.stack.splice(index, 1);\n }\n\n isTop(id: string): boolean {\n return this.stack.length > 0 && this.stack[this.stack.length - 1] === id;\n }\n\n /**\n * Whether overlay `id` may act on this dismiss event. True only for the\n * top-most overlay and only once per event; the caller then closes and, for\n * keyboard events, stops propagation so nothing outside the stack sees it.\n */\n claim(id: string, event: Event): boolean {\n if (this.claimed.has(event) || !this.isTop(id)) return false;\n this.claimed.add(event);\n return true;\n }\n}\n"],"mappings":";;AAWA,IAAa,sBAAb,MAAa,oBAAmB;CACb,QAAkB,CAAA;CAQlB,0BAAU,IAAI,QAAO;CAGtC,KAAK,IAAU;EACb,KAAK,OAAO,EAAE;EACd,KAAK,MAAM,KAAK,EAAE;CACpB;CAEA,OAAO,IAAU;EACf,MAAM,QAAQ,KAAK,MAAM,QAAQ,EAAE;EACnC,IAAI,UAAU,IAAI,KAAK,MAAM,OAAO,OAAO,CAAC;CAC9C;CAEA,MAAM,IAAU;EACd,OAAO,KAAK,MAAM,SAAS,KAAK,KAAK,MAAM,KAAK,MAAM,SAAS,OAAO;CACxE;CAOA,MAAM,IAAY,OAAY;EAC5B,IAAI,KAAK,QAAQ,IAAI,KAAK,KAAK,CAAC,KAAK,MAAM,EAAE,GAAG,OAAO;EACvD,KAAK,QAAQ,IAAI,KAAK;EACtB,OAAO;CACT;CAnCW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAmB,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAnB,OAAA,QAAA,GAAA,sBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAmB,YADN;CAAM,CAAA;;AACnB,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAmB,YAAA,CAAA;EAD/B,MAAA;EAAW,MAAA,CAAA,EAAE,YAAY,OAAM,CAAE"}
|
|
1
|
+
{"version":3,"file":"lifecorner-angular-ui-kit-internal-overlay.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/internal/overlay/overlay-stack.service.ts","../../../../libs/angular-ui-kit/src/lib/internal/overlay/overlay-insets.service.ts"],"sourcesContent":["import { Injectable } from '@angular/core';\n\n/**\n * Keeps the open overlays (modal, drawer, popover, menu, tooltip, …) in\n * opening order so that a dismiss gesture — Escape, click outside — reaches\n * only the top-most one instead of closing every open layer at once.\n *\n * Every overlay pushes its id when it opens and removes it when it closes or\n * is destroyed. Internal to the library; not part of the public API.\n */\n@Injectable({ providedIn: 'root' })\nexport class OverlayStackService {\n private readonly stack: string[] = [];\n\n /**\n * Events already consumed by an overlay. All overlays listen on `document`,\n * where listeners fire in registration order — not z-order — so by the time a\n * lower overlay's listener runs, the top one may already have closed and left\n * the stack, making the lower one \"top\" for the very same event.\n */\n private readonly claimed = new WeakSet<Event>();\n\n /** Register `id` as the new top-most overlay (re-pushing moves it to the top). */\n push(id: string): void {\n this.remove(id);\n this.stack.push(id);\n }\n\n remove(id: string): void {\n const index = this.stack.indexOf(id);\n if (index !== -1) this.stack.splice(index, 1);\n }\n\n isTop(id: string): boolean {\n return this.stack.length > 0 && this.stack[this.stack.length - 1] === id;\n }\n\n /**\n * Whether overlay `id` may act on this dismiss event. True only for the\n * top-most overlay and only once per event; the caller then closes and, for\n * keyboard events, stops propagation so nothing outside the stack sees it.\n */\n claim(id: string, event: Event): boolean {\n if (this.claimed.has(event) || !this.isTop(id)) return false;\n this.claimed.add(event);\n return true;\n }\n}\n","import { Injectable, signal } from '@angular/core';\n\n/**\n * The space a fixed overlay keeps free at the top and the bottom of the\n * viewport, as CSS lengths (3.28, AS-34). `lc-app-shell` writes them in the\n * browser: `top` is its header's bottom edge, `bottom` the shown bottom bar's\n * top edge, measured from the viewport's bottom (or the safe-area insets when\n * there is no header or no bar). `lc-toast-outlet` reads them, so its stacks\n * clear the header and the bar even when the app renders the outlet outside\n * the shell (no custom property can inherit there).\n *\n * `null` means \"no shell\": on the server, before the shell has measured,\n * without ResizeObserver and after the shell is destroyed. Internal to the\n * library; not part of the public API and without a semver guarantee.\n */\n@Injectable({ providedIn: 'root' })\nexport class OverlayInsetsService {\n /** CSS length kept free above top-edge overlays, e.g. `'56px'`. */\n readonly top = signal<string | null>(null);\n\n /** CSS length kept free below bottom-edge overlays, e.g. `'102px'`. */\n readonly bottom = signal<string | null>(null);\n}\n"],"mappings":";;AAWA,IAAa,sBAAb,MAAa,oBAAmB;CACb,QAAkB,CAAA;CAQlB,0BAAU,IAAI,QAAO;CAGtC,KAAK,IAAU;EACb,KAAK,OAAO,EAAE;EACd,KAAK,MAAM,KAAK,EAAE;CACpB;CAEA,OAAO,IAAU;EACf,MAAM,QAAQ,KAAK,MAAM,QAAQ,EAAE;EACnC,IAAI,UAAU,IAAI,KAAK,MAAM,OAAO,OAAO,CAAC;CAC9C;CAEA,MAAM,IAAU;EACd,OAAO,KAAK,MAAM,SAAS,KAAK,KAAK,MAAM,KAAK,MAAM,SAAS,OAAO;CACxE;CAOA,MAAM,IAAY,OAAY;EAC5B,IAAI,KAAK,QAAQ,IAAI,KAAK,KAAK,CAAC,KAAK,MAAM,EAAE,GAAG,OAAO;EACvD,KAAK,QAAQ,IAAI,KAAK;EACtB,OAAO;CACT;CAnCW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAmB,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAnB,OAAA,QAAA,GAAA,sBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAmB,YADN;CAAM,CAAA;;AACnB,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAmB,YAAA,CAAA;EAD/B,MAAA;EAAW,MAAA,CAAA,EAAE,YAAY,OAAM,CAAE;;;ACMlC,IAAa,uBAAb,MAAa,qBAAoB;CAEtB,MAAM,OAAsB,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,MAAA,CAAA,+BAAA,CAAA,CAAA;CAGjC,SAAS,OAAsB,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,SAAA,CAAA,+BAAA,CAAA,CAAA;CALlC,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAoB,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAApB,OAAA,QAAA,GAAA,sBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAoB,YADP;CAAM,CAAA;;AACnB,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAoB,YAAA,CAAA;EADhC,MAAA;EAAW,MAAA,CAAA,EAAE,YAAY,OAAM,CAAE"}
|
|
@@ -3,6 +3,7 @@ import { ChangeDetectionStrategy, Component, Injectable, PLATFORM_ID, ViewEncaps
|
|
|
3
3
|
import { isPlatformBrowser } from "@angular/common";
|
|
4
4
|
import { IconComponent, ɵprovideKitIcons } from "@lifecorner/angular-ui-kit/icon";
|
|
5
5
|
import { ICON_CHECK_CIRCLE, ICON_CIRCLE_X, ICON_EXCLAMATION_TRIANGLE, ICON_INFORMATION_CIRCLE, ICON_X_MARK } from "@lifecorner/angular-ui-kit/icons";
|
|
6
|
+
import { OverlayInsetsService } from "@lifecorner/angular-ui-kit/internal/overlay";
|
|
6
7
|
var ToastService = class ToastService {
|
|
7
8
|
toasts;
|
|
8
9
|
toastList = signal([], ...ngDevMode ? [{ debugName: "toastList" }] : /* istanbul ignore next */ []);
|
|
@@ -279,6 +280,7 @@ var ToastOutletComponent = class ToastOutletComponent {
|
|
|
279
280
|
positions = POSITIONS;
|
|
280
281
|
liveLevels = ["assertive", "polite"];
|
|
281
282
|
toastService = inject(ToastService);
|
|
283
|
+
insets = inject(OverlayInsetsService);
|
|
282
284
|
closeAriaLabel = input("Close notification", ...ngDevMode ? [{ debugName: "closeAriaLabel" }] : /* istanbul ignore next */ []);
|
|
283
285
|
groups = computed(() => {
|
|
284
286
|
const groups = /* @__PURE__ */ new Map();
|
|
@@ -320,9 +322,13 @@ var ToastOutletComponent = class ToastOutletComponent {
|
|
|
320
322
|
isRequired: false,
|
|
321
323
|
transformFunction: null
|
|
322
324
|
} },
|
|
325
|
+
host: { properties: {
|
|
326
|
+
"style.--_lc-toast-outlet-frame-top": "insets.top()",
|
|
327
|
+
"style.--_lc-toast-outlet-frame-bottom": "insets.bottom()"
|
|
328
|
+
} },
|
|
323
329
|
ngImport: i0,
|
|
324
330
|
template: "@for (position of positions; track position) {\n <div class=\"lc-toast-outlet__stack\" [class]=\"'lc-toast-outlet__stack lc-toast-outlet__stack--' + position\">\n <!-- Both regions are always present (even empty) so that toasts inserted\n later are announced; the toasts themselves do not announce again. -->\n @for (live of liveLevels; track live) {\n <div class=\"lc-toast-outlet__region\" [attr.aria-live]=\"live\" aria-atomic=\"false\">\n @for (toast of toastsFor(position, live); track toast.id) {\n <lc-toast [toast]=\"toast\" [announce]=\"false\" [closeAriaLabel]=\"closeAriaLabel()\" (closed)=\"onClosed($event)\" />\n }\n </div>\n }\n </div>\n}\n",
|
|
325
|
-
styles: [":host{display:contents}.lc-toast-outlet__stack{position:fixed;z-index:1200;display:flex;flex-direction:column;width:100%;max-width:calc(400px + 2 * var(--spacing-4));padding:var(--spacing-4);box-sizing:border-box;pointer-events:none}.lc-toast-outlet__stack--top-left,.lc-toast-outlet__stack--top-center,.lc-toast-outlet__stack--top-right{top:
|
|
331
|
+
styles: [":host{display:contents}@property --_lc-toast-outlet-top{syntax: \"<length-percentage>\"; inherits: false; initial-value: 0px;}@property --_lc-toast-outlet-bottom{syntax: \"<length-percentage>\"; inherits: false; initial-value: 0px;}.lc-toast-outlet__stack{--_lc-toast-outlet-top: var(--lc-toast-outlet-offset-top, var(--_lc-toast-outlet-frame-top, 0px));--_lc-toast-outlet-bottom: var(--lc-toast-outlet-offset-bottom, var(--_lc-toast-outlet-frame-bottom, 0px));position:fixed;z-index:1200;display:flex;flex-direction:column;width:100%;max-width:calc(400px + 2 * var(--spacing-4));padding:var(--spacing-4);box-sizing:border-box;pointer-events:none;max-height:calc(100vh - var(--_lc-toast-outlet-top) - var(--_lc-toast-outlet-bottom));max-height:calc(100dvh - var(--_lc-toast-outlet-top) - var(--_lc-toast-outlet-bottom));overflow-y:auto;overscroll-behavior:contain}.lc-toast-outlet__stack--top-left,.lc-toast-outlet__stack--top-center,.lc-toast-outlet__stack--top-right{top:var(--_lc-toast-outlet-top)}.lc-toast-outlet__stack--bottom-left,.lc-toast-outlet__stack--bottom-center,.lc-toast-outlet__stack--bottom-right{bottom:var(--_lc-toast-outlet-bottom);flex-direction:column-reverse}.lc-toast-outlet__stack--top-left,.lc-toast-outlet__stack--bottom-left{left:0;align-items:flex-start}.lc-toast-outlet__stack--top-right,.lc-toast-outlet__stack--bottom-right{right:0;align-items:flex-end}.lc-toast-outlet__stack--top-center,.lc-toast-outlet__stack--bottom-center{left:50%;transform:translate(-50%);align-items:center}.lc-toast-outlet__region{display:flex;flex-direction:inherit;align-items:inherit;width:100%}\n"],
|
|
326
332
|
dependencies: [{
|
|
327
333
|
kind: "component",
|
|
328
334
|
type: ToastComponent,
|
|
@@ -349,8 +355,12 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
349
355
|
standalone: true,
|
|
350
356
|
imports: [ToastComponent],
|
|
351
357
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
358
|
+
host: {
|
|
359
|
+
"[style.--_lc-toast-outlet-frame-top]": "insets.top()",
|
|
360
|
+
"[style.--_lc-toast-outlet-frame-bottom]": "insets.bottom()"
|
|
361
|
+
},
|
|
352
362
|
template: "@for (position of positions; track position) {\n <div class=\"lc-toast-outlet__stack\" [class]=\"'lc-toast-outlet__stack lc-toast-outlet__stack--' + position\">\n <!-- Both regions are always present (even empty) so that toasts inserted\n later are announced; the toasts themselves do not announce again. -->\n @for (live of liveLevels; track live) {\n <div class=\"lc-toast-outlet__region\" [attr.aria-live]=\"live\" aria-atomic=\"false\">\n @for (toast of toastsFor(position, live); track toast.id) {\n <lc-toast [toast]=\"toast\" [announce]=\"false\" [closeAriaLabel]=\"closeAriaLabel()\" (closed)=\"onClosed($event)\" />\n }\n </div>\n }\n </div>\n}\n",
|
|
353
|
-
styles: [":host{display:contents}.lc-toast-outlet__stack{position:fixed;z-index:1200;display:flex;flex-direction:column;width:100%;max-width:calc(400px + 2 * var(--spacing-4));padding:var(--spacing-4);box-sizing:border-box;pointer-events:none}.lc-toast-outlet__stack--top-left,.lc-toast-outlet__stack--top-center,.lc-toast-outlet__stack--top-right{top:
|
|
363
|
+
styles: [":host{display:contents}@property --_lc-toast-outlet-top{syntax: \"<length-percentage>\"; inherits: false; initial-value: 0px;}@property --_lc-toast-outlet-bottom{syntax: \"<length-percentage>\"; inherits: false; initial-value: 0px;}.lc-toast-outlet__stack{--_lc-toast-outlet-top: var(--lc-toast-outlet-offset-top, var(--_lc-toast-outlet-frame-top, 0px));--_lc-toast-outlet-bottom: var(--lc-toast-outlet-offset-bottom, var(--_lc-toast-outlet-frame-bottom, 0px));position:fixed;z-index:1200;display:flex;flex-direction:column;width:100%;max-width:calc(400px + 2 * var(--spacing-4));padding:var(--spacing-4);box-sizing:border-box;pointer-events:none;max-height:calc(100vh - var(--_lc-toast-outlet-top) - var(--_lc-toast-outlet-bottom));max-height:calc(100dvh - var(--_lc-toast-outlet-top) - var(--_lc-toast-outlet-bottom));overflow-y:auto;overscroll-behavior:contain}.lc-toast-outlet__stack--top-left,.lc-toast-outlet__stack--top-center,.lc-toast-outlet__stack--top-right{top:var(--_lc-toast-outlet-top)}.lc-toast-outlet__stack--bottom-left,.lc-toast-outlet__stack--bottom-center,.lc-toast-outlet__stack--bottom-right{bottom:var(--_lc-toast-outlet-bottom);flex-direction:column-reverse}.lc-toast-outlet__stack--top-left,.lc-toast-outlet__stack--bottom-left{left:0;align-items:flex-start}.lc-toast-outlet__stack--top-right,.lc-toast-outlet__stack--bottom-right{right:0;align-items:flex-end}.lc-toast-outlet__stack--top-center,.lc-toast-outlet__stack--bottom-center{left:50%;transform:translate(-50%);align-items:center}.lc-toast-outlet__region{display:flex;flex-direction:inherit;align-items:inherit;width:100%}\n"]
|
|
354
364
|
}]
|
|
355
365
|
}],
|
|
356
366
|
propDecorators: { closeAriaLabel: [{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecorner-angular-ui-kit-toast.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/toast/toast.service.ts","../../../../libs/angular-ui-kit/src/lib/toast/toast.component.ts","../../../../libs/angular-ui-kit/src/lib/toast/toast.component.html","../../../../libs/angular-ui-kit/src/lib/toast/toast-outlet.component.ts","../../../../libs/angular-ui-kit/src/lib/toast/toast-outlet.component.html"],"sourcesContent":["import { Injectable, PLATFORM_ID, inject, signal, Signal } from '@angular/core';\nimport { isPlatformBrowser } from '@angular/common';\n\nexport type ToastVariant = 'success' | 'error' | 'warning' | 'info';\nexport type ToastPosition =\n | 'top-left'\n | 'top-center'\n | 'top-right'\n | 'bottom-left'\n | 'bottom-center'\n | 'bottom-right';\n\nexport interface ToastAction {\n label: string;\n onClick: () => void;\n}\n\nexport interface ToastConfig {\n message: string;\n variant?: ToastVariant;\n position?: ToastPosition;\n duration?: number;\n showCloseButton?: boolean;\n action?: ToastAction;\n}\n\nexport interface Toast extends Required<Omit<ToastConfig, 'action'>> {\n id: string;\n action?: ToastAction;\n}\n\n/**\n * Toast service for showing ephemeral notifications.\n *\n * The toasts are rendered by `<lc-toast-outlet />`, which the application\n * places once (e.g. in its shell); without it nothing is shown. The options,\n * their defaults, the timing and how a toast is announced to screen readers\n * are documented under *Feedback/Toast Outlet* (3.17).\n *\n * @example\n * ```typescript\n * private readonly toastService = inject(ToastService);\n *\n * showSuccess() {\n * this.toastService.show({\n * message: 'Changes saved successfully!',\n * variant: 'success',\n * duration: 3000\n * });\n * }\n * ```\n */\n@Injectable({\n providedIn: 'root',\n})\nexport class ToastService {\n /**\n * Public readonly signal for toasts\n */\n public readonly toasts: Signal<readonly Toast[]>;\n\n /**\n * Internal signal for toast list\n */\n private readonly toastList = signal<Toast[]>([]);\n\n private readonly MAX_TOASTS = 5;\n private toastIdCounter = 0;\n private activeTimeouts = new Map<string, ReturnType<typeof setTimeout>>();\n /** Auto-dismiss timers only make sense in the browser (and `window` does not exist on the server). */\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n\n constructor() {\n this.toasts = this.toastList.asReadonly();\n }\n\n /**\n * Show a toast notification\n */\n show(config: ToastConfig): string {\n const toast: Toast = {\n id: this.generateId(),\n message: config.message,\n variant: config.variant || 'info',\n position: config.position || 'top-right',\n duration: config.duration !== undefined ? config.duration : 3000,\n showCloseButton: config.showCloseButton !== undefined ? config.showCloseButton : true,\n action: config.action,\n };\n\n // Add toast to list\n let currentToasts = this.toastList();\n\n // Remove oldest toast if at max capacity\n if (currentToasts.length >= this.MAX_TOASTS) {\n const oldestToast = currentToasts[0];\n if (oldestToast) {\n this.clearTimeout(oldestToast.id);\n currentToasts = currentToasts.slice(1);\n }\n }\n\n this.toastList.set([...currentToasts, toast]);\n\n // Auto-dismiss if duration > 0\n if (toast.duration > 0 && this.isBrowser) {\n const timeoutId = setTimeout(() => {\n this.close(toast.id);\n }, toast.duration);\n\n this.activeTimeouts.set(toast.id, timeoutId);\n }\n\n return toast.id;\n }\n\n /**\n * Close a specific toast\n */\n close(id: string): void {\n this.removeToast(id);\n }\n\n /**\n * Close all toasts\n */\n closeAll(): void {\n const currentToasts = this.toastList();\n currentToasts.forEach((toast) => {\n this.clearTimeout(toast.id);\n });\n this.toastList.set([]);\n }\n\n /**\n * Generate unique toast ID\n */\n private generateId(): string {\n return `toast-${++this.toastIdCounter}-${Date.now()}`;\n }\n\n /**\n * Remove a toast from the list\n */\n private removeToast(id: string): void {\n this.clearTimeout(id);\n const currentToasts = this.toastList();\n this.toastList.set(currentToasts.filter((t) => t.id !== id));\n }\n\n /**\n * Clear timeout for a toast\n */\n private clearTimeout(id: string): void {\n const timeoutId = this.activeTimeouts.get(id);\n if (timeoutId !== undefined) {\n clearTimeout(timeoutId);\n this.activeTimeouts.delete(id);\n }\n }\n}\n","import {\n Component,\n ChangeDetectionStrategy,\n input,\n output,\n computed,\n ViewEncapsulation,\n} from '@angular/core';\nimport { IconComponent, ɵprovideKitIcons } from '@lifecorner/angular-ui-kit/icon';\nimport {\n ICON_CHECK_CIRCLE,\n ICON_CIRCLE_X,\n ICON_EXCLAMATION_TRIANGLE,\n ICON_INFORMATION_CIRCLE,\n ICON_X_MARK,\n} from '@lifecorner/angular-ui-kit/icons';\nimport type { Toast, ToastVariant } from './toast.service';\n\n/**\n * Toast component for displaying ephemeral notification messages.\n *\n * Features:\n * - Semantic variants (success, warning, error, info)\n * - Auto-dismiss with configurable duration\n * - Manual dismiss with close button\n * - Icon display per variant\n * - Stacked toast positioning via ToastService + `<lc-toast-outlet />`\n * - Animated enter/exit transitions\n *\n * Usually rendered by `<lc-toast-outlet />`; can also be placed directly:\n *\n * @example\n * ```html\n * <lc-toast [toast]=\"toast\" (closed)=\"dismiss($event)\" />\n * ```\n */\n@Component({\n selector: 'lc-toast',\n standalone: true,\n imports: [IconComponent],\n viewProviders: [\n ɵprovideKitIcons([ICON_X_MARK, ICON_CHECK_CIRCLE, ICON_CIRCLE_X, ICON_EXCLAMATION_TRIANGLE, ICON_INFORMATION_CIRCLE]),\n ],\n templateUrl: './toast.component.html',\n styleUrls: ['./toast.component.scss'],\n encapsulation: ViewEncapsulation.None, // Required for dynamic variant class styling\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n '[class]': 'hostClasses()',\n '[attr.role]': 'ariaRole()',\n '[attr.aria-live]': 'ariaLive()',\n '[attr.aria-atomic]': 'announce() ? \"true\" : null',\n },\n})\nexport class ToastComponent {\n /**\n * Toast data\n */\n toast = input.required<Toast>();\n\n /**\n * Whether the toast is its own live region (`role=\"status\"`/`\"alert\"` on the\n * host). `lc-toast-outlet` sets this to false because its persistent live\n * regions already announce the toast — announcing twice would be noise.\n * @default true\n */\n announce = input<boolean>(true);\n\n /** Accessible name of the close button (`toast.showCloseButton`). */\n closeAriaLabel = input('Close notification');\n\n /**\n * Emitted when toast is closed\n */\n readonly closed = output<string>();\n\n /**\n * Computed host classes\n */\n protected hostClasses = computed(() => {\n const toast = this.toast();\n return `lc-toast lc-toast--${toast.variant}`;\n });\n\n /**\n * Computed container classes\n */\n protected containerClasses = computed(() => {\n return 'lc-toast__container';\n });\n\n /**\n * Computed ARIA role\n */\n protected ariaRole = computed(() => {\n if (!this.announce()) return null;\n const variant = this.toast().variant;\n return variant === 'error' || variant === 'warning' ? 'alert' : 'status';\n });\n\n /**\n * Computed ARIA live region priority\n */\n protected ariaLive = computed(() => {\n if (!this.announce()) return null;\n const variant = this.toast().variant;\n return variant === 'error' || variant === 'warning' ? 'assertive' : 'polite';\n });\n\n /**\n * Computed default icon based on variant\n */\n protected defaultIcon = computed(() => {\n const icons: Record<ToastVariant, string> = {\n success: 'check-circle',\n error: 'x-circle',\n warning: 'exclamation-triangle',\n info: 'information-circle',\n };\n return icons[this.toast().variant];\n });\n\n /**\n * Handle close button click\n */\n protected onCloseClick(event: Event): void {\n event.preventDefault();\n event.stopPropagation();\n this.closed.emit(this.toast().id);\n }\n\n /**\n * Handle action button click\n */\n protected onActionClick(event: Event): void {\n event.preventDefault();\n event.stopPropagation();\n const action = this.toast().action;\n if (action) {\n action.onClick();\n this.closed.emit(this.toast().id);\n }\n }\n}\n","<div [class]=\"containerClasses()\">\n <div class=\"lc-toast__icon\">\n <lc-icon [name]=\"defaultIcon()\" size=\"md\" [decorative]=\"true\" />\n </div>\n \n <div class=\"lc-toast__message\">\n {{ toast().message }}\n </div>\n \n @if (toast().action) {\n <button\n type=\"button\"\n class=\"lc-toast__action\"\n (click)=\"onActionClick($event)\"\n >\n {{ toast().action?.label }}\n </button>\n }\n \n @if (toast().showCloseButton) {\n <button\n type=\"button\"\n class=\"lc-toast__close\"\n [attr.aria-label]=\"closeAriaLabel()\"\n (click)=\"onCloseClick($event)\"\n >\n <lc-icon name=\"x-mark\" size=\"sm\" [decorative]=\"true\" />\n </button>\n }\n</div>\n","import { Component, ChangeDetectionStrategy, computed, inject, input } from '@angular/core';\nimport { ToastComponent } from './toast.component';\nimport { ToastService, type Toast, type ToastPosition } from './toast.service';\n\n/** Politeness of the live region a toast is announced from. */\ntype ToastLive = 'polite' | 'assertive';\n\nconst POSITIONS: readonly ToastPosition[] = [\n 'top-left',\n 'top-center',\n 'top-right',\n 'bottom-left',\n 'bottom-center',\n 'bottom-right',\n];\n\n/**\n * Renders the toasts of {@link ToastService}. Place it once in the application\n * shell; without it `ToastService.show()` has no visible effect.\n *\n * ```html\n * <lc-toast-outlet />\n * ```\n *\n * Toasts are stacked per `position`. Every position owns two live regions that\n * exist from the start — screen readers only announce content that is inserted\n * into an *existing* live region — one `polite` for success/info toasts and one\n * `assertive` for error/warning toasts. Dismissing a toast (close button,\n * action, auto-dismiss) removes it from the service.\n */\n@Component({\n selector: 'lc-toast-outlet',\n standalone: true,\n imports: [ToastComponent],\n templateUrl: './toast-outlet.component.html',\n styleUrls: ['./toast-outlet.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class ToastOutletComponent {\n protected readonly positions = POSITIONS;\n protected readonly liveLevels: readonly ToastLive[] = ['assertive', 'polite'];\n\n private readonly toastService = inject(ToastService);\n\n /** Accessible name of every toast's close button (forwarded to lc-toast). */\n readonly closeAriaLabel = input('Close notification');\n\n /** Toasts keyed by `${position}:${politeness}` in the order they were shown. */\n private readonly groups = computed(() => {\n const groups = new Map<string, Toast[]>();\n for (const toast of this.toastService.toasts()) {\n const key = `${toast.position}:${ToastOutletComponent.liveFor(toast)}`;\n const group = groups.get(key);\n if (group) {\n group.push(toast);\n } else {\n groups.set(key, [toast]);\n }\n }\n return groups;\n });\n\n protected toastsFor(position: ToastPosition, live: ToastLive): readonly Toast[] {\n return this.groups().get(`${position}:${live}`) ?? [];\n }\n\n protected onClosed(id: string): void {\n this.toastService.close(id);\n }\n\n /** Errors and warnings interrupt (assertive), everything else waits its turn. */\n private static liveFor(toast: Toast): ToastLive {\n return toast.variant === 'error' || toast.variant === 'warning' ? 'assertive' : 'polite';\n }\n}\n","@for (position of positions; track position) {\n <div class=\"lc-toast-outlet__stack\" [class]=\"'lc-toast-outlet__stack lc-toast-outlet__stack--' + position\">\n <!-- Both regions are always present (even empty) so that toasts inserted\n later are announced; the toasts themselves do not announce again. -->\n @for (live of liveLevels; track live) {\n <div class=\"lc-toast-outlet__region\" [attr.aria-live]=\"live\" aria-atomic=\"false\">\n @for (toast of toastsFor(position, live); track toast.id) {\n <lc-toast [toast]=\"toast\" [announce]=\"false\" [closeAriaLabel]=\"closeAriaLabel()\" (closed)=\"onClosed($event)\" />\n }\n </div>\n }\n </div>\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"lifecorner-angular-ui-kit-toast.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/toast/toast.service.ts","../../../../libs/angular-ui-kit/src/lib/toast/toast.component.ts","../../../../libs/angular-ui-kit/src/lib/toast/toast.component.html","../../../../libs/angular-ui-kit/src/lib/toast/toast-outlet.component.ts","../../../../libs/angular-ui-kit/src/lib/toast/toast-outlet.component.html"],"sourcesContent":["import { Injectable, PLATFORM_ID, inject, signal, Signal } from '@angular/core';\nimport { isPlatformBrowser } from '@angular/common';\n\nexport type ToastVariant = 'success' | 'error' | 'warning' | 'info';\nexport type ToastPosition =\n | 'top-left'\n | 'top-center'\n | 'top-right'\n | 'bottom-left'\n | 'bottom-center'\n | 'bottom-right';\n\nexport interface ToastAction {\n label: string;\n onClick: () => void;\n}\n\nexport interface ToastConfig {\n message: string;\n variant?: ToastVariant;\n position?: ToastPosition;\n duration?: number;\n showCloseButton?: boolean;\n action?: ToastAction;\n}\n\nexport interface Toast extends Required<Omit<ToastConfig, 'action'>> {\n id: string;\n action?: ToastAction;\n}\n\n/**\n * Toast service for showing ephemeral notifications.\n *\n * The toasts are rendered by `<lc-toast-outlet />`, which the application\n * places once (e.g. in its shell); without it nothing is shown. The options,\n * their defaults, the timing and how a toast is announced to screen readers\n * are documented under *Feedback/Toast Outlet* (3.17).\n *\n * @example\n * ```typescript\n * private readonly toastService = inject(ToastService);\n *\n * showSuccess() {\n * this.toastService.show({\n * message: 'Changes saved successfully!',\n * variant: 'success',\n * duration: 3000\n * });\n * }\n * ```\n */\n@Injectable({\n providedIn: 'root',\n})\nexport class ToastService {\n /**\n * Public readonly signal for toasts\n */\n public readonly toasts: Signal<readonly Toast[]>;\n\n /**\n * Internal signal for toast list\n */\n private readonly toastList = signal<Toast[]>([]);\n\n private readonly MAX_TOASTS = 5;\n private toastIdCounter = 0;\n private activeTimeouts = new Map<string, ReturnType<typeof setTimeout>>();\n /** Auto-dismiss timers only make sense in the browser (and `window` does not exist on the server). */\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n\n constructor() {\n this.toasts = this.toastList.asReadonly();\n }\n\n /**\n * Show a toast notification\n */\n show(config: ToastConfig): string {\n const toast: Toast = {\n id: this.generateId(),\n message: config.message,\n variant: config.variant || 'info',\n position: config.position || 'top-right',\n duration: config.duration !== undefined ? config.duration : 3000,\n showCloseButton: config.showCloseButton !== undefined ? config.showCloseButton : true,\n action: config.action,\n };\n\n // Add toast to list\n let currentToasts = this.toastList();\n\n // Remove oldest toast if at max capacity\n if (currentToasts.length >= this.MAX_TOASTS) {\n const oldestToast = currentToasts[0];\n if (oldestToast) {\n this.clearTimeout(oldestToast.id);\n currentToasts = currentToasts.slice(1);\n }\n }\n\n this.toastList.set([...currentToasts, toast]);\n\n // Auto-dismiss if duration > 0\n if (toast.duration > 0 && this.isBrowser) {\n const timeoutId = setTimeout(() => {\n this.close(toast.id);\n }, toast.duration);\n\n this.activeTimeouts.set(toast.id, timeoutId);\n }\n\n return toast.id;\n }\n\n /**\n * Close a specific toast\n */\n close(id: string): void {\n this.removeToast(id);\n }\n\n /**\n * Close all toasts\n */\n closeAll(): void {\n const currentToasts = this.toastList();\n currentToasts.forEach((toast) => {\n this.clearTimeout(toast.id);\n });\n this.toastList.set([]);\n }\n\n /**\n * Generate unique toast ID\n */\n private generateId(): string {\n return `toast-${++this.toastIdCounter}-${Date.now()}`;\n }\n\n /**\n * Remove a toast from the list\n */\n private removeToast(id: string): void {\n this.clearTimeout(id);\n const currentToasts = this.toastList();\n this.toastList.set(currentToasts.filter((t) => t.id !== id));\n }\n\n /**\n * Clear timeout for a toast\n */\n private clearTimeout(id: string): void {\n const timeoutId = this.activeTimeouts.get(id);\n if (timeoutId !== undefined) {\n clearTimeout(timeoutId);\n this.activeTimeouts.delete(id);\n }\n }\n}\n","import {\n Component,\n ChangeDetectionStrategy,\n input,\n output,\n computed,\n ViewEncapsulation,\n} from '@angular/core';\nimport { IconComponent, ɵprovideKitIcons } from '@lifecorner/angular-ui-kit/icon';\nimport {\n ICON_CHECK_CIRCLE,\n ICON_CIRCLE_X,\n ICON_EXCLAMATION_TRIANGLE,\n ICON_INFORMATION_CIRCLE,\n ICON_X_MARK,\n} from '@lifecorner/angular-ui-kit/icons';\nimport type { Toast, ToastVariant } from './toast.service';\n\n/**\n * Toast component for displaying ephemeral notification messages.\n *\n * Features:\n * - Semantic variants (success, warning, error, info)\n * - Auto-dismiss with configurable duration\n * - Manual dismiss with close button\n * - Icon display per variant\n * - Stacked toast positioning via ToastService + `<lc-toast-outlet />`\n * - Animated enter/exit transitions\n *\n * Usually rendered by `<lc-toast-outlet />`; can also be placed directly:\n *\n * @example\n * ```html\n * <lc-toast [toast]=\"toast\" (closed)=\"dismiss($event)\" />\n * ```\n */\n@Component({\n selector: 'lc-toast',\n standalone: true,\n imports: [IconComponent],\n viewProviders: [\n ɵprovideKitIcons([ICON_X_MARK, ICON_CHECK_CIRCLE, ICON_CIRCLE_X, ICON_EXCLAMATION_TRIANGLE, ICON_INFORMATION_CIRCLE]),\n ],\n templateUrl: './toast.component.html',\n styleUrls: ['./toast.component.scss'],\n encapsulation: ViewEncapsulation.None, // Required for dynamic variant class styling\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n '[class]': 'hostClasses()',\n '[attr.role]': 'ariaRole()',\n '[attr.aria-live]': 'ariaLive()',\n '[attr.aria-atomic]': 'announce() ? \"true\" : null',\n },\n})\nexport class ToastComponent {\n /**\n * Toast data\n */\n toast = input.required<Toast>();\n\n /**\n * Whether the toast is its own live region (`role=\"status\"`/`\"alert\"` on the\n * host). `lc-toast-outlet` sets this to false because its persistent live\n * regions already announce the toast — announcing twice would be noise.\n * @default true\n */\n announce = input<boolean>(true);\n\n /** Accessible name of the close button (`toast.showCloseButton`). */\n closeAriaLabel = input('Close notification');\n\n /**\n * Emitted when toast is closed\n */\n readonly closed = output<string>();\n\n /**\n * Computed host classes\n */\n protected hostClasses = computed(() => {\n const toast = this.toast();\n return `lc-toast lc-toast--${toast.variant}`;\n });\n\n /**\n * Computed container classes\n */\n protected containerClasses = computed(() => {\n return 'lc-toast__container';\n });\n\n /**\n * Computed ARIA role\n */\n protected ariaRole = computed(() => {\n if (!this.announce()) return null;\n const variant = this.toast().variant;\n return variant === 'error' || variant === 'warning' ? 'alert' : 'status';\n });\n\n /**\n * Computed ARIA live region priority\n */\n protected ariaLive = computed(() => {\n if (!this.announce()) return null;\n const variant = this.toast().variant;\n return variant === 'error' || variant === 'warning' ? 'assertive' : 'polite';\n });\n\n /**\n * Computed default icon based on variant\n */\n protected defaultIcon = computed(() => {\n const icons: Record<ToastVariant, string> = {\n success: 'check-circle',\n error: 'x-circle',\n warning: 'exclamation-triangle',\n info: 'information-circle',\n };\n return icons[this.toast().variant];\n });\n\n /**\n * Handle close button click\n */\n protected onCloseClick(event: Event): void {\n event.preventDefault();\n event.stopPropagation();\n this.closed.emit(this.toast().id);\n }\n\n /**\n * Handle action button click\n */\n protected onActionClick(event: Event): void {\n event.preventDefault();\n event.stopPropagation();\n const action = this.toast().action;\n if (action) {\n action.onClick();\n this.closed.emit(this.toast().id);\n }\n }\n}\n","<div [class]=\"containerClasses()\">\n <div class=\"lc-toast__icon\">\n <lc-icon [name]=\"defaultIcon()\" size=\"md\" [decorative]=\"true\" />\n </div>\n \n <div class=\"lc-toast__message\">\n {{ toast().message }}\n </div>\n \n @if (toast().action) {\n <button\n type=\"button\"\n class=\"lc-toast__action\"\n (click)=\"onActionClick($event)\"\n >\n {{ toast().action?.label }}\n </button>\n }\n \n @if (toast().showCloseButton) {\n <button\n type=\"button\"\n class=\"lc-toast__close\"\n [attr.aria-label]=\"closeAriaLabel()\"\n (click)=\"onCloseClick($event)\"\n >\n <lc-icon name=\"x-mark\" size=\"sm\" [decorative]=\"true\" />\n </button>\n }\n</div>\n","import { Component, ChangeDetectionStrategy, computed, inject, input } from '@angular/core';\nimport { OverlayInsetsService } from '@lifecorner/angular-ui-kit/internal/overlay';\nimport { ToastComponent } from './toast.component';\nimport { ToastService, type Toast, type ToastPosition } from './toast.service';\n\n/** Politeness of the live region a toast is announced from. */\ntype ToastLive = 'polite' | 'assertive';\n\nconst POSITIONS: readonly ToastPosition[] = [\n 'top-left',\n 'top-center',\n 'top-right',\n 'bottom-left',\n 'bottom-center',\n 'bottom-right',\n];\n\n/**\n * Renders the toasts of {@link ToastService}. Place it once in the application\n * shell; without it `ToastService.show()` has no visible effect.\n *\n * ```html\n * <lc-toast-outlet />\n * ```\n *\n * Toasts are stacked per `position`. Every position owns two live regions that\n * exist from the start — screen readers only announce content that is inserted\n * into an *existing* live region — one `polite` for success/info toasts and one\n * `assertive` for error/warning toasts. Dismissing a toast (close button,\n * action, auto-dismiss) removes it from the service.\n *\n * Offsets (3.28, AS-34): with `lc-app-shell` in the app (the outlet may sit\n * before or after it), the `top-*` stacks start below the shell's header and\n * the `bottom-*` stacks end above its bottom bar, each with the stack's 16 px\n * padding as the gap. The shell measures both in the browser (the header's\n * bottom edge with the top inset; the shown bar's top edge, from the\n * viewport's bottom, with the bottom inset). From 1024 px, where the bar is\n * hidden, the bottom offset is the safe-area inset. Without a shell, and in\n * prerendered HTML, both are `0px`, as before. Every stack is at most as high\n * as the space between the offsets and scrolls when its toasts need more\n * (200 % text), so each toast stays reachable. In the shell, prefer the\n * `bottom-*` positions: a top toast can still cover a page title placed\n * right under the header.\n *\n * CSS hooks (set them on `lc-toast-outlet` or any ancestor, e.g. `:root`;\n * each wins over the shell's value; write lengths, `0px` rather than `0`):\n * - `--lc-toast-outlet-offset-top` — the space kept free above the `top-*`\n * stacks. Default `0px`; with `lc-app-shell`: its header's bottom edge, or\n * `env(safe-area-inset-top, 0px)` without a header.\n * - `--lc-toast-outlet-offset-bottom` — the space kept free below the\n * `bottom-*` stacks. Default `0px`; with `lc-app-shell`: the shown bottom\n * bar's top edge, measured from the viewport's bottom, or\n * `env(safe-area-inset-bottom, 0px)` when no bar shows.\n */\n@Component({\n selector: 'lc-toast-outlet',\n standalone: true,\n imports: [ToastComponent],\n templateUrl: './toast-outlet.component.html',\n styleUrls: ['./toast-outlet.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n // The shell's measured offsets (AS-34), private; `null` without a shell,\n // so the host gets no style attribute. The public hooks win in the CSS.\n '[style.--_lc-toast-outlet-frame-top]': 'insets.top()',\n '[style.--_lc-toast-outlet-frame-bottom]': 'insets.bottom()',\n },\n})\nexport class ToastOutletComponent {\n protected readonly positions = POSITIONS;\n protected readonly liveLevels: readonly ToastLive[] = ['assertive', 'polite'];\n\n private readonly toastService = inject(ToastService);\n protected readonly insets = inject(OverlayInsetsService);\n\n /** Accessible name of every toast's close button (forwarded to lc-toast). */\n readonly closeAriaLabel = input('Close notification');\n\n /** Toasts keyed by `${position}:${politeness}` in the order they were shown. */\n private readonly groups = computed(() => {\n const groups = new Map<string, Toast[]>();\n for (const toast of this.toastService.toasts()) {\n const key = `${toast.position}:${ToastOutletComponent.liveFor(toast)}`;\n const group = groups.get(key);\n if (group) {\n group.push(toast);\n } else {\n groups.set(key, [toast]);\n }\n }\n return groups;\n });\n\n protected toastsFor(position: ToastPosition, live: ToastLive): readonly Toast[] {\n return this.groups().get(`${position}:${live}`) ?? [];\n }\n\n protected onClosed(id: string): void {\n this.toastService.close(id);\n }\n\n /** Errors and warnings interrupt (assertive), everything else waits its turn. */\n private static liveFor(toast: Toast): ToastLive {\n return toast.variant === 'error' || toast.variant === 'warning' ? 'assertive' : 'polite';\n }\n}\n","@for (position of positions; track position) {\n <div class=\"lc-toast-outlet__stack\" [class]=\"'lc-toast-outlet__stack lc-toast-outlet__stack--' + position\">\n <!-- Both regions are always present (even empty) so that toasts inserted\n later are announced; the toasts themselves do not announce again. -->\n @for (live of liveLevels; track live) {\n <div class=\"lc-toast-outlet__region\" [attr.aria-live]=\"live\" aria-atomic=\"false\">\n @for (toast of toastsFor(position, live); track toast.id) {\n <lc-toast [toast]=\"toast\" [announce]=\"false\" [closeAriaLabel]=\"closeAriaLabel()\" (closed)=\"onClosed($event)\" />\n }\n </div>\n }\n </div>\n}\n"],"mappings":";;;;;;AAuDA,IAAa,eAAb,MAAa,aAAY;CAIP;CAKC,YAAY,OAAgB,CAAA,GAAG,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;CAE/B,aAAa;CACtB,iBAAiB;CACjB,iCAAiB,IAAI,IAAG;CAEf,YAAY,kBAAkB,OAAO,WAAW,CAAC;CAElE,cAAA;EACE,KAAK,SAAS,KAAK,UAAU,WAAU;CACzC;CAKA,KAAK,QAAmB;EACtB,MAAM,QAAe;GACnB,IAAI,KAAK,WAAU;GACnB,SAAS,OAAO;GAChB,SAAS,OAAO,WAAW;GAC3B,UAAU,OAAO,YAAY;GAC7B,UAAU,OAAO,aAAa,SAAY,OAAO,WAAW;GAC5D,iBAAiB,OAAO,oBAAoB,SAAY,OAAO,kBAAkB;GACjF,QAAQ,OAAO;EAChB;EAGD,IAAI,gBAAgB,KAAK,UAAS;EAGlC,IAAI,cAAc,UAAU,KAAK,YAAY;GAC3C,MAAM,cAAc,cAAc;GAClC,IAAI,aAAa;IACf,KAAK,aAAa,YAAY,EAAE;IAChC,gBAAgB,cAAc,MAAM,CAAC;GACvC;EACF;EAEA,KAAK,UAAU,IAAI,CAAC,GAAG,eAAe,KAAK,CAAC;EAG5C,IAAI,MAAM,WAAW,KAAK,KAAK,WAAW;GACxC,MAAM,YAAY,iBAAgB;IAChC,KAAK,MAAM,MAAM,EAAE;GACrB,GAAG,MAAM,QAAQ;GAEjB,KAAK,eAAe,IAAI,MAAM,IAAI,SAAS;EAC7C;EAEA,OAAO,MAAM;CACf;CAKA,MAAM,IAAU;EACd,KAAK,YAAY,EAAE;CACrB;CAKA,WAAQ;EAEN,AADsB,KAAK,UAC3B,CAAA,CAAc,SAAS,UAAS;GAC9B,KAAK,aAAa,MAAM,EAAE;EAC5B,CAAC;EACD,KAAK,UAAU,IAAI,CAAA,CAAE;CACvB;CAKQ,aAAU;EAChB,OAAO,SAAS,EAAE,KAAK,eAAc,GAAI,KAAK,IAAG;CACnD;CAKQ,YAAY,IAAU;EAC5B,KAAK,aAAa,EAAE;EACpB,MAAM,gBAAgB,KAAK,UAAS;EACpC,KAAK,UAAU,IAAI,cAAc,QAAQ,MAAM,EAAE,OAAO,EAAE,CAAC;CAC7D;CAKQ,aAAa,IAAU;EAC7B,MAAM,YAAY,KAAK,eAAe,IAAI,EAAE;EAC5C,IAAI,cAAc,QAAW;GAC3B,aAAa,SAAS;GACtB,KAAK,eAAe,OAAO,EAAE;EAC/B;CACF;CAxGW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAY,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAZ,OAAA,QAAA,GAAA,sBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAY,YAFX;CAAM,CAAA;;AAEP,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAY,YAAA,CAAA;EAHxB,MAAA;EAAW,MAAA,CAAA,EACV,YAAY,OACb,CAAA;;;;ACAD,IAAa,iBAAb,MAAa,eAAc;CAIzB,QAAQ,MAAM,SAAiB,GAAA,YAAA,CAAA,EAAA,WAAA,QAAA,CAAA,+BAAA,CAAA,CAAA;CAQ/B,WAAW,MAAe,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;CAG/B,iBAAiB,MAAM,sBAAqB,GAAA,YAAA,CAAA,EAAA,WAAA,iBAAA,CAAA,+BAAA,CAAA,CAAA;CAKnC,SAAS,OAAM;CAKd,cAAc,eAAc;EAEpC,OAAO,sBADO,KAAK,MACU,CAAA,CAAM;CACrC,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,cAAA,CAAA,+BAAA,CAAA,CAAA;CAKQ,mBAAmB,eAAc;EACzC,OAAO;CACT,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,mBAAA,CAAA,+BAAA,CAAA,CAAA;CAKQ,WAAW,eAAc;EACjC,IAAI,CAAC,KAAK,SAAQ,GAAI,OAAO;EAC7B,MAAM,UAAU,KAAK,MAAK,CAAE,CAAC;EAC7B,OAAO,YAAY,WAAW,YAAY,YAAY,UAAU;CAClE,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;CAKQ,WAAW,eAAc;EACjC,IAAI,CAAC,KAAK,SAAQ,GAAI,OAAO;EAC7B,MAAM,UAAU,KAAK,MAAK,CAAE,CAAC;EAC7B,OAAO,YAAY,WAAW,YAAY,YAAY,cAAc;CACtE,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;CAKQ,cAAc,eAAc;EAOpC,OAAO;GALL,SAAS;GACT,OAAO;GACP,SAAS;GACT,MAAM;EAED,EAAM,KAAK,MAAK,CAAE,CAAC;CAC5B,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,cAAA,CAAA,+BAAA,CAAA,CAAA;CAKQ,aAAa,OAAY;EACjC,MAAM,eAAc;EACpB,MAAM,gBAAe;EACrB,KAAK,OAAO,KAAK,KAAK,MAAK,CAAE,CAAC,EAAE;CAClC;CAKU,cAAc,OAAY;EAClC,MAAM,eAAc;EACpB,MAAM,gBAAe;EACrB,MAAM,SAAS,KAAK,MAAK,CAAE,CAAC;EAC5B,IAAI,QAAQ;GACV,OAAO,QAAO;GACd,KAAK,OAAO,KAAK,KAAK,MAAK,CAAE,CAAC,EAAE;EAClC;CACF;CAxFW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAc,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAd,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAAc,cAAA;EAAA,UAAA;EAAA,QAAA;GAAA,OAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,UAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,gBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;EAAA;EAAA,SAAA,EAAA,QAAA,SAAA;EAAA,MAAA,EAAA,YAAA;GAAA,SAAA;GAAA,aAAA;GAAA,kBAAA;GAAA,oBAAA;EAAA,EAAA;EAAA,UAAA;EAAA,UCtD3B;EA8BA,QAAA,CAAA,o3HAAA;EAAA,cAAA,CAAA;GAAA,MAAA;GAAA,MDSY;GAAa,UAAA;GAAA,QAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;GAAA;EAAA,CAAA;EAAA,eACR,CACb,iBAAiB;GAAC;GAAa;GAAmB;GAAe;GAA2B;EAAuB,CAAC,CACrH;EAAA,iBAAA,GAAA,wBAAA;EAAA,eAAA,GAAA,kBAAA;CAAA,CAAA;;AAYU,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAc,YAAA,CAAA;EAlB1B,MAAA;EACW,MAAA,CAAA;GAAA,UAAA;GAAU,YACR;GAAI,SACP,CAAC,aAAa;GAAC,eACT,CACb,iBAAiB;IAAC;IAAa;IAAmB;IAAe;IAA2B;GAAuB,CAAC,CACrH;GAAA,eAGc,kBAAkB;GAAI,iBACpB,wBAAwB;GAAM,MACzC;IACJ,WAAW;IACX,eAAe;IACf,oBAAoB;IACpB,sBAAsB;GACvB;GAAA,UAAA;GAAA,QAAA,CAAA,o3HAAA;EAAA,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AE5CH,MAAM,YAAsC;CAC1C;CACA;CACA;CACA;CACA;CACA;AACD;AAqDD,IAAa,uBAAb,MAAa,qBAAoB;CACZ,YAAY;CACZ,aAAmC,CAAC,aAAa,QAAQ;CAE3D,eAAe,OAAO,YAAY;CAChC,SAAS,OAAO,oBAAoB;CAG9C,iBAAiB,MAAM,sBAAqB,GAAA,YAAA,CAAA,EAAA,WAAA,iBAAA,CAAA,+BAAA,CAAA,CAAA;CAGpC,SAAS,eAAc;EACtC,MAAM,yBAAS,IAAI,IAAG;EACtB,KAAK,MAAM,SAAS,KAAK,aAAa,OAAM,GAAI;GAC9C,MAAM,MAAM,GAAG,MAAM,SAAQ,GAAI,qBAAqB,QAAQ,KAAK;GACnE,MAAM,QAAQ,OAAO,IAAI,GAAG;GAC5B,IAAI,OACF,MAAM,KAAK,KAAK;QAEhB,OAAO,IAAI,KAAK,CAAC,KAAK,CAAC;EAE3B;EACA,OAAO;CACT,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,SAAA,CAAA,+BAAA,CAAA,CAAA;CAEQ,UAAU,UAAyB,MAAe;EAC1D,OAAO,KAAK,OAAM,CAAE,CAAC,IAAI,GAAG,SAAQ,GAAI,MAAM,KAAK,CAAA;CACrD;CAEU,SAAS,IAAU;EAC3B,KAAK,aAAa,MAAM,EAAE;CAC5B;CAGQ,OAAO,QAAQ,OAAY;EACjC,OAAO,MAAM,YAAY,WAAW,MAAM,YAAY,YAAY,cAAc;CAClF;CApCW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAoB,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAApB,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAAoB,cAAA;EAAA,UAAA;EAAA,QAAA,EAAA,gBAAA;GAAA,mBAAA;GAAA,YAAA;GAAA,UAAA;GAAA,YAAA;GAAA,mBAAA;EAAA,EAAA;EAAA,MAAA,EAAA,YAAA;GAAA,sCAAA;GAAA,yCAAA;EAAA,EAAA;EAAA,UAAA;EAAA,UCpEjC;EAaA,QAAA,CAAA,2kDAAA;EAAA,cAAA,CAAA;GAAA,MAAA;GAAA,MD4CY;GAAc,UAAA;GAAA,QAAA;IAAA;IAAA;IAAA;GAAA;GAAA,SAAA,CAAA,QAAA;EAAA,CAAA;EAAA,iBAAA,GAAA,wBAAA;CAAA,CAAA;;AAWb,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAoB,YAAA,CAAA;EAdhC,MAAA;EACW,MAAA,CAAA;GAAA,UAAA;GAAiB,YACf;GAAI,SACP,CAAC,cAAc;GAAC,iBAGR,wBAAwB;GAAM,MACzC;IAGJ,wCAAwC;IACxC,2CAA2C;GAC5C;GAAA,UAAA;GAAA,QAAA,CAAA,2kDAAA;EAAA,CAAA"}
|
package/package.json
CHANGED
|
@@ -52,6 +52,12 @@ export type AppShellNavPlacement = 'below-header' | 'full-height';
|
|
|
52
52
|
* content). The land and the footer run under the nav; the footer's content
|
|
53
53
|
* starts at the nav width.
|
|
54
54
|
*
|
|
55
|
+
* Toasts (3.28, AS-34): in the browser the shell tells `lc-toast-outlet` —
|
|
56
|
+
* wherever the app puts it, also after `</lc-app-shell>` — where its header
|
|
57
|
+
* ends and where the shown bar starts, so top toasts sit below the header and
|
|
58
|
+
* bottom toasts 16 px above the bar (from 1024 px: above the safe-area
|
|
59
|
+
* inset). `--lc-toast-outlet-offset-top` / `-bottom` override both.
|
|
60
|
+
*
|
|
55
61
|
* CSS hooks (set them on `lc-app-shell` or any ancestor):
|
|
56
62
|
* - `--lc-app-shell-nav-width` — the nav column from 1024 px. Default
|
|
57
63
|
* `240px` (`248px` from 1440, `280px` from 1920).
|
|
@@ -104,6 +110,7 @@ export declare class AppShellComponent {
|
|
|
104
110
|
readonly navPlacement: import("@angular/core").InputSignal<AppShellNavPlacement>;
|
|
105
111
|
protected readonly shellClasses: import("@angular/core").Signal<string>;
|
|
106
112
|
private readonly shell;
|
|
113
|
+
private readonly headerSlot;
|
|
107
114
|
private readonly barSlot;
|
|
108
115
|
constructor();
|
|
109
116
|
static ɵfac: i0.ɵɵFactoryDeclaration<AppShellComponent, never>;
|
|
@@ -133,6 +140,14 @@ export type AppPagePaddingX = 'default' | 'header';
|
|
|
133
140
|
* - `none` 0, `sm` 8 px, `md` 16 px at every width.
|
|
134
141
|
*/
|
|
135
142
|
export type AppPagePaddingTop = 'default' | 'none' | 'sm' | 'md';
|
|
143
|
+
/**
|
|
144
|
+
* Width of `lc-app-page`'s content from 1280 px (3.28, AS-36).
|
|
145
|
+
* - `default`: the content column, centred, capped at `contentMaxWidth`.
|
|
146
|
+
* - `wide`: the standard column of a page with a right column, the right
|
|
147
|
+
* column left empty, and `[page-wide]` children running over it, up to
|
|
148
|
+
* 928 px. Below 1280 px, and next to a named right column, as `default`.
|
|
149
|
+
*/
|
|
150
|
+
export type AppPageContentWidth = 'default' | 'wide';
|
|
136
151
|
/**
|
|
137
152
|
* A page inside `lc-app-shell`: the `<main>` landmark with the page's content
|
|
138
153
|
* centred at a reading width, and an optional right column docked to the
|
|
@@ -150,6 +165,16 @@ export type AppPagePaddingTop = 'default' | 'none' | 'sm' | 'md';
|
|
|
150
165
|
* inside main instead (e.g. a member row). It renders only with an
|
|
151
166
|
* accessible name — `asideLabelledby` (the id of its heading) or
|
|
152
167
|
* `asideAriaLabel`.
|
|
168
|
+
* - `reserveAside` keeps that column from 1280 px — width, tint and hairline —
|
|
169
|
+
* with no content and no landmark (`aria-hidden="true"`), so a page without
|
|
170
|
+
* right-column content keeps the same column as a page with one (3.28,
|
|
171
|
+
* AS-35). A named aside wins.
|
|
172
|
+
* - `contentWidth="wide"` (3.28, AS-36) lays the page out like a page with a
|
|
173
|
+
* right column, leaves the column empty and untinted, and lets children
|
|
174
|
+
* marked `[page-wide]` (e.g. a table's wrapper) run over it, up to 928 px;
|
|
175
|
+
* the other children (the page header, the next-step card) keep the
|
|
176
|
+
* standard column, so the title doesn't move between pages. Next to a named
|
|
177
|
+
* aside, and below 1280 px, it renders like `'default'`.
|
|
153
178
|
* - `busy` marks the page as loading: `aria-busy="true"` on `<main>` while a
|
|
154
179
|
* skeleton stands in for the content (AS-12). No visual change.
|
|
155
180
|
* - `paddingX="header"` lines the content up with `lc-header` (3.15): its
|
|
@@ -220,8 +245,37 @@ export declare class AppPageComponent {
|
|
|
220
245
|
* @default 'default'
|
|
221
246
|
*/
|
|
222
247
|
readonly paddingTop: import("@angular/core").InputSignal<AppPagePaddingTop>;
|
|
248
|
+
/**
|
|
249
|
+
* Keeps the right column from 1280 px (360 · 400 · 460 px,
|
|
250
|
+
* `--lc-app-page-aside-width`) with its tint and hairline but no content
|
|
251
|
+
* and no landmark (`aria-hidden="true"`, no role), so pages without
|
|
252
|
+
* right-column content share one column with pages that have one. A named
|
|
253
|
+
* aside (`asideLabelledby` / `asideAriaLabel`) wins; `contentWidth="wide"`
|
|
254
|
+
* leaves the column empty and untinted. Below 1280 px nothing shows.
|
|
255
|
+
* @default false
|
|
256
|
+
*/
|
|
257
|
+
readonly reserveAside: import("@angular/core").InputSignal<boolean>;
|
|
258
|
+
/**
|
|
259
|
+
* `'wide'`, from 1280 px: the page keeps the standard column of a page with
|
|
260
|
+
* a right column, the column stays empty and untinted, and `[page-wide]`
|
|
261
|
+
* children run over it, up to 928 px (or `contentMaxWidth` when it is not
|
|
262
|
+
* `880px`; `none` means the whole column). The other children keep the
|
|
263
|
+
* standard column. With a named aside, `'wide'` has no effect. Below
|
|
264
|
+
* 1280 px `'wide'` renders like `'default'`.
|
|
265
|
+
* @default 'default'
|
|
266
|
+
*/
|
|
267
|
+
readonly contentWidth: import("@angular/core").InputSignal<AppPageContentWidth>;
|
|
223
268
|
/** The right column renders only when it has an accessible name. */
|
|
224
269
|
protected readonly hasAside: import("@angular/core").Signal<boolean>;
|
|
270
|
+
/** `contentWidth="wide"` takes effect only without a named aside, which it yields to. */
|
|
271
|
+
protected readonly wide: import("@angular/core").Signal<boolean>;
|
|
272
|
+
/** The empty, unnamed column (`reserveAside`), unless a named aside or `wide` takes the space. */
|
|
273
|
+
protected readonly reserved: import("@angular/core").Signal<boolean>;
|
|
274
|
+
/**
|
|
275
|
+
* The cap of `[page-wide]` children: `null` keeps the CSS's 928 px (also
|
|
276
|
+
* for the default `contentMaxWidth`), `none` means the whole column.
|
|
277
|
+
*/
|
|
278
|
+
protected readonly wideMax: import("@angular/core").Signal<string | null>;
|
|
225
279
|
protected readonly pageClasses: import("@angular/core").Signal<string>;
|
|
226
280
|
static ɵfac: i0.ɵɵFactoryDeclaration<AppPageComponent, never>;
|
|
227
281
|
static ɵcmp: i0.ɵɵComponentDeclaration<AppPageComponent, "lc-app-page", never, {
|
|
@@ -260,5 +314,15 @@ export declare class AppPageComponent {
|
|
|
260
314
|
"required": false;
|
|
261
315
|
"isSignal": true;
|
|
262
316
|
};
|
|
317
|
+
"reserveAside": {
|
|
318
|
+
"alias": "reserveAside";
|
|
319
|
+
"required": false;
|
|
320
|
+
"isSignal": true;
|
|
321
|
+
};
|
|
322
|
+
"contentWidth": {
|
|
323
|
+
"alias": "contentWidth";
|
|
324
|
+
"required": false;
|
|
325
|
+
"isSignal": true;
|
|
326
|
+
};
|
|
263
327
|
}, {}, never, ["*", "[page-aside]"], true, never>;
|
|
264
328
|
}
|
|
@@ -28,4 +28,25 @@ export declare class OverlayStackService {
|
|
|
28
28
|
claim(id: string, event: Event): boolean;
|
|
29
29
|
static ɵfac: i0.ɵɵFactoryDeclaration<OverlayStackService, never>;
|
|
30
30
|
static ɵprov: i0.ɵɵInjectableDeclaration<any>;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The space a fixed overlay keeps free at the top and the bottom of the
|
|
34
|
+
* viewport, as CSS lengths (3.28, AS-34). `lc-app-shell` writes them in the
|
|
35
|
+
* browser: `top` is its header's bottom edge, `bottom` the shown bottom bar's
|
|
36
|
+
* top edge, measured from the viewport's bottom (or the safe-area insets when
|
|
37
|
+
* there is no header or no bar). `lc-toast-outlet` reads them, so its stacks
|
|
38
|
+
* clear the header and the bar even when the app renders the outlet outside
|
|
39
|
+
* the shell (no custom property can inherit there).
|
|
40
|
+
*
|
|
41
|
+
* `null` means "no shell": on the server, before the shell has measured,
|
|
42
|
+
* without ResizeObserver and after the shell is destroyed. Internal to the
|
|
43
|
+
* library; not part of the public API and without a semver guarantee.
|
|
44
|
+
*/
|
|
45
|
+
export declare class OverlayInsetsService {
|
|
46
|
+
/** CSS length kept free above top-edge overlays, e.g. `'56px'`. */
|
|
47
|
+
readonly top: import("@angular/core").WritableSignal<string | null>;
|
|
48
|
+
/** CSS length kept free below bottom-edge overlays, e.g. `'102px'`. */
|
|
49
|
+
readonly bottom: import("@angular/core").WritableSignal<string | null>;
|
|
50
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<OverlayInsetsService, never>;
|
|
51
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<any>;
|
|
31
52
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as i0 from "@angular/core";
|
|
2
2
|
import { Signal } from "@angular/core";
|
|
3
|
+
import { OverlayInsetsService } from "@lifecorner/angular-ui-kit/internal/overlay";
|
|
3
4
|
export type ToastVariant = 'success' | 'error' | 'warning' | 'info';
|
|
4
5
|
export type ToastPosition = 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right';
|
|
5
6
|
export interface ToastAction {
|
|
@@ -181,11 +182,35 @@ type ToastLive = 'polite' | 'assertive';
|
|
|
181
182
|
* into an *existing* live region — one `polite` for success/info toasts and one
|
|
182
183
|
* `assertive` for error/warning toasts. Dismissing a toast (close button,
|
|
183
184
|
* action, auto-dismiss) removes it from the service.
|
|
185
|
+
*
|
|
186
|
+
* Offsets (3.28, AS-34): with `lc-app-shell` in the app (the outlet may sit
|
|
187
|
+
* before or after it), the `top-*` stacks start below the shell's header and
|
|
188
|
+
* the `bottom-*` stacks end above its bottom bar, each with the stack's 16 px
|
|
189
|
+
* padding as the gap. The shell measures both in the browser (the header's
|
|
190
|
+
* bottom edge with the top inset; the shown bar's top edge, from the
|
|
191
|
+
* viewport's bottom, with the bottom inset). From 1024 px, where the bar is
|
|
192
|
+
* hidden, the bottom offset is the safe-area inset. Without a shell, and in
|
|
193
|
+
* prerendered HTML, both are `0px`, as before. Every stack is at most as high
|
|
194
|
+
* as the space between the offsets and scrolls when its toasts need more
|
|
195
|
+
* (200 % text), so each toast stays reachable. In the shell, prefer the
|
|
196
|
+
* `bottom-*` positions: a top toast can still cover a page title placed
|
|
197
|
+
* right under the header.
|
|
198
|
+
*
|
|
199
|
+
* CSS hooks (set them on `lc-toast-outlet` or any ancestor, e.g. `:root`;
|
|
200
|
+
* each wins over the shell's value; write lengths, `0px` rather than `0`):
|
|
201
|
+
* - `--lc-toast-outlet-offset-top` — the space kept free above the `top-*`
|
|
202
|
+
* stacks. Default `0px`; with `lc-app-shell`: its header's bottom edge, or
|
|
203
|
+
* `env(safe-area-inset-top, 0px)` without a header.
|
|
204
|
+
* - `--lc-toast-outlet-offset-bottom` — the space kept free below the
|
|
205
|
+
* `bottom-*` stacks. Default `0px`; with `lc-app-shell`: the shown bottom
|
|
206
|
+
* bar's top edge, measured from the viewport's bottom, or
|
|
207
|
+
* `env(safe-area-inset-bottom, 0px)` when no bar shows.
|
|
184
208
|
*/
|
|
185
209
|
export declare class ToastOutletComponent {
|
|
186
210
|
protected readonly positions: readonly ToastPosition[];
|
|
187
211
|
protected readonly liveLevels: readonly ToastLive[];
|
|
188
212
|
private readonly toastService;
|
|
213
|
+
protected readonly insets: OverlayInsetsService;
|
|
189
214
|
/** Accessible name of every toast's close button (forwarded to lc-toast). */
|
|
190
215
|
readonly closeAriaLabel: import("@angular/core").InputSignal<string>;
|
|
191
216
|
/** Toasts keyed by `${position}:${politeness}` in the order they were shown. */
|