@lifecorner/angular-ui-kit 3.14.0 → 3.16.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.
Files changed (60) hide show
  1. package/README.md +2 -1
  2. package/fesm2022/lifecorner-angular-ui-kit-app-shell.mjs +36 -2
  3. package/fesm2022/lifecorner-angular-ui-kit-app-shell.mjs.map +1 -1
  4. package/fesm2022/lifecorner-angular-ui-kit-avatar-group.mjs +2 -1
  5. package/fesm2022/lifecorner-angular-ui-kit-avatar-group.mjs.map +1 -1
  6. package/fesm2022/lifecorner-angular-ui-kit-avatar.mjs +23 -5
  7. package/fesm2022/lifecorner-angular-ui-kit-avatar.mjs.map +1 -1
  8. package/fesm2022/lifecorner-angular-ui-kit-button.mjs +39 -6
  9. package/fesm2022/lifecorner-angular-ui-kit-button.mjs.map +1 -1
  10. package/fesm2022/lifecorner-angular-ui-kit-choice-prompt.mjs +1 -0
  11. package/fesm2022/lifecorner-angular-ui-kit-choice-prompt.mjs.map +1 -1
  12. package/fesm2022/lifecorner-angular-ui-kit-confirm-dialog.mjs +1 -0
  13. package/fesm2022/lifecorner-angular-ui-kit-confirm-dialog.mjs.map +1 -1
  14. package/fesm2022/lifecorner-angular-ui-kit-copy-field.mjs +1 -0
  15. package/fesm2022/lifecorner-angular-ui-kit-copy-field.mjs.map +1 -1
  16. package/fesm2022/lifecorner-angular-ui-kit-empty-state.mjs +42 -5
  17. package/fesm2022/lifecorner-angular-ui-kit-empty-state.mjs.map +1 -1
  18. package/fesm2022/lifecorner-angular-ui-kit-header.mjs +21 -3
  19. package/fesm2022/lifecorner-angular-ui-kit-header.mjs.map +1 -1
  20. package/fesm2022/lifecorner-angular-ui-kit-icon.mjs +2 -2
  21. package/fesm2022/lifecorner-angular-ui-kit-icon.mjs.map +1 -1
  22. package/fesm2022/lifecorner-angular-ui-kit-icons.mjs +26 -2
  23. package/fesm2022/lifecorner-angular-ui-kit-icons.mjs.map +1 -1
  24. package/fesm2022/lifecorner-angular-ui-kit-internal-icon-set.mjs +10 -1
  25. package/fesm2022/lifecorner-angular-ui-kit-internal-icon-set.mjs.map +1 -1
  26. package/fesm2022/lifecorner-angular-ui-kit-list.mjs +30 -6
  27. package/fesm2022/lifecorner-angular-ui-kit-list.mjs.map +1 -1
  28. package/fesm2022/lifecorner-angular-ui-kit-money.mjs +250 -0
  29. package/fesm2022/lifecorner-angular-ui-kit-money.mjs.map +1 -0
  30. package/fesm2022/lifecorner-angular-ui-kit-next-step-card.mjs +22 -2
  31. package/fesm2022/lifecorner-angular-ui-kit-next-step-card.mjs.map +1 -1
  32. package/fesm2022/lifecorner-angular-ui-kit-page-header.mjs +20 -3
  33. package/fesm2022/lifecorner-angular-ui-kit-page-header.mjs.map +1 -1
  34. package/fesm2022/lifecorner-angular-ui-kit-sidenav-placeholder.mjs +2 -2
  35. package/fesm2022/lifecorner-angular-ui-kit-sidenav-placeholder.mjs.map +1 -1
  36. package/fesm2022/lifecorner-angular-ui-kit-sidenav.mjs +2 -2
  37. package/fesm2022/lifecorner-angular-ui-kit-sidenav.mjs.map +1 -1
  38. package/fesm2022/lifecorner-angular-ui-kit-switch.mjs +21 -4
  39. package/fesm2022/lifecorner-angular-ui-kit-switch.mjs.map +1 -1
  40. package/fesm2022/lifecorner-angular-ui-kit-table.mjs +72 -7
  41. package/fesm2022/lifecorner-angular-ui-kit-table.mjs.map +1 -1
  42. package/fesm2022/lifecorner-angular-ui-kit.mjs +1 -0
  43. package/package.json +5 -1
  44. package/src/styles/theme-contrast.spec.ts +74 -0
  45. package/types/lifecorner-angular-ui-kit-app-shell.d.ts +56 -0
  46. package/types/lifecorner-angular-ui-kit-avatar.d.ts +24 -0
  47. package/types/lifecorner-angular-ui-kit-button.d.ts +31 -0
  48. package/types/lifecorner-angular-ui-kit-empty-state.d.ts +38 -0
  49. package/types/lifecorner-angular-ui-kit-header.d.ts +15 -2
  50. package/types/lifecorner-angular-ui-kit-icon.d.ts +2 -1
  51. package/types/lifecorner-angular-ui-kit-icons.d.ts +5 -1
  52. package/types/lifecorner-angular-ui-kit-list.d.ts +38 -0
  53. package/types/lifecorner-angular-ui-kit-money.d.ts +150 -0
  54. package/types/lifecorner-angular-ui-kit-next-step-card.d.ts +26 -0
  55. package/types/lifecorner-angular-ui-kit-page-header.d.ts +20 -0
  56. package/types/lifecorner-angular-ui-kit-sidenav-placeholder.d.ts +4 -2
  57. package/types/lifecorner-angular-ui-kit-sidenav.d.ts +5 -2
  58. package/types/lifecorner-angular-ui-kit-switch.d.ts +19 -0
  59. package/types/lifecorner-angular-ui-kit-table.d.ts +42 -1
  60. package/types/lifecorner-angular-ui-kit.d.ts +1 -0
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @lifecorner/angular-ui-kit
2
2
 
3
- Angular UI component library for the **LifeCorner Design System** — 110 accessible, themeable Angular standalone components (for apps on Angular 21 and 22) with signal-based inputs, OnPush change detection, light/dark themes, and a full density token system (`compact` / `cosy` / `comfortable`).
3
+ Angular UI component library for the **LifeCorner Design System** — 111 accessible, themeable Angular standalone components (for apps on Angular 21 and 22) with signal-based inputs, OnPush change detection, light/dark themes, and a full density token system (`compact` / `cosy` / `comfortable`).
4
4
 
5
5
  ## Resources
6
6
 
@@ -145,6 +145,7 @@ and the CSP notes.
145
145
  | List | `lc-list` | List display |
146
146
  | Log Viewer | `lc-log-viewer` | Streaming log/terminal viewer with virtualization |
147
147
  | Metric Card | `lc-metric-card` | KPI/metric display |
148
+ | Money | `lc-money` | An amount in minor units: locale, ISO 4217 minor units, U+2212 minus, tabular figures, spoken text; `formatMoney()` for formatters (3.16) |
148
149
  | Pipeline | `lc-pipeline` | Status timeline of connected process nodes; per-node status (complete/current/pending/warning/error) with captions, horizontal/vertical |
149
150
  | Skeleton | `lc-skeleton` | Loading placeholder |
150
151
  | Spinner | `lc-spinner` | Loading indicator |
