@lifecorner/angular-ui-kit 3.35.0 → 3.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/fesm2022/lifecorner-angular-ui-kit-button.mjs +6 -6
- package/fesm2022/lifecorner-angular-ui-kit-button.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-card.mjs +82 -16
- package/fesm2022/lifecorner-angular-ui-kit-card.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-combobox.mjs +18 -2
- package/fesm2022/lifecorner-angular-ui-kit-combobox.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-description-list.mjs +43 -5
- package/fesm2022/lifecorner-angular-ui-kit-description-list.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-header.mjs +45 -8
- package/fesm2022/lifecorner-angular-ui-kit-header.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-hero.mjs +57 -5
- package/fesm2022/lifecorner-angular-ui-kit-hero.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-list.mjs +24 -5
- package/fesm2022/lifecorner-angular-ui-kit-list.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-stacked-bar-chart.mjs +47 -5
- package/fesm2022/lifecorner-angular-ui-kit-stacked-bar-chart.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit-state-strip.mjs +203 -0
- package/fesm2022/lifecorner-angular-ui-kit-state-strip.mjs.map +1 -0
- package/fesm2022/lifecorner-angular-ui-kit-table.mjs +25 -4
- package/fesm2022/lifecorner-angular-ui-kit-table.mjs.map +1 -1
- package/fesm2022/lifecorner-angular-ui-kit.mjs +1 -0
- package/package.json +5 -1
- package/src/styles/theme-contrast.spec.ts +72 -0
- package/types/lifecorner-angular-ui-kit-button.d.ts +7 -3
- package/types/lifecorner-angular-ui-kit-card.d.ts +51 -3
- package/types/lifecorner-angular-ui-kit-combobox.d.ts +15 -0
- package/types/lifecorner-angular-ui-kit-description-list.d.ts +40 -0
- package/types/lifecorner-angular-ui-kit-header.d.ts +44 -2
- package/types/lifecorner-angular-ui-kit-hero.d.ts +32 -0
- package/types/lifecorner-angular-ui-kit-list.d.ts +17 -0
- package/types/lifecorner-angular-ui-kit-stacked-bar-chart.d.ts +36 -0
- package/types/lifecorner-angular-ui-kit-state-strip.d.ts +107 -0
- package/types/lifecorner-angular-ui-kit-table.d.ts +37 -0
- package/types/lifecorner-angular-ui-kit.d.ts +1 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as i0 from "@angular/core";
|
|
2
2
|
import { ChangeDetectionStrategy, Component, Directive, Injector, TemplateRef, afterNextRender, computed, contentChild, effect, inject, input, isDevMode, output, viewChild } from "@angular/core";
|
|
3
3
|
import { NgTemplateOutlet } from "@angular/common";
|
|
4
|
-
import { IconComponent } from "@lifecorner/angular-ui-kit/icon";
|
|
4
|
+
import { IconComponent, ɵprovideKitIcons } from "@lifecorner/angular-ui-kit/icon";
|
|
5
|
+
import { ICON_CHEVRON_RIGHT } from "@lifecorner/angular-ui-kit/icons";
|
|
5
6
|
import { ButtonComponent } from "@lifecorner/angular-ui-kit/button";
|
|
6
7
|
var ListItemTemplateDirective = class ListItemTemplateDirective {
|
|
7
8
|
template = inject(TemplateRef);
|
|
@@ -46,6 +47,7 @@ var ListComponent = class ListComponent {
|
|
|
46
47
|
clampDescription = input(true, ...ngDevMode ? [{ debugName: "clampDescription" }] : /* istanbul ignore next */ []);
|
|
47
48
|
actionVariant = input("ghost", ...ngDevMode ? [{ debugName: "actionVariant" }] : /* istanbul ignore next */ []);
|
|
48
49
|
actionSize = input("xs", ...ngDevMode ? [{ debugName: "actionSize" }] : /* istanbul ignore next */ []);
|
|
50
|
+
showChevron = input(false, ...ngDevMode ? [{ debugName: "showChevron" }] : /* istanbul ignore next */ []);
|
|
49
51
|
itemClick = output();
|
|
50
52
|
actionClick = output();
|
|
51
53
|
linkClick = output();
|
|
@@ -186,6 +188,13 @@ var ListComponent = class ListComponent {
|
|
|
186
188
|
isSignal: true,
|
|
187
189
|
isRequired: false,
|
|
188
190
|
transformFunction: null
|
|
191
|
+
},
|
|
192
|
+
showChevron: {
|
|
193
|
+
classPropertyName: "showChevron",
|
|
194
|
+
publicName: "showChevron",
|
|
195
|
+
isSignal: true,
|
|
196
|
+
isRequired: false,
|
|
197
|
+
transformFunction: null
|
|
189
198
|
}
|
|
190
199
|
},
|
|
191
200
|
outputs: {
|
|
@@ -208,8 +217,8 @@ var ListComponent = class ListComponent {
|
|
|
208
217
|
isSignal: true
|
|
209
218
|
}],
|
|
210
219
|
ngImport: i0,
|
|
211
|
-
template: "<!--\n Row content is shared between the plain (legacy) row and the keyboard-\n operable row rendered when `clickable` is set.\n-->\n<ng-template #rowContent let-item>\n @if (itemTemplate(); as tpl) {\n <ng-container *ngTemplateOutlet=\"tpl.template; context: { $implicit: item }\"></ng-container>\n } @else {\n <!-- Leading: Avatar or Icon -->\n @if (item.avatar) {\n <div [class]=\"avatarClasses(item)\">\n @if (item.avatar.startsWith('http') || item.avatar.startsWith('/') || item.avatar.startsWith('data:')) {\n <img [src]=\"item.avatar\" [alt]=\"item.label\" class=\"lc-list__avatar-img\" />\n } @else {\n <span class=\"lc-list__avatar-initials\">{{ item.avatar }}</span>\n }\n </div>\n } @else if (item.icon) {\n <lc-icon [name]=\"item.icon\" class=\"lc-list__icon\" size=\"sm\" [decorative]=\"true\" />\n }\n\n <!-- Content: Label, Subtitle, Description -->\n <div class=\"lc-list__content\">\n <span class=\"lc-list__label\">{{ item.label }}</span>\n @if (item.subtitle) {\n <span class=\"lc-list__subtitle\">{{ item.subtitle }}</span>\n }\n @if (item.description) {\n <span class=\"lc-list__description\">{{ item.description }}</span>\n }\n </div>\n\n <!-- Trailing: Badge, Metadata, Action -->\n <div class=\"lc-list__trailing\">\n @if (item.badge) {\n <span class=\"lc-list__badge lc-list__badge--{{ item.badgeVariant || 'default' }}\">{{ item.badge }}</span>\n }\n @if (item.metadata) {\n <span class=\"lc-list__metadata\">{{ item.metadata }}</span>\n }\n @if (item.action && !isLinkRow(item)) {\n <!-- The action is its own control: its click must not also activate the row. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -- only stops the action click from also activating the row; the action is a real button -->\n <span class=\"lc-list__action-area\" (click)=\"$event.stopPropagation()\">\n <lc-button\n [variant]=\"actionVariant()\"\n [size]=\"actionSize()\"\n [ariaLabel]=\"item.actionAriaLabel ?? ''\"\n class=\"lc-list__action\"\n (clicked)=\"onActionClick(item)\"\n >\n {{ item.action }}\n </lc-button>\n </span>\n }\n </div>\n }\n</ng-template>\n\n<div #listEl [class]=\"listClasses()\" role=\"list\">\n @for (item of items(); track item.id || $index) {\n @if (isLinkRow(item)) {\n <!-- A real link over the whole row (3.16, TD-4); the row element carries the item styles. -->\n <div class=\"lc-list__item-wrapper\" role=\"listitem\">\n <a\n [class]=\"getItemClasses(item) + ' lc-list__item--link'\"\n [href]=\"item.href\"\n [attr.aria-current]=\"item.selected ? 'true' : null\"\n (click)=\"onLinkClick($event, item)\"\n >\n <ng-container *ngTemplateOutlet=\"rowContent; context: { $implicit: item }\"></ng-container>\n </a>\n </div>\n } @else if (clickable()) {\n <div class=\"lc-list__item-wrapper\" role=\"listitem\">\n <div\n [class]=\"getItemClasses(item)\"\n role=\"button\"\n [attr.tabindex]=\"item.disabled ? -1 : 0\"\n [attr.aria-disabled]=\"item.disabled ? 'true' : null\"\n [attr.aria-current]=\"item.selected ? 'true' : null\"\n (click)=\"onItemClick(item)\"\n (keydown)=\"onItemKeydown($event, item)\"\n >\n <ng-container *ngTemplateOutlet=\"rowContent; context: { $implicit: item }\"></ng-container>\n </div>\n </div>\n } @else {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events -- non-clickable lists keep reporting mouse clicks (backward compatible); use clickable for keyboard rows -->\n <div\n [class]=\"getItemClasses(item)\"\n role=\"listitem\"\n [attr.aria-disabled]=\"item.disabled ? 'true' : null\"\n [attr.aria-current]=\"item.selected ? 'true' : null\"\n (click)=\"onItemClick(item)\"\n >\n <ng-container *ngTemplateOutlet=\"rowContent; context: { $implicit: item }\"></ng-container>\n </div>\n }\n }\n</div>\n",
|
|
212
|
-
styles: [".lc-list{display:flex;width:100%;background-color:var(--color-surface, #ffffff);border-radius:var(--border-radius-md);overflow:hidden}.lc-list--vertical{flex-direction:column}.lc-list--vertical .lc-list__item{border-bottom:1px solid transparent}.lc-list--vertical>.lc-list__item:last-child,.lc-list--vertical>.lc-list__item-wrapper:last-child .lc-list__item{border-bottom:none}.lc-list--vertical.lc-list--with-dividers .lc-list__item{border-bottom-color:var(--color-divider)}.lc-list--horizontal{flex-direction:row}.lc-list--horizontal .lc-list__item,.lc-list--horizontal .lc-list__item-wrapper{flex:1}.lc-list--horizontal .lc-list__item{border-right:1px solid transparent}.lc-list--horizontal>.lc-list__item:last-child,.lc-list--horizontal>.lc-list__item-wrapper:last-child .lc-list__item{border-right:none}.lc-list--horizontal.lc-list--with-dividers .lc-list__item{border-right-color:var(--color-divider)}.lc-list__item-wrapper{display:flex;min-width:0}.lc-list__item-wrapper>.lc-list__item{flex:1;min-width:0}.lc-list__item{display:flex;align-items:center;gap:var(--spacing-3);padding:var(--lc-density-padding-sm, var(--spacing-3)) var(--lc-density-padding-md, var(--spacing-4));transition:background-color .2s ease}.lc-list__item:focus-visible{outline:none;box-shadow:inset 0 0 0 2px var(--color-primary-500)}.lc-list__item--disabled{opacity:.5;cursor:not-allowed;pointer-events:none}.lc-list__icon{flex-shrink:0;color:var(--color-text-tertiary)}.lc-list__label{flex:1;min-width:0;overflow-wrap:anywhere;color:var(--color-text-primary, #111827);font-size:var(--font-size-base);line-height:var(--line-height-base)}.lc-list__avatar{flex-shrink:0;width:40px;height:40px;border-radius:50%;overflow:hidden;display:flex;align-items:center;justify-content:center;background-color:var(--color-primary-100, #dbeafe)}.lc-list__avatar-img{width:100%;height:100%;object-fit:cover}.lc-list__avatar-initials{font-size:var(--font-size-sm, .875rem);font-weight:600;color:var(--color-primary-700, #1d4ed8);text-transform:uppercase;line-height:1;-webkit-user-select:none;user-select:none}.lc-list__avatar--member-1{background-color:var(--color-member-1, #1a6a79)}.lc-list__avatar--member-2{background-color:var(--color-member-2, #b04a22)}.lc-list__avatar--member-3{background-color:var(--color-member-3, #4e3f84)}.lc-list__avatar--member-4{background-color:var(--color-member-4, #5f7150)}.lc-list__avatar--member-5{background-color:var(--color-member-5, #9e3846)}.lc-list__avatar--member-6{background-color:var(--color-member-6, #5a798c)}.lc-list__avatar--member .lc-list__avatar-initials{color:var(--color-on-member, #ffffff);font-weight:700;letter-spacing:.02em}.lc-list__content{flex:1;display:flex;flex-direction:column;gap:2px;min-width:0}.lc-list__subtitle{font-size:var(--font-size-sm, .875rem);color:var(--color-text-tertiary);line-height:var(--line-height-sm, 1.25rem);overflow-wrap:anywhere}.lc-list__description{overflow-wrap:anywhere;font-size:var(--font-size-xs, .75rem);color:var(--color-text-tertiary);line-height:var(--line-height-xs, 1rem);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.lc-list--no-clamp .lc-list__description{display:block;-webkit-line-clamp:unset;overflow:visible}.lc-list__trailing{flex-shrink:0;display:flex;flex-direction:column;align-items:flex-end;gap:4px}.lc-list__action{flex-shrink:0}.lc-list--clickable .lc-list__item{cursor:pointer}.lc-list--clickable .lc-list__item:hover:not(.lc-list__item--disabled){background-color:var(--color-surface-hover)}.lc-list--clickable .lc-list__item:active:not(.lc-list__item--disabled){background-color:var(--color-surface-sunken)}.lc-list__item--link{min-height:var(--size-min-touch-height, 2.75rem);color:inherit;text-decoration:none;cursor:pointer}.lc-list__item--link:hover{background-color:var(--color-surface-hover)}.lc-list__item--link:active{background-color:var(--color-surface-sunken)}.lc-list__item--link.lc-list__item--selected:hover{background-color:var(--color-primary-100, #dbeafe)}@media(forced-colors:active){.lc-list__item:focus-visible{outline:2px solid Highlight;outline-offset:-2px}}.lc-list__badge{display:inline-flex;align-items:center;padding:2px 8px;border-radius:9999px;font-size:var(--font-size-xs, .75rem);font-weight:500;line-height:1.25rem;white-space:nowrap}.lc-list__badge--default{background-color:var(--color-surface-hover);color:var(--color-text-secondary)}.lc-list__badge--primary{background-color:var(--color-primary-100, #dbeafe);color:var(--color-primary-700, #1d4ed8)}.lc-list__badge--success{background-color:var(--color-success-subtle);color:var(--color-on-success-subtle)}.lc-list__badge--warning{background-color:var(--color-warning-subtle);color:var(--color-on-warning-subtle)}.lc-list__badge--error{background-color:var(--color-error-100, #fee2e2);color:var(--color-on-error-subtle)}.lc-list__metadata{font-size:var(--font-size-xs, .75rem);color:var(--color-text-tertiary);white-space:nowrap}.lc-list__item--selected{background-color:var(--color-primary-50, #eff6ff)}.lc-list--clickable .lc-list__item--selected:hover:not(.lc-list__item--disabled){background-color:var(--color-primary-100, #dbeafe)}.lc-list--sm .lc-list__item{padding:var(--lc-density-padding-xs, var(--spacing-2)) var(--lc-density-padding-sm, var(--spacing-3));gap:var(--lc-density-gap-sm, var(--spacing-2))}.lc-list--sm .lc-list__avatar{width:32px;height:32px}.lc-list--sm .lc-list__avatar-initials{font-size:var(--font-size-xs, .75rem)}.lc-list--sm .lc-list__label{font-size:var(--font-size-sm, .875rem)}.lc-list--sm .lc-list__subtitle{font-size:var(--font-size-xs, .75rem)}.lc-list--sm .lc-list__description{display:none}.lc-list--lg .lc-list__item{padding:var(--lc-density-padding-md, var(--spacing-4)) var(--spacing-5);gap:var(--lc-density-gap-md, var(--spacing-4))}.lc-list--lg .lc-list__avatar{width:48px;height:48px}.lc-list--lg .lc-list__avatar-initials{font-size:var(--font-size-base, 1rem)}.lc-list--lg .lc-list__label{font-size:var(--font-size-lg, 1.125rem)}.lc-list--lg .lc-list__content{gap:4px}.lc-list--card{background:transparent;gap:.5rem;overflow:visible}.lc-list--card .lc-list__item{padding:.75rem 1rem;border-radius:var(--border-radius-md, .5rem);background:var(--color-surface-2);border:1px solid var(--color-border);transition:background-color .15s ease,border-color .15s ease,box-shadow .15s ease}.lc-list--card.lc-list--clickable .lc-list__item:hover:not(.lc-list__item--disabled),.lc-list--card .lc-list__item--link:hover:not(.lc-list__item--disabled){background:var(--color-surface-hover);border-color:var(--color-border-strong);box-shadow:var(--elevation-1)}.lc-list--card.lc-list--clickable .lc-list__item:active:not(.lc-list__item--disabled),.lc-list--card .lc-list__item--link:active:not(.lc-list__item--disabled){background:var(--color-surface-sunken)}.lc-list--card.lc-list--with-dividers .lc-list__item{border-bottom-color:transparent}\n"],
|
|
220
|
+
template: "<!--\n Row content is shared between the plain (legacy) row and the keyboard-\n operable row rendered when `clickable` is set.\n-->\n<ng-template #rowContent let-item>\n @if (itemTemplate(); as tpl) {\n <ng-container *ngTemplateOutlet=\"tpl.template; context: { $implicit: item }\"></ng-container>\n } @else {\n <!-- Leading: Avatar or Icon -->\n @if (item.avatar) {\n <div [class]=\"avatarClasses(item)\">\n @if (item.avatar.startsWith('http') || item.avatar.startsWith('/') || item.avatar.startsWith('data:')) {\n <img [src]=\"item.avatar\" [alt]=\"item.label\" class=\"lc-list__avatar-img\" />\n } @else {\n <span class=\"lc-list__avatar-initials\">{{ item.avatar }}</span>\n }\n </div>\n } @else if (item.icon) {\n <lc-icon [name]=\"item.icon\" class=\"lc-list__icon\" size=\"sm\" [decorative]=\"true\" />\n }\n\n <!-- Content: Label, Subtitle, Description -->\n <div class=\"lc-list__content\">\n <span class=\"lc-list__label\">{{ item.label }}</span>\n @if (item.subtitle) {\n <span class=\"lc-list__subtitle\">{{ item.subtitle }}</span>\n }\n @if (item.description) {\n <span class=\"lc-list__description\">{{ item.description }}</span>\n }\n </div>\n\n <!-- Trailing: Badge, Metadata, Action -->\n <div class=\"lc-list__trailing\">\n @if (item.badge) {\n <span class=\"lc-list__badge lc-list__badge--{{ item.badgeVariant || 'default' }}\">{{ item.badge }}</span>\n }\n @if (item.metadata) {\n <span class=\"lc-list__metadata\">{{ item.metadata }}</span>\n }\n @if (item.action && !isLinkRow(item)) {\n <!-- The action is its own control: its click must not also activate the row. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -- only stops the action click from also activating the row; the action is a real button -->\n <span class=\"lc-list__action-area\" (click)=\"$event.stopPropagation()\">\n <lc-button\n [variant]=\"actionVariant()\"\n [size]=\"actionSize()\"\n [ariaLabel]=\"item.actionAriaLabel ?? ''\"\n class=\"lc-list__action\"\n (clicked)=\"onActionClick(item)\"\n >\n {{ item.action }}\n </lc-button>\n </span>\n }\n </div>\n\n <!-- 3.36: a decorative chevron ends a clickable or linked row. -->\n @if (showChevron() && (clickable() || !!item.href)) {\n <lc-icon name=\"chevron-right\" class=\"lc-list__chevron\" size=\"sm\" [decorative]=\"true\" />\n }\n }\n</ng-template>\n\n<div #listEl [class]=\"listClasses()\" role=\"list\">\n @for (item of items(); track item.id || $index) {\n @if (isLinkRow(item)) {\n <!-- A real link over the whole row (3.16, TD-4); the row element carries the item styles. -->\n <div class=\"lc-list__item-wrapper\" role=\"listitem\">\n <a\n [class]=\"getItemClasses(item) + ' lc-list__item--link'\"\n [href]=\"item.href\"\n [attr.aria-current]=\"item.selected ? 'true' : null\"\n (click)=\"onLinkClick($event, item)\"\n >\n <ng-container *ngTemplateOutlet=\"rowContent; context: { $implicit: item }\"></ng-container>\n </a>\n </div>\n } @else if (clickable()) {\n <div class=\"lc-list__item-wrapper\" role=\"listitem\">\n <div\n [class]=\"getItemClasses(item)\"\n role=\"button\"\n [attr.tabindex]=\"item.disabled ? -1 : 0\"\n [attr.aria-disabled]=\"item.disabled ? 'true' : null\"\n [attr.aria-current]=\"item.selected ? 'true' : null\"\n (click)=\"onItemClick(item)\"\n (keydown)=\"onItemKeydown($event, item)\"\n >\n <ng-container *ngTemplateOutlet=\"rowContent; context: { $implicit: item }\"></ng-container>\n </div>\n </div>\n } @else {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events -- non-clickable lists keep reporting mouse clicks (backward compatible); use clickable for keyboard rows -->\n <div\n [class]=\"getItemClasses(item)\"\n role=\"listitem\"\n [attr.aria-disabled]=\"item.disabled ? 'true' : null\"\n [attr.aria-current]=\"item.selected ? 'true' : null\"\n (click)=\"onItemClick(item)\"\n >\n <ng-container *ngTemplateOutlet=\"rowContent; context: { $implicit: item }\"></ng-container>\n </div>\n }\n }\n</div>\n",
|
|
221
|
+
styles: [".lc-list{display:flex;width:100%;background-color:var(--color-surface, #ffffff);border-radius:var(--border-radius-md);overflow:hidden}.lc-list--vertical{flex-direction:column}.lc-list--vertical .lc-list__item{border-bottom:1px solid transparent}.lc-list--vertical>.lc-list__item:last-child,.lc-list--vertical>.lc-list__item-wrapper:last-child .lc-list__item{border-bottom:none}.lc-list--vertical.lc-list--with-dividers .lc-list__item{border-bottom-color:var(--color-divider)}.lc-list--horizontal{flex-direction:row}.lc-list--horizontal .lc-list__item,.lc-list--horizontal .lc-list__item-wrapper{flex:1}.lc-list--horizontal .lc-list__item{border-right:1px solid transparent}.lc-list--horizontal>.lc-list__item:last-child,.lc-list--horizontal>.lc-list__item-wrapper:last-child .lc-list__item{border-right:none}.lc-list--horizontal.lc-list--with-dividers .lc-list__item{border-right-color:var(--color-divider)}.lc-list__item-wrapper{display:flex;min-width:0}.lc-list__item-wrapper>.lc-list__item{flex:1;min-width:0}.lc-list__item{display:flex;align-items:center;gap:var(--spacing-3);padding:var(--lc-density-padding-sm, var(--spacing-3)) var(--lc-density-padding-md, var(--spacing-4));transition:background-color .2s ease}.lc-list__item:focus-visible{outline:none;box-shadow:inset 0 0 0 2px var(--color-primary-500)}.lc-list__item--disabled{opacity:.5;cursor:not-allowed;pointer-events:none}.lc-list__icon{flex-shrink:0;color:var(--color-text-tertiary)}.lc-list__label{flex:1;min-width:0;overflow-wrap:anywhere;color:var(--color-text-primary, #111827);font-size:var(--font-size-base);line-height:var(--line-height-base)}.lc-list__avatar{flex-shrink:0;width:40px;height:40px;border-radius:50%;overflow:hidden;display:flex;align-items:center;justify-content:center;background-color:var(--color-primary-100, #dbeafe)}.lc-list__avatar-img{width:100%;height:100%;object-fit:cover}.lc-list__avatar-initials{font-size:var(--font-size-sm, .875rem);font-weight:600;color:var(--color-primary-700, #1d4ed8);text-transform:uppercase;line-height:1;-webkit-user-select:none;user-select:none}.lc-list__avatar--member-1{background-color:var(--color-member-1, #1a6a79)}.lc-list__avatar--member-2{background-color:var(--color-member-2, #b04a22)}.lc-list__avatar--member-3{background-color:var(--color-member-3, #4e3f84)}.lc-list__avatar--member-4{background-color:var(--color-member-4, #5f7150)}.lc-list__avatar--member-5{background-color:var(--color-member-5, #9e3846)}.lc-list__avatar--member-6{background-color:var(--color-member-6, #5a798c)}.lc-list__avatar--member .lc-list__avatar-initials{color:var(--color-on-member, #ffffff);font-weight:700;letter-spacing:.02em}.lc-list__content{flex:1;display:flex;flex-direction:column;gap:2px;min-width:0}.lc-list__subtitle{font-size:var(--font-size-sm, .875rem);color:var(--color-text-tertiary);line-height:var(--line-height-sm, 1.25rem);overflow-wrap:anywhere}.lc-list__description{overflow-wrap:anywhere;font-size:var(--font-size-xs, .75rem);color:var(--color-text-tertiary);line-height:var(--line-height-xs, 1rem);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.lc-list--no-clamp .lc-list__description{display:block;-webkit-line-clamp:unset;overflow:visible}.lc-list__trailing{flex-shrink:0;display:flex;flex-direction:column;align-items:flex-end;gap:4px}.lc-list__action{flex-shrink:0}.lc-list__chevron{flex-shrink:0;color:var(--color-text-tertiary)}.lc-list__chevron:dir(rtl){transform:scaleX(-1)}.lc-list--clickable .lc-list__item{cursor:pointer}.lc-list--clickable .lc-list__item:hover:not(.lc-list__item--disabled){background-color:var(--color-surface-hover)}.lc-list--clickable .lc-list__item:active:not(.lc-list__item--disabled){background-color:var(--color-surface-sunken)}.lc-list__item--link{min-height:var(--size-min-touch-height, 2.75rem);color:inherit;text-decoration:none;cursor:pointer}.lc-list__item--link:hover{background-color:var(--color-surface-hover)}.lc-list__item--link:active{background-color:var(--color-surface-sunken)}.lc-list__item--link.lc-list__item--selected:hover{background-color:var(--color-primary-100, #dbeafe)}@media(forced-colors:active){.lc-list__item:focus-visible{outline:2px solid Highlight;outline-offset:-2px}}.lc-list__badge{display:inline-flex;align-items:center;padding:2px 8px;border-radius:9999px;font-size:var(--font-size-xs, .75rem);font-weight:500;line-height:1.25rem;white-space:nowrap}.lc-list__badge--default{background-color:var(--color-surface-hover);color:var(--color-text-secondary)}.lc-list__badge--primary{background-color:var(--color-primary-100, #dbeafe);color:var(--color-primary-700, #1d4ed8)}.lc-list__badge--success{background-color:var(--color-success-subtle);color:var(--color-on-success-subtle)}.lc-list__badge--warning{background-color:var(--color-warning-subtle);color:var(--color-on-warning-subtle)}.lc-list__badge--error{background-color:var(--color-error-100, #fee2e2);color:var(--color-on-error-subtle)}.lc-list__metadata{font-size:var(--font-size-xs, .75rem);color:var(--color-text-tertiary);white-space:nowrap}.lc-list__item--selected{background-color:var(--color-primary-50, #eff6ff)}.lc-list--clickable .lc-list__item--selected:hover:not(.lc-list__item--disabled){background-color:var(--color-primary-100, #dbeafe)}.lc-list--sm .lc-list__item{padding:var(--lc-density-padding-xs, var(--spacing-2)) var(--lc-density-padding-sm, var(--spacing-3));gap:var(--lc-density-gap-sm, var(--spacing-2))}.lc-list--sm .lc-list__avatar{width:32px;height:32px}.lc-list--sm .lc-list__avatar-initials{font-size:var(--font-size-xs, .75rem)}.lc-list--sm .lc-list__label{font-size:var(--font-size-sm, .875rem)}.lc-list--sm .lc-list__subtitle{font-size:var(--font-size-xs, .75rem)}.lc-list--sm .lc-list__description{display:none}.lc-list--lg .lc-list__item{padding:var(--lc-density-padding-md, var(--spacing-4)) var(--spacing-5);gap:var(--lc-density-gap-md, var(--spacing-4))}.lc-list--lg .lc-list__avatar{width:48px;height:48px}.lc-list--lg .lc-list__avatar-initials{font-size:var(--font-size-base, 1rem)}.lc-list--lg .lc-list__label{font-size:var(--font-size-lg, 1.125rem)}.lc-list--lg .lc-list__content{gap:4px}.lc-list--card{background:transparent;gap:.5rem;overflow:visible}.lc-list--card .lc-list__item{padding:.75rem 1rem;border-radius:var(--border-radius-md, .5rem);background:var(--color-surface-2);border:1px solid var(--color-border);transition:background-color .15s ease,border-color .15s ease,box-shadow .15s ease}.lc-list--card.lc-list--clickable .lc-list__item:hover:not(.lc-list__item--disabled),.lc-list--card .lc-list__item--link:hover:not(.lc-list__item--disabled){background:var(--color-surface-hover);border-color:var(--color-border-strong);box-shadow:var(--elevation-1)}.lc-list--card.lc-list--clickable .lc-list__item:active:not(.lc-list__item--disabled),.lc-list--card .lc-list__item--link:active:not(.lc-list__item--disabled){background:var(--color-surface-sunken)}.lc-list--card.lc-list--with-dividers .lc-list__item{border-bottom-color:transparent}\n"],
|
|
213
222
|
dependencies: [
|
|
214
223
|
{
|
|
215
224
|
kind: "directive",
|
|
@@ -262,6 +271,7 @@ var ListComponent = class ListComponent {
|
|
|
262
271
|
]
|
|
263
272
|
}
|
|
264
273
|
],
|
|
274
|
+
viewProviders: [ɵprovideKitIcons([ICON_CHEVRON_RIGHT])],
|
|
265
275
|
changeDetection: i0.ChangeDetectionStrategy.OnPush
|
|
266
276
|
});
|
|
267
277
|
};
|
|
@@ -281,8 +291,9 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
281
291
|
ButtonComponent
|
|
282
292
|
],
|
|
283
293
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
284
|
-
|
|
285
|
-
|
|
294
|
+
viewProviders: [ɵprovideKitIcons([ICON_CHEVRON_RIGHT])],
|
|
295
|
+
template: "<!--\n Row content is shared between the plain (legacy) row and the keyboard-\n operable row rendered when `clickable` is set.\n-->\n<ng-template #rowContent let-item>\n @if (itemTemplate(); as tpl) {\n <ng-container *ngTemplateOutlet=\"tpl.template; context: { $implicit: item }\"></ng-container>\n } @else {\n <!-- Leading: Avatar or Icon -->\n @if (item.avatar) {\n <div [class]=\"avatarClasses(item)\">\n @if (item.avatar.startsWith('http') || item.avatar.startsWith('/') || item.avatar.startsWith('data:')) {\n <img [src]=\"item.avatar\" [alt]=\"item.label\" class=\"lc-list__avatar-img\" />\n } @else {\n <span class=\"lc-list__avatar-initials\">{{ item.avatar }}</span>\n }\n </div>\n } @else if (item.icon) {\n <lc-icon [name]=\"item.icon\" class=\"lc-list__icon\" size=\"sm\" [decorative]=\"true\" />\n }\n\n <!-- Content: Label, Subtitle, Description -->\n <div class=\"lc-list__content\">\n <span class=\"lc-list__label\">{{ item.label }}</span>\n @if (item.subtitle) {\n <span class=\"lc-list__subtitle\">{{ item.subtitle }}</span>\n }\n @if (item.description) {\n <span class=\"lc-list__description\">{{ item.description }}</span>\n }\n </div>\n\n <!-- Trailing: Badge, Metadata, Action -->\n <div class=\"lc-list__trailing\">\n @if (item.badge) {\n <span class=\"lc-list__badge lc-list__badge--{{ item.badgeVariant || 'default' }}\">{{ item.badge }}</span>\n }\n @if (item.metadata) {\n <span class=\"lc-list__metadata\">{{ item.metadata }}</span>\n }\n @if (item.action && !isLinkRow(item)) {\n <!-- The action is its own control: its click must not also activate the row. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -- only stops the action click from also activating the row; the action is a real button -->\n <span class=\"lc-list__action-area\" (click)=\"$event.stopPropagation()\">\n <lc-button\n [variant]=\"actionVariant()\"\n [size]=\"actionSize()\"\n [ariaLabel]=\"item.actionAriaLabel ?? ''\"\n class=\"lc-list__action\"\n (clicked)=\"onActionClick(item)\"\n >\n {{ item.action }}\n </lc-button>\n </span>\n }\n </div>\n\n <!-- 3.36: a decorative chevron ends a clickable or linked row. -->\n @if (showChevron() && (clickable() || !!item.href)) {\n <lc-icon name=\"chevron-right\" class=\"lc-list__chevron\" size=\"sm\" [decorative]=\"true\" />\n }\n }\n</ng-template>\n\n<div #listEl [class]=\"listClasses()\" role=\"list\">\n @for (item of items(); track item.id || $index) {\n @if (isLinkRow(item)) {\n <!-- A real link over the whole row (3.16, TD-4); the row element carries the item styles. -->\n <div class=\"lc-list__item-wrapper\" role=\"listitem\">\n <a\n [class]=\"getItemClasses(item) + ' lc-list__item--link'\"\n [href]=\"item.href\"\n [attr.aria-current]=\"item.selected ? 'true' : null\"\n (click)=\"onLinkClick($event, item)\"\n >\n <ng-container *ngTemplateOutlet=\"rowContent; context: { $implicit: item }\"></ng-container>\n </a>\n </div>\n } @else if (clickable()) {\n <div class=\"lc-list__item-wrapper\" role=\"listitem\">\n <div\n [class]=\"getItemClasses(item)\"\n role=\"button\"\n [attr.tabindex]=\"item.disabled ? -1 : 0\"\n [attr.aria-disabled]=\"item.disabled ? 'true' : null\"\n [attr.aria-current]=\"item.selected ? 'true' : null\"\n (click)=\"onItemClick(item)\"\n (keydown)=\"onItemKeydown($event, item)\"\n >\n <ng-container *ngTemplateOutlet=\"rowContent; context: { $implicit: item }\"></ng-container>\n </div>\n </div>\n } @else {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events -- non-clickable lists keep reporting mouse clicks (backward compatible); use clickable for keyboard rows -->\n <div\n [class]=\"getItemClasses(item)\"\n role=\"listitem\"\n [attr.aria-disabled]=\"item.disabled ? 'true' : null\"\n [attr.aria-current]=\"item.selected ? 'true' : null\"\n (click)=\"onItemClick(item)\"\n >\n <ng-container *ngTemplateOutlet=\"rowContent; context: { $implicit: item }\"></ng-container>\n </div>\n }\n }\n</div>\n",
|
|
296
|
+
styles: [".lc-list{display:flex;width:100%;background-color:var(--color-surface, #ffffff);border-radius:var(--border-radius-md);overflow:hidden}.lc-list--vertical{flex-direction:column}.lc-list--vertical .lc-list__item{border-bottom:1px solid transparent}.lc-list--vertical>.lc-list__item:last-child,.lc-list--vertical>.lc-list__item-wrapper:last-child .lc-list__item{border-bottom:none}.lc-list--vertical.lc-list--with-dividers .lc-list__item{border-bottom-color:var(--color-divider)}.lc-list--horizontal{flex-direction:row}.lc-list--horizontal .lc-list__item,.lc-list--horizontal .lc-list__item-wrapper{flex:1}.lc-list--horizontal .lc-list__item{border-right:1px solid transparent}.lc-list--horizontal>.lc-list__item:last-child,.lc-list--horizontal>.lc-list__item-wrapper:last-child .lc-list__item{border-right:none}.lc-list--horizontal.lc-list--with-dividers .lc-list__item{border-right-color:var(--color-divider)}.lc-list__item-wrapper{display:flex;min-width:0}.lc-list__item-wrapper>.lc-list__item{flex:1;min-width:0}.lc-list__item{display:flex;align-items:center;gap:var(--spacing-3);padding:var(--lc-density-padding-sm, var(--spacing-3)) var(--lc-density-padding-md, var(--spacing-4));transition:background-color .2s ease}.lc-list__item:focus-visible{outline:none;box-shadow:inset 0 0 0 2px var(--color-primary-500)}.lc-list__item--disabled{opacity:.5;cursor:not-allowed;pointer-events:none}.lc-list__icon{flex-shrink:0;color:var(--color-text-tertiary)}.lc-list__label{flex:1;min-width:0;overflow-wrap:anywhere;color:var(--color-text-primary, #111827);font-size:var(--font-size-base);line-height:var(--line-height-base)}.lc-list__avatar{flex-shrink:0;width:40px;height:40px;border-radius:50%;overflow:hidden;display:flex;align-items:center;justify-content:center;background-color:var(--color-primary-100, #dbeafe)}.lc-list__avatar-img{width:100%;height:100%;object-fit:cover}.lc-list__avatar-initials{font-size:var(--font-size-sm, .875rem);font-weight:600;color:var(--color-primary-700, #1d4ed8);text-transform:uppercase;line-height:1;-webkit-user-select:none;user-select:none}.lc-list__avatar--member-1{background-color:var(--color-member-1, #1a6a79)}.lc-list__avatar--member-2{background-color:var(--color-member-2, #b04a22)}.lc-list__avatar--member-3{background-color:var(--color-member-3, #4e3f84)}.lc-list__avatar--member-4{background-color:var(--color-member-4, #5f7150)}.lc-list__avatar--member-5{background-color:var(--color-member-5, #9e3846)}.lc-list__avatar--member-6{background-color:var(--color-member-6, #5a798c)}.lc-list__avatar--member .lc-list__avatar-initials{color:var(--color-on-member, #ffffff);font-weight:700;letter-spacing:.02em}.lc-list__content{flex:1;display:flex;flex-direction:column;gap:2px;min-width:0}.lc-list__subtitle{font-size:var(--font-size-sm, .875rem);color:var(--color-text-tertiary);line-height:var(--line-height-sm, 1.25rem);overflow-wrap:anywhere}.lc-list__description{overflow-wrap:anywhere;font-size:var(--font-size-xs, .75rem);color:var(--color-text-tertiary);line-height:var(--line-height-xs, 1rem);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.lc-list--no-clamp .lc-list__description{display:block;-webkit-line-clamp:unset;overflow:visible}.lc-list__trailing{flex-shrink:0;display:flex;flex-direction:column;align-items:flex-end;gap:4px}.lc-list__action{flex-shrink:0}.lc-list__chevron{flex-shrink:0;color:var(--color-text-tertiary)}.lc-list__chevron:dir(rtl){transform:scaleX(-1)}.lc-list--clickable .lc-list__item{cursor:pointer}.lc-list--clickable .lc-list__item:hover:not(.lc-list__item--disabled){background-color:var(--color-surface-hover)}.lc-list--clickable .lc-list__item:active:not(.lc-list__item--disabled){background-color:var(--color-surface-sunken)}.lc-list__item--link{min-height:var(--size-min-touch-height, 2.75rem);color:inherit;text-decoration:none;cursor:pointer}.lc-list__item--link:hover{background-color:var(--color-surface-hover)}.lc-list__item--link:active{background-color:var(--color-surface-sunken)}.lc-list__item--link.lc-list__item--selected:hover{background-color:var(--color-primary-100, #dbeafe)}@media(forced-colors:active){.lc-list__item:focus-visible{outline:2px solid Highlight;outline-offset:-2px}}.lc-list__badge{display:inline-flex;align-items:center;padding:2px 8px;border-radius:9999px;font-size:var(--font-size-xs, .75rem);font-weight:500;line-height:1.25rem;white-space:nowrap}.lc-list__badge--default{background-color:var(--color-surface-hover);color:var(--color-text-secondary)}.lc-list__badge--primary{background-color:var(--color-primary-100, #dbeafe);color:var(--color-primary-700, #1d4ed8)}.lc-list__badge--success{background-color:var(--color-success-subtle);color:var(--color-on-success-subtle)}.lc-list__badge--warning{background-color:var(--color-warning-subtle);color:var(--color-on-warning-subtle)}.lc-list__badge--error{background-color:var(--color-error-100, #fee2e2);color:var(--color-on-error-subtle)}.lc-list__metadata{font-size:var(--font-size-xs, .75rem);color:var(--color-text-tertiary);white-space:nowrap}.lc-list__item--selected{background-color:var(--color-primary-50, #eff6ff)}.lc-list--clickable .lc-list__item--selected:hover:not(.lc-list__item--disabled){background-color:var(--color-primary-100, #dbeafe)}.lc-list--sm .lc-list__item{padding:var(--lc-density-padding-xs, var(--spacing-2)) var(--lc-density-padding-sm, var(--spacing-3));gap:var(--lc-density-gap-sm, var(--spacing-2))}.lc-list--sm .lc-list__avatar{width:32px;height:32px}.lc-list--sm .lc-list__avatar-initials{font-size:var(--font-size-xs, .75rem)}.lc-list--sm .lc-list__label{font-size:var(--font-size-sm, .875rem)}.lc-list--sm .lc-list__subtitle{font-size:var(--font-size-xs, .75rem)}.lc-list--sm .lc-list__description{display:none}.lc-list--lg .lc-list__item{padding:var(--lc-density-padding-md, var(--spacing-4)) var(--spacing-5);gap:var(--lc-density-gap-md, var(--spacing-4))}.lc-list--lg .lc-list__avatar{width:48px;height:48px}.lc-list--lg .lc-list__avatar-initials{font-size:var(--font-size-base, 1rem)}.lc-list--lg .lc-list__label{font-size:var(--font-size-lg, 1.125rem)}.lc-list--lg .lc-list__content{gap:4px}.lc-list--card{background:transparent;gap:.5rem;overflow:visible}.lc-list--card .lc-list__item{padding:.75rem 1rem;border-radius:var(--border-radius-md, .5rem);background:var(--color-surface-2);border:1px solid var(--color-border);transition:background-color .15s ease,border-color .15s ease,box-shadow .15s ease}.lc-list--card.lc-list--clickable .lc-list__item:hover:not(.lc-list__item--disabled),.lc-list--card .lc-list__item--link:hover:not(.lc-list__item--disabled){background:var(--color-surface-hover);border-color:var(--color-border-strong);box-shadow:var(--elevation-1)}.lc-list--card.lc-list--clickable .lc-list__item:active:not(.lc-list__item--disabled),.lc-list--card .lc-list__item--link:active:not(.lc-list__item--disabled){background:var(--color-surface-sunken)}.lc-list--card.lc-list--with-dividers .lc-list__item{border-bottom-color:transparent}\n"]
|
|
286
297
|
}]
|
|
287
298
|
}],
|
|
288
299
|
ctorParameters: () => [],
|
|
@@ -363,6 +374,14 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
363
374
|
required: false
|
|
364
375
|
}]
|
|
365
376
|
}],
|
|
377
|
+
showChevron: [{
|
|
378
|
+
type: i0.Input,
|
|
379
|
+
args: [{
|
|
380
|
+
isSignal: true,
|
|
381
|
+
alias: "showChevron",
|
|
382
|
+
required: false
|
|
383
|
+
}]
|
|
384
|
+
}],
|
|
366
385
|
itemClick: [{
|
|
367
386
|
type: i0.Output,
|
|
368
387
|
args: ["itemClick"]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecorner-angular-ui-kit-list.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/list/list-item-template.directive.ts","../../../../libs/angular-ui-kit/src/lib/list/list.component.ts","../../../../libs/angular-ui-kit/src/lib/list/list.component.html"],"sourcesContent":["import { Directive, TemplateRef, inject } from '@angular/core';\nimport { ListItem } from './list.component';\n\n/**\n * Directive for providing a custom template to render each list item.\n *\n * @example\n * ```html\n * <lc-list [items]=\"runs()\" (itemClick)=\"onRunClick($event)\">\n * <ng-template lcListItem let-item>\n * <lc-badge [variant]=\"getStatusVariant(item.status)\">{{ item.status }}</lc-badge>\n * <span>{{ item.symbol }}</span>\n * <span class=\"text-sm text-neutral-500\">{{ item.date }}</span>\n * </ng-template>\n * </lc-list>\n * ```\n */\n@Directive({\n selector: '[lcListItem]',\n standalone: true,\n})\nexport class ListItemTemplateDirective {\n public readonly template = inject(TemplateRef<{ $implicit: ListItem }>);\n}\n","import {\n AfterRenderRef,\n Component,\n ChangeDetectionStrategy,\n ElementRef,\n Injector,\n input,\n computed,\n contentChild,\n effect,\n inject,\n isDevMode,\n output,\n viewChild,\n afterNextRender,\n} from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport { IconComponent } from '@lifecorner/angular-ui-kit/icon';\nimport { ButtonComponent, ButtonSize, ButtonVariant } from '@lifecorner/angular-ui-kit/button';\nimport { ListItemTemplateDirective } from './list-item-template.directive';\nimport type { AvatarColor } from '@lifecorner/angular-ui-kit/avatar';\n\n/**\n * List orientation type\n */\nexport type ListOrientation = 'vertical' | 'horizontal';\n\n/**\n * List visual variant\n * - default: flat rows (original)\n * - card: each item rendered as a mini-card with border, radius and shadow on hover\n */\nexport type ListVariant = 'default' | 'card';\n\n/**\n * List item interface\n */\nexport interface ListItem {\n /** Item label text (primary line) */\n label: string;\n /** Secondary text shown below the label */\n subtitle?: string;\n /** Description text shown below subtitle (third line) */\n description?: string;\n /** Optional icon identifier */\n icon?: string;\n /** Avatar URL or initials (renders a circular avatar instead of icon) */\n avatar?: string;\n /**\n * Background of avatar initials: `default` or a member colour `member-1` …\n * `member-6` with white initials (as `lc-avatar`'s `color`).\n */\n avatarColor?: AvatarColor;\n /** Badge text (e.g. count, status label) shown on the trailing side */\n badge?: string;\n /** Badge color variant */\n badgeVariant?: 'default' | 'primary' | 'success' | 'warning' | 'error';\n /** Metadata text shown on the trailing side (e.g. date, size) */\n metadata?: string;\n /** Optional action label */\n action?: string;\n /**\n * Accessible name of this row's action when the visible label repeats on\n * every row (e.g. „Entfernen“ → „iPhone · Safari entfernen“). The visible\n * label must be part of it (WCAG 2.5.3).\n */\n actionAriaLabel?: string;\n /**\n * URL of the row link (3.16, TD-4). The row renders as an `<a>` over its\n * full area (at least 44 px high) with real link semantics (middle click,\n * \"open in new tab\"). Not rendered with `href`: `action` (no control inside\n * a link; a dev warning). Not emitted: `itemClick` (see `linkClick`). With\n * `disabled` the row is the plain disabled row, no link. A custom\n * `lcListItem` template must not hold controls in `href` rows.\n */\n href?: string;\n /** Item identifier */\n id?: string;\n /** Whether the item is disabled */\n disabled?: boolean;\n /**\n * Whether the item is the current choice: a tinted row and, since 3.33,\n * `aria-current=\"true\"` on the row element (the button with `clickable`,\n * the link with `href`, the row otherwise). Pair it with a visible mark such\n * as a badge; the tint alone is colour.\n */\n selected?: boolean;\n /** Any additional data */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n [key: string]: any;\n}\n\n/**\n * List item size\n */\nexport type ListSize = 'sm' | 'md' | 'lg';\n\n/**\n * A plain click (or Enter) on an `href` row (3.16, TD-4): `item` is the row,\n * `event` the click. Call `event.preventDefault()` to navigate with your\n * router instead of a full page load.\n */\nexport interface ListLinkClickEvent {\n item: ListItem;\n event: MouseEvent;\n}\n\n/**\n * The part of a row that `focusItem()` focuses (3.34): `row`, the row's\n * button (`clickable`) or link (`href`), or `action`, its built-in action\n * button.\n */\nexport type ListItemPart = 'row' | 'action';\n\n/**\n * List component for displaying structured item collections.\n *\n * Features:\n * - Vertical and horizontal layout orientations\n * - Variant styles (default, bordered, card)\n * - Optional dividers between items\n * - Icon and avatar support per item\n * - Action buttons with click events\n * - Accessible with ARIA list role; `clickable` rows are keyboard-operable\n * - `ListItem.href` rows are real links over the full row (3.16, TD-4);\n * `linkClick` hands plain clicks to your router\n * - `focusItem(id, part)` (3.34): moves the focus to a row's button or link,\n * or to its action, e.g. when the sheet that action opened closes\n * (WCAG 2.4.3)\n *\n * @example\n * ```html\n * <lc-list\n * [items]=\"listItems\"\n * orientation=\"vertical\"\n * [showDividers]=\"true\"\n * (itemClick)=\"handleItemClick($event)\"\n * />\n * ```\n */\n@Component({\n selector: 'lc-list',\n standalone: true,\n imports: [NgTemplateOutlet, IconComponent, ButtonComponent],\n templateUrl: './list.component.html',\n styleUrl: './list.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class ListComponent {\n /** Custom item template provided via lcListItem directive */\n readonly itemTemplate = contentChild(ListItemTemplateDirective);\n\n /** Array of list items to display */\n items = input<ListItem[]>([]);\n\n /** Orientation of the list */\n orientation = input<ListOrientation>('vertical');\n\n /** Visual variant */\n variant = input<ListVariant>('default');\n\n /** Size of list items */\n size = input<ListSize>('md');\n\n /** Whether to show dividers between items */\n showDividers = input<boolean>(false);\n\n /**\n * Whether rows are interactive. When set, each row is rendered as a\n * focusable button (Enter / Space emit `itemClick`) so `itemClick` is\n * reachable from the keyboard. Set this whenever you handle `itemClick`.\n */\n clickable = input<boolean>(false);\n\n /**\n * Clamp each item's `description` to two lines (3.2). Set `false` for\n * reading text that must show in full (3.3, G-11).\n * @default true\n */\n clampDescription = input<boolean>(true);\n\n /**\n * Variant of the built-in row action (`ListItem.action`).\n * @default 'ghost'\n */\n actionVariant = input<ButtonVariant>('ghost');\n\n /**\n * Size of the built-in row action: `xs` (3.2, about 25 px) … `lg` (48.5 px,\n * the 44 px touch target).\n * @default 'xs'\n */\n actionSize = input<ButtonSize>('xs');\n\n /** Event emitted when an item is clicked */\n readonly itemClick = output<ListItem>();\n\n /** Event emitted when an action button is clicked */\n readonly actionClick = output<ListItem>();\n\n /**\n * A plain click (or Enter) on an `href` row: primary button, no\n * Ctrl/Cmd/Shift/Alt (3.16, TD-4). Emitted before the browser follows the\n * link; call `event.preventDefault()` and navigate with your router for an\n * in-app route (`router.navigateByUrl(item.href)`). The kit never prevents\n * the default; modifier and middle clicks are never emitted, so \"open in new\n * tab\" keeps working.\n */\n readonly linkClick = output<ListLinkClickEvent>();\n\n private readonly injector = inject(Injector);\n\n /** The `role=\"list\"` element; each item renders exactly one child of it. */\n private readonly listEl = viewChild<ElementRef<HTMLElement>>('listEl');\n\n /** The `focusItem()` hook waiting for the next render. */\n private pendingItemFocus?: AfterRenderRef;\n\n constructor() {\n effect(() => {\n if (!isDevMode()) return;\n // `items` may be bound to undefined (tolerated since 3.2).\n for (const item of this.items() ?? []) {\n if (this.isLinkRow(item) && item.action) {\n console.warn(`[lc-list] item \"${item.label}\" has href and action: the action is not rendered inside a link`);\n }\n }\n });\n }\n\n /**\n * Moves the focus into the row of the first item with this `id` after the\n * next render (3.34). `'row'`: the row's button (`clickable`) or link\n * (`href`). `'action'`: its built-in action button. Does nothing for an\n * unknown `id`, for a row without that part (a plain row, a disabled row for\n * `'row'`, no `action`, an `lcListItem` template's own controls), or when\n * the list is destroyed first. A second call before that render replaces the\n * first.\n * @param id The `ListItem.id` of the row.\n * @param part Which part of the row gets the focus.\n */\n focusItem(id: string, part: ListItemPart = 'row'): void {\n this.pendingItemFocus?.destroy();\n this.pendingItemFocus = afterNextRender(\n () => {\n this.pendingItemFocus = undefined;\n const index = (this.items() ?? []).findIndex((item) => item.id === id);\n const row = index < 0 ? null : this.listEl()?.nativeElement.children.item(index);\n // querySelector skips the row element itself, so a plain row matches nothing.\n const target = row?.querySelector<HTMLElement>(\n part === 'action'\n ? '.lc-list__action-area button'\n : 'a.lc-list__item, .lc-list__item[role=button]:not([aria-disabled=true])',\n );\n target?.focus();\n },\n { injector: this.injector },\n );\n }\n\n /** Whether a row renders as a link: `href` set and not disabled. */\n protected isLinkRow(item: ListItem): boolean {\n return !!item.href && !item.disabled;\n }\n\n /**\n * Computed CSS classes for the list\n */\n listClasses = computed(() => {\n const classes = ['lc-list'];\n\n classes.push(`lc-list--${this.orientation()}`);\n classes.push(`lc-list--${this.variant()}`);\n classes.push(`lc-list--${this.size()}`);\n\n if (this.showDividers()) {\n classes.push('lc-list--with-dividers');\n }\n\n if (this.clickable()) {\n classes.push('lc-list--clickable');\n }\n\n if (!this.clampDescription()) {\n classes.push('lc-list--no-clamp');\n }\n\n return classes.join(' ');\n });\n\n /**\n * Get CSS classes for a list item\n */\n getItemClasses(item: ListItem): string {\n const classes = ['lc-list__item'];\n\n if (item.disabled) {\n classes.push('lc-list__item--disabled');\n }\n\n if (item.selected) {\n classes.push('lc-list__item--selected');\n }\n\n return classes.join(' ');\n }\n\n /** CSS classes of an item's avatar (member colour, LK-7). */\n protected avatarClasses(item: ListItem): string {\n const color = item.avatarColor;\n return color && color !== 'default'\n ? `lc-list__avatar lc-list__avatar--member lc-list__avatar--${color}`\n : 'lc-list__avatar';\n }\n\n /**\n * Handle item click\n */\n onItemClick(item: ListItem): void {\n if (!item.disabled) {\n this.itemClick.emit(item);\n }\n }\n\n /**\n * Keyboard activation of a clickable row. Only reacts to keys on the row\n * itself: a nested control (e.g. the action button) handles its own Enter.\n */\n onItemKeydown(event: KeyboardEvent, item: ListItem): void {\n if (event.target !== event.currentTarget) return;\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n this.onItemClick(item);\n }\n }\n\n /**\n * Handle action button click\n */\n onActionClick(item: ListItem): void {\n this.actionClick.emit(item);\n }\n\n /**\n * A click on a link row: only a plain primary click reaches `linkClick`.\n * The default (following the link) is left to the browser and the consumer.\n */\n protected onLinkClick(event: MouseEvent, item: ListItem): void {\n if (event.button !== 0 || event.ctrlKey || event.metaKey || event.shiftKey || event.altKey) return;\n this.linkClick.emit({ item, event });\n }\n}\n","<!--\n Row content is shared between the plain (legacy) row and the keyboard-\n operable row rendered when `clickable` is set.\n-->\n<ng-template #rowContent let-item>\n @if (itemTemplate(); as tpl) {\n <ng-container *ngTemplateOutlet=\"tpl.template; context: { $implicit: item }\"></ng-container>\n } @else {\n <!-- Leading: Avatar or Icon -->\n @if (item.avatar) {\n <div [class]=\"avatarClasses(item)\">\n @if (item.avatar.startsWith('http') || item.avatar.startsWith('/') || item.avatar.startsWith('data:')) {\n <img [src]=\"item.avatar\" [alt]=\"item.label\" class=\"lc-list__avatar-img\" />\n } @else {\n <span class=\"lc-list__avatar-initials\">{{ item.avatar }}</span>\n }\n </div>\n } @else if (item.icon) {\n <lc-icon [name]=\"item.icon\" class=\"lc-list__icon\" size=\"sm\" [decorative]=\"true\" />\n }\n\n <!-- Content: Label, Subtitle, Description -->\n <div class=\"lc-list__content\">\n <span class=\"lc-list__label\">{{ item.label }}</span>\n @if (item.subtitle) {\n <span class=\"lc-list__subtitle\">{{ item.subtitle }}</span>\n }\n @if (item.description) {\n <span class=\"lc-list__description\">{{ item.description }}</span>\n }\n </div>\n\n <!-- Trailing: Badge, Metadata, Action -->\n <div class=\"lc-list__trailing\">\n @if (item.badge) {\n <span class=\"lc-list__badge lc-list__badge--{{ item.badgeVariant || 'default' }}\">{{ item.badge }}</span>\n }\n @if (item.metadata) {\n <span class=\"lc-list__metadata\">{{ item.metadata }}</span>\n }\n @if (item.action && !isLinkRow(item)) {\n <!-- The action is its own control: its click must not also activate the row. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -- only stops the action click from also activating the row; the action is a real button -->\n <span class=\"lc-list__action-area\" (click)=\"$event.stopPropagation()\">\n <lc-button\n [variant]=\"actionVariant()\"\n [size]=\"actionSize()\"\n [ariaLabel]=\"item.actionAriaLabel ?? ''\"\n class=\"lc-list__action\"\n (clicked)=\"onActionClick(item)\"\n >\n {{ item.action }}\n </lc-button>\n </span>\n }\n </div>\n }\n</ng-template>\n\n<div #listEl [class]=\"listClasses()\" role=\"list\">\n @for (item of items(); track item.id || $index) {\n @if (isLinkRow(item)) {\n <!-- A real link over the whole row (3.16, TD-4); the row element carries the item styles. -->\n <div class=\"lc-list__item-wrapper\" role=\"listitem\">\n <a\n [class]=\"getItemClasses(item) + ' lc-list__item--link'\"\n [href]=\"item.href\"\n [attr.aria-current]=\"item.selected ? 'true' : null\"\n (click)=\"onLinkClick($event, item)\"\n >\n <ng-container *ngTemplateOutlet=\"rowContent; context: { $implicit: item }\"></ng-container>\n </a>\n </div>\n } @else if (clickable()) {\n <div class=\"lc-list__item-wrapper\" role=\"listitem\">\n <div\n [class]=\"getItemClasses(item)\"\n role=\"button\"\n [attr.tabindex]=\"item.disabled ? -1 : 0\"\n [attr.aria-disabled]=\"item.disabled ? 'true' : null\"\n [attr.aria-current]=\"item.selected ? 'true' : null\"\n (click)=\"onItemClick(item)\"\n (keydown)=\"onItemKeydown($event, item)\"\n >\n <ng-container *ngTemplateOutlet=\"rowContent; context: { $implicit: item }\"></ng-container>\n </div>\n </div>\n } @else {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events -- non-clickable lists keep reporting mouse clicks (backward compatible); use clickable for keyboard rows -->\n <div\n [class]=\"getItemClasses(item)\"\n role=\"listitem\"\n [attr.aria-disabled]=\"item.disabled ? 'true' : null\"\n [attr.aria-current]=\"item.selected ? 'true' : null\"\n (click)=\"onItemClick(item)\"\n >\n <ng-container *ngTemplateOutlet=\"rowContent; context: { $implicit: item }\"></ng-container>\n </div>\n }\n }\n</div>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"lifecorner-angular-ui-kit-list.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/list/list-item-template.directive.ts","../../../../libs/angular-ui-kit/src/lib/list/list.component.ts","../../../../libs/angular-ui-kit/src/lib/list/list.component.html"],"sourcesContent":["import { Directive, TemplateRef, inject } from '@angular/core';\nimport { ListItem } from './list.component';\n\n/**\n * Directive for providing a custom template to render each list item.\n *\n * @example\n * ```html\n * <lc-list [items]=\"runs()\" (itemClick)=\"onRunClick($event)\">\n * <ng-template lcListItem let-item>\n * <lc-badge [variant]=\"getStatusVariant(item.status)\">{{ item.status }}</lc-badge>\n * <span>{{ item.symbol }}</span>\n * <span class=\"text-sm text-neutral-500\">{{ item.date }}</span>\n * </ng-template>\n * </lc-list>\n * ```\n */\n@Directive({\n selector: '[lcListItem]',\n standalone: true,\n})\nexport class ListItemTemplateDirective {\n public readonly template = inject(TemplateRef<{ $implicit: ListItem }>);\n}\n","import {\n AfterRenderRef,\n Component,\n ChangeDetectionStrategy,\n ElementRef,\n Injector,\n input,\n computed,\n contentChild,\n effect,\n inject,\n isDevMode,\n output,\n viewChild,\n afterNextRender,\n} from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport { IconComponent, ɵprovideKitIcons } from '@lifecorner/angular-ui-kit/icon';\nimport { ICON_CHEVRON_RIGHT } from '@lifecorner/angular-ui-kit/icons';\nimport { ButtonComponent, ButtonSize, ButtonVariant } from '@lifecorner/angular-ui-kit/button';\nimport { ListItemTemplateDirective } from './list-item-template.directive';\nimport type { AvatarColor } from '@lifecorner/angular-ui-kit/avatar';\n\n/**\n * List orientation type\n */\nexport type ListOrientation = 'vertical' | 'horizontal';\n\n/**\n * List visual variant\n * - default: flat rows (original)\n * - card: each item rendered as a mini-card with border, radius and shadow on hover\n */\nexport type ListVariant = 'default' | 'card';\n\n/**\n * List item interface\n */\nexport interface ListItem {\n /** Item label text (primary line) */\n label: string;\n /** Secondary text shown below the label */\n subtitle?: string;\n /** Description text shown below subtitle (third line) */\n description?: string;\n /** Optional icon identifier */\n icon?: string;\n /** Avatar URL or initials (renders a circular avatar instead of icon) */\n avatar?: string;\n /**\n * Background of avatar initials: `default` or a member colour `member-1` …\n * `member-6` with white initials (as `lc-avatar`'s `color`).\n */\n avatarColor?: AvatarColor;\n /** Badge text (e.g. count, status label) shown on the trailing side */\n badge?: string;\n /** Badge color variant */\n badgeVariant?: 'default' | 'primary' | 'success' | 'warning' | 'error';\n /** Metadata text shown on the trailing side (e.g. date, size) */\n metadata?: string;\n /** Optional action label */\n action?: string;\n /**\n * Accessible name of this row's action when the visible label repeats on\n * every row (e.g. „Entfernen“ → „iPhone · Safari entfernen“). The visible\n * label must be part of it (WCAG 2.5.3).\n */\n actionAriaLabel?: string;\n /**\n * URL of the row link (3.16, TD-4). The row renders as an `<a>` over its\n * full area (at least 44 px high) with real link semantics (middle click,\n * \"open in new tab\"). Not rendered with `href`: `action` (no control inside\n * a link; a dev warning). Not emitted: `itemClick` (see `linkClick`). With\n * `disabled` the row is the plain disabled row, no link. A custom\n * `lcListItem` template must not hold controls in `href` rows.\n */\n href?: string;\n /** Item identifier */\n id?: string;\n /** Whether the item is disabled */\n disabled?: boolean;\n /**\n * Whether the item is the current choice: a tinted row and, since 3.33,\n * `aria-current=\"true\"` on the row element (the button with `clickable`,\n * the link with `href`, the row otherwise). Pair it with a visible mark such\n * as a badge; the tint alone is colour.\n */\n selected?: boolean;\n /** Any additional data */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n [key: string]: any;\n}\n\n/**\n * List item size\n */\nexport type ListSize = 'sm' | 'md' | 'lg';\n\n/**\n * A plain click (or Enter) on an `href` row (3.16, TD-4): `item` is the row,\n * `event` the click. Call `event.preventDefault()` to navigate with your\n * router instead of a full page load.\n */\nexport interface ListLinkClickEvent {\n item: ListItem;\n event: MouseEvent;\n}\n\n/**\n * The part of a row that `focusItem()` focuses (3.34): `row`, the row's\n * button (`clickable`) or link (`href`), or `action`, its built-in action\n * button.\n */\nexport type ListItemPart = 'row' | 'action';\n\n/**\n * List component for displaying structured item collections.\n *\n * Features:\n * - Vertical and horizontal layout orientations\n * - Variant styles (default, bordered, card)\n * - Optional dividers between items\n * - Icon and avatar support per item\n * - Action buttons with click events\n * - Accessible with ARIA list role; `clickable` rows are keyboard-operable\n * - `ListItem.href` rows are real links over the full row (3.16, TD-4);\n * `linkClick` hands plain clicks to your router\n * - `focusItem(id, part)` (3.34): moves the focus to a row's button or link,\n * or to its action, e.g. when the sheet that action opened closes\n * (WCAG 2.4.3)\n * - `showChevron` (3.36): a decorative chevron at the end of every\n * `clickable` or `href` row\n *\n * @example\n * ```html\n * <lc-list\n * [items]=\"listItems\"\n * orientation=\"vertical\"\n * [showDividers]=\"true\"\n * (itemClick)=\"handleItemClick($event)\"\n * />\n * ```\n */\n@Component({\n selector: 'lc-list',\n standalone: true,\n imports: [NgTemplateOutlet, IconComponent, ButtonComponent],\n templateUrl: './list.component.html',\n styleUrl: './list.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n viewProviders: [ɵprovideKitIcons([ICON_CHEVRON_RIGHT])],\n})\nexport class ListComponent {\n /** Custom item template provided via lcListItem directive */\n readonly itemTemplate = contentChild(ListItemTemplateDirective);\n\n /** Array of list items to display */\n items = input<ListItem[]>([]);\n\n /** Orientation of the list */\n orientation = input<ListOrientation>('vertical');\n\n /** Visual variant */\n variant = input<ListVariant>('default');\n\n /** Size of list items */\n size = input<ListSize>('md');\n\n /** Whether to show dividers between items */\n showDividers = input<boolean>(false);\n\n /**\n * Whether rows are interactive. When set, each row is rendered as a\n * focusable button (Enter / Space emit `itemClick`) so `itemClick` is\n * reachable from the keyboard. Set this whenever you handle `itemClick`.\n */\n clickable = input<boolean>(false);\n\n /**\n * Clamp each item's `description` to two lines (3.2). Set `false` for\n * reading text that must show in full (3.3, G-11).\n * @default true\n */\n clampDescription = input<boolean>(true);\n\n /**\n * Variant of the built-in row action (`ListItem.action`).\n * @default 'ghost'\n */\n actionVariant = input<ButtonVariant>('ghost');\n\n /**\n * Size of the built-in row action: `xs` (3.2, about 25 px) … `lg` (48.5 px,\n * the 44 px touch target).\n * @default 'xs'\n */\n actionSize = input<ButtonSize>('xs');\n\n /**\n * Ends every row that opens something, a `clickable` row or an `href` row,\n * with the kit's `chevron-right` (20 px, `--color-text-tertiary`) after its\n * badge, metadata and action (3.36, quellen row 3). Decorative: hidden from\n * screen readers. Plain rows get none, and neither do rows drawn by an\n * `lcListItem` template (add the icon to the template there). Row height,\n * target size, focus ring, badge and `actionClick` don't change.\n * @default false\n */\n showChevron = input<boolean>(false);\n\n /** Event emitted when an item is clicked */\n readonly itemClick = output<ListItem>();\n\n /** Event emitted when an action button is clicked */\n readonly actionClick = output<ListItem>();\n\n /**\n * A plain click (or Enter) on an `href` row: primary button, no\n * Ctrl/Cmd/Shift/Alt (3.16, TD-4). Emitted before the browser follows the\n * link; call `event.preventDefault()` and navigate with your router for an\n * in-app route (`router.navigateByUrl(item.href)`). The kit never prevents\n * the default; modifier and middle clicks are never emitted, so \"open in new\n * tab\" keeps working.\n */\n readonly linkClick = output<ListLinkClickEvent>();\n\n private readonly injector = inject(Injector);\n\n /** The `role=\"list\"` element; each item renders exactly one child of it. */\n private readonly listEl = viewChild<ElementRef<HTMLElement>>('listEl');\n\n /** The `focusItem()` hook waiting for the next render. */\n private pendingItemFocus?: AfterRenderRef;\n\n constructor() {\n effect(() => {\n if (!isDevMode()) return;\n // `items` may be bound to undefined (tolerated since 3.2).\n for (const item of this.items() ?? []) {\n if (this.isLinkRow(item) && item.action) {\n console.warn(`[lc-list] item \"${item.label}\" has href and action: the action is not rendered inside a link`);\n }\n }\n });\n }\n\n /**\n * Moves the focus into the row of the first item with this `id` after the\n * next render (3.34). `'row'`: the row's button (`clickable`) or link\n * (`href`). `'action'`: its built-in action button. Does nothing for an\n * unknown `id`, for a row without that part (a plain row, a disabled row for\n * `'row'`, no `action`, an `lcListItem` template's own controls), or when\n * the list is destroyed first. A second call before that render replaces the\n * first.\n * @param id The `ListItem.id` of the row.\n * @param part Which part of the row gets the focus.\n */\n focusItem(id: string, part: ListItemPart = 'row'): void {\n this.pendingItemFocus?.destroy();\n this.pendingItemFocus = afterNextRender(\n () => {\n this.pendingItemFocus = undefined;\n const index = (this.items() ?? []).findIndex((item) => item.id === id);\n const row = index < 0 ? null : this.listEl()?.nativeElement.children.item(index);\n // querySelector skips the row element itself, so a plain row matches nothing.\n const target = row?.querySelector<HTMLElement>(\n part === 'action'\n ? '.lc-list__action-area button'\n : 'a.lc-list__item, .lc-list__item[role=button]:not([aria-disabled=true])',\n );\n target?.focus();\n },\n { injector: this.injector },\n );\n }\n\n /** Whether a row renders as a link: `href` set and not disabled. */\n protected isLinkRow(item: ListItem): boolean {\n return !!item.href && !item.disabled;\n }\n\n /**\n * Computed CSS classes for the list\n */\n listClasses = computed(() => {\n const classes = ['lc-list'];\n\n classes.push(`lc-list--${this.orientation()}`);\n classes.push(`lc-list--${this.variant()}`);\n classes.push(`lc-list--${this.size()}`);\n\n if (this.showDividers()) {\n classes.push('lc-list--with-dividers');\n }\n\n if (this.clickable()) {\n classes.push('lc-list--clickable');\n }\n\n if (!this.clampDescription()) {\n classes.push('lc-list--no-clamp');\n }\n\n return classes.join(' ');\n });\n\n /**\n * Get CSS classes for a list item\n */\n getItemClasses(item: ListItem): string {\n const classes = ['lc-list__item'];\n\n if (item.disabled) {\n classes.push('lc-list__item--disabled');\n }\n\n if (item.selected) {\n classes.push('lc-list__item--selected');\n }\n\n return classes.join(' ');\n }\n\n /** CSS classes of an item's avatar (member colour, LK-7). */\n protected avatarClasses(item: ListItem): string {\n const color = item.avatarColor;\n return color && color !== 'default'\n ? `lc-list__avatar lc-list__avatar--member lc-list__avatar--${color}`\n : 'lc-list__avatar';\n }\n\n /**\n * Handle item click\n */\n onItemClick(item: ListItem): void {\n if (!item.disabled) {\n this.itemClick.emit(item);\n }\n }\n\n /**\n * Keyboard activation of a clickable row. Only reacts to keys on the row\n * itself: a nested control (e.g. the action button) handles its own Enter.\n */\n onItemKeydown(event: KeyboardEvent, item: ListItem): void {\n if (event.target !== event.currentTarget) return;\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n this.onItemClick(item);\n }\n }\n\n /**\n * Handle action button click\n */\n onActionClick(item: ListItem): void {\n this.actionClick.emit(item);\n }\n\n /**\n * A click on a link row: only a plain primary click reaches `linkClick`.\n * The default (following the link) is left to the browser and the consumer.\n */\n protected onLinkClick(event: MouseEvent, item: ListItem): void {\n if (event.button !== 0 || event.ctrlKey || event.metaKey || event.shiftKey || event.altKey) return;\n this.linkClick.emit({ item, event });\n }\n}\n","<!--\n Row content is shared between the plain (legacy) row and the keyboard-\n operable row rendered when `clickable` is set.\n-->\n<ng-template #rowContent let-item>\n @if (itemTemplate(); as tpl) {\n <ng-container *ngTemplateOutlet=\"tpl.template; context: { $implicit: item }\"></ng-container>\n } @else {\n <!-- Leading: Avatar or Icon -->\n @if (item.avatar) {\n <div [class]=\"avatarClasses(item)\">\n @if (item.avatar.startsWith('http') || item.avatar.startsWith('/') || item.avatar.startsWith('data:')) {\n <img [src]=\"item.avatar\" [alt]=\"item.label\" class=\"lc-list__avatar-img\" />\n } @else {\n <span class=\"lc-list__avatar-initials\">{{ item.avatar }}</span>\n }\n </div>\n } @else if (item.icon) {\n <lc-icon [name]=\"item.icon\" class=\"lc-list__icon\" size=\"sm\" [decorative]=\"true\" />\n }\n\n <!-- Content: Label, Subtitle, Description -->\n <div class=\"lc-list__content\">\n <span class=\"lc-list__label\">{{ item.label }}</span>\n @if (item.subtitle) {\n <span class=\"lc-list__subtitle\">{{ item.subtitle }}</span>\n }\n @if (item.description) {\n <span class=\"lc-list__description\">{{ item.description }}</span>\n }\n </div>\n\n <!-- Trailing: Badge, Metadata, Action -->\n <div class=\"lc-list__trailing\">\n @if (item.badge) {\n <span class=\"lc-list__badge lc-list__badge--{{ item.badgeVariant || 'default' }}\">{{ item.badge }}</span>\n }\n @if (item.metadata) {\n <span class=\"lc-list__metadata\">{{ item.metadata }}</span>\n }\n @if (item.action && !isLinkRow(item)) {\n <!-- The action is its own control: its click must not also activate the row. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -- only stops the action click from also activating the row; the action is a real button -->\n <span class=\"lc-list__action-area\" (click)=\"$event.stopPropagation()\">\n <lc-button\n [variant]=\"actionVariant()\"\n [size]=\"actionSize()\"\n [ariaLabel]=\"item.actionAriaLabel ?? ''\"\n class=\"lc-list__action\"\n (clicked)=\"onActionClick(item)\"\n >\n {{ item.action }}\n </lc-button>\n </span>\n }\n </div>\n\n <!-- 3.36: a decorative chevron ends a clickable or linked row. -->\n @if (showChevron() && (clickable() || !!item.href)) {\n <lc-icon name=\"chevron-right\" class=\"lc-list__chevron\" size=\"sm\" [decorative]=\"true\" />\n }\n }\n</ng-template>\n\n<div #listEl [class]=\"listClasses()\" role=\"list\">\n @for (item of items(); track item.id || $index) {\n @if (isLinkRow(item)) {\n <!-- A real link over the whole row (3.16, TD-4); the row element carries the item styles. -->\n <div class=\"lc-list__item-wrapper\" role=\"listitem\">\n <a\n [class]=\"getItemClasses(item) + ' lc-list__item--link'\"\n [href]=\"item.href\"\n [attr.aria-current]=\"item.selected ? 'true' : null\"\n (click)=\"onLinkClick($event, item)\"\n >\n <ng-container *ngTemplateOutlet=\"rowContent; context: { $implicit: item }\"></ng-container>\n </a>\n </div>\n } @else if (clickable()) {\n <div class=\"lc-list__item-wrapper\" role=\"listitem\">\n <div\n [class]=\"getItemClasses(item)\"\n role=\"button\"\n [attr.tabindex]=\"item.disabled ? -1 : 0\"\n [attr.aria-disabled]=\"item.disabled ? 'true' : null\"\n [attr.aria-current]=\"item.selected ? 'true' : null\"\n (click)=\"onItemClick(item)\"\n (keydown)=\"onItemKeydown($event, item)\"\n >\n <ng-container *ngTemplateOutlet=\"rowContent; context: { $implicit: item }\"></ng-container>\n </div>\n </div>\n } @else {\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events -- non-clickable lists keep reporting mouse clicks (backward compatible); use clickable for keyboard rows -->\n <div\n [class]=\"getItemClasses(item)\"\n role=\"listitem\"\n [attr.aria-disabled]=\"item.disabled ? 'true' : null\"\n [attr.aria-current]=\"item.selected ? 'true' : null\"\n (click)=\"onItemClick(item)\"\n >\n <ng-container *ngTemplateOutlet=\"rowContent; context: { $implicit: item }\"></ng-container>\n </div>\n }\n }\n</div>\n"],"mappings":"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"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as i0 from "@angular/core";
|
|
2
|
-
import { ChangeDetectionStrategy, Component, DestroyRef, ElementRef, afterNextRender, computed, inject, input, signal } from "@angular/core";
|
|
2
|
+
import { ChangeDetectionStrategy, Component, DestroyRef, ElementRef, afterNextRender, computed, effect, inject, input, isDevMode, signal } from "@angular/core";
|
|
3
3
|
import { chartColor, formatChartValue, niceScale, toFinite } from "@lifecorner/angular-ui-kit/internal/chart";
|
|
4
|
+
const roundPercent = (value) => Math.round(value * 1e4) / 1e4;
|
|
4
5
|
const DEFAULT_WIDTH = 400;
|
|
5
6
|
const MIN_SEGMENT_LABEL_PX = 14;
|
|
6
7
|
var StackedBarChartComponent = class StackedBarChartComponent {
|
|
@@ -15,6 +16,10 @@ var StackedBarChartComponent = class StackedBarChartComponent {
|
|
|
15
16
|
obs.observe(this._el.nativeElement);
|
|
16
17
|
this._destroyRef.onDestroy(() => obs.disconnect());
|
|
17
18
|
});
|
|
19
|
+
if (isDevMode()) effect(() => {
|
|
20
|
+
const count = (this.categories() ?? []).length;
|
|
21
|
+
if (this.variant() === "band" && count !== 1) console.warn(`[lc-stacked-bar-chart] variant="band" needs exactly one category (got ${count}): rendering as chart`);
|
|
22
|
+
});
|
|
18
23
|
}
|
|
19
24
|
categories = input.required(...ngDevMode ? [{ debugName: "categories" }] : /* istanbul ignore next */ []);
|
|
20
25
|
legends = input([], ...ngDevMode ? [{ debugName: "legends" }] : /* istanbul ignore next */ []);
|
|
@@ -28,6 +33,28 @@ var StackedBarChartComponent = class StackedBarChartComponent {
|
|
|
28
33
|
barGap = input(.3, ...ngDevMode ? [{ debugName: "barGap" }] : /* istanbul ignore next */ []);
|
|
29
34
|
formatValue = input(formatChartValue, ...ngDevMode ? [{ debugName: "formatValue" }] : /* istanbul ignore next */ []);
|
|
30
35
|
ariaLabel = input("", ...ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []);
|
|
36
|
+
variant = input("chart", ...ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []);
|
|
37
|
+
isBand = computed(() => this.variant() === "band" && (this.categories() ?? []).length === 1, ...ngDevMode ? [{ debugName: "isBand" }] : /* istanbul ignore next */ []);
|
|
38
|
+
bandParts = computed(() => {
|
|
39
|
+
if (!this.isBand()) return [];
|
|
40
|
+
const legs = this.legends();
|
|
41
|
+
const values = (this.categories()[0].values ?? []).map((raw) => Math.max(0, toFinite(raw)));
|
|
42
|
+
const sum = values.reduce((a, b) => a + b, 0);
|
|
43
|
+
if (sum <= 0) return [];
|
|
44
|
+
let at = 0;
|
|
45
|
+
return values.map((value, i) => {
|
|
46
|
+
const x = at / sum * 100;
|
|
47
|
+
at += value;
|
|
48
|
+
return {
|
|
49
|
+
x: roundPercent(x),
|
|
50
|
+
w: roundPercent(value / sum * 100),
|
|
51
|
+
color: legs[i]?.color || chartColor(i)
|
|
52
|
+
};
|
|
53
|
+
});
|
|
54
|
+
}, ...ngDevMode ? [{ debugName: "bandParts" }] : /* istanbul ignore next */ []);
|
|
55
|
+
bandGaps = computed(() => {
|
|
56
|
+
return this.bandParts().filter((part) => part.w > 0).slice(1).map((part) => part.x);
|
|
57
|
+
}, ...ngDevMode ? [{ debugName: "bandGaps" }] : /* istanbul ignore next */ []);
|
|
31
58
|
PL = 40;
|
|
32
59
|
PR = 10;
|
|
33
60
|
PT = 10;
|
|
@@ -282,12 +309,19 @@ var StackedBarChartComponent = class StackedBarChartComponent {
|
|
|
282
309
|
isSignal: true,
|
|
283
310
|
isRequired: false,
|
|
284
311
|
transformFunction: null
|
|
312
|
+
},
|
|
313
|
+
variant: {
|
|
314
|
+
classPropertyName: "variant",
|
|
315
|
+
publicName: "variant",
|
|
316
|
+
isSignal: true,
|
|
317
|
+
isRequired: false,
|
|
318
|
+
transformFunction: null
|
|
285
319
|
}
|
|
286
320
|
},
|
|
287
321
|
host: { properties: { "style.max-width.px": "width()" } },
|
|
288
322
|
ngImport: i0,
|
|
289
|
-
template: "<div class=\"lc-stacked-bar-chart\">\n <svg\n width=\"100%\"\n [attr.height]=\"height()\"\n [attr.viewBox]=\"viewBox()\"\n role=\"img\"\n [attr.aria-label]=\"effectiveAriaLabel()\"\n class=\"lc-stacked-bar-chart__svg\"\n >\n <title>{{ effectiveAriaLabel() }}</title>\n\n @if (showGrid()) {\n @for (line of gridLines(); track line.pos) {\n @if (isVertical()) {\n <line [attr.x1]=\"axisLine().hx1\" [attr.y1]=\"line.pos\" [attr.x2]=\"axisLine().hx2\" [attr.y2]=\"line.pos\" class=\"lc-stacked-bar-chart__grid\" />\n <text [attr.x]=\"axisLine().vx1 - 6\" [attr.y]=\"line.pos\" text-anchor=\"end\" dominant-baseline=\"central\" class=\"lc-stacked-bar-chart__grid-label\">{{ line.label }}</text>\n } @else {\n <line [attr.x1]=\"line.pos\" [attr.y1]=\"axisLine().vy1\" [attr.x2]=\"line.pos\" [attr.y2]=\"axisLine().vy2\" class=\"lc-stacked-bar-chart__grid\" />\n <text [attr.x]=\"line.pos\" [attr.y]=\"plotBottom() + 12\" text-anchor=\"middle\" class=\"lc-stacked-bar-chart__grid-label\">{{ line.label }}</text>\n }\n }\n }\n\n <line [attr.x1]=\"axisLine().vx1\" [attr.y1]=\"axisLine().vy1\" [attr.x2]=\"axisLine().vx2\" [attr.y2]=\"axisLine().vy2\" class=\"lc-stacked-bar-chart__axis\" />\n <line [attr.x1]=\"axisLine().hx1\" [attr.y1]=\"axisLine().hy1\" [attr.x2]=\"axisLine().hx2\" [attr.y2]=\"axisLine().hy2\" class=\"lc-stacked-bar-chart__axis\" />\n\n @for (stack of stacks(); track $index) {\n @for (seg of stack.segments; track $index) {\n <rect [attr.x]=\"seg.x\" [attr.y]=\"seg.y\" [attr.width]=\"seg.w\" [attr.height]=\"seg.h\" [attr.fill]=\"seg.color\" class=\"lc-stacked-bar-chart__segment\" />\n @if (showValues() && seg.showValue) {\n <text [attr.x]=\"seg.valueX\" [attr.y]=\"seg.valueY\" text-anchor=\"middle\" dominant-baseline=\"central\" class=\"lc-stacked-bar-chart__segment-value\">{{ seg.valueText }}</text>\n }\n }\n @if (showValues()) {\n <text [attr.x]=\"stack.totalPos.x\" [attr.y]=\"stack.totalPos.y\" [attr.text-anchor]=\"stack.totalPos.anchor\" [attr.dominant-baseline]=\"isVertical() ? 'auto' : 'central'\" class=\"lc-stacked-bar-chart__total\">{{ stack.totalText }}</text>\n }\n @if (showLabels()) {\n <text [attr.x]=\"stack.labelPos.x\" [attr.y]=\"stack.labelPos.y\" [attr.text-anchor]=\"isVertical() ? 'middle' : 'end'\" [attr.dominant-baseline]=\"isVertical() ? 'auto' : 'central'\" class=\"lc-stacked-bar-chart__label\">{{ stack.label }}</text>\n }\n }\n </svg>\n\n @if (showLegend() && legendItems().length) {\n <div class=\"lc-stacked-bar-chart__legend\">\n @for (item of legendItems(); track $index) {\n <div class=\"lc-stacked-bar-chart__legend-item\">\n <span class=\"lc-stacked-bar-chart__legend-dot\" [style.background-color]=\"item.color\"></span>\n <span class=\"lc-stacked-bar-chart__legend-text\">{{ item.label }}</span>\n </div>\n }\n </div>\n }\n</div>\n",
|
|
290
|
-
styles: [":host{display:block}.lc-stacked-bar-chart{display:flex;flex-direction:column;align-items:center;gap:.5rem;width:100%}.lc-stacked-bar-chart__svg{overflow:visible}.lc-stacked-bar-chart__axis{stroke:var(--color-border);stroke-width:1}.lc-stacked-bar-chart__grid{stroke:var(--color-border);stroke-width:1;stroke-dasharray:3 3}.lc-stacked-bar-chart__grid-label{font-size:.625rem;fill:var(--color-text-tertiary)}.lc-stacked-bar-chart__segment{transition:opacity .15s ease}.lc-stacked-bar-chart__segment:hover{opacity:.8}.lc-stacked-bar-chart__label{font-size:.6875rem;fill:var(--color-text-secondary)}.lc-stacked-bar-chart__segment-value{font-size:.625rem;font-weight:600;fill:var(--color-text-primary);paint-order:stroke;stroke:var(--color-surface);stroke-width:3px;stroke-linejoin:round;pointer-events:none}.lc-stacked-bar-chart__total{font-size:.625rem;font-weight:600;fill:var(--color-text-secondary)}.lc-stacked-bar-chart__legend{display:flex;flex-wrap:wrap;gap:.5rem 1rem;justify-content:center}.lc-stacked-bar-chart__legend-item{display:flex;align-items:center;gap:.375rem;font-size:var(--typography-font-size-sm, .875rem)}.lc-stacked-bar-chart__legend-dot{width:.5rem;height:.5rem;border-radius:2px;flex-shrink:0}.lc-stacked-bar-chart__legend-text{color:var(--color-text-secondary)}\n"],
|
|
323
|
+
template: "<div class=\"lc-stacked-bar-chart\">\n @if (isBand()) {\n <!-- 3.36 band: one bar edge to edge, its parts as % of the width (exact before JS, no viewBox). -->\n <svg\n width=\"100%\"\n [attr.height]=\"height()\"\n role=\"img\"\n [attr.aria-label]=\"effectiveAriaLabel()\"\n class=\"lc-stacked-bar-chart__svg lc-stacked-bar-chart__svg--band\"\n >\n <title>{{ effectiveAriaLabel() }}</title>\n @for (part of bandParts(); track $index) {\n <rect [attr.x]=\"part.x + '%'\" y=\"0\" [attr.width]=\"part.w + '%'\" height=\"100%\" [attr.fill]=\"part.color\" class=\"lc-stacked-bar-chart__segment\" />\n }\n @for (gap of bandGaps(); track $index) {\n <line [attr.x1]=\"gap + '%'\" y1=\"0\" [attr.x2]=\"gap + '%'\" y2=\"100%\" class=\"lc-stacked-bar-chart__band-gap\" />\n }\n </svg>\n } @else {\n <svg\n width=\"100%\"\n [attr.height]=\"height()\"\n [attr.viewBox]=\"viewBox()\"\n role=\"img\"\n [attr.aria-label]=\"effectiveAriaLabel()\"\n class=\"lc-stacked-bar-chart__svg\"\n >\n <title>{{ effectiveAriaLabel() }}</title>\n\n @if (showGrid()) {\n @for (line of gridLines(); track line.pos) {\n @if (isVertical()) {\n <line [attr.x1]=\"axisLine().hx1\" [attr.y1]=\"line.pos\" [attr.x2]=\"axisLine().hx2\" [attr.y2]=\"line.pos\" class=\"lc-stacked-bar-chart__grid\" />\n <text [attr.x]=\"axisLine().vx1 - 6\" [attr.y]=\"line.pos\" text-anchor=\"end\" dominant-baseline=\"central\" class=\"lc-stacked-bar-chart__grid-label\">{{ line.label }}</text>\n } @else {\n <line [attr.x1]=\"line.pos\" [attr.y1]=\"axisLine().vy1\" [attr.x2]=\"line.pos\" [attr.y2]=\"axisLine().vy2\" class=\"lc-stacked-bar-chart__grid\" />\n <text [attr.x]=\"line.pos\" [attr.y]=\"plotBottom() + 12\" text-anchor=\"middle\" class=\"lc-stacked-bar-chart__grid-label\">{{ line.label }}</text>\n }\n }\n }\n\n <line [attr.x1]=\"axisLine().vx1\" [attr.y1]=\"axisLine().vy1\" [attr.x2]=\"axisLine().vx2\" [attr.y2]=\"axisLine().vy2\" class=\"lc-stacked-bar-chart__axis\" />\n <line [attr.x1]=\"axisLine().hx1\" [attr.y1]=\"axisLine().hy1\" [attr.x2]=\"axisLine().hx2\" [attr.y2]=\"axisLine().hy2\" class=\"lc-stacked-bar-chart__axis\" />\n\n @for (stack of stacks(); track $index) {\n @for (seg of stack.segments; track $index) {\n <rect [attr.x]=\"seg.x\" [attr.y]=\"seg.y\" [attr.width]=\"seg.w\" [attr.height]=\"seg.h\" [attr.fill]=\"seg.color\" class=\"lc-stacked-bar-chart__segment\" />\n @if (showValues() && seg.showValue) {\n <text [attr.x]=\"seg.valueX\" [attr.y]=\"seg.valueY\" text-anchor=\"middle\" dominant-baseline=\"central\" class=\"lc-stacked-bar-chart__segment-value\">{{ seg.valueText }}</text>\n }\n }\n @if (showValues()) {\n <text [attr.x]=\"stack.totalPos.x\" [attr.y]=\"stack.totalPos.y\" [attr.text-anchor]=\"stack.totalPos.anchor\" [attr.dominant-baseline]=\"isVertical() ? 'auto' : 'central'\" class=\"lc-stacked-bar-chart__total\">{{ stack.totalText }}</text>\n }\n @if (showLabels()) {\n <text [attr.x]=\"stack.labelPos.x\" [attr.y]=\"stack.labelPos.y\" [attr.text-anchor]=\"isVertical() ? 'middle' : 'end'\" [attr.dominant-baseline]=\"isVertical() ? 'auto' : 'central'\" class=\"lc-stacked-bar-chart__label\">{{ stack.label }}</text>\n }\n }\n </svg>\n }\n\n @if (showLegend() && legendItems().length) {\n <div class=\"lc-stacked-bar-chart__legend\">\n @for (item of legendItems(); track $index) {\n <div class=\"lc-stacked-bar-chart__legend-item\">\n <span class=\"lc-stacked-bar-chart__legend-dot\" [style.background-color]=\"item.color\"></span>\n <span class=\"lc-stacked-bar-chart__legend-text\">{{ item.label }}</span>\n </div>\n }\n </div>\n }\n</div>\n",
|
|
324
|
+
styles: [":host{display:block}.lc-stacked-bar-chart{display:flex;flex-direction:column;align-items:center;gap:.5rem;width:100%}.lc-stacked-bar-chart__svg{overflow:visible}.lc-stacked-bar-chart__axis{stroke:var(--color-border);stroke-width:1}.lc-stacked-bar-chart__grid{stroke:var(--color-border);stroke-width:1;stroke-dasharray:3 3}.lc-stacked-bar-chart__grid-label{font-size:.625rem;fill:var(--color-text-tertiary)}.lc-stacked-bar-chart__segment{transition:opacity .15s ease}.lc-stacked-bar-chart__segment:hover{opacity:.8}.lc-stacked-bar-chart__label{font-size:.6875rem;fill:var(--color-text-secondary)}.lc-stacked-bar-chart__segment-value{font-size:.625rem;font-weight:600;fill:var(--color-text-primary);paint-order:stroke;stroke:var(--color-surface);stroke-width:3px;stroke-linejoin:round;pointer-events:none}.lc-stacked-bar-chart__total{font-size:.625rem;font-weight:600;fill:var(--color-text-secondary)}.lc-stacked-bar-chart__legend{display:flex;flex-wrap:wrap;gap:.5rem 1rem;justify-content:center}.lc-stacked-bar-chart__legend-item{display:flex;align-items:center;gap:.375rem;font-size:var(--typography-font-size-sm, .875rem)}.lc-stacked-bar-chart__legend-dot{width:.5rem;height:.5rem;border-radius:2px;flex-shrink:0}.lc-stacked-bar-chart__legend-text{color:var(--color-text-secondary)}.lc-stacked-bar-chart__svg--band{overflow:hidden}.lc-stacked-bar-chart__band-gap{stroke:var(--lc-stacked-bar-chart-band-gap-color, var(--color-surface));stroke-width:var(--lc-stacked-bar-chart-band-gap, 2px)}@media(forced-colors:active){.lc-stacked-bar-chart__band-gap{stroke:Canvas}}\n"],
|
|
291
325
|
changeDetection: i0.ChangeDetectionStrategy.OnPush
|
|
292
326
|
});
|
|
293
327
|
};
|
|
@@ -303,8 +337,8 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
303
337
|
standalone: true,
|
|
304
338
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
305
339
|
host: { "[style.max-width.px]": "width()" },
|
|
306
|
-
template: "<div class=\"lc-stacked-bar-chart\">\n <svg\n width=\"100%\"\n [attr.height]=\"height()\"\n [attr.viewBox]=\"viewBox()\"\n role=\"img\"\n [attr.aria-label]=\"effectiveAriaLabel()\"\n class=\"lc-stacked-bar-chart__svg\"\n >\n <title>{{ effectiveAriaLabel() }}</title>\n\n @if (showGrid()) {\n @for (line of gridLines(); track line.pos) {\n @if (isVertical()) {\n <line [attr.x1]=\"axisLine().hx1\" [attr.y1]=\"line.pos\" [attr.x2]=\"axisLine().hx2\" [attr.y2]=\"line.pos\" class=\"lc-stacked-bar-chart__grid\" />\n <text [attr.x]=\"axisLine().vx1 - 6\" [attr.y]=\"line.pos\" text-anchor=\"end\" dominant-baseline=\"central\" class=\"lc-stacked-bar-chart__grid-label\">{{ line.label }}</text>\n } @else {\n <line [attr.x1]=\"line.pos\" [attr.y1]=\"axisLine().vy1\" [attr.x2]=\"line.pos\" [attr.y2]=\"axisLine().vy2\" class=\"lc-stacked-bar-chart__grid\" />\n <text [attr.x]=\"line.pos\" [attr.y]=\"plotBottom() + 12\" text-anchor=\"middle\" class=\"lc-stacked-bar-chart__grid-label\">{{ line.label }}</text>\n }\n }\n }\n\n <line [attr.x1]=\"axisLine().vx1\" [attr.y1]=\"axisLine().vy1\" [attr.x2]=\"axisLine().vx2\" [attr.y2]=\"axisLine().vy2\" class=\"lc-stacked-bar-chart__axis\" />\n <line [attr.x1]=\"axisLine().hx1\" [attr.y1]=\"axisLine().hy1\" [attr.x2]=\"axisLine().hx2\" [attr.y2]=\"axisLine().hy2\" class=\"lc-stacked-bar-chart__axis\" />\n\n @for (stack of stacks(); track $index) {\n @for (seg of stack.segments; track $index) {\n <rect [attr.x]=\"seg.x\" [attr.y]=\"seg.y\" [attr.width]=\"seg.w\" [attr.height]=\"seg.h\" [attr.fill]=\"seg.color\" class=\"lc-stacked-bar-chart__segment\" />\n @if (showValues() && seg.showValue) {\n <text [attr.x]=\"seg.valueX\" [attr.y]=\"seg.valueY\" text-anchor=\"middle\" dominant-baseline=\"central\" class=\"lc-stacked-bar-chart__segment-value\">{{ seg.valueText }}</text>\n }\n }\n @if (showValues()) {\n <text [attr.x]=\"stack.totalPos.x\" [attr.y]=\"stack.totalPos.y\" [attr.text-anchor]=\"stack.totalPos.anchor\" [attr.dominant-baseline]=\"isVertical() ? 'auto' : 'central'\" class=\"lc-stacked-bar-chart__total\">{{ stack.totalText }}</text>\n }\n @if (showLabels()) {\n <text [attr.x]=\"stack.labelPos.x\" [attr.y]=\"stack.labelPos.y\" [attr.text-anchor]=\"isVertical() ? 'middle' : 'end'\" [attr.dominant-baseline]=\"isVertical() ? 'auto' : 'central'\" class=\"lc-stacked-bar-chart__label\">{{ stack.label }}</text>\n }\n }\n </svg>\n\n @if (showLegend() && legendItems().length) {\n <div class=\"lc-stacked-bar-chart__legend\">\n @for (item of legendItems(); track $index) {\n <div class=\"lc-stacked-bar-chart__legend-item\">\n <span class=\"lc-stacked-bar-chart__legend-dot\" [style.background-color]=\"item.color\"></span>\n <span class=\"lc-stacked-bar-chart__legend-text\">{{ item.label }}</span>\n </div>\n }\n </div>\n }\n</div>\n",
|
|
307
|
-
styles: [":host{display:block}.lc-stacked-bar-chart{display:flex;flex-direction:column;align-items:center;gap:.5rem;width:100%}.lc-stacked-bar-chart__svg{overflow:visible}.lc-stacked-bar-chart__axis{stroke:var(--color-border);stroke-width:1}.lc-stacked-bar-chart__grid{stroke:var(--color-border);stroke-width:1;stroke-dasharray:3 3}.lc-stacked-bar-chart__grid-label{font-size:.625rem;fill:var(--color-text-tertiary)}.lc-stacked-bar-chart__segment{transition:opacity .15s ease}.lc-stacked-bar-chart__segment:hover{opacity:.8}.lc-stacked-bar-chart__label{font-size:.6875rem;fill:var(--color-text-secondary)}.lc-stacked-bar-chart__segment-value{font-size:.625rem;font-weight:600;fill:var(--color-text-primary);paint-order:stroke;stroke:var(--color-surface);stroke-width:3px;stroke-linejoin:round;pointer-events:none}.lc-stacked-bar-chart__total{font-size:.625rem;font-weight:600;fill:var(--color-text-secondary)}.lc-stacked-bar-chart__legend{display:flex;flex-wrap:wrap;gap:.5rem 1rem;justify-content:center}.lc-stacked-bar-chart__legend-item{display:flex;align-items:center;gap:.375rem;font-size:var(--typography-font-size-sm, .875rem)}.lc-stacked-bar-chart__legend-dot{width:.5rem;height:.5rem;border-radius:2px;flex-shrink:0}.lc-stacked-bar-chart__legend-text{color:var(--color-text-secondary)}\n"]
|
|
340
|
+
template: "<div class=\"lc-stacked-bar-chart\">\n @if (isBand()) {\n <!-- 3.36 band: one bar edge to edge, its parts as % of the width (exact before JS, no viewBox). -->\n <svg\n width=\"100%\"\n [attr.height]=\"height()\"\n role=\"img\"\n [attr.aria-label]=\"effectiveAriaLabel()\"\n class=\"lc-stacked-bar-chart__svg lc-stacked-bar-chart__svg--band\"\n >\n <title>{{ effectiveAriaLabel() }}</title>\n @for (part of bandParts(); track $index) {\n <rect [attr.x]=\"part.x + '%'\" y=\"0\" [attr.width]=\"part.w + '%'\" height=\"100%\" [attr.fill]=\"part.color\" class=\"lc-stacked-bar-chart__segment\" />\n }\n @for (gap of bandGaps(); track $index) {\n <line [attr.x1]=\"gap + '%'\" y1=\"0\" [attr.x2]=\"gap + '%'\" y2=\"100%\" class=\"lc-stacked-bar-chart__band-gap\" />\n }\n </svg>\n } @else {\n <svg\n width=\"100%\"\n [attr.height]=\"height()\"\n [attr.viewBox]=\"viewBox()\"\n role=\"img\"\n [attr.aria-label]=\"effectiveAriaLabel()\"\n class=\"lc-stacked-bar-chart__svg\"\n >\n <title>{{ effectiveAriaLabel() }}</title>\n\n @if (showGrid()) {\n @for (line of gridLines(); track line.pos) {\n @if (isVertical()) {\n <line [attr.x1]=\"axisLine().hx1\" [attr.y1]=\"line.pos\" [attr.x2]=\"axisLine().hx2\" [attr.y2]=\"line.pos\" class=\"lc-stacked-bar-chart__grid\" />\n <text [attr.x]=\"axisLine().vx1 - 6\" [attr.y]=\"line.pos\" text-anchor=\"end\" dominant-baseline=\"central\" class=\"lc-stacked-bar-chart__grid-label\">{{ line.label }}</text>\n } @else {\n <line [attr.x1]=\"line.pos\" [attr.y1]=\"axisLine().vy1\" [attr.x2]=\"line.pos\" [attr.y2]=\"axisLine().vy2\" class=\"lc-stacked-bar-chart__grid\" />\n <text [attr.x]=\"line.pos\" [attr.y]=\"plotBottom() + 12\" text-anchor=\"middle\" class=\"lc-stacked-bar-chart__grid-label\">{{ line.label }}</text>\n }\n }\n }\n\n <line [attr.x1]=\"axisLine().vx1\" [attr.y1]=\"axisLine().vy1\" [attr.x2]=\"axisLine().vx2\" [attr.y2]=\"axisLine().vy2\" class=\"lc-stacked-bar-chart__axis\" />\n <line [attr.x1]=\"axisLine().hx1\" [attr.y1]=\"axisLine().hy1\" [attr.x2]=\"axisLine().hx2\" [attr.y2]=\"axisLine().hy2\" class=\"lc-stacked-bar-chart__axis\" />\n\n @for (stack of stacks(); track $index) {\n @for (seg of stack.segments; track $index) {\n <rect [attr.x]=\"seg.x\" [attr.y]=\"seg.y\" [attr.width]=\"seg.w\" [attr.height]=\"seg.h\" [attr.fill]=\"seg.color\" class=\"lc-stacked-bar-chart__segment\" />\n @if (showValues() && seg.showValue) {\n <text [attr.x]=\"seg.valueX\" [attr.y]=\"seg.valueY\" text-anchor=\"middle\" dominant-baseline=\"central\" class=\"lc-stacked-bar-chart__segment-value\">{{ seg.valueText }}</text>\n }\n }\n @if (showValues()) {\n <text [attr.x]=\"stack.totalPos.x\" [attr.y]=\"stack.totalPos.y\" [attr.text-anchor]=\"stack.totalPos.anchor\" [attr.dominant-baseline]=\"isVertical() ? 'auto' : 'central'\" class=\"lc-stacked-bar-chart__total\">{{ stack.totalText }}</text>\n }\n @if (showLabels()) {\n <text [attr.x]=\"stack.labelPos.x\" [attr.y]=\"stack.labelPos.y\" [attr.text-anchor]=\"isVertical() ? 'middle' : 'end'\" [attr.dominant-baseline]=\"isVertical() ? 'auto' : 'central'\" class=\"lc-stacked-bar-chart__label\">{{ stack.label }}</text>\n }\n }\n </svg>\n }\n\n @if (showLegend() && legendItems().length) {\n <div class=\"lc-stacked-bar-chart__legend\">\n @for (item of legendItems(); track $index) {\n <div class=\"lc-stacked-bar-chart__legend-item\">\n <span class=\"lc-stacked-bar-chart__legend-dot\" [style.background-color]=\"item.color\"></span>\n <span class=\"lc-stacked-bar-chart__legend-text\">{{ item.label }}</span>\n </div>\n }\n </div>\n }\n</div>\n",
|
|
341
|
+
styles: [":host{display:block}.lc-stacked-bar-chart{display:flex;flex-direction:column;align-items:center;gap:.5rem;width:100%}.lc-stacked-bar-chart__svg{overflow:visible}.lc-stacked-bar-chart__axis{stroke:var(--color-border);stroke-width:1}.lc-stacked-bar-chart__grid{stroke:var(--color-border);stroke-width:1;stroke-dasharray:3 3}.lc-stacked-bar-chart__grid-label{font-size:.625rem;fill:var(--color-text-tertiary)}.lc-stacked-bar-chart__segment{transition:opacity .15s ease}.lc-stacked-bar-chart__segment:hover{opacity:.8}.lc-stacked-bar-chart__label{font-size:.6875rem;fill:var(--color-text-secondary)}.lc-stacked-bar-chart__segment-value{font-size:.625rem;font-weight:600;fill:var(--color-text-primary);paint-order:stroke;stroke:var(--color-surface);stroke-width:3px;stroke-linejoin:round;pointer-events:none}.lc-stacked-bar-chart__total{font-size:.625rem;font-weight:600;fill:var(--color-text-secondary)}.lc-stacked-bar-chart__legend{display:flex;flex-wrap:wrap;gap:.5rem 1rem;justify-content:center}.lc-stacked-bar-chart__legend-item{display:flex;align-items:center;gap:.375rem;font-size:var(--typography-font-size-sm, .875rem)}.lc-stacked-bar-chart__legend-dot{width:.5rem;height:.5rem;border-radius:2px;flex-shrink:0}.lc-stacked-bar-chart__legend-text{color:var(--color-text-secondary)}.lc-stacked-bar-chart__svg--band{overflow:hidden}.lc-stacked-bar-chart__band-gap{stroke:var(--lc-stacked-bar-chart-band-gap-color, var(--color-surface));stroke-width:var(--lc-stacked-bar-chart-band-gap, 2px)}@media(forced-colors:active){.lc-stacked-bar-chart__band-gap{stroke:Canvas}}\n"]
|
|
308
342
|
}]
|
|
309
343
|
}],
|
|
310
344
|
ctorParameters: () => [],
|
|
@@ -404,6 +438,14 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
404
438
|
alias: "ariaLabel",
|
|
405
439
|
required: false
|
|
406
440
|
}]
|
|
441
|
+
}],
|
|
442
|
+
variant: [{
|
|
443
|
+
type: i0.Input,
|
|
444
|
+
args: [{
|
|
445
|
+
isSignal: true,
|
|
446
|
+
alias: "variant",
|
|
447
|
+
required: false
|
|
448
|
+
}]
|
|
407
449
|
}]
|
|
408
450
|
}
|
|
409
451
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecorner-angular-ui-kit-stacked-bar-chart.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/stacked-bar-chart/stacked-bar-chart.component.ts","../../../../libs/angular-ui-kit/src/lib/stacked-bar-chart/stacked-bar-chart.component.html"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n DestroyRef,\n ElementRef,\n afterNextRender,\n inject,\n input,\n computed,\n signal,\n} from '@angular/core';\nimport { chartColor } from '@lifecorner/angular-ui-kit/internal/chart';\nimport {\n ChartValueFormatter,\n formatChartValue,\n niceScale,\n toFinite,\n} from '@lifecorner/angular-ui-kit/internal/chart';\n\nexport interface StackedBarCategory {\n label: string;\n /** One value per series. Negative values stack downward (left) from the zero baseline. */\n values: number[];\n}\n\nexport interface StackedBarLegend {\n label: string;\n color?: string;\n}\n\nexport type StackedBarOrientation = 'vertical' | 'horizontal';\n\n/** viewBox width used until the container has been measured. */\nconst DEFAULT_WIDTH = 400;\n\n/** Segments shorter than this along the value axis get no inline value label. */\nconst MIN_SEGMENT_LABEL_PX = 14;\n\n@Component({\n selector: 'lc-stacked-bar-chart',\n standalone: true,\n templateUrl: './stacked-bar-chart.component.html',\n styleUrls: ['./stacked-bar-chart.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n '[style.max-width.px]': 'width()',\n },\n})\n/**\n * Stacked bar chart component for comparing category compositions.\n *\n * Features:\n * - Multiple stacked value segments per category\n * - Vertical and horizontal orientation\n * - Negative values stacked downward from a zero baseline\n * - Optional legend, grid, value labels (segments + totals), and axis labels\n * - Configurable bar gap spacing\n * - Color-coded segments with legend mapping\n * - Responsive SVG rendering\n *\n * @example\n * ```html\n * <lc-stacked-bar-chart [categories]=\"data\" [legends]=\"legends\" [showLegend]=\"true\" />\n * ```\n */\nexport class StackedBarChartComponent {\n private readonly _el = inject(ElementRef);\n private readonly _destroyRef = inject(DestroyRef);\n private readonly _containerWidth = signal<number>(0);\n\n constructor() {\n afterNextRender(() => {\n const obs = new ResizeObserver(([entry]) => {\n this._containerWidth.set(entry.contentRect.width);\n });\n obs.observe(this._el.nativeElement);\n this._destroyRef.onDestroy(() => obs.disconnect());\n });\n }\n\n /** Categories with stacked values. */\n categories = input.required<StackedBarCategory[]>();\n\n /** Legend items mapping to each value index. */\n legends = input<StackedBarLegend[]>([]);\n\n /**\n * Intrinsic chart width in pixels. The chart fills its container (the SVG is\n * `width=\"100%\"` and its viewBox follows the measured container width);\n * `width` is the viewBox width until the container has been measured and,\n * when set, the host's `max-width`, so the chart never grows past it. Leave\n * unset for a fully fluid chart.\n */\n width = input<number | undefined>(undefined);\n height = input<number>(200);\n orientation = input<StackedBarOrientation>('vertical');\n showLabels = input<boolean>(true);\n /** Show the value inside each segment (when it fits) and the total at the end of each stack. */\n showValues = input<boolean>(false);\n showLegend = input<boolean>(true);\n showGrid = input<boolean>(true);\n barGap = input<number>(0.3);\n\n /** Formats segment/total value labels and axis ticks. Defaults to a float-safe `String(value)`. */\n formatValue = input<ChartValueFormatter>(formatChartValue);\n\n /**\n * Accessible name of the chart. Defaults to a generated summary listing the\n * series names and every category's total.\n */\n ariaLabel = input<string>('');\n\n private readonly PL = 40;\n private readonly PR = 10;\n private readonly PT = 10;\n private readonly PB = 30;\n\n protected readonly effectiveWidth = computed(\n () => this._containerWidth() || this.width() || DEFAULT_WIDTH\n );\n\n protected readonly viewBox = computed(() => `0 0 ${this.effectiveWidth()} ${this.height()}`);\n\n protected readonly isVertical = computed(() => this.orientation() === 'vertical');\n\n private readonly plot = computed(() => ({\n x: this.PL,\n y: this.PT,\n w: this.effectiveWidth() - this.PL - this.PR,\n h: this.height() - this.PT - this.PB,\n }));\n\n /** Positive and negative stack extents per category (positives and negatives stack separately). */\n private readonly extents = computed(() =>\n (this.categories() ?? []).map((c) => {\n let pos = 0;\n let neg = 0;\n for (const raw of c.values ?? []) {\n const v = toFinite(raw);\n if (v >= 0) pos += v;\n else neg += v;\n }\n return { pos, neg, total: pos + neg };\n })\n );\n\n /**\n * One scale for grid *and* segments: nice tick bounds that always include\n * zero and extend below it when a stack goes negative.\n */\n protected readonly scale = computed(() => {\n const ext = this.extents();\n const max = Math.max(0, ...ext.map((e) => e.pos));\n const min = Math.min(0, ...ext.map((e) => e.neg));\n return niceScale(min, max, 4);\n });\n\n private valueToPos(value: number): number {\n const { min, max } = this.scale();\n const p = this.plot();\n const frac = (value - min) / (max - min || 1);\n return this.isVertical() ? p.y + p.h - frac * p.h : p.x + frac * p.w;\n }\n\n protected readonly gridLines = computed(() => {\n if (!this.showGrid() || !this.categories()?.length) return [];\n const fmt = this.formatValue();\n return this.scale().ticks.map((tick) => ({ pos: this.valueToPos(tick), label: fmt(tick) }));\n });\n\n protected readonly stacks = computed(() => {\n const cats = this.categories();\n const legs = this.legends();\n if (!cats?.length) return [];\n\n const p = this.plot();\n const h = this.height();\n const isV = this.isVertical();\n const gap = this.barGap();\n const fmt = this.formatValue();\n const ext = this.extents();\n\n const slot = (isV ? p.w : p.h) / cats.length;\n const thickness = slot * (1 - gap);\n const off = (slot - thickness) / 2;\n\n return cats.map((cat, ci) => {\n const along = (isV ? p.x : p.y) + ci * slot + off;\n let cumPos = 0;\n let cumNeg = 0;\n\n const segments = (cat.values ?? []).map((raw, vi) => {\n const val = toFinite(raw);\n const color = legs[vi]?.color || chartColor(vi);\n const from = val >= 0 ? cumPos : cumNeg;\n const to = from + val;\n if (val >= 0) cumPos = to;\n else cumNeg = to;\n\n const a = this.valueToPos(from);\n const b = this.valueToPos(to);\n const start = Math.min(a, b);\n const len = Math.abs(b - a);\n const rect = isV\n ? { x: along, y: start, w: thickness, h: len }\n : { x: start, y: along, w: len, h: thickness };\n return {\n ...rect,\n color,\n value: val,\n valueText: fmt(val),\n showValue: len >= MIN_SEGMENT_LABEL_PX,\n valueX: rect.x + rect.w / 2,\n valueY: rect.y + rect.h / 2,\n };\n });\n\n const total = ext[ci].total;\n const posEnd = this.valueToPos(ext[ci].pos);\n const labelPos = isV\n ? { x: p.x + (ci + 0.5) * slot, y: h - this.PB + 16 }\n : { x: p.x - 6, y: p.y + (ci + 0.5) * slot };\n // Total sits past the positive end of the stack (or at the baseline for all-negative stacks).\n const totalPos = isV\n ? { x: along + thickness / 2, y: posEnd - 6, anchor: 'middle' }\n : { x: posEnd + 6, y: along + thickness / 2, anchor: 'start' };\n\n return { label: cat.label, segments, labelPos, total, totalText: fmt(total), totalPos };\n });\n });\n\n protected readonly legendItems = computed(() => {\n const legs = this.legends();\n return legs.map((l, i) => ({\n label: l.label,\n color: l.color || chartColor(i),\n }));\n });\n\n /** Value axis along the plot edge plus the zero baseline (inside the plot when data goes negative). */\n protected readonly axisLine = computed(() => {\n const p = this.plot();\n const isV = this.isVertical();\n const zero = this.categories()?.length ? this.valueToPos(0) : (isV ? p.y + p.h : p.x);\n return {\n vx1: isV ? p.x : zero, vy1: p.y,\n vx2: isV ? p.x : zero, vy2: p.y + p.h,\n hx1: p.x, hy1: isV ? zero : p.y + p.h,\n hx2: p.x + p.w, hy2: isV ? zero : p.y + p.h,\n };\n });\n\n protected readonly plotBottom = computed(() => this.plot().y + this.plot().h);\n\n protected readonly effectiveAriaLabel = computed(() => {\n const explicit = this.ariaLabel();\n if (explicit) return explicit;\n const cats = this.categories() ?? [];\n if (!cats.length) return 'Stacked bar chart: no data';\n const fmt = this.formatValue();\n const ext = this.extents();\n const seriesNames = this.legends().map((l) => l.label).filter(Boolean);\n const series = seriesNames.length ? ` (${seriesNames.join(', ')})` : '';\n const totals = cats.map((c, i) => `${c.label} ${fmt(ext[i].total)}`).join(', ');\n return `Stacked bar chart${series}: ${totals}`;\n });\n}\n","<div class=\"lc-stacked-bar-chart\">\n <svg\n width=\"100%\"\n [attr.height]=\"height()\"\n [attr.viewBox]=\"viewBox()\"\n role=\"img\"\n [attr.aria-label]=\"effectiveAriaLabel()\"\n class=\"lc-stacked-bar-chart__svg\"\n >\n <title>{{ effectiveAriaLabel() }}</title>\n\n @if (showGrid()) {\n @for (line of gridLines(); track line.pos) {\n @if (isVertical()) {\n <line [attr.x1]=\"axisLine().hx1\" [attr.y1]=\"line.pos\" [attr.x2]=\"axisLine().hx2\" [attr.y2]=\"line.pos\" class=\"lc-stacked-bar-chart__grid\" />\n <text [attr.x]=\"axisLine().vx1 - 6\" [attr.y]=\"line.pos\" text-anchor=\"end\" dominant-baseline=\"central\" class=\"lc-stacked-bar-chart__grid-label\">{{ line.label }}</text>\n } @else {\n <line [attr.x1]=\"line.pos\" [attr.y1]=\"axisLine().vy1\" [attr.x2]=\"line.pos\" [attr.y2]=\"axisLine().vy2\" class=\"lc-stacked-bar-chart__grid\" />\n <text [attr.x]=\"line.pos\" [attr.y]=\"plotBottom() + 12\" text-anchor=\"middle\" class=\"lc-stacked-bar-chart__grid-label\">{{ line.label }}</text>\n }\n }\n }\n\n <line [attr.x1]=\"axisLine().vx1\" [attr.y1]=\"axisLine().vy1\" [attr.x2]=\"axisLine().vx2\" [attr.y2]=\"axisLine().vy2\" class=\"lc-stacked-bar-chart__axis\" />\n <line [attr.x1]=\"axisLine().hx1\" [attr.y1]=\"axisLine().hy1\" [attr.x2]=\"axisLine().hx2\" [attr.y2]=\"axisLine().hy2\" class=\"lc-stacked-bar-chart__axis\" />\n\n @for (stack of stacks(); track $index) {\n @for (seg of stack.segments; track $index) {\n <rect [attr.x]=\"seg.x\" [attr.y]=\"seg.y\" [attr.width]=\"seg.w\" [attr.height]=\"seg.h\" [attr.fill]=\"seg.color\" class=\"lc-stacked-bar-chart__segment\" />\n @if (showValues() && seg.showValue) {\n <text [attr.x]=\"seg.valueX\" [attr.y]=\"seg.valueY\" text-anchor=\"middle\" dominant-baseline=\"central\" class=\"lc-stacked-bar-chart__segment-value\">{{ seg.valueText }}</text>\n }\n }\n @if (showValues()) {\n <text [attr.x]=\"stack.totalPos.x\" [attr.y]=\"stack.totalPos.y\" [attr.text-anchor]=\"stack.totalPos.anchor\" [attr.dominant-baseline]=\"isVertical() ? 'auto' : 'central'\" class=\"lc-stacked-bar-chart__total\">{{ stack.totalText }}</text>\n }\n @if (showLabels()) {\n <text [attr.x]=\"stack.labelPos.x\" [attr.y]=\"stack.labelPos.y\" [attr.text-anchor]=\"isVertical() ? 'middle' : 'end'\" [attr.dominant-baseline]=\"isVertical() ? 'auto' : 'central'\" class=\"lc-stacked-bar-chart__label\">{{ stack.label }}</text>\n }\n }\n </svg>\n\n @if (showLegend() && legendItems().length) {\n <div class=\"lc-stacked-bar-chart__legend\">\n @for (item of legendItems(); track $index) {\n <div class=\"lc-stacked-bar-chart__legend-item\">\n <span class=\"lc-stacked-bar-chart__legend-dot\" [style.background-color]=\"item.color\"></span>\n <span class=\"lc-stacked-bar-chart__legend-text\">{{ item.label }}</span>\n </div>\n }\n </div>\n }\n</div>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"lifecorner-angular-ui-kit-stacked-bar-chart.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/stacked-bar-chart/stacked-bar-chart.component.ts","../../../../libs/angular-ui-kit/src/lib/stacked-bar-chart/stacked-bar-chart.component.html"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n DestroyRef,\n ElementRef,\n afterNextRender,\n effect,\n inject,\n input,\n isDevMode,\n computed,\n signal,\n} from '@angular/core';\nimport { chartColor } from '@lifecorner/angular-ui-kit/internal/chart';\nimport {\n ChartValueFormatter,\n formatChartValue,\n niceScale,\n toFinite,\n} from '@lifecorner/angular-ui-kit/internal/chart';\n\nexport interface StackedBarCategory {\n label: string;\n /** One value per series. Negative values stack downward (left) from the zero baseline. */\n values: number[];\n}\n\nexport interface StackedBarLegend {\n label: string;\n color?: string;\n}\n\nexport type StackedBarOrientation = 'vertical' | 'horizontal';\n\n/**\n * Form of `lc-stacked-bar-chart` (3.36): `chart` (axes, grid, labels and\n * paddings, as before) or `band` (one horizontal bar edge to edge).\n */\nexport type StackedBarVariant = 'chart' | 'band';\n\n/** Percentages are rounded so the prerendered markup is the same everywhere. */\nconst roundPercent = (value: number): number => Math.round(value * 10000) / 10000;\n\n/** viewBox width used until the container has been measured. */\nconst DEFAULT_WIDTH = 400;\n\n/** Segments shorter than this along the value axis get no inline value label. */\nconst MIN_SEGMENT_LABEL_PX = 14;\n\n@Component({\n selector: 'lc-stacked-bar-chart',\n standalone: true,\n templateUrl: './stacked-bar-chart.component.html',\n styleUrls: ['./stacked-bar-chart.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n '[style.max-width.px]': 'width()',\n },\n})\n/**\n * Stacked bar chart component for comparing category compositions.\n *\n * Features:\n * - Multiple stacked value segments per category\n * - Vertical and horizontal orientation\n * - Negative values stacked downward from a zero baseline\n * - Optional legend, grid, value labels (segments + totals), and axis labels\n * - Configurable bar gap spacing\n * - Color-coded segments with legend mapping\n * - Responsive SVG rendering\n * - `variant=\"band\"` (3.36): one horizontal distribution bar edge to edge,\n * without axes, grid or paddings\n *\n * @example\n * ```html\n * <lc-stacked-bar-chart [categories]=\"data\" [legends]=\"legends\" [showLegend]=\"true\" />\n * ```\n */\nexport class StackedBarChartComponent {\n private readonly _el = inject(ElementRef);\n private readonly _destroyRef = inject(DestroyRef);\n private readonly _containerWidth = signal<number>(0);\n\n constructor() {\n afterNextRender(() => {\n const obs = new ResizeObserver(([entry]) => {\n this._containerWidth.set(entry.contentRect.width);\n });\n obs.observe(this._el.nativeElement);\n this._destroyRef.onDestroy(() => obs.disconnect());\n });\n if (isDevMode()) {\n effect(() => {\n const count = (this.categories() ?? []).length;\n if (this.variant() === 'band' && count !== 1) {\n console.warn(`[lc-stacked-bar-chart] variant=\"band\" needs exactly one category (got ${count}): rendering as chart`);\n }\n });\n }\n }\n\n /** Categories with stacked values. */\n categories = input.required<StackedBarCategory[]>();\n\n /** Legend items mapping to each value index. */\n legends = input<StackedBarLegend[]>([]);\n\n /**\n * Intrinsic chart width in pixels. The chart fills its container (the SVG is\n * `width=\"100%\"` and its viewBox follows the measured container width);\n * `width` is the viewBox width until the container has been measured and,\n * when set, the host's `max-width`, so the chart never grows past it. Leave\n * unset for a fully fluid chart.\n */\n width = input<number | undefined>(undefined);\n height = input<number>(200);\n orientation = input<StackedBarOrientation>('vertical');\n showLabels = input<boolean>(true);\n /** Show the value inside each segment (when it fits) and the total at the end of each stack. */\n showValues = input<boolean>(false);\n showLegend = input<boolean>(true);\n showGrid = input<boolean>(true);\n barGap = input<number>(0.3);\n\n /** Formats segment/total value labels and axis ticks. Defaults to a float-safe `String(value)`. */\n formatValue = input<ChartValueFormatter>(formatChartValue);\n\n /**\n * Accessible name of the chart. Defaults to a generated summary listing the\n * series names and every category's total.\n */\n ariaLabel = input<string>('');\n\n /**\n * Form of the chart (3.36, cockpit row 2). `chart`: axes, grid, labels and\n * paddings, as before. `band`: one horizontal bar that fills the host's\n * width edge to edge, `height` px thick, scaled from 0 to the sum of the\n * values, without axes, grid, labels, values or paddings; the parts are\n * split by a 2 px gap in the surface colour (CSS hooks\n * `--lc-stacked-bar-chart-band-gap`, default `2px`, and\n * `--lc-stacked-bar-chart-band-gap-color`, default `var(--color-surface)`:\n * set it to the band's background off a card). `ariaLabel`, `legends` and\n * the legend work as in `chart`. Needs exactly one category (otherwise it\n * renders as `chart`, with a warning in dev mode); `orientation` is\n * ignored; negative values draw nothing, and a sum of 0 draws no parts.\n * Part widths are percentages, so the band is exact in prerendered HTML.\n * @default 'chart'\n */\n variant = input<StackedBarVariant>('chart');\n\n /** `band` with exactly one category (3.36); otherwise the chart renders. */\n protected readonly isBand = computed(() => this.variant() === 'band' && (this.categories() ?? []).length === 1);\n\n /** The band's parts as percentages of the width: 0 … sum of the positive values. */\n protected readonly bandParts = computed(() => {\n if (!this.isBand()) return [];\n const legs = this.legends();\n const values = (this.categories()[0].values ?? []).map((raw) => Math.max(0, toFinite(raw)));\n const sum = values.reduce((a, b) => a + b, 0);\n if (sum <= 0) return [];\n let at = 0;\n return values.map((value, i) => {\n const x = (at / sum) * 100;\n at += value;\n return { x: roundPercent(x), w: roundPercent((value / sum) * 100), color: legs[i]?.color || chartColor(i) };\n });\n });\n\n /** The boundaries between two drawn parts, where the band's gap lines sit. */\n protected readonly bandGaps = computed(() => {\n const drawn = this.bandParts().filter((part) => part.w > 0);\n return drawn.slice(1).map((part) => part.x);\n });\n\n private readonly PL = 40;\n private readonly PR = 10;\n private readonly PT = 10;\n private readonly PB = 30;\n\n protected readonly effectiveWidth = computed(\n () => this._containerWidth() || this.width() || DEFAULT_WIDTH\n );\n\n protected readonly viewBox = computed(() => `0 0 ${this.effectiveWidth()} ${this.height()}`);\n\n protected readonly isVertical = computed(() => this.orientation() === 'vertical');\n\n private readonly plot = computed(() => ({\n x: this.PL,\n y: this.PT,\n w: this.effectiveWidth() - this.PL - this.PR,\n h: this.height() - this.PT - this.PB,\n }));\n\n /** Positive and negative stack extents per category (positives and negatives stack separately). */\n private readonly extents = computed(() =>\n (this.categories() ?? []).map((c) => {\n let pos = 0;\n let neg = 0;\n for (const raw of c.values ?? []) {\n const v = toFinite(raw);\n if (v >= 0) pos += v;\n else neg += v;\n }\n return { pos, neg, total: pos + neg };\n })\n );\n\n /**\n * One scale for grid *and* segments: nice tick bounds that always include\n * zero and extend below it when a stack goes negative.\n */\n protected readonly scale = computed(() => {\n const ext = this.extents();\n const max = Math.max(0, ...ext.map((e) => e.pos));\n const min = Math.min(0, ...ext.map((e) => e.neg));\n return niceScale(min, max, 4);\n });\n\n private valueToPos(value: number): number {\n const { min, max } = this.scale();\n const p = this.plot();\n const frac = (value - min) / (max - min || 1);\n return this.isVertical() ? p.y + p.h - frac * p.h : p.x + frac * p.w;\n }\n\n protected readonly gridLines = computed(() => {\n if (!this.showGrid() || !this.categories()?.length) return [];\n const fmt = this.formatValue();\n return this.scale().ticks.map((tick) => ({ pos: this.valueToPos(tick), label: fmt(tick) }));\n });\n\n protected readonly stacks = computed(() => {\n const cats = this.categories();\n const legs = this.legends();\n if (!cats?.length) return [];\n\n const p = this.plot();\n const h = this.height();\n const isV = this.isVertical();\n const gap = this.barGap();\n const fmt = this.formatValue();\n const ext = this.extents();\n\n const slot = (isV ? p.w : p.h) / cats.length;\n const thickness = slot * (1 - gap);\n const off = (slot - thickness) / 2;\n\n return cats.map((cat, ci) => {\n const along = (isV ? p.x : p.y) + ci * slot + off;\n let cumPos = 0;\n let cumNeg = 0;\n\n const segments = (cat.values ?? []).map((raw, vi) => {\n const val = toFinite(raw);\n const color = legs[vi]?.color || chartColor(vi);\n const from = val >= 0 ? cumPos : cumNeg;\n const to = from + val;\n if (val >= 0) cumPos = to;\n else cumNeg = to;\n\n const a = this.valueToPos(from);\n const b = this.valueToPos(to);\n const start = Math.min(a, b);\n const len = Math.abs(b - a);\n const rect = isV\n ? { x: along, y: start, w: thickness, h: len }\n : { x: start, y: along, w: len, h: thickness };\n return {\n ...rect,\n color,\n value: val,\n valueText: fmt(val),\n showValue: len >= MIN_SEGMENT_LABEL_PX,\n valueX: rect.x + rect.w / 2,\n valueY: rect.y + rect.h / 2,\n };\n });\n\n const total = ext[ci].total;\n const posEnd = this.valueToPos(ext[ci].pos);\n const labelPos = isV\n ? { x: p.x + (ci + 0.5) * slot, y: h - this.PB + 16 }\n : { x: p.x - 6, y: p.y + (ci + 0.5) * slot };\n // Total sits past the positive end of the stack (or at the baseline for all-negative stacks).\n const totalPos = isV\n ? { x: along + thickness / 2, y: posEnd - 6, anchor: 'middle' }\n : { x: posEnd + 6, y: along + thickness / 2, anchor: 'start' };\n\n return { label: cat.label, segments, labelPos, total, totalText: fmt(total), totalPos };\n });\n });\n\n protected readonly legendItems = computed(() => {\n const legs = this.legends();\n return legs.map((l, i) => ({\n label: l.label,\n color: l.color || chartColor(i),\n }));\n });\n\n /** Value axis along the plot edge plus the zero baseline (inside the plot when data goes negative). */\n protected readonly axisLine = computed(() => {\n const p = this.plot();\n const isV = this.isVertical();\n const zero = this.categories()?.length ? this.valueToPos(0) : (isV ? p.y + p.h : p.x);\n return {\n vx1: isV ? p.x : zero, vy1: p.y,\n vx2: isV ? p.x : zero, vy2: p.y + p.h,\n hx1: p.x, hy1: isV ? zero : p.y + p.h,\n hx2: p.x + p.w, hy2: isV ? zero : p.y + p.h,\n };\n });\n\n protected readonly plotBottom = computed(() => this.plot().y + this.plot().h);\n\n protected readonly effectiveAriaLabel = computed(() => {\n const explicit = this.ariaLabel();\n if (explicit) return explicit;\n const cats = this.categories() ?? [];\n if (!cats.length) return 'Stacked bar chart: no data';\n const fmt = this.formatValue();\n const ext = this.extents();\n const seriesNames = this.legends().map((l) => l.label).filter(Boolean);\n const series = seriesNames.length ? ` (${seriesNames.join(', ')})` : '';\n const totals = cats.map((c, i) => `${c.label} ${fmt(ext[i].total)}`).join(', ');\n return `Stacked bar chart${series}: ${totals}`;\n });\n}\n","<div class=\"lc-stacked-bar-chart\">\n @if (isBand()) {\n <!-- 3.36 band: one bar edge to edge, its parts as % of the width (exact before JS, no viewBox). -->\n <svg\n width=\"100%\"\n [attr.height]=\"height()\"\n role=\"img\"\n [attr.aria-label]=\"effectiveAriaLabel()\"\n class=\"lc-stacked-bar-chart__svg lc-stacked-bar-chart__svg--band\"\n >\n <title>{{ effectiveAriaLabel() }}</title>\n @for (part of bandParts(); track $index) {\n <rect [attr.x]=\"part.x + '%'\" y=\"0\" [attr.width]=\"part.w + '%'\" height=\"100%\" [attr.fill]=\"part.color\" class=\"lc-stacked-bar-chart__segment\" />\n }\n @for (gap of bandGaps(); track $index) {\n <line [attr.x1]=\"gap + '%'\" y1=\"0\" [attr.x2]=\"gap + '%'\" y2=\"100%\" class=\"lc-stacked-bar-chart__band-gap\" />\n }\n </svg>\n } @else {\n <svg\n width=\"100%\"\n [attr.height]=\"height()\"\n [attr.viewBox]=\"viewBox()\"\n role=\"img\"\n [attr.aria-label]=\"effectiveAriaLabel()\"\n class=\"lc-stacked-bar-chart__svg\"\n >\n <title>{{ effectiveAriaLabel() }}</title>\n\n @if (showGrid()) {\n @for (line of gridLines(); track line.pos) {\n @if (isVertical()) {\n <line [attr.x1]=\"axisLine().hx1\" [attr.y1]=\"line.pos\" [attr.x2]=\"axisLine().hx2\" [attr.y2]=\"line.pos\" class=\"lc-stacked-bar-chart__grid\" />\n <text [attr.x]=\"axisLine().vx1 - 6\" [attr.y]=\"line.pos\" text-anchor=\"end\" dominant-baseline=\"central\" class=\"lc-stacked-bar-chart__grid-label\">{{ line.label }}</text>\n } @else {\n <line [attr.x1]=\"line.pos\" [attr.y1]=\"axisLine().vy1\" [attr.x2]=\"line.pos\" [attr.y2]=\"axisLine().vy2\" class=\"lc-stacked-bar-chart__grid\" />\n <text [attr.x]=\"line.pos\" [attr.y]=\"plotBottom() + 12\" text-anchor=\"middle\" class=\"lc-stacked-bar-chart__grid-label\">{{ line.label }}</text>\n }\n }\n }\n\n <line [attr.x1]=\"axisLine().vx1\" [attr.y1]=\"axisLine().vy1\" [attr.x2]=\"axisLine().vx2\" [attr.y2]=\"axisLine().vy2\" class=\"lc-stacked-bar-chart__axis\" />\n <line [attr.x1]=\"axisLine().hx1\" [attr.y1]=\"axisLine().hy1\" [attr.x2]=\"axisLine().hx2\" [attr.y2]=\"axisLine().hy2\" class=\"lc-stacked-bar-chart__axis\" />\n\n @for (stack of stacks(); track $index) {\n @for (seg of stack.segments; track $index) {\n <rect [attr.x]=\"seg.x\" [attr.y]=\"seg.y\" [attr.width]=\"seg.w\" [attr.height]=\"seg.h\" [attr.fill]=\"seg.color\" class=\"lc-stacked-bar-chart__segment\" />\n @if (showValues() && seg.showValue) {\n <text [attr.x]=\"seg.valueX\" [attr.y]=\"seg.valueY\" text-anchor=\"middle\" dominant-baseline=\"central\" class=\"lc-stacked-bar-chart__segment-value\">{{ seg.valueText }}</text>\n }\n }\n @if (showValues()) {\n <text [attr.x]=\"stack.totalPos.x\" [attr.y]=\"stack.totalPos.y\" [attr.text-anchor]=\"stack.totalPos.anchor\" [attr.dominant-baseline]=\"isVertical() ? 'auto' : 'central'\" class=\"lc-stacked-bar-chart__total\">{{ stack.totalText }}</text>\n }\n @if (showLabels()) {\n <text [attr.x]=\"stack.labelPos.x\" [attr.y]=\"stack.labelPos.y\" [attr.text-anchor]=\"isVertical() ? 'middle' : 'end'\" [attr.dominant-baseline]=\"isVertical() ? 'auto' : 'central'\" class=\"lc-stacked-bar-chart__label\">{{ stack.label }}</text>\n }\n }\n </svg>\n }\n\n @if (showLegend() && legendItems().length) {\n <div class=\"lc-stacked-bar-chart__legend\">\n @for (item of legendItems(); track $index) {\n <div class=\"lc-stacked-bar-chart__legend-item\">\n <span class=\"lc-stacked-bar-chart__legend-dot\" [style.background-color]=\"item.color\"></span>\n <span class=\"lc-stacked-bar-chart__legend-text\">{{ item.label }}</span>\n </div>\n }\n </div>\n }\n</div>\n"],"mappings":"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"}
|