@truenas/ui-components 0.7.7 → 0.7.9

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.
@@ -369,8 +369,8 @@ function kebabTestSegment(part) {
369
369
  * re-implemented at each call site.
370
370
  *
371
371
  * Note this is *base-first*: the suffix trails the base. Fixed chrome whose role
372
- * should lead (e.g. `tn-dialog-shell`'s `button-close-<base>`) prepends the role
373
- * manually instead of calling this.
372
+ * should lead (e.g. `tn-dialog-shell`'s `button-close-<base>` and
373
+ * `dialog-title-<base>`) prepends the role manually instead of calling this.
374
374
  *
375
375
  * Falsy segments are preserved here (not filtered) so `composeTestId` can apply
376
376
  * its own drop/scoping rules — `scopeTestId(undefined, 'edit')` yields
@@ -4342,6 +4342,16 @@ class TnBannerComponent {
4342
4342
  message = input(undefined, ...(ngDevMode ? [{ debugName: "message" }] : []));
4343
4343
  type = input('info', ...(ngDevMode ? [{ debugName: "type" }] : []));
4344
4344
  bordered = input(false, ...(ngDevMode ? [{ debugName: "bordered" }] : []));
4345
+ /**
4346
+ * Semantic base for the banner's test id, rendered onto the banner root as
4347
+ * `banner-<testId>` through {@link TnTestIdDirective} — so the value follows
4348
+ * the library's own naming and honours `TN_TEST_ATTR`.
4349
+ *
4350
+ * The root, rather than the host element, because that is what carries the
4351
+ * live-region role: the element a test asserts a warning on is the same one
4352
+ * assistive tech announces.
4353
+ */
4354
+ testId = input(undefined, ...(ngDevMode ? [{ debugName: "testId" }] : []));
4345
4355
  constructor() {
4346
4356
  this.registerMdiIcons();
4347
4357
  }
@@ -4397,12 +4407,12 @@ class TnBannerComponent {
4397
4407
  return result;
4398
4408
  }, ...(ngDevMode ? [{ debugName: "classes" }] : []));
4399
4409
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnBannerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4400
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnBannerComponent, isStandalone: true, selector: "tn-banner", inputs: { heading: { classPropertyName: "heading", publicName: "heading", isSignal: true, isRequired: false, transformFunction: null }, message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, bordered: { classPropertyName: "bordered", publicName: "bordered", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "actionContent", predicate: TnBannerActionDirective, isSignal: true }], ngImport: i0, template: "<!--\n `ariaRole()` is the only thing declaring how urgently the banner announces:\n it implies the politeness, and an explicit `aria-live` here would override it\n \u2014 which is what the static `aria-live=\"polite\"` used to do, downgrading the\n two types deliberately made assertive. See `../a11y/live-region.ts`.\n-->\n<div\n [ngClass]=\"classes()\"\n [attr.role]=\"ariaRole()\"\n>\n @if (hasStructuredContent()) {\n <div class=\"tn-banner__main\">\n <div class=\"tn-banner__icon\">\n <tn-icon\n library=\"mdi\"\n size=\"md\"\n aria-hidden=\"true\"\n [name]=\"iconName()\"\n />\n </div>\n\n <div class=\"tn-banner__content\">\n @if (heading()) {\n <div class=\"tn-banner__heading\">\n {{ heading() }}\n </div>\n }\n\n @if (message()) {\n <div class=\"tn-banner__message\">\n {{ message() }}\n </div>\n }\n </div>\n </div>\n } @else {\n <div class=\"tn-banner__main\">\n <ng-content />\n </div>\n }\n\n @if (hasAction()) {\n <div class=\"tn-banner__action\">\n <ng-content select=\"[tnBannerAction]\" />\n </div>\n }\n</div>\n", styles: [".tn-banner{display:grid;gap:8px;align-items:center;padding:16px;border-radius:6px;border-left:4px solid;transition:opacity .2s ease}@media(prefers-reduced-motion:reduce){.tn-banner{transition:none}}.tn-banner--info{border-left-color:var(--tn-info, var(--tn-blue, #006997));background-color:var(--tn-alt-bg1, #383838)}.tn-banner--info .tn-banner__heading,.tn-banner--info .tn-banner__icon{color:var(--tn-info, var(--tn-blue, #006997))}.tn-banner--warning{border-left-color:var(--tn-warning, var(--tn-orange, #955313));background-color:var(--tn-alt-bg1, #383838)}.tn-banner--warning .tn-banner__heading,.tn-banner--warning .tn-banner__icon{color:var(--tn-warning, var(--tn-orange, #955313))}.tn-banner--error{border-left-color:var(--tn-error, var(--tn-red, #bd2626));background-color:var(--tn-alt-bg1, #383838)}.tn-banner--error .tn-banner__heading,.tn-banner--error .tn-banner__icon{color:var(--tn-error, var(--tn-red, #bd2626))}.tn-banner--success{border-left-color:var(--tn-success, var(--tn-green, #416f26));background-color:var(--tn-alt-bg1, #383838)}.tn-banner--success .tn-banner__heading,.tn-banner--success .tn-banner__icon{color:var(--tn-success, var(--tn-green, #416f26))}.tn-banner--bordered{border-top:1px solid var(--tn-lines, #404040);border-right:1px solid var(--tn-lines, #404040);border-bottom:1px solid var(--tn-lines, #404040)}.tn-banner__main{display:flex;align-items:flex-start;gap:12px;flex:1;min-width:0}.tn-banner__icon{flex-shrink:0;display:flex;align-items:center;justify-content:center;margin-top:2px}.tn-banner__content{flex:1;min-width:0}.tn-banner__heading{font-size:1rem;font-weight:600;line-height:1.5;margin:0}.tn-banner__message{font-size:1rem;color:var(--tn-fg2, #6b7280);line-height:1.5;margin-top:4px}.tn-banner__action{display:flex;align-items:center;gap:8px 16px;justify-content:flex-end}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: TnIconComponent, selector: "tn-icon", inputs: ["name", "size", "color", "tooltip", "ariaLabel", "library", "testId", "fullSize", "customSize"] }] });
4410
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnBannerComponent, isStandalone: true, selector: "tn-banner", inputs: { heading: { classPropertyName: "heading", publicName: "heading", isSignal: true, isRequired: false, transformFunction: null }, message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, bordered: { classPropertyName: "bordered", publicName: "bordered", isSignal: true, isRequired: false, transformFunction: null }, testId: { classPropertyName: "testId", publicName: "testId", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "actionContent", predicate: TnBannerActionDirective, isSignal: true }], ngImport: i0, template: "<!--\n `ariaRole()` is the only thing declaring how urgently the banner announces:\n it implies the politeness, and an explicit `aria-live` here would override it\n \u2014 which is what the static `aria-live=\"polite\"` used to do, downgrading the\n two types deliberately made assertive. See `../a11y/live-region.ts`.\n-->\n<div\n tnTestIdType=\"banner\"\n [ngClass]=\"classes()\"\n [attr.role]=\"ariaRole()\"\n [tnTestId]=\"testId()\"\n>\n @if (hasStructuredContent()) {\n <div class=\"tn-banner__main\">\n <div class=\"tn-banner__icon\">\n <tn-icon\n library=\"mdi\"\n size=\"md\"\n aria-hidden=\"true\"\n [name]=\"iconName()\"\n />\n </div>\n\n <div class=\"tn-banner__content\">\n @if (heading()) {\n <div class=\"tn-banner__heading\">\n {{ heading() }}\n </div>\n }\n\n @if (message()) {\n <div class=\"tn-banner__message\">\n {{ message() }}\n </div>\n }\n </div>\n </div>\n } @else {\n <div class=\"tn-banner__main\">\n <ng-content />\n </div>\n }\n\n @if (hasAction()) {\n <div class=\"tn-banner__action\">\n <ng-content select=\"[tnBannerAction]\" />\n </div>\n }\n</div>\n", styles: [".tn-banner{display:grid;gap:8px;align-items:center;padding:16px;border-radius:6px;border-left:4px solid;transition:opacity .2s ease}@media(prefers-reduced-motion:reduce){.tn-banner{transition:none}}.tn-banner--info{border-left-color:var(--tn-info, var(--tn-blue, #006997));background-color:var(--tn-alt-bg1, #383838)}.tn-banner--info .tn-banner__heading,.tn-banner--info .tn-banner__icon{color:var(--tn-info, var(--tn-blue, #006997))}.tn-banner--warning{border-left-color:var(--tn-warning, var(--tn-orange, #955313));background-color:var(--tn-alt-bg1, #383838)}.tn-banner--warning .tn-banner__heading,.tn-banner--warning .tn-banner__icon{color:var(--tn-warning, var(--tn-orange, #955313))}.tn-banner--error{border-left-color:var(--tn-error, var(--tn-red, #bd2626));background-color:var(--tn-alt-bg1, #383838)}.tn-banner--error .tn-banner__heading,.tn-banner--error .tn-banner__icon{color:var(--tn-error, var(--tn-red, #bd2626))}.tn-banner--success{border-left-color:var(--tn-success, var(--tn-green, #416f26));background-color:var(--tn-alt-bg1, #383838)}.tn-banner--success .tn-banner__heading,.tn-banner--success .tn-banner__icon{color:var(--tn-success, var(--tn-green, #416f26))}.tn-banner--bordered{border-top:1px solid var(--tn-lines, #404040);border-right:1px solid var(--tn-lines, #404040);border-bottom:1px solid var(--tn-lines, #404040)}.tn-banner__main{display:flex;align-items:flex-start;gap:12px;flex:1;min-width:0}.tn-banner__icon{flex-shrink:0;display:flex;align-items:center;justify-content:center;margin-top:2px}.tn-banner__content{flex:1;min-width:0}.tn-banner__heading{font-size:1rem;font-weight:600;line-height:1.5;margin:0}.tn-banner__message{font-size:1rem;color:var(--tn-fg2, #6b7280);line-height:1.5;margin-top:4px}.tn-banner__action{display:flex;align-items:center;gap:8px 16px;justify-content:flex-end}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: TnIconComponent, selector: "tn-icon", inputs: ["name", "size", "color", "tooltip", "ariaLabel", "library", "testId", "fullSize", "customSize"] }, { kind: "directive", type: TnTestIdDirective, selector: "[tnTestId]", inputs: ["tnTestId", "tnTestIdType"] }] });
4401
4411
  }
4402
4412
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnBannerComponent, decorators: [{
4403
4413
  type: Component,
4404
- args: [{ selector: 'tn-banner', standalone: true, imports: [CommonModule, TnIconComponent], template: "<!--\n `ariaRole()` is the only thing declaring how urgently the banner announces:\n it implies the politeness, and an explicit `aria-live` here would override it\n \u2014 which is what the static `aria-live=\"polite\"` used to do, downgrading the\n two types deliberately made assertive. See `../a11y/live-region.ts`.\n-->\n<div\n [ngClass]=\"classes()\"\n [attr.role]=\"ariaRole()\"\n>\n @if (hasStructuredContent()) {\n <div class=\"tn-banner__main\">\n <div class=\"tn-banner__icon\">\n <tn-icon\n library=\"mdi\"\n size=\"md\"\n aria-hidden=\"true\"\n [name]=\"iconName()\"\n />\n </div>\n\n <div class=\"tn-banner__content\">\n @if (heading()) {\n <div class=\"tn-banner__heading\">\n {{ heading() }}\n </div>\n }\n\n @if (message()) {\n <div class=\"tn-banner__message\">\n {{ message() }}\n </div>\n }\n </div>\n </div>\n } @else {\n <div class=\"tn-banner__main\">\n <ng-content />\n </div>\n }\n\n @if (hasAction()) {\n <div class=\"tn-banner__action\">\n <ng-content select=\"[tnBannerAction]\" />\n </div>\n }\n</div>\n", styles: [".tn-banner{display:grid;gap:8px;align-items:center;padding:16px;border-radius:6px;border-left:4px solid;transition:opacity .2s ease}@media(prefers-reduced-motion:reduce){.tn-banner{transition:none}}.tn-banner--info{border-left-color:var(--tn-info, var(--tn-blue, #006997));background-color:var(--tn-alt-bg1, #383838)}.tn-banner--info .tn-banner__heading,.tn-banner--info .tn-banner__icon{color:var(--tn-info, var(--tn-blue, #006997))}.tn-banner--warning{border-left-color:var(--tn-warning, var(--tn-orange, #955313));background-color:var(--tn-alt-bg1, #383838)}.tn-banner--warning .tn-banner__heading,.tn-banner--warning .tn-banner__icon{color:var(--tn-warning, var(--tn-orange, #955313))}.tn-banner--error{border-left-color:var(--tn-error, var(--tn-red, #bd2626));background-color:var(--tn-alt-bg1, #383838)}.tn-banner--error .tn-banner__heading,.tn-banner--error .tn-banner__icon{color:var(--tn-error, var(--tn-red, #bd2626))}.tn-banner--success{border-left-color:var(--tn-success, var(--tn-green, #416f26));background-color:var(--tn-alt-bg1, #383838)}.tn-banner--success .tn-banner__heading,.tn-banner--success .tn-banner__icon{color:var(--tn-success, var(--tn-green, #416f26))}.tn-banner--bordered{border-top:1px solid var(--tn-lines, #404040);border-right:1px solid var(--tn-lines, #404040);border-bottom:1px solid var(--tn-lines, #404040)}.tn-banner__main{display:flex;align-items:flex-start;gap:12px;flex:1;min-width:0}.tn-banner__icon{flex-shrink:0;display:flex;align-items:center;justify-content:center;margin-top:2px}.tn-banner__content{flex:1;min-width:0}.tn-banner__heading{font-size:1rem;font-weight:600;line-height:1.5;margin:0}.tn-banner__message{font-size:1rem;color:var(--tn-fg2, #6b7280);line-height:1.5;margin-top:4px}.tn-banner__action{display:flex;align-items:center;gap:8px 16px;justify-content:flex-end}\n"] }]
4405
- }], ctorParameters: () => [], propDecorators: { actionContent: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => TnBannerActionDirective), { isSignal: true }] }], heading: [{ type: i0.Input, args: [{ isSignal: true, alias: "heading", required: false }] }], message: [{ type: i0.Input, args: [{ isSignal: true, alias: "message", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], bordered: [{ type: i0.Input, args: [{ isSignal: true, alias: "bordered", required: false }] }] } });
4414
+ args: [{ selector: 'tn-banner', standalone: true, imports: [CommonModule, TnIconComponent, TnTestIdDirective], template: "<!--\n `ariaRole()` is the only thing declaring how urgently the banner announces:\n it implies the politeness, and an explicit `aria-live` here would override it\n \u2014 which is what the static `aria-live=\"polite\"` used to do, downgrading the\n two types deliberately made assertive. See `../a11y/live-region.ts`.\n-->\n<div\n tnTestIdType=\"banner\"\n [ngClass]=\"classes()\"\n [attr.role]=\"ariaRole()\"\n [tnTestId]=\"testId()\"\n>\n @if (hasStructuredContent()) {\n <div class=\"tn-banner__main\">\n <div class=\"tn-banner__icon\">\n <tn-icon\n library=\"mdi\"\n size=\"md\"\n aria-hidden=\"true\"\n [name]=\"iconName()\"\n />\n </div>\n\n <div class=\"tn-banner__content\">\n @if (heading()) {\n <div class=\"tn-banner__heading\">\n {{ heading() }}\n </div>\n }\n\n @if (message()) {\n <div class=\"tn-banner__message\">\n {{ message() }}\n </div>\n }\n </div>\n </div>\n } @else {\n <div class=\"tn-banner__main\">\n <ng-content />\n </div>\n }\n\n @if (hasAction()) {\n <div class=\"tn-banner__action\">\n <ng-content select=\"[tnBannerAction]\" />\n </div>\n }\n</div>\n", styles: [".tn-banner{display:grid;gap:8px;align-items:center;padding:16px;border-radius:6px;border-left:4px solid;transition:opacity .2s ease}@media(prefers-reduced-motion:reduce){.tn-banner{transition:none}}.tn-banner--info{border-left-color:var(--tn-info, var(--tn-blue, #006997));background-color:var(--tn-alt-bg1, #383838)}.tn-banner--info .tn-banner__heading,.tn-banner--info .tn-banner__icon{color:var(--tn-info, var(--tn-blue, #006997))}.tn-banner--warning{border-left-color:var(--tn-warning, var(--tn-orange, #955313));background-color:var(--tn-alt-bg1, #383838)}.tn-banner--warning .tn-banner__heading,.tn-banner--warning .tn-banner__icon{color:var(--tn-warning, var(--tn-orange, #955313))}.tn-banner--error{border-left-color:var(--tn-error, var(--tn-red, #bd2626));background-color:var(--tn-alt-bg1, #383838)}.tn-banner--error .tn-banner__heading,.tn-banner--error .tn-banner__icon{color:var(--tn-error, var(--tn-red, #bd2626))}.tn-banner--success{border-left-color:var(--tn-success, var(--tn-green, #416f26));background-color:var(--tn-alt-bg1, #383838)}.tn-banner--success .tn-banner__heading,.tn-banner--success .tn-banner__icon{color:var(--tn-success, var(--tn-green, #416f26))}.tn-banner--bordered{border-top:1px solid var(--tn-lines, #404040);border-right:1px solid var(--tn-lines, #404040);border-bottom:1px solid var(--tn-lines, #404040)}.tn-banner__main{display:flex;align-items:flex-start;gap:12px;flex:1;min-width:0}.tn-banner__icon{flex-shrink:0;display:flex;align-items:center;justify-content:center;margin-top:2px}.tn-banner__content{flex:1;min-width:0}.tn-banner__heading{font-size:1rem;font-weight:600;line-height:1.5;margin:0}.tn-banner__message{font-size:1rem;color:var(--tn-fg2, #6b7280);line-height:1.5;margin-top:4px}.tn-banner__action{display:flex;align-items:center;gap:8px 16px;justify-content:flex-end}\n"] }]
4415
+ }], ctorParameters: () => [], propDecorators: { actionContent: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => TnBannerActionDirective), { isSignal: true }] }], heading: [{ type: i0.Input, args: [{ isSignal: true, alias: "heading", required: false }] }], message: [{ type: i0.Input, args: [{ isSignal: true, alias: "message", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], bordered: [{ type: i0.Input, args: [{ isSignal: true, alias: "bordered", required: false }] }], testId: [{ type: i0.Input, args: [{ isSignal: true, alias: "testId", required: false }] }] } });
4406
4416
 