@@ -144,10 +144,14 @@ var AppPageComponent = class AppPageComponent {
144
144
  asideLabelledby = input("", ...ngDevMode ? [{ debugName: "asideLabelledby" }] : /* istanbul ignore next */ []);
145
145
  asideAriaLabel = input("", ...ngDevMode ? [{ debugName: "asideAriaLabel" }] : /* istanbul ignore next */ []);
146
146
  busy = input(false, ...ngDevMode ? [{ debugName: "busy" }] : /* istanbul ignore next */ []);
147
+ paddingX = input("default", ...ngDevMode ? [{ debugName: "paddingX" }] : /* istanbul ignore next */ []);
148
+ paddingTop = input("default", ...ngDevMode ? [{ debugName: "paddingTop" }] : /* istanbul ignore next */ []);
147
149
  hasAside = computed(() => !!(this.asideLabelledby() || this.asideAriaLabel()), ...ngDevMode ? [{ debugName: "hasAside" }] : /* istanbul ignore next */ []);
148
150
  pageClasses = computed(() => {
149
151
  const classes = ["lc-app-page"];
150
152
  if (this.hasAside()) classes.push("lc-app-page--aside");
153
+ if (this.paddingX() === "header") classes.push("lc-app-page--pad-x-header");
154
+ if (this.paddingTop() !== "default") classes.push(`lc-app-page--pad-top-${this.paddingTop()}`);
151
155
  return classes.join(" ");
152
156
  }, ...ngDevMode ? [{ debugName: "pageClasses" }] : /* istanbul ignore next */ []);
153
157
  static ɵfac = i0.ɵɵngDeclareFactory({
@@ -199,11 +203,25 @@ var AppPageComponent = class AppPageComponent {
199
203
  isSignal: true,
200
204
  isRequired: false,
201
205
  transformFunction: null
206
+ },
207
+ paddingX: {
208
+ classPropertyName: "paddingX",
209
+ publicName: "paddingX",
210
+ isSignal: true,
211
+ isRequired: false,
212
+ transformFunction: null
213
+ },
214
+ paddingTop: {
215
+ classPropertyName: "paddingTop",
216
+ publicName: "paddingTop",
217
+ isSignal: true,
218
+ isRequired: false,
219
+ transformFunction: null
202
220
  }
203
221
  },
204
222
  ngImport: i0,
205
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 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",
206
- 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)}}@media(forced-colors:active){.lc-app-page__aside{background:none;border-image:none;border-left-color:CanvasText}}\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"],
207
225
  changeDetection: i0.ChangeDetectionStrategy.OnPush
208
226
  });
209
227
  };
@@ -219,7 +237,7 @@ i0.ɵɵngDeclareClassMetadata({
219
237
  standalone: true,
220
238
  changeDetection: ChangeDetectionStrategy.OnPush,
221
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 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",
222
- 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)}}@media(forced-colors:active){.lc-app-page__aside{background:none;border-image:none;border-left-color:CanvasText}}\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"]
223
241
  }]
224
242
  }],
225
243
  propDecorators: {
@@ -262,6 +280,22 @@ i0.ɵɵngDeclareClassMetadata({
262
280
  alias: "busy",
263
281
  required: false
264
282
  }]
283
+ }],
284
+ paddingX: [{
285
+ type: i0.Input,
286
+ args: [{
287
+ isSignal: true,
288
+ alias: "paddingX",
289
+ required: false
290
+ }]
291
+ }],
292
+ paddingTop: [{
293
+ type: i0.Input,
294
+ args: [{
295
+ isSignal: true,
296
+ alias: "paddingTop",
297
+ required: false
298
+ }]
265
299
  }]
266
300
  }
267
301
  });
