@lifecorner/angular-ui-kit 3.22.3 → 3.23.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.
@@ -221,7 +221,7 @@ var AccordionComponent = class AccordionComponent {
221
221
  }],
222
222
  ngImport: i0,
223
223
  template: "<div [class]=\"accordionClasses()\">\n <!-- The heading wrapper gives the disclosure button a heading level (WAI-ARIA accordion pattern). -->\n <div class=\"lc-accordion__heading\" role=\"heading\" [attr.aria-level]=\"headingLevel()\">\n <button\n class=\"lc-accordion__header\"\n type=\"button\"\n [id]=\"headerId\"\n [attr.aria-expanded]=\"expanded()\"\n [attr.aria-controls]=\"panelId\"\n [attr.aria-label]=\"headerAriaLabel()\"\n [attr.aria-labelledby]=\"headerLabelledBy()\"\n [attr.aria-disabled]=\"disabled()\"\n [disabled]=\"disabled()\"\n (click)=\"toggle()\"\n (keydown)=\"onKeydown($event)\"\n >\n @if (headerTemplate(); as header) {\n <!-- The label starts the name and the template's text follows it, so\n the name contains what is shown (WCAG 2.5.3). -->\n @if (richHeaderLabel()) {\n <span hidden [id]=\"headerId + '-label'\">{{ richHeaderLabel() }},</span>\n }\n <span class=\"lc-accordion__header-content\" [attr.id]=\"richHeaderLabel() ? headerId + '-content' : null\">\n <ng-container *ngTemplateOutlet=\"header.template\"></ng-container>\n </span>\n } @else {\n <span class=\"lc-accordion__title\">{{ title() }}</span>\n }\n <lc-icon\n class=\"lc-accordion__icon\"\n name=\"chevron-down\"\n size=\"sm\"\n [decorative]=\"true\"\n ></lc-icon>\n </button>\n </div>\n\n <!--\n A collapsed panel is `inert`: its eagerly projected body stays in the DOM\n (for the height animation) but is neither focusable nor exposed to AT.\n -->\n <div\n class=\"lc-accordion__panel\"\n [id]=\"panelId\"\n role=\"region\"\n [attr.aria-labelledby]=\"headerLabelledBy() ?? headerId\"\n [attr.aria-hidden]=\"!expanded()\"\n [attr.inert]=\"expanded() ? null : ''\"\n >\n <div class=\"lc-accordion__body\">\n @if (contentTemplate(); as content) {\n @if (shouldRenderBody()) {\n <ng-container *ngTemplateOutlet=\"content.template\"></ng-container>\n }\n } @else {\n <ng-content></ng-content>\n }\n </div>\n </div>\n</div>\n",
224
- styles: [":host{display:block}.lc-accordion{border-radius:var(--border-radius-lg);overflow:hidden;transition:box-shadow .2s ease,border-color .2s ease}.lc-accordion--outlined{border:1px solid var(--lc-accordion-border, var(--color-border));background:var(--color-surface);box-shadow:var(--elevation-1)}.lc-accordion--outlined:hover:not(.lc-accordion--disabled){box-shadow:var(--elevation-1)}.lc-accordion--flat{border:none;background:transparent}.lc-accordion--disabled{opacity:.5;pointer-events:none}.lc-accordion__heading{display:block;margin:0}.lc-accordion__header{display:flex;width:100%;align-items:center;justify-content:space-between;gap:.75rem;padding:var(--lc-accordion-header-padding, .875rem 1.125rem);font-weight:600;letter-spacing:-.01em;color:var(--lc-accordion-header-fg, var(--color-text-primary));background:var(--lc-accordion-header-bg, transparent);border:var(--lc-accordion-header-border, none);border-radius:0;cursor:pointer;transition:background-color .15s ease,color .15s ease}.lc-accordion__header:hover{background-color:var(--lc-accordion-header-hover-bg, var(--color-surface-hover))}.lc-accordion__header:focus-visible{outline:2px solid var(--color-primary);outline-offset:-2px;border-radius:var(--border-radius-md)}.lc-accordion__header:disabled{cursor:not-allowed}.lc-accordion--sm .lc-accordion__header{padding:var(--lc-accordion-header-padding, .625rem .875rem);font-size:.8125rem}.lc-accordion--md .lc-accordion__header{padding:var(--lc-accordion-header-padding, .875rem 1.125rem);font-size:.9375rem}.lc-accordion--lg .lc-accordion__header{padding:var(--lc-accordion-header-padding, 1.125rem 1.375rem);font-size:1.0625rem}.lc-accordion__title{flex:1;text-align:left}.lc-accordion__header-content{display:flex;flex:1;min-width:0;align-items:center;gap:.75rem;text-align:left;font-weight:inherit}.lc-accordion__icon{flex-shrink:0;color:var(--color-text-secondary);transition:transform .3s cubic-bezier(.25,.46,.45,.94),color .2s ease}.lc-accordion--chevron-leading .lc-accordion__icon{order:-1}.lc-accordion__header:hover .lc-accordion__icon{color:var(--color-text-primary)}.lc-accordion--expanded .lc-accordion__icon{transform:rotate(-180deg)}.lc-accordion__panel{display:grid;grid-template-rows:0fr;transition:grid-template-rows .3s cubic-bezier(.25,.46,.45,.94);overflow:hidden}.lc-accordion--expanded .lc-accordion__panel{grid-template-rows:1fr}.lc-accordion__body{min-height:0;overflow:hidden;padding:0 1.125rem;color:var(--color-text-secondary);font-size:.875rem;line-height:1.6;opacity:0;transform:translateY(-4px);visibility:hidden;transition:opacity .2s ease 50ms,transform .2s ease 50ms,padding .3s cubic-bezier(.25,.46,.45,.94),visibility 0s linear .3s}.lc-accordion--expanded .lc-accordion__body{padding:var(--lc-accordion-body-padding, .875rem 1.125rem 1rem);opacity:1;transform:translateY(0);visibility:visible;transition:opacity .25s ease 80ms,transform .25s ease 80ms,padding .3s cubic-bezier(.25,.46,.45,.94),visibility 0s}.lc-accordion--sm .lc-accordion__body{padding:0 .875rem;font-size:.8125rem}.lc-accordion--sm.lc-accordion--expanded .lc-accordion__body{padding:var(--lc-accordion-body-padding, .625rem .875rem .75rem)}.lc-accordion--lg .lc-accordion__body{padding:0 1.375rem;font-size:.9375rem}.lc-accordion--lg.lc-accordion--expanded .lc-accordion__body{padding:var(--lc-accordion-body-padding, 1rem 1.375rem 1.25rem)}\n"],
224
+ styles: [":host{display:block}.lc-accordion{border-radius:var(--border-radius-lg);overflow:hidden;transition:box-shadow .2s ease,border-color .2s ease}.lc-accordion--outlined{border:1px solid var(--lc-accordion-border, var(--color-border));background:var(--color-surface);box-shadow:var(--elevation-1)}.lc-accordion--outlined:hover:not(.lc-accordion--disabled){box-shadow:var(--elevation-1)}.lc-accordion--flat{border:none;background:transparent}.lc-accordion--disabled{opacity:.5;pointer-events:none}.lc-accordion__heading{display:block;margin:0}.lc-accordion__header{display:flex;width:100%;align-items:center;justify-content:space-between;gap:.75rem;padding:var(--lc-accordion-header-padding, .875rem 1.125rem);font-weight:600;letter-spacing:-.01em;color:var(--lc-accordion-header-fg, var(--color-text-primary));background:var(--lc-accordion-header-bg, transparent);border:var(--lc-accordion-header-border, none);border-radius:0;cursor:pointer;transition:background-color .15s ease,color .15s ease}.lc-accordion__header:hover{background-color:var(--lc-accordion-header-hover-bg, var(--color-surface-hover))}.lc-accordion__header:focus-visible{outline:2px solid var(--color-primary);outline-offset:-2px;border-radius:var(--border-radius-md)}.lc-accordion__header:disabled{cursor:not-allowed}.lc-accordion--sm .lc-accordion__header{padding:var(--lc-accordion-header-padding, .625rem .875rem);font-size:.8125rem}.lc-accordion--md .lc-accordion__header{padding:var(--lc-accordion-header-padding, .875rem 1.125rem);font-size:.9375rem}.lc-accordion--lg .lc-accordion__header{padding:var(--lc-accordion-header-padding, 1.125rem 1.375rem);font-size:1.0625rem}.lc-accordion__title{flex:1;text-align:left}.lc-accordion__header-content{display:flex;flex:1;min-width:0;align-items:center;gap:.75rem;text-align:left;font-weight:inherit}.lc-accordion__icon{flex-shrink:0;color:var(--color-text-secondary);transition:transform .3s cubic-bezier(.25,.46,.45,.94),color .2s ease}.lc-accordion--chevron-leading .lc-accordion__icon{order:-1}.lc-accordion__header:hover .lc-accordion__icon{color:var(--color-text-primary)}.lc-accordion--expanded .lc-accordion__icon{transform:rotate(-180deg)}.lc-accordion__panel{display:grid;grid-template-rows:0fr;transition:grid-template-rows .3s cubic-bezier(.25,.46,.45,.94);overflow:hidden}.lc-accordion--expanded .lc-accordion__panel{grid-template-rows:1fr}.lc-accordion__body{min-height:0;overflow:hidden;padding:0 1.125rem;color:var(--color-text-secondary);font-size:.875rem;line-height:1.6;opacity:0;transform:translateY(-4px);visibility:hidden;transition:opacity .2s ease 50ms,transform .2s ease 50ms,padding .3s cubic-bezier(.25,.46,.45,.94),visibility 0s linear .3s}.lc-accordion--expanded .lc-accordion__body{padding:var(--lc-accordion-body-padding, .875rem 1.125rem 1rem);opacity:1;transform:translateY(0);visibility:visible;transition:opacity .25s ease 80ms,transform .25s ease 80ms,padding .3s cubic-bezier(.25,.46,.45,.94),visibility 0s}.lc-accordion--sm .lc-accordion__body{padding:0 .875rem;font-size:.8125rem}.lc-accordion--sm.lc-accordion--expanded .lc-accordion__body{padding:var(--lc-accordion-body-padding, .625rem .875rem .75rem)}.lc-accordion--lg .lc-accordion__body{padding:0 1.375rem;font-size:.9375rem}.lc-accordion--lg.lc-accordion--expanded .lc-accordion__body{padding:var(--lc-accordion-body-padding, 1rem 1.375rem 1.25rem)}.lc-accordion--plain{border:none;background:transparent;overflow:visible}.lc-accordion--plain .lc-accordion__header{position:relative;isolation:isolate;min-height:var(--size-min-touch-height, 2.75rem);background:transparent;border:none}.lc-accordion--plain.lc-accordion--sm .lc-accordion__header{padding:var(--lc-accordion-header-padding, .625rem 0)}.lc-accordion--plain.lc-accordion--md .lc-accordion__header{padding:var(--lc-accordion-header-padding, .875rem 0)}.lc-accordion--plain.lc-accordion--lg .lc-accordion__header{padding:var(--lc-accordion-header-padding, 1.125rem 0)}.lc-accordion--plain .lc-accordion__header:hover{background-color:transparent}.lc-accordion--plain .lc-accordion__header:focus-visible{outline:none}.lc-accordion--plain .lc-accordion__header:before{content:\"\";position:absolute;inset-block:0;inset-inline:calc(-1 * var(--lc-accordion-header-bleed, var(--spacing-2)));z-index:-1;border:var(--lc-accordion-header-border, none);border-radius:var(--border-radius-md);background:var(--lc-accordion-header-bg, transparent);transition:background-color .15s ease}.lc-accordion--plain .lc-accordion__header:hover:before{background-color:var(--lc-accordion-header-hover-bg, var(--color-surface-hover))}.lc-accordion--plain .lc-accordion__header:focus-visible:before{outline:2px solid var(--color-primary);outline-offset:-2px}.lc-accordion--plain .lc-accordion__title{min-width:0;overflow-wrap:break-word;hyphens:auto;hyphenate-limit-chars:12 4 3;text-align:start}.lc-accordion--plain .lc-accordion__panel{overflow-x:visible;overflow-y:clip}.lc-accordion--plain .lc-accordion__body{padding:0;overflow-x:visible;overflow-y:clip}.lc-accordion--plain.lc-accordion--sm.lc-accordion--expanded .lc-accordion__body{padding:var(--lc-accordion-body-padding, .625rem 0 .75rem)}.lc-accordion--plain.lc-accordion--md.lc-accordion--expanded .lc-accordion__body{padding:var(--lc-accordion-body-padding, .875rem 0 1rem)}.lc-accordion--plain.lc-accordion--lg.lc-accordion--expanded .lc-accordion__body{padding:var(--lc-accordion-body-padding, 1rem 0 1.25rem)}@media(prefers-reduced-motion:reduce){.lc-accordion--plain .lc-accordion__header,.lc-accordion--plain .lc-accordion__header:before,.lc-accordion--plain .lc-accordion__icon,.lc-accordion--plain .lc-accordion__panel,.lc-accordion--plain .lc-accordion__body{transition:none}}@media(forced-colors:active){.lc-accordion--plain .lc-accordion__header:focus-visible:before{outline-color:Highlight}}\n"],
225
225
  dependencies: [{
226
226
  kind: "component",
227
227
  type: IconComponent,
@@ -263,7 +263,7 @@ i0.ɵɵngDeclareClassMetadata({
263
263
  viewProviders: [ɵprovideKitIcons([ICON_CHEVRON_DOWN])],
264
264
  changeDetection: ChangeDetectionStrategy.OnPush,
265
265
  template: "<div [class]=\"accordionClasses()\">\n <!-- The heading wrapper gives the disclosure button a heading level (WAI-ARIA accordion pattern). -->\n <div class=\"lc-accordion__heading\" role=\"heading\" [attr.aria-level]=\"headingLevel()\">\n <button\n class=\"lc-accordion__header\"\n type=\"button\"\n [id]=\"headerId\"\n [attr.aria-expanded]=\"expanded()\"\n [attr.aria-controls]=\"panelId\"\n [attr.aria-label]=\"headerAriaLabel()\"\n [attr.aria-labelledby]=\"headerLabelledBy()\"\n [attr.aria-disabled]=\"disabled()\"\n [disabled]=\"disabled()\"\n (click)=\"toggle()\"\n (keydown)=\"onKeydown($event)\"\n >\n @if (headerTemplate(); as header) {\n <!-- The label starts the name and the template's text follows it, so\n the name contains what is shown (WCAG 2.5.3). -->\n @if (richHeaderLabel()) {\n <span hidden [id]=\"headerId + '-label'\">{{ richHeaderLabel() }},</span>\n }\n <span class=\"lc-accordion__header-content\" [attr.id]=\"richHeaderLabel() ? headerId + '-content' : null\">\n <ng-container *ngTemplateOutlet=\"header.template\"></ng-container>\n </span>\n } @else {\n <span class=\"lc-accordion__title\">{{ title() }}</span>\n }\n <lc-icon\n class=\"lc-accordion__icon\"\n name=\"chevron-down\"\n size=\"sm\"\n [decorative]=\"true\"\n ></lc-icon>\n </button>\n </div>\n\n <!--\n A collapsed panel is `inert`: its eagerly projected body stays in the DOM\n (for the height animation) but is neither focusable nor exposed to AT.\n -->\n <div\n class=\"lc-accordion__panel\"\n [id]=\"panelId\"\n role=\"region\"\n [attr.aria-labelledby]=\"headerLabelledBy() ?? headerId\"\n [attr.aria-hidden]=\"!expanded()\"\n [attr.inert]=\"expanded() ? null : ''\"\n >\n <div class=\"lc-accordion__body\">\n @if (contentTemplate(); as content) {\n @if (shouldRenderBody()) {\n <ng-container *ngTemplateOutlet=\"content.template\"></ng-container>\n }\n } @else {\n <ng-content></ng-content>\n }\n </div>\n </div>\n</div>\n",
266
- styles: [":host{display:block}.lc-accordion{border-radius:var(--border-radius-lg);overflow:hidden;transition:box-shadow .2s ease,border-color .2s ease}.lc-accordion--outlined{border:1px solid var(--lc-accordion-border, var(--color-border));background:var(--color-surface);box-shadow:var(--elevation-1)}.lc-accordion--outlined:hover:not(.lc-accordion--disabled){box-shadow:var(--elevation-1)}.lc-accordion--flat{border:none;background:transparent}.lc-accordion--disabled{opacity:.5;pointer-events:none}.lc-accordion__heading{display:block;margin:0}.lc-accordion__header{display:flex;width:100%;align-items:center;justify-content:space-between;gap:.75rem;padding:var(--lc-accordion-header-padding, .875rem 1.125rem);font-weight:600;letter-spacing:-.01em;color:var(--lc-accordion-header-fg, var(--color-text-primary));background:var(--lc-accordion-header-bg, transparent);border:var(--lc-accordion-header-border, none);border-radius:0;cursor:pointer;transition:background-color .15s ease,color .15s ease}.lc-accordion__header:hover{background-color:var(--lc-accordion-header-hover-bg, var(--color-surface-hover))}.lc-accordion__header:focus-visible{outline:2px solid var(--color-primary);outline-offset:-2px;border-radius:var(--border-radius-md)}.lc-accordion__header:disabled{cursor:not-allowed}.lc-accordion--sm .lc-accordion__header{padding:var(--lc-accordion-header-padding, .625rem .875rem);font-size:.8125rem}.lc-accordion--md .lc-accordion__header{padding:var(--lc-accordion-header-padding, .875rem 1.125rem);font-size:.9375rem}.lc-accordion--lg .lc-accordion__header{padding:var(--lc-accordion-header-padding, 1.125rem 1.375rem);font-size:1.0625rem}.lc-accordion__title{flex:1;text-align:left}.lc-accordion__header-content{display:flex;flex:1;min-width:0;align-items:center;gap:.75rem;text-align:left;font-weight:inherit}.lc-accordion__icon{flex-shrink:0;color:var(--color-text-secondary);transition:transform .3s cubic-bezier(.25,.46,.45,.94),color .2s ease}.lc-accordion--chevron-leading .lc-accordion__icon{order:-1}.lc-accordion__header:hover .lc-accordion__icon{color:var(--color-text-primary)}.lc-accordion--expanded .lc-accordion__icon{transform:rotate(-180deg)}.lc-accordion__panel{display:grid;grid-template-rows:0fr;transition:grid-template-rows .3s cubic-bezier(.25,.46,.45,.94);overflow:hidden}.lc-accordion--expanded .lc-accordion__panel{grid-template-rows:1fr}.lc-accordion__body{min-height:0;overflow:hidden;padding:0 1.125rem;color:var(--color-text-secondary);font-size:.875rem;line-height:1.6;opacity:0;transform:translateY(-4px);visibility:hidden;transition:opacity .2s ease 50ms,transform .2s ease 50ms,padding .3s cubic-bezier(.25,.46,.45,.94),visibility 0s linear .3s}.lc-accordion--expanded .lc-accordion__body{padding:var(--lc-accordion-body-padding, .875rem 1.125rem 1rem);opacity:1;transform:translateY(0);visibility:visible;transition:opacity .25s ease 80ms,transform .25s ease 80ms,padding .3s cubic-bezier(.25,.46,.45,.94),visibility 0s}.lc-accordion--sm .lc-accordion__body{padding:0 .875rem;font-size:.8125rem}.lc-accordion--sm.lc-accordion--expanded .lc-accordion__body{padding:var(--lc-accordion-body-padding, .625rem .875rem .75rem)}.lc-accordion--lg .lc-accordion__body{padding:0 1.375rem;font-size:.9375rem}.lc-accordion--lg.lc-accordion--expanded .lc-accordion__body{padding:var(--lc-accordion-body-padding, 1rem 1.375rem 1.25rem)}\n"]
266
+ styles: [":host{display:block}.lc-accordion{border-radius:var(--border-radius-lg);overflow:hidden;transition:box-shadow .2s ease,border-color .2s ease}.lc-accordion--outlined{border:1px solid var(--lc-accordion-border, var(--color-border));background:var(--color-surface);box-shadow:var(--elevation-1)}.lc-accordion--outlined:hover:not(.lc-accordion--disabled){box-shadow:var(--elevation-1)}.lc-accordion--flat{border:none;background:transparent}.lc-accordion--disabled{opacity:.5;pointer-events:none}.lc-accordion__heading{display:block;margin:0}.lc-accordion__header{display:flex;width:100%;align-items:center;justify-content:space-between;gap:.75rem;padding:var(--lc-accordion-header-padding, .875rem 1.125rem);font-weight:600;letter-spacing:-.01em;color:var(--lc-accordion-header-fg, var(--color-text-primary));background:var(--lc-accordion-header-bg, transparent);border:var(--lc-accordion-header-border, none);border-radius:0;cursor:pointer;transition:background-color .15s ease,color .15s ease}.lc-accordion__header:hover{background-color:var(--lc-accordion-header-hover-bg, var(--color-surface-hover))}.lc-accordion__header:focus-visible{outline:2px solid var(--color-primary);outline-offset:-2px;border-radius:var(--border-radius-md)}.lc-accordion__header:disabled{cursor:not-allowed}.lc-accordion--sm .lc-accordion__header{padding:var(--lc-accordion-header-padding, .625rem .875rem);font-size:.8125rem}.lc-accordion--md .lc-accordion__header{padding:var(--lc-accordion-header-padding, .875rem 1.125rem);font-size:.9375rem}.lc-accordion--lg .lc-accordion__header{padding:var(--lc-accordion-header-padding, 1.125rem 1.375rem);font-size:1.0625rem}.lc-accordion__title{flex:1;text-align:left}.lc-accordion__header-content{display:flex;flex:1;min-width:0;align-items:center;gap:.75rem;text-align:left;font-weight:inherit}.lc-accordion__icon{flex-shrink:0;color:var(--color-text-secondary);transition:transform .3s cubic-bezier(.25,.46,.45,.94),color .2s ease}.lc-accordion--chevron-leading .lc-accordion__icon{order:-1}.lc-accordion__header:hover .lc-accordion__icon{color:var(--color-text-primary)}.lc-accordion--expanded .lc-accordion__icon{transform:rotate(-180deg)}.lc-accordion__panel{display:grid;grid-template-rows:0fr;transition:grid-template-rows .3s cubic-bezier(.25,.46,.45,.94);overflow:hidden}.lc-accordion--expanded .lc-accordion__panel{grid-template-rows:1fr}.lc-accordion__body{min-height:0;overflow:hidden;padding:0 1.125rem;color:var(--color-text-secondary);font-size:.875rem;line-height:1.6;opacity:0;transform:translateY(-4px);visibility:hidden;transition:opacity .2s ease 50ms,transform .2s ease 50ms,padding .3s cubic-bezier(.25,.46,.45,.94),visibility 0s linear .3s}.lc-accordion--expanded .lc-accordion__body{padding:var(--lc-accordion-body-padding, .875rem 1.125rem 1rem);opacity:1;transform:translateY(0);visibility:visible;transition:opacity .25s ease 80ms,transform .25s ease 80ms,padding .3s cubic-bezier(.25,.46,.45,.94),visibility 0s}.lc-accordion--sm .lc-accordion__body{padding:0 .875rem;font-size:.8125rem}.lc-accordion--sm.lc-accordion--expanded .lc-accordion__body{padding:var(--lc-accordion-body-padding, .625rem .875rem .75rem)}.lc-accordion--lg .lc-accordion__body{padding:0 1.375rem;font-size:.9375rem}.lc-accordion--lg.lc-accordion--expanded .lc-accordion__body{padding:var(--lc-accordion-body-padding, 1rem 1.375rem 1.25rem)}.lc-accordion--plain{border:none;background:transparent;overflow:visible}.lc-accordion--plain .lc-accordion__header{position:relative;isolation:isolate;min-height:var(--size-min-touch-height, 2.75rem);background:transparent;border:none}.lc-accordion--plain.lc-accordion--sm .lc-accordion__header{padding:var(--lc-accordion-header-padding, .625rem 0)}.lc-accordion--plain.lc-accordion--md .lc-accordion__header{padding:var(--lc-accordion-header-padding, .875rem 0)}.lc-accordion--plain.lc-accordion--lg .lc-accordion__header{padding:var(--lc-accordion-header-padding, 1.125rem 0)}.lc-accordion--plain .lc-accordion__header:hover{background-color:transparent}.lc-accordion--plain .lc-accordion__header:focus-visible{outline:none}.lc-accordion--plain .lc-accordion__header:before{content:\"\";position:absolute;inset-block:0;inset-inline:calc(-1 * var(--lc-accordion-header-bleed, var(--spacing-2)));z-index:-1;border:var(--lc-accordion-header-border, none);border-radius:var(--border-radius-md);background:var(--lc-accordion-header-bg, transparent);transition:background-color .15s ease}.lc-accordion--plain .lc-accordion__header:hover:before{background-color:var(--lc-accordion-header-hover-bg, var(--color-surface-hover))}.lc-accordion--plain .lc-accordion__header:focus-visible:before{outline:2px solid var(--color-primary);outline-offset:-2px}.lc-accordion--plain .lc-accordion__title{min-width:0;overflow-wrap:break-word;hyphens:auto;hyphenate-limit-chars:12 4 3;text-align:start}.lc-accordion--plain .lc-accordion__panel{overflow-x:visible;overflow-y:clip}.lc-accordion--plain .lc-accordion__body{padding:0;overflow-x:visible;overflow-y:clip}.lc-accordion--plain.lc-accordion--sm.lc-accordion--expanded .lc-accordion__body{padding:var(--lc-accordion-body-padding, .625rem 0 .75rem)}.lc-accordion--plain.lc-accordion--md.lc-accordion--expanded .lc-accordion__body{padding:var(--lc-accordion-body-padding, .875rem 0 1rem)}.lc-accordion--plain.lc-accordion--lg.lc-accordion--expanded .lc-accordion__body{padding:var(--lc-accordion-body-padding, 1rem 0 1.25rem)}@media(prefers-reduced-motion:reduce){.lc-accordion--plain .lc-accordion__header,.lc-accordion--plain .lc-accordion__header:before,.lc-accordion--plain .lc-accordion__icon,.lc-accordion--plain .lc-accordion__panel,.lc-accordion--plain .lc-accordion__body{transition:none}}@media(forced-colors:active){.lc-accordion--plain .lc-accordion__header:focus-visible:before{outline-color:Highlight}}\n"]
267
267
  }]
268
268
  }],
