@truenas/ui-components 0.7.8 → 0.7.10
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.
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Overlay, OverlayPositionBuilder, OverlayModule } from '@angular/cdk/overlay';
|
|
2
|
-
import { TemplatePortal, ComponentPortal, PortalModule } from '@angular/cdk/portal';
|
|
1
|
+
import { Overlay, OverlayPositionBuilder, OverlayModule, createOverlayRef, createNoopScrollStrategy, createGlobalPositionStrategy } from '@angular/cdk/overlay';
|
|
2
|
+
import { TemplatePortal, ComponentPortal, PortalModule, DomPortal } from '@angular/cdk/portal';
|
|
3
3
|
import * as i0 from '@angular/core';
|
|
4
4
|
import { InjectionToken, inject, computed, isSignal, isDevMode, effect, signal, input, ViewEncapsulation, ChangeDetectionStrategy, Component, Injector, Renderer2, ElementRef, Directive, DestroyRef, untracked, ViewContainerRef, output, viewChild, forwardRef, NgZone, afterNextRender, model, Injectable, contentChildren, Pipe, HostListener, linkedSignal, contentChild, TemplateRef, viewChildren, ChangeDetectorRef, afterEveryRender, IterableDiffers, booleanAttribute, LOCALE_ID, ApplicationRef, EnvironmentInjector, createComponent, PLATFORM_ID } from '@angular/core';
|
|
5
5
|
import * as i1$3 from '@angular/forms';
|
|
@@ -16477,6 +16477,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
16477
16477
|
* The primary-text slot is the deliberate exception — it has no gate, so
|
|
16478
16478
|
* `[tnListItemTitle]` and `[tnListItemPrimary]` render either way. See the
|
|
16479
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.
|
|
16480
16488
|
*/
|
|
16481
16489
|
class TnListItemComponent {
|
|
16482
16490
|
disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
|
|
@@ -16522,11 +16530,11 @@ class TnListItemComponent {
|
|
|
16522
16530
|
}
|
|
16523
16531
|
}
|
|
16524
16532
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnListItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
16525
|
-
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"] });
|
|
16526
16534
|
}
|
|
16527
16535
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnListItemComponent, decorators: [{
|
|
16528
16536
|
type: Component,
|
|
16529
|
-
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: {
|
|
16530
16538
|
'class': 'tn-list-item',
|
|
16531
16539
|
'[class.tn-list-item--disabled]': 'disabled()',
|
|
16532
16540
|
'[class.tn-list-item--clickable]': 'clickable()',
|
|
@@ -26974,6 +26982,40 @@ const TN_SIDE_PANEL_CONTENT_LABEL = 'Panel content';
|
|
|
26974
26982
|
* why it is copied from the rule at all.
|
|
26975
26983
|
*/
|
|
26976
26984
|
const TN_SIDE_PANEL_OVERFLOW_TOLERANCE_PX = TN_SCROLLABLE_REGION_TOLERANCE_PX;
|
|
26985
|
+
/**
|
|
26986
|
+
* Marks the overlay as currently hosted in a CDK overlay (#322).
|
|
26987
|
+
*
|
|
26988
|
+
* It is what makes the overlay render at all — `.tn-side-panel__overlay` is
|
|
26989
|
+
* `display: none` without it. A detached overlay is back inside
|
|
26990
|
+
* `<tn-side-panel>`, whose ancestors the consumer owns, and one of them having a
|
|
26991
|
+
* `transform` would make `position: fixed` resolve against that box instead of
|
|
26992
|
+
* the viewport and paint the closed panel next to it. On `<body>`, where the
|
|
26993
|
+
* overlay used to live unconditionally, that could not happen.
|
|
26994
|
+
*
|
|
26995
|
+
* Applied imperatively rather than bound, for the reason
|
|
26996
|
+
* `TN_SIDE_PANEL_OPEN_CLASS` gives — the two have to land in that order and in
|
|
26997
|
+
* that relationship to the attach.
|
|
26998
|
+
*/
|
|
26999
|
+
const TN_SIDE_PANEL_ATTACHED_CLASS = 'tn-side-panel__overlay--attached';
|
|
27000
|
+
/**
|
|
27001
|
+
* Puts the panel in its open position, and is the class the open/close
|
|
27002
|
+
* transition runs on.
|
|
27003
|
+
*
|
|
27004
|
+
* WHY THIS IS NOT A TEMPLATE BINDING (#322)
|
|
27005
|
+
* -----------------------------------------
|
|
27006
|
+
* Angular refreshes a component's template BEFORE it runs that component's
|
|
27007
|
+
* effects, so a `[class.…--open]="open()"` binding is written while the overlay
|
|
27008
|
+
* is still detached and `display: none`. The effect below then attaches it, and
|
|
27009
|
+
* the browser's first look at the element has it already open — a transition
|
|
27010
|
+
* needs a previous computed style to run from, and there is none. The panel
|
|
27011
|
+
* appears instead of sliding in.
|
|
27012
|
+
*
|
|
27013
|
+
* So the order is owned here instead: attach, force a layout read so the closed
|
|
27014
|
+
* position is what the browser has computed, then add this. It is still applied
|
|
27015
|
+
* inside the same change detection pass the `open` change arrives in, so a spec
|
|
27016
|
+
* that calls `detectChanges()` and asserts on the class sees it.
|
|
27017
|
+
*/
|
|
27018
|
+
const TN_SIDE_PANEL_OPEN_CLASS = 'tn-side-panel__overlay--open';
|
|
26977
27019
|
/**
|
|
26978
27020
|
* Directive to mark an element as a side-panel footer action.
|
|
26979
27021
|
*
|
|
@@ -27033,15 +27075,89 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
27033
27075
|
* own, focus it yourself once the panel is open; the component leaves focus
|
|
27034
27076
|
* alone as soon as it is inside the panel. `lib/a11y/initial-focus.ts` holds
|
|
27035
27077
|
* the reasoning for capturing the container rather than a control.
|
|
27078
|
+
*
|
|
27079
|
+
* WHERE IT RENDERS, AND WHY THAT DECIDES WHAT IT STACKS AGAINST
|
|
27080
|
+
* ------------------------------------------------------------
|
|
27081
|
+
* The panel renders through a CDK `OverlayRef` created WHEN IT OPENS (#322),
|
|
27082
|
+
* which is what makes it stack with `TnDialog`, `tn-menu` and tooltips by open
|
|
27083
|
+
* order: whatever attached last paints on top, in both directions. It used to
|
|
27084
|
+
* append its overlay to `<body>` at construction time with a fixed `z-index`,
|
|
27085
|
+
* and the CDK overlay container is a `<body>` child with the same one — so the
|
|
27086
|
+
* winner was whichever element `<body>` happened to receive last, which follows
|
|
27087
|
+
* nothing a caller can see. A panel opened FROM a dialog landed under that
|
|
27088
|
+
* dialog's backdrop; on a browser with the popover API, where CDK puts its
|
|
27089
|
+
* overlays in the top layer, no `z-index` on a `<body>` child could have won at
|
|
27090
|
+
* all.
|
|
27091
|
+
*
|
|
27092
|
+
* Three things follow from being a CDK overlay, and each replaces something
|
|
27093
|
+
* this component used to do for itself:
|
|
27094
|
+
*
|
|
27095
|
+
* - **Escape reaches the topmost overlay only**, through CDK's
|
|
27096
|
+
* `OverlayKeyboardDispatcher`, rather than through a `keydown` handler on the
|
|
27097
|
+
* panel that stopped propagation to keep a dialog underneath from closing too.
|
|
27098
|
+
* The panel therefore also closes on Escape pressed outside it, which is what
|
|
27099
|
+
* `TnDialog` does — `tn-drawer` still binds `keydown` on its own panel and
|
|
27100
|
+
* does not.
|
|
27101
|
+
* - **A CDK dialog opened over an open panel hides the panel from assistive
|
|
27102
|
+
* technology.** CDK does this by sweeping the overlay container's SIBLINGS,
|
|
27103
|
+
* which no longer includes the panel, so the component tracks it — see
|
|
27104
|
+
* `dialogsAbove`.
|
|
27105
|
+
* - **A consumer no longer re-homes the overlay or overrides its `z-index` and
|
|
27106
|
+
* `pointer-events`.** There is no `z-index` on it any more; the CDK pane it
|
|
27107
|
+
* lives in carries the stacking.
|
|
27036
27108
|
*/
|
|
27037
27109
|
class TnSidePanelComponent {
|
|
27038
27110
|
iconRegistry = inject(TnIconRegistryService);
|
|
27039
27111
|
document = inject(DOCUMENT);
|
|
27040
27112
|
destroyRef = inject(DestroyRef);
|
|
27113
|
+
injector = inject(Injector);
|
|
27114
|
+
dialog = inject(Dialog);
|
|
27041
27115
|
overlayRef = viewChild.required('overlay');
|
|
27042
27116
|
panelRef = viewChild.required('panel');
|
|
27043
27117
|
contentRef = viewChild.required('content');
|
|
27044
27118
|
initialized = signal(false, ...(ngDevMode ? [{ debugName: "initialized" }] : []));
|
|
27119
|
+
/**
|
|
27120
|
+
* The CDK overlay currently hosting `overlayRef`'s element, or `null` while
|
|
27121
|
+
* the panel is closed (#322).
|
|
27122
|
+
*
|
|
27123
|
+
* **Created on open and disposed once the close has settled**, not once for
|
|
27124
|
+
* the component's lifetime. That is the whole mechanism: CDK decides stacking
|
|
27125
|
+
* by the order overlays ATTACH — `showPopover()` order in the top layer, and
|
|
27126
|
+
* `_updateStackingOrder()`'s move to the end of the container where the
|
|
27127
|
+
* popover API is missing — so an overlay created when the component was built
|
|
27128
|
+
* would stack by construction order, which for a panel rendered inside a
|
|
27129
|
+
* dialog is exactly backwards.
|
|
27130
|
+
*
|
|
27131
|
+
* Non-null is therefore also the answer to "is this panel currently one of
|
|
27132
|
+
* the overlays on screen", which is what `dialogsAbove` keys off.
|
|
27133
|
+
*/
|
|
27134
|
+
cdkOverlay = null;
|
|
27135
|
+
/** Escape handling for the overlay above, dropped with it. */
|
|
27136
|
+
keydowns = null;
|
|
27137
|
+
/**
|
|
27138
|
+
* The CDK dialogs currently covering this panel (#322).
|
|
27139
|
+
*
|
|
27140
|
+
* The panel is hidden from assistive technology while this is non-empty,
|
|
27141
|
+
* which is what CDK's own `Dialog` does for everything outside the overlay
|
|
27142
|
+
* container — it sweeps the container's SIBLINGS, and a panel that now lives
|
|
27143
|
+
* INSIDE the container is not one. A dialog already open when the panel opens
|
|
27144
|
+
* is underneath it and never joins this; only dialogs that arrive afterwards
|
|
27145
|
+
* are above.
|
|
27146
|
+
*
|
|
27147
|
+
* A SET RATHER THAN A COUNT, and each dialog removes ITSELF. Nothing
|
|
27148
|
+
* unsubscribes a dialog's `closed` when the panel closes underneath it — the
|
|
27149
|
+
* panel cannot outlive its own release to do that — so with a count, a
|
|
27150
|
+
* dialog opened during one open and closed during the NEXT one decremented a
|
|
27151
|
+
* tally it had never contributed to, and put the panel back in the
|
|
27152
|
+
* accessibility tree with a live modal still stacked over it. Removing a
|
|
27153
|
+
* member that is not there is a no-op, which is the property a count does
|
|
27154
|
+
* not have.
|
|
27155
|
+
*
|
|
27156
|
+
* Emptied only when the last one goes, on the same reasoning as CDK's
|
|
27157
|
+
* `_removeOpenDialog`: two stacked dialogs closing one at a time must not
|
|
27158
|
+
* un-hide the panel while one is still covering it.
|
|
27159
|
+
*/
|
|
27160
|
+
dialogsAbove = signal(new Set(), ...(ngDevMode ? [{ debugName: "dialogsAbove" }] : []));
|
|
27045
27161
|
/**
|
|
27046
27162
|
* Whether the content region carries the tab stop, its role and its name
|
|
27047
27163
|
* (#248) — which is NOT the same question as whether it currently overflows.
|
|
@@ -27054,10 +27170,14 @@ class TnSidePanelComponent {
|
|
|
27054
27170
|
* answer is held true while the region has focus, and why `role` and
|
|
27055
27171
|
* `aria-label` are gated on the same signal as `tabindex` rather than left on.
|
|
27056
27172
|
*
|
|
27057
|
-
*
|
|
27058
|
-
*
|
|
27059
|
-
*
|
|
27060
|
-
*
|
|
27173
|
+
* The helper's FIRST measurement reads a closed panel, which is
|
|
27174
|
+
* `display: none` since #322 and so reads as not overflowing. That is the
|
|
27175
|
+
* right answer for a panel nobody can reach yet, and it does not stick: the
|
|
27176
|
+
* helper's `ResizeObserver` fires when the overlay attaches and the region
|
|
27177
|
+
* gets a box, which is the same instrument that already answered a viewport
|
|
27178
|
+
* resize. Once open, `.tn-side-panel__overlay` is `position: fixed;
|
|
27179
|
+
* inset: 0`, so the region's size comes from the viewport rather than from
|
|
27180
|
+
* whichever CDK pane is hosting it.
|
|
27061
27181
|
*/
|
|
27062
27182
|
contentKeyboardReachable = tnScrollableRegion(() => this.contentRef().nativeElement);
|
|
27063
27183
|
// Two-way bindable via [(open)]
|
|
@@ -27171,6 +27291,14 @@ class TnSidePanelComponent {
|
|
|
27171
27291
|
ariaLabel: this.ariaLabel,
|
|
27172
27292
|
ariaLabelledby: this.resolvedAriaLabelledby,
|
|
27173
27293
|
});
|
|
27294
|
+
/**
|
|
27295
|
+
* Whether the overlay is out of the accessibility tree: closed, or covered by
|
|
27296
|
+
* a CDK dialog that opened over it (#322).
|
|
27297
|
+
*
|
|
27298
|
+
* The two are one attribute because they are one question, and rendering them
|
|
27299
|
+
* from separate bindings would mean the second could clear the first.
|
|
27300
|
+
*/
|
|
27301
|
+
hiddenFromAssistiveTech = computed(() => !this.open() || this.dialogsAbove().size > 0, ...(ngDevMode ? [{ debugName: "hiddenFromAssistiveTech" }] : []));
|
|
27174
27302
|
// Focus restoration
|
|
27175
27303
|
previouslyFocusedElement = null;
|
|
27176
27304
|
/**
|
|
@@ -27178,8 +27306,22 @@ class TnSidePanelComponent {
|
|
|
27178
27306
|
* above fire exactly once per change whether or not a transition ran. A field
|
|
27179
27307
|
* initializer rather than the constructor, because it registers an `effect`
|
|
27180
27308
|
* and so needs an injection context.
|
|
27309
|
+
*
|
|
27310
|
+
* It is also what times the overlay's release (#322). The CDK overlay has to
|
|
27311
|
+
* outlive the CLOSE — disposing it puts the element back in this component's
|
|
27312
|
+
* view, where it is `display: none`, so doing that the moment `open` goes
|
|
27313
|
+
* false replaces the slide-out with a disappearance. "The close has settled"
|
|
27314
|
+
* is exactly the question this helper already answers, for a transition that
|
|
27315
|
+
* may never fire.
|
|
27181
27316
|
*/
|
|
27182
|
-
lifecycle = tnTransitionLifecycle(this.open, (open) =>
|
|
27317
|
+
lifecycle = tnTransitionLifecycle(this.open, (open) => {
|
|
27318
|
+
if (open) {
|
|
27319
|
+
this.opened.emit();
|
|
27320
|
+
return;
|
|
27321
|
+
}
|
|
27322
|
+
this.releaseOverlay();
|
|
27323
|
+
this.closed.emit();
|
|
27324
|
+
});
|
|
27183
27325
|
constructor() {
|
|
27184
27326
|
this.registerMdiIcons();
|
|
27185
27327
|
// Moves focus onto the panel when it opens (#227) — the other half of the
|
|
@@ -27205,10 +27347,103 @@ class TnSidePanelComponent {
|
|
|
27205
27347
|
this.restoreFocus();
|
|
27206
27348
|
}
|
|
27207
27349
|
});
|
|
27208
|
-
|
|
27209
|
-
|
|
27210
|
-
|
|
27350
|
+
// Hosts the overlay in a CDK overlay while the panel is open (#322). The
|
|
27351
|
+
// element it moves is the one this component renders, so the projected
|
|
27352
|
+
// content, the view queries and the bindings on it all survive the move —
|
|
27353
|
+
// which a `TemplatePortal` would not give, since detaching one destroys the
|
|
27354
|
+
// view and takes the caller's content with it.
|
|
27355
|
+
effect(() => {
|
|
27356
|
+
if (this.open()) {
|
|
27357
|
+
this.showOverlay();
|
|
27358
|
+
}
|
|
27359
|
+
else {
|
|
27360
|
+
this.overlayRef().nativeElement.classList.remove(TN_SIDE_PANEL_OPEN_CLASS);
|
|
27361
|
+
}
|
|
27362
|
+
});
|
|
27363
|
+
// A dialog opened while this panel is on screen is above it, because CDK
|
|
27364
|
+
// stacks by attach order — so it covers the panel, and the panel leaves the
|
|
27365
|
+
// accessibility tree for as long as it does. Dialogs already open when the
|
|
27366
|
+
// panel opens are underneath it and are not counted: `cdkOverlay` is null
|
|
27367
|
+
// until the panel attaches.
|
|
27368
|
+
this.dialog.afterOpened.pipe(takeUntilDestroyed()).subscribe((ref) => {
|
|
27369
|
+
if (!this.cdkOverlay) {
|
|
27370
|
+
return;
|
|
27371
|
+
}
|
|
27372
|
+
this.dialogsAbove.update((above) => new Set(above).add(ref));
|
|
27373
|
+
ref.closed.pipe(take$1(1), takeUntilDestroyed(this.destroyRef)).subscribe(() => {
|
|
27374
|
+
this.dialogsAbove.update((above) => {
|
|
27375
|
+
const remaining = new Set(above);
|
|
27376
|
+
remaining.delete(ref);
|
|
27377
|
+
return remaining;
|
|
27378
|
+
});
|
|
27379
|
+
});
|
|
27211
27380
|
});
|
|
27381
|
+
afterNextRender(() => this.initialized.set(true));
|
|
27382
|
+
}
|
|
27383
|
+
/**
|
|
27384
|
+
* Put the overlay on screen, in a CDK overlay created now (#322).
|
|
27385
|
+
*
|
|
27386
|
+
* The three steps are ordered, and the order is the reason this is not two
|
|
27387
|
+
* bindings:
|
|
27388
|
+
*
|
|
27389
|
+
* 1. **Attach**, which is what fixes where the panel sits in the stack.
|
|
27390
|
+
* 2. **Read layout**, which forces the browser to compute the panel's closed
|
|
27391
|
+
* position in its new home. Without it the attach and the open class land
|
|
27392
|
+
* in one style update, the browser has no "before" to transition from, and
|
|
27393
|
+
* the panel appears rather than slides. A deliberate synchronous reflow,
|
|
27394
|
+
* and the only one: it happens once per open.
|
|
27395
|
+
* 3. **Open**, which is the change the transition runs on.
|
|
27396
|
+
*
|
|
27397
|
+
* Re-entrant on purpose. A panel reopened while its close is still animating
|
|
27398
|
+
* keeps the overlay it already has — the stack has not changed under it, and
|
|
27399
|
+
* `tnTransitionLifecycle` has already cancelled the close that would have
|
|
27400
|
+
* released it.
|
|
27401
|
+
*/
|
|
27402
|
+
showOverlay() {
|
|
27403
|
+
const element = this.overlayRef().nativeElement;
|
|
27404
|
+
if (!this.cdkOverlay) {
|
|
27405
|
+
this.cdkOverlay = createOverlayRef(this.injector, {
|
|
27406
|
+
positionStrategy: createGlobalPositionStrategy(this.injector),
|
|
27407
|
+
// The panel is `position: fixed` and fills the viewport, so there is
|
|
27408
|
+
// nothing to reposition or block when the page scrolls.
|
|
27409
|
+
scrollStrategy: createNoopScrollStrategy(),
|
|
27410
|
+
// The panel draws its own backdrop inside the overlay, because it is
|
|
27411
|
+
// the thing that fades with the panel.
|
|
27412
|
+
hasBackdrop: false,
|
|
27413
|
+
// Names the pane, the same way `TnDialog` names its own
|
|
27414
|
+
// `tn-dialog-panel` — it is how anything looking at the overlay
|
|
27415
|
+
// container tells one of these apart from a dialog or a menu.
|
|
27416
|
+
panelClass: 'tn-side-panel-pane',
|
|
27417
|
+
});
|
|
27418
|
+
this.cdkOverlay.attach(new DomPortal(element));
|
|
27419
|
+
element.classList.add(TN_SIDE_PANEL_ATTACHED_CLASS);
|
|
27420
|
+
// Escape, from CDK's keyboard dispatcher, which delivers it to the
|
|
27421
|
+
// TOPMOST attached overlay and nothing else. That is what keeps a dialog
|
|
27422
|
+
// underneath this panel from closing on the same keystroke, and it works
|
|
27423
|
+
// in the other direction too — a dialog raised from the panel takes the
|
|
27424
|
+
// key instead, which a handler on this element could not have known.
|
|
27425
|
+
this.keydowns = this.cdkOverlay
|
|
27426
|
+
.keydownEvents()
|
|
27427
|
+
.subscribe((event) => this.onOverlayKeydown(event));
|
|
27428
|
+
}
|
|
27429
|
+
element.getBoundingClientRect();
|
|
27430
|
+
element.classList.add(TN_SIDE_PANEL_OPEN_CLASS);
|
|
27431
|
+
}
|
|
27432
|
+
/**
|
|
27433
|
+
* Give the CDK overlay back, once the close has finished animating.
|
|
27434
|
+
*
|
|
27435
|
+
* Disposing rather than detaching, so that the next open builds a new overlay
|
|
27436
|
+
* and takes a new place in the stack — see `cdkOverlay`. It restores the
|
|
27437
|
+
* element to this component's own view, where `display: none` keeps it out of
|
|
27438
|
+
* the way until then.
|
|
27439
|
+
*/
|
|
27440
|
+
releaseOverlay() {
|
|
27441
|
+
this.keydowns?.unsubscribe();
|
|
27442
|
+
this.keydowns = null;
|
|
27443
|
+
this.cdkOverlay?.dispose();
|
|
27444
|
+
this.cdkOverlay = null;
|
|
27445
|
+
this.dialogsAbove.set(new Set());
|
|
27446
|
+
this.overlayRef().nativeElement.classList.remove(TN_SIDE_PANEL_ATTACHED_CLASS);
|
|
27212
27447
|
}
|
|
27213
27448
|
ngOnDestroy() {
|
|
27214
27449
|
const overlay = this.overlayRef().nativeElement;
|
|
@@ -27225,6 +27460,11 @@ class TnSidePanelComponent {
|
|
|
27225
27460
|
// in and back to a trigger they left minutes ago. A no-op after an
|
|
27226
27461
|
// ordinary close either way, which clears `previouslyFocusedElement`.
|
|
27227
27462
|
const heldFocus = overlay.contains(this.document.activeElement);
|
|
27463
|
+
// Released BEFORE the removal, and unconditionally: a panel destroyed while
|
|
27464
|
+
// open still holds a CDK overlay, and disposing it is what takes the pane
|
|
27465
|
+
// out of the overlay container. The element itself comes back here first,
|
|
27466
|
+
// which is what `remove()` below then takes out of the document.
|
|
27467
|
+
this.releaseOverlay();
|
|
27228
27468
|
overlay.remove();
|
|
27229
27469
|
if (heldFocus) {
|
|
27230
27470
|
this.restoreFocus();
|
|
@@ -27249,9 +27489,21 @@ class TnSidePanelComponent {
|
|
|
27249
27489
|
this.dismiss();
|
|
27250
27490
|
}
|
|
27251
27491
|
}
|
|
27252
|
-
|
|
27492
|
+
/**
|
|
27493
|
+
* Escape, delivered by CDK's `OverlayKeyboardDispatcher` (#322).
|
|
27494
|
+
*
|
|
27495
|
+
* It arrives only while this panel is the topmost attached overlay, so
|
|
27496
|
+
* nothing here has to decide whether the key was meant for something above or
|
|
27497
|
+
* below — which is what the `stopPropagation` this replaced was standing in
|
|
27498
|
+
* for, and it could only ever guard the direction it knew about.
|
|
27499
|
+
*
|
|
27500
|
+
* `preventDefault` rather than `stopPropagation`, matching CDK's own
|
|
27501
|
+
* `DialogRef`: the key has already been routed, and swallowing it would hide
|
|
27502
|
+
* it from a consumer listening at the document for their own reasons.
|
|
27503
|
+
*/
|
|
27504
|
+
onOverlayKeydown(event) {
|
|
27253
27505
|
if (event.key === 'Escape' && this.closeOnEscape() && this.open()) {
|
|
27254
|
-
event.
|
|
27506
|
+
event.preventDefault();
|
|
27255
27507
|
this.dismiss();
|
|
27256
27508
|
}
|
|
27257
27509
|
}
|
|
@@ -27287,14 +27539,14 @@ class TnSidePanelComponent {
|
|
|
27287
27539
|
});
|
|
27288
27540
|
}
|
|
27289
27541
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnSidePanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
27290
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnSidePanelComponent, isStandalone: true, selector: "tn-side-panel", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, hasBackdrop: { classPropertyName: "hasBackdrop", publicName: "hasBackdrop", isSignal: true, isRequired: false, transformFunction: null }, closeOnBackdropClick: { classPropertyName: "closeOnBackdropClick", publicName: "closeOnBackdropClick", isSignal: true, isRequired: false, transformFunction: null }, closeOnEscape: { classPropertyName: "closeOnEscape", publicName: "closeOnEscape", isSignal: true, isRequired: false, transformFunction: null }, closeGuard: { classPropertyName: "closeGuard", publicName: "closeGuard", isSignal: true, isRequired: false, transformFunction: null }, testId: { classPropertyName: "testId", publicName: "testId", isSignal: true, isRequired: false, transformFunction: null }, closeButtonTestId: { classPropertyName: "closeButtonTestId", publicName: "closeButtonTestId", isSignal: true, isRequired: false, transformFunction: null }, closeButtonAriaLabel: { classPropertyName: "closeButtonAriaLabel", publicName: "closeButtonAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, contentAriaLabel: { classPropertyName: "contentAriaLabel", publicName: "contentAriaLabel", 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 } }, outputs: { open: "openChange", opened: "opened", closed: "closed" }, host: { properties: { "attr.data-tn-panel": "panelId" }, classAttribute: "tn-side-panel" }, queries: [{ propertyName: "actionContent", predicate: TnSidePanelActionDirective, isSignal: true }], viewQueries: [{ propertyName: "overlayRef", first: true, predicate: ["overlay"], descendants: true, isSignal: true }, { propertyName: "panelRef", first: true, predicate: ["panel"], descendants: true, isSignal: true }, { propertyName: "contentRef", first: true, predicate: ["content"], descendants: true, isSignal: true }], ngImport: i0, template: "<!--\n Overlay wrapper: portaled to document.body.\n\n A CLOSED panel is `inert` as well as `aria-hidden` (#214). It stays in the DOM\n and keeps its close button and its projected content, so `aria-hidden` alone\n hid a subtree a keyboard user could still Tab into \u2014 landing focus on controls\n that are translated off-screen. `inert` takes the subtree out of the focus\n order and the accessibility tree together; `aria-hidden` stays beside it for\n browsers without `inert` (Firefox before 112), where it is still the half that\n works. Same reasoning as the checkbox in `list-option.component.html`.\n\n `aria-label` and `aria-labelledby` are gated on `open()` for the same reason\n `aria-modal` is: a closed panel is not in the accessibility tree, so a name on\n it would describe nothing. `resolvedAriaLabelledby` prefers the visible\n heading. `resolvedAriaLabel` is `null` when the panel is named by a heading or\n an IDREF and the caller passed no `ariaLabel` of their own \u2014 an explicit one is\n always emitted, so that a dangling IDREF cannot leave the panel unnamed.\n-->\n<div\n #overlay\n class=\"tn-side-panel__overlay\"\n role=\"dialog\"\n tnTestIdType=\"side-panel\"\n [attr.data-tn-panel]=\"panelId\"\n [tnTestId]=\"testId()\"\n [class.tn-side-panel__overlay--initialized]=\"initialized()\"\n [class.tn-side-panel__overlay--open]=\"open()\"\n [attr.aria-modal]=\"open() ? 'true' : null\"\n [attr.aria-labelledby]=\"open() ? resolvedAriaLabelledby() : null\"\n [attr.aria-label]=\"open() ? resolvedAriaLabel() : null\"\n [attr.aria-hidden]=\"!open() ? 'true' : null\"\n [attr.inert]=\"open() ? null : ''\">\n\n <!-- Backdrop -->\n @if (hasBackdrop()) {\n <div\n class=\"tn-side-panel__backdrop\"\n aria-hidden=\"true\"\n (click)=\"onBackdropClick()\">\n </div>\n }\n\n <!--\n Panel. `cdkTrapFocus` keeps Tab inside it while it is open; what moves focus\n IN on open is `tnFocusOnOpen` in the component, not\n `[cdkTrapFocusAutoCapture]`, which used to be here and could silently do\n nothing (#227). `tabindex=\"-1\"` is what makes this element the target it\n focuses \u2014 see `../a11y/initial-focus.ts` for why the container and not the\n first control inside it.\n -->\n <div\n #panel\n class=\"tn-side-panel__panel\"\n tabindex=\"-1\"\n cdkTrapFocus\n [style.width]=\"width()\"\n (transitionend)=\"onTransitionEnd($event)\"\n (keydown)=\"onKeydown($event)\">\n\n <!-- Header -->\n <header class=\"tn-side-panel__header\">\n <!--\n No heading element at all when there is no title, rather than an empty\n one: an `<h2>` with no text is an `empty-heading` violation, and it puts\n a level-2 heading with nothing in it into the document outline.\n -->\n @if (hasTitle()) {\n <h2 class=\"tn-side-panel__title\" [id]=\"titleId\">{{ title() }}</h2>\n }\n <div class=\"tn-side-panel__header-actions\">\n <ng-content select=\"[tnSidePanelHeaderAction]\" />\n <tn-icon-button\n name=\"close\"\n library=\"mdi\"\n size=\"sm\"\n [ariaLabel]=\"closeButtonAriaLabel()\"\n [testId]=\"closeButtonTestId()\"\n (onClick)=\"dismiss()\" />\n </div>\n </header>\n\n <!--\n Content. This is the element that scrolls, and a scrolling element a\n keyboard cannot reach is content below the fold that a keyboard cannot\n read (#248). The panel's own `tabindex=\"-1\"` above does not cover it: that\n is a focus TARGET, not a tab stop, and it is on the container rather than\n on the element with the overflow.\n\n All three attributes are gated together, and the gate is the point \u2014 an\n unconditional tab stop would land on every panel in the library, including\n the ones whose content fits, where it announces a group and does nothing.\n\n The gate is `contentKeyboardReachable`, which is the measurement OR the\n region's own focus, not the measurement alone. Content that stops\n overflowing while the region is focused would otherwise take the tab stop\n off the focused element and blur a keyboard user out of the open panel.\n Both halves are `tnScrollableRegion`'s, in `../a11y/scrollable-region.ts`,\n which says why in full \u2014 including why it listens for this element's own\n `focus`/`blur` itself rather than leaving each caller to bind them.\n\n `role=\"group\"` rather than the `region` a named `<section>` would imply:\n `region` is a landmark, and a landmark named the same thing in every panel\n is what `landmark-unique` reports the moment two of them are open. A group\n is not summarised, which is what this is.\n -->\n <section\n #content\n class=\"tn-side-panel__content\"\n [attr.tabindex]=\"contentKeyboardReachable() ? '0' : null\"\n [attr.role]=\"contentKeyboardReachable() ? 'group' : null\"\n [attr.aria-label]=\"contentKeyboardReachable() ? contentAriaLabel() : null\">\n <ng-content />\n </section>\n\n <!-- Actions -->\n @if (hasActions()) {\n <footer class=\"tn-side-panel__actions\">\n <ng-content select=\"[tnSidePanelAction]\" />\n </footer>\n }\n </div>\n</div>\n", styles: [":host{display:contents}.tn-side-panel__overlay{position:fixed;inset:0;z-index:1000;pointer-events:none}.tn-side-panel__overlay--open{pointer-events:auto}.tn-side-panel__overlay--open .tn-side-panel__backdrop{opacity:1}.tn-side-panel__overlay--open .tn-side-panel__panel{transform:translate(0);box-shadow:-4px 0 24px #0000004d}.tn-side-panel__backdrop{position:absolute;inset:0;background:#00000080;opacity:0}.tn-side-panel__overlay--initialized .tn-side-panel__backdrop{transition:opacity .2s ease}.tn-side-panel__panel{position:absolute;top:0;right:0;bottom:0;display:flex;flex-direction:column;max-width:100vw;background:var(--tn-bg2, #282828);color:var(--tn-fg1, #ffffff);box-shadow:-4px 0 24px #0000;outline:none;transform:translate(100%)}.tn-side-panel__overlay--initialized .tn-side-panel__panel{transition:transform .3s cubic-bezier(.4,0,.2,1),box-shadow .3s cubic-bezier(.4,0,.2,1)}.tn-side-panel__header{flex:0 0 auto;display:flex;align-items:center;gap:16px;padding:16px 24px;border-bottom:1px solid var(--tn-lines, #383838)}.tn-side-panel__title{margin:0;font-size:1.25rem;font-weight:600;line-height:1.5;color:var(--tn-fg1, #ffffff);flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tn-side-panel__header-actions{display:flex;align-items:center;gap:8px;flex-shrink:0;margin-left:auto}.tn-side-panel__content{flex:1 1 auto;min-height:0;overflow-y:auto;overflow-x:hidden;padding:var(--tn-content-padding, 24px);-webkit-overflow-scrolling:touch;overflow-wrap:break-word}.tn-side-panel__actions{flex:0 0 auto;display:flex;justify-content:flex-end;gap:12px;padding:16px 24px;border-top:1px solid var(--tn-lines, #383838)}@media(max-width:640px){.tn-side-panel__panel{width:100vw!important}}@media(prefers-reduced-motion:reduce){.tn-side-panel__panel,.tn-side-panel__backdrop{transition-duration:0ms!important}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: A11yModule }, { kind: "directive", type: i1.CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "component", type: TnIconButtonComponent, selector: "tn-icon-button", inputs: ["disabled", "dense", "ariaLabel", "ariaExpanded", "testId", "name", "size", "color", "tooltip", "tooltipPosition", "tooltipSticky", "library", "iconClass"], outputs: ["onClick"] }, { kind: "directive", type: TnTestIdDirective, selector: "[tnTestId]", inputs: ["tnTestId", "tnTestIdType"] }] });
|
|
27542
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnSidePanelComponent, isStandalone: true, selector: "tn-side-panel", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, hasBackdrop: { classPropertyName: "hasBackdrop", publicName: "hasBackdrop", isSignal: true, isRequired: false, transformFunction: null }, closeOnBackdropClick: { classPropertyName: "closeOnBackdropClick", publicName: "closeOnBackdropClick", isSignal: true, isRequired: false, transformFunction: null }, closeOnEscape: { classPropertyName: "closeOnEscape", publicName: "closeOnEscape", isSignal: true, isRequired: false, transformFunction: null }, closeGuard: { classPropertyName: "closeGuard", publicName: "closeGuard", isSignal: true, isRequired: false, transformFunction: null }, testId: { classPropertyName: "testId", publicName: "testId", isSignal: true, isRequired: false, transformFunction: null }, closeButtonTestId: { classPropertyName: "closeButtonTestId", publicName: "closeButtonTestId", isSignal: true, isRequired: false, transformFunction: null }, closeButtonAriaLabel: { classPropertyName: "closeButtonAriaLabel", publicName: "closeButtonAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, contentAriaLabel: { classPropertyName: "contentAriaLabel", publicName: "contentAriaLabel", 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 } }, outputs: { open: "openChange", opened: "opened", closed: "closed" }, host: { properties: { "attr.data-tn-panel": "panelId" }, classAttribute: "tn-side-panel" }, queries: [{ propertyName: "actionContent", predicate: TnSidePanelActionDirective, isSignal: true }], viewQueries: [{ propertyName: "overlayRef", first: true, predicate: ["overlay"], descendants: true, isSignal: true }, { propertyName: "panelRef", first: true, predicate: ["panel"], descendants: true, isSignal: true }, { propertyName: "contentRef", first: true, predicate: ["content"], descendants: true, isSignal: true }], ngImport: i0, template: "<!--\n Overlay wrapper: moved into a CDK overlay while the panel is open (#322), and\n back here \u2014 `display: none` \u2014 while it is not. `--open` and `--attached` are\n applied by the component rather than bound, because both have to land in a\n fixed order around the move; see the constants in `side-panel.component.ts`.\n\n A CLOSED panel is `inert` as well as `aria-hidden` (#214). It stays in the DOM\n and keeps its close button and its projected content, so `aria-hidden` alone\n hid a subtree a keyboard user could still Tab into \u2014 landing focus on controls\n that are translated off-screen. `inert` takes the subtree out of the focus\n order and the accessibility tree together; `aria-hidden` stays beside it for\n browsers without `inert` (Firefox before 112), where it is still the half that\n works. Same reasoning as the checkbox in `list-option.component.html`.\n\n `aria-label` and `aria-labelledby` are gated on `open()` for the same reason\n `aria-modal` is: a closed panel is not in the accessibility tree, so a name on\n it would describe nothing. `resolvedAriaLabelledby` prefers the visible\n heading. `resolvedAriaLabel` is `null` when the panel is named by a heading or\n an IDREF and the caller passed no `ariaLabel` of their own \u2014 an explicit one is\n always emitted, so that a dangling IDREF cannot leave the panel unnamed.\n\n `aria-hidden` is `hiddenFromAssistiveTech` rather than `!open()` because a\n dialog opened OVER the panel hides it too (#322) \u2014 the panel is still open\n underneath, which is why the other three stay on `open()`.\n-->\n<div\n #overlay\n class=\"tn-side-panel__overlay\"\n role=\"dialog\"\n tnTestIdType=\"side-panel\"\n [attr.data-tn-panel]=\"panelId\"\n [tnTestId]=\"testId()\"\n [class.tn-side-panel__overlay--initialized]=\"initialized()\"\n [attr.aria-modal]=\"open() ? 'true' : null\"\n [attr.aria-labelledby]=\"open() ? resolvedAriaLabelledby() : null\"\n [attr.aria-label]=\"open() ? resolvedAriaLabel() : null\"\n [attr.aria-hidden]=\"hiddenFromAssistiveTech() ? 'true' : null\"\n [attr.inert]=\"open() ? null : ''\">\n\n <!-- Backdrop -->\n @if (hasBackdrop()) {\n <div\n class=\"tn-side-panel__backdrop\"\n aria-hidden=\"true\"\n (click)=\"onBackdropClick()\">\n </div>\n }\n\n <!--\n Panel. `cdkTrapFocus` keeps Tab inside it while it is open; what moves focus\n IN on open is `tnFocusOnOpen` in the component, not\n `[cdkTrapFocusAutoCapture]`, which used to be here and could silently do\n nothing (#227). `tabindex=\"-1\"` is what makes this element the target it\n focuses \u2014 see `../a11y/initial-focus.ts` for why the container and not the\n first control inside it.\n\n No `(keydown)` here since #322: Escape arrives through CDK's overlay\n keyboard dispatcher, which knows which overlay is on top and this element\n does not.\n -->\n <div\n #panel\n class=\"tn-side-panel__panel\"\n tabindex=\"-1\"\n cdkTrapFocus\n [style.width]=\"width()\"\n (transitionend)=\"onTransitionEnd($event)\">\n\n <!-- Header -->\n <header class=\"tn-side-panel__header\">\n <!--\n No heading element at all when there is no title, rather than an empty\n one: an `<h2>` with no text is an `empty-heading` violation, and it puts\n a level-2 heading with nothing in it into the document outline.\n -->\n @if (hasTitle()) {\n <h2 class=\"tn-side-panel__title\" [id]=\"titleId\">{{ title() }}</h2>\n }\n <div class=\"tn-side-panel__header-actions\">\n <ng-content select=\"[tnSidePanelHeaderAction]\" />\n <tn-icon-button\n name=\"close\"\n library=\"mdi\"\n size=\"sm\"\n [ariaLabel]=\"closeButtonAriaLabel()\"\n [testId]=\"closeButtonTestId()\"\n (onClick)=\"dismiss()\" />\n </div>\n </header>\n\n <!--\n Content. This is the element that scrolls, and a scrolling element a\n keyboard cannot reach is content below the fold that a keyboard cannot\n read (#248). The panel's own `tabindex=\"-1\"` above does not cover it: that\n is a focus TARGET, not a tab stop, and it is on the container rather than\n on the element with the overflow.\n\n All three attributes are gated together, and the gate is the point \u2014 an\n unconditional tab stop would land on every panel in the library, including\n the ones whose content fits, where it announces a group and does nothing.\n\n The gate is `contentKeyboardReachable`, which is the measurement OR the\n region's own focus, not the measurement alone. Content that stops\n overflowing while the region is focused would otherwise take the tab stop\n off the focused element and blur a keyboard user out of the open panel.\n Both halves are `tnScrollableRegion`'s, in `../a11y/scrollable-region.ts`,\n which says why in full \u2014 including why it listens for this element's own\n `focus`/`blur` itself rather than leaving each caller to bind them.\n\n `role=\"group\"` rather than the `region` a named `<section>` would imply:\n `region` is a landmark, and a landmark named the same thing in every panel\n is what `landmark-unique` reports the moment two of them are open. A group\n is not summarised, which is what this is.\n -->\n <section\n #content\n class=\"tn-side-panel__content\"\n [attr.tabindex]=\"contentKeyboardReachable() ? '0' : null\"\n [attr.role]=\"contentKeyboardReachable() ? 'group' : null\"\n [attr.aria-label]=\"contentKeyboardReachable() ? contentAriaLabel() : null\">\n <ng-content />\n </section>\n\n <!-- Actions -->\n @if (hasActions()) {\n <footer class=\"tn-side-panel__actions\">\n <ng-content select=\"[tnSidePanelAction]\" />\n </footer>\n }\n </div>\n</div>\n", styles: [":host{display:contents}.tn-side-panel__overlay{position:fixed;inset:0;display:none;pointer-events:none}.tn-side-panel__overlay--attached{display:block}.tn-side-panel__overlay--open{pointer-events:auto}.tn-side-panel__overlay--open .tn-side-panel__backdrop{opacity:1}.tn-side-panel__overlay--open .tn-side-panel__panel{transform:translate(0);box-shadow:-4px 0 24px #0000004d}.tn-side-panel__backdrop{position:absolute;inset:0;background:#00000080;opacity:0}.tn-side-panel__overlay--initialized .tn-side-panel__backdrop{transition:opacity .2s ease}.tn-side-panel__panel{position:absolute;top:0;right:0;bottom:0;display:flex;flex-direction:column;max-width:100vw;background:var(--tn-bg2, #282828);color:var(--tn-fg1, #ffffff);box-shadow:-4px 0 24px #0000;outline:none;transform:translate(100%)}.tn-side-panel__overlay--initialized .tn-side-panel__panel{transition:transform .3s cubic-bezier(.4,0,.2,1),box-shadow .3s cubic-bezier(.4,0,.2,1)}.tn-side-panel__header{flex:0 0 auto;display:flex;align-items:center;gap:16px;padding:16px 24px;border-bottom:1px solid var(--tn-lines, #383838)}.tn-side-panel__title{margin:0;font-size:1.25rem;font-weight:600;line-height:1.5;color:var(--tn-fg1, #ffffff);flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tn-side-panel__header-actions{display:flex;align-items:center;gap:8px;flex-shrink:0;margin-left:auto}.tn-side-panel__content{flex:1 1 auto;min-height:0;overflow-y:auto;overflow-x:hidden;padding:var(--tn-content-padding, 24px);-webkit-overflow-scrolling:touch;overflow-wrap:break-word}.tn-side-panel__actions{flex:0 0 auto;display:flex;justify-content:flex-end;gap:12px;padding:16px 24px;border-top:1px solid var(--tn-lines, #383838)}@media(max-width:640px){.tn-side-panel__panel{width:100vw!important}}@media(prefers-reduced-motion:reduce){.tn-side-panel__panel,.tn-side-panel__backdrop{transition-duration:0ms!important}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: A11yModule }, { kind: "directive", type: i1.CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "component", type: TnIconButtonComponent, selector: "tn-icon-button", inputs: ["disabled", "dense", "ariaLabel", "ariaExpanded", "testId", "name", "size", "color", "tooltip", "tooltipPosition", "tooltipSticky", "library", "iconClass"], outputs: ["onClick"] }, { kind: "directive", type: TnTestIdDirective, selector: "[tnTestId]", inputs: ["tnTestId", "tnTestIdType"] }] });
|
|
27291
27543
|
}
|
|
27292
27544
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnSidePanelComponent, decorators: [{
|
|
27293
27545
|
type: Component,
|
|
27294
27546
|
args: [{ selector: 'tn-side-panel', standalone: true, imports: [CommonModule, A11yModule, TnIconButtonComponent, TnTestIdDirective], host: {
|
|
27295
27547
|
'class': 'tn-side-panel',
|
|
27296
27548
|
'[attr.data-tn-panel]': 'panelId',
|
|
27297
|
-
}, template: "<!--\n Overlay wrapper:
|
|
27549
|
+
}, template: "<!--\n Overlay wrapper: moved into a CDK overlay while the panel is open (#322), and\n back here \u2014 `display: none` \u2014 while it is not. `--open` and `--attached` are\n applied by the component rather than bound, because both have to land in a\n fixed order around the move; see the constants in `side-panel.component.ts`.\n\n A CLOSED panel is `inert` as well as `aria-hidden` (#214). It stays in the DOM\n and keeps its close button and its projected content, so `aria-hidden` alone\n hid a subtree a keyboard user could still Tab into \u2014 landing focus on controls\n that are translated off-screen. `inert` takes the subtree out of the focus\n order and the accessibility tree together; `aria-hidden` stays beside it for\n browsers without `inert` (Firefox before 112), where it is still the half that\n works. Same reasoning as the checkbox in `list-option.component.html`.\n\n `aria-label` and `aria-labelledby` are gated on `open()` for the same reason\n `aria-modal` is: a closed panel is not in the accessibility tree, so a name on\n it would describe nothing. `resolvedAriaLabelledby` prefers the visible\n heading. `resolvedAriaLabel` is `null` when the panel is named by a heading or\n an IDREF and the caller passed no `ariaLabel` of their own \u2014 an explicit one is\n always emitted, so that a dangling IDREF cannot leave the panel unnamed.\n\n `aria-hidden` is `hiddenFromAssistiveTech` rather than `!open()` because a\n dialog opened OVER the panel hides it too (#322) \u2014 the panel is still open\n underneath, which is why the other three stay on `open()`.\n-->\n<div\n #overlay\n class=\"tn-side-panel__overlay\"\n role=\"dialog\"\n tnTestIdType=\"side-panel\"\n [attr.data-tn-panel]=\"panelId\"\n [tnTestId]=\"testId()\"\n [class.tn-side-panel__overlay--initialized]=\"initialized()\"\n [attr.aria-modal]=\"open() ? 'true' : null\"\n [attr.aria-labelledby]=\"open() ? resolvedAriaLabelledby() : null\"\n [attr.aria-label]=\"open() ? resolvedAriaLabel() : null\"\n [attr.aria-hidden]=\"hiddenFromAssistiveTech() ? 'true' : null\"\n [attr.inert]=\"open() ? null : ''\">\n\n <!-- Backdrop -->\n @if (hasBackdrop()) {\n <div\n class=\"tn-side-panel__backdrop\"\n aria-hidden=\"true\"\n (click)=\"onBackdropClick()\">\n </div>\n }\n\n <!--\n Panel. `cdkTrapFocus` keeps Tab inside it while it is open; what moves focus\n IN on open is `tnFocusOnOpen` in the component, not\n `[cdkTrapFocusAutoCapture]`, which used to be here and could silently do\n nothing (#227). `tabindex=\"-1\"` is what makes this element the target it\n focuses \u2014 see `../a11y/initial-focus.ts` for why the container and not the\n first control inside it.\n\n No `(keydown)` here since #322: Escape arrives through CDK's overlay\n keyboard dispatcher, which knows which overlay is on top and this element\n does not.\n -->\n <div\n #panel\n class=\"tn-side-panel__panel\"\n tabindex=\"-1\"\n cdkTrapFocus\n [style.width]=\"width()\"\n (transitionend)=\"onTransitionEnd($event)\">\n\n <!-- Header -->\n <header class=\"tn-side-panel__header\">\n <!--\n No heading element at all when there is no title, rather than an empty\n one: an `<h2>` with no text is an `empty-heading` violation, and it puts\n a level-2 heading with nothing in it into the document outline.\n -->\n @if (hasTitle()) {\n <h2 class=\"tn-side-panel__title\" [id]=\"titleId\">{{ title() }}</h2>\n }\n <div class=\"tn-side-panel__header-actions\">\n <ng-content select=\"[tnSidePanelHeaderAction]\" />\n <tn-icon-button\n name=\"close\"\n library=\"mdi\"\n size=\"sm\"\n [ariaLabel]=\"closeButtonAriaLabel()\"\n [testId]=\"closeButtonTestId()\"\n (onClick)=\"dismiss()\" />\n </div>\n </header>\n\n <!--\n Content. This is the element that scrolls, and a scrolling element a\n keyboard cannot reach is content below the fold that a keyboard cannot\n read (#248). The panel's own `tabindex=\"-1\"` above does not cover it: that\n is a focus TARGET, not a tab stop, and it is on the container rather than\n on the element with the overflow.\n\n All three attributes are gated together, and the gate is the point \u2014 an\n unconditional tab stop would land on every panel in the library, including\n the ones whose content fits, where it announces a group and does nothing.\n\n The gate is `contentKeyboardReachable`, which is the measurement OR the\n region's own focus, not the measurement alone. Content that stops\n overflowing while the region is focused would otherwise take the tab stop\n off the focused element and blur a keyboard user out of the open panel.\n Both halves are `tnScrollableRegion`'s, in `../a11y/scrollable-region.ts`,\n which says why in full \u2014 including why it listens for this element's own\n `focus`/`blur` itself rather than leaving each caller to bind them.\n\n `role=\"group\"` rather than the `region` a named `<section>` would imply:\n `region` is a landmark, and a landmark named the same thing in every panel\n is what `landmark-unique` reports the moment two of them are open. A group\n is not summarised, which is what this is.\n -->\n <section\n #content\n class=\"tn-side-panel__content\"\n [attr.tabindex]=\"contentKeyboardReachable() ? '0' : null\"\n [attr.role]=\"contentKeyboardReachable() ? 'group' : null\"\n [attr.aria-label]=\"contentKeyboardReachable() ? contentAriaLabel() : null\">\n <ng-content />\n </section>\n\n <!-- Actions -->\n @if (hasActions()) {\n <footer class=\"tn-side-panel__actions\">\n <ng-content select=\"[tnSidePanelAction]\" />\n </footer>\n }\n </div>\n</div>\n", styles: [":host{display:contents}.tn-side-panel__overlay{position:fixed;inset:0;display:none;pointer-events:none}.tn-side-panel__overlay--attached{display:block}.tn-side-panel__overlay--open{pointer-events:auto}.tn-side-panel__overlay--open .tn-side-panel__backdrop{opacity:1}.tn-side-panel__overlay--open .tn-side-panel__panel{transform:translate(0);box-shadow:-4px 0 24px #0000004d}.tn-side-panel__backdrop{position:absolute;inset:0;background:#00000080;opacity:0}.tn-side-panel__overlay--initialized .tn-side-panel__backdrop{transition:opacity .2s ease}.tn-side-panel__panel{position:absolute;top:0;right:0;bottom:0;display:flex;flex-direction:column;max-width:100vw;background:var(--tn-bg2, #282828);color:var(--tn-fg1, #ffffff);box-shadow:-4px 0 24px #0000;outline:none;transform:translate(100%)}.tn-side-panel__overlay--initialized .tn-side-panel__panel{transition:transform .3s cubic-bezier(.4,0,.2,1),box-shadow .3s cubic-bezier(.4,0,.2,1)}.tn-side-panel__header{flex:0 0 auto;display:flex;align-items:center;gap:16px;padding:16px 24px;border-bottom:1px solid var(--tn-lines, #383838)}.tn-side-panel__title{margin:0;font-size:1.25rem;font-weight:600;line-height:1.5;color:var(--tn-fg1, #ffffff);flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tn-side-panel__header-actions{display:flex;align-items:center;gap:8px;flex-shrink:0;margin-left:auto}.tn-side-panel__content{flex:1 1 auto;min-height:0;overflow-y:auto;overflow-x:hidden;padding:var(--tn-content-padding, 24px);-webkit-overflow-scrolling:touch;overflow-wrap:break-word}.tn-side-panel__actions{flex:0 0 auto;display:flex;justify-content:flex-end;gap:12px;padding:16px 24px;border-top:1px solid var(--tn-lines, #383838)}@media(max-width:640px){.tn-side-panel__panel{width:100vw!important}}@media(prefers-reduced-motion:reduce){.tn-side-panel__panel,.tn-side-panel__backdrop{transition-duration:0ms!important}}\n"] }]
|
|
27298
27550
|
}], ctorParameters: () => [], propDecorators: { overlayRef: [{ type: i0.ViewChild, args: ['overlay', { isSignal: true }] }], panelRef: [{ type: i0.ViewChild, args: ['panel', { isSignal: true }] }], contentRef: [{ type: i0.ViewChild, args: ['content', { isSignal: true }] }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], hasBackdrop: [{ type: i0.Input, args: [{ isSignal: true, alias: "hasBackdrop", required: false }] }], closeOnBackdropClick: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnBackdropClick", required: false }] }], closeOnEscape: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnEscape", required: false }] }], closeGuard: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeGuard", required: false }] }], testId: [{ type: i0.Input, args: [{ isSignal: true, alias: "testId", required: false }] }], closeButtonTestId: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeButtonTestId", required: false }] }], closeButtonAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeButtonAriaLabel", required: false }] }], contentAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "contentAriaLabel", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], ariaLabelledby: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabelledby", required: false }] }], opened: [{ type: i0.Output, args: ["opened"] }], closed: [{ type: i0.Output, args: ["closed"] }], actionContent: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => TnSidePanelActionDirective), { isSignal: true }] }] } });
|
|
27299
27551
|
|
|
27300
27552
|
/**
|
|
@@ -27315,7 +27567,8 @@ class TnSidePanelHarness extends ComponentHarness {
|
|
|
27315
27567
|
.addOption('title', options.title, (harness, title) => HarnessPredicate.stringMatches(harness.getTitle(), title));
|
|
27316
27568
|
}
|
|
27317
27569
|
/**
|
|
27318
|
-
* Locate the overlay wrapper, which
|
|
27570
|
+
* Locate the overlay wrapper, which is portaled into a CDK overlay while the
|
|
27571
|
+
* panel is open and sits inside the host element while it is not.
|
|
27319
27572
|
* Uses the data-tn-panel attribute to correlate the host with its overlay.
|
|
27320
27573
|
*/
|
|
27321
27574
|
async getOverlay() {
|