@@ -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 * 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 *\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 /** 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 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":"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"}
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":"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"}
@@ -89,7 +89,8 @@ var AvatarGroupComponent = class AvatarGroupComponent {
89
89
  "status",
90
90
  "color",
91
91
  "ring",
92
- "decorative"
92
+ "decorative",
93
+ "fallback"
93
94
  ]
94
95
  }],
95
96
  changeDetection: i0.ChangeDetectionStrategy.OnPush
@@ -1 +1 @@
1
- {"version":3,"file":"lifecorner-angular-ui-kit-avatar-group.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/avatar-group/avatar-group.component.ts","../../../../libs/angular-ui-kit/src/lib/avatar-group/avatar-group.component.html"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n input,\n computed,\n} from '@angular/core';\nimport { AvatarColor, AvatarComponent, AvatarSize } from '@lifecorner/angular-ui-kit/avatar';\n\nexport interface AvatarGroupItem {\n /** Image source URL */\n src?: string;\n /** Alt text */\n alt?: string;\n /** Full name for initials fallback */\n name?: string;\n /** Background of the initials: `default` or a member colour `member-1` … `member-6` (see `lc-avatar`). */\n color?: AvatarColor;\n}\n\n@Component({\n selector: 'lc-avatar-group',\n standalone: true,\n imports: [AvatarComponent],\n templateUrl: './avatar-group.component.html',\n styleUrls: ['./avatar-group.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\n/**\n * Avatar group component for displaying multiple user avatars.\n *\n * Features:\n * - Overlapping avatar display with configurable max visible count\n * - Overflow indicator showing remaining count\n * - Shared size variant across all avatars (xs, sm, md, lg)\n * - Automatic truncation based on max property\n * - Member colours per item (`color`), later avatars on top\n * - `ring`: each avatar gets a ring in the page colour instead of the surface\n * border; with `overlap=\"4px\"` this is the \"Weite\" member row\n *\n * @example\n * ```html\n * <lc-avatar-group [avatars]=\"users\" [max]=\"5\" size=\"md\" />\n * <lc-avatar-group\n * [avatars]=\"[{ name: 'Johanna Apfelbaum', color: 'member-1' }, { name: 'Tom Ek', color: 'member-2' }]\"\n * [ring]=\"true\"\n * overlap=\"4px\"\n * />\n * ```\n */\nexport class AvatarGroupComponent {\n /** List of avatars to show */\n avatars = input.required<AvatarGroupItem[]>();\n\n /** Avatar size */\n size = input<AvatarSize>('md');\n\n /** Maximum visible avatars before showing overflow count */\n max = input<number>(5);\n\n /**\n * Separate the avatars with a ring in the page colour\n * (`--lc-avatar-ring-color`, default `--color-background`) instead of the\n * 2 px surface border. Later avatars sit on top.\n */\n ring = input<boolean>(false);\n\n /**\n * How far each avatar overlaps the previous one, as a CSS length (e.g.\n * `4px`). Empty keeps the size's default (6–12 px).\n */\n overlap = input<string>('');\n\n protected visibleAvatars = computed(() => {\n const all = this.avatars();\n const limit = this.max();\n return all.slice(0, limit);\n });\n\n protected overflowCount = computed(() => {\n const all = this.avatars();\n const limit = this.max();\n return Math.max(0, all.length - limit);\n });\n\n protected groupClasses = computed(() => {\n const classes = ['avatar-group', `avatar-group--${this.size()}`];\n if (this.ring()) classes.push('avatar-group--ring');\n return classes.join(' ');\n });\n}\n","<div [class]=\"groupClasses()\" [style.--lc-avatar-group-overlap]=\"overlap() || null\">\n @for (avatar of visibleAvatars(); track avatar.name ?? $index) {\n <lc-avatar\n class=\"avatar-group__item\"\n [src]=\"avatar.src\"\n [alt]=\"avatar.alt\"\n [name]=\"avatar.name\"\n [size]=\"size()\"\n [color]=\"avatar.color ?? 'default'\"\n [ring]=\"ring()\"\n />\n }\n @if (overflowCount() > 0) {\n <span class=\"avatar-group__overflow avatar-group__overflow--{{ size() }}\">\n +{{ overflowCount() }}\n </span>\n }\n</div>\n"],"mappings":";;;AAiDA,IAAa,uBAAb,MAAa,qBAAoB;CAE/B,UAAU,MAAM,SAA6B,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CAG7C,OAAO,MAAkB,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,OAAA,CAAA,+BAAA,CAAA,CAAA;CAG9B,MAAM,MAAc,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,MAAA,CAAA,+BAAA,CAAA,CAAA;CAOtB,OAAO,MAAe,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,OAAA,CAAA,+BAAA,CAAA,CAAA;CAM5B,UAAU,MAAc,IAAG,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CAEjB,iBAAiB,eAAc;EACvC,MAAM,MAAM,KAAK,QAAO;EACxB,MAAM,QAAQ,KAAK,IAAG;EACtB,OAAO,IAAI,MAAM,GAAG,KAAK;CAC3B,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,iBAAA,CAAA,+BAAA,CAAA,CAAA;CAEQ,gBAAgB,eAAc;EACtC,MAAM,MAAM,KAAK,QAAO;EACxB,MAAM,QAAQ,KAAK,IAAG;EACtB,OAAO,KAAK,IAAI,GAAG,IAAI,SAAS,KAAK;CACvC,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,gBAAA,CAAA,+BAAA,CAAA,CAAA;CAEQ,eAAe,eAAc;EACrC,MAAM,UAAU,CAAC,gBAAgB,iBAAiB,KAAK,KAAI,GAAI;EAC/D,IAAI,KAAK,KAAI,GAAI,QAAQ,KAAK,oBAAoB;EAClD,OAAO,QAAQ,KAAK,GAAG;CACzB,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,eAAA,CAAA,+BAAA,CAAA,CAAA;CAvCS,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;GAAA,SAAA;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,KAAA;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,SAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;EAAA;EAAA,UAAA;EAAA,UCjDjC;EAkBA,QAAA,CAAA,6xDAAA;EAAA,cAAA,CAAA;GAAA,MAAA;GAAA,MDIY;GAAe,UAAA;GAAA,QAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;GAAA;EAAA,CAAA;EAAA,iBAAA,GAAA,wBAAA;CAAA,CAAA;;AA2Bd,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAoB,YAAA,CAAA;EA9BhC,MAAA;EACW,MAAA,CAAA;GAAA,UAAA;GAAiB,YACf;GAAI,SACP,CAAC,eAAe;GAAC,iBAGT,wBAAwB;GAAM,UAAA;GAAA,QAAA,CAAA,6xDAAA;EAAA,CAAA"}
1
+ {"version":3,"file":"lifecorner-angular-ui-kit-avatar-group.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/avatar-group/avatar-group.component.ts","../../../../libs/angular-ui-kit/src/lib/avatar-group/avatar-group.component.html"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n input,\n computed,\n} from '@angular/core';\nimport { AvatarColor, AvatarComponent, AvatarSize } from '@lifecorner/angular-ui-kit/avatar';\n\nexport interface AvatarGroupItem {\n /** Image source URL */\n src?: string;\n /** Alt text */\n alt?: string;\n /** Full name for initials fallback */\n name?: string;\n /** Background of the initials: `default` or a member colour `member-1` … `member-6` (see `lc-avatar`). */\n color?: AvatarColor;\n}\n\n@Component({\n selector: 'lc-avatar-group',\n standalone: true,\n imports: [AvatarComponent],\n templateUrl: './avatar-group.component.html',\n styleUrls: ['./avatar-group.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\n/**\n * Avatar group component for displaying multiple user avatars.\n *\n * Features:\n * - Overlapping avatar display with configurable max visible count\n * - Overflow indicator showing remaining count\n * - Shared size variant across all avatars (xs, sm, md, lg)\n * - Automatic truncation based on max property\n * - Member colours per item (`color`), later avatars on top\n * - `ring`: each avatar gets a ring in the page colour instead of the surface\n * border; with `overlap=\"4px\"` this is the \"Weite\" member row\n *\n * @example\n * ```html\n * <lc-avatar-group [avatars]=\"users\" [max]=\"5\" size=\"md\" />\n * <lc-avatar-group\n * [avatars]=\"[{ name: 'Johanna Apfelbaum', color: 'member-1' }, { name: 'Tom Ek', color: 'member-2' }]\"\n * [ring]=\"true\"\n * overlap=\"4px\"\n * />\n * ```\n */\nexport class AvatarGroupComponent {\n /** List of avatars to show */\n avatars = input.required<AvatarGroupItem[]>();\n\n /** Avatar size */\n size = input<AvatarSize>('md');\n\n /** Maximum visible avatars before showing overflow count */\n max = input<number>(5);\n\n /**\n * Separate the avatars with a ring in the page colour\n * (`--lc-avatar-ring-color`, default `--color-background`) instead of the\n * 2 px surface border. Later avatars sit on top.\n */\n ring = input<boolean>(false);\n\n /**\n * How far each avatar overlaps the previous one, as a CSS length (e.g.\n * `4px`). Empty keeps the size's default (6–12 px).\n */\n overlap = input<string>('');\n\n protected visibleAvatars = computed(() => {\n const all = this.avatars();\n const limit = this.max();\n return all.slice(0, limit);\n });\n\n protected overflowCount = computed(() => {\n const all = this.avatars();\n const limit = this.max();\n return Math.max(0, all.length - limit);\n });\n\n protected groupClasses = computed(() => {\n const classes = ['avatar-group', `avatar-group--${this.size()}`];\n if (this.ring()) classes.push('avatar-group--ring');\n return classes.join(' ');\n });\n}\n","<div [class]=\"groupClasses()\" [style.--lc-avatar-group-overlap]=\"overlap() || null\">\n @for (avatar of visibleAvatars(); track avatar.name ?? $index) {\n <lc-avatar\n class=\"avatar-group__item\"\n [src]=\"avatar.src\"\n [alt]=\"avatar.alt\"\n [name]=\"avatar.name\"\n [size]=\"size()\"\n [color]=\"avatar.color ?? 'default'\"\n [ring]=\"ring()\"\n />\n }\n @if (overflowCount() > 0) {\n <span class=\"avatar-group__overflow avatar-group__overflow--{{ size() }}\">\n +{{ overflowCount() }}\n </span>\n }\n</div>\n"],"mappings":";;;AAiDA,IAAa,uBAAb,MAAa,qBAAoB;CAE/B,UAAU,MAAM,SAA6B,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CAG7C,OAAO,MAAkB,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,OAAA,CAAA,+BAAA,CAAA,CAAA;CAG9B,MAAM,MAAc,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,MAAA,CAAA,+BAAA,CAAA,CAAA;CAOtB,OAAO,MAAe,OAAM,GAAA,YAAA,CAAA,EAAA,WAAA,OAAA,CAAA,+BAAA,CAAA,CAAA;CAM5B,UAAU,MAAc,IAAG,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;CAEjB,iBAAiB,eAAc;EACvC,MAAM,MAAM,KAAK,QAAO;EACxB,MAAM,QAAQ,KAAK,IAAG;EACtB,OAAO,IAAI,MAAM,GAAG,KAAK;CAC3B,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,iBAAA,CAAA,+BAAA,CAAA,CAAA;CAEQ,gBAAgB,eAAc;EACtC,MAAM,MAAM,KAAK,QAAO;EACxB,MAAM,QAAQ,KAAK,IAAG;EACtB,OAAO,KAAK,IAAI,GAAG,IAAI,SAAS,KAAK;CACvC,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,gBAAA,CAAA,+BAAA,CAAA,CAAA;CAEQ,eAAe,eAAc;EACrC,MAAM,UAAU,CAAC,gBAAgB,iBAAiB,KAAK,KAAI,GAAI;EAC/D,IAAI,KAAK,KAAI,GAAI,QAAQ,KAAK,oBAAoB;EAClD,OAAO,QAAQ,KAAK,GAAG;CACzB,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,eAAA,CAAA,+BAAA,CAAA,CAAA;CAvCS,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;GAAA,SAAA;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,KAAA;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,SAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;EAAA;EAAA,UAAA;EAAA,UCjDjC;EAkBA,QAAA,CAAA,6xDAAA;EAAA,cAAA,CAAA;GAAA,MAAA;GAAA,MDIY;GAAe,UAAA;GAAA,QAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;IAAA;GAAA;EAAA,CAAA;EAAA,iBAAA,GAAA,wBAAA;CAAA,CAAA;;AA2Bd,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAoB,YAAA,CAAA;EA9BhC,MAAA;EACW,MAAA,CAAA;GAAA,UAAA;GAAiB,YACf;GAAI,SACP,CAAC,eAAe;GAAC,iBAGT,wBAAwB;GAAM,UAAA;GAAA,QAAA,CAAA,6xDAAA;EAAA,CAAA"}
@@ -35,12 +35,15 @@ var AvatarComponent = class AvatarComponent {
35
35
  color = input("default", ...ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []);
36
36
  ring = input(false, ...ngDevMode ? [{ debugName: "ring" }] : /* istanbul ignore next */ []);
37
37
  decorative = input(false, ...ngDevMode ? [{ debugName: "decorative" }] : /* istanbul ignore next */ []);
38
+ fallback = input("initials", ...ngDevMode ? [{ debugName: "fallback" }] : /* istanbul ignore next */ []);
38
39
  initials = computed(() => ɵavatarInitials(this.name()), ...ngDevMode ? [{ debugName: "initials" }] : /* istanbul ignore next */ []);
40
+ showPerson = computed(() => this.fallback() === "person" && !this.showImage() && !this.name()?.trim(), ...ngDevMode ? [{ debugName: "showPerson" }] : /* istanbul ignore next */ []);
39
41
  avatarClasses = computed(() => {
40
42
  const classes = ["lc-avatar"];
41
43
  classes.push(`lc-avatar--${this.size()}`);
42
44
  if (this.status()) classes.push("lc-avatar--with-status");
43
- if (this.color() !== "default") classes.push("lc-avatar--member", `lc-avatar--${this.color()}`);
45
+ if (this.showPerson()) classes.push("lc-avatar--person");
46
+ else if (this.color() !== "default") classes.push("lc-avatar--member", `lc-avatar--${this.color()}`);
44
47
  if (this.ring()) classes.push("lc-avatar--ring");
45
48
  return classes.join(" ");
46
49
  }, ...ngDevMode ? [{ debugName: "avatarClasses" }] : /* istanbul ignore next */ []);
@@ -136,11 +139,18 @@ var AvatarComponent = class AvatarComponent {
136
139
  isSignal: true,
137
140
  isRequired: false,
138
141
  transformFunction: null
142
+ },
143
+ fallback: {
144
+ classPropertyName: "fallback",
145
+ publicName: "fallback",
146
+ isSignal: true,
147
+ isRequired: false,
148
+ transformFunction: null
139
149
  }
140
150
  },