4407
4417
  /**
4408
4418
  * Harness for a single action projected into a `tn-banner`'s action slot.
@@ -16467,6 +16477,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
16467
16477
  * The primary-text slot is the deliberate exception — it has no gate, so
16468
16478
  * `[tnListItemTitle]` and `[tnListItemPrimary]` render either way. See the
16469
16479
  * comment in `list-item.component.html` for why the asymmetry is there.
16480
+ *
16481
+ * `[testId]` names the row for automation. It lands on the host, which is both
16482
+ * the click target (`(click)` is a host binding) and the `listitem` — so one id
16483
+ * addresses the row whether a suite clicks it or reads what it renders. The
16484
+ * value is written verbatim, with no element-type prefix, matching the other
16485
+ * components whose host *is* the target (`tn-tree-node`, `tn-table`): a row of
16486
+ * a list is not a control whose type the library can name, and its meaning
16487
+ * comes from the list it sits in, which only the consumer knows.
16470
16488
  */
16471
16489
  class TnListItemComponent {
16472
16490
  disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
@@ -16512,11 +16530,11 @@ class TnListItemComponent {
16512
16530
  }
16513
16531
  }
16514
16532
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnListItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
16515
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnListItemComponent, isStandalone: true, selector: "tn-list-item", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, clickable: { classPropertyName: "clickable", publicName: "clickable", isSignal: true, isRequired: false, transformFunction: null }, dense: { classPropertyName: "dense", publicName: "dense", isSignal: true, isRequired: false, transformFunction: null }, wrap: { classPropertyName: "wrap", publicName: "wrap", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { itemClick: "itemClick" }, host: { attributes: { "role": "listitem" }, listeners: { "click": "onClick($event)" }, properties: { "class.tn-list-item--disabled": "disabled()", "class.tn-list-item--clickable": "clickable()", "class.tn-list-item--dense": "dense()", "class.tn-list-item--wrap": "wrap()", "class.tn-list-item--two-line": "hasSecondaryText()", "class.tn-list-item--three-line": "hasThirdText()" }, classAttribute: "tn-list-item" }, queries: [{ propertyName: "leadingIcons", predicate: TnListIconDirective, descendants: true, isSignal: true }, { propertyName: "leadingAvatars", predicate: TnListAvatarDirective, descendants: true, isSignal: true }, { propertyName: "secondaryLines", predicate: TnListItemLineDirective, descendants: true, isSignal: true }, { propertyName: "secondaryTexts", predicate: TnListItemSecondaryDirective, descendants: true, isSignal: true }, { propertyName: "trailing", predicate: TnListItemTrailingDirective, descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"tn-list-item__content\">\n <!-- Leading icon/avatar slot -->\n @if (hasLeadingContent()) {\n <div class=\"tn-list-item__leading\">\n <ng-content select=\"[tnListIcon], [tnListAvatar]\" />\n </div>\n }\n\n <!-- Text content -->\n <div class=\"tn-list-item__text\">\n <!--\n Primary text. The catch-all slot needs no guard: a projected node is\n assigned to exactly one slot, so anything carrying [tnListItemTitle] or\n [tnListItemPrimary] lands in the slot above and can never reach the\n catch-all. Gating this on a directive query would instead key projection\n and rendering off two different things \u2014 the slot matches the attribute,\n a contentChildren query matches the directive instance \u2014 so an attribute\n written without its directive imported would toggle the guard while\n projecting exactly as before.\n\n The leading, secondary and trailing slots below are still gated that way,\n deliberately: their wrappers carry gutters that would space out every row\n if rendered empty. The cost is that they require the directive to be\n imported, which is documented on the component and on each directive.\n -->\n <div class=\"tn-list-item__primary-text\">\n <ng-content select=\"[tnListItemTitle], [tnListItemPrimary]\" />\n <ng-content />\n </div>\n\n <!-- Secondary text -->\n @if (hasSecondaryText()) {\n <div class=\"tn-list-item__secondary-text\">\n <ng-content select=\"[tnListItemLine], [tnListItemSecondary]\" />\n </div>\n }\n </div>\n\n <!-- Trailing content slot -->\n @if (hasTrailingContent()) {\n <div class=\"tn-list-item__trailing\">\n <ng-content select=\"[tnListItemTrailing]\" />\n </div>\n }\n</div>", styles: [".tn-list-item{display:block;position:relative;min-height:48px;padding:0;cursor:default;text-decoration:none;color:var(--tn-fg1);transition:background-color .2s ease}.tn-list-item__content{display:flex;align-items:center;padding:8px 16px;min-height:inherit;box-sizing:border-box}.tn-list-item__leading{flex-shrink:0;margin-right:16px;display:flex;align-items:center;justify-content:center}.tn-list-item__text{flex:1;min-width:0;display:flex;flex-direction:column;justify-content:center}.tn-list-item__primary-text{font-size:1rem;font-weight:400;line-height:1.5;color:var(--tn-fg1);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tn-list-item__secondary-text{font-size:14px;font-weight:400;line-height:1.4;color:var(--tn-fg2);margin-top:2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tn-list-item__trailing{flex-shrink:0;margin-left:16px;display:flex;align-items:center}.tn-list-item--clickable{cursor:pointer}.tn-list-item--clickable:hover:not(.tn-list-item--disabled){background-color:var(--tn-alt-bg1)}.tn-list-item--clickable:focus{outline:none;background-color:var(--tn-alt-bg1)}.tn-list-item--clickable:active:not(.tn-list-item--disabled){background-color:var(--tn-alt-bg2)}.tn-list-item--two-line{min-height:64px}.tn-list-item--two-line .tn-list-item__primary-text{white-space:normal;line-height:1.4}.tn-list-item--two-line .tn-list-item__secondary-text{white-space:normal;line-height:1.3}.tn-list-item--three-line{min-height:88px}.tn-list-item--three-line .tn-list-item__text{align-items:flex-start;padding:8px 0}.tn-list-item--three-line .tn-list-item__primary-text{white-space:normal;line-height:1.4}.tn-list-item--three-line .tn-list-item__secondary-text{white-space:normal;line-height:1.3;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.tn-list-item--dense{min-height:32px}.tn-list-item--dense .tn-list-item__content{padding:4px 16px}.tn-list-item--dense .tn-list-item__leading{margin-right:8px}.tn-list-item--dense .tn-list-item__trailing{margin-left:8px}.tn-list-item--wrap .tn-list-item__primary-text,.tn-list-item--wrap .tn-list-item__secondary-text{overflow:visible;text-overflow:clip;white-space:normal}.tn-list-item--disabled{opacity:.6;cursor:not-allowed;pointer-events:none}::ng-deep [tnListIcon]{width:24px;height:24px;font-size:24px;color:var(--tn-fg2);display:flex;align-items:center;justify-content:center}::ng-deep [tnListAvatar]{width:40px;height:40px;border-radius:50%;object-fit:cover;background-color:var(--tn-alt-bg1);color:var(--tn-fg1);display:flex;align-items:center;justify-content:center;font-weight:500}::ng-deep [tnListItemTitle]{font-weight:400}::ng-deep [tnListItemLine]{display:block;margin-top:4px;font-size:14px;color:var(--tn-fg2);line-height:1.4}::ng-deep [tnListItemTrailing]{color:var(--tn-fg2);font-size:14px}\n"] });
16533
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnListItemComponent, isStandalone: true, selector: "tn-list-item", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, clickable: { classPropertyName: "clickable", publicName: "clickable", isSignal: true, isRequired: false, transformFunction: null }, dense: { classPropertyName: "dense", publicName: "dense", isSignal: true, isRequired: false, transformFunction: null }, wrap: { classPropertyName: "wrap", publicName: "wrap", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { itemClick: "itemClick" }, host: { attributes: { "role": "listitem" }, listeners: { "click": "onClick($event)" }, properties: { "class.tn-list-item--disabled": "disabled()", "class.tn-list-item--clickable": "clickable()", "class.tn-list-item--dense": "dense()", "class.tn-list-item--wrap": "wrap()", "class.tn-list-item--two-line": "hasSecondaryText()", "class.tn-list-item--three-line": "hasThirdText()" }, classAttribute: "tn-list-item" }, queries: [{ propertyName: "leadingIcons", predicate: TnListIconDirective, descendants: true, isSignal: true }, { propertyName: "leadingAvatars", predicate: TnListAvatarDirective, descendants: true, isSignal: true }, { propertyName: "secondaryLines", predicate: TnListItemLineDirective, descendants: true, isSignal: true }, { propertyName: "secondaryTexts", predicate: TnListItemSecondaryDirective, descendants: true, isSignal: true }, { propertyName: "trailing", predicate: TnListItemTrailingDirective, descendants: true, isSignal: true }], hostDirectives: [{ directive: TnTestIdDirective, inputs: ["tnTestId", "testId"] }], ngImport: i0, template: "<div class=\"tn-list-item__content\">\n <!-- Leading icon/avatar slot -->\n @if (hasLeadingContent()) {\n <div class=\"tn-list-item__leading\">\n <ng-content select=\"[tnListIcon], [tnListAvatar]\" />\n </div>\n }\n\n <!-- Text content -->\n <div class=\"tn-list-item__text\">\n <!--\n Primary text. The catch-all slot needs no guard: a projected node is\n assigned to exactly one slot, so anything carrying [tnListItemTitle] or\n [tnListItemPrimary] lands in the slot above and can never reach the\n catch-all. Gating this on a directive query would instead key projection\n and rendering off two different things \u2014 the slot matches the attribute,\n a contentChildren query matches the directive instance \u2014 so an attribute\n written without its directive imported would toggle the guard while\n projecting exactly as before.\n\n The leading, secondary and trailing slots below are still gated that way,\n deliberately: their wrappers carry gutters that would space out every row\n if rendered empty. The cost is that they require the directive to be\n imported, which is documented on the component and on each directive.\n -->\n <div class=\"tn-list-item__primary-text\">\n <ng-content select=\"[tnListItemTitle], [tnListItemPrimary]\" />\n <ng-content />\n </div>\n\n <!-- Secondary text -->\n @if (hasSecondaryText()) {\n <div class=\"tn-list-item__secondary-text\">\n <ng-content select=\"[tnListItemLine], [tnListItemSecondary]\" />\n </div>\n }\n </div>\n\n <!-- Trailing content slot -->\n @if (hasTrailingContent()) {\n <div class=\"tn-list-item__trailing\">\n <ng-content select=\"[tnListItemTrailing]\" />\n </div>\n }\n</div>", styles: [".tn-list-item{display:block;position:relative;min-height:48px;padding:0;cursor:default;text-decoration:none;color:var(--tn-fg1);transition:background-color .2s ease}.tn-list-item__content{display:flex;align-items:center;padding:8px 16px;min-height:inherit;box-sizing:border-box}.tn-list-item__leading{flex-shrink:0;margin-right:16px;display:flex;align-items:center;justify-content:center}.tn-list-item__text{flex:1;min-width:0;display:flex;flex-direction:column;justify-content:center}.tn-list-item__primary-text{font-size:1rem;font-weight:400;line-height:1.5;color:var(--tn-fg1);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tn-list-item__secondary-text{font-size:14px;font-weight:400;line-height:1.4;color:var(--tn-fg2);margin-top:2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tn-list-item__trailing{flex-shrink:0;margin-left:16px;display:flex;align-items:center}.tn-list-item--clickable{cursor:pointer}.tn-list-item--clickable:hover:not(.tn-list-item--disabled){background-color:var(--tn-alt-bg1)}.tn-list-item--clickable:focus{outline:none;background-color:var(--tn-alt-bg1)}.tn-list-item--clickable:active:not(.tn-list-item--disabled){background-color:var(--tn-alt-bg2)}.tn-list-item--two-line{min-height:64px}.tn-list-item--two-line .tn-list-item__primary-text{white-space:normal;line-height:1.4}.tn-list-item--two-line .tn-list-item__secondary-text{white-space:normal;line-height:1.3}.tn-list-item--three-line{min-height:88px}.tn-list-item--three-line .tn-list-item__text{align-items:flex-start;padding:8px 0}.tn-list-item--three-line .tn-list-item__primary-text{white-space:normal;line-height:1.4}.tn-list-item--three-line .tn-list-item__secondary-text{white-space:normal;line-height:1.3;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.tn-list-item--dense{min-height:32px}.tn-list-item--dense .tn-list-item__content{padding:4px 16px}.tn-list-item--dense .tn-list-item__leading{margin-right:8px}.tn-list-item--dense .tn-list-item__trailing{margin-left:8px}.tn-list-item--wrap .tn-list-item__primary-text,.tn-list-item--wrap .tn-list-item__secondary-text{overflow:visible;text-overflow:clip;white-space:normal}.tn-list-item--disabled{opacity:.6;cursor:not-allowed;pointer-events:none}::ng-deep [tnListIcon]{width:24px;height:24px;font-size:24px;color:var(--tn-fg2);display:flex;align-items:center;justify-content:center}::ng-deep [tnListAvatar]{width:40px;height:40px;border-radius:50%;object-fit:cover;background-color:var(--tn-alt-bg1);color:var(--tn-fg1);display:flex;align-items:center;justify-content:center;font-weight:500}::ng-deep [tnListItemTitle]{font-weight:400}::ng-deep [tnListItemLine]{display:block;margin-top:4px;font-size:14px;color:var(--tn-fg2);line-height:1.4}::ng-deep [tnListItemTrailing]{color:var(--tn-fg2);font-size:14px}\n"] });
16516
16534
  }