269
269
  ctorParameters: () => [],
@@ -1 +1 @@
1
- {"version":3,"file":"lifecorner-angular-ui-kit-accordion.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/accordion/accordion-header.directive.ts","../../../../libs/angular-ui-kit/src/lib/accordion/accordion-content.directive.ts","../../../../libs/angular-ui-kit/src/lib/accordion/accordion.component.ts","../../../../libs/angular-ui-kit/src/lib/accordion/accordion.component.html","../../../../libs/angular-ui-kit/src/lib/accordion/accordion-group.component.ts"],"sourcesContent":["import { Directive, TemplateRef, inject } from '@angular/core';\n\n/**\n * Marks an `<ng-template>` as the rich header content for an `<lc-accordion>`.\n *\n * When present, the projected template is rendered inside the header button\n * instead of the plain `title` string. The accordion still owns the disclosure\n * chevron, click/keyboard handling and focus ring — the consumer only supplies\n * the (non-interactive) inner content.\n *\n * A11y constraint: the header is itself a `<button>`, so the template must not\n * contain nested interactive elements (no button/link/input). Text, badges and\n * other non-interactive nodes only.\n *\n * Name (WCAG 2.5.3 Label in Name): the header is named by the template's\n * text; the accordion's `ariaLabel` (or `title`), if set, comes first through\n * `aria-labelledby`. Separate the template's parts with whitespace\n * (`&ngsp;`), so they read as words (\"Item label Done 12:04\"), and set a\n * label only for context that the template doesn't show.\n *\n * @example\n * ```html\n * <lc-accordion variant=\"flat\" chevronPosition=\"leading\">\n * <ng-template lcAccordionHeader>\n * <span class=\"title\">Item label</span>&ngsp;\n * <lc-badge variant=\"success\" size=\"sm\">Done</lc-badge>&ngsp;\n * <span style=\"margin-left: auto;\">12:04</span>\n * </ng-template>\n * <div>Body…</div>\n * </lc-accordion>\n * ```\n */\n@Directive({\n selector: '[lcAccordionHeader]',\n standalone: true,\n})\nexport class AccordionHeaderDirective {\n public readonly template = inject(TemplateRef<unknown>);\n}\n","import { Directive, TemplateRef, inject } from '@angular/core';\n\n/**\n * Marks an `<ng-template>` as the (optionally lazy) body content for an\n * `<lc-accordion>`.\n *\n * Unlike default `<ng-content>` projection — which Angular always instantiates\n * eagerly — a template referenced through this directive is only stamped out\n * when the accordion decides to render its body. That is what makes\n * `[lazy]=\"true\"` and `[destroyOnClose]=\"true\"` possible: expensive children\n * (HTTP-backed panels, charts, live-polling views) are not created until the\n * panel is first opened.\n *\n * @example\n * ```html\n * <lc-accordion [lazy]=\"true\">\n * <ng-template lcAccordionContent>\n * <expensive-panel [id]=\"id\" />\n * </ng-template>\n * </lc-accordion>\n * ```\n */\n@Directive({\n selector: '[lcAccordionContent]',\n standalone: true,\n})\nexport class AccordionContentDirective {\n public readonly template = inject(TemplateRef<unknown>);\n}\n","import {\n Component,\n ChangeDetectionStrategy,\n input,\n model,\n computed,\n signal,\n contentChild,\n effect,\n} from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport { IconComponent, ɵprovideKitIcons } from '@lifecorner/angular-ui-kit/icon';\nimport { ICON_CHEVRON_DOWN } from '@lifecorner/angular-ui-kit/icons';\nimport { AccordionHeaderDirective } from './accordion-header.directive';\nimport { AccordionContentDirective } from './accordion-content.directive';\nimport { nameWithText } from '@lifecorner/angular-ui-kit/internal/a11y';\n\n/** Unique id source for header/panel wiring (aria-controls / aria-labelledby). */\nlet accordionUid = 0;\n\n/** Where the disclosure chevron sits relative to the header content. */\nexport type AccordionChevronPosition = 'leading' | 'trailing';\n\n/**\n * Accordion component for collapsible content sections.\n *\n * Features:\n * - Expandable/collapsible content panels\n * - Two-way binding for expanded state\n * - Animated expand/collapse transitions\n * - Plain-string `title` **or** a rich projected header (`lcAccordionHeader`)\n * - Eager `<ng-content>` body **or** lazy/destroyable template body\n * (`lcAccordionContent` + `[lazy]` / `[destroyOnClose]`)\n * - Accessible: header is a real `<button>` inside a heading with\n * `aria-expanded` / `aria-controls`, keyboard support and a visible focus\n * ring; the collapsed panel is `inert` (not focusable, hidden from AT)\n *\n * @example Plain (unchanged, fully backward compatible)\n * ```html\n * <lc-accordion title=\"Section Title\" [(expanded)]=\"isOpen\">\n * <p>Collapsible content here</p>\n * </lc-accordion>\n * ```\n *\n * @example Rich header + lazy body\n * ```html\n * <lc-accordion variant=\"flat\" chevronPosition=\"leading\" [lazy]=\"true\">\n * <ng-template lcAccordionHeader>\n * <span class=\"title\">Item label</span>&ngsp;\n * <lc-badge variant=\"success\" size=\"sm\">Done</lc-badge>&ngsp;\n * <span style=\"margin-left: auto;\">12:04</span>\n * </ng-template>\n * <ng-template lcAccordionContent>\n * <expensive-panel />\n * </ng-template>\n * </lc-accordion>\n * ```\n */\n@Component({\n selector: 'lc-accordion',\n standalone: true,\n imports: [IconComponent, NgTemplateOutlet],\n viewProviders: [ɵprovideKitIcons([ICON_CHEVRON_DOWN])],\n templateUrl: './accordion.component.html',\n styleUrl: './accordion.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class AccordionComponent {\n /**\n * Title displayed in the accordion header. Optional: when a\n * `lcAccordionHeader` template is projected it takes precedence, and `title`\n * (if set) starts the header's accessible name when `ariaLabel` is empty.\n * @default ''\n */\n readonly title = input<string>('');\n\n /**\n * Start of the header button's accessible name; falls back to `title`. A\n * plain `title` is appended unless contained (WCAG 2.5.3 Label in Name).\n * With an `lcAccordionHeader` template, the name is this label (or `title`)\n * followed by the template's text, through `aria-labelledby`. Separate the\n * template's parts with whitespace (`&ngsp;`), and set a label only for\n * context that the template doesn't show.\n * @default ''\n */\n readonly ariaLabel = input<string>('');\n\n /** Whether the accordion is expanded (two-way binding) */\n readonly expanded = model<boolean>(false);\n\n /** Visual variant */\n readonly variant = input<'outlined' | 'flat'>('outlined');\n\n /** Size of the header */\n readonly size = input<'sm' | 'md' | 'lg'>('md');\n\n /** Whether the accordion is disabled */\n readonly disabled = input<boolean>(false);\n\n /**\n * Heading level (1–6) announced for the header button, so the accordion\n * fits into the surrounding document outline.\n * @default 3\n */\n readonly headingLevel = input<1 | 2 | 3 | 4 | 5 | 6>(3);\n\n /**\n * Where the disclosure chevron sits. `'trailing'` (default) preserves the\n * original layout. `'leading'` puts the chevron first so a right-aligned\n * header element (e.g. a timestamp using `margin-left: auto`) stays pinned to\n * the right edge.\n */\n readonly chevronPosition = input<AccordionChevronPosition>('trailing');\n\n /**\n * When `true`, a `lcAccordionContent` body is not instantiated until the\n * panel is first expanded; once created it is kept in the DOM on collapse.\n * Default `false` = today's behavior (projected immediately). Has no effect\n * on default `<ng-content>` bodies, which Angular always projects eagerly.\n */\n readonly lazy = input<boolean>(false);\n\n /**\n * When `true`, a `lcAccordionContent` body is destroyed on collapse and\n * recreated on the next open. Takes precedence over {@link lazy}.\n */\n readonly destroyOnClose = input<boolean>(false);\n\n /** Rich header template, if projected via `lcAccordionHeader`. */\n readonly headerTemplate = contentChild(AccordionHeaderDirective);\n\n /** Lazy/deferred body template, if projected via `lcAccordionContent`. */\n readonly contentTemplate = contentChild(AccordionContentDirective);\n\n /** Stable ids wiring the header button to its panel for screen readers. */\n protected readonly headerId = `lc-accordion-header-${accordionUid}`;\n protected readonly panelId = `lc-accordion-panel-${accordionUid++}`;\n\n /** Latches to `true` the first time the panel is opened (for lazy bodies). */\n private readonly hasBeenOpened = signal(false);\n\n constructor() {\n // Record the first open so lazy bodies can render (and stay) afterwards.\n effect(() => {\n if (this.expanded()) {\n this.hasBeenOpened.set(true);\n }\n });\n }\n\n /**\n * Label that starts a rich header's name (`ariaLabel`, else `title`); empty\n * without a header template.\n */\n protected readonly richHeaderLabel = computed(() =>\n this.headerTemplate() ? (this.ariaLabel() || this.title()).trim() : '',\n );\n\n /**\n * Accessible label of a plain header button (AS-33, WCAG 2.5.3 Label in\n * Name): the explicit override, else the title, then the visible title\n * unless contained. A rich header has none: it is named by its content.\n */\n protected readonly headerAriaLabel = computed(() =>\n this.headerTemplate() ? null : nameWithText(this.ariaLabel() || this.title(), this.title()) || null,\n );\n\n /** A labelled rich header: its hidden label, then the template's text. */\n protected readonly headerLabelledBy = computed(() =>\n this.richHeaderLabel() ? `${this.headerId}-label ${this.headerId}-content` : null,\n );\n\n /**\n * Whether the `lcAccordionContent` body should currently be in the DOM.\n * - `destroyOnClose`: only while expanded.\n * - `lazy`: from the first open onward.\n * - otherwise: always (eager).\n */\n protected readonly shouldRenderBody = computed(() => {\n if (this.destroyOnClose()) return this.expanded();\n if (this.lazy()) return this.hasBeenOpened();\n return true;\n });\n\n /** Computed CSS classes */\n protected accordionClasses = computed(() => {\n const classes = [\n 'lc-accordion',\n `lc-accordion--${this.variant()}`,\n `lc-accordion--${this.size()}`,\n `lc-accordion--chevron-${this.chevronPosition()}`,\n ];\n if (this.expanded()) classes.push('lc-accordion--expanded');\n if (this.disabled()) classes.push('lc-accordion--disabled');\n return classes.join(' ');\n });\n\n /** Toggle expanded state */\n protected toggle(): void {\n if (!this.disabled()) {\n this.expanded.update((v) => !v);\n }\n }\n\n /** Handle keyboard events for accessibility */\n protected onKeydown(event: KeyboardEvent): void {\n if (this.disabled()) return;\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n this.toggle();\n }\n }\n}\n","<div [class]=\"accordionClasses()\">\n <!-- The heading wrapper gives the disclosure button a heading level (WAI-ARIA accordion pattern). -->\n <div class=\"lc-accordion__heading\" role=\"heading\" [attr.aria-level]=\"headingLevel()\">\n <button\n class=\"lc-accordion__header\"\n type=\"button\"\n [id]=\"headerId\"\n [attr.aria-expanded]=\"expanded()\"\n [attr.aria-controls]=\"panelId\"\n [attr.aria-label]=\"headerAriaLabel()\"\n [attr.aria-labelledby]=\"headerLabelledBy()\"\n [attr.aria-disabled]=\"disabled()\"\n [disabled]=\"disabled()\"\n (click)=\"toggle()\"\n (keydown)=\"onKeydown($event)\"\n >\n @if (headerTemplate(); as header) {\n <!-- The label starts the name and the template's text follows it, so\n the name contains what is shown (WCAG 2.5.3). -->\n @if (richHeaderLabel()) {\n <span hidden [id]=\"headerId + '-label'\">{{ richHeaderLabel() }},</span>\n }\n <span class=\"lc-accordion__header-content\" [attr.id]=\"richHeaderLabel() ? headerId + '-content' : null\">\n <ng-container *ngTemplateOutlet=\"header.template\"></ng-container>\n </span>\n } @else {\n <span class=\"lc-accordion__title\">{{ title() }}</span>\n }\n <lc-icon\n class=\"lc-accordion__icon\"\n name=\"chevron-down\"\n size=\"sm\"\n [decorative]=\"true\"\n ></lc-icon>\n </button>\n </div>\n\n <!--\n A collapsed panel is `inert`: its eagerly projected body stays in the DOM\n (for the height animation) but is neither focusable nor exposed to AT.\n -->\n <div\n class=\"lc-accordion__panel\"\n [id]=\"panelId\"\n role=\"region\"\n [attr.aria-labelledby]=\"headerLabelledBy() ?? headerId\"\n [attr.aria-hidden]=\"!expanded()\"\n [attr.inert]=\"expanded() ? null : ''\"\n >\n <div class=\"lc-accordion__body\">\n @if (contentTemplate(); as content) {\n @if (shouldRenderBody()) {\n <ng-container *ngTemplateOutlet=\"content.template\"></ng-container>\n }\n } @else {\n <ng-content></ng-content>\n }\n </div>\n </div>\n</div>\n","import {\n Component,\n ChangeDetectionStrategy,\n input,\n contentChildren,\n effect,\n untracked,\n} from '@angular/core';\nimport { AccordionComponent } from './accordion.component';\n\n@Component({\n selector: 'lc-accordion-group',\n standalone: true,\n template: '<ng-content></ng-content>',\n styleUrl: './accordion-group.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class AccordionGroupComponent {\n /** When false (default), only one accordion can be expanded at a time. */\n readonly multi = input<boolean>(false);\n\n readonly accordions = contentChildren(AccordionComponent);\n\n private previousExpanded = new Set<AccordionComponent>();\n\n constructor() {\n effect(() => {\n const accordions = this.accordions();\n const multi = this.multi();\n\n // Read expanded state of every child so the effect re-runs on change\n const currentlyExpanded = accordions.filter((a) => a.expanded());\n\n if (multi) {\n this.previousExpanded = new Set(currentlyExpanded);\n return;\n }\n\n // Find newly expanded accordion\n const newlyExpanded = currentlyExpanded.find(\n (a) => !this.previousExpanded.has(a)\n );\n\n if (newlyExpanded && currentlyExpanded.length > 1) {\n untracked(() => {\n accordions.forEach((a) => {\n if (a !== newlyExpanded && a.expanded()) {\n a.expanded.set(false);\n }\n });\n });\n }\n\n this.previousExpanded = new Set(\n accordions.filter((a) => a.expanded())\n );\n });\n }\n\n /** Collapse all accordions. */\n collapseAll(): void {\n this.accordions().forEach((a) => a.expanded.set(false));\n }\n\n /** Expand all accordions (only meaningful when multi=true). */\n expandAll(): void {\n this.accordions().forEach((a) => {\n if (!a.disabled()) {\n a.expanded.set(true);\n }\n });\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"lifecorner-angular-ui-kit-accordion.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/accordion/accordion-header.directive.ts","../../../../libs/angular-ui-kit/src/lib/accordion/accordion-content.directive.ts","../../../../libs/angular-ui-kit/src/lib/accordion/accordion.component.ts","../../../../libs/angular-ui-kit/src/lib/accordion/accordion.component.html","../../../../libs/angular-ui-kit/src/lib/accordion/accordion-group.component.ts"],"sourcesContent":["import { Directive, TemplateRef, inject } from '@angular/core';\n\n/**\n * Marks an `<ng-template>` as the rich header content for an `<lc-accordion>`.\n *\n * When present, the projected template is rendered inside the header button\n * instead of the plain `title` string. The accordion still owns the disclosure\n * chevron, click/keyboard handling and focus ring — the consumer only supplies\n * the (non-interactive) inner content.\n *\n * A11y constraint: the header is itself a `<button>`, so the template must not\n * contain nested interactive elements (no button/link/input). Text, badges and\n * other non-interactive nodes only.\n *\n * Name (WCAG 2.5.3 Label in Name): the header is named by the template's\n * text; the accordion's `ariaLabel` (or `title`), if set, comes first through\n * `aria-labelledby`. Separate the template's parts with whitespace\n * (`&ngsp;`), so they read as words (\"Item label Done 12:04\"), and set a\n * label only for context that the template doesn't show.\n *\n * @example\n * ```html\n * <lc-accordion variant=\"flat\" chevronPosition=\"leading\">\n * <ng-template lcAccordionHeader>\n * <span class=\"title\">Item label</span>&ngsp;\n * <lc-badge variant=\"success\" size=\"sm\">Done</lc-badge>&ngsp;\n * <span style=\"margin-left: auto;\">12:04</span>\n * </ng-template>\n * <div>Body…</div>\n * </lc-accordion>\n * ```\n */\n@Directive({\n selector: '[lcAccordionHeader]',\n standalone: true,\n})\nexport class AccordionHeaderDirective {\n public readonly template = inject(TemplateRef<unknown>);\n}\n","import { Directive, TemplateRef, inject } from '@angular/core';\n\n/**\n * Marks an `<ng-template>` as the (optionally lazy) body content for an\n * `<lc-accordion>`.\n *\n * Unlike default `<ng-content>` projection — which Angular always instantiates\n * eagerly — a template referenced through this directive is only stamped out\n * when the accordion decides to render its body. That is what makes\n * `[lazy]=\"true\"` and `[destroyOnClose]=\"true\"` possible: expensive children\n * (HTTP-backed panels, charts, live-polling views) are not created until the\n * panel is first opened.\n *\n * @example\n * ```html\n * <lc-accordion [lazy]=\"true\">\n * <ng-template lcAccordionContent>\n * <expensive-panel [id]=\"id\" />\n * </ng-template>\n * </lc-accordion>\n * ```\n */\n@Directive({\n selector: '[lcAccordionContent]',\n standalone: true,\n})\nexport class AccordionContentDirective {\n public readonly template = inject(TemplateRef<unknown>);\n}\n","import {\n Component,\n ChangeDetectionStrategy,\n input,\n model,\n computed,\n signal,\n contentChild,\n effect,\n} from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport { IconComponent, ɵprovideKitIcons } from '@lifecorner/angular-ui-kit/icon';\nimport { ICON_CHEVRON_DOWN } from '@lifecorner/angular-ui-kit/icons';\nimport { AccordionHeaderDirective } from './accordion-header.directive';\nimport { AccordionContentDirective } from './accordion-content.directive';\nimport { nameWithText } from '@lifecorner/angular-ui-kit/internal/a11y';\n\n/** Unique id source for header/panel wiring (aria-controls / aria-labelledby). */\nlet accordionUid = 0;\n\n/** Where the disclosure chevron sits relative to the header content. */\nexport type AccordionChevronPosition = 'leading' | 'trailing';\n\n/** Visual variant of `lc-accordion`. */\nexport type AccordionVariant = 'outlined' | 'flat' | 'plain';\n\n/**\n * Accordion component for collapsible content sections.\n *\n * Features:\n * - Expandable/collapsible content panels\n * - Two-way binding for expanded state\n * - Animated expand/collapse transitions\n * - Plain-string `title` **or** a rich projected header (`lcAccordionHeader`)\n * - Eager `<ng-content>` body **or** lazy/destroyable template body\n * (`lcAccordionContent` + `[lazy]` / `[destroyOnClose]`)\n * - Accessible: header is a real `<button>` inside a heading with\n * `aria-expanded` / `aria-controls`, keyboard support and a visible focus\n * ring; the collapsed panel is `inert` (not focusable, hidden from AT)\n *\n * @example Plain (unchanged, fully backward compatible)\n * ```html\n * <lc-accordion title=\"Section Title\" [(expanded)]=\"isOpen\">\n * <p>Collapsible content here</p>\n * </lc-accordion>\n * ```\n *\n * @example Rich header + lazy body\n * ```html\n * <lc-accordion variant=\"flat\" chevronPosition=\"leading\" [lazy]=\"true\">\n * <ng-template lcAccordionHeader>\n * <span class=\"title\">Item label</span>&ngsp;\n * <lc-badge variant=\"success\" size=\"sm\">Done</lc-badge>&ngsp;\n * <span style=\"margin-left: auto;\">12:04</span>\n * </ng-template>\n * <ng-template lcAccordionContent>\n * <expensive-panel />\n * </ng-template>\n * </lc-accordion>\n * ```\n */\n@Component({\n selector: 'lc-accordion',\n standalone: true,\n imports: [IconComponent, NgTemplateOutlet],\n viewProviders: [ɵprovideKitIcons([ICON_CHEVRON_DOWN])],\n templateUrl: './accordion.component.html',\n styleUrl: './accordion.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class AccordionComponent {\n /**\n * Title displayed in the accordion header. Optional: when a\n * `lcAccordionHeader` template is projected it takes precedence, and `title`\n * (if set) starts the header's accessible name when `ariaLabel` is empty.\n * @default ''\n */\n readonly title = input<string>('');\n\n /**\n * Start of the header button's accessible name; falls back to `title`. A\n * plain `title` is appended unless contained (WCAG 2.5.3 Label in Name).\n * With an `lcAccordionHeader` template, the name is this label (or `title`)\n * followed by the template's text, through `aria-labelledby`. Separate the\n * template's parts with whitespace (`&ngsp;`), and set a label only for\n * context that the template doesn't show.\n * @default ''\n */\n readonly ariaLabel = input<string>('');\n\n /** Whether the accordion is expanded (two-way binding) */\n readonly expanded = model<boolean>(false);\n\n /**\n * Visual variant. `'outlined'`: border and surface. `'flat'`: no border or\n * background; header and body keep their inset. `'plain'`: no border,\n * background or inline inset, so the title, chevron and body line up with\n * the content edge of the sheet or card around it; the hover background and\n * focus ring reach 8 px past that edge (`--lc-accordion-header-bleed`), so\n * keep that much room before a clipping edge.\n * @default 'outlined'\n */\n readonly variant = input<AccordionVariant>('outlined');\n\n /** Size of the header */\n readonly size = input<'sm' | 'md' | 'lg'>('md');\n\n /** Whether the accordion is disabled */\n readonly disabled = input<boolean>(false);\n\n /**\n * Heading level (1–6) announced for the header button, so the accordion\n * fits into the surrounding document outline.\n * @default 3\n */\n readonly headingLevel = input<1 | 2 | 3 | 4 | 5 | 6>(3);\n\n /**\n * Where the disclosure chevron sits. `'trailing'` (default) preserves the\n * original layout. `'leading'` puts the chevron first so a right-aligned\n * header element (e.g. a timestamp using `margin-left: auto`) stays pinned to\n * the right edge.\n */\n readonly chevronPosition = input<AccordionChevronPosition>('trailing');\n\n /**\n * When `true`, a `lcAccordionContent` body is not instantiated until the\n * panel is first expanded; once created it is kept in the DOM on collapse.\n * Default `false` = today's behavior (projected immediately). Has no effect\n * on default `<ng-content>` bodies, which Angular always projects eagerly.\n */\n readonly lazy = input<boolean>(false);\n\n /**\n * When `true`, a `lcAccordionContent` body is destroyed on collapse and\n * recreated on the next open. Takes precedence over {@link lazy}.\n */\n readonly destroyOnClose = input<boolean>(false);\n\n /** Rich header template, if projected via `lcAccordionHeader`. */\n readonly headerTemplate = contentChild(AccordionHeaderDirective);\n\n /** Lazy/deferred body template, if projected via `lcAccordionContent`. */\n readonly contentTemplate = contentChild(AccordionContentDirective);\n\n /** Stable ids wiring the header button to its panel for screen readers. */\n protected readonly headerId = `lc-accordion-header-${accordionUid}`;\n protected readonly panelId = `lc-accordion-panel-${accordionUid++}`;\n\n /** Latches to `true` the first time the panel is opened (for lazy bodies). */\n private readonly hasBeenOpened = signal(false);\n\n constructor() {\n // Record the first open so lazy bodies can render (and stay) afterwards.\n effect(() => {\n if (this.expanded()) {\n this.hasBeenOpened.set(true);\n }\n });\n }\n\n /**\n * Label that starts a rich header's name (`ariaLabel`, else `title`); empty\n * without a header template.\n */\n protected readonly richHeaderLabel = computed(() =>\n this.headerTemplate() ? (this.ariaLabel() || this.title()).trim() : '',\n );\n\n /**\n * Accessible label of a plain header button (AS-33, WCAG 2.5.3 Label in\n * Name): the explicit override, else the title, then the visible title\n * unless contained. A rich header has none: it is named by its content.\n */\n protected readonly headerAriaLabel = computed(() =>\n this.headerTemplate() ? null : nameWithText(this.ariaLabel() || this.title(), this.title()) || null,\n );\n\n /** A labelled rich header: its hidden label, then the template's text. */\n protected readonly headerLabelledBy = computed(() =>\n this.richHeaderLabel() ? `${this.headerId}-label ${this.headerId}-content` : null,\n );\n\n /**\n * Whether the `lcAccordionContent` body should currently be in the DOM.\n * - `destroyOnClose`: only while expanded.\n * - `lazy`: from the first open onward.\n * - otherwise: always (eager).\n */\n protected readonly shouldRenderBody = computed(() => {\n if (this.destroyOnClose()) return this.expanded();\n if (this.lazy()) return this.hasBeenOpened();\n return true;\n });\n\n /** Computed CSS classes */\n protected accordionClasses = computed(() => {\n const classes = [\n 'lc-accordion',\n `lc-accordion--${this.variant()}`,\n `lc-accordion--${this.size()}`,\n `lc-accordion--chevron-${this.chevronPosition()}`,\n ];\n if (this.expanded()) classes.push('lc-accordion--expanded');\n if (this.disabled()) classes.push('lc-accordion--disabled');\n return classes.join(' ');\n });\n\n /** Toggle expanded state */\n protected toggle(): void {\n if (!this.disabled()) {\n this.expanded.update((v) => !v);\n }\n }\n\n /** Handle keyboard events for accessibility */\n protected onKeydown(event: KeyboardEvent): void {\n if (this.disabled()) return;\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n this.toggle();\n }\n }\n}\n","<div [class]=\"accordionClasses()\">\n <!-- The heading wrapper gives the disclosure button a heading level (WAI-ARIA accordion pattern). -->\n <div class=\"lc-accordion__heading\" role=\"heading\" [attr.aria-level]=\"headingLevel()\">\n <button\n class=\"lc-accordion__header\"\n type=\"button\"\n [id]=\"headerId\"\n [attr.aria-expanded]=\"expanded()\"\n [attr.aria-controls]=\"panelId\"\n [attr.aria-label]=\"headerAriaLabel()\"\n [attr.aria-labelledby]=\"headerLabelledBy()\"\n [attr.aria-disabled]=\"disabled()\"\n [disabled]=\"disabled()\"\n (click)=\"toggle()\"\n (keydown)=\"onKeydown($event)\"\n >\n @if (headerTemplate(); as header) {\n <!-- The label starts the name and the template's text follows it, so\n the name contains what is shown (WCAG 2.5.3). -->\n @if (richHeaderLabel()) {\n <span hidden [id]=\"headerId + '-label'\">{{ richHeaderLabel() }},</span>\n }\n <span class=\"lc-accordion__header-content\" [attr.id]=\"richHeaderLabel() ? headerId + '-content' : null\">\n <ng-container *ngTemplateOutlet=\"header.template\"></ng-container>\n </span>\n } @else {\n <span class=\"lc-accordion__title\">{{ title() }}</span>\n }\n <lc-icon\n class=\"lc-accordion__icon\"\n name=\"chevron-down\"\n size=\"sm\"\n [decorative]=\"true\"\n ></lc-icon>\n </button>\n </div>\n\n <!--\n A collapsed panel is `inert`: its eagerly projected body stays in the DOM\n (for the height animation) but is neither focusable nor exposed to AT.\n -->\n <div\n class=\"lc-accordion__panel\"\n [id]=\"panelId\"\n role=\"region\"\n [attr.aria-labelledby]=\"headerLabelledBy() ?? headerId\"\n [attr.aria-hidden]=\"!expanded()\"\n [attr.inert]=\"expanded() ? null : ''\"\n >\n <div class=\"lc-accordion__body\">\n @if (contentTemplate(); as content) {\n @if (shouldRenderBody()) {\n <ng-container *ngTemplateOutlet=\"content.template\"></ng-container>\n }\n } @else {\n <ng-content></ng-content>\n }\n </div>\n </div>\n</div>\n","import {\n Component,\n ChangeDetectionStrategy,\n input,\n contentChildren,\n effect,\n untracked,\n} from '@angular/core';\nimport { AccordionComponent } from './accordion.component';\n\n@Component({\n selector: 'lc-accordion-group',\n standalone: true,\n template: '<ng-content></ng-content>',\n styleUrl: './accordion-group.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class AccordionGroupComponent {\n /** When false (default), only one accordion can be expanded at a time. */\n readonly multi = input<boolean>(false);\n\n readonly accordions = contentChildren(AccordionComponent);\n\n private previousExpanded = new Set<AccordionComponent>();\n\n constructor() {\n effect(() => {\n const accordions = this.accordions();\n const multi = this.multi();\n\n // Read expanded state of every child so the effect re-runs on change\n const currentlyExpanded = accordions.filter((a) => a.expanded());\n\n if (multi) {\n this.previousExpanded = new Set(currentlyExpanded);\n return;\n }\n\n // Find newly expanded accordion\n const newlyExpanded = currentlyExpanded.find(\n (a) => !this.previousExpanded.has(a)\n );\n\n if (newlyExpanded && currentlyExpanded.length > 1) {\n untracked(() => {\n accordions.forEach((a) => {\n if (a !== newlyExpanded && a.expanded()) {\n a.expanded.set(false);\n }\n });\n });\n }\n\n this.previousExpanded = new Set(\n accordions.filter((a) => a.expanded())\n );\n });\n }\n\n /** Collapse all accordions. */\n collapseAll(): void {\n this.accordions().forEach((a) => a.expanded.set(false));\n }\n\n /** Expand all accordions (only meaningful when multi=true). */\n expandAll(): void {\n this.accordions().forEach((a) => {\n if (!a.disabled()) {\n a.expanded.set(true);\n }\n });\n }\n}\n"],"mappings":"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"}
@@ -161,7 +161,7 @@ var CardComponent = class CardComponent {
161
161
  host: { listeners: { "keydown": "handleKeydown($event)" } },
162
162
  ngImport: i0,
163
163
  template: "<!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events -- Enter/Space are handled by the host keydown listener (role=button, tabindex=0 when clickable) -->\n<div\n [class]=\"cardClasses()\"\n [attr.role]=\"role()\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.tabindex]=\"isClickable() ? 0 : null\"\n (click)=\"handleClick($event)\"\n>\n @if (title()) {\n <div class=\"card__header\">\n @if (icon()) {\n <div\n class=\"card__icon card__icon--{{ iconVariant() }} card__icon--size-{{ iconSize() }}\"\n aria-hidden=\"true\"\n >\n <lc-icon [name]=\"icon()!\" [size]=\"iconSize() === 'sm' ? 'xs' : 'sm'\" [decorative]=\"true\" />\n </div>\n }\n <div class=\"card__header-left\">\n <div class=\"card__title-row\">\n @switch (headingLevel()) {\n @case (2) {\n <h2 class=\"card__title\">{{ title() }}</h2>\n }\n @case (4) {\n <h4 class=\"card__title\">{{ title() }}</h4>\n }\n @case (5) {\n <h5 class=\"card__title\">{{ title() }}</h5>\n }\n @case (6) {\n <h6 class=\"card__title\">{{ title() }}</h6>\n }\n @default {\n <h3 class=\"card__title\">{{ title() }}</h3>\n }\n }\n <!-- Rich status badge: project an lc-badge here (takes visual\n precedence over the `badge` string). Wrapper hides when empty. -->\n <span class=\"card__badge-slot\"><ng-content select=\"[card-badge]\"></ng-content></span>\n @if (badge()) {\n <span class=\"card__badge card__badge--{{ badgeVariant() }}\">{{ badge() }}</span>\n }\n </div>\n @if (subtitle()) {\n <p class=\"card__subtitle\">{{ subtitle() }}</p>\n }\n </div>\n <div class=\"card__header-action\">\n <ng-content select=\"[card-header-action]\"></ng-content>\n </div>\n </div>\n }\n <ng-content select=\"[card-header]\"></ng-content>\n <div class=\"card__body\">\n <ng-content></ng-content>\n </div>\n <ng-content select=\"[card-footer]\"></ng-content>\n</div>\n",
164
- styles: [":host{display:block;height:100%}.card{position:relative;display:flex;flex-direction:column;height:100%;background-color:var(--color-surface);color:var(--color-text-primary);transition:box-shadow .15s ease-in-out,transform .15s ease-in-out,border-color .15s ease-in-out}.card__body{flex:1;display:flex;flex-direction:column}.card-elevated{position:relative;overflow:hidden;background-image:var(--gradient-surface);border:1px solid var(--color-border);box-shadow:var(--elevation-1)}.card-elevated:after{content:\"\";position:absolute;inset:0 0 auto;height:3px;background:var(--accent-line);opacity:.7;pointer-events:none}.card-elevated:hover{box-shadow:var(--elevation-2)}.card-outlined{background-image:none;border:1px solid var(--color-border)}.card-flat{background-image:none}.card-raised{background-image:none;border:1px solid var(--color-border);box-shadow:var(--shadow-auth-card, var(--elevation-3))}.card-dashed{background-color:var(--color-muted-tile, var(--color-surface-muted));background-image:none;border:1px dashed var(--color-muted-tile-border, var(--color-border));box-shadow:none;color:var(--color-text-secondary)}.card-padding-none{padding:0}.card-padding-xs{padding:var(--lc-density-padding-xs, var(--spacing-2))}.card-padding-sm{padding:var(--lc-density-padding-sm, var(--spacing-3))}.card-padding-md{padding:var(--lc-density-padding-md, var(--spacing-4))}.card-padding-lg{padding:var(--lc-density-padding-lg, var(--spacing-6))}.card-radius-none{border-radius:var(--border-radius-none)}.card-radius-sm{border-radius:var(--border-radius-sm)}.card-radius-md{border-radius:var(--border-radius-md)}.card-radius-lg{border-radius:var(--border-radius-lg)}.card-radius-xl{border-radius:var(--border-radius-xl, 1.5rem)}.card-radius-full{border-radius:var(--border-radius-full)}.card-clickable{cursor:pointer;-webkit-user-select:none;user-select:none}.card-clickable:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.card-clickable:hover{background-color:var(--color-surface-hover);border-color:var(--color-border-strong)}.card-clickable:active{transform:scale(.98)}.card-selected{border:1px solid var(--color-primary);background-color:var(--color-surface-selected);box-shadow:var(--shadow-brand-glow)}.card-selected.card-elevated{box-shadow:var(--shadow-brand-glow),var(--elevation-2)}.card__header{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--lc-density-gap-md, var(--spacing-4));margin-bottom:var(--lc-density-padding-md, var(--spacing-4));padding-bottom:var(--lc-density-padding-sm, var(--spacing-3));border-bottom:1px solid var(--color-border, #e5e7eb)}.card__icon{flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;width:2.25rem;height:2.25rem;border-radius:var(--border-radius-md)}.card__icon--size-sm{width:1.75rem;height:1.75rem;border-radius:var(--border-radius-sm)}.card__icon--brand{background-image:var(--gradient-brand-tile);color:var(--color-on-primary, #fff);box-shadow:var(--shadow-brand-glow)}.card__icon--subtle{background-color:#20849724;color:var(--color-primary-500);border:1px solid var(--color-border-strong)}.card__header-left{flex:1;min-width:0}.card__title-row{display:flex;align-items:center;gap:.5rem}.card__badge-slot{display:inline-flex;align-items:center}.card__badge-slot:empty{display:none}.card__title{margin:0;font-weight:600;font-size:var(--lc-card-title-size, var(--font-size-lg, 1.125rem));line-height:1.3;color:var(--color-text-primary)}.card__badge{display:inline-flex;align-items:center;justify-content:center;min-width:1.25rem;height:1.25rem;padding:0 .375rem;border-radius:9999px;font-size:.75rem;font-weight:600;line-height:1}.card__badge--default{background-color:var(--color-surface-hover);color:var(--color-text-secondary);border:1px solid var(--color-border)}.card__badge--primary{background-color:#20849729;color:var(--color-primary-500);border:1px solid var(--color-border-strong)}.card__badge--success{background-color:var(--color-success-subtle);color:var(--color-on-success-subtle)}.card__badge--warning{background-color:var(--color-warning-subtle);color:var(--color-on-warning-subtle)}.card__badge--error{background-color:var(--color-error-subtle);color:var(--color-on-error-subtle)}.card__subtitle{margin:.25rem 0 0;font-size:.8125rem;color:var(--color-text-secondary);line-height:1.4}.card__header-action{flex-shrink:0;display:flex;align-items:center;gap:.5rem}:host ::ng-deep [card-header],:host ::ng-deep .card-header{display:flex;align-items:center;justify-content:space-between;gap:var(--lc-density-gap-sm, var(--spacing-2));margin-bottom:var(--lc-density-padding-md, var(--spacing-4));padding-bottom:var(--lc-density-padding-sm, var(--spacing-3));border-bottom:1px solid var(--color-divider, #e5e7eb);font-weight:600;font-size:1.125rem;color:var(--color-text-primary)}:host ::ng-deep .card-content{color:var(--color-text-primary)}:host ::ng-deep [card-footer],:host ::ng-deep .card-footer{display:flex;align-items:center;justify-content:flex-end;gap:var(--lc-density-gap-sm, var(--spacing-2));margin-top:var(--lc-density-padding-md, var(--spacing-4));padding-top:var(--lc-density-padding-sm, var(--spacing-3));border-top:1px solid var(--color-divider, #e5e7eb);color:var(--color-text-secondary)}:host ::ng-deep .card-dashed lc-icon{color:var(--color-muted-tile-icon, var(--color-text-tertiary))}\n"],
164
+ styles: [":host{display:block;height:100%}.card{position:relative;display:flex;flex-direction:column;height:100%;background-color:var(--color-surface);color:var(--color-text-primary);transition:box-shadow .15s ease-in-out,transform .15s ease-in-out,border-color .15s ease-in-out}.card__body{flex:1;display:flex;flex-direction:column}.card-elevated{position:relative;overflow:hidden;background-image:var(--gradient-surface);border:1px solid var(--color-border);box-shadow:var(--elevation-1)}.card-elevated:after{content:\"\";position:absolute;inset:0 0 auto;height:3px;background:var(--accent-line);opacity:.7;pointer-events:none}.card-elevated:hover{box-shadow:var(--elevation-2)}.card-outlined{background-image:none;border:1px solid var(--color-border)}.card-flat{background-image:none}.card-raised{background-image:none;border:1px solid var(--color-border);box-shadow:var(--shadow-auth-card, var(--elevation-3))}.card-dashed{background-color:var(--color-muted-tile, var(--color-surface-muted));background-image:none;border:1px dashed var(--color-muted-tile-border, var(--color-border));box-shadow:none;color:var(--color-text-secondary)}.card-padding-none{padding:0}.card-padding-xs{padding:var(--lc-density-padding-xs, var(--spacing-2))}.card-padding-sm{padding:var(--lc-density-padding-sm, var(--spacing-3))}.card-padding-md{padding:var(--lc-density-padding-md, var(--spacing-4))}.card-padding-lg{padding:var(--lc-density-padding-lg, var(--spacing-6))}.card-radius-none{border-radius:var(--border-radius-none)}.card-radius-sm{border-radius:var(--border-radius-sm)}.card-radius-md{border-radius:var(--border-radius-md)}.card-radius-lg{border-radius:var(--border-radius-lg)}.card-radius-xl{border-radius:var(--border-radius-xl, 1.5rem)}.card-radius-full{border-radius:var(--border-radius-full)}.card-clickable{cursor:pointer;-webkit-user-select:none;user-select:none}.card-clickable:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.card-clickable:hover{background-color:var(--color-surface-hover);border-color:var(--color-border-strong)}.card-clickable:active{transform:scale(.98)}.card-selected{border:1px solid var(--color-primary);background-color:var(--color-surface-selected);box-shadow:var(--shadow-brand-glow)}.card-selected.card-elevated{box-shadow:var(--shadow-brand-glow),var(--elevation-2)}.card__header{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--lc-density-gap-md, var(--spacing-4));margin-bottom:var(--lc-density-padding-md, var(--spacing-4));padding-bottom:var(--lc-density-padding-sm, var(--spacing-3));border-bottom:1px solid var(--color-border, #e5e7eb)}.card__icon{flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;width:2.25rem;height:2.25rem;border-radius:var(--border-radius-md)}.card__icon--size-sm{width:1.75rem;height:1.75rem;border-radius:var(--border-radius-sm)}.card__icon--brand{background-image:var(--gradient-brand-tile);color:var(--color-on-primary, #fff);box-shadow:var(--shadow-brand-glow)}.card__icon--subtle{background-color:#20849724;color:var(--color-primary-500);border:1px solid var(--color-border-strong)}.card__header-left{flex:1;min-width:0}.card__title-row{display:flex;align-items:center;gap:.5rem}.card__badge-slot{display:inline-flex;align-items:center}.card__badge-slot:empty{display:none}.card__title{margin:0;font-weight:600;font-size:var(--lc-card-title-size, var(--font-size-lg, 1.125rem));line-height:1.3;color:var(--color-text-primary)}.card__badge{display:inline-flex;align-items:center;justify-content:center;min-width:1.25rem;height:1.25rem;padding:0 .375rem;border-radius:9999px;font-size:.75rem;font-weight:600;line-height:1}.card__badge--default{background-color:var(--color-surface-hover);color:var(--color-text-secondary);border:1px solid var(--color-border)}.card__badge--primary{background-color:var(--color-primary-subtle);color:var(--color-on-primary-subtle);border:1px solid var(--color-primary-500)}.card__badge--success{background-color:var(--color-success-subtle);color:var(--color-on-success-subtle)}.card__badge--warning{background-color:var(--color-warning-subtle);color:var(--color-on-warning-subtle)}.card__badge--error{background-color:var(--color-error-subtle);color:var(--color-on-error-subtle)}.card__subtitle{margin:.25rem 0 0;font-size:.8125rem;color:var(--color-text-secondary);line-height:1.4}.card__header-action{flex-shrink:0;display:flex;align-items:center;gap:.5rem}:host ::ng-deep [card-header],:host ::ng-deep .card-header{display:flex;align-items:center;justify-content:space-between;gap:var(--lc-density-gap-sm, var(--spacing-2));margin-bottom:var(--lc-density-padding-md, var(--spacing-4));padding-bottom:var(--lc-density-padding-sm, var(--spacing-3));border-bottom:1px solid var(--color-divider, #e5e7eb);font-weight:600;font-size:1.125rem;color:var(--color-text-primary)}:host ::ng-deep .card-content{color:var(--color-text-primary)}:host ::ng-deep [card-footer],:host ::ng-deep .card-footer{display:flex;align-items:center;justify-content:flex-end;gap:var(--lc-density-gap-sm, var(--spacing-2));margin-top:var(--lc-density-padding-md, var(--spacing-4));padding-top:var(--lc-density-padding-sm, var(--spacing-3));border-top:1px solid var(--color-divider, #e5e7eb);color:var(--color-text-secondary)}:host ::ng-deep .card-dashed lc-icon{color:var(--color-muted-tile-icon, var(--color-text-tertiary))}\n"],
165
165
  dependencies: [{
166
166
  kind: "component",
167
167
  type: IconComponent,
@@ -192,7 +192,7 @@ i0.ɵɵngDeclareClassMetadata({
192
192
  imports: [IconComponent],
193
193
  changeDetection: ChangeDetectionStrategy.OnPush,
194
194
  template: "<!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events -- Enter/Space are handled by the host keydown listener (role=button, tabindex=0 when clickable) -->\n<div\n [class]=\"cardClasses()\"\n [attr.role]=\"role()\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.tabindex]=\"isClickable() ? 0 : null\"\n (click)=\"handleClick($event)\"\n>\n @if (title()) {\n <div class=\"card__header\">\n @if (icon()) {\n <div\n class=\"card__icon card__icon--{{ iconVariant() }} card__icon--size-{{ iconSize() }}\"\n aria-hidden=\"true\"\n >\n <lc-icon [name]=\"icon()!\" [size]=\"iconSize() === 'sm' ? 'xs' : 'sm'\" [decorative]=\"true\" />\n </div>\n }\n <div class=\"card__header-left\">\n <div class=\"card__title-row\">\n @switch (headingLevel()) {\n @case (2) {\n <h2 class=\"card__title\">{{ title() }}</h2>\n }\n @case (4) {\n <h4 class=\"card__title\">{{ title() }}</h4>\n }\n @case (5) {\n <h5 class=\"card__title\">{{ title() }}</h5>\n }\n @case (6) {\n <h6 class=\"card__title\">{{ title() }}</h6>\n }\n @default {\n <h3 class=\"card__title\">{{ title() }}</h3>\n }\n }\n <!-- Rich status badge: project an lc-badge here (takes visual\n precedence over the `badge` string). Wrapper hides when empty. -->\n <span class=\"card__badge-slot\"><ng-content select=\"[card-badge]\"></ng-content></span>\n @if (badge()) {\n <span class=\"card__badge card__badge--{{ badgeVariant() }}\">{{ badge() }}</span>\n }\n </div>\n @if (subtitle()) {\n <p class=\"card__subtitle\">{{ subtitle() }}</p>\n }\n </div>\n <div class=\"card__header-action\">\n <ng-content select=\"[card-header-action]\"></ng-content>\n </div>\n </div>\n }\n <ng-content select=\"[card-header]\"></ng-content>\n <div class=\"card__body\">\n <ng-content></ng-content>\n </div>\n <ng-content select=\"[card-footer]\"></ng-content>\n</div>\n",
195
- styles: [":host{display:block;height:100%}.card{position:relative;display:flex;flex-direction:column;height:100%;background-color:var(--color-surface);color:var(--color-text-primary);transition:box-shadow .15s ease-in-out,transform .15s ease-in-out,border-color .15s ease-in-out}.card__body{flex:1;display:flex;flex-direction:column}.card-elevated{position:relative;overflow:hidden;background-image:var(--gradient-surface);border:1px solid var(--color-border);box-shadow:var(--elevation-1)}.card-elevated:after{content:\"\";position:absolute;inset:0 0 auto;height:3px;background:var(--accent-line);opacity:.7;pointer-events:none}.card-elevated:hover{box-shadow:var(--elevation-2)}.card-outlined{background-image:none;border:1px solid var(--color-border)}.card-flat{background-image:none}.card-raised{background-image:none;border:1px solid var(--color-border);box-shadow:var(--shadow-auth-card, var(--elevation-3))}.card-dashed{background-color:var(--color-muted-tile, var(--color-surface-muted));background-image:none;border:1px dashed var(--color-muted-tile-border, var(--color-border));box-shadow:none;color:var(--color-text-secondary)}.card-padding-none{padding:0}.card-padding-xs{padding:var(--lc-density-padding-xs, var(--spacing-2))}.card-padding-sm{padding:var(--lc-density-padding-sm, var(--spacing-3))}.card-padding-md{padding:var(--lc-density-padding-md, var(--spacing-4))}.card-padding-lg{padding:var(--lc-density-padding-lg, var(--spacing-6))}.card-radius-none{border-radius:var(--border-radius-none)}.card-radius-sm{border-radius:var(--border-radius-sm)}.card-radius-md{border-radius:var(--border-radius-md)}.card-radius-lg{border-radius:var(--border-radius-lg)}.card-radius-xl{border-radius:var(--border-radius-xl, 1.5rem)}.card-radius-full{border-radius:var(--border-radius-full)}.card-clickable{cursor:pointer;-webkit-user-select:none;user-select:none}.card-clickable:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.card-clickable:hover{background-color:var(--color-surface-hover);border-color:var(--color-border-strong)}.card-clickable:active{transform:scale(.98)}.card-selected{border:1px solid var(--color-primary);background-color:var(--color-surface-selected);box-shadow:var(--shadow-brand-glow)}.card-selected.card-elevated{box-shadow:var(--shadow-brand-glow),var(--elevation-2)}.card__header{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--lc-density-gap-md, var(--spacing-4));margin-bottom:var(--lc-density-padding-md, var(--spacing-4));padding-bottom:var(--lc-density-padding-sm, var(--spacing-3));border-bottom:1px solid var(--color-border, #e5e7eb)}.card__icon{flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;width:2.25rem;height:2.25rem;border-radius:var(--border-radius-md)}.card__icon--size-sm{width:1.75rem;height:1.75rem;border-radius:var(--border-radius-sm)}.card__icon--brand{background-image:var(--gradient-brand-tile);color:var(--color-on-primary, #fff);box-shadow:var(--shadow-brand-glow)}.card__icon--subtle{background-color:#20849724;color:var(--color-primary-500);border:1px solid var(--color-border-strong)}.card__header-left{flex:1;min-width:0}.card__title-row{display:flex;align-items:center;gap:.5rem}.card__badge-slot{display:inline-flex;align-items:center}.card__badge-slot:empty{display:none}.card__title{margin:0;font-weight:600;font-size:var(--lc-card-title-size, var(--font-size-lg, 1.125rem));line-height:1.3;color:var(--color-text-primary)}.card__badge{display:inline-flex;align-items:center;justify-content:center;min-width:1.25rem;height:1.25rem;padding:0 .375rem;border-radius:9999px;font-size:.75rem;font-weight:600;line-height:1}.card__badge--default{background-color:var(--color-surface-hover);color:var(--color-text-secondary);border:1px solid var(--color-border)}.card__badge--primary{background-color:#20849729;color:var(--color-primary-500);border:1px solid var(--color-border-strong)}.card__badge--success{background-color:var(--color-success-subtle);color:var(--color-on-success-subtle)}.card__badge--warning{background-color:var(--color-warning-subtle);color:var(--color-on-warning-subtle)}.card__badge--error{background-color:var(--color-error-subtle);color:var(--color-on-error-subtle)}.card__subtitle{margin:.25rem 0 0;font-size:.8125rem;color:var(--color-text-secondary);line-height:1.4}.card__header-action{flex-shrink:0;display:flex;align-items:center;gap:.5rem}:host ::ng-deep [card-header],:host ::ng-deep .card-header{display:flex;align-items:center;justify-content:space-between;gap:var(--lc-density-gap-sm, var(--spacing-2));margin-bottom:var(--lc-density-padding-md, var(--spacing-4));padding-bottom:var(--lc-density-padding-sm, var(--spacing-3));border-bottom:1px solid var(--color-divider, #e5e7eb);font-weight:600;font-size:1.125rem;color:var(--color-text-primary)}:host ::ng-deep .card-content{color:var(--color-text-primary)}:host ::ng-deep [card-footer],:host ::ng-deep .card-footer{display:flex;align-items:center;justify-content:flex-end;gap:var(--lc-density-gap-sm, var(--spacing-2));margin-top:var(--lc-density-padding-md, var(--spacing-4));padding-top:var(--lc-density-padding-sm, var(--spacing-3));border-top:1px solid var(--color-divider, #e5e7eb);color:var(--color-text-secondary)}:host ::ng-deep .card-dashed lc-icon{color:var(--color-muted-tile-icon, var(--color-text-tertiary))}\n"]
195
+ styles: [":host{display:block;height:100%}.card{position:relative;display:flex;flex-direction:column;height:100%;background-color:var(--color-surface);color:var(--color-text-primary);transition:box-shadow .15s ease-in-out,transform .15s ease-in-out,border-color .15s ease-in-out}.card__body{flex:1;display:flex;flex-direction:column}.card-elevated{position:relative;overflow:hidden;background-image:var(--gradient-surface);border:1px solid var(--color-border);box-shadow:var(--elevation-1)}.card-elevated:after{content:\"\";position:absolute;inset:0 0 auto;height:3px;background:var(--accent-line);opacity:.7;pointer-events:none}.card-elevated:hover{box-shadow:var(--elevation-2)}.card-outlined{background-image:none;border:1px solid var(--color-border)}.card-flat{background-image:none}.card-raised{background-image:none;border:1px solid var(--color-border);box-shadow:var(--shadow-auth-card, var(--elevation-3))}.card-dashed{background-color:var(--color-muted-tile, var(--color-surface-muted));background-image:none;border:1px dashed var(--color-muted-tile-border, var(--color-border));box-shadow:none;color:var(--color-text-secondary)}.card-padding-none{padding:0}.card-padding-xs{padding:var(--lc-density-padding-xs, var(--spacing-2))}.card-padding-sm{padding:var(--lc-density-padding-sm, var(--spacing-3))}.card-padding-md{padding:var(--lc-density-padding-md, var(--spacing-4))}.card-padding-lg{padding:var(--lc-density-padding-lg, var(--spacing-6))}.card-radius-none{border-radius:var(--border-radius-none)}.card-radius-sm{border-radius:var(--border-radius-sm)}.card-radius-md{border-radius:var(--border-radius-md)}.card-radius-lg{border-radius:var(--border-radius-lg)}.card-radius-xl{border-radius:var(--border-radius-xl, 1.5rem)}.card-radius-full{border-radius:var(--border-radius-full)}.card-clickable{cursor:pointer;-webkit-user-select:none;user-select:none}.card-clickable:focus-visible{outline:2px solid var(--color-primary-500);outline-offset:2px}.card-clickable:hover{background-color:var(--color-surface-hover);border-color:var(--color-border-strong)}.card-clickable:active{transform:scale(.98)}.card-selected{border:1px solid var(--color-primary);background-color:var(--color-surface-selected);box-shadow:var(--shadow-brand-glow)}.card-selected.card-elevated{box-shadow:var(--shadow-brand-glow),var(--elevation-2)}.card__header{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--lc-density-gap-md, var(--spacing-4));margin-bottom:var(--lc-density-padding-md, var(--spacing-4));padding-bottom:var(--lc-density-padding-sm, var(--spacing-3));border-bottom:1px solid var(--color-border, #e5e7eb)}.card__icon{flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;width:2.25rem;height:2.25rem;border-radius:var(--border-radius-md)}.card__icon--size-sm{width:1.75rem;height:1.75rem;border-radius:var(--border-radius-sm)}.card__icon--brand{background-image:var(--gradient-brand-tile);color:var(--color-on-primary, #fff);box-shadow:var(--shadow-brand-glow)}.card__icon--subtle{background-color:#20849724;color:var(--color-primary-500);border:1px solid var(--color-border-strong)}.card__header-left{flex:1;min-width:0}.card__title-row{display:flex;align-items:center;gap:.5rem}.card__badge-slot{display:inline-flex;align-items:center}.card__badge-slot:empty{display:none}.card__title{margin:0;font-weight:600;font-size:var(--lc-card-title-size, var(--font-size-lg, 1.125rem));line-height:1.3;color:var(--color-text-primary)}.card__badge{display:inline-flex;align-items:center;justify-content:center;min-width:1.25rem;height:1.25rem;padding:0 .375rem;border-radius:9999px;font-size:.75rem;font-weight:600;line-height:1}.card__badge--default{background-color:var(--color-surface-hover);color:var(--color-text-secondary);border:1px solid var(--color-border)}.card__badge--primary{background-color:var(--color-primary-subtle);color:var(--color-on-primary-subtle);border:1px solid var(--color-primary-500)}.card__badge--success{background-color:var(--color-success-subtle);color:var(--color-on-success-subtle)}.card__badge--warning{background-color:var(--color-warning-subtle);color:var(--color-on-warning-subtle)}.card__badge--error{background-color:var(--color-error-subtle);color:var(--color-on-error-subtle)}.card__subtitle{margin:.25rem 0 0;font-size:.8125rem;color:var(--color-text-secondary);line-height:1.4}.card__header-action{flex-shrink:0;display:flex;align-items:center;gap:.5rem}:host ::ng-deep [card-header],:host ::ng-deep .card-header{display:flex;align-items:center;justify-content:space-between;gap:var(--lc-density-gap-sm, var(--spacing-2));margin-bottom:var(--lc-density-padding-md, var(--spacing-4));padding-bottom:var(--lc-density-padding-sm, var(--spacing-3));border-bottom:1px solid var(--color-divider, #e5e7eb);font-weight:600;font-size:1.125rem;color:var(--color-text-primary)}:host ::ng-deep .card-content{color:var(--color-text-primary)}:host ::ng-deep [card-footer],:host ::ng-deep .card-footer{display:flex;align-items:center;justify-content:flex-end;gap:var(--lc-density-gap-sm, var(--spacing-2));margin-top:var(--lc-density-padding-md, var(--spacing-4));padding-top:var(--lc-density-padding-sm, var(--spacing-3));border-top:1px solid var(--color-divider, #e5e7eb);color:var(--color-text-secondary)}:host ::ng-deep .card-dashed lc-icon{color:var(--color-muted-tile-icon, var(--color-text-tertiary))}\n"]
196
196
  }]
197
197
  }],
198
198
  propDecorators: {
@@ -1 +1 @@
1
- {"version":3,"file":"lifecorner-angular-ui-kit-card.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/card/card.component.ts","../../../../libs/angular-ui-kit/src/lib/card/card.component.html"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n input,\n output,\n HostListener,\n computed,\n} from '@angular/core';\nimport { IconComponent } from '@lifecorner/angular-ui-kit/icon';\n\n/** Visual variant of `lc-card`. */\nexport type CardVariant = 'elevated' | 'outlined' | 'flat' | 'raised' | 'dashed';\n\n/** Heading level of `lc-card`'s built-in title. */\nexport type CardHeadingLevel = 2 | 3 | 4 | 5 | 6;\n\n/**\n * Card component for content grouping and visual hierarchy.\n *\n * Features:\n * - Variant styles (elevated, outlined, flat, raised, dashed)\n * - Configurable padding (none, xs, sm, md, lg)\n * - Border radius options (none, sm, md, lg, xl, full)\n * - Optional title display, with an optional leading icon tile\n * - Content projection for flexible body content\n *\n * @example\n * ```html\n * <lc-card title=\"Card Title\" variant=\"elevated\" padding=\"md\">\n * <p>Card body content</p>\n * </lc-card>\n *\n * <!-- With a leading brand-tile icon in the header -->\n * <lc-card title=\"Repository\" subtitle=\"Checkout &amp; profile\" icon=\"git-branch\">\n * ...\n * </lc-card>\n * ```\n */\n@Component({\n selector: 'lc-card',\n standalone: true,\n imports: [IconComponent],\n templateUrl: './card.component.html',\n styleUrls: ['./card.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class CardComponent {\n /**\n * Optional title rendered as a styled header.\n * For complex headers (e.g. title + action button), use content projection instead.\n */\n title = input<string | undefined>(undefined);\n\n /**\n * Optional subtitle rendered below the title.\n */\n subtitle = input<string | undefined>(undefined);\n\n /**\n * Optional leading icon (Tabler icon name) rendered as a teal brand tile to\n * the left of the title. Only shown when `title` is set.\n */\n icon = input<string | undefined>(undefined);\n\n /**\n * Visual treatment of the leading icon tile.\n * - brand: gradient teal tile with light ink (default)\n * - subtle: translucent surface tile with brand-teal ink\n * @default 'brand'\n */\n iconVariant = input<'brand' | 'subtle'>('brand');\n\n /**\n * Size of the leading icon tile.\n * - md: 2.25rem tile (default)\n * - sm: compact ~1.75rem chip for dense card headers\n * @default 'md'\n */\n iconSize = input<'sm' | 'md'>('md');\n\n /**\n * Optional badge text/count shown next to the title (e.g. item count).\n * For a richer status badge (e.g. a leading status dot), project an\n * `lc-badge` into the `card-badge` slot instead — the slot takes precedence.\n */\n badge = input<string | undefined>(undefined);\n\n /**\n * Badge color variant.\n * @default 'default'\n */\n badgeVariant = input<'default' | 'primary' | 'success' | 'warning' | 'error'>('default');\n\n /**\n * Visual variant of the card\n * - elevated: Box shadow (default)\n * - outlined: Border only (the hairline `--color-border`, no shadow) — the\n * content card on the \"Weite\" sky with `borderRadius=\"xl\"`\n * - flat: No shadow or border\n * - raised: plain surface, hairline border and the long, soft\n * `--shadow-auth-card`, no accent line — the sign-in card\n * - dashed: the muted \"coming soon\" tile — `--color-muted-tile` fill, 1 px\n * dashed `--color-muted-tile-border`, secondary ink, icons in\n * `--color-muted-tile-icon`. Never clickable: `clickable` is ignored\n * @default 'elevated'\n */\n variant = input<CardVariant>('elevated');\n\n /**\n * Padding inside the card\n * @default 'md'\n */\n padding = input<'none' | 'xs' | 'sm' | 'md' | 'lg'>('md');\n\n /**\n * Whether the card is clickable (hover effects, cursor pointer)\n * @default false\n */\n clickable = input<boolean>(false);\n\n /**\n * Whether the card is selected (if clickable)\n * @default false\n */\n selected = input<boolean>(false);\n\n /**\n * Border radius size: `none`, `sm`, `md`, `lg` (18 px), `xl` (24 px, the\n * \"Weite\" content card) or `full`.\n * @default 'md'\n */\n borderRadius = input<'none' | 'sm' | 'md' | 'lg' | 'xl' | 'full'>('md');\n\n /**\n * Heading level of the built-in `title` (`h2`–`h6`), so the card fits the\n * page's outline. Semantics only, the look stays the same.\n * @default 3\n */\n headingLevel = input<CardHeadingLevel>(3);\n\n /**\n * ARIA label for accessibility\n */\n ariaLabel = input<string | undefined>(undefined);\n\n /**\n * Emitted when card is clicked (if clickable)\n */\n readonly cardClick = output<MouseEvent>();\n\n /**\n * Whether the card acts as a button: `clickable`, except for the dashed\n * \"coming soon\" tile, which is never interactive.\n */\n protected isClickable = computed(() => this.clickable() && this.variant() !== 'dashed');\n\n /**\n * Compute card CSS classes based on inputs\n */\n protected cardClasses = computed(() => {\n const classes = [\n 'card',\n `card-${this.variant()}`,\n `card-padding-${this.padding()}`,\n `card-radius-${this.borderRadius()}`,\n ];\n\n if (this.isClickable()) {\n classes.push('card-clickable');\n }\n\n // The dashed tile is never interactive, so it is never selected either.\n if (this.selected() && this.variant() !== 'dashed') {\n classes.push('card-selected');\n }\n\n return classes.join(' ');\n });\n\n /**\n * Compute ARIA role based on clickable state\n */\n protected role = computed(() => (this.isClickable() ? 'button' : 'article'));\n\n /**\n * Handle card click events\n */\n protected handleClick(event: MouseEvent): void {\n if (this.isClickable()) {\n this.cardClick.emit(event);\n }\n }\n\n /**\n * Handle keyboard events for accessibility\n */\n @HostListener('keydown', ['$event'])\n protected handleKeydown(event: KeyboardEvent): void {\n if (this.isClickable() && (event.key === 'Enter' || event.key === ' ')) {\n event.preventDefault();\n const mouseEvent = new MouseEvent('click', {\n bubbles: true,\n cancelable: true,\n });\n this.cardClick.emit(mouseEvent);\n }\n }\n}\n","<!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events -- Enter/Space are handled by the host keydown listener (role=button, tabindex=0 when clickable) -->\n<div\n [class]=\"cardClasses()\"\n [attr.role]=\"role()\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.tabindex]=\"isClickable() ? 0 : null\"\n (click)=\"handleClick($event)\"\n>\n @if (title()) {\n <div class=\"card__header\">\n @if (icon()) {\n <div\n class=\"card__icon card__icon--{{ iconVariant() }} card__icon--size-{{ iconSize() }}\"\n aria-hidden=\"true\"\n >\n <lc-icon [name]=\"icon()!\" [size]=\"iconSize() === 'sm' ? 'xs' : 'sm'\" [decorative]=\"true\" />\n </div>\n }\n <div class=\"card__header-left\">\n <div class=\"card__title-row\">\n @switch (headingLevel()) {\n @case (2) {\n <h2 class=\"card__title\">{{ title() }}</h2>\n }\n @case (4) {\n <h4 class=\"card__title\">{{ title() }}</h4>\n }\n @case (5) {\n <h5 class=\"card__title\">{{ title() }}</h5>\n }\n @case (6) {\n <h6 class=\"card__title\">{{ title() }}</h6>\n }\n @default {\n <h3 class=\"card__title\">{{ title() }}</h3>\n }\n }\n <!-- Rich status badge: project an lc-badge here (takes visual\n precedence over the `badge` string). Wrapper hides when empty. -->\n <span class=\"card__badge-slot\"><ng-content select=\"[card-badge]\"></ng-content></span>\n @if (badge()) {\n <span class=\"card__badge card__badge--{{ badgeVariant() }}\">{{ badge() }}</span>\n }\n </div>\n @if (subtitle()) {\n <p class=\"card__subtitle\">{{ subtitle() }}</p>\n }\n </div>\n <div class=\"card__header-action\">\n <ng-content select=\"[card-header-action]\"></ng-content>\n </div>\n </div>\n }\n <ng-content select=\"[card-header]\"></ng-content>\n <div class=\"card__body\">\n <ng-content></ng-content>\n </div>\n <ng-content select=\"[card-footer]\"></ng-content>\n</div>\n"],"mappings":"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"}
1
+ {"version":3,"file":"lifecorner-angular-ui-kit-card.mjs","names":[],"sources":["../../../../libs/angular-ui-kit/src/lib/card/card.component.ts","../../../../libs/angular-ui-kit/src/lib/card/card.component.html"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n input,\n output,\n HostListener,\n computed,\n} from '@angular/core';\nimport { IconComponent } from '@lifecorner/angular-ui-kit/icon';\n\n/** Visual variant of `lc-card`. */\nexport type CardVariant = 'elevated' | 'outlined' | 'flat' | 'raised' | 'dashed';\n\n/** Heading level of `lc-card`'s built-in title. */\nexport type CardHeadingLevel = 2 | 3 | 4 | 5 | 6;\n\n/**\n * Card component for content grouping and visual hierarchy.\n *\n * Features:\n * - Variant styles (elevated, outlined, flat, raised, dashed)\n * - Configurable padding (none, xs, sm, md, lg)\n * - Border radius options (none, sm, md, lg, xl, full)\n * - Optional title display, with an optional leading icon tile\n * - Content projection for flexible body content\n *\n * @example\n * ```html\n * <lc-card title=\"Card Title\" variant=\"elevated\" padding=\"md\">\n * <p>Card body content</p>\n * </lc-card>\n *\n * <!-- With a leading brand-tile icon in the header -->\n * <lc-card title=\"Repository\" subtitle=\"Checkout &amp; profile\" icon=\"git-branch\">\n * ...\n * </lc-card>\n * ```\n */\n@Component({\n selector: 'lc-card',\n standalone: true,\n imports: [IconComponent],\n templateUrl: './card.component.html',\n styleUrls: ['./card.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class CardComponent {\n /**\n * Optional title rendered as a styled header.\n * For complex headers (e.g. title + action button), use content projection instead.\n */\n title = input<string | undefined>(undefined);\n\n /**\n * Optional subtitle rendered below the title.\n */\n subtitle = input<string | undefined>(undefined);\n\n /**\n * Optional leading icon (Tabler icon name) rendered as a teal brand tile to\n * the left of the title. Only shown when `title` is set.\n */\n icon = input<string | undefined>(undefined);\n\n /**\n * Visual treatment of the leading icon tile.\n * - brand: gradient teal tile with light ink (default)\n * - subtle: translucent surface tile with brand-teal ink\n * @default 'brand'\n */\n iconVariant = input<'brand' | 'subtle'>('brand');\n\n /**\n * Size of the leading icon tile.\n * - md: 2.25rem tile (default)\n * - sm: compact ~1.75rem chip for dense card headers\n * @default 'md'\n */\n iconSize = input<'sm' | 'md'>('md');\n\n /**\n * Optional badge text/count shown next to the title (e.g. item count).\n * For a richer status badge (e.g. a leading status dot), project an\n * `lc-badge` into the `card-badge` slot instead — the slot takes precedence.\n */\n badge = input<string | undefined>(undefined);\n\n /**\n * Badge color variant.\n * @default 'default'\n */\n badgeVariant = input<'default' | 'primary' | 'success' | 'warning' | 'error'>('default');\n\n /**\n * Visual variant of the card\n * - elevated: Box shadow (default)\n * - outlined: Border only (the hairline `--color-border`, no shadow) — the\n * content card on the \"Weite\" sky with `borderRadius=\"xl\"`\n * - flat: No shadow or border\n * - raised: plain surface, hairline border and the long, soft\n * `--shadow-auth-card`, no accent line — the sign-in card\n * - dashed: the muted \"coming soon\" tile — `--color-muted-tile` fill, 1 px\n * dashed `--color-muted-tile-border`, secondary ink, icons in\n * `--color-muted-tile-icon`. Never clickable: `clickable` is ignored\n * @default 'elevated'\n */\n variant = input<CardVariant>('elevated');\n\n /**\n * Padding inside the card\n * @default 'md'\n */\n padding = input<'none' | 'xs' | 'sm' | 'md' | 'lg'>('md');\n\n /**\n * Whether the card is clickable (hover effects, cursor pointer)\n * @default false\n */\n clickable = input<boolean>(false);\n\n /**\n * Whether the card is selected (if clickable)\n * @default false\n */\n selected = input<boolean>(false);\n\n /**\n * Border radius size: `none`, `sm`, `md`, `lg` (18 px), `xl` (24 px, the\n * \"Weite\" content card) or `full`.\n * @default 'md'\n */\n borderRadius = input<'none' | 'sm' | 'md' | 'lg' | 'xl' | 'full'>('md');\n\n /**\n * Heading level of the built-in `title` (`h2`–`h6`), so the card fits the\n * page's outline. Semantics only, the look stays the same.\n * @default 3\n */\n headingLevel = input<CardHeadingLevel>(3);\n\n /**\n * ARIA label for accessibility\n */\n ariaLabel = input<string | undefined>(undefined);\n\n /**\n * Emitted when card is clicked (if clickable)\n */\n readonly cardClick = output<MouseEvent>();\n\n /**\n * Whether the card acts as a button: `clickable`, except for the dashed\n * \"coming soon\" tile, which is never interactive.\n */\n protected isClickable = computed(() => this.clickable() && this.variant() !== 'dashed');\n\n /**\n * Compute card CSS classes based on inputs\n */\n protected cardClasses = computed(() => {\n const classes = [\n 'card',\n `card-${this.variant()}`,\n `card-padding-${this.padding()}`,\n `card-radius-${this.borderRadius()}`,\n ];\n\n if (this.isClickable()) {\n classes.push('card-clickable');\n }\n\n // The dashed tile is never interactive, so it is never selected either.\n if (this.selected() && this.variant() !== 'dashed') {\n classes.push('card-selected');\n }\n\n return classes.join(' ');\n });\n\n /**\n * Compute ARIA role based on clickable state\n */\n protected role = computed(() => (this.isClickable() ? 'button' : 'article'));\n\n /**\n * Handle card click events\n */\n protected handleClick(event: MouseEvent): void {\n if (this.isClickable()) {\n this.cardClick.emit(event);\n }\n }\n\n /**\n * Handle keyboard events for accessibility\n */\n @HostListener('keydown', ['$event'])\n protected handleKeydown(event: KeyboardEvent): void {\n if (this.isClickable() && (event.key === 'Enter' || event.key === ' ')) {\n event.preventDefault();\n const mouseEvent = new MouseEvent('click', {\n bubbles: true,\n cancelable: true,\n });\n this.cardClick.emit(mouseEvent);\n }\n }\n}\n","<!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events -- Enter/Space are handled by the host keydown listener (role=button, tabindex=0 when clickable) -->\n<div\n [class]=\"cardClasses()\"\n [attr.role]=\"role()\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.tabindex]=\"isClickable() ? 0 : null\"\n (click)=\"handleClick($event)\"\n>\n @if (title()) {\n <div class=\"card__header\">\n @if (icon()) {\n <div\n class=\"card__icon card__icon--{{ iconVariant() }} card__icon--size-{{ iconSize() }}\"\n aria-hidden=\"true\"\n >\n <lc-icon [name]=\"icon()!\" [size]=\"iconSize() === 'sm' ? 'xs' : 'sm'\" [decorative]=\"true\" />\n </div>\n }\n <div class=\"card__header-left\">\n <div class=\"card__title-row\">\n @switch (headingLevel()) {\n @case (2) {\n <h2 class=\"card__title\">{{ title() }}</h2>\n }\n @case (4) {\n <h4 class=\"card__title\">{{ title() }}</h4>\n }\n @case (5) {\n <h5 class=\"card__title\">{{ title() }}</h5>\n }\n @case (6) {\n <h6 class=\"card__title\">{{ title() }}</h6>\n }\n @default {\n <h3 class=\"card__title\">{{ title() }}</h3>\n }\n }\n <!-- Rich status badge: project an lc-badge here (takes visual\n precedence over the `badge` string). Wrapper hides when empty. -->\n <span class=\"card__badge-slot\"><ng-content select=\"[card-badge]\"></ng-content></span>\n @if (badge()) {\n <span class=\"card__badge card__badge--{{ badgeVariant() }}\">{{ badge() }}</span>\n }\n </div>\n @if (subtitle()) {\n <p class=\"card__subtitle\">{{ subtitle() }}</p>\n }\n </div>\n <div class=\"card__header-action\">\n <ng-content select=\"[card-header-action]\"></ng-content>\n </div>\n </div>\n }\n <ng-content select=\"[card-header]\"></ng-content>\n <div class=\"card__body\">\n <ng-content></ng-content>\n </div>\n <ng-content select=\"[card-footer]\"></ng-content>\n</div>\n"],"mappings":"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"}
@@ -156,7 +156,7 @@ const RELATION_STYLES = {
156
156
  label: "requires"
157
157
  },
158
158
  extends: {
159
- color: "var(--color-primary-400)",
159
+ color: "var(--color-on-primary-subtle)",
160
160
  dashed: true,
161
161
  label: "extends"
162
162
  },
@@ -166,7 +166,7 @@ const RELATION_STYLES = {
166
166
  label: "implements"
167
167
  },
168
168
  uses: {
169
- color: "var(--color-neutral-300)",
169
+ color: "var(--color-text-tertiary)",
170
170
  dashed: true,
171
171
  label: "uses"
172
172
  }
@@ -1532,7 +1532,7 @@ var DependencyViewerComponent = class DependencyViewerComponent {
1532
1532
  isSignal: true
1533
1533
  }],
1534
1534
  ngImport: i0,
1535
- template: "<!-- Toolbar. The zoom cluster is navigation, so it goes with `interactive`; the\n direction indicator is a read-out and stays. -->\n@if (showToolbar()) {\n <div class=\"dep-viewer__toolbar\">\n @if (interactive()) {\n <button class=\"dep-viewer__btn\" (click)=\"zoomIn()\" [attr.title]=\"zoomInLabel()\" [attr.aria-label]=\"zoomInLabel()\">+</button>\n <span class=\"dep-viewer__zoom\">{{ zoomLabel() }}%</span>\n <button class=\"dep-viewer__btn\" (click)=\"zoomOut()\" [attr.title]=\"zoomOutLabel()\" [attr.aria-label]=\"zoomOutLabel()\">−</button>\n <button class=\"dep-viewer__btn dep-viewer__btn--reset\" (click)=\"resetZoom()\" [attr.title]=\"resetZoomLabel()\" [attr.aria-label]=\"resetZoomLabel()\">⟲</button>\n }\n <span class=\"dep-viewer__direction-label\">{{ direction() === 'horizontal' ? '→' : '↓' }}</span>\n </div>\n}\n\n<!-- Canvas -->\n<!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -- background click clears the pointer selection; node selection is pointer-only in this component (known gap) -->\n<div\n #canvas\n class=\"dep-viewer__canvas\"\n [class.dep-viewer__canvas--static]=\"!interactive()\"\n [class.dep-viewer__canvas--sized]=\"contentHeight() !== null\"\n [style.height.px]=\"contentHeight()\"\n (mousedown)=\"onMouseDown($event)\"\n (wheel)=\"onWheel($event)\"\n (click)=\"deselectNode()\"\n>\n <svg\n [attr.width]=\"svgWidth()\"\n [attr.height]=\"svgHeight()\"\n [attr.viewBox]=\"viewBox()\"\n [style.transform]=\"transform()\"\n class=\"dep-viewer__svg\"\n >\n <!-- Arrow markers for cross-reference edges -->\n <defs>\n <marker id=\"arrow-default\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-neutral-400)\"/></marker>\n <marker id=\"arrow-blocks\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-on-error-subtle)\"/></marker>\n <marker id=\"arrow-references\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-on-info-subtle)\"/></marker>\n <marker id=\"arrow-requires\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-on-warning-subtle)\"/></marker>\n <marker id=\"arrow-extends\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-primary-400)\"/></marker>\n <marker id=\"arrow-implements\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-on-success-subtle)\"/></marker>\n <marker id=\"arrow-uses\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-neutral-300)\"/></marker>\n <marker id=\"arrow-depends\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-neutral-400)\"/></marker>\n </defs>\n\n <!-- Edges. Their labels are deliberately NOT drawn here — see below. -->\n @for (edge of graph().edges; track edge.id) {\n <path\n [attr.d]=\"edge.path\"\n [attr.stroke]=\"edge.color\"\n [attr.stroke-width]=\"edgeWidth()\"\n [attr.stroke-dasharray]=\"edge.dashed ? '6 3' : 'none'\"\n [attr.marker-end]=\"edge.arrow ? 'url(#arrow-' + edge.marker + ')' : ''\"\n fill=\"none\"\n class=\"dep-viewer__edge\"\n [class.dep-viewer__edge--cross-ref]=\"edge.isCrossRef\"\n />\n }\n\n <!-- Nodes -->\n @for (node of graph().nodes; track node.id) {\n <g\n class=\"dep-viewer__node\"\n [class.dep-viewer__node--selected]=\"selectedNodeId() === node.id\"\n (click)=\"selectNode(node.id, $event)\"\n (dblclick)=\"expandNode(node.id, $event)\"\n >\n <!-- A label the box couldn't hold in full is offered whole as a tooltip. -->\n @if (node.truncated) {\n <title>{{ node.label }}</title>\n }\n <!-- Opaque backdrop. The status fills are translucent tints (in the dark\n theme --color-success-50 & co. resolve to rgba(…, 0.18)), so without\n something solid beneath them the edges routed under a node show\n straight through it. This keeps the tint compositing against the\n viewer's own surface instead of against whatever passes behind. -->\n <rect\n [attr.x]=\"node.x\"\n [attr.y]=\"node.y\"\n [attr.width]=\"node.width\"\n [attr.height]=\"node.height\"\n rx=\"6\" ry=\"6\"\n class=\"dep-viewer__node-backdrop\"\n />\n <rect\n class=\"dep-viewer__node-fill\"\n [attr.x]=\"node.x\"\n [attr.y]=\"node.y\"\n [attr.width]=\"node.width\"\n [attr.height]=\"node.height\"\n rx=\"6\" ry=\"6\"\n [attr.fill]=\"getNodeBg(node)\"\n [attr.stroke]=\"selectedNodeId() === node.id ? 'var(--color-primary-500)' : getNodeBorder(node)\"\n stroke-width=\"1.5\"\n />\n <!-- One <tspan> per wrapped line, each restarting at the box's centre; the\n block of lines is centred on the box vertically. No whitespace between\n the spans — it would end up in the text. -->\n <text\n [attr.x]=\"node.x + node.width / 2\"\n [attr.y]=\"node.y + node.height / 2 + baselineShift - ((node.lines.length - 1) * lineHeight) / 2\"\n text-anchor=\"middle\"\n [attr.fill]=\"getNodeText(node)\"\n class=\"dep-viewer__node-label\"\n >@for (line of node.lines; track $index) {<tspan [attr.x]=\"node.x + node.width / 2\" [attr.dy]=\"$first ? 0 : lineHeight\">{{ line }}</tspan>}</text>\n\n <!-- \"+N\" marker: this node has more neighbours than are shown -->\n @if (node.moreCount) {\n <g class=\"dep-viewer__more\">\n <rect\n [attr.x]=\"node.x + node.width - 26\"\n [attr.y]=\"node.y - 8\"\n width=\"32\" height=\"16\" rx=\"8\" ry=\"8\"\n />\n <text\n [attr.x]=\"node.x + node.width - 10\"\n [attr.y]=\"node.y + 3\"\n text-anchor=\"middle\"\n >+{{ node.moreCount }}</text>\n </g>\n }\n\n <!-- Collapse/expand toggle -->\n @if (hasChildren(node.id)) {\n <g\n class=\"dep-viewer__toggle\"\n (click)=\"toggleCollapse(node.id, $event)\"\n >\n @if (direction() === 'horizontal') {\n <circle\n [attr.cx]=\"node.x + node.width + 10\"\n [attr.cy]=\"node.y + node.height / 2\"\n r=\"8\"\n fill=\"var(--color-neutral-100)\" stroke=\"var(--color-neutral-300)\" stroke-width=\"1\"\n />\n <text\n [attr.x]=\"node.x + node.width + 10\"\n [attr.y]=\"node.y + node.height / 2 + 4\"\n text-anchor=\"middle\"\n class=\"dep-viewer__toggle-text\"\n >{{ isCollapsed(node.id) ? '+' : '−' }}</text>\n } @else {\n <circle\n [attr.cx]=\"node.x + node.width / 2\"\n [attr.cy]=\"node.y + node.height + 10\"\n r=\"8\"\n fill=\"var(--color-neutral-100)\" stroke=\"var(--color-neutral-300)\" stroke-width=\"1\"\n />\n <text\n [attr.x]=\"node.x + node.width / 2\"\n [attr.y]=\"node.y + node.height + 14\"\n text-anchor=\"middle\"\n class=\"dep-viewer__toggle-text\"\n >{{ isCollapsed(node.id) ? '+' : '−' }}</text>\n }\n </g>\n }\n </g>\n }\n\n <!-- Edge labels last: SVG paints in document order, so anything drawn before\n the nodes is covered by them. A label sits at the midpoint of its edge,\n which frequently lands on a node — it has to come after. The halo (see\n the stylesheet) keeps it legible over whatever it lands on. -->\n @if (showEdgeLabels()) {\n @for (edge of graph().edges; track edge.id) {\n @if (edge.label) {\n <text\n [attr.x]=\"edge.labelX\"\n [attr.y]=\"edge.labelY\"\n class=\"dep-viewer__edge-label\"\n [attr.fill]=\"edge.color\"\n [attr.text-anchor]=\"edge.labelAnchor ?? 'middle'\"\n >{{ edge.label }}</text>\n }\n }\n }\n </svg>\n</div>\n\n<!-- Legend -->\n@if (legendItems().length || typeLegendItems().length) {\n <div class=\"dep-viewer__legend\">\n @for (item of legendItems(); track item.label) {\n <span class=\"dep-viewer__legend-item\">\n <svg width=\"24\" height=\"10\">\n <line\n x1=\"0\" y1=\"5\" x2=\"24\" y2=\"5\"\n [attr.stroke]=\"item.color\"\n stroke-width=\"2\"\n [attr.stroke-dasharray]=\"item.dashed ? '4 2' : 'none'\"\n />\n </svg>\n <span class=\"dep-viewer__legend-text\">{{ item.label }}</span>\n </span>\n }\n @for (item of typeLegendItems(); track item.type) {\n <span class=\"dep-viewer__legend-item\">\n <svg width=\"10\" height=\"10\">\n <rect width=\"10\" height=\"10\" rx=\"2\" ry=\"2\" [attr.fill]=\"item.color\" />\n </svg>\n <span class=\"dep-viewer__legend-text\">{{ item.type }}</span>\n </span>\n }\n </div>\n}\n\n<!-- Detail panel -->\n@if (selectedNode(); as node) {\n <div class=\"dep-viewer__detail\">\n <div class=\"dep-viewer__detail-header\">\n <span class=\"dep-viewer__detail-status dep-viewer__detail-status--{{ node.status }}\">●</span>\n <strong>{{ node.label }}</strong>\n @if (node.type) {\n <span class=\"dep-viewer__detail-type\">{{ node.type }}</span>\n }\n </div>\n @if (node.description) {\n <p class=\"dep-viewer__detail-desc\">{{ node.description }}</p>\n }\n <div class=\"dep-viewer__detail-meta\">\n <span>{{ depthLabel() }} {{ node.depth }}</span>\n <span>{{ childrenLabel() }} {{ node.outgoingCount }}</span>\n <span>{{ dependsOnLabel() }} {{ node.incomingCount }}</span>\n </div>\n @if (selectedDependsOn().length) {\n <div class=\"dep-viewer__detail-deps\">\n <strong>{{ dependenciesLabel() }}</strong>\n <ul>\n @for (dep of selectedDependsOn(); track dep.id) {\n <li>\n <span class=\"dep-viewer__dep-relation\">{{\n dep.relationLabel || getRelationLabel(dep.relation || 'depends')\n }}</span>\n → {{ dep.id }}\n </li>\n }\n </ul>\n </div>\n }\n </div>\n}\n",
1535
+ template: "<!-- Toolbar. The zoom cluster is navigation, so it goes with `interactive`; the\n direction indicator is a read-out and stays. -->\n@if (showToolbar()) {\n <div class=\"dep-viewer__toolbar\">\n @if (interactive()) {\n <button class=\"dep-viewer__btn\" (click)=\"zoomIn()\" [attr.title]=\"zoomInLabel()\" [attr.aria-label]=\"zoomInLabel()\">+</button>\n <span class=\"dep-viewer__zoom\">{{ zoomLabel() }}%</span>\n <button class=\"dep-viewer__btn\" (click)=\"zoomOut()\" [attr.title]=\"zoomOutLabel()\" [attr.aria-label]=\"zoomOutLabel()\">−</button>\n <button class=\"dep-viewer__btn dep-viewer__btn--reset\" (click)=\"resetZoom()\" [attr.title]=\"resetZoomLabel()\" [attr.aria-label]=\"resetZoomLabel()\">⟲</button>\n }\n <span class=\"dep-viewer__direction-label\">{{ direction() === 'horizontal' ? '→' : '↓' }}</span>\n </div>\n}\n\n<!-- Canvas -->\n<!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -- background click clears the pointer selection; node selection is pointer-only in this component (known gap) -->\n<div\n #canvas\n class=\"dep-viewer__canvas\"\n [class.dep-viewer__canvas--static]=\"!interactive()\"\n [class.dep-viewer__canvas--sized]=\"contentHeight() !== null\"\n [style.height.px]=\"contentHeight()\"\n (mousedown)=\"onMouseDown($event)\"\n (wheel)=\"onWheel($event)\"\n (click)=\"deselectNode()\"\n>\n <svg\n [attr.width]=\"svgWidth()\"\n [attr.height]=\"svgHeight()\"\n [attr.viewBox]=\"viewBox()\"\n [style.transform]=\"transform()\"\n class=\"dep-viewer__svg\"\n >\n <!-- Arrow markers for cross-reference edges -->\n <defs>\n <marker id=\"arrow-default\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-neutral-400)\"/></marker>\n <marker id=\"arrow-blocks\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-on-error-subtle)\"/></marker>\n <marker id=\"arrow-references\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-on-info-subtle)\"/></marker>\n <marker id=\"arrow-requires\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-on-warning-subtle)\"/></marker>\n <marker id=\"arrow-extends\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-on-primary-subtle)\"/></marker>\n <marker id=\"arrow-implements\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-on-success-subtle)\"/></marker>\n <marker id=\"arrow-uses\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-text-tertiary)\"/></marker>\n <marker id=\"arrow-depends\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-neutral-400)\"/></marker>\n </defs>\n\n <!-- Edges. Their labels are deliberately NOT drawn here — see below. -->\n @for (edge of graph().edges; track edge.id) {\n <path\n [attr.d]=\"edge.path\"\n [attr.stroke]=\"edge.color\"\n [attr.stroke-width]=\"edgeWidth()\"\n [attr.stroke-dasharray]=\"edge.dashed ? '6 3' : 'none'\"\n [attr.marker-end]=\"edge.arrow ? 'url(#arrow-' + edge.marker + ')' : ''\"\n fill=\"none\"\n class=\"dep-viewer__edge\"\n [class.dep-viewer__edge--cross-ref]=\"edge.isCrossRef\"\n />\n }\n\n <!-- Nodes -->\n @for (node of graph().nodes; track node.id) {\n <g\n class=\"dep-viewer__node\"\n [class.dep-viewer__node--selected]=\"selectedNodeId() === node.id\"\n (click)=\"selectNode(node.id, $event)\"\n (dblclick)=\"expandNode(node.id, $event)\"\n >\n <!-- A label the box couldn't hold in full is offered whole as a tooltip. -->\n @if (node.truncated) {\n <title>{{ node.label }}</title>\n }\n <!-- Opaque backdrop. The status fills are translucent tints (in the dark\n theme --color-success-50 & co. resolve to rgba(…, 0.18)), so without\n something solid beneath them the edges routed under a node show\n straight through it. This keeps the tint compositing against the\n viewer's own surface instead of against whatever passes behind. -->\n <rect\n [attr.x]=\"node.x\"\n [attr.y]=\"node.y\"\n [attr.width]=\"node.width\"\n [attr.height]=\"node.height\"\n rx=\"6\" ry=\"6\"\n class=\"dep-viewer__node-backdrop\"\n />\n <rect\n class=\"dep-viewer__node-fill\"\n [attr.x]=\"node.x\"\n [attr.y]=\"node.y\"\n [attr.width]=\"node.width\"\n [attr.height]=\"node.height\"\n rx=\"6\" ry=\"6\"\n [attr.fill]=\"getNodeBg(node)\"\n [attr.stroke]=\"selectedNodeId() === node.id ? 'var(--color-primary-500)' : getNodeBorder(node)\"\n stroke-width=\"1.5\"\n />\n <!-- One <tspan> per wrapped line, each restarting at the box's centre; the\n block of lines is centred on the box vertically. No whitespace between\n the spans — it would end up in the text. -->\n <text\n [attr.x]=\"node.x + node.width / 2\"\n [attr.y]=\"node.y + node.height / 2 + baselineShift - ((node.lines.length - 1) * lineHeight) / 2\"\n text-anchor=\"middle\"\n [attr.fill]=\"getNodeText(node)\"\n class=\"dep-viewer__node-label\"\n >@for (line of node.lines; track $index) {<tspan [attr.x]=\"node.x + node.width / 2\" [attr.dy]=\"$first ? 0 : lineHeight\">{{ line }}</tspan>}</text>\n\n <!-- \"+N\" marker: this node has more neighbours than are shown -->\n @if (node.moreCount) {\n <g class=\"dep-viewer__more\">\n <rect\n [attr.x]=\"node.x + node.width - 26\"\n [attr.y]=\"node.y - 8\"\n width=\"32\" height=\"16\" rx=\"8\" ry=\"8\"\n />\n <text\n [attr.x]=\"node.x + node.width - 10\"\n [attr.y]=\"node.y + 3\"\n text-anchor=\"middle\"\n >+{{ node.moreCount }}</text>\n </g>\n }\n\n <!-- Collapse/expand toggle -->\n @if (hasChildren(node.id)) {\n <g\n class=\"dep-viewer__toggle\"\n (click)=\"toggleCollapse(node.id, $event)\"\n >\n @if (direction() === 'horizontal') {\n <circle\n [attr.cx]=\"node.x + node.width + 10\"\n [attr.cy]=\"node.y + node.height / 2\"\n r=\"8\"\n fill=\"var(--color-neutral-100)\" stroke=\"var(--color-neutral-300)\" stroke-width=\"1\"\n />\n <text\n [attr.x]=\"node.x + node.width + 10\"\n [attr.y]=\"node.y + node.height / 2 + 4\"\n text-anchor=\"middle\"\n class=\"dep-viewer__toggle-text\"\n >{{ isCollapsed(node.id) ? '+' : '−' }}</text>\n } @else {\n <circle\n [attr.cx]=\"node.x + node.width / 2\"\n [attr.cy]=\"node.y + node.height + 10\"\n r=\"8\"\n fill=\"var(--color-neutral-100)\" stroke=\"var(--color-neutral-300)\" stroke-width=\"1\"\n />\n <text\n [attr.x]=\"node.x + node.width / 2\"\n [attr.y]=\"node.y + node.height + 14\"\n text-anchor=\"middle\"\n class=\"dep-viewer__toggle-text\"\n >{{ isCollapsed(node.id) ? '+' : '−' }}</text>\n }\n </g>\n }\n </g>\n }\n\n <!-- Edge labels last: SVG paints in document order, so anything drawn before\n the nodes is covered by them. A label sits at the midpoint of its edge,\n which frequently lands on a node — it has to come after. The halo (see\n the stylesheet) keeps it legible over whatever it lands on. -->\n @if (showEdgeLabels()) {\n @for (edge of graph().edges; track edge.id) {\n @if (edge.label) {\n <text\n [attr.x]=\"edge.labelX\"\n [attr.y]=\"edge.labelY\"\n class=\"dep-viewer__edge-label\"\n [attr.fill]=\"edge.color\"\n [attr.text-anchor]=\"edge.labelAnchor ?? 'middle'\"\n >{{ edge.label }}</text>\n }\n }\n }\n </svg>\n</div>\n\n<!-- Legend -->\n@if (legendItems().length || typeLegendItems().length) {\n <div class=\"dep-viewer__legend\">\n @for (item of legendItems(); track item.label) {\n <span class=\"dep-viewer__legend-item\">\n <svg width=\"24\" height=\"10\">\n <line\n x1=\"0\" y1=\"5\" x2=\"24\" y2=\"5\"\n [attr.stroke]=\"item.color\"\n stroke-width=\"2\"\n [attr.stroke-dasharray]=\"item.dashed ? '4 2' : 'none'\"\n />\n </svg>\n <span class=\"dep-viewer__legend-text\">{{ item.label }}</span>\n </span>\n }\n @for (item of typeLegendItems(); track item.type) {\n <span class=\"dep-viewer__legend-item\">\n <svg width=\"10\" height=\"10\">\n <rect width=\"10\" height=\"10\" rx=\"2\" ry=\"2\" [attr.fill]=\"item.color\" />\n </svg>\n <span class=\"dep-viewer__legend-text\">{{ item.type }}</span>\n </span>\n }\n </div>\n}\n\n<!-- Detail panel -->\n@if (selectedNode(); as node) {\n <div class=\"dep-viewer__detail\">\n <div class=\"dep-viewer__detail-header\">\n <span class=\"dep-viewer__detail-status dep-viewer__detail-status--{{ node.status }}\">●</span>\n <strong>{{ node.label }}</strong>\n @if (node.type) {\n <span class=\"dep-viewer__detail-type\">{{ node.type }}</span>\n }\n </div>\n @if (node.description) {\n <p class=\"dep-viewer__detail-desc\">{{ node.description }}</p>\n }\n <div class=\"dep-viewer__detail-meta\">\n <span>{{ depthLabel() }} {{ node.depth }}</span>\n <span>{{ childrenLabel() }} {{ node.outgoingCount }}</span>\n <span>{{ dependsOnLabel() }} {{ node.incomingCount }}</span>\n </div>\n @if (selectedDependsOn().length) {\n <div class=\"dep-viewer__detail-deps\">\n <strong>{{ dependenciesLabel() }}</strong>\n <ul>\n @for (dep of selectedDependsOn(); track dep.id) {\n <li>\n <span class=\"dep-viewer__dep-relation\">{{\n dep.relationLabel || getRelationLabel(dep.relation || 'depends')\n }}</span>\n → {{ dep.id }}\n </li>\n }\n </ul>\n </div>\n }\n </div>\n}\n",
1536
1536
  styles: [":host{display:flex;flex-direction:column;position:relative;overflow:hidden;border:1px solid var(--color-border);border-radius:8px;background:var(--color-surface)}.dep-viewer__toolbar{display:flex;align-items:center;gap:4px;padding:8px 12px;border-bottom:1px solid var(--color-border);background:var(--color-surface-2);-webkit-user-select:none;user-select:none}.dep-viewer__btn{width:28px;height:28px;display:flex;align-items:center;justify-content:center;border:1px solid var(--color-border);border-radius:4px;background:var(--color-surface);color:var(--color-text-secondary);cursor:pointer;font-size:14px;line-height:1}.dep-viewer__btn:hover{background:var(--color-surface-hover)}.dep-viewer__btn--reset{margin-left:4px}.dep-viewer__zoom{font-size:12px;min-width:40px;text-align:center;color:var(--color-text-secondary)}.dep-viewer__direction-label{margin-left:8px;font-size:14px;color:var(--color-text-tertiary)}.dep-viewer__canvas{cursor:grab;overflow:hidden;width:100%;flex:1 1 auto;min-height:0}.dep-viewer__canvas:active{cursor:grabbing}.dep-viewer__canvas--static,.dep-viewer__canvas--static:active{cursor:default}.dep-viewer__canvas--sized{flex:0 0 auto}.dep-viewer__svg{transform-origin:0 0;transition:transform .1s ease-out;overflow:visible}.dep-viewer__edge{pointer-events:none;transition:opacity .15s}.dep-viewer__edge--cross-ref{opacity:.8}.dep-viewer__edge-label{font-size:9px;font-weight:500;pointer-events:none;-webkit-user-select:none;user-select:none;dominant-baseline:central;paint-order:stroke;stroke:var(--color-surface);stroke-width:3px;stroke-linejoin:round}.dep-viewer__node{cursor:pointer;transition:filter .15s}.dep-viewer__node:hover{filter:brightness(.96)}.dep-viewer__node--selected rect{stroke-width:2.5}.dep-viewer__node-backdrop{fill:var(--color-surface)}.dep-viewer__node-label{font-size:12px;font-weight:500;pointer-events:none;-webkit-user-select:none;user-select:none}.dep-viewer__toggle{cursor:pointer}.dep-viewer__toggle:hover circle{fill:var(--color-surface-hover)}.dep-viewer__more{pointer-events:none}.dep-viewer__more rect{fill:var(--color-surface-2);stroke:var(--color-border-strong);stroke-width:1}.dep-viewer__more text{font-size:9px;font-weight:600;fill:var(--color-text-secondary);-webkit-user-select:none;user-select:none}.dep-viewer__toggle-text{font-size:11px;font-weight:600;fill:var(--color-text-secondary);pointer-events:none;-webkit-user-select:none;user-select:none}.dep-viewer__legend{display:flex;flex-wrap:wrap;gap:12px;padding:6px 12px;border-top:1px solid var(--color-border);background:var(--color-surface-2)}.dep-viewer__legend-item{display:flex;align-items:center;gap:4px}.dep-viewer__legend-text{font-size:11px;color:var(--color-text-secondary)}.dep-viewer__detail{position:absolute;bottom:12px;right:12px;width:240px;padding:12px;background:var(--color-surface-2);border:1px solid var(--color-border);border-radius:8px;box-shadow:var(--elevation-1);font-size:12px;z-index:10}.dep-viewer__detail-header{display:flex;align-items:center;gap:6px;margin-bottom:4px;font-size:13px}.dep-viewer__detail-type{margin-left:auto;padding:1px 6px;border-radius:3px;background:var(--color-surface-hover);color:var(--color-text-secondary);font-size:10px;font-weight:500}.dep-viewer__detail-status{font-size:10px}.dep-viewer__detail-status--default{color:var(--color-text-tertiary)}.dep-viewer__detail-status--active{color:var(--color-primary-400)}.dep-viewer__detail-status--success{color:var(--color-on-success-subtle)}.dep-viewer__detail-status--warning{color:var(--color-on-warning-subtle)}.dep-viewer__detail-status--error{color:var(--color-on-error-subtle)}.dep-viewer__detail-status--muted{color:var(--color-text-disabled)}.dep-viewer__detail-desc{color:var(--color-text-secondary);margin:0 0 6px;line-height:1.4}.dep-viewer__detail-meta{display:flex;flex-wrap:wrap;gap:8px;color:var(--color-text-tertiary);font-size:11px}.dep-viewer__detail-deps{margin-top:8px;padding-top:8px;border-top:1px solid var(--color-border)}.dep-viewer__detail-deps ul{list-style:none;margin:4px 0 0;padding:0}.dep-viewer__detail-deps li{padding:2px 0;color:var(--color-text-secondary)}.dep-viewer__dep-relation{display:inline-block;padding:1px 4px;background:var(--color-surface-hover);border-radius:3px;font-size:10px;font-weight:500;color:var(--color-text-secondary)}\n"],
1537
1537
  changeDetection: i0.ChangeDetectionStrategy.OnPush
1538
1538
  });
@@ -1552,7 +1552,7 @@ i0.ɵɵngDeclareClassMetadata({
1552
1552
  "[class]": "\"dep-viewer\"",
1553
1553
  "[style.height]": "hostHeight()"
1554
1554
  },
1555
- template: "<!-- Toolbar. The zoom cluster is navigation, so it goes with `interactive`; the\n direction indicator is a read-out and stays. -->\n@if (showToolbar()) {\n <div class=\"dep-viewer__toolbar\">\n @if (interactive()) {\n <button class=\"dep-viewer__btn\" (click)=\"zoomIn()\" [attr.title]=\"zoomInLabel()\" [attr.aria-label]=\"zoomInLabel()\">+</button>\n <span class=\"dep-viewer__zoom\">{{ zoomLabel() }}%</span>\n <button class=\"dep-viewer__btn\" (click)=\"zoomOut()\" [attr.title]=\"zoomOutLabel()\" [attr.aria-label]=\"zoomOutLabel()\">−</button>\n <button class=\"dep-viewer__btn dep-viewer__btn--reset\" (click)=\"resetZoom()\" [attr.title]=\"resetZoomLabel()\" [attr.aria-label]=\"resetZoomLabel()\">⟲</button>\n }\n <span class=\"dep-viewer__direction-label\">{{ direction() === 'horizontal' ? '→' : '↓' }}</span>\n </div>\n}\n\n<!-- Canvas -->\n<!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -- background click clears the pointer selection; node selection is pointer-only in this component (known gap) -->\n<div\n #canvas\n class=\"dep-viewer__canvas\"\n [class.dep-viewer__canvas--static]=\"!interactive()\"\n [class.dep-viewer__canvas--sized]=\"contentHeight() !== null\"\n [style.height.px]=\"contentHeight()\"\n (mousedown)=\"onMouseDown($event)\"\n (wheel)=\"onWheel($event)\"\n (click)=\"deselectNode()\"\n>\n <svg\n [attr.width]=\"svgWidth()\"\n [attr.height]=\"svgHeight()\"\n [attr.viewBox]=\"viewBox()\"\n [style.transform]=\"transform()\"\n class=\"dep-viewer__svg\"\n >\n <!-- Arrow markers for cross-reference edges -->\n <defs>\n <marker id=\"arrow-default\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-neutral-400)\"/></marker>\n <marker id=\"arrow-blocks\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-on-error-subtle)\"/></marker>\n <marker id=\"arrow-references\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-on-info-subtle)\"/></marker>\n <marker id=\"arrow-requires\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-on-warning-subtle)\"/></marker>\n <marker id=\"arrow-extends\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-primary-400)\"/></marker>\n <marker id=\"arrow-implements\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-on-success-subtle)\"/></marker>\n <marker id=\"arrow-uses\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-neutral-300)\"/></marker>\n <marker id=\"arrow-depends\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-neutral-400)\"/></marker>\n </defs>\n\n <!-- Edges. Their labels are deliberately NOT drawn here — see below. -->\n @for (edge of graph().edges; track edge.id) {\n <path\n [attr.d]=\"edge.path\"\n [attr.stroke]=\"edge.color\"\n [attr.stroke-width]=\"edgeWidth()\"\n [attr.stroke-dasharray]=\"edge.dashed ? '6 3' : 'none'\"\n [attr.marker-end]=\"edge.arrow ? 'url(#arrow-' + edge.marker + ')' : ''\"\n fill=\"none\"\n class=\"dep-viewer__edge\"\n [class.dep-viewer__edge--cross-ref]=\"edge.isCrossRef\"\n />\n }\n\n <!-- Nodes -->\n @for (node of graph().nodes; track node.id) {\n <g\n class=\"dep-viewer__node\"\n [class.dep-viewer__node--selected]=\"selectedNodeId() === node.id\"\n (click)=\"selectNode(node.id, $event)\"\n (dblclick)=\"expandNode(node.id, $event)\"\n >\n <!-- A label the box couldn't hold in full is offered whole as a tooltip. -->\n @if (node.truncated) {\n <title>{{ node.label }}</title>\n }\n <!-- Opaque backdrop. The status fills are translucent tints (in the dark\n theme --color-success-50 & co. resolve to rgba(…, 0.18)), so without\n something solid beneath them the edges routed under a node show\n straight through it. This keeps the tint compositing against the\n viewer's own surface instead of against whatever passes behind. -->\n <rect\n [attr.x]=\"node.x\"\n [attr.y]=\"node.y\"\n [attr.width]=\"node.width\"\n [attr.height]=\"node.height\"\n rx=\"6\" ry=\"6\"\n class=\"dep-viewer__node-backdrop\"\n />\n <rect\n class=\"dep-viewer__node-fill\"\n [attr.x]=\"node.x\"\n [attr.y]=\"node.y\"\n [attr.width]=\"node.width\"\n [attr.height]=\"node.height\"\n rx=\"6\" ry=\"6\"\n [attr.fill]=\"getNodeBg(node)\"\n [attr.stroke]=\"selectedNodeId() === node.id ? 'var(--color-primary-500)' : getNodeBorder(node)\"\n stroke-width=\"1.5\"\n />\n <!-- One <tspan> per wrapped line, each restarting at the box's centre; the\n block of lines is centred on the box vertically. No whitespace between\n the spans — it would end up in the text. -->\n <text\n [attr.x]=\"node.x + node.width / 2\"\n [attr.y]=\"node.y + node.height / 2 + baselineShift - ((node.lines.length - 1) * lineHeight) / 2\"\n text-anchor=\"middle\"\n [attr.fill]=\"getNodeText(node)\"\n class=\"dep-viewer__node-label\"\n >@for (line of node.lines; track $index) {<tspan [attr.x]=\"node.x + node.width / 2\" [attr.dy]=\"$first ? 0 : lineHeight\">{{ line }}</tspan>}</text>\n\n <!-- \"+N\" marker: this node has more neighbours than are shown -->\n @if (node.moreCount) {\n <g class=\"dep-viewer__more\">\n <rect\n [attr.x]=\"node.x + node.width - 26\"\n [attr.y]=\"node.y - 8\"\n width=\"32\" height=\"16\" rx=\"8\" ry=\"8\"\n />\n <text\n [attr.x]=\"node.x + node.width - 10\"\n [attr.y]=\"node.y + 3\"\n text-anchor=\"middle\"\n >+{{ node.moreCount }}</text>\n </g>\n }\n\n <!-- Collapse/expand toggle -->\n @if (hasChildren(node.id)) {\n <g\n class=\"dep-viewer__toggle\"\n (click)=\"toggleCollapse(node.id, $event)\"\n >\n @if (direction() === 'horizontal') {\n <circle\n [attr.cx]=\"node.x + node.width + 10\"\n [attr.cy]=\"node.y + node.height / 2\"\n r=\"8\"\n fill=\"var(--color-neutral-100)\" stroke=\"var(--color-neutral-300)\" stroke-width=\"1\"\n />\n <text\n [attr.x]=\"node.x + node.width + 10\"\n [attr.y]=\"node.y + node.height / 2 + 4\"\n text-anchor=\"middle\"\n class=\"dep-viewer__toggle-text\"\n >{{ isCollapsed(node.id) ? '+' : '−' }}</text>\n } @else {\n <circle\n [attr.cx]=\"node.x + node.width / 2\"\n [attr.cy]=\"node.y + node.height + 10\"\n r=\"8\"\n fill=\"var(--color-neutral-100)\" stroke=\"var(--color-neutral-300)\" stroke-width=\"1\"\n />\n <text\n [attr.x]=\"node.x + node.width / 2\"\n [attr.y]=\"node.y + node.height + 14\"\n text-anchor=\"middle\"\n class=\"dep-viewer__toggle-text\"\n >{{ isCollapsed(node.id) ? '+' : '−' }}</text>\n }\n </g>\n }\n </g>\n }\n\n <!-- Edge labels last: SVG paints in document order, so anything drawn before\n the nodes is covered by them. A label sits at the midpoint of its edge,\n which frequently lands on a node — it has to come after. The halo (see\n the stylesheet) keeps it legible over whatever it lands on. -->\n @if (showEdgeLabels()) {\n @for (edge of graph().edges; track edge.id) {\n @if (edge.label) {\n <text\n [attr.x]=\"edge.labelX\"\n [attr.y]=\"edge.labelY\"\n class=\"dep-viewer__edge-label\"\n [attr.fill]=\"edge.color\"\n [attr.text-anchor]=\"edge.labelAnchor ?? 'middle'\"\n >{{ edge.label }}</text>\n }\n }\n }\n </svg>\n</div>\n\n<!-- Legend -->\n@if (legendItems().length || typeLegendItems().length) {\n <div class=\"dep-viewer__legend\">\n @for (item of legendItems(); track item.label) {\n <span class=\"dep-viewer__legend-item\">\n <svg width=\"24\" height=\"10\">\n <line\n x1=\"0\" y1=\"5\" x2=\"24\" y2=\"5\"\n [attr.stroke]=\"item.color\"\n stroke-width=\"2\"\n [attr.stroke-dasharray]=\"item.dashed ? '4 2' : 'none'\"\n />\n </svg>\n <span class=\"dep-viewer__legend-text\">{{ item.label }}</span>\n </span>\n }\n @for (item of typeLegendItems(); track item.type) {\n <span class=\"dep-viewer__legend-item\">\n <svg width=\"10\" height=\"10\">\n <rect width=\"10\" height=\"10\" rx=\"2\" ry=\"2\" [attr.fill]=\"item.color\" />\n </svg>\n <span class=\"dep-viewer__legend-text\">{{ item.type }}</span>\n </span>\n }\n </div>\n}\n\n<!-- Detail panel -->\n@if (selectedNode(); as node) {\n <div class=\"dep-viewer__detail\">\n <div class=\"dep-viewer__detail-header\">\n <span class=\"dep-viewer__detail-status dep-viewer__detail-status--{{ node.status }}\">●</span>\n <strong>{{ node.label }}</strong>\n @if (node.type) {\n <span class=\"dep-viewer__detail-type\">{{ node.type }}</span>\n }\n </div>\n @if (node.description) {\n <p class=\"dep-viewer__detail-desc\">{{ node.description }}</p>\n }\n <div class=\"dep-viewer__detail-meta\">\n <span>{{ depthLabel() }} {{ node.depth }}</span>\n <span>{{ childrenLabel() }} {{ node.outgoingCount }}</span>\n <span>{{ dependsOnLabel() }} {{ node.incomingCount }}</span>\n </div>\n @if (selectedDependsOn().length) {\n <div class=\"dep-viewer__detail-deps\">\n <strong>{{ dependenciesLabel() }}</strong>\n <ul>\n @for (dep of selectedDependsOn(); track dep.id) {\n <li>\n <span class=\"dep-viewer__dep-relation\">{{\n dep.relationLabel || getRelationLabel(dep.relation || 'depends')\n }}</span>\n → {{ dep.id }}\n </li>\n }\n </ul>\n </div>\n }\n </div>\n}\n",
1555
+ template: "<!-- Toolbar. The zoom cluster is navigation, so it goes with `interactive`; the\n direction indicator is a read-out and stays. -->\n@if (showToolbar()) {\n <div class=\"dep-viewer__toolbar\">\n @if (interactive()) {\n <button class=\"dep-viewer__btn\" (click)=\"zoomIn()\" [attr.title]=\"zoomInLabel()\" [attr.aria-label]=\"zoomInLabel()\">+</button>\n <span class=\"dep-viewer__zoom\">{{ zoomLabel() }}%</span>\n <button class=\"dep-viewer__btn\" (click)=\"zoomOut()\" [attr.title]=\"zoomOutLabel()\" [attr.aria-label]=\"zoomOutLabel()\">−</button>\n <button class=\"dep-viewer__btn dep-viewer__btn--reset\" (click)=\"resetZoom()\" [attr.title]=\"resetZoomLabel()\" [attr.aria-label]=\"resetZoomLabel()\">⟲</button>\n }\n <span class=\"dep-viewer__direction-label\">{{ direction() === 'horizontal' ? '→' : '↓' }}</span>\n </div>\n}\n\n<!-- Canvas -->\n<!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -- background click clears the pointer selection; node selection is pointer-only in this component (known gap) -->\n<div\n #canvas\n class=\"dep-viewer__canvas\"\n [class.dep-viewer__canvas--static]=\"!interactive()\"\n [class.dep-viewer__canvas--sized]=\"contentHeight() !== null\"\n [style.height.px]=\"contentHeight()\"\n (mousedown)=\"onMouseDown($event)\"\n (wheel)=\"onWheel($event)\"\n (click)=\"deselectNode()\"\n>\n <svg\n [attr.width]=\"svgWidth()\"\n [attr.height]=\"svgHeight()\"\n [attr.viewBox]=\"viewBox()\"\n [style.transform]=\"transform()\"\n class=\"dep-viewer__svg\"\n >\n <!-- Arrow markers for cross-reference edges -->\n <defs>\n <marker id=\"arrow-default\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-neutral-400)\"/></marker>\n <marker id=\"arrow-blocks\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-on-error-subtle)\"/></marker>\n <marker id=\"arrow-references\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-on-info-subtle)\"/></marker>\n <marker id=\"arrow-requires\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-on-warning-subtle)\"/></marker>\n <marker id=\"arrow-extends\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-on-primary-subtle)\"/></marker>\n <marker id=\"arrow-implements\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-on-success-subtle)\"/></marker>\n <marker id=\"arrow-uses\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-text-tertiary)\"/></marker>\n <marker id=\"arrow-depends\" markerWidth=\"8\" markerHeight=\"6\" refX=\"8\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L8,3 L0,6 Z\" fill=\"var(--color-neutral-400)\"/></marker>\n </defs>\n\n <!-- Edges. Their labels are deliberately NOT drawn here — see below. -->\n @for (edge of graph().edges; track edge.id) {\n <path\n [attr.d]=\"edge.path\"\n [attr.stroke]=\"edge.color\"\n [attr.stroke-width]=\"edgeWidth()\"\n [attr.stroke-dasharray]=\"edge.dashed ? '6 3' : 'none'\"\n [attr.marker-end]=\"edge.arrow ? 'url(#arrow-' + edge.marker + ')' : ''\"\n fill=\"none\"\n class=\"dep-viewer__edge\"\n [class.dep-viewer__edge--cross-ref]=\"edge.isCrossRef\"\n />\n }\n\n <!-- Nodes -->\n @for (node of graph().nodes; track node.id) {\n <g\n class=\"dep-viewer__node\"\n [class.dep-viewer__node--selected]=\"selectedNodeId() === node.id\"\n (click)=\"selectNode(node.id, $event)\"\n (dblclick)=\"expandNode(node.id, $event)\"\n >\n <!-- A label the box couldn't hold in full is offered whole as a tooltip. -->\n @if (node.truncated) {\n <title>{{ node.label }}</title>\n }\n <!-- Opaque backdrop. The status fills are translucent tints (in the dark\n theme --color-success-50 & co. resolve to rgba(…, 0.18)), so without\n something solid beneath them the edges routed under a node show\n straight through it. This keeps the tint compositing against the\n viewer's own surface instead of against whatever passes behind. -->\n <rect\n [attr.x]=\"node.x\"\n [attr.y]=\"node.y\"\n [attr.width]=\"node.width\"\n [attr.height]=\"node.height\"\n rx=\"6\" ry=\"6\"\n class=\"dep-viewer__node-backdrop\"\n />\n <rect\n class=\"dep-viewer__node-fill\"\n [attr.x]=\"node.x\"\n [attr.y]=\"node.y\"\n [attr.width]=\"node.width\"\n [attr.height]=\"node.height\"\n rx=\"6\" ry=\"6\"\n [attr.fill]=\"getNodeBg(node)\"\n [attr.stroke]=\"selectedNodeId() === node.id ? 'var(--color-primary-500)' : getNodeBorder(node)\"\n stroke-width=\"1.5\"\n />\n <!-- One <tspan> per wrapped line, each restarting at the box's centre; the\n block of lines is centred on the box vertically. No whitespace between\n the spans — it would end up in the text. -->\n <text\n [attr.x]=\"node.x + node.width / 2\"\n [attr.y]=\"node.y + node.height / 2 + baselineShift - ((node.lines.length - 1) * lineHeight) / 2\"\n text-anchor=\"middle\"\n [attr.fill]=\"getNodeText(node)\"\n class=\"dep-viewer__node-label\"\n >@for (line of node.lines; track $index) {<tspan [attr.x]=\"node.x + node.width / 2\" [attr.dy]=\"$first ? 0 : lineHeight\">{{ line }}</tspan>}</text>\n\n <!-- \"+N\" marker: this node has more neighbours than are shown -->\n @if (node.moreCount) {\n <g class=\"dep-viewer__more\">\n <rect\n [attr.x]=\"node.x + node.width - 26\"\n [attr.y]=\"node.y - 8\"\n width=\"32\" height=\"16\" rx=\"8\" ry=\"8\"\n />\n <text\n [attr.x]=\"node.x + node.width - 10\"\n [attr.y]=\"node.y + 3\"\n text-anchor=\"middle\"\n >+{{ node.moreCount }}</text>\n </g>\n }\n\n <!-- Collapse/expand toggle -->\n @if (hasChildren(node.id)) {\n <g\n class=\"dep-viewer__toggle\"\n (click)=\"toggleCollapse(node.id, $event)\"\n >\n @if (direction() === 'horizontal') {\n <circle\n [attr.cx]=\"node.x + node.width + 10\"\n [attr.cy]=\"node.y + node.height / 2\"\n r=\"8\"\n fill=\"var(--color-neutral-100)\" stroke=\"var(--color-neutral-300)\" stroke-width=\"1\"\n />\n <text\n [attr.x]=\"node.x + node.width + 10\"\n [attr.y]=\"node.y + node.height / 2 + 4\"\n text-anchor=\"middle\"\n class=\"dep-viewer__toggle-text\"\n >{{ isCollapsed(node.id) ? '+' : '−' }}</text>\n } @else {\n <circle\n [attr.cx]=\"node.x + node.width / 2\"\n [attr.cy]=\"node.y + node.height + 10\"\n r=\"8\"\n fill=\"var(--color-neutral-100)\" stroke=\"var(--color-neutral-300)\" stroke-width=\"1\"\n />\n <text\n [attr.x]=\"node.x + node.width / 2\"\n [attr.y]=\"node.y + node.height + 14\"\n text-anchor=\"middle\"\n class=\"dep-viewer__toggle-text\"\n >{{ isCollapsed(node.id) ? '+' : '−' }}</text>\n }\n </g>\n }\n </g>\n }\n\n <!-- Edge labels last: SVG paints in document order, so anything drawn before\n the nodes is covered by them. A label sits at the midpoint of its edge,\n which frequently lands on a node — it has to come after. The halo (see\n the stylesheet) keeps it legible over whatever it lands on. -->\n @if (showEdgeLabels()) {\n @for (edge of graph().edges; track edge.id) {\n @if (edge.label) {\n <text\n [attr.x]=\"edge.labelX\"\n [attr.y]=\"edge.labelY\"\n class=\"dep-viewer__edge-label\"\n [attr.fill]=\"edge.color\"\n [attr.text-anchor]=\"edge.labelAnchor ?? 'middle'\"\n >{{ edge.label }}</text>\n }\n }\n }\n </svg>\n</div>\n\n<!-- Legend -->\n@if (legendItems().length || typeLegendItems().length) {\n <div class=\"dep-viewer__legend\">\n @for (item of legendItems(); track item.label) {\n <span class=\"dep-viewer__legend-item\">\n <svg width=\"24\" height=\"10\">\n <line\n x1=\"0\" y1=\"5\" x2=\"24\" y2=\"5\"\n [attr.stroke]=\"item.color\"\n stroke-width=\"2\"\n [attr.stroke-dasharray]=\"item.dashed ? '4 2' : 'none'\"\n />\n </svg>\n <span class=\"dep-viewer__legend-text\">{{ item.label }}</span>\n </span>\n }\n @for (item of typeLegendItems(); track item.type) {\n <span class=\"dep-viewer__legend-item\">\n <svg width=\"10\" height=\"10\">\n <rect width=\"10\" height=\"10\" rx=\"2\" ry=\"2\" [attr.fill]=\"item.color\" />\n </svg>\n <span class=\"dep-viewer__legend-text\">{{ item.type }}</span>\n </span>\n }\n </div>\n}\n\n<!-- Detail panel -->\n@if (selectedNode(); as node) {\n <div class=\"dep-viewer__detail\">\n <div class=\"dep-viewer__detail-header\">\n <span class=\"dep-viewer__detail-status dep-viewer__detail-status--{{ node.status }}\">●</span>\n <strong>{{ node.label }}</strong>\n @if (node.type) {\n <span class=\"dep-viewer__detail-type\">{{ node.type }}</span>\n }\n </div>\n @if (node.description) {\n <p class=\"dep-viewer__detail-desc\">{{ node.description }}</p>\n }\n <div class=\"dep-viewer__detail-meta\">\n <span>{{ depthLabel() }} {{ node.depth }}</span>\n <span>{{ childrenLabel() }} {{ node.outgoingCount }}</span>\n <span>{{ dependsOnLabel() }} {{ node.incomingCount }}</span>\n </div>\n @if (selectedDependsOn().length) {\n <div class=\"dep-viewer__detail-deps\">\n <strong>{{ dependenciesLabel() }}</strong>\n <ul>\n @for (dep of selectedDependsOn(); track dep.id) {\n <li>\n <span class=\"dep-viewer__dep-relation\">{{\n dep.relationLabel || getRelationLabel(dep.relation || 'depends')\n }}</span>\n → {{ dep.id }}\n </li>\n }\n </ul>\n </div>\n }\n </div>\n}\n",
1556
1556
  styles: [":host{display:flex;flex-direction:column;position:relative;overflow:hidden;border:1px solid var(--color-border);border-radius:8px;background:var(--color-surface)}.dep-viewer__toolbar{display:flex;align-items:center;gap:4px;padding:8px 12px;border-bottom:1px solid var(--color-border);background:var(--color-surface-2);-webkit-user-select:none;user-select:none}.dep-viewer__btn{width:28px;height:28px;display:flex;align-items:center;justify-content:center;border:1px solid var(--color-border);border-radius:4px;background:var(--color-surface);color:var(--color-text-secondary);cursor:pointer;font-size:14px;line-height:1}.dep-viewer__btn:hover{background:var(--color-surface-hover)}.dep-viewer__btn--reset{margin-left:4px}.dep-viewer__zoom{font-size:12px;min-width:40px;text-align:center;color:var(--color-text-secondary)}.dep-viewer__direction-label{margin-left:8px;font-size:14px;color:var(--color-text-tertiary)}.dep-viewer__canvas{cursor:grab;overflow:hidden;width:100%;flex:1 1 auto;min-height:0}.dep-viewer__canvas:active{cursor:grabbing}.dep-viewer__canvas--static,.dep-viewer__canvas--static:active{cursor:default}.dep-viewer__canvas--sized{flex:0 0 auto}.dep-viewer__svg{transform-origin:0 0;transition:transform .1s ease-out;overflow:visible}.dep-viewer__edge{pointer-events:none;transition:opacity .15s}.dep-viewer__edge--cross-ref{opacity:.8}.dep-viewer__edge-label{font-size:9px;font-weight:500;pointer-events:none;-webkit-user-select:none;user-select:none;dominant-baseline:central;paint-order:stroke;stroke:var(--color-surface);stroke-width:3px;stroke-linejoin:round}.dep-viewer__node{cursor:pointer;transition:filter .15s}.dep-viewer__node:hover{filter:brightness(.96)}.dep-viewer__node--selected rect{stroke-width:2.5}.dep-viewer__node-backdrop{fill:var(--color-surface)}.dep-viewer__node-label{font-size:12px;font-weight:500;pointer-events:none;-webkit-user-select:none;user-select:none}.dep-viewer__toggle{cursor:pointer}.dep-viewer__toggle:hover circle{fill:var(--color-surface-hover)}.dep-viewer__more{pointer-events:none}.dep-viewer__more rect{fill:var(--color-surface-2);stroke:var(--color-border-strong);stroke-width:1}.dep-viewer__more text{font-size:9px;font-weight:600;fill:var(--color-text-secondary);-webkit-user-select:none;user-select:none}.dep-viewer__toggle-text{font-size:11px;font-weight:600;fill:var(--color-text-secondary);pointer-events:none;-webkit-user-select:none;user-select:none}.dep-viewer__legend{display:flex;flex-wrap:wrap;gap:12px;padding:6px 12px;border-top:1px solid var(--color-border);background:var(--color-surface-2)}.dep-viewer__legend-item{display:flex;align-items:center;gap:4px}.dep-viewer__legend-text{font-size:11px;color:var(--color-text-secondary)}.dep-viewer__detail{position:absolute;bottom:12px;right:12px;width:240px;padding:12px;background:var(--color-surface-2);border:1px solid var(--color-border);border-radius:8px;box-shadow:var(--elevation-1);font-size:12px;z-index:10}.dep-viewer__detail-header{display:flex;align-items:center;gap:6px;margin-bottom:4px;font-size:13px}.dep-viewer__detail-type{margin-left:auto;padding:1px 6px;border-radius:3px;background:var(--color-surface-hover);color:var(--color-text-secondary);font-size:10px;font-weight:500}.dep-viewer__detail-status{font-size:10px}.dep-viewer__detail-status--default{color:var(--color-text-tertiary)}.dep-viewer__detail-status--active{color:var(--color-primary-400)}.dep-viewer__detail-status--success{color:var(--color-on-success-subtle)}.dep-viewer__detail-status--warning{color:var(--color-on-warning-subtle)}.dep-viewer__detail-status--error{color:var(--color-on-error-subtle)}.dep-viewer__detail-status--muted{color:var(--color-text-disabled)}.dep-viewer__detail-desc{color:var(--color-text-secondary);margin:0 0 6px;line-height:1.4}.dep-viewer__detail-meta{display:flex;flex-wrap:wrap;gap:8px;color:var(--color-text-tertiary);font-size:11px}.dep-viewer__detail-deps{margin-top:8px;padding-top:8px;border-top:1px solid var(--color-border)}.dep-viewer__detail-deps ul{list-style:none;margin:4px 0 0;padding:0}.dep-viewer__detail-deps li{padding:2px 0;color:var(--color-text-secondary)}.dep-viewer__dep-relation{display:inline-block;padding:1px 4px;background:var(--color-surface-hover);border-radius:3px;font-size:10px;font-weight:500;color:var(--color-text-secondary)}\n"]
1557
1557
  }]
1558
1558
  }],