141
151
  ngImport: i0,
142
- template: "<div \n [class]=\"avatarClasses()\"\n [attr.role]=\"decorative() ? null : 'img'\"\n [attr.aria-label]=\"decorative() ? null : ariaLabel()\"\n [attr.aria-hidden]=\"decorative() ? 'true' : null\"\n>\n @if (showImage()) {\n <!-- `loading` before `src`: attribute bindings run in template order, after\n every property binding, so `[attr.src]` keeps the source behind\n `loading` (G-40; an engine may start the request when src is set). -->\n <img\n [alt]=\"alt() || name() || 'Avatar'\"\n class=\"lc-avatar__image\"\n [attr.width]=\"imagePx()\"\n [attr.height]=\"imagePx()\"\n [attr.loading]=\"loading() === 'lazy' ? 'lazy' : null\"\n [attr.src]=\"src()!\"\n (error)=\"onImageError()\"\n />\n } @else {\n <span class=\"lc-avatar__initials\">{{ initials() }}</span>\n }\n\n @if (status()) {\n <span \n class=\"lc-avatar__status lc-avatar__status--{{ status() }}\"\n [attr.aria-label]=\"status() + ' status'\"\n ></span>\n }\n</div>\n",
143
- styles: [".lc-avatar{position:relative;display:inline-flex;align-items:center;justify-content:center;border-radius:50%;background-color:var(--color-surface-hover);color:var(--color-text-secondary);font-weight:var(--typography-font-weight-medium, 500);line-height:1;overflow:hidden;flex-shrink:0;-webkit-user-select:none;user-select:none}.lc-avatar__image{width:100%;height:100%;object-fit:cover;border-radius:50%}.lc-avatar__initials{display:flex;align-items:center;justify-content:center;width:100%;height:100%;font-size:inherit;text-transform:uppercase}.lc-avatar__status{position:absolute;bottom:0;right:0;width:25%;height:25%;border-radius:50%;border:2px solid var(--color-surface)}.lc-avatar__status--online{background-color:var(--color-success)}.lc-avatar__status--offline{background-color:var(--color-text-tertiary)}.lc-avatar__status--away{background-color:var(--color-warning-500)}.lc-avatar__status--busy{background-color:var(--color-error-500)}.lc-avatar--xs{width:1.5rem;height:1.5rem;font-size:var(--typography-font-size-xs, .75rem)}.lc-avatar--xs .lc-avatar__status{width:.375rem;height:.375rem;border-width:1px}.lc-avatar--sm{width:2rem;height:2rem;font-size:var(--typography-font-size-sm, .875rem)}.lc-avatar--sm .lc-avatar__status{width:.5rem;height:.5rem;border-width:1.5px}.lc-avatar--md{width:2.5rem;height:2.5rem;font-size:var(--typography-font-size-base, 1rem)}.lc-avatar--md .lc-avatar__status{width:.625rem;height:.625rem;border-width:2px}.lc-avatar--lg{width:3rem;height:3rem;font-size:var(--typography-font-size-lg, 1.125rem)}.lc-avatar--lg .lc-avatar__status{width:.75rem;height:.75rem;border-width:2px}.lc-avatar--xl{width:4rem;height:4rem;font-size:var(--typography-font-size-xl, 1.25rem)}.lc-avatar--xl .lc-avatar__status{width:1rem;height:1rem;border-width:2px}.lc-avatar--member{color:var(--color-on-member, #ffffff);font-weight:var(--typography-font-weight-bold, 700);letter-spacing:.02em}.lc-avatar--member.lc-avatar--xs{font-size:.625rem}.lc-avatar--member.lc-avatar--sm{font-size:.75rem}.lc-avatar--member.lc-avatar--md{font-size:.875rem}.lc-avatar--member.lc-avatar--lg{font-size:1rem}.lc-avatar--member.lc-avatar--xl{font-size:1.25rem}.lc-avatar--member-1{background-color:var(--color-member-1, #1a6a79)}.lc-avatar--member-2{background-color:var(--color-member-2, #b04a22)}.lc-avatar--member-3{background-color:var(--color-member-3, #4e3f84)}.lc-avatar--member-4{background-color:var(--color-member-4, #5f7150)}.lc-avatar--member-5{background-color:var(--color-member-5, #9e3846)}.lc-avatar--member-6{background-color:var(--color-member-6, #5a798c)}.lc-avatar--ring{box-shadow:0 0 0 var(--lc-avatar-ring-width, 3px) var(--lc-avatar-ring-color, var(--color-background))}.lc-avatar--ring.lc-avatar--xs,.lc-avatar--ring.lc-avatar--sm{box-shadow:0 0 0 var(--lc-avatar-ring-width, 2px) var(--lc-avatar-ring-color, var(--color-background))}\n"],
152
+ template: "<div \n [class]=\"avatarClasses()\"\n [attr.role]=\"decorative() ? null : 'img'\"\n [attr.aria-label]=\"decorative() ? null : ariaLabel()\"\n [attr.aria-hidden]=\"decorative() ? 'true' : null\"\n>\n @if (showImage()) {\n <!-- `loading` before `src`: attribute bindings run in template order, after\n every property binding, so `[attr.src]` keeps the source behind\n `loading` (G-40; an engine may start the request when src is set). -->\n <img\n [alt]=\"alt() || name() || 'Avatar'\"\n class=\"lc-avatar__image\"\n [attr.width]=\"imagePx()\"\n [attr.height]=\"imagePx()\"\n [attr.loading]=\"loading() === 'lazy' ? 'lazy' : null\"\n [attr.src]=\"src()!\"\n (error)=\"onImageError()\"\n />\n } @else if (showPerson()) {\n <!-- The kit's user glyph (ICON_USER_SOLID) inline: no icon registration, final in prerendered HTML. -->\n <svg class=\"lc-avatar__person\" viewBox=\"0 0 24 24\" aria-hidden=\"true\" focusable=\"false\">\n <path\n fill-rule=\"evenodd\"\n clip-rule=\"evenodd\"\n d=\"M7.5 6a4.5 4.5 0 119 0 4.5 4.5 0 01-9 0zM3.751 20.105a8.25 8.25 0 0116.498 0 .75.75 0 01-.437.695A18.683 18.683 0 0112 22.5c-2.786 0-5.433-.608-7.812-1.7a.75.75 0 01-.437-.695z\"\n />\n </svg>\n } @else {\n <span class=\"lc-avatar__initials\">{{ initials() }}</span>\n }\n\n @if (status()) {\n <span \n class=\"lc-avatar__status lc-avatar__status--{{ status() }}\"\n [attr.aria-label]=\"status() + ' status'\"\n ></span>\n }\n</div>\n",
153
+ styles: [".lc-avatar{position:relative;display:inline-flex;align-items:center;justify-content:center;border-radius:50%;background-color:var(--color-surface-hover);color:var(--color-text-secondary);font-weight:var(--typography-font-weight-medium, 500);line-height:1;overflow:hidden;flex-shrink:0;-webkit-user-select:none;user-select:none}.lc-avatar__image{width:100%;height:100%;object-fit:cover;border-radius:50%}.lc-avatar__initials{display:flex;align-items:center;justify-content:center;width:100%;height:100%;font-size:inherit;text-transform:uppercase}.lc-avatar__status{position:absolute;bottom:0;right:0;width:25%;height:25%;border-radius:50%;border:2px solid var(--color-surface)}.lc-avatar__status--online{background-color:var(--color-success)}.lc-avatar__status--offline{background-color:var(--color-text-tertiary)}.lc-avatar__status--away{background-color:var(--color-warning-500)}.lc-avatar__status--busy{background-color:var(--color-error-500)}.lc-avatar--xs{width:1.5rem;height:1.5rem;font-size:var(--typography-font-size-xs, .75rem)}.lc-avatar--xs .lc-avatar__status{width:.375rem;height:.375rem;border-width:1px}.lc-avatar--sm{width:2rem;height:2rem;font-size:var(--typography-font-size-sm, .875rem)}.lc-avatar--sm .lc-avatar__status{width:.5rem;height:.5rem;border-width:1.5px}.lc-avatar--md{width:2.5rem;height:2.5rem;font-size:var(--typography-font-size-base, 1rem)}.lc-avatar--md .lc-avatar__status{width:.625rem;height:.625rem;border-width:2px}.lc-avatar--lg{width:3rem;height:3rem;font-size:var(--typography-font-size-lg, 1.125rem)}.lc-avatar--lg .lc-avatar__status{width:.75rem;height:.75rem;border-width:2px}.lc-avatar--xl{width:4rem;height:4rem;font-size:var(--typography-font-size-xl, 1.25rem)}.lc-avatar--xl .lc-avatar__status{width:1rem;height:1rem;border-width:2px}.lc-avatar--member{color:var(--color-on-member, #ffffff);font-weight:var(--typography-font-weight-bold, 700);letter-spacing:.02em}.lc-avatar--member.lc-avatar--xs{font-size:.625rem}.lc-avatar--member.lc-avatar--sm{font-size:.75rem}.lc-avatar--member.lc-avatar--md{font-size:.875rem}.lc-avatar--member.lc-avatar--lg{font-size:1rem}.lc-avatar--member.lc-avatar--xl{font-size:1.25rem}.lc-avatar--member-1{background-color:var(--color-member-1, #1a6a79)}.lc-avatar--member-2{background-color:var(--color-member-2, #b04a22)}.lc-avatar--member-3{background-color:var(--color-member-3, #4e3f84)}.lc-avatar--member-4{background-color:var(--color-member-4, #5f7150)}.lc-avatar--member-5{background-color:var(--color-member-5, #9e3846)}.lc-avatar--member-6{background-color:var(--color-member-6, #5a798c)}.lc-avatar--person{background-color:var(--color-neutral-300);color:var(--color-neutral-700)}.lc-avatar__person{display:block;width:60%;height:60%;fill:currentColor}@media(forced-colors:active){.lc-avatar--person{box-sizing:border-box;border:1px solid CanvasText}}.lc-avatar--ring{box-shadow:0 0 0 var(--lc-avatar-ring-width, 3px) var(--lc-avatar-ring-color, var(--color-background))}.lc-avatar--ring.lc-avatar--xs,.lc-avatar--ring.lc-avatar--sm{box-shadow:0 0 0 var(--lc-avatar-ring-width, 2px) var(--lc-avatar-ring-color, var(--color-background))}\n"],
144
154
  changeDetection: i0.ChangeDetectionStrategy.OnPush