16517
16535
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnListItemComponent, decorators: [{
16518
16536
  type: Component,
16519
- args: [{ selector: 'tn-list-item', standalone: true, imports: [], host: {
16537
+ args: [{ selector: 'tn-list-item', standalone: true, imports: [], hostDirectives: [{ directive: TnTestIdDirective, inputs: ['tnTestId: testId'] }], host: {
16520
16538
  'class': 'tn-list-item',
16521
16539
  '[class.tn-list-item--disabled]': 'disabled()',
16522
16540
  '[class.tn-list-item--clickable]': 'clickable()',
@@ -26408,9 +26426,10 @@ class TnDialogShellComponent {
26408
26426
  /** Hide the actions footer. Same wrapper-case rationale as {@link hideContent}. */
26409
26427
  hideActions = input(false, ...(ngDevMode ? [{ debugName: "hideActions" }] : []));
26410
26428
  /**
26411
- * Optional semantic base that scopes the shell's chrome buttons. The close
26412
- * and fullscreen buttons emit `button-close` / `button-fullscreen` by default,
26413
- * or `button-close-<testId>` / `button-fullscreen-<testId>` when a base is
26429
+ * Optional semantic base that scopes the shell's chrome. The close and
26430
+ * fullscreen buttons emit `button-close` / `button-fullscreen` by default, and
26431
+ * the title heading `dialog-title`, or `button-close-<testId>` /
26432
+ * `button-fullscreen-<testId>` / `dialog-title-<testId>` when a base is
26414
26433
  * provided (useful when more than one dialog can be open).
26415
26434
  */
26416
26435
  testId = input(undefined, ...(ngDevMode ? [{ debugName: "testId" }] : []));
@@ -26452,6 +26471,19 @@ class TnDialogShellComponent {
26452
26471
  closeTestId = computed(() => ['close', ...this.baseSegments()], ...(ngDevMode ? [{ debugName: "closeTestId" }] : []));
26453
26472
  /** Role-first test-id segments for the fullscreen button: `button-fullscreen[-<base>]`. */
26454
26473
  fullscreenTestId = computed(() => ['fullscreen', ...this.baseSegments()], ...(ngDevMode ? [{ debugName: "fullscreenTestId" }] : []));
26474
+ /**
26475
+ * Role-first test-id segments for the title heading: `dialog-title[-<base>]`.
26476
+ *
26477
+ * The heading is fixed chrome like the two buttons, so it follows the same
26478
+ * role-first shape — and a test that knows a dialog's base knows its title's
26479
+ * id without being told separately, which is the point: dialogs raised
26480
+ * through one generic error path are otherwise indistinguishable by id, and
26481
+ * the only remaining handle is a match on their prose.
26482
+ *
26483
+ * There is no id when there is no title: the heading is not rendered at all
26484
+ * (see the template), so nothing is left behind to select.
26485
+ */
26486
+ titleTestId = computed(() => ['title', ...this.baseSegments()], ...(ngDevMode ? [{ debugName: "titleTestId" }] : []));
26455
26487
  /** Stable id for the title heading, referenced by the dialog's aria-labelledby. */
26456
26488
  titleId = `tn-dialog-title-${nextUniqueId++}`;
26457
26489
  isFullscreen = signal(false, ...(ngDevMode ? [{ debugName: "isFullscreen" }] : []));
@@ -26594,13 +26626,13 @@ class TnDialogShellComponent {
26594
26626
  }
26595
26627
  }
26596
26628
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnDialogShellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
26597
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnDialogShellComponent, isStandalone: true, selector: "tn-dialog-shell", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, showFullscreenButton: { classPropertyName: "showFullscreenButton", publicName: "showFullscreenButton", isSignal: true, isRequired: false, transformFunction: null }, showCloseButton: { classPropertyName: "showCloseButton", publicName: "showCloseButton", isSignal: true, isRequired: false, transformFunction: null }, hideContent: { classPropertyName: "hideContent", publicName: "hideContent", isSignal: true, isRequired: false, transformFunction: null }, hideActions: { classPropertyName: "hideActions", publicName: "hideActions", isSignal: true, isRequired: false, transformFunction: null }, testId: { classPropertyName: "testId", publicName: "testId", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelledby: { classPropertyName: "ariaLabelledby", publicName: "ariaLabelledby", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "tn-dialog-shell" }, ngImport: i0, template: "<header class=\"tn-dialog__header\">\n <!--\n No heading element at all when there is no title, rather than an empty one:\n an `<h2>` with no text is an `empty-heading` violation, and it puts a level-2\n heading with nothing in it into the document outline (#219). The dialog is\n then named by `ariaLabel`/`ariaLabelledby` instead \u2014 see the component.\n -->\n @if (hasTitle()) {\n <h2 class=\"tn-dialog__title\" [id]=\"titleId\">{{ title() }}</h2>\n }\n @if (showFullscreenButton()) {\n <!--\n `data-fullscreen` is the language-independent state hook `TnDialogHarness.isFullscreen()`\n reads. The state is also on the panel's `.tn-dialog--fullscreen` class, but the panel is an\n ANCESTOR of this component's host \u2014 out of reach for a harness locator, and ambiguous while\n more than one dialog is open. `aria-label` is not the hook: it is now DI-configurable, so a\n translated app would flip the harness's answer. Same call the table's sort-direction toggle\n makes, for the same reason (see `data-sort-direction`).\n -->\n <button\n type=\"button\"\n class=\"tn-dialog__fullscreen\"\n tnTestIdType=\"button\"\n [tnTestId]=\"fullscreenTestId()\"\n [attr.aria-label]=\"fullscreenLabel()\"\n [attr.data-fullscreen]=\"isFullscreen()\"\n (click)=\"toggleFullscreen()\">\n <span class=\"tn-dialog__fullscreen-icon\" aria-hidden=\"true\">{{ isFullscreen() ? '\u2913' : '\u2922' }}</span>\n </button>\n }\n @if (showCloseButton()) {\n <button type=\"button\" class=\"tn-dialog__close\" tnTestIdType=\"button\" [attr.aria-label]=\"labels().close\" [tnTestId]=\"closeTestId()\" (click)=\"close()\">\n <span class=\"tn-dialog__close-icon\" aria-hidden=\"true\">\u2715</span>\n </button>\n }\n</header>\n\n<section class=\"tn-dialog__content\" cdkDialogContent [class.tn-dialog__content--hidden]=\"hideContent()\">\n <ng-content />\n</section>\n\n<footer class=\"tn-dialog__actions\" cdkDialogActions [class.tn-dialog__actions--hidden]=\"hideActions()\">\n <ng-content select=\"[tnDialogAction]\" />\n</footer>\n", dependencies: [{ kind: "directive", type: TnTestIdDirective, selector: "[tnTestId]", inputs: ["tnTestId", "tnTestIdType"] }] });
26629
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnDialogShellComponent, isStandalone: true, selector: "tn-dialog-shell", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, showFullscreenButton: { classPropertyName: "showFullscreenButton", publicName: "showFullscreenButton", isSignal: true, isRequired: false, transformFunction: null }, showCloseButton: { classPropertyName: "showCloseButton", publicName: "showCloseButton", isSignal: true, isRequired: false, transformFunction: null }, hideContent: { classPropertyName: "hideContent", publicName: "hideContent", isSignal: true, isRequired: false, transformFunction: null }, hideActions: { classPropertyName: "hideActions", publicName: "hideActions", isSignal: true, isRequired: false, transformFunction: null }, testId: { classPropertyName: "testId", publicName: "testId", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelledby: { classPropertyName: "ariaLabelledby", publicName: "ariaLabelledby", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "tn-dialog-shell" }, ngImport: i0, template: "<header class=\"tn-dialog__header\">\n <!--\n No heading element at all when there is no title, rather than an empty one:\n an `<h2>` with no text is an `empty-heading` violation, and it puts a level-2\n heading with nothing in it into the document outline (#219). The dialog is\n then named by `ariaLabel`/`ariaLabelledby` instead \u2014 see the component. Nothing\n renders, so an untitled dialog also leaves no `dialog-title-*` id behind.\n -->\n @if (hasTitle()) {\n <h2 class=\"tn-dialog__title\" tnTestIdType=\"dialog\" [id]=\"titleId\" [tnTestId]=\"titleTestId()\">{{ title() }}</h2>\n }\n @if (showFullscreenButton()) {\n <!--\n `data-fullscreen` is the language-independent state hook `TnDialogHarness.isFullscreen()`\n reads. The state is also on the panel's `.tn-dialog--fullscreen` class, but the panel is an\n ANCESTOR of this component's host \u2014 out of reach for a harness locator, and ambiguous while\n more than one dialog is open. `aria-label` is not the hook: it is now DI-configurable, so a\n translated app would flip the harness's answer. Same call the table's sort-direction toggle\n makes, for the same reason (see `data-sort-direction`).\n -->\n <button\n type=\"button\"\n class=\"tn-dialog__fullscreen\"\n tnTestIdType=\"button\"\n [tnTestId]=\"fullscreenTestId()\"\n [attr.aria-label]=\"fullscreenLabel()\"\n [attr.data-fullscreen]=\"isFullscreen()\"\n (click)=\"toggleFullscreen()\">\n <span class=\"tn-dialog__fullscreen-icon\" aria-hidden=\"true\">{{ isFullscreen() ? '\u2913' : '\u2922' }}</span>\n </button>\n }\n @if (showCloseButton()) {\n <button type=\"button\" class=\"tn-dialog__close\" tnTestIdType=\"button\" [attr.aria-label]=\"labels().close\" [tnTestId]=\"closeTestId()\" (click)=\"close()\">\n <span class=\"tn-dialog__close-icon\" aria-hidden=\"true\">\u2715</span>\n </button>\n }\n</header>\n\n<section class=\"tn-dialog__content\" cdkDialogContent [class.tn-dialog__content--hidden]=\"hideContent()\">\n <ng-content />\n</section>\n\n<footer class=\"tn-dialog__actions\" cdkDialogActions [class.tn-dialog__actions--hidden]=\"hideActions()\">\n <ng-content select=\"[tnDialogAction]\" />\n</footer>\n", dependencies: [{ kind: "directive", type: TnTestIdDirective, selector: "[tnTestId]", inputs: ["tnTestId", "tnTestIdType"] }] });
26598
26630
  }
26599
26631
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnDialogShellComponent, decorators: [{
26600
26632
  type: Component,
26601
26633
  args: [{ selector: 'tn-dialog-shell', standalone: true, imports: [TnTestIdDirective], host: {
26602
26634
  'class': 'tn-dialog-shell'
26603
- }, template: "<header class=\"tn-dialog__header\">\n <!--\n No heading element at all when there is no title, rather than an empty one:\n an `<h2>` with no text is an `empty-heading` violation, and it puts a level-2\n heading with nothing in it into the document outline (#219). The dialog is\n then named by `ariaLabel`/`ariaLabelledby` instead \u2014 see the component.\n -->\n @if (hasTitle()) {\n <h2 class=\"tn-dialog__title\" [id]=\"titleId\">{{ title() }}</h2>\n }\n @if (showFullscreenButton()) {\n <!--\n `data-fullscreen` is the language-independent state hook `TnDialogHarness.isFullscreen()`\n reads. The state is also on the panel's `.tn-dialog--fullscreen` class, but the panel is an\n ANCESTOR of this component's host \u2014 out of reach for a harness locator, and ambiguous while\n more than one dialog is open. `aria-label` is not the hook: it is now DI-configurable, so a\n translated app would flip the harness's answer. Same call the table's sort-direction toggle\n makes, for the same reason (see `data-sort-direction`).\n -->\n <button\n type=\"button\"\n class=\"tn-dialog__fullscreen\"\n tnTestIdType=\"button\"\n [tnTestId]=\"fullscreenTestId()\"\n [attr.aria-label]=\"fullscreenLabel()\"\n [attr.data-fullscreen]=\"isFullscreen()\"\n (click)=\"toggleFullscreen()\">\n <span class=\"tn-dialog__fullscreen-icon\" aria-hidden=\"true\">{{ isFullscreen() ? '\u2913' : '\u2922' }}</span>\n </button>\n }\n @if (showCloseButton()) {\n <button type=\"button\" class=\"tn-dialog__close\" tnTestIdType=\"button\" [attr.aria-label]=\"labels().close\" [tnTestId]=\"closeTestId()\" (click)=\"close()\">\n <span class=\"tn-dialog__close-icon\" aria-hidden=\"true\">\u2715</span>\n </button>\n }\n</header>\n\n<section class=\"tn-dialog__content\" cdkDialogContent [class.tn-dialog__content--hidden]=\"hideContent()\">\n <ng-content />\n</section>\n\n<footer class=\"tn-dialog__actions\" cdkDialogActions [class.tn-dialog__actions--hidden]=\"hideActions()\">\n <ng-content select=\"[tnDialogAction]\" />\n</footer>\n" }]
26635
+ }, template: "<header class=\"tn-dialog__header\">\n <!--\n No heading element at all when there is no title, rather than an empty one:\n an `<h2>` with no text is an `empty-heading` violation, and it puts a level-2\n heading with nothing in it into the document outline (#219). The dialog is\n then named by `ariaLabel`/`ariaLabelledby` instead \u2014 see the component. Nothing\n renders, so an untitled dialog also leaves no `dialog-title-*` id behind.\n -->\n @if (hasTitle()) {\n <h2 class=\"tn-dialog__title\" tnTestIdType=\"dialog\" [id]=\"titleId\" [tnTestId]=\"titleTestId()\">{{ title() }}</h2>\n }\n @if (showFullscreenButton()) {\n <!--\n `data-fullscreen` is the language-independent state hook `TnDialogHarness.isFullscreen()`\n reads. The state is also on the panel's `.tn-dialog--fullscreen` class, but the panel is an\n ANCESTOR of this component's host \u2014 out of reach for a harness locator, and ambiguous while\n more than one dialog is open. `aria-label` is not the hook: it is now DI-configurable, so a\n translated app would flip the harness's answer. Same call the table's sort-direction toggle\n makes, for the same reason (see `data-sort-direction`).\n -->\n <button\n type=\"button\"\n class=\"tn-dialog__fullscreen\"\n tnTestIdType=\"button\"\n [tnTestId]=\"fullscreenTestId()\"\n [attr.aria-label]=\"fullscreenLabel()\"\n [attr.data-fullscreen]=\"isFullscreen()\"\n (click)=\"toggleFullscreen()\">\n <span class=\"tn-dialog__fullscreen-icon\" aria-hidden=\"true\">{{ isFullscreen() ? '\u2913' : '\u2922' }}</span>\n </button>\n }\n @if (showCloseButton()) {\n <button type=\"button\" class=\"tn-dialog__close\" tnTestIdType=\"button\" [attr.aria-label]=\"labels().close\" [tnTestId]=\"closeTestId()\" (click)=\"close()\">\n <span class=\"tn-dialog__close-icon\" aria-hidden=\"true\">\u2715</span>\n </button>\n }\n</header>\n\n<section class=\"tn-dialog__content\" cdkDialogContent [class.tn-dialog__content--hidden]=\"hideContent()\">\n <ng-content />\n</section>\n\n<footer class=\"tn-dialog__actions\" cdkDialogActions [class.tn-dialog__actions--hidden]=\"hideActions()\">\n <ng-content select=\"[tnDialogAction]\" />\n</footer>\n" }]
26604
26636
  }], ctorParameters: () => [], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], showFullscreenButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFullscreenButton", required: false }] }], showCloseButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCloseButton", required: false }] }], hideContent: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideContent", required: false }] }], hideActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideActions", required: false }] }], testId: [{ type: i0.Input, args: [{ isSignal: true, alias: "testId", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], ariaLabelledby: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabelledby", required: false }] }] } });
26605
26637
 
26606
26638
  class TnConfirmDialogComponent {