145
155
  });
146
156
  };
@@ -156,8 +166,8 @@ i0.ɵɵngDeclareClassMetadata({
156
166
  standalone: true,
157
167
  imports: [],
158
168
  changeDetection: ChangeDetectionStrategy.OnPush,
159
- template: "<div \n [class]=\"avatarClasses()\"\n [attr.role]=\"decorative() ? null : 'img'\"\n [attr.aria-label]=\"decorative() ? null : ariaLabel()\"\n [attr.aria-hidden]=\"decorative() ? 'true' : null\"\n>\n @if (showImage()) {\n <!-- `loading` before `src`: attribute bindings run in template order, after\n every property binding, so `[attr.src]` keeps the source behind\n `loading` (G-40; an engine may start the request when src is set). -->\n <img\n [alt]=\"alt() || name() || 'Avatar'\"\n class=\"lc-avatar__image\"\n [attr.width]=\"imagePx()\"\n [attr.height]=\"imagePx()\"\n [attr.loading]=\"loading() === 'lazy' ? 'lazy' : null\"\n [attr.src]=\"src()!\"\n (error)=\"onImageError()\"\n />\n } @else {\n <span class=\"lc-avatar__initials\">{{ initials() }}</span>\n }\n\n @if (status()) {\n <span \n class=\"lc-avatar__status lc-avatar__status--{{ status() }}\"\n [attr.aria-label]=\"status() + ' status'\"\n ></span>\n }\n</div>\n",
160
- styles: [".lc-avatar{position:relative;display:inline-flex;align-items:center;justify-content:center;border-radius:50%;background-color:var(--color-surface-hover);color:var(--color-text-secondary);font-weight:var(--typography-font-weight-medium, 500);line-height:1;overflow:hidden;flex-shrink:0;-webkit-user-select:none;user-select:none}.lc-avatar__image{width:100%;height:100%;object-fit:cover;border-radius:50%}.lc-avatar__initials{display:flex;align-items:center;justify-content:center;width:100%;height:100%;font-size:inherit;text-transform:uppercase}.lc-avatar__status{position:absolute;bottom:0;right:0;width:25%;height:25%;border-radius:50%;border:2px solid var(--color-surface)}.lc-avatar__status--online{background-color:var(--color-success)}.lc-avatar__status--offline{background-color:var(--color-text-tertiary)}.lc-avatar__status--away{background-color:var(--color-warning-500)}.lc-avatar__status--busy{background-color:var(--color-error-500)}.lc-avatar--xs{width:1.5rem;height:1.5rem;font-size:var(--typography-font-size-xs, .75rem)}.lc-avatar--xs .lc-avatar__status{width:.375rem;height:.375rem;border-width:1px}.lc-avatar--sm{width:2rem;height:2rem;font-size:var(--typography-font-size-sm, .875rem)}.lc-avatar--sm .lc-avatar__status{width:.5rem;height:.5rem;border-width:1.5px}.lc-avatar--md{width:2.5rem;height:2.5rem;font-size:var(--typography-font-size-base, 1rem)}.lc-avatar--md .lc-avatar__status{width:.625rem;height:.625rem;border-width:2px}.lc-avatar--lg{width:3rem;height:3rem;font-size:var(--typography-font-size-lg, 1.125rem)}.lc-avatar--lg .lc-avatar__status{width:.75rem;height:.75rem;border-width:2px}.lc-avatar--xl{width:4rem;height:4rem;font-size:var(--typography-font-size-xl, 1.25rem)}.lc-avatar--xl .lc-avatar__status{width:1rem;height:1rem;border-width:2px}.lc-avatar--member{color:var(--color-on-member, #ffffff);font-weight:var(--typography-font-weight-bold, 700);letter-spacing:.02em}.lc-avatar--member.lc-avatar--xs{font-size:.625rem}.lc-avatar--member.lc-avatar--sm{font-size:.75rem}.lc-avatar--member.lc-avatar--md{font-size:.875rem}.lc-avatar--member.lc-avatar--lg{font-size:1rem}.lc-avatar--member.lc-avatar--xl{font-size:1.25rem}.lc-avatar--member-1{background-color:var(--color-member-1, #1a6a79)}.lc-avatar--member-2{background-color:var(--color-member-2, #b04a22)}.lc-avatar--member-3{background-color:var(--color-member-3, #4e3f84)}.lc-avatar--member-4{background-color:var(--color-member-4, #5f7150)}.lc-avatar--member-5{background-color:var(--color-member-5, #9e3846)}.lc-avatar--member-6{background-color:var(--color-member-6, #5a798c)}.lc-avatar--ring{box-shadow:0 0 0 var(--lc-avatar-ring-width, 3px) var(--lc-avatar-ring-color, var(--color-background))}.lc-avatar--ring.lc-avatar--xs,.lc-avatar--ring.lc-avatar--sm{box-shadow:0 0 0 var(--lc-avatar-ring-width, 2px) var(--lc-avatar-ring-color, var(--color-background))}\n"]
169
+ template: "<div \n [class]=\"avatarClasses()\"\n [attr.role]=\"decorative() ? null : 'img'\"\n [attr.aria-label]=\"decorative() ? null : ariaLabel()\"\n [attr.aria-hidden]=\"decorative() ? 'true' : null\"\n>\n @if (showImage()) {\n <!-- `loading` before `src`: attribute bindings run in template order, after\n every property binding, so `[attr.src]` keeps the source behind\n `loading` (G-40; an engine may start the request when src is set). -->\n <img\n [alt]=\"alt() || name() || 'Avatar'\"\n class=\"lc-avatar__image\"\n [attr.width]=\"imagePx()\"\n [attr.height]=\"imagePx()\"\n [attr.loading]=\"loading() === 'lazy' ? 'lazy' : null\"\n [attr.src]=\"src()!\"\n (error)=\"onImageError()\"\n />\n } @else if (showPerson()) {\n <!-- The kit's user glyph (ICON_USER_SOLID) inline: no icon registration, final in prerendered HTML. -->\n <svg class=\"lc-avatar__person\" viewBox=\"0 0 24 24\" aria-hidden=\"true\" focusable=\"false\">\n <path\n fill-rule=\"evenodd\"\n clip-rule=\"evenodd\"\n d=\"M7.5 6a4.5 4.5 0 119 0 4.5 4.5 0 01-9 0zM3.751 20.105a8.25 8.25 0 0116.498 0 .75.75 0 01-.437.695A18.683 18.683 0 0112 22.5c-2.786 0-5.433-.608-7.812-1.7a.75.75 0 01-.437-.695z\"\n />\n </svg>\n } @else {\n <span class=\"lc-avatar__initials\">{{ initials() }}</span>\n }\n\n @if (status()) {\n <span \n class=\"lc-avatar__status lc-avatar__status--{{ status() }}\"\n [attr.aria-label]=\"status() + ' status'\"\n ></span>\n }\n</div>\n",
170
+ styles: [".lc-avatar{position:relative;display:inline-flex;align-items:center;justify-content:center;border-radius:50%;background-color:var(--color-surface-hover);color:var(--color-text-secondary);font-weight:var(--typography-font-weight-medium, 500);line-height:1;overflow:hidden;flex-shrink:0;-webkit-user-select:none;user-select:none}.lc-avatar__image{width:100%;height:100%;object-fit:cover;border-radius:50%}.lc-avatar__initials{display:flex;align-items:center;justify-content:center;width:100%;height:100%;font-size:inherit;text-transform:uppercase}.lc-avatar__status{position:absolute;bottom:0;right:0;width:25%;height:25%;border-radius:50%;border:2px solid var(--color-surface)}.lc-avatar__status--online{background-color:var(--color-success)}.lc-avatar__status--offline{background-color:var(--color-text-tertiary)}.lc-avatar__status--away{background-color:var(--color-warning-500)}.lc-avatar__status--busy{background-color:var(--color-error-500)}.lc-avatar--xs{width:1.5rem;height:1.5rem;font-size:var(--typography-font-size-xs, .75rem)}.lc-avatar--xs .lc-avatar__status{width:.375rem;height:.375rem;border-width:1px}.lc-avatar--sm{width:2rem;height:2rem;font-size:var(--typography-font-size-sm, .875rem)}.lc-avatar--sm .lc-avatar__status{width:.5rem;height:.5rem;border-width:1.5px}.lc-avatar--md{width:2.5rem;height:2.5rem;font-size:var(--typography-font-size-base, 1rem)}.lc-avatar--md .lc-avatar__status{width:.625rem;height:.625rem;border-width:2px}.lc-avatar--lg{width:3rem;height:3rem;font-size:var(--typography-font-size-lg, 1.125rem)}.lc-avatar--lg .lc-avatar__status{width:.75rem;height:.75rem;border-width:2px}.lc-avatar--xl{width:4rem;height:4rem;font-size:var(--typography-font-size-xl, 1.25rem)}.lc-avatar--xl .lc-avatar__status{width:1rem;height:1rem;border-width:2px}.lc-avatar--member{color:var(--color-on-member, #ffffff);font-weight:var(--typography-font-weight-bold, 700);letter-spacing:.02em}.lc-avatar--member.lc-avatar--xs{font-size:.625rem}.lc-avatar--member.lc-avatar--sm{font-size:.75rem}.lc-avatar--member.lc-avatar--md{font-size:.875rem}.lc-avatar--member.lc-avatar--lg{font-size:1rem}.lc-avatar--member.lc-avatar--xl{font-size:1.25rem}.lc-avatar--member-1{background-color:var(--color-member-1, #1a6a79)}.lc-avatar--member-2{background-color:var(--color-member-2, #b04a22)}.lc-avatar--member-3{background-color:var(--color-member-3, #4e3f84)}.lc-avatar--member-4{background-color:var(--color-member-4, #5f7150)}.lc-avatar--member-5{background-color:var(--color-member-5, #9e3846)}.lc-avatar--member-6{background-color:var(--color-member-6, #5a798c)}.lc-avatar--person{background-color:var(--color-neutral-300);color:var(--color-neutral-700)}.lc-avatar__person{display:block;width:60%;height:60%;fill:currentColor}@media(forced-colors:active){.lc-avatar--person{box-sizing:border-box;border:1px solid CanvasText}}.lc-avatar--ring{box-shadow:0 0 0 var(--lc-avatar-ring-width, 3px) var(--lc-avatar-ring-color, var(--color-background))}.lc-avatar--ring.lc-avatar--xs,.lc-avatar--ring.lc-avatar--sm{box-shadow:0 0 0 var(--lc-avatar-ring-width, 2px) var(--lc-avatar-ring-color, var(--color-background))}\n"]
161
171
  }]
162
172
  }],
163
173
  propDecorators: {
@@ -232,6 +242,14 @@ i0.ɵɵngDeclareClassMetadata({
232
242
  alias: "decorative",
233
243
  required: false
234
244
  }]
245
+ }],
246
+ fallback: [{
247
+ type: i0.Input,
248
+ args: [{
249
+ isSignal: true,
250
+ alias: "fallback",
251
+ required: false
252
+ }]
235
253
  }]
236
254
  }
237
255
  });
@@ -1 +1 @@
1
- {"version":3,"file":"lifecorner-angular-ui-kit-avatar.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/avatar/avatar.component.ts","../../../../libs/angular-ui-kit/src/lib/avatar/avatar.component.html"],"sourcesContent":["import { Component, ChangeDetectionStrategy, input, computed, linkedSignal } from '@angular/core';\n\nexport type AvatarSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';\nexport type AvatarStatus = 'online' | 'offline' | 'away' | 'busy';\n\n/**\n * How the browser loads the image (3.11.2, G-40). The same union as\n * `CharacterLoading`, kept separate because entries don't import each other.\n * - `eager`: no attribute, the browser default.\n * - `lazy`: `loading=\"lazy\"`, for an avatar below the fold.\n */\nexport type AvatarLoading = 'eager' | 'lazy';\n\n/** The box of each size in px at 100 % text, written as the `<img>` `width`/`height` (G-40). */\nconst AVATAR_PX: Readonly<Record<AvatarSize, number>> = { xs: 24, sm: 32, md: 40, lg: 48, xl: 64 };\n\n/**\n * Background of the initials. `default` is the neutral kit avatar; `member-1`\n * … `member-6` are the member colours (`--color-member-1` … `-6`: teal, rust,\n * violet, sage, berry, slate) with white initials, each at least 4.5:1.\n * Assign them per person in a stable order (e.g. join order) and always show\n * the name or initials beside the colour — colour is never the only cue.\n */\nexport type AvatarColor =\n | 'default'\n | 'member-1'\n | 'member-2'\n | 'member-3'\n | 'member-4'\n | 'member-5'\n | 'member-6';\n\n/** The six member colours in their order (`AvatarColor` without `default`). */\nexport const AVATAR_MEMBER_COLORS: readonly AvatarColor[] = [\n 'member-1',\n 'member-2',\n 'member-3',\n 'member-4',\n 'member-5',\n 'member-6',\n];\n\n/**\n * The initials `lc-avatar` shows for `name`: first and last word's first\n * letter, upper case; one word: its first letter; empty: `?`. Kit-internal\n * (used by `lc-header`, so the account button's name holds exactly the text\n * the avatar shows, AS-33); no semver guarantee.\n */\nexport function ɵavatarInitials(name: string | undefined): string {\n if (!name || name.trim() === '') {\n return '?';\n }\n\n const parts = name\n .trim()\n .split(/\\s+/)\n .filter((p) => p.length > 0);\n\n if (parts.length === 0) {\n return '?';\n }\n\n if (parts.length === 1) {\n return parts[0]?.[0]?.toUpperCase() ?? '?';\n }\n\n // First and last name initials\n const firstInitial = parts[0]?.[0];\n const lastInitial = parts[parts.length - 1]?.[0];\n\n if (firstInitial && lastInitial) {\n return (firstInitial + lastInitial).toUpperCase();\n }\n return '?';\n}\n\n/**\n * Avatar component for circular user representation.\n *\n * Features:\n * - Image display with fallback to initials\n * - Auto-generated initials from user name\n * - Status indicator (online, offline, busy, away)\n * - Multiple size variants (xs, sm, md, lg, xl)\n * - Member colours (`color`: `member-1` … `member-6`) with white initials\n * - `ring`: a gap in the page colour around the avatar, for stacks and for\n * avatars on images or tints\n * - Accessible with alt text support\n *\n * @example\n * ```html\n * <lc-avatar src=\"https://example.com/avatar.jpg\" alt=\"John Doe\" />\n * <lc-avatar name=\"John Doe\" status=\"online\" size=\"lg\" />\n * <lc-avatar name=\"Tom Ek\" color=\"member-2\" [ring]=\"true\" />\n * ```\n */\n@Component({\n selector: 'lc-avatar',\n standalone: true,\n imports: [],\n templateUrl: './avatar.component.html',\n styleUrl: './avatar.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class AvatarComponent {\n /** Image source URL */\n src = input<string>();\n\n /** Alt text for the image */\n alt = input<string>();\n\n /** Full name for generating initials */\n name = input<string>();\n\n /**\n * Size of the avatar: xs 24, sm 32, md 40, lg 48, xl 64 px (1.5 … 4 rem).\n * The `<img>` carries the size's px as `width`/`height`; CSS sizes the box,\n * so at 200 % text the box grows and the attributes only give the 1:1 ratio.\n * @default 'md'\n */\n size = input<AvatarSize>('md');\n\n /**\n * How the browser loads the image. `lazy`: for an avatar below the fold;\n * writes `loading=\"lazy\"`. `eager` writes no attribute. The initials still\n * replace an image that fails.\n * @default 'eager'\n */\n loading = input<AvatarLoading>('eager');\n\n /** Status indicator */\n status = input<AvatarStatus>();\n\n /**\n * Background of the initials: `default` (neutral) or a member colour\n * `member-1` … `member-6` with white initials (LK-7).\n */\n color = input<AvatarColor>('default');\n\n /**\n * Draw a ring in the page colour around the avatar (3 px; 2 px for `xs` and\n * `sm`), so stacked or overlapping avatars stay apart. The ring colour is\n * `--lc-avatar-ring-color` (default `--color-background`); set it to the\n * surface the avatar sits on.\n */\n ring = input<boolean>(false);\n\n /**\n * Hide the avatar from assistive technology (no `img` role, no label), for\n * avatars inside a control that has its own accessible name — e.g. a menu\n * button labelled \"Menu for Tom Ek\".\n */\n decorative = input<boolean>(false);\n\n /**\n * Computed initials from name\n */\n initials = computed(() => ɵavatarInitials(this.name()));\n\n /**\n * Computed CSS classes for the avatar\n */\n avatarClasses = computed(() => {\n const classes = ['lc-avatar'];\n\n classes.push(`lc-avatar--${this.size()}`);\n\n if (this.status()) {\n classes.push('lc-avatar--with-status');\n }\n\n if (this.color() !== 'default') {\n classes.push('lc-avatar--member', `lc-avatar--${this.color()}`);\n }\n\n if (this.ring()) {\n classes.push('lc-avatar--ring');\n }\n\n return classes.join(' ');\n });\n\n /**\n * Get aria-label for accessibility\n */\n ariaLabel = computed(() => {\n return this.alt() || this.name() || 'Avatar';\n });\n\n /** The size's px, written as the `<img>` `width` and `height` (G-40). */\n protected readonly imagePx = computed(() => AVATAR_PX[this.size()]);\n\n /**\n * Check if image should be displayed\n */\n showImage = computed(() => {\n return this.src() && !this.imageError();\n });\n\n /**\n * Track if image failed to load. Resets whenever `src` changes so a new\n * image gets a fresh attempt instead of staying stuck on the initials.\n */\n protected imageError = linkedSignal<string | undefined, boolean>({\n source: this.src,\n computation: () => false,\n });\n\n /**\n * Handle image load error\n */\n onImageError(): void {\n this.imageError.set(true);\n }\n}\n","<div \n [class]=\"avatarClasses()\"\n [attr.role]=\"decorative() ? null : 'img'\"\n [attr.aria-label]=\"decorative() ? null : ariaLabel()\"\n [attr.aria-hidden]=\"decorative() ? 'true' : null\"\n>\n @if (showImage()) {\n <!-- `loading` before `src`: attribute bindings run in template order, after\n every property binding, so `[attr.src]` keeps the source behind\n `loading` (G-40; an engine may start the request when src is set). -->\n <img\n [alt]=\"alt() || name() || 'Avatar'\"\n class=\"lc-avatar__image\"\n [attr.width]=\"imagePx()\"\n [attr.height]=\"imagePx()\"\n [attr.loading]=\"loading() === 'lazy' ? 'lazy' : null\"\n [attr.src]=\"src()!\"\n (error)=\"onImageError()\"\n />\n } @else {\n <span class=\"lc-avatar__initials\">{{ initials() }}</span>\n }\n\n @if (status()) {\n <span \n class=\"lc-avatar__status lc-avatar__status--{{ status() }}\"\n [attr.aria-label]=\"status() + ' status'\"\n ></span>\n }\n</div>\n"],"mappings":"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"}
1
+ {"version":3,"file":"lifecorner-angular-ui-kit-avatar.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/avatar/avatar.component.ts","../../../../libs/angular-ui-kit/src/lib/avatar/avatar.component.html"],"sourcesContent":["import { Component, ChangeDetectionStrategy, input, computed, linkedSignal } from '@angular/core';\n\nexport type AvatarSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';\nexport type AvatarStatus = 'online' | 'offline' | 'away' | 'busy';\n\n/**\n * How the browser loads the image (3.11.2, G-40). The same union as\n * `CharacterLoading`, kept separate because entries don't import each other.\n * - `eager`: no attribute, the browser default.\n * - `lazy`: `loading=\"lazy\"`, for an avatar below the fold.\n */\nexport type AvatarLoading = 'eager' | 'lazy';\n\n/** The box of each size in px at 100 % text, written as the `<img>` `width`/`height` (G-40). */\nconst AVATAR_PX: Readonly<Record<AvatarSize, number>> = { xs: 24, sm: 32, md: 40, lg: 48, xl: 64 };\n\n/**\n * Background of the initials. `default` is the neutral kit avatar; `member-1`\n * … `member-6` are the member colours (`--color-member-1` … `-6`: teal, rust,\n * violet, sage, berry, slate) with white initials, each at least 4.5:1.\n * Assign them per person in a stable order (e.g. join order) and always show\n * the name or initials beside the colour — colour is never the only cue.\n */\nexport type AvatarColor =\n | 'default'\n | 'member-1'\n | 'member-2'\n | 'member-3'\n | 'member-4'\n | 'member-5'\n | 'member-6';\n\n/**\n * What `lc-avatar` shows with no image and no name (3.15).\n * - `initials`: \"?\"\n * - `person`: a person symbol on a neutral disc (`--color-neutral-300`, glyph\n * `--color-neutral-700`; `color` is ignored while it shows)\n */\nexport type AvatarFallback = 'initials' | 'person';\n\n/** The six member colours in their order (`AvatarColor` without `default`). */\nexport const AVATAR_MEMBER_COLORS: readonly AvatarColor[] = [\n 'member-1',\n 'member-2',\n 'member-3',\n 'member-4',\n 'member-5',\n 'member-6',\n];\n\n/**\n * The initials `lc-avatar` shows for `name`: first and last word's first\n * letter, upper case; one word: its first letter; empty: `?`. Kit-internal\n * (used by `lc-header`, so the account button's name holds exactly the text\n * the avatar shows, AS-33); no semver guarantee.\n */\nexport function ɵavatarInitials(name: string | undefined): string {\n if (!name || name.trim() === '') {\n return '?';\n }\n\n const parts = name\n .trim()\n .split(/\\s+/)\n .filter((p) => p.length > 0);\n\n if (parts.length === 0) {\n return '?';\n }\n\n if (parts.length === 1) {\n return parts[0]?.[0]?.toUpperCase() ?? '?';\n }\n\n // First and last name initials\n const firstInitial = parts[0]?.[0];\n const lastInitial = parts[parts.length - 1]?.[0];\n\n if (firstInitial && lastInitial) {\n return (firstInitial + lastInitial).toUpperCase();\n }\n return '?';\n}\n\n/**\n * Avatar component for circular user representation.\n *\n * Features:\n * - Image display with fallback to initials\n * - Auto-generated initials from user name\n * - Status indicator (online, offline, busy, away)\n * - Multiple size variants (xs, sm, md, lg, xl)\n * - Member colours (`color`: `member-1` … `member-6`) with white initials\n * - `ring`: a gap in the page colour around the avatar, for stacks and for\n * avatars on images or tints\n * - Accessible with alt text support\n * - `fallback=\"person\"`: a person symbol on a neutral disc instead of \"?\"\n * when there is no name (3.15)\n *\n * @example\n * ```html\n * <lc-avatar src=\"https://example.com/avatar.jpg\" alt=\"John Doe\" />\n * <lc-avatar name=\"John Doe\" status=\"online\" size=\"lg\" />\n * <lc-avatar name=\"Tom Ek\" color=\"member-2\" [ring]=\"true\" />\n * ```\n */\n@Component({\n selector: 'lc-avatar',\n standalone: true,\n imports: [],\n templateUrl: './avatar.component.html',\n styleUrl: './avatar.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class AvatarComponent {\n /** Image source URL */\n src = input<string>();\n\n /** Alt text for the image */\n alt = input<string>();\n\n /** Full name for generating initials */\n name = input<string>();\n\n /**\n * Size of the avatar: xs 24, sm 32, md 40, lg 48, xl 64 px (1.5 … 4 rem).\n * The `<img>` carries the size's px as `width`/`height`; CSS sizes the box,\n * so at 200 % text the box grows and the attributes only give the 1:1 ratio.\n * @default 'md'\n */\n size = input<AvatarSize>('md');\n\n /**\n * How the browser loads the image. `lazy`: for an avatar below the fold;\n * writes `loading=\"lazy\"`. `eager` writes no attribute. The initials still\n * replace an image that fails.\n * @default 'eager'\n */\n loading = input<AvatarLoading>('eager');\n\n /** Status indicator */\n status = input<AvatarStatus>();\n\n /**\n * Background of the initials: `default` (neutral) or a member colour\n * `member-1` … `member-6` with white initials (LK-7).\n */\n color = input<AvatarColor>('default');\n\n /**\n * Draw a ring in the page colour around the avatar (3 px; 2 px for `xs` and\n * `sm`), so stacked or overlapping avatars stay apart. The ring colour is\n * `--lc-avatar-ring-color` (default `--color-background`); set it to the\n * surface the avatar sits on.\n */\n ring = input<boolean>(false);\n\n /**\n * Hide the avatar from assistive technology (no `img` role, no label), for\n * avatars inside a control that has its own accessible name — e.g. a menu\n * button labelled \"Menu for Tom Ek\".\n */\n decorative = input<boolean>(false);\n\n /**\n * What shows with no image and no name: `'initials'` shows \"?\"; `'person'`\n * shows a person symbol on a neutral disc (`color` is ignored while it\n * shows). With a `name`, both show the initials. The accessible name stays\n * `alt`, then `name`, then \"Avatar\".\n * @default 'initials'\n */\n fallback = input<AvatarFallback>('initials');\n\n /**\n * Computed initials from name\n */\n initials = computed(() => ɵavatarInitials(this.name()));\n\n /** The person symbol: `fallback=\"person\"`, no image (or a broken one) and no name. */\n protected readonly showPerson = computed(\n () => this.fallback() === 'person' && !this.showImage() && !this.name()?.trim(),\n );\n\n /**\n * Computed CSS classes for the avatar\n */\n avatarClasses = computed(() => {\n const classes = ['lc-avatar'];\n\n classes.push(`lc-avatar--${this.size()}`);\n\n if (this.status()) {\n classes.push('lc-avatar--with-status');\n }\n\n if (this.showPerson()) {\n classes.push('lc-avatar--person');\n } else if (this.color() !== 'default') {\n classes.push('lc-avatar--member', `lc-avatar--${this.color()}`);\n }\n\n if (this.ring()) {\n classes.push('lc-avatar--ring');\n }\n\n return classes.join(' ');\n });\n\n /**\n * Get aria-label for accessibility\n */\n ariaLabel = computed(() => {\n return this.alt() || this.name() || 'Avatar';\n });\n\n /** The size's px, written as the `<img>` `width` and `height` (G-40). */\n protected readonly imagePx = computed(() => AVATAR_PX[this.size()]);\n\n /**\n * Check if image should be displayed\n */\n showImage = computed(() => {\n return this.src() && !this.imageError();\n });\n\n /**\n * Track if image failed to load. Resets whenever `src` changes so a new\n * image gets a fresh attempt instead of staying stuck on the initials.\n */\n protected imageError = linkedSignal<string | undefined, boolean>({\n source: this.src,\n computation: () => false,\n });\n\n /**\n * Handle image load error\n */\n onImageError(): void {\n this.imageError.set(true);\n }\n}\n","<div \n [class]=\"avatarClasses()\"\n [attr.role]=\"decorative() ? null : 'img'\"\n [attr.aria-label]=\"decorative() ? null : ariaLabel()\"\n [attr.aria-hidden]=\"decorative() ? 'true' : null\"\n>\n @if (showImage()) {\n <!-- `loading` before `src`: attribute bindings run in template order, after\n every property binding, so `[attr.src]` keeps the source behind\n `loading` (G-40; an engine may start the request when src is set). -->\n <img\n [alt]=\"alt() || name() || 'Avatar'\"\n class=\"lc-avatar__image\"\n [attr.width]=\"imagePx()\"\n [attr.height]=\"imagePx()\"\n [attr.loading]=\"loading() === 'lazy' ? 'lazy' : null\"\n [attr.src]=\"src()!\"\n (error)=\"onImageError()\"\n />\n } @else if (showPerson()) {\n <!-- The kit's user glyph (ICON_USER_SOLID) inline: no icon registration, final in prerendered HTML. -->\n <svg class=\"lc-avatar__person\" viewBox=\"0 0 24 24\" aria-hidden=\"true\" focusable=\"false\">\n <path\n fill-rule=\"evenodd\"\n clip-rule=\"evenodd\"\n d=\"M7.5 6a4.5 4.5 0 119 0 4.5 4.5 0 01-9 0zM3.751 20.105a8.25 8.25 0 0116.498 0 .75.75 0 01-.437.695A18.683 18.683 0 0112 22.5c-2.786 0-5.433-.608-7.812-1.7a.75.75 0 01-.437-.695z\"\n />\n </svg>\n } @else {\n <span class=\"lc-avatar__initials\">{{ initials() }}</span>\n }\n\n @if (status()) {\n <span \n class=\"lc-avatar__status lc-avatar__status--{{ status() }}\"\n [attr.aria-label]=\"status() + ' status'\"\n ></span>\n }\n</div>\n"],"mappings":"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"}