@almoamendev/ngx-md3 0.7.5 → 0.8.1

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.
@@ -8,12 +8,12 @@ import * as i1 from '@angular/router';
8
8
  import { Router, NavigationEnd, RouterLinkActive } from '@angular/router';
9
9
  import { CdkPortalOutlet, ComponentPortal } from '@angular/cdk/portal';
10
10
  import { CdkDialogContainer, Dialog as Dialog$1 } from '@angular/cdk/dialog';
11
- import { Overlay, OverlayContainer, OverlayConfig } from '@angular/cdk/overlay';
11
+ import { Overlay, OverlayConfig, OverlayContainer } from '@angular/cdk/overlay';
12
12
  import { hasModifierKey } from '@angular/cdk/keycodes';
13
13
  import * as i2 from '@angular/cdk/scrolling';
14
14
  import { CdkScrollable } from '@angular/cdk/scrolling';
15
15
  import { Directionality } from '@angular/cdk/bidi';
16
- import { FormControlName } from '@angular/forms';
16
+ import { FormControlName, NgControl } from '@angular/forms';
17
17
 
18
18
  class TypeDisplay {
19
19
  el;
@@ -4353,7 +4353,6 @@ class Switch {
4353
4353
  inputDisabledState = signal(false, /* @ts-ignore */
4354
4354
  ...(ngDevMode ? [{ debugName: "inputDisabledState" }] : /* istanbul ignore next */ []));
4355
4355
  isDisabled = computed(() => {
4356
- console.log('Switch disabled');
4357
4356
  return this.inputDisabledState();
4358
4357
  }, /* @ts-ignore */
4359
4358
  ...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
@@ -6019,7 +6018,7 @@ class TextField {
6019
6018
  provide: MD3_BUTTON_CONTEXT,
6020
6019
  useExisting: TextField,
6021
6020
  },
6022
- ], queries: [{ propertyName: "input", first: true, predicate: InputElement, descendants: true, isSignal: true }, { propertyName: "iconElements", predicate: IconElement, descendants: true, isSignal: true }, { propertyName: "iconButtons", predicate: IconButton, descendants: true, isSignal: true }, { propertyName: "controlName", first: true, predicate: FormControlName, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "inputContainer", first: true, predicate: ["inputContainer"], descendants: true, isSignal: true }], ngImport: i0, template: "<label #inputContainer class=\"md3-text-field-container\" [ngClass]=\"{\n 'md3-text-field-filled': fieldType() == 'filled',\n 'md3-text-field-outlined': fieldType() == 'outlined',\n}\">\n <ng-content></ng-content>\n <fieldset aria-hidden=\"true\" class=\"md3-text-field-outline\">\n @if (label() && fieldType() == 'outlined') {\n <legend class=\"md3-label-gap\">{{ label() }}</legend>\n }\n </fieldset>\n @if (label()) {\n <label class=\"md3-text-field-label\">{{ label() }}</label>\n }\n <ng-content select=\"[md3-icon-element=leading]\"></ng-content>\n <ng-content select=\"[md3-icon-button], [md3-icon-element=trailing]\"></ng-content>\n @if (fieldType() != 'outlined') {\n <div class=\"md3-text-field-state\"></div>\n }\n</label>\n<div class=\"md3-supporting-text-container\" [class.md3-has-error]=\"inputError()\">\n <ng-content select=\"[md3-supporting-text]\"></ng-content>\n @if (inputCounter()) {\n <div class=\"md3-text-input-counter\">{{ valueLength() + (maxLength() ? \"/\" + maxLength() : \"\") }}</div>\n }\n</div>\n", styles: [":host{--background-color: rgb(var(--md-scheme-surface-container-highest));--foreground-color: rgb(var(--md-scheme-on-surface));--label-color: rgb(var(--md-scheme-on-surface-variant));--caret-color: rgb(var(--md-scheme-primary));--outline-color: rgb(var(--md-scheme-on-surface-variant));font-size:1rem;width:100%}:host .md3-text-field-container{display:block;position:relative;border-radius:var(--md-border-radius-xsmall) var(--md-border-radius-xsmall) 0 0;overflow:hidden}:host .md3-text-field-container ::ng-deep [md3-icon-element=leading]{position:absolute;font-size:1.5em!important;height:1em!important;width:1em!important;top:.666667em;inset-inline-start:.5em;pointer-events:none;color:var(--label-color)}:host .md3-text-field-container ::ng-deep [md3-icon-element=trailing]{position:absolute;font-size:1.5em!important;height:1em!important;width:1em!important;top:.666667em;inset-inline-end:.5em;pointer-events:none;color:var(--label-color)}:host .md3-text-field-container ::ng-deep [md3-icon-button]{position:absolute;font-size:1em!important;top:.5em;inset-inline-end:.5em}:host .md3-text-field-container .md3-text-field-outline{position:absolute;inset:0;pointer-events:none;padding:0 .625em;margin:.0625em 0 0;border:none;border-bottom:.0625em solid var(--outline-color);transition:all var(--md-motion-standard-fast-spatial-duration) var(--md-motion-standard-fast-spatial-easing),border-width none}:host .md3-text-field-container .md3-text-field-outline .md3-label-gap{padding:0;max-width:0em;font-size:.75em;font-weight:400;line-height:1.333332em;letter-spacing:.041667em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;opacity:0;visibility:hidden;transition:all var(--md-motion-standard-fast-spatial-duration) var(--md-motion-standard-fast-spatial-easing);interpolate-size:allow-keywords}:host .md3-text-field-container .md3-text-field-label{position:absolute;top:1em;inset-inline-start:1em;inset-inline-end:1em;padding:0;font-size:1em;font-weight:400;line-height:1.5em;letter-spacing:.03125em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;pointer-events:none;color:var(--label-color);transform-origin:top left;transform:scale(1);transition:all var(--md-motion-standard-fast-spatial-duration) var(--md-motion-standard-fast-spatial-easing)}:host .md3-text-field-container .md3-text-field-label:dir(rtl){transform-origin:top right}:host .md3-text-field-container .md3-text-field-state{position:absolute;inset:0;pointer-events:none;border-radius:inherit;background-color:rgba(var(--md-scheme-on-surface));opacity:0;transition:all var(--md-motion-standard-fast-spatial-duration) var(--md-motion-standard-fast-spatial-easing)}:host .md3-text-field-container ::ng-deep .md3-input-element{display:block;width:100%;border:none;outline:none;padding:1.5em 1em .5em;font-size:1em;font-weight:400;line-height:1.5em;letter-spacing:.03125em;resize:none;background-color:var(--background-color);color:var(--foreground-color);caret-color:var(--caret-color)}:host .md3-text-field-container ::ng-deep .md3-input-element:not(:placeholder-shown)~.md3-text-field-label{top:.4375em;transform:scale(.75)}:host .md3-text-field-container ::ng-deep .md3-input-element:hover~.md3-text-field-state{opacity:.08}:host .md3-text-field-container ::ng-deep .md3-input-element:hover~.md3-text-field-outline{--outline-color: rgb(var(--md-scheme-on-surface))}:host .md3-text-field-container ::ng-deep .md3-input-element:focus~.md3-text-field-outline{--outline-color: rgb(var(--md-scheme-primary));border-bottom-width:.1875em}:host .md3-text-field-container ::ng-deep .md3-input-element:focus~.md3-text-field-label{color:var(--caret-color);top:.4375em;transform:scale(.75)}:host .md3-text-field-container ::ng-deep .md3-input-element:disabled{--background-color: rgba(var(--md-scheme-surface-container), .4);--foreground-color: rgba(var(--md-scheme-on-surface), .38)}:host .md3-text-field-container ::ng-deep .md3-input-element:disabled~.md3-text-field-state{display:none}:host .md3-text-field-container ::ng-deep .md3-input-element:disabled~.md3-text-field-outline{border-bottom:.0625em solid rgba(var(--md-scheme-on-surface),.38)}:host .md3-text-field-container ::ng-deep .md3-input-element:disabled~.md3-text-field-label{color:rgba(var(--md-scheme-on-surface),.38)}:host .md3-text-field-container ::ng-deep .md3-input-element:disabled~[md3-icon-element]{color:rgba(var(--md-scheme-on-surface),.38)}:host .md3-text-field-container ::ng-deep .md3-input-element[aria-invalid=true]{caret-color:rgb(var(--md-scheme-error))}:host .md3-text-field-container ::ng-deep .md3-input-element[aria-invalid=true]~.md3-text-field-outline{--outline-color: rgb(var(--md-scheme-error))}:host .md3-text-field-container ::ng-deep .md3-input-element[aria-invalid=true]~.md3-text-field-label{color:rgb(var(--md-scheme-error))}:host .md3-text-field-container ::ng-deep .md3-input-element[aria-invalid=true]~[md3-icon-element=trailing]{color:rgb(var(--md-scheme-error))}:host .md3-text-field-container ::ng-deep .md3-input-element[aria-invalid=true]~[md3-icon-button].md3-filled{--background-color: rgb(var(--md-scheme-error));--foreground-color: rgb(var(--md-scheme-on-error));--state-color: var(--md-scheme-on-error)}:host .md3-text-field-container ::ng-deep .md3-input-element[aria-invalid=true]~[md3-icon-button].md3-tonal{--background-color: rgb(var(--md-scheme-error-container));--foreground-color: rgb(var(--md-scheme-on-error-container));--state-color: var(--md-scheme-on-error-container)}:host .md3-text-field-container ::ng-deep .md3-input-element[aria-invalid=true]~[md3-icon-button].md3-outlined{--background-color: transparent;--foreground-color: rgb(var(--md-scheme-error));--state-color: var(--md-scheme-error);border-color:var(--md-scheme-error)}:host .md3-text-field-container ::ng-deep .md3-input-element[aria-invalid=true]~[md3-icon-button].md3-standard{--background-color: transparent;--foreground-color: rgb(var(--md-scheme-error));--state-color: var(--md-scheme-error)}:host .md3-text-field-container ::ng-deep .md3-input-element[aria-invalid=true]:hover~.md3-text-field-outline{--outline-color: rgb(var(--md-scheme-on-error-container))}:host .md3-text-field-container ::ng-deep .md3-input-element[aria-invalid=true]:hover~.md3-text-field-label{color:rgb(var(--md-scheme-on-error-container))}:host .md3-text-field-container ::ng-deep .md3-input-element[aria-invalid=true]:hover~[md3-icon-element=trailing]{color:rgb(var(--md-scheme-on-error-container))}:host .md3-text-field-container.md3-has-leading-icon ::ng-deep .md3-input-element{padding-inline-start:3.25em}:host .md3-text-field-container.md3-has-leading-icon .md3-text-field-label{inset-inline-start:3.25em}:host .md3-text-field-container.md3-has-trailing-icon ::ng-deep .md3-input-element{padding-inline-end:3.25em}:host .md3-text-field-container.md3-has-trailing-icon .md3-text-field-label{inset-inline-end:3.25em}:host .md3-text-field-container.md3-text-field-outlined{--background-color: transparent;border-radius:var(--md-border-radius-xsmall);padding-top:.5em}:host .md3-text-field-container.md3-text-field-outlined .md3-text-field-outline{border-radius:inherit;border:.0625em solid var(--outline-color)}:host .md3-text-field-container.md3-text-field-outlined ::ng-deep [md3-icon-element=leading],:host .md3-text-field-container.md3-text-field-outlined ::ng-deep [md3-icon-element=trailing],:host .md3-text-field-container.md3-text-field-outlined ::ng-deep [md3-icon-button]{top:1em}:host .md3-text-field-container.md3-text-field-outlined .md3-text-field-label{top:1.5em}:host .md3-text-field-container.md3-text-field-outlined ::ng-deep .md3-input-element{padding-block:1em}:host .md3-text-field-container.md3-text-field-outlined ::ng-deep .md3-input-element:focus~.md3-text-field-outline{padding:0 .5em;border-width:.1875em}:host .md3-text-field-container.md3-text-field-outlined ::ng-deep .md3-input-element:focus~.md3-text-field-outline .md3-label-gap,:host .md3-text-field-container.md3-text-field-outlined ::ng-deep .md3-input-element:not(:placeholder-shown)~.md3-text-field-outline .md3-label-gap{padding:0 .375em;max-width:fit-content}:host .md3-text-field-container.md3-text-field-outlined ::ng-deep .md3-input-element:focus~.md3-text-field-label,:host .md3-text-field-container.md3-text-field-outlined ::ng-deep .md3-input-element:not(:placeholder-shown)~.md3-text-field-label{top:0;inset-inline:1em}:host .md3-text-field-container.md3-text-field-outlined ::ng-deep .md3-input-element:disabled{--foreground-color: rgba(var(--md-scheme-on-surface), .38)}:host .md3-text-field-container.md3-text-field-outlined ::ng-deep .md3-input-element:disabled~.md3-text-field-outline{border:.0625em solid rgba(var(--md-scheme-on-surface),.38)}:host .md3-text-field-container.md3-text-field-outlined ::ng-deep .md3-input-element:disabled~.md3-text-field-label{color:rgba(var(--md-scheme-on-surface),.38)}:host .md3-text-field-container.md3-text-field-outlined ::ng-deep .md3-input-element:disabled~[md3-icon-element]{color:rgba(var(--md-scheme-on-surface),.38)}:host .md3-supporting-text-container{display:flex;flex-direction:row;align-items:flex-start;justify-content:space-between;gap:1.333332em;padding:0 1.333332em;font-size:.75em;font-weight:400;line-height:1.333332em;letter-spacing:.033332em;color:var(--label-color)}:host .md3-supporting-text-container.md3-has-error{color:rgb(var(--md-scheme-error))}:host .md3-supporting-text-container .md3-text-input-counter{margin-inline-start:auto}:host .md3-supporting-text-container>::ng-deep *{margin-top:.333332em}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] });
6021
+ ], queries: [{ propertyName: "input", first: true, predicate: InputElement, descendants: true, isSignal: true }, { propertyName: "iconElements", predicate: IconElement, descendants: true, isSignal: true }, { propertyName: "iconButtons", predicate: IconButton, descendants: true, isSignal: true }, { propertyName: "controlName", first: true, predicate: FormControlName, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "inputContainer", first: true, predicate: ["inputContainer"], descendants: true, isSignal: true }], ngImport: i0, template: "<label #inputContainer class=\"md3-text-field-container\" [ngClass]=\"{\n 'md3-text-field-filled': fieldType() == 'filled',\n 'md3-text-field-outlined': fieldType() == 'outlined',\n}\">\n <ng-content></ng-content>\n <fieldset aria-hidden=\"true\" class=\"md3-text-field-outline\">\n @if (label() && fieldType() == 'outlined') {\n <legend class=\"md3-label-gap\">{{ label() }}</legend>\n }\n </fieldset>\n @if (label()) {\n <span class=\"md3-text-field-label\">{{ label() }}</span>\n }\n <ng-content select=\"[md3-icon-element=leading]\"></ng-content>\n <ng-content select=\"[md3-icon-button], [md3-icon-element=trailing]\"></ng-content>\n @if (fieldType() != 'outlined') {\n <div class=\"md3-text-field-state\"></div>\n }\n</label>\n<div class=\"md3-supporting-text-container\" [class.md3-has-error]=\"inputError()\">\n <ng-content select=\"[md3-supporting-text]\"></ng-content>\n @if (inputCounter()) {\n <div class=\"md3-text-input-counter\">{{ valueLength() + (maxLength() ? \"/\" + maxLength() : \"\") }}</div>\n }\n</div>\n", styles: [":host{--background-color: rgb(var(--md-scheme-surface-container-highest));--foreground-color: rgb(var(--md-scheme-on-surface));--label-color: rgb(var(--md-scheme-on-surface-variant));--caret-color: rgb(var(--md-scheme-primary));--outline-color: rgb(var(--md-scheme-on-surface-variant));font-size:1rem;width:100%}:host .md3-text-field-container{display:block;position:relative;border-radius:var(--md-border-radius-xsmall) var(--md-border-radius-xsmall) 0 0;overflow:hidden}:host .md3-text-field-container>::ng-deep [md3-icon-element=leading]{position:absolute;font-size:1.5em!important;height:1em!important;width:1em!important;top:.666667em;inset-inline-start:.5em;pointer-events:none;color:var(--label-color)}:host .md3-text-field-container>::ng-deep [md3-icon-element=trailing]{position:absolute;font-size:1.5em!important;height:1em!important;width:1em!important;top:.666667em;inset-inline-end:.5em;pointer-events:none;color:var(--label-color)}:host .md3-text-field-container>::ng-deep [md3-icon-button]{position:absolute;font-size:1em!important;top:.5em;inset-inline-end:.5em}:host .md3-text-field-container .md3-text-field-outline{position:absolute;inset:0;pointer-events:none;padding:0 .625em;margin:.0625em 0 0;border:none;border-bottom:.0625em solid var(--outline-color);transition:all var(--md-motion-standard-fast-spatial-duration) var(--md-motion-standard-fast-spatial-easing),border-width none}:host .md3-text-field-container .md3-text-field-outline .md3-label-gap{padding:0;max-width:0em;font-size:.75em;font-weight:400;line-height:1.333332em;letter-spacing:.041667em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;opacity:0;visibility:hidden;transition:all var(--md-motion-standard-fast-spatial-duration) var(--md-motion-standard-fast-spatial-easing);interpolate-size:allow-keywords}:host .md3-text-field-container .md3-text-field-label{position:absolute;top:1em;inset-inline-start:1em;inset-inline-end:1em;padding:0;font-size:1em;font-weight:400;line-height:1.5em;letter-spacing:.03125em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;pointer-events:none;color:var(--label-color);transform-origin:top left;transform:scale(1);transition:all var(--md-motion-standard-fast-spatial-duration) var(--md-motion-standard-fast-spatial-easing)}:host .md3-text-field-container .md3-text-field-label:dir(rtl){transform-origin:top right}:host .md3-text-field-container .md3-text-field-state{position:absolute;inset:0;pointer-events:none;border-radius:inherit;background-color:rgba(var(--md-scheme-on-surface));opacity:0;transition:all var(--md-motion-standard-fast-spatial-duration) var(--md-motion-standard-fast-spatial-easing)}:host .md3-text-field-container ::ng-deep .md3-input-element{display:block;width:100%;border:none;outline:none;padding:1.5em 1em .5em;font-size:1em;font-weight:400;line-height:1.5em;letter-spacing:.03125em;resize:none;background-color:var(--background-color);color:var(--foreground-color);caret-color:var(--caret-color)}:host .md3-text-field-container ::ng-deep .md3-input-element:not(:placeholder-shown)~.md3-text-field-label{top:.4375em;transform:scale(.75)}:host .md3-text-field-container ::ng-deep .md3-input-element:hover~.md3-text-field-state{opacity:.08}:host .md3-text-field-container ::ng-deep .md3-input-element:hover~.md3-text-field-outline{--outline-color: rgb(var(--md-scheme-on-surface))}:host .md3-text-field-container ::ng-deep .md3-input-element:focus~.md3-text-field-outline{--outline-color: rgb(var(--md-scheme-primary));border-bottom-width:.1875em}:host .md3-text-field-container ::ng-deep .md3-input-element:focus:not(:read-only)~.md3-text-field-label{color:var(--caret-color);top:.4375em;transform:scale(.75)}:host .md3-text-field-container ::ng-deep .md3-input-element:disabled{--background-color: rgba(var(--md-scheme-surface-container), .4);--foreground-color: rgba(var(--md-scheme-on-surface), .38)}:host .md3-text-field-container ::ng-deep .md3-input-element:disabled~.md3-text-field-state{display:none}:host .md3-text-field-container ::ng-deep .md3-input-element:disabled~.md3-text-field-outline{border-bottom:.0625em solid rgba(var(--md-scheme-on-surface),.38)}:host .md3-text-field-container ::ng-deep .md3-input-element:disabled~.md3-text-field-label{color:rgba(var(--md-scheme-on-surface),.38)}:host .md3-text-field-container ::ng-deep .md3-input-element:disabled~[md3-icon-element]{color:rgba(var(--md-scheme-on-surface),.38)}:host .md3-text-field-container ::ng-deep .md3-input-element[aria-invalid=true]{caret-color:rgb(var(--md-scheme-error))}:host .md3-text-field-container ::ng-deep .md3-input-element[aria-invalid=true]~.md3-text-field-outline{--outline-color: rgb(var(--md-scheme-error))}:host .md3-text-field-container ::ng-deep .md3-input-element[aria-invalid=true]~.md3-text-field-label{color:rgb(var(--md-scheme-error))}:host .md3-text-field-container ::ng-deep .md3-input-element[aria-invalid=true]~[md3-icon-element=trailing]{color:rgb(var(--md-scheme-error))}:host .md3-text-field-container ::ng-deep .md3-input-element[aria-invalid=true]~[md3-icon-button].md3-filled{--background-color: rgb(var(--md-scheme-error));--foreground-color: rgb(var(--md-scheme-on-error));--state-color: var(--md-scheme-on-error)}:host .md3-text-field-container ::ng-deep .md3-input-element[aria-invalid=true]~[md3-icon-button].md3-tonal{--background-color: rgb(var(--md-scheme-error-container));--foreground-color: rgb(var(--md-scheme-on-error-container));--state-color: var(--md-scheme-on-error-container)}:host .md3-text-field-container ::ng-deep .md3-input-element[aria-invalid=true]~[md3-icon-button].md3-outlined{--background-color: transparent;--foreground-color: rgb(var(--md-scheme-error));--state-color: var(--md-scheme-error);border-color:var(--md-scheme-error)}:host .md3-text-field-container ::ng-deep .md3-input-element[aria-invalid=true]~[md3-icon-button].md3-standard{--background-color: transparent;--foreground-color: rgb(var(--md-scheme-error));--state-color: var(--md-scheme-error)}:host .md3-text-field-container ::ng-deep .md3-input-element[aria-invalid=true]:hover~.md3-text-field-outline{--outline-color: rgb(var(--md-scheme-on-error-container))}:host .md3-text-field-container ::ng-deep .md3-input-element[aria-invalid=true]:hover~.md3-text-field-label{color:rgb(var(--md-scheme-on-error-container))}:host .md3-text-field-container ::ng-deep .md3-input-element[aria-invalid=true]:hover~[md3-icon-element=trailing]{color:rgb(var(--md-scheme-on-error-container))}:host .md3-text-field-container.md3-has-leading-icon ::ng-deep .md3-input-element{padding-inline-start:3.25em}:host .md3-text-field-container.md3-has-leading-icon .md3-text-field-label{inset-inline-start:3.25em}:host .md3-text-field-container.md3-has-trailing-icon ::ng-deep .md3-input-element{padding-inline-end:3.25em}:host .md3-text-field-container.md3-has-trailing-icon .md3-text-field-label{inset-inline-end:3.25em}:host .md3-text-field-container.md3-text-field-outlined{--background-color: transparent;border-radius:var(--md-border-radius-xsmall);padding-top:.5em}:host .md3-text-field-container.md3-text-field-outlined .md3-text-field-outline{border-radius:inherit;border:.0625em solid var(--outline-color)}:host .md3-text-field-container.md3-text-field-outlined>::ng-deep [md3-icon-element=leading],:host .md3-text-field-container.md3-text-field-outlined>::ng-deep [md3-icon-element=trailing],:host .md3-text-field-container.md3-text-field-outlined>::ng-deep [md3-icon-button]{top:1em}:host .md3-text-field-container.md3-text-field-outlined .md3-text-field-label{top:1.5em}:host .md3-text-field-container.md3-text-field-outlined ::ng-deep .md3-input-element{padding-block:1em}:host .md3-text-field-container.md3-text-field-outlined ::ng-deep .md3-input-element:focus~.md3-text-field-outline{padding:0 .5em;border-width:.1875em}:host .md3-text-field-container.md3-text-field-outlined ::ng-deep .md3-input-element:not(:read-only):focus~.md3-text-field-outline .md3-label-gap,:host .md3-text-field-container.md3-text-field-outlined ::ng-deep .md3-input-element:not(:placeholder-shown)~.md3-text-field-outline .md3-label-gap{padding:0 .375em;max-width:fit-content}:host .md3-text-field-container.md3-text-field-outlined ::ng-deep .md3-input-element:not(:read-only):focus~.md3-text-field-label,:host .md3-text-field-container.md3-text-field-outlined ::ng-deep .md3-input-element:not(:placeholder-shown)~.md3-text-field-label{top:0;inset-inline:1em}:host .md3-text-field-container.md3-text-field-outlined ::ng-deep .md3-input-element:disabled{--foreground-color: rgba(var(--md-scheme-on-surface), .38)}:host .md3-text-field-container.md3-text-field-outlined ::ng-deep .md3-input-element:disabled~.md3-text-field-outline{border:.0625em solid rgba(var(--md-scheme-on-surface),.38)}:host .md3-text-field-container.md3-text-field-outlined ::ng-deep .md3-input-element:disabled~.md3-text-field-label{color:rgba(var(--md-scheme-on-surface),.38)}:host .md3-text-field-container.md3-text-field-outlined ::ng-deep .md3-input-element:disabled~[md3-icon-element]{color:rgba(var(--md-scheme-on-surface),.38)}:host .md3-supporting-text-container{display:flex;flex-direction:row;align-items:flex-start;justify-content:space-between;gap:1.333332em;padding:0 1.333332em;font-size:.75em;font-weight:400;line-height:1.333332em;letter-spacing:.033332em;color:var(--label-color)}:host .md3-supporting-text-container.md3-has-error{color:rgb(var(--md-scheme-error))}:host .md3-supporting-text-container .md3-text-input-counter{margin-inline-start:auto}:host .md3-supporting-text-container>::ng-deep *{margin-top:.333332em}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] });
6023
6022
  }
6024
6023
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: TextField, decorators: [{
6025
6024
  type: Component,
@@ -6030,7 +6029,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
6030
6029
  provide: MD3_BUTTON_CONTEXT,
6031
6030
  useExisting: TextField,
6032
6031
  },
6033
- ], template: "<label #inputContainer class=\"md3-text-field-container\" [ngClass]=\"{\n 'md3-text-field-filled': fieldType() == 'filled',\n 'md3-text-field-outlined': fieldType() == 'outlined',\n}\">\n <ng-content></ng-content>\n <fieldset aria-hidden=\"true\" class=\"md3-text-field-outline\">\n @if (label() && fieldType() == 'outlined') {\n <legend class=\"md3-label-gap\">{{ label() }}</legend>\n }\n </fieldset>\n @if (label()) {\n <label class=\"md3-text-field-label\">{{ label() }}</label>\n }\n <ng-content select=\"[md3-icon-element=leading]\"></ng-content>\n <ng-content select=\"[md3-icon-button], [md3-icon-element=trailing]\"></ng-content>\n @if (fieldType() != 'outlined') {\n <div class=\"md3-text-field-state\"></div>\n }\n</label>\n<div class=\"md3-supporting-text-container\" [class.md3-has-error]=\"inputError()\">\n <ng-content select=\"[md3-supporting-text]\"></ng-content>\n @if (inputCounter()) {\n <div class=\"md3-text-input-counter\">{{ valueLength() + (maxLength() ? \"/\" + maxLength() : \"\") }}</div>\n }\n</div>\n", styles: [":host{--background-color: rgb(var(--md-scheme-surface-container-highest));--foreground-color: rgb(var(--md-scheme-on-surface));--label-color: rgb(var(--md-scheme-on-surface-variant));--caret-color: rgb(var(--md-scheme-primary));--outline-color: rgb(var(--md-scheme-on-surface-variant));font-size:1rem;width:100%}:host .md3-text-field-container{display:block;position:relative;border-radius:var(--md-border-radius-xsmall) var(--md-border-radius-xsmall) 0 0;overflow:hidden}:host .md3-text-field-container ::ng-deep [md3-icon-element=leading]{position:absolute;font-size:1.5em!important;height:1em!important;width:1em!important;top:.666667em;inset-inline-start:.5em;pointer-events:none;color:var(--label-color)}:host .md3-text-field-container ::ng-deep [md3-icon-element=trailing]{position:absolute;font-size:1.5em!important;height:1em!important;width:1em!important;top:.666667em;inset-inline-end:.5em;pointer-events:none;color:var(--label-color)}:host .md3-text-field-container ::ng-deep [md3-icon-button]{position:absolute;font-size:1em!important;top:.5em;inset-inline-end:.5em}:host .md3-text-field-container .md3-text-field-outline{position:absolute;inset:0;pointer-events:none;padding:0 .625em;margin:.0625em 0 0;border:none;border-bottom:.0625em solid var(--outline-color);transition:all var(--md-motion-standard-fast-spatial-duration) var(--md-motion-standard-fast-spatial-easing),border-width none}:host .md3-text-field-container .md3-text-field-outline .md3-label-gap{padding:0;max-width:0em;font-size:.75em;font-weight:400;line-height:1.333332em;letter-spacing:.041667em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;opacity:0;visibility:hidden;transition:all var(--md-motion-standard-fast-spatial-duration) var(--md-motion-standard-fast-spatial-easing);interpolate-size:allow-keywords}:host .md3-text-field-container .md3-text-field-label{position:absolute;top:1em;inset-inline-start:1em;inset-inline-end:1em;padding:0;font-size:1em;font-weight:400;line-height:1.5em;letter-spacing:.03125em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;pointer-events:none;color:var(--label-color);transform-origin:top left;transform:scale(1);transition:all var(--md-motion-standard-fast-spatial-duration) var(--md-motion-standard-fast-spatial-easing)}:host .md3-text-field-container .md3-text-field-label:dir(rtl){transform-origin:top right}:host .md3-text-field-container .md3-text-field-state{position:absolute;inset:0;pointer-events:none;border-radius:inherit;background-color:rgba(var(--md-scheme-on-surface));opacity:0;transition:all var(--md-motion-standard-fast-spatial-duration) var(--md-motion-standard-fast-spatial-easing)}:host .md3-text-field-container ::ng-deep .md3-input-element{display:block;width:100%;border:none;outline:none;padding:1.5em 1em .5em;font-size:1em;font-weight:400;line-height:1.5em;letter-spacing:.03125em;resize:none;background-color:var(--background-color);color:var(--foreground-color);caret-color:var(--caret-color)}:host .md3-text-field-container ::ng-deep .md3-input-element:not(:placeholder-shown)~.md3-text-field-label{top:.4375em;transform:scale(.75)}:host .md3-text-field-container ::ng-deep .md3-input-element:hover~.md3-text-field-state{opacity:.08}:host .md3-text-field-container ::ng-deep .md3-input-element:hover~.md3-text-field-outline{--outline-color: rgb(var(--md-scheme-on-surface))}:host .md3-text-field-container ::ng-deep .md3-input-element:focus~.md3-text-field-outline{--outline-color: rgb(var(--md-scheme-primary));border-bottom-width:.1875em}:host .md3-text-field-container ::ng-deep .md3-input-element:focus~.md3-text-field-label{color:var(--caret-color);top:.4375em;transform:scale(.75)}:host .md3-text-field-container ::ng-deep .md3-input-element:disabled{--background-color: rgba(var(--md-scheme-surface-container), .4);--foreground-color: rgba(var(--md-scheme-on-surface), .38)}:host .md3-text-field-container ::ng-deep .md3-input-element:disabled~.md3-text-field-state{display:none}:host .md3-text-field-container ::ng-deep .md3-input-element:disabled~.md3-text-field-outline{border-bottom:.0625em solid rgba(var(--md-scheme-on-surface),.38)}:host .md3-text-field-container ::ng-deep .md3-input-element:disabled~.md3-text-field-label{color:rgba(var(--md-scheme-on-surface),.38)}:host .md3-text-field-container ::ng-deep .md3-input-element:disabled~[md3-icon-element]{color:rgba(var(--md-scheme-on-surface),.38)}:host .md3-text-field-container ::ng-deep .md3-input-element[aria-invalid=true]{caret-color:rgb(var(--md-scheme-error))}:host .md3-text-field-container ::ng-deep .md3-input-element[aria-invalid=true]~.md3-text-field-outline{--outline-color: rgb(var(--md-scheme-error))}:host .md3-text-field-container ::ng-deep .md3-input-element[aria-invalid=true]~.md3-text-field-label{color:rgb(var(--md-scheme-error))}:host .md3-text-field-container ::ng-deep .md3-input-element[aria-invalid=true]~[md3-icon-element=trailing]{color:rgb(var(--md-scheme-error))}:host .md3-text-field-container ::ng-deep .md3-input-element[aria-invalid=true]~[md3-icon-button].md3-filled{--background-color: rgb(var(--md-scheme-error));--foreground-color: rgb(var(--md-scheme-on-error));--state-color: var(--md-scheme-on-error)}:host .md3-text-field-container ::ng-deep .md3-input-element[aria-invalid=true]~[md3-icon-button].md3-tonal{--background-color: rgb(var(--md-scheme-error-container));--foreground-color: rgb(var(--md-scheme-on-error-container));--state-color: var(--md-scheme-on-error-container)}:host .md3-text-field-container ::ng-deep .md3-input-element[aria-invalid=true]~[md3-icon-button].md3-outlined{--background-color: transparent;--foreground-color: rgb(var(--md-scheme-error));--state-color: var(--md-scheme-error);border-color:var(--md-scheme-error)}:host .md3-text-field-container ::ng-deep .md3-input-element[aria-invalid=true]~[md3-icon-button].md3-standard{--background-color: transparent;--foreground-color: rgb(var(--md-scheme-error));--state-color: var(--md-scheme-error)}:host .md3-text-field-container ::ng-deep .md3-input-element[aria-invalid=true]:hover~.md3-text-field-outline{--outline-color: rgb(var(--md-scheme-on-error-container))}:host .md3-text-field-container ::ng-deep .md3-input-element[aria-invalid=true]:hover~.md3-text-field-label{color:rgb(var(--md-scheme-on-error-container))}:host .md3-text-field-container ::ng-deep .md3-input-element[aria-invalid=true]:hover~[md3-icon-element=trailing]{color:rgb(var(--md-scheme-on-error-container))}:host .md3-text-field-container.md3-has-leading-icon ::ng-deep .md3-input-element{padding-inline-start:3.25em}:host .md3-text-field-container.md3-has-leading-icon .md3-text-field-label{inset-inline-start:3.25em}:host .md3-text-field-container.md3-has-trailing-icon ::ng-deep .md3-input-element{padding-inline-end:3.25em}:host .md3-text-field-container.md3-has-trailing-icon .md3-text-field-label{inset-inline-end:3.25em}:host .md3-text-field-container.md3-text-field-outlined{--background-color: transparent;border-radius:var(--md-border-radius-xsmall);padding-top:.5em}:host .md3-text-field-container.md3-text-field-outlined .md3-text-field-outline{border-radius:inherit;border:.0625em solid var(--outline-color)}:host .md3-text-field-container.md3-text-field-outlined ::ng-deep [md3-icon-element=leading],:host .md3-text-field-container.md3-text-field-outlined ::ng-deep [md3-icon-element=trailing],:host .md3-text-field-container.md3-text-field-outlined ::ng-deep [md3-icon-button]{top:1em}:host .md3-text-field-container.md3-text-field-outlined .md3-text-field-label{top:1.5em}:host .md3-text-field-container.md3-text-field-outlined ::ng-deep .md3-input-element{padding-block:1em}:host .md3-text-field-container.md3-text-field-outlined ::ng-deep .md3-input-element:focus~.md3-text-field-outline{padding:0 .5em;border-width:.1875em}:host .md3-text-field-container.md3-text-field-outlined ::ng-deep .md3-input-element:focus~.md3-text-field-outline .md3-label-gap,:host .md3-text-field-container.md3-text-field-outlined ::ng-deep .md3-input-element:not(:placeholder-shown)~.md3-text-field-outline .md3-label-gap{padding:0 .375em;max-width:fit-content}:host .md3-text-field-container.md3-text-field-outlined ::ng-deep .md3-input-element:focus~.md3-text-field-label,:host .md3-text-field-container.md3-text-field-outlined ::ng-deep .md3-input-element:not(:placeholder-shown)~.md3-text-field-label{top:0;inset-inline:1em}:host .md3-text-field-container.md3-text-field-outlined ::ng-deep .md3-input-element:disabled{--foreground-color: rgba(var(--md-scheme-on-surface), .38)}:host .md3-text-field-container.md3-text-field-outlined ::ng-deep .md3-input-element:disabled~.md3-text-field-outline{border:.0625em solid rgba(var(--md-scheme-on-surface),.38)}:host .md3-text-field-container.md3-text-field-outlined ::ng-deep .md3-input-element:disabled~.md3-text-field-label{color:rgba(var(--md-scheme-on-surface),.38)}:host .md3-text-field-container.md3-text-field-outlined ::ng-deep .md3-input-element:disabled~[md3-icon-element]{color:rgba(var(--md-scheme-on-surface),.38)}:host .md3-supporting-text-container{display:flex;flex-direction:row;align-items:flex-start;justify-content:space-between;gap:1.333332em;padding:0 1.333332em;font-size:.75em;font-weight:400;line-height:1.333332em;letter-spacing:.033332em;color:var(--label-color)}:host .md3-supporting-text-container.md3-has-error{color:rgb(var(--md-scheme-error))}:host .md3-supporting-text-container .md3-text-input-counter{margin-inline-start:auto}:host .md3-supporting-text-container>::ng-deep *{margin-top:.333332em}\n"] }]
6032
+ ], template: "<label #inputContainer class=\"md3-text-field-container\" [ngClass]=\"{\n 'md3-text-field-filled': fieldType() == 'filled',\n 'md3-text-field-outlined': fieldType() == 'outlined',\n}\">\n <ng-content></ng-content>\n <fieldset aria-hidden=\"true\" class=\"md3-text-field-outline\">\n @if (label() && fieldType() == 'outlined') {\n <legend class=\"md3-label-gap\">{{ label() }}</legend>\n }\n </fieldset>\n @if (label()) {\n <span class=\"md3-text-field-label\">{{ label() }}</span>\n }\n <ng-content select=\"[md3-icon-element=leading]\"></ng-content>\n <ng-content select=\"[md3-icon-button], [md3-icon-element=trailing]\"></ng-content>\n @if (fieldType() != 'outlined') {\n <div class=\"md3-text-field-state\"></div>\n }\n</label>\n<div class=\"md3-supporting-text-container\" [class.md3-has-error]=\"inputError()\">\n <ng-content select=\"[md3-supporting-text]\"></ng-content>\n @if (inputCounter()) {\n <div class=\"md3-text-input-counter\">{{ valueLength() + (maxLength() ? \"/\" + maxLength() : \"\") }}</div>\n }\n</div>\n", styles: [":host{--background-color: rgb(var(--md-scheme-surface-container-highest));--foreground-color: rgb(var(--md-scheme-on-surface));--label-color: rgb(var(--md-scheme-on-surface-variant));--caret-color: rgb(var(--md-scheme-primary));--outline-color: rgb(var(--md-scheme-on-surface-variant));font-size:1rem;width:100%}:host .md3-text-field-container{display:block;position:relative;border-radius:var(--md-border-radius-xsmall) var(--md-border-radius-xsmall) 0 0;overflow:hidden}:host .md3-text-field-container>::ng-deep [md3-icon-element=leading]{position:absolute;font-size:1.5em!important;height:1em!important;width:1em!important;top:.666667em;inset-inline-start:.5em;pointer-events:none;color:var(--label-color)}:host .md3-text-field-container>::ng-deep [md3-icon-element=trailing]{position:absolute;font-size:1.5em!important;height:1em!important;width:1em!important;top:.666667em;inset-inline-end:.5em;pointer-events:none;color:var(--label-color)}:host .md3-text-field-container>::ng-deep [md3-icon-button]{position:absolute;font-size:1em!important;top:.5em;inset-inline-end:.5em}:host .md3-text-field-container .md3-text-field-outline{position:absolute;inset:0;pointer-events:none;padding:0 .625em;margin:.0625em 0 0;border:none;border-bottom:.0625em solid var(--outline-color);transition:all var(--md-motion-standard-fast-spatial-duration) var(--md-motion-standard-fast-spatial-easing),border-width none}:host .md3-text-field-container .md3-text-field-outline .md3-label-gap{padding:0;max-width:0em;font-size:.75em;font-weight:400;line-height:1.333332em;letter-spacing:.041667em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;opacity:0;visibility:hidden;transition:all var(--md-motion-standard-fast-spatial-duration) var(--md-motion-standard-fast-spatial-easing);interpolate-size:allow-keywords}:host .md3-text-field-container .md3-text-field-label{position:absolute;top:1em;inset-inline-start:1em;inset-inline-end:1em;padding:0;font-size:1em;font-weight:400;line-height:1.5em;letter-spacing:.03125em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;pointer-events:none;color:var(--label-color);transform-origin:top left;transform:scale(1);transition:all var(--md-motion-standard-fast-spatial-duration) var(--md-motion-standard-fast-spatial-easing)}:host .md3-text-field-container .md3-text-field-label:dir(rtl){transform-origin:top right}:host .md3-text-field-container .md3-text-field-state{position:absolute;inset:0;pointer-events:none;border-radius:inherit;background-color:rgba(var(--md-scheme-on-surface));opacity:0;transition:all var(--md-motion-standard-fast-spatial-duration) var(--md-motion-standard-fast-spatial-easing)}:host .md3-text-field-container ::ng-deep .md3-input-element{display:block;width:100%;border:none;outline:none;padding:1.5em 1em .5em;font-size:1em;font-weight:400;line-height:1.5em;letter-spacing:.03125em;resize:none;background-color:var(--background-color);color:var(--foreground-color);caret-color:var(--caret-color)}:host .md3-text-field-container ::ng-deep .md3-input-element:not(:placeholder-shown)~.md3-text-field-label{top:.4375em;transform:scale(.75)}:host .md3-text-field-container ::ng-deep .md3-input-element:hover~.md3-text-field-state{opacity:.08}:host .md3-text-field-container ::ng-deep .md3-input-element:hover~.md3-text-field-outline{--outline-color: rgb(var(--md-scheme-on-surface))}:host .md3-text-field-container ::ng-deep .md3-input-element:focus~.md3-text-field-outline{--outline-color: rgb(var(--md-scheme-primary));border-bottom-width:.1875em}:host .md3-text-field-container ::ng-deep .md3-input-element:focus:not(:read-only)~.md3-text-field-label{color:var(--caret-color);top:.4375em;transform:scale(.75)}:host .md3-text-field-container ::ng-deep .md3-input-element:disabled{--background-color: rgba(var(--md-scheme-surface-container), .4);--foreground-color: rgba(var(--md-scheme-on-surface), .38)}:host .md3-text-field-container ::ng-deep .md3-input-element:disabled~.md3-text-field-state{display:none}:host .md3-text-field-container ::ng-deep .md3-input-element:disabled~.md3-text-field-outline{border-bottom:.0625em solid rgba(var(--md-scheme-on-surface),.38)}:host .md3-text-field-container ::ng-deep .md3-input-element:disabled~.md3-text-field-label{color:rgba(var(--md-scheme-on-surface),.38)}:host .md3-text-field-container ::ng-deep .md3-input-element:disabled~[md3-icon-element]{color:rgba(var(--md-scheme-on-surface),.38)}:host .md3-text-field-container ::ng-deep .md3-input-element[aria-invalid=true]{caret-color:rgb(var(--md-scheme-error))}:host .md3-text-field-container ::ng-deep .md3-input-element[aria-invalid=true]~.md3-text-field-outline{--outline-color: rgb(var(--md-scheme-error))}:host .md3-text-field-container ::ng-deep .md3-input-element[aria-invalid=true]~.md3-text-field-label{color:rgb(var(--md-scheme-error))}:host .md3-text-field-container ::ng-deep .md3-input-element[aria-invalid=true]~[md3-icon-element=trailing]{color:rgb(var(--md-scheme-error))}:host .md3-text-field-container ::ng-deep .md3-input-element[aria-invalid=true]~[md3-icon-button].md3-filled{--background-color: rgb(var(--md-scheme-error));--foreground-color: rgb(var(--md-scheme-on-error));--state-color: var(--md-scheme-on-error)}:host .md3-text-field-container ::ng-deep .md3-input-element[aria-invalid=true]~[md3-icon-button].md3-tonal{--background-color: rgb(var(--md-scheme-error-container));--foreground-color: rgb(var(--md-scheme-on-error-container));--state-color: var(--md-scheme-on-error-container)}:host .md3-text-field-container ::ng-deep .md3-input-element[aria-invalid=true]~[md3-icon-button].md3-outlined{--background-color: transparent;--foreground-color: rgb(var(--md-scheme-error));--state-color: var(--md-scheme-error);border-color:var(--md-scheme-error)}:host .md3-text-field-container ::ng-deep .md3-input-element[aria-invalid=true]~[md3-icon-button].md3-standard{--background-color: transparent;--foreground-color: rgb(var(--md-scheme-error));--state-color: var(--md-scheme-error)}:host .md3-text-field-container ::ng-deep .md3-input-element[aria-invalid=true]:hover~.md3-text-field-outline{--outline-color: rgb(var(--md-scheme-on-error-container))}:host .md3-text-field-container ::ng-deep .md3-input-element[aria-invalid=true]:hover~.md3-text-field-label{color:rgb(var(--md-scheme-on-error-container))}:host .md3-text-field-container ::ng-deep .md3-input-element[aria-invalid=true]:hover~[md3-icon-element=trailing]{color:rgb(var(--md-scheme-on-error-container))}:host .md3-text-field-container.md3-has-leading-icon ::ng-deep .md3-input-element{padding-inline-start:3.25em}:host .md3-text-field-container.md3-has-leading-icon .md3-text-field-label{inset-inline-start:3.25em}:host .md3-text-field-container.md3-has-trailing-icon ::ng-deep .md3-input-element{padding-inline-end:3.25em}:host .md3-text-field-container.md3-has-trailing-icon .md3-text-field-label{inset-inline-end:3.25em}:host .md3-text-field-container.md3-text-field-outlined{--background-color: transparent;border-radius:var(--md-border-radius-xsmall);padding-top:.5em}:host .md3-text-field-container.md3-text-field-outlined .md3-text-field-outline{border-radius:inherit;border:.0625em solid var(--outline-color)}:host .md3-text-field-container.md3-text-field-outlined>::ng-deep [md3-icon-element=leading],:host .md3-text-field-container.md3-text-field-outlined>::ng-deep [md3-icon-element=trailing],:host .md3-text-field-container.md3-text-field-outlined>::ng-deep [md3-icon-button]{top:1em}:host .md3-text-field-container.md3-text-field-outlined .md3-text-field-label{top:1.5em}:host .md3-text-field-container.md3-text-field-outlined ::ng-deep .md3-input-element{padding-block:1em}:host .md3-text-field-container.md3-text-field-outlined ::ng-deep .md3-input-element:focus~.md3-text-field-outline{padding:0 .5em;border-width:.1875em}:host .md3-text-field-container.md3-text-field-outlined ::ng-deep .md3-input-element:not(:read-only):focus~.md3-text-field-outline .md3-label-gap,:host .md3-text-field-container.md3-text-field-outlined ::ng-deep .md3-input-element:not(:placeholder-shown)~.md3-text-field-outline .md3-label-gap{padding:0 .375em;max-width:fit-content}:host .md3-text-field-container.md3-text-field-outlined ::ng-deep .md3-input-element:not(:read-only):focus~.md3-text-field-label,:host .md3-text-field-container.md3-text-field-outlined ::ng-deep .md3-input-element:not(:placeholder-shown)~.md3-text-field-label{top:0;inset-inline:1em}:host .md3-text-field-container.md3-text-field-outlined ::ng-deep .md3-input-element:disabled{--foreground-color: rgba(var(--md-scheme-on-surface), .38)}:host .md3-text-field-container.md3-text-field-outlined ::ng-deep .md3-input-element:disabled~.md3-text-field-outline{border:.0625em solid rgba(var(--md-scheme-on-surface),.38)}:host .md3-text-field-container.md3-text-field-outlined ::ng-deep .md3-input-element:disabled~.md3-text-field-label{color:rgba(var(--md-scheme-on-surface),.38)}:host .md3-text-field-container.md3-text-field-outlined ::ng-deep .md3-input-element:disabled~[md3-icon-element]{color:rgba(var(--md-scheme-on-surface),.38)}:host .md3-supporting-text-container{display:flex;flex-direction:row;align-items:flex-start;justify-content:space-between;gap:1.333332em;padding:0 1.333332em;font-size:.75em;font-weight:400;line-height:1.333332em;letter-spacing:.033332em;color:var(--label-color)}:host .md3-supporting-text-container.md3-has-error{color:rgb(var(--md-scheme-error))}:host .md3-supporting-text-container .md3-text-input-counter{margin-inline-start:auto}:host .md3-supporting-text-container>::ng-deep *{margin-top:.333332em}\n"] }]
6034
6033
  }], ctorParameters: () => [], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], fieldType: [{ type: i0.Input, args: [{ isSignal: true, alias: "field-type", required: false }] }], inputCounter: [{ type: i0.Input, args: [{ isSignal: true, alias: "input-counter", required: false }] }], control: [{ type: i0.Input, args: [{ isSignal: true, alias: "control", required: false }] }], inputContainer: [{ type: i0.ViewChild, args: ['inputContainer', { isSignal: true }] }], input: [{ type: i0.ContentChild, args: [i0.forwardRef(() => InputElement), { isSignal: true }] }], iconElements: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => IconElement), { ...{ descendants: true }, isSignal: true }] }], iconButtons: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => IconButton), { ...{ descendants: true }, isSignal: true }] }], controlName: [{ type: i0.ContentChild, args: [i0.forwardRef(() => FormControlName), { isSignal: true }] }] } });
6035
6034
 
6036
6035
  class SupportingText {
@@ -6047,1207 +6046,1437 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
6047
6046
  }]
6048
6047
  }] });
6049
6048
 
6050
- /**
6051
- * Material 3 dialog shell. It extends the CDK dialog container, so focus
6052
- * trapping, focus restoration and the ARIA attributes are handled by the CDK
6053
- * while this component only owns the MD3 surface and its animation.
6054
- */
6055
- class Dialog extends CdkDialogContainer {
6056
- surface = viewChild('surface', /* @ts-ignore */
6057
- ...(ngDevMode ? [{ debugName: "surface" }] : /* istanbul ignore next */ []));
6058
- isActive = signal(false, /* @ts-ignore */
6059
- ...(ngDevMode ? [{ debugName: "isActive" }] : /* istanbul ignore next */ []));
6060
- config = inject(DIALOG_CONFIG, { optional: true }) ?? {};
6061
- get surfaceElement() {
6062
- return this.surface()?.nativeElement ?? null;
6063
- }
6064
- /**
6065
- * Started by DialogService, either right after the dialog is created or
6066
- * once the dialog it replaces has had its head start. The animation runs on
6067
- * the next frame so the surface has a real from/to state.
6068
- */
6069
- startEnterAnimation() {
6070
- requestAnimationFrame(() => {
6071
- this.isActive.set(true);
6072
- });
6073
- }
6074
- setActive(value) {
6075
- this.isActive.set(value);
6076
- }
6077
- recaptureFocus() {
6078
- this._recaptureFocus();
6079
- }
6080
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Dialog, deps: null, target: i0.ɵɵFactoryTarget.Component });
6081
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.1.0", type: Dialog, isStandalone: true, selector: "md3-dialog", viewQueries: [{ propertyName: "surface", first: true, predicate: ["surface"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<!-- The role and aria attributes live on the host element, applied by the CDK dialog container. -->\n<div\n #surface\n class=\"md3-dialog-container\"\n [class.md3-active]=\"isActive()\"\n [class.md-scheme-dark]=\"config.scheme == 'dark'\"\n [class.md-scheme-light]=\"config.scheme == 'light'\"\n [attr.dir]=\"config.direction ?? null\">\n <ng-template cdkPortalOutlet></ng-template>\n</div>\n", styles: ["::ng-deep .cdk-overlay-container{position:fixed;z-index:1000;inset:0;pointer-events:none;font-size:1rem}::ng-deep .cdk-overlay-container .md3-dialog-scrim{position:absolute;inset:0;pointer-events:auto;background-color:rgb(var(--md-scheme-scrim));transition:all var(--md-motion-expressive-slow-spatial-duration) var(--md-motion-expressive-slow-spatial-easing);opacity:.32}::ng-deep .cdk-overlay-container .md3-dialog-scrim.md3-dialog-opening{opacity:0}::ng-deep .cdk-overlay-container .md3-dialog-scrim.md3-dialog-closing{opacity:0}::ng-deep .cdk-overlay-container .md3-dialog-scrim.md3-dialog-hidden{opacity:0;pointer-events:none}::ng-deep .cdk-overlay-container .md3-dialog-scrim.md3-dialog-scrim-kept{opacity:.32;transition:none}::ng-deep .cdk-overlay-container .md3-dialog-scrim.md3-dialog-scrim-instant{transition:none}::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper{position:absolute;display:flex;inset:0;pointer-events:none;padding:3.5rem 1.5rem;overflow:hidden}::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper .md3-dialog-panel{position:absolute;pointer-events:auto;max-width:35em}::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper .md3-dialog-panel.md3-dialog-closing,::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper .md3-dialog-panel.md3-dialog-hidden{pointer-events:none}:host{display:block;max-width:100%;min-width:17.5em;outline:0}:host .md3-dialog-container{display:block;width:100%;max-height:inherit;overflow:auto;color:rgb(var(--md-scheme-on-surface));background-color:rgb(var(--md-scheme-surface-container-high));border-radius:var(--md-border-radius-xlarge);box-shadow:var(--md-shadow-6dp);transform-origin:center;transition:all var(--md-motion-expressive-default-spatial-duration) var(--md-motion-expressive-default-spatial-easing);opacity:0;transform:scaleY(.8);margin-top:-8rem}:host .md3-dialog-container.md3-active{opacity:1;transform:scaleY(1);margin-top:0}\n"], dependencies: [{ kind: "directive", type: CdkPortalOutlet, selector: "[cdkPortalOutlet]", inputs: ["cdkPortalOutlet"], outputs: ["attached"], exportAs: ["cdkPortalOutlet"] }] });
6049
+ class MenuGroup {
6050
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: MenuGroup, deps: [], target: i0.ɵɵFactoryTarget.Component });
6051
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.0", type: MenuGroup, isStandalone: true, selector: "md3-menu-group", host: { attributes: { "role": "group" } }, ngImport: i0, template: "<ng-content></ng-content>\n", styles: [":host{display:flex;flex-direction:column;gap:.125em;padding:.25em;background-color:var(--background-color);color:var(--foreground-color);border-radius:var(--menu-group-radius);box-shadow:var(--menu-group-shadow)}:host:first-child{border-start-start-radius:var(--border-radius);border-start-end-radius:var(--border-radius)}:host:last-child{border-end-start-radius:var(--border-radius);border-end-end-radius:var(--border-radius)}:host ::ng-deep md3-divider{margin:.125em .5em}:host ::ng-deep [md3-menu-item]:first-child .md3-menu-item-container{--border-top-start-r: var(--md-border-radius-medium);--border-top-end-r: var(--md-border-radius-medium);border-start-start-radius:var(--border-top-start-r);border-start-end-radius:var(--border-top-end-r)}:host ::ng-deep [md3-menu-item]:last-child .md3-menu-item-container{--border-bottom-start-r: var(--md-border-radius-medium);--border-bottom-end-r: var(--md-border-radius-medium);border-end-start-radius:var(--border-bottom-start-r);border-end-end-radius:var(--border-bottom-end-r)}.md3-menu-group-label{padding:.5rem var(--menu-item-inline-padding, .75rem) .25rem;color:rgb(var(--md-scheme-on-surface-variant));font-size:.75rem;font-weight:500;line-height:1rem;letter-spacing:.03125rem}\n"] });
6082
6052
  }
6083
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Dialog, decorators: [{
6053
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: MenuGroup, decorators: [{
6084
6054
  type: Component,
6085
- args: [{ selector: 'md3-dialog', imports: [
6086
- CdkPortalOutlet,
6087
- ], template: "<!-- The role and aria attributes live on the host element, applied by the CDK dialog container. -->\n<div\n #surface\n class=\"md3-dialog-container\"\n [class.md3-active]=\"isActive()\"\n [class.md-scheme-dark]=\"config.scheme == 'dark'\"\n [class.md-scheme-light]=\"config.scheme == 'light'\"\n [attr.dir]=\"config.direction ?? null\">\n <ng-template cdkPortalOutlet></ng-template>\n</div>\n", styles: ["::ng-deep .cdk-overlay-container{position:fixed;z-index:1000;inset:0;pointer-events:none;font-size:1rem}::ng-deep .cdk-overlay-container .md3-dialog-scrim{position:absolute;inset:0;pointer-events:auto;background-color:rgb(var(--md-scheme-scrim));transition:all var(--md-motion-expressive-slow-spatial-duration) var(--md-motion-expressive-slow-spatial-easing);opacity:.32}::ng-deep .cdk-overlay-container .md3-dialog-scrim.md3-dialog-opening{opacity:0}::ng-deep .cdk-overlay-container .md3-dialog-scrim.md3-dialog-closing{opacity:0}::ng-deep .cdk-overlay-container .md3-dialog-scrim.md3-dialog-hidden{opacity:0;pointer-events:none}::ng-deep .cdk-overlay-container .md3-dialog-scrim.md3-dialog-scrim-kept{opacity:.32;transition:none}::ng-deep .cdk-overlay-container .md3-dialog-scrim.md3-dialog-scrim-instant{transition:none}::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper{position:absolute;display:flex;inset:0;pointer-events:none;padding:3.5rem 1.5rem;overflow:hidden}::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper .md3-dialog-panel{position:absolute;pointer-events:auto;max-width:35em}::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper .md3-dialog-panel.md3-dialog-closing,::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper .md3-dialog-panel.md3-dialog-hidden{pointer-events:none}:host{display:block;max-width:100%;min-width:17.5em;outline:0}:host .md3-dialog-container{display:block;width:100%;max-height:inherit;overflow:auto;color:rgb(var(--md-scheme-on-surface));background-color:rgb(var(--md-scheme-surface-container-high));border-radius:var(--md-border-radius-xlarge);box-shadow:var(--md-shadow-6dp);transform-origin:center;transition:all var(--md-motion-expressive-default-spatial-duration) var(--md-motion-expressive-default-spatial-easing);opacity:0;transform:scaleY(.8);margin-top:-8rem}:host .md3-dialog-container.md3-active{opacity:1;transform:scaleY(1);margin-top:0}\n"] }]
6088
- }], propDecorators: { surface: [{ type: i0.ViewChild, args: ['surface', { isSignal: true }] }] } });
6055
+ args: [{ selector: 'md3-menu-group', imports: [], host: {
6056
+ 'role': 'group',
6057
+ }, template: "<ng-content></ng-content>\n", styles: [":host{display:flex;flex-direction:column;gap:.125em;padding:.25em;background-color:var(--background-color);color:var(--foreground-color);border-radius:var(--menu-group-radius);box-shadow:var(--menu-group-shadow)}:host:first-child{border-start-start-radius:var(--border-radius);border-start-end-radius:var(--border-radius)}:host:last-child{border-end-start-radius:var(--border-radius);border-end-end-radius:var(--border-radius)}:host ::ng-deep md3-divider{margin:.125em .5em}:host ::ng-deep [md3-menu-item]:first-child .md3-menu-item-container{--border-top-start-r: var(--md-border-radius-medium);--border-top-end-r: var(--md-border-radius-medium);border-start-start-radius:var(--border-top-start-r);border-start-end-radius:var(--border-top-end-r)}:host ::ng-deep [md3-menu-item]:last-child .md3-menu-item-container{--border-bottom-start-r: var(--md-border-radius-medium);--border-bottom-end-r: var(--md-border-radius-medium);border-end-start-radius:var(--border-bottom-start-r);border-end-end-radius:var(--border-bottom-end-r)}.md3-menu-group-label{padding:.5rem var(--menu-item-inline-padding, .75rem) .25rem;color:rgb(var(--md-scheme-on-surface-variant));font-size:.75rem;font-weight:500;line-height:1rem;letter-spacing:.03125rem}\n"] }]
6058
+ }] });
6089
6059
 
6090
- /**
6091
- * Material 3 full screen dialog shell. It covers the viewport and holds the content.
6092
- *
6093
- * The content is an `md3-layout`. The layout places the regions — the bar rows for an app bar
6094
- * or a toolbar, the rail columns, and the panes where side sheets open — so the dialog itself
6095
- * places nothing but the content. A content root that is not a layout gets a warning in
6096
- * development mode, and the shell falls back to panes of its own, so a side sheet never opens
6097
- * behind the dialog where nobody can see it.
6098
- */
6099
- class FullScreenDialog extends CdkDialogContainer {
6100
- surface = viewChild('surface', /* @ts-ignore */
6101
- ...(ngDevMode ? [{ debugName: "surface" }] : /* istanbul ignore next */ []));
6102
- mainPane = viewChild('mainPane', /* @ts-ignore */
6103
- ...(ngDevMode ? [{ debugName: "mainPane" }] : /* istanbul ignore next */ []));
6104
- startOutlet = viewChild('startOutlet', { ...(ngDevMode ? { debugName: "startOutlet" } : /* istanbul ignore next */ {}), read: CdkPortalOutlet });
6105
- endOutlet = viewChild('endOutlet', { ...(ngDevMode ? { debugName: "endOutlet" } : /* istanbul ignore next */ {}), read: CdkPortalOutlet });
6106
- sheets = inject(SheetsService);
6107
- /** True while the shell holds the side sheet outlets, which happens without a layout only. */
6108
- ownsSideSheetOutlets = false;
6109
- isActive = signal(false, /* @ts-ignore */
6110
- ...(ngDevMode ? [{ debugName: "isActive" }] : /* istanbul ignore next */ []));
6111
- config = inject(DIALOG_CONFIG, { optional: true }) ?? {};
6112
- get surfaceElement() {
6113
- return this.surface()?.nativeElement ?? null;
6060
+ class MenuItem {
6061
+ el;
6062
+ label = input('', { ...(ngDevMode ? { debugName: "label" } : /* istanbul ignore next */ {}), alias: 'label' });
6063
+ supportingText = input(null, { ...(ngDevMode ? { debugName: "supportingText" } : /* istanbul ignore next */ {}), alias: 'supporting-text' });
6064
+ trailingText = input(null, { ...(ngDevMode ? { debugName: "trailingText" } : /* istanbul ignore next */ {}), alias: 'trailing-text' });
6065
+ selected = model(null, { ...(ngDevMode ? { debugName: "selected" } : /* istanbul ignore next */ {}), alias: 'selected' });
6066
+ iconElements = contentChildren(IconElement, /* @ts-ignore */
6067
+ ...(ngDevMode ? [{ debugName: "iconElements" }] : /* istanbul ignore next */ []));
6068
+ get leading() {
6069
+ return this.iconElements().find((icon) => icon.iconType() === 'leading');
6114
6070
  }
6115
- ngAfterViewInit() {
6116
- // The content is attached by now, so the layout it brings has already taken the
6117
- // outlets. Its panes sit under the bar regions, which is where a side sheet belongs.
6118
- const layout = this.findContentLayout();
6119
- if (typeof ngDevMode === 'undefined' || ngDevMode) {
6120
- this.checkContentLayout(layout);
6121
- }
6122
- if (layout) {
6071
+ get trailing() {
6072
+ return this.iconElements().find((icon) => icon.iconType() === 'trailing');
6073
+ }
6074
+ get showLeadingIcon() {
6075
+ return (!!this.leading && !this.selected()) || this.selected() == true;
6076
+ }
6077
+ onHostClick() {
6078
+ const selected = this.selected();
6079
+ if (selected === null) {
6123
6080
  return;
6124
6081
  }
6125
- // No layout, so the shell keeps the dialog self-sufficient. Without this the next side
6126
- // sheet would open in the outlet underneath — the page scaffold, behind the dialog.
6127
- const startOutlet = this.startOutlet();
6128
- const endOutlet = this.endOutlet();
6129
- if (startOutlet) {
6130
- this.sheets.registerSideSheetOutlet('start', startOutlet);
6131
- }
6132
- if (endOutlet) {
6133
- this.sheets.registerSideSheetOutlet('end', endOutlet);
6134
- }
6135
- this.ownsSideSheetOutlets = !!startOutlet || !!endOutlet;
6082
+ this.selected.set(!selected);
6136
6083
  }
6137
- ngOnDestroy() {
6138
- if (this.ownsSideSheetOutlets) {
6139
- const startOutlet = this.startOutlet();
6140
- const endOutlet = this.endOutlet();
6141
- if (startOutlet) {
6142
- this.sheets.unregisterSideSheetOutlet('start', startOutlet);
6143
- }
6144
- if (endOutlet) {
6145
- this.sheets.unregisterSideSheetOutlet('end', endOutlet);
6146
- }
6147
- this.ownsSideSheetOutlets = false;
6148
- }
6149
- super.ngOnDestroy();
6084
+ constructor(el) {
6085
+ this.el = el;
6086
+ }
6087
+ get element() {
6088
+ return this.el.nativeElement;
6150
6089
  }
6090
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: MenuItem, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
6091
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: MenuItem, isStandalone: true, selector: "button[md3-menu-item], a[md3-menu-item]", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, supportingText: { classPropertyName: "supportingText", publicName: "supporting-text", isSignal: true, isRequired: false, transformFunction: null }, trailingText: { classPropertyName: "trailingText", publicName: "trailing-text", isSignal: true, isRequired: false, transformFunction: null }, selected: { classPropertyName: "selected", publicName: "selected", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selected: "selectedChange" }, host: { listeners: { "click": "onHostClick()" } }, queries: [{ propertyName: "iconElements", predicate: IconElement, isSignal: true }], ngImport: i0, template: "@if (selected() !== null) {\n <input type=\"checkbox\" md3-input-element [checked]=\"selected()\">\n}\n\n<div class=\"md3-menu-item-container\" md3-state-component>\n <div class=\"md3-menu-item-leading\" [class.has-icon]=\"showLeadingIcon\">\n <ng-content select=\"[md3-icon-element=leading]\"></ng-content>\n @if (!leading) {\n <md3-icon>check</md3-icon>\n }\n </div>\n\n <div class=\"md3-menu-item-content\">\n <md3-type-label size=\"large\">{{ label() }}</md3-type-label>\n @if (supportingText()?.length) {\n <md3-type-body size=\"small\">{{ supportingText() }}</md3-type-body>\n }\n </div>\n\n <div class=\"md3-menu-item-trailing\">\n <ng-content select=\"md3-badge\"></ng-content>\n @if (trailingText()?.length) {\n <md3-type-title size=\"small\">{{ trailingText() }}</md3-type-title>\n }\n <ng-content select=\"[md3-icon-element=trailing]\"></ng-content>\n </div>\n</div>", styles: [":host{--state-color: var(--menu-item-state-color);--border-radius: var(--menu-item-border-radius);--outline-color: var(--menu-item-outline-color);--outline-width: var(--menu-item-outline-width);--outline-offset: var(--menu-item-outline-offset);position:relative;display:block;padding:0;border:0;background:transparent;color:var(--menu-item-label-color);font:inherit;text-align:start;text-decoration:none;cursor:pointer;outline:none;appearance:none;-webkit-user-select:none;user-select:none;z-index:0}:host:focus{z-index:1}:host:focus:not(:hover) .md3-menu-item-container:after{opacity:1}:host:active{z-index:2}:host:active .md3-menu-item-container:after{opacity:0}:host .md3-menu-item-container{display:flex;align-items:center;min-height:3em;padding:0 .75em;border-radius:var(--border-radius);transition:background-color var(--md-motion-standard-fast-spatial-duration) var(--md-motion-standard-fast-spatial-easing),color var(--md-motion-standard-fast-spatial-duration) var(--md-motion-standard-fast-spatial-easing)}:host .md3-menu-item-container .md3-menu-item-leading{display:inline-flex;align-items:center;justify-content:center;font-size:inherit;opacity:0;transform:scale(1.2);width:0;height:1.25em;color:var(--menu-item-supporting-color);transition:all var(--md-motion-standard-fast-spatial-duration) var(--md-motion-standard-fast-spatial-easing)}:host .md3-menu-item-container .md3-menu-item-leading ::ng-deep [md3-icon-element]{font-size:1.25em!important;height:1em!important;width:1em!important;line-height:1em!important}:host .md3-menu-item-container .md3-menu-item-leading.has-icon{margin-inline-end:.5em;opacity:1;transform:scale(1);height:1.25em;width:1.25em}:host .md3-menu-item-container .md3-menu-item-trailing{flex:0 1 content;display:inline-flex;gap:.5em;align-items:center;justify-content:center;font-size:inherit;margin-inline-start:.5em;color:var(--menu-item-supporting-color)}:host .md3-menu-item-container .md3-menu-item-trailing:empty{margin:0}:host .md3-menu-item-container .md3-menu-item-trailing ::ng-deep md3-badge{background-color:var(--menu-item-badge-background);color:var(--menu-item-badge-foreground)}:host .md3-menu-item-container .md3-menu-item-trailing .md3-text-title.md3-text-small{margin:0;padding:0;white-space:nowrap;letter-spacing:.007143em}:host .md3-menu-item-container .md3-menu-item-trailing ::ng-deep [md3-icon-element]{font-size:1.25em!important;height:1em!important;width:1em!important;line-height:1em!important}:host .md3-menu-item-container .md3-menu-item-content{flex:1 1 auto}:host .md3-menu-item-container .md3-menu-item-content .md3-text-label.md3-text-large{display:block;margin:0;padding:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;letter-spacing:.007143em;color:var(--menu-item-label-color)}:host .md3-menu-item-container .md3-menu-item-content .md3-text-body.md3-text-small{display:block;margin:0;padding:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;letter-spacing:.0333334em;color:var(--menu-item-supporting-color)}:host [md3-input-element]{position:absolute;height:0;width:0;opacity:0;pointer-events:none}:host [md3-input-element]+.md3-menu-item-container .md3-menu-item-leading{opacity:0;transform:scale(1.2);width:0}:host [md3-input-element]:checked+.md3-menu-item-container{--menu-item-label-color: var(--menu-item-selected-foreground);--menu-item-supporting-color: var(--menu-item-selected-foreground);--border-radius: var(--md-border-radius-medium);background-color:var(--menu-item-selected-background)}:host [md3-input-element]:checked+.md3-menu-item-container .md3-menu-item-leading{opacity:1;transform:scale(1);width:1.25em}:host:disabled{cursor:default}:host:disabled .md3-menu-item-container{--menu-item-label-color: var(--menu-item-label-color);--menu-item-supporting-color: var(--menu-item-label-color);--menu-item-selected-background: transparent;--menu-item-selected-foreground: var(--menu-item-label-color);pointer-events:none;opacity:.38}:host:disabled .md3-menu-item-container.md3-state-layer,:host:disabled .md3-menu-item-container:after{display:none}\n"], dependencies: [{ kind: "directive", type: InputElement, selector: "[md3-input-element]" }, { kind: "directive", type: StateComponent, selector: "[md3-state-component]" }, { kind: "component", type: MaterialIcon, selector: "md3-icon", inputs: ["icon-version", "filled", "icon-style", "icon-size", "bi-directional"] }, { kind: "directive", type: TypeLabel, selector: "md3-type-label, [md3-type-label]", inputs: ["size", "color", "emphasized"] }, { kind: "directive", type: TypeBody, selector: "md3-type-body, [md3-type-body]", inputs: ["size", "color", "emphasized"] }, { kind: "directive", type: TypeTitle, selector: "md3-type-title, [md3-type-title]", inputs: ["size", "color", "emphasized"] }] });
6092
+ }
6093
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: MenuItem, decorators: [{
6094
+ type: Component,
6095
+ args: [{ selector: 'button[md3-menu-item], a[md3-menu-item]', imports: [
6096
+ InputElement,
6097
+ StateComponent,
6098
+ MaterialIcon,
6099
+ TypeLabel,
6100
+ TypeBody,
6101
+ TypeTitle,
6102
+ ], host: {
6103
+ '(click)': 'onHostClick()',
6104
+ }, template: "@if (selected() !== null) {\n <input type=\"checkbox\" md3-input-element [checked]=\"selected()\">\n}\n\n<div class=\"md3-menu-item-container\" md3-state-component>\n <div class=\"md3-menu-item-leading\" [class.has-icon]=\"showLeadingIcon\">\n <ng-content select=\"[md3-icon-element=leading]\"></ng-content>\n @if (!leading) {\n <md3-icon>check</md3-icon>\n }\n </div>\n\n <div class=\"md3-menu-item-content\">\n <md3-type-label size=\"large\">{{ label() }}</md3-type-label>\n @if (supportingText()?.length) {\n <md3-type-body size=\"small\">{{ supportingText() }}</md3-type-body>\n }\n </div>\n\n <div class=\"md3-menu-item-trailing\">\n <ng-content select=\"md3-badge\"></ng-content>\n @if (trailingText()?.length) {\n <md3-type-title size=\"small\">{{ trailingText() }}</md3-type-title>\n }\n <ng-content select=\"[md3-icon-element=trailing]\"></ng-content>\n </div>\n</div>", styles: [":host{--state-color: var(--menu-item-state-color);--border-radius: var(--menu-item-border-radius);--outline-color: var(--menu-item-outline-color);--outline-width: var(--menu-item-outline-width);--outline-offset: var(--menu-item-outline-offset);position:relative;display:block;padding:0;border:0;background:transparent;color:var(--menu-item-label-color);font:inherit;text-align:start;text-decoration:none;cursor:pointer;outline:none;appearance:none;-webkit-user-select:none;user-select:none;z-index:0}:host:focus{z-index:1}:host:focus:not(:hover) .md3-menu-item-container:after{opacity:1}:host:active{z-index:2}:host:active .md3-menu-item-container:after{opacity:0}:host .md3-menu-item-container{display:flex;align-items:center;min-height:3em;padding:0 .75em;border-radius:var(--border-radius);transition:background-color var(--md-motion-standard-fast-spatial-duration) var(--md-motion-standard-fast-spatial-easing),color var(--md-motion-standard-fast-spatial-duration) var(--md-motion-standard-fast-spatial-easing)}:host .md3-menu-item-container .md3-menu-item-leading{display:inline-flex;align-items:center;justify-content:center;font-size:inherit;opacity:0;transform:scale(1.2);width:0;height:1.25em;color:var(--menu-item-supporting-color);transition:all var(--md-motion-standard-fast-spatial-duration) var(--md-motion-standard-fast-spatial-easing)}:host .md3-menu-item-container .md3-menu-item-leading ::ng-deep [md3-icon-element]{font-size:1.25em!important;height:1em!important;width:1em!important;line-height:1em!important}:host .md3-menu-item-container .md3-menu-item-leading.has-icon{margin-inline-end:.5em;opacity:1;transform:scale(1);height:1.25em;width:1.25em}:host .md3-menu-item-container .md3-menu-item-trailing{flex:0 1 content;display:inline-flex;gap:.5em;align-items:center;justify-content:center;font-size:inherit;margin-inline-start:.5em;color:var(--menu-item-supporting-color)}:host .md3-menu-item-container .md3-menu-item-trailing:empty{margin:0}:host .md3-menu-item-container .md3-menu-item-trailing ::ng-deep md3-badge{background-color:var(--menu-item-badge-background);color:var(--menu-item-badge-foreground)}:host .md3-menu-item-container .md3-menu-item-trailing .md3-text-title.md3-text-small{margin:0;padding:0;white-space:nowrap;letter-spacing:.007143em}:host .md3-menu-item-container .md3-menu-item-trailing ::ng-deep [md3-icon-element]{font-size:1.25em!important;height:1em!important;width:1em!important;line-height:1em!important}:host .md3-menu-item-container .md3-menu-item-content{flex:1 1 auto}:host .md3-menu-item-container .md3-menu-item-content .md3-text-label.md3-text-large{display:block;margin:0;padding:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;letter-spacing:.007143em;color:var(--menu-item-label-color)}:host .md3-menu-item-container .md3-menu-item-content .md3-text-body.md3-text-small{display:block;margin:0;padding:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;letter-spacing:.0333334em;color:var(--menu-item-supporting-color)}:host [md3-input-element]{position:absolute;height:0;width:0;opacity:0;pointer-events:none}:host [md3-input-element]+.md3-menu-item-container .md3-menu-item-leading{opacity:0;transform:scale(1.2);width:0}:host [md3-input-element]:checked+.md3-menu-item-container{--menu-item-label-color: var(--menu-item-selected-foreground);--menu-item-supporting-color: var(--menu-item-selected-foreground);--border-radius: var(--md-border-radius-medium);background-color:var(--menu-item-selected-background)}:host [md3-input-element]:checked+.md3-menu-item-container .md3-menu-item-leading{opacity:1;transform:scale(1);width:1.25em}:host:disabled{cursor:default}:host:disabled .md3-menu-item-container{--menu-item-label-color: var(--menu-item-label-color);--menu-item-supporting-color: var(--menu-item-label-color);--menu-item-selected-background: transparent;--menu-item-selected-foreground: var(--menu-item-label-color);pointer-events:none;opacity:.38}:host:disabled .md3-menu-item-container.md3-state-layer,:host:disabled .md3-menu-item-container:after{display:none}\n"] }]
6105
+ }], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], supportingText: [{ type: i0.Input, args: [{ isSignal: true, alias: "supporting-text", required: false }] }], trailingText: [{ type: i0.Input, args: [{ isSignal: true, alias: "trailing-text", required: false }] }], selected: [{ type: i0.Input, args: [{ isSignal: true, alias: "selected", required: false }] }, { type: i0.Output, args: ["selectedChange"] }], iconElements: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => IconElement), { isSignal: true }] }] } });
6106
+
6107
+ class SelectOptions {
6108
+ options = input.required(/* @ts-ignore */
6109
+ ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
6110
+ multiple = input(false, /* @ts-ignore */
6111
+ ...(ngDevMode ? [{ debugName: "multiple" }] : /* istanbul ignore next */ []));
6151
6112
  /**
6152
- * The layout at the root of the content, if the content brought one.
6153
- *
6154
- * The root is either the layout itself, or the component that holds it — a dialog is
6155
- * normally opened with a component whose own template starts with `md3-layout`.
6113
+ * The options left after the search filter of the field. SelectField writes into this
6114
+ * signal while the menu is open, because menu inputs are bound once, at open. null means
6115
+ * that no filter applies, so every option is shown.
6156
6116
  */
6157
- findContentLayout() {
6158
- const content = this.mainPane()?.nativeElement.firstElementChild;
6159
- if (!content) {
6160
- return null;
6117
+ visibleOptions = signal(null, /* @ts-ignore */
6118
+ ...(ngDevMode ? [{ debugName: "visibleOptions" }] : /* istanbul ignore next */ []));
6119
+ /** Emits the values selected by the user. The field owns the selection state. */
6120
+ selectionChange = output();
6121
+ renderedOptions = computed(() => this.visibleOptions() ?? this.options(), /* @ts-ignore */
6122
+ ...(ngDevMode ? [{ debugName: "renderedOptions" }] : /* istanbul ignore next */ []));
6123
+ optionClick(option) {
6124
+ const options = this.options();
6125
+ if (this.multiple()) {
6126
+ option.selected = !(option.selected ?? false);
6161
6127
  }
6162
- if (content.tagName === 'MD3-LAYOUT') {
6163
- return content;
6128
+ else {
6129
+ options.forEach((item) => {
6130
+ item.selected = item === option;
6131
+ });
6164
6132
  }
6165
- const root = content.firstElementChild;
6166
- return root?.tagName === 'MD3-LAYOUT' ? root : null;
6133
+ // Reported from the full list, so a selected option hidden by the search is kept.
6134
+ this.selectionChange.emit(options.filter((item) => item.selected).map((item) => item.value));
6167
6135
  }
6168
- checkContentLayout(layout) {
6169
- if (layout) {
6136
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: SelectOptions, deps: [], target: i0.ɵɵFactoryTarget.Component });
6137
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: SelectOptions, isStandalone: true, selector: "md3-select-options", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectionChange: "selectionChange" }, ngImport: i0, template: "<md3-menu-group class=\"md3-scrollable\">\n @for (item of renderedOptions(); track item.value) {\n <button\n md3-menu-item\n [selected]=\"item.selected ?? false\"\n [label]=\"item.label\"\n [supporting-text]=\"item.supportingText ?? null\"\n [trailing-text]=\"item.trailingText ?? null\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"optionClick(item)\"\n >\n </button>\n }\n</md3-menu-group>\n", styles: [":host{max-height:inherit}:host md3-menu-group.md3-scrollable{padding:.5em .375em;max-height:inherit;overflow-y:auto}\n"], dependencies: [{ kind: "component", type: MenuGroup, selector: "md3-menu-group" }, { kind: "component", type: MenuItem, selector: "button[md3-menu-item], a[md3-menu-item]", inputs: ["label", "supporting-text", "trailing-text", "selected"], outputs: ["selectedChange"] }] });
6138
+ }
6139
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: SelectOptions, decorators: [{
6140
+ type: Component,
6141
+ args: [{ selector: 'md3-select-options', imports: [
6142
+ MenuGroup,
6143
+ MenuItem,
6144
+ ], template: "<md3-menu-group class=\"md3-scrollable\">\n @for (item of renderedOptions(); track item.value) {\n <button\n md3-menu-item\n [selected]=\"item.selected ?? false\"\n [label]=\"item.label\"\n [supporting-text]=\"item.supportingText ?? null\"\n [trailing-text]=\"item.trailingText ?? null\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"optionClick(item)\"\n >\n </button>\n }\n</md3-menu-group>\n", styles: [":host{max-height:inherit}:host md3-menu-group.md3-scrollable{padding:.5em .375em;max-height:inherit;overflow-y:auto}\n"] }]
6145
+ }], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: true }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }] } });
6146
+
6147
+ const MENU_DATA = new InjectionToken('MD3_MENU_DATA');
6148
+ const MENU_CONFIG = new InjectionToken('MD3_MENU_CONFIG');
6149
+ const MENU_COMPONENT = new InjectionToken('MD3_MENU_COMPONENT');
6150
+ const MENU_EXIT_ANIMATION_FALLBACK_MS = 300;
6151
+ class MenuRef {
6152
+ overlayRef;
6153
+ previouslyFocusedElement;
6154
+ closed = new Subject();
6155
+ isClosed = false;
6156
+ menuInstance;
6157
+ /**
6158
+ * Filled by MenuService after the user component is attached. Keeping the
6159
+ * instance here lets callers imperatively update inputs when that is useful.
6160
+ */
6161
+ componentInstance;
6162
+ constructor(overlayRef, previouslyFocusedElement) {
6163
+ this.overlayRef = overlayRef;
6164
+ this.previouslyFocusedElement = previouslyFocusedElement;
6165
+ }
6166
+ close(result) {
6167
+ if (this.isClosed) {
6170
6168
  return;
6171
6169
  }
6172
- console.warn('[md3-fullscreen-dialog] the content of a full screen dialog should start with '
6173
- + 'md3-layout. Without it the dialog has no bar and no rail region, so an app bar or '
6174
- + 'a toolbar has nowhere to go, and the content does not scroll on its own.');
6175
- }
6176
- startEnterAnimation() {
6177
- requestAnimationFrame(() => {
6178
- this.isActive.set(true);
6170
+ this.isClosed = true;
6171
+ this.startCloseAnimation().then(() => {
6172
+ // Disposing the overlay removes the wrapper, the backdrop, and the
6173
+ // dynamically attached content component in one operation.
6174
+ this.overlayRef.dispose();
6175
+ // this.previouslyFocusedElement?.focus();
6176
+ this.closed.next(result);
6177
+ this.closed.complete();
6179
6178
  });
6180
6179
  }
6181
- setActive(value) {
6182
- this.isActive.set(value);
6180
+ startCloseAnimation() {
6181
+ let panel = this.overlayRef.overlayElement;
6182
+ let animatedElement = panel.querySelector('.md3-menu-container') ?? panel;
6183
+ panel.classList.add('md3-menu-closing');
6184
+ this.menuInstance?.showMenu(false);
6185
+ return new Promise((resolve) => {
6186
+ let isResolved = false;
6187
+ let timeoutId = setTimeout(done, MENU_EXIT_ANIMATION_FALLBACK_MS);
6188
+ function done() {
6189
+ if (isResolved) {
6190
+ return;
6191
+ }
6192
+ isResolved = true;
6193
+ clearTimeout(timeoutId);
6194
+ animatedElement.removeEventListener('transitionend', onTransitionEnd);
6195
+ resolve();
6196
+ }
6197
+ function onTransitionEnd(event) {
6198
+ if (event.target === animatedElement) {
6199
+ done();
6200
+ }
6201
+ }
6202
+ animatedElement.addEventListener('transitionend', onTransitionEnd);
6203
+ });
6183
6204
  }
6184
- recaptureFocus() {
6185
- this._recaptureFocus();
6205
+ afterClosed() {
6206
+ return this.closed.asObservable();
6186
6207
  }
6187
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: FullScreenDialog, deps: null, target: i0.ɵɵFactoryTarget.Component });
6188
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.1.0", type: FullScreenDialog, isStandalone: true, selector: "md3-fullscreen-dialog", viewQueries: [{ propertyName: "surface", first: true, predicate: ["surface"], descendants: true, isSignal: true }, { propertyName: "mainPane", first: true, predicate: ["mainPane"], descendants: true, isSignal: true }, { propertyName: "startOutlet", first: true, predicate: ["startOutlet"], descendants: true, read: CdkPortalOutlet, isSignal: true }, { propertyName: "endOutlet", first: true, predicate: ["endOutlet"], descendants: true, read: CdkPortalOutlet, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<!-- The role and aria attributes live on the host element, applied by the CDK dialog container. -->\n<div\n #surface\n class=\"md3-fullscreen-dialog-container\"\n [class.md3-active]=\"isActive()\"\n [class.md-scheme-dark]=\"config.scheme == 'dark'\"\n [class.md-scheme-light]=\"config.scheme == 'light'\"\n [attr.dir]=\"config.direction ?? null\">\n <div class=\"md3-panes-container md3-scrollable\">\n <!--\n The CDK attaches the dialog content to the first portal outlet of\n this template, so the main pane has to come first. The panes are\n placed by grid area, which makes the DOM order irrelevant visually.\n -->\n <div class=\"md3-pane md3-pane-main\" #mainPane>\n <ng-template cdkPortalOutlet></ng-template>\n </div>\n <div class=\"md3-pane md3-pane-start\">\n <ng-template #startOutlet cdkPortalOutlet></ng-template>\n </div>\n <div class=\"md3-pane md3-pane-end\">\n <ng-template #endOutlet cdkPortalOutlet></ng-template>\n </div>\n </div>\n</div>\n", styles: ["::ng-deep .cdk-overlay-container{position:fixed;z-index:1000;inset:0;pointer-events:none;font-size:1rem}::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper{position:absolute;display:flex;inset:0;pointer-events:none;overflow:hidden}::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper:has(>.md3-fullscreen-dialog-panel){padding:0}::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper .md3-fullscreen-dialog-panel{width:100%;height:100%;max-width:none;pointer-events:auto}::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper .md3-fullscreen-dialog-panel.md3-dialog-closing,::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper .md3-fullscreen-dialog-panel.md3-dialog-hidden{pointer-events:none}:host{display:block;height:100%;width:100%;outline:0}:host .md3-fullscreen-dialog-container{display:grid;grid-template-rows:minmax(0,1fr);height:100%;min-height:0;overflow:hidden;color:rgb(var(--md-scheme-on-surface));background-color:rgb(var(--md-scheme-surface));transition:all var(--md-motion-expressive-default-spatial-duration) var(--md-motion-expressive-default-spatial-easing);opacity:0;transform:translateY(1.5em)}:host .md3-fullscreen-dialog-container.md3-active{opacity:1;transform:none}:host .md3-panes-container{display:grid;grid-template-areas:\"start main end\";grid-template-columns:auto minmax(0,1fr) auto;grid-template-rows:minmax(0,1fr);min-width:0;min-height:0;overflow:hidden}:host .md3-panes-container .md3-pane{justify-self:stretch;align-self:stretch;min-width:0;min-height:0;height:100%;overflow:hidden}:host .md3-panes-container .md3-pane.md3-pane-start{grid-area:start}:host .md3-panes-container .md3-pane.md3-pane-main{position:relative;grid-area:main;z-index:1;display:grid;grid-template-rows:minmax(0,1fr);grid-template-columns:minmax(0,1fr)}:host .md3-panes-container .md3-pane.md3-pane-main>::ng-deep *{min-width:0;min-height:0;height:100%;max-height:100dvh;max-width:100dvw}:host .md3-panes-container .md3-pane.md3-pane-end{grid-area:end}\n"], dependencies: [{ kind: "directive", type: CdkPortalOutlet, selector: "[cdkPortalOutlet]", inputs: ["cdkPortalOutlet"], outputs: ["attached"], exportAs: ["cdkPortalOutlet"] }] });
6189
6208
  }
6190
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: FullScreenDialog, decorators: [{
6191
- type: Component,
6192
- args: [{ selector: 'md3-fullscreen-dialog', imports: [
6193
- CdkPortalOutlet,
6194
- ], template: "<!-- The role and aria attributes live on the host element, applied by the CDK dialog container. -->\n<div\n #surface\n class=\"md3-fullscreen-dialog-container\"\n [class.md3-active]=\"isActive()\"\n [class.md-scheme-dark]=\"config.scheme == 'dark'\"\n [class.md-scheme-light]=\"config.scheme == 'light'\"\n [attr.dir]=\"config.direction ?? null\">\n <div class=\"md3-panes-container md3-scrollable\">\n <!--\n The CDK attaches the dialog content to the first portal outlet of\n this template, so the main pane has to come first. The panes are\n placed by grid area, which makes the DOM order irrelevant visually.\n -->\n <div class=\"md3-pane md3-pane-main\" #mainPane>\n <ng-template cdkPortalOutlet></ng-template>\n </div>\n <div class=\"md3-pane md3-pane-start\">\n <ng-template #startOutlet cdkPortalOutlet></ng-template>\n </div>\n <div class=\"md3-pane md3-pane-end\">\n <ng-template #endOutlet cdkPortalOutlet></ng-template>\n </div>\n </div>\n</div>\n", styles: ["::ng-deep .cdk-overlay-container{position:fixed;z-index:1000;inset:0;pointer-events:none;font-size:1rem}::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper{position:absolute;display:flex;inset:0;pointer-events:none;overflow:hidden}::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper:has(>.md3-fullscreen-dialog-panel){padding:0}::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper .md3-fullscreen-dialog-panel{width:100%;height:100%;max-width:none;pointer-events:auto}::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper .md3-fullscreen-dialog-panel.md3-dialog-closing,::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper .md3-fullscreen-dialog-panel.md3-dialog-hidden{pointer-events:none}:host{display:block;height:100%;width:100%;outline:0}:host .md3-fullscreen-dialog-container{display:grid;grid-template-rows:minmax(0,1fr);height:100%;min-height:0;overflow:hidden;color:rgb(var(--md-scheme-on-surface));background-color:rgb(var(--md-scheme-surface));transition:all var(--md-motion-expressive-default-spatial-duration) var(--md-motion-expressive-default-spatial-easing);opacity:0;transform:translateY(1.5em)}:host .md3-fullscreen-dialog-container.md3-active{opacity:1;transform:none}:host .md3-panes-container{display:grid;grid-template-areas:\"start main end\";grid-template-columns:auto minmax(0,1fr) auto;grid-template-rows:minmax(0,1fr);min-width:0;min-height:0;overflow:hidden}:host .md3-panes-container .md3-pane{justify-self:stretch;align-self:stretch;min-width:0;min-height:0;height:100%;overflow:hidden}:host .md3-panes-container .md3-pane.md3-pane-start{grid-area:start}:host .md3-panes-container .md3-pane.md3-pane-main{position:relative;grid-area:main;z-index:1;display:grid;grid-template-rows:minmax(0,1fr);grid-template-columns:minmax(0,1fr)}:host .md3-panes-container .md3-pane.md3-pane-main>::ng-deep *{min-width:0;min-height:0;height:100%;max-height:100dvh;max-width:100dvw}:host .md3-panes-container .md3-pane.md3-pane-end{grid-area:end}\n"] }]
6195
- }], propDecorators: { surface: [{ type: i0.ViewChild, args: ['surface', { isSignal: true }] }], mainPane: [{ type: i0.ViewChild, args: ['mainPane', { isSignal: true }] }], startOutlet: [{ type: i0.ViewChild, args: ['startOutlet', { ...{ read: CdkPortalOutlet }, isSignal: true }] }], endOutlet: [{ type: i0.ViewChild, args: ['endOutlet', { ...{ read: CdkPortalOutlet }, isSignal: true }] }] } });
6196
6209
 
6197
- const PANEL_CLASS = 'md3-dialog-panel';
6198
- const FULL_SCREEN_PANEL_CLASS = 'md3-fullscreen-dialog-panel';
6199
- const OPENING_CLASS = 'md3-dialog-opening';
6200
- const SCRIM_KEPT_CLASS = 'md3-dialog-scrim-kept';
6201
- const SCRIM_INSTANT_CLASS = 'md3-dialog-scrim-instant';
6202
- /**
6203
- * Head start given to a dialog that leaves the screen before the dialog taking
6204
- * its place animates in. Long enough to read as one dialog replacing another,
6205
- * short enough to still feel like a single gesture.
6206
- */
6207
- const DIALOG_HANDOVER_DELAY_MS = 160;
6208
- class DialogService {
6209
- cdkDialog = inject(Dialog$1);
6210
- overlay = inject(Overlay);
6211
- overlayContainer = inject(OverlayContainer);
6212
- injector = inject(Injector);
6213
- document = inject(DOCUMENT);
6214
- /** Open dialogs, from the first one opened to the one currently on top. */
6215
- refs = [];
6216
- /**
6217
- * Elements the page was made of that the CDK hid from assistive technology,
6218
- * kept here while the dialogs are hidden and the page is given back.
6219
- */
6220
- pageAriaHidden = new Map();
6210
+ class Menu {
6211
+ el;
6212
+ portalOutlet = viewChild(CdkPortalOutlet, /* @ts-ignore */
6213
+ ...(ngDevMode ? [{ debugName: "portalOutlet" }] : /* istanbul ignore next */ []));
6214
+ config = inject(MENU_CONFIG, { optional: true }) ?? {};
6215
+ menuColors = signal('standard', /* @ts-ignore */
6216
+ ...(ngDevMode ? [{ debugName: "menuColors" }] : /* istanbul ignore next */ []));
6217
+ isVisible = signal(false, /* @ts-ignore */
6218
+ ...(ngDevMode ? [{ debugName: "isVisible" }] : /* istanbul ignore next */ []));
6219
+ scheme = signal('inherit', /* @ts-ignore */
6220
+ ...(ngDevMode ? [{ debugName: "scheme" }] : /* istanbul ignore next */ []));
6221
+ direction = signal(null, /* @ts-ignore */
6222
+ ...(ngDevMode ? [{ debugName: "direction" }] : /* istanbul ignore next */ []));
6223
+ isActive = signal(true, /* @ts-ignore */
6224
+ ...(ngDevMode ? [{ debugName: "isActive" }] : /* istanbul ignore next */ []));
6225
+ get element() {
6226
+ return this.el.nativeElement;
6227
+ }
6228
+ constructor(el) {
6229
+ this.el = el;
6230
+ effect((onCleanup) => {
6231
+ const color = 'md3-' + this.menuColors();
6232
+ this.element.classList.add(color);
6233
+ onCleanup(() => {
6234
+ this.element.classList.remove(color);
6235
+ });
6236
+ });
6237
+ this.menuColors.set(this.config.menuColors ?? 'standard');
6238
+ this.scheme.set(this.config.scheme ?? 'inherit');
6239
+ this.direction.set(this.config.direction ?? null);
6240
+ }
6241
+ ngAfterContentInit() {
6242
+ requestAnimationFrame(() => {
6243
+ this.showMenu(true);
6244
+ });
6245
+ }
6246
+ showMenu(value) {
6247
+ this.isVisible.set(value);
6248
+ }
6221
6249
  /**
6222
- * Page scrolling is blocked here instead of per overlay, so stacked dialogs
6223
- * cannot unblock the page while another dialog is still open.
6250
+ * The service creates this wrapper first, then calls this method to mount
6251
+ * the user supplied component into the dialog container.
6224
6252
  */
6225
- scrollBlock = this.overlay.scrollStrategies.block();
6226
- /** Element that was focused before the first dialog of the stack opened. */
6227
- rootTrigger = null;
6228
- pageStateQueued = false;
6229
- get openDialogs() {
6230
- return this.refs;
6253
+ attachContent(component, injector) {
6254
+ const portal = new ComponentPortal(component, null, injector);
6255
+ return this.portalOutlet().attachComponentPortal(portal);
6231
6256
  }
6232
- /** Open dialogs that are currently on screen, from the bottom one up. */
6233
- get visibleDialogs() {
6234
- return this.refs.filter((ref) => !ref.isHidden);
6257
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Menu, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
6258
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.1.0", type: Menu, isStandalone: true, selector: "md3-menu", host: { attributes: { "role": "menu" }, properties: { "class.md3-visible": "isVisible()", "class.md3-inactive": "!isActive()", "class.md-scheme-dark": "scheme() == \"dark\"", "class.md-scheme-light": "scheme() == \"light\"", "dir": "direction()" }, classAttribute: "md3-menu-container" }, viewQueries: [{ propertyName: "portalOutlet", first: true, predicate: CdkPortalOutlet, descendants: true, isSignal: true }], ngImport: i0, template: "<ng-template cdkPortalOutlet></ng-template>", styles: ["::ng-deep .cdk-overlay-container{position:fixed;z-index:1000;inset:0;pointer-events:none;font-size:1rem}::ng-deep .cdk-overlay-container .md3-menu-scrim{position:absolute;inset:0;pointer-events:auto}::ng-deep .cdk-overlay-container .cdk-overlay-connected-position-bounding-box{pointer-events:none}::ng-deep .cdk-overlay-container .md3-menu-panel{pointer-events:auto}::ng-deep .cdk-overlay-container .md3-menu-panel.md3-select-menu>.md3-menu-container{--menu-min-width: 100%;--menu-max-width: 100%}::ng-deep .cdk-overlay-container .md3-menu-panel.md3-menu-closing{pointer-events:none}::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper{position:absolute;display:flex;inset:0;pointer-events:none;padding:3.5rem 0;overflow:hidden}::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper .md3-menu-panel{position:absolute;pointer-events:auto}::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper .md3-menu-panel.md3-menu-closing{pointer-events:none}:host{--background-color: rgb(var(--md-scheme-surface-container-low));--foreground-color: rgb(var(--md-scheme-on-surface));--border-radius: var(--md-border-radius-large);--menu-min-width: 7em;--menu-max-width: 17.5em;--menu-group-radius: var(--md-border-radius-small);--menu-group-shadow: var(--md-shadow-3dp);--menu-group-label-color: rgb(var(--md-scheme-on-surface-variant));--menu-item-label-color: rgb(var(--md-scheme-on-surface));--menu-item-supporting-color: rgb(var(--md-scheme-on-surface-variant));--menu-item-selected-background: rgb(var(--md-scheme-tertiary-container));--menu-item-selected-foreground: rgb(var(--md-scheme-on-tertiary-container));--menu-item-state-color: var(--md-scheme-on-surface);--menu-item-border-radius: var(--md-border-radius-xsmall);--menu-item-outline-color: rgb(var(--md-scheme-secondary));--menu-item-outline-width: .1875em;--menu-item-outline-offset: -.1875em;--menu-item-badge-background: rgb(var(--md-scheme-primary-container));--menu-item-badge-foreground: rgb(var(--md-scheme-on-primary-container));position:relative;display:block;font-size:1rem;min-width:var(--menu-min-width);max-width:min(var(--menu-max-width),100vw - 2rem);max-height:min(32rem,100vh - 2rem);outline:none;transition:all var(--md-motion-expressive-default-spatial-duration) var(--md-motion-expressive-default-spatial-easing);opacity:0;transform:scaleY(.8)}:host.md3-visible{opacity:1;transform:scaleY(1)}:host.md3-vibrant{--background-color: rgb(var(--md-scheme-tertiary-container));--foreground-color: rgb(var(--md-scheme-on-tertiary-container));--menu-group-label-color: rgb(var(--md-scheme-on-tertiary-container));--menu-item-label-color: rgb(var(--md-scheme-on-tertiary-container));--menu-item-supporting-color: rgb(var(--md-scheme-on-tertiary-container));--menu-item-selected-background: rgb(var(--md-scheme-tertiary));--menu-item-selected-foreground: rgb(var(--md-scheme-on-tertiary));--menu-item-state-color: var(--md-scheme-on-tertiary-container)}:host.md3-inactive{--border-radius: var(--md-border-radius-small)}:host ::ng-deep md3-menu-group{margin-top:.125em}:host ::ng-deep md3-menu-group:first-child{margin-top:0}\n"], dependencies: [{ kind: "directive", type: CdkPortalOutlet, selector: "[cdkPortalOutlet]", inputs: ["cdkPortalOutlet"], outputs: ["attached"], exportAs: ["cdkPortalOutlet"] }] });
6259
+ }
6260
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Menu, decorators: [{
6261
+ type: Component,
6262
+ args: [{ selector: 'md3-menu', imports: [
6263
+ CdkPortalOutlet,
6264
+ ], host: {
6265
+ 'class': 'md3-menu-container',
6266
+ 'role': 'menu',
6267
+ '[class.md3-visible]': 'isVisible()',
6268
+ '[class.md3-inactive]': '!isActive()',
6269
+ '[class.md-scheme-dark]': 'scheme() == "dark"',
6270
+ '[class.md-scheme-light]': 'scheme() == "light"',
6271
+ '[dir]': 'direction()',
6272
+ }, template: "<ng-template cdkPortalOutlet></ng-template>", styles: ["::ng-deep .cdk-overlay-container{position:fixed;z-index:1000;inset:0;pointer-events:none;font-size:1rem}::ng-deep .cdk-overlay-container .md3-menu-scrim{position:absolute;inset:0;pointer-events:auto}::ng-deep .cdk-overlay-container .cdk-overlay-connected-position-bounding-box{pointer-events:none}::ng-deep .cdk-overlay-container .md3-menu-panel{pointer-events:auto}::ng-deep .cdk-overlay-container .md3-menu-panel.md3-select-menu>.md3-menu-container{--menu-min-width: 100%;--menu-max-width: 100%}::ng-deep .cdk-overlay-container .md3-menu-panel.md3-menu-closing{pointer-events:none}::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper{position:absolute;display:flex;inset:0;pointer-events:none;padding:3.5rem 0;overflow:hidden}::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper .md3-menu-panel{position:absolute;pointer-events:auto}::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper .md3-menu-panel.md3-menu-closing{pointer-events:none}:host{--background-color: rgb(var(--md-scheme-surface-container-low));--foreground-color: rgb(var(--md-scheme-on-surface));--border-radius: var(--md-border-radius-large);--menu-min-width: 7em;--menu-max-width: 17.5em;--menu-group-radius: var(--md-border-radius-small);--menu-group-shadow: var(--md-shadow-3dp);--menu-group-label-color: rgb(var(--md-scheme-on-surface-variant));--menu-item-label-color: rgb(var(--md-scheme-on-surface));--menu-item-supporting-color: rgb(var(--md-scheme-on-surface-variant));--menu-item-selected-background: rgb(var(--md-scheme-tertiary-container));--menu-item-selected-foreground: rgb(var(--md-scheme-on-tertiary-container));--menu-item-state-color: var(--md-scheme-on-surface);--menu-item-border-radius: var(--md-border-radius-xsmall);--menu-item-outline-color: rgb(var(--md-scheme-secondary));--menu-item-outline-width: .1875em;--menu-item-outline-offset: -.1875em;--menu-item-badge-background: rgb(var(--md-scheme-primary-container));--menu-item-badge-foreground: rgb(var(--md-scheme-on-primary-container));position:relative;display:block;font-size:1rem;min-width:var(--menu-min-width);max-width:min(var(--menu-max-width),100vw - 2rem);max-height:min(32rem,100vh - 2rem);outline:none;transition:all var(--md-motion-expressive-default-spatial-duration) var(--md-motion-expressive-default-spatial-easing);opacity:0;transform:scaleY(.8)}:host.md3-visible{opacity:1;transform:scaleY(1)}:host.md3-vibrant{--background-color: rgb(var(--md-scheme-tertiary-container));--foreground-color: rgb(var(--md-scheme-on-tertiary-container));--menu-group-label-color: rgb(var(--md-scheme-on-tertiary-container));--menu-item-label-color: rgb(var(--md-scheme-on-tertiary-container));--menu-item-supporting-color: rgb(var(--md-scheme-on-tertiary-container));--menu-item-selected-background: rgb(var(--md-scheme-tertiary));--menu-item-selected-foreground: rgb(var(--md-scheme-on-tertiary));--menu-item-state-color: var(--md-scheme-on-tertiary-container)}:host.md3-inactive{--border-radius: var(--md-border-radius-small)}:host ::ng-deep md3-menu-group{margin-top:.125em}:host ::ng-deep md3-menu-group:first-child{margin-top:0}\n"] }]
6273
+ }], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { portalOutlet: [{ type: i0.ViewChild, args: [i0.forwardRef(() => CdkPortalOutlet), { isSignal: true }] }] } });
6274
+
6275
+ class MenuService {
6276
+ overlay = inject(Overlay);
6277
+ injector = inject(Injector);
6278
+ document = inject(DOCUMENT);
6279
+ open(component, config = {}) {
6280
+ const menuConfig = this.mergeConfig(config);
6281
+ const previouslyFocusedElement = this.getFocusedElement();
6282
+ const triggerElement = this.resolveTriggerElement(menuConfig.origin, previouslyFocusedElement);
6283
+ const menuOverlay = this.createOverlay(menuConfig, previouslyFocusedElement);
6284
+ const overlayRef = menuOverlay.overlayRef;
6285
+ const menuRef = new MenuRef(overlayRef, previouslyFocusedElement);
6286
+ const injector = this.createInjector(component, menuConfig, menuRef);
6287
+ // The overlay hosts the MD3 menu shell. The shell is responsible for
6288
+ // the surface markup and receives the dynamic content through a portal.
6289
+ const menuPortal = new ComponentPortal(Menu, menuConfig.viewContainerRef ?? null, injector);
6290
+ const menuComponentRef = overlayRef.attach(menuPortal);
6291
+ const contentComponentRef = menuComponentRef.instance.attachContent(component, injector);
6292
+ this.bindDataToInputs(contentComponentRef, menuConfig);
6293
+ this.flipPositionIfPushBreaksAnchor(menuOverlay, menuConfig);
6294
+ menuRef.componentInstance = contentComponentRef.instance;
6295
+ menuRef.menuInstance = menuComponentRef.instance;
6296
+ this.bindTriggerActiveState(triggerElement, overlayRef, menuRef);
6297
+ this.startOpenAnimation(overlayRef);
6298
+ this.connectCloseEvents(overlayRef, menuRef, menuOverlay.origin);
6299
+ return menuRef;
6235
6300
  }
6236
- /** The full screen dialog that is open, if there is one. */
6237
- get fullScreenDialog() {
6238
- return this.refs.find((ref) => ref.isFullScreen && !ref.isClosing);
6301
+ openSubMenu(component, config = {}) {
6302
+ const xPosition = this.getSubMenuXPosition(config.xPosition);
6303
+ const yPosition = config.yPosition ?? 'below';
6304
+ const offsetX = config.offsetX ?? 0;
6305
+ const offsetY = config.offsetY ?? 0;
6306
+ const subMenuConfig = {
6307
+ ...config,
6308
+ isSubMenu: true,
6309
+ xPosition,
6310
+ yPosition,
6311
+ overlapTrigger: config.overlapTrigger ?? true,
6312
+ offsetX,
6313
+ offsetY,
6314
+ scheme: config.scheme ?? 'inherit',
6315
+ direction: config.direction ?? null,
6316
+ };
6317
+ return this.open(component, subMenuConfig);
6239
6318
  }
6240
- open(component, config = {}) {
6241
- const dialogConfig = this.mergeConfig(config);
6242
- if (this.refs.length === 0) {
6243
- this.rootTrigger = this.getFocusedElement();
6244
- }
6245
- // The dialog that is on screen starts leaving first, so the two dialogs
6246
- // never animate on top of each other. A full screen dialog is not part
6247
- // of this: regular dialogs open on top of it and leave it alone.
6248
- const previousRef = this.topRegularRef();
6249
- if (previousRef) {
6250
- this.dismissPrevious(previousRef, dialogConfig.previousDialog);
6251
- }
6252
- let dialogRef;
6253
- const cdkRef = this.cdkDialog.open(component, {
6254
- // The MD3 shell replaces the default CDK container, so the overlay,
6255
- // focus trap and focus restoration come from the CDK while the shell
6256
- // only renders the surface.
6257
- container: {
6258
- type: Dialog,
6259
- providers: () => [
6260
- { provide: DIALOG_CONFIG, useValue: dialogConfig },
6261
- ],
6262
- },
6263
- // These tokens make the dialog controllable from the dynamic
6264
- // component without coupling that component to the service.
6265
- providers: (cdkDialogRef) => {
6266
- dialogRef = new DialogRef(cdkDialogRef, dialogConfig.disableCloseEvents);
6267
- return [
6268
- { provide: DialogRef, useValue: dialogRef },
6269
- { provide: DIALOG_DATA, useValue: dialogConfig.data },
6270
- { provide: DIALOG_CONFIG, useValue: dialogConfig },
6271
- { provide: DIALOG_COMPONENT, useValue: component },
6272
- ];
6273
- },
6274
- data: dialogConfig.data,
6275
- role: dialogConfig.role,
6276
- ariaModal: true,
6277
- ariaLabel: dialogConfig.ariaLabel || null,
6278
- ariaLabelledBy: dialogConfig.ariaLabelledBy || null,
6279
- ariaDescribedBy: dialogConfig.ariaDescribedBy || null,
6280
- direction: dialogConfig.direction ?? undefined,
6281
- viewContainerRef: dialogConfig.viewContainerRef,
6282
- injector: dialogConfig.injector,
6283
- hasBackdrop: true,
6284
- backdropClass: ['md3-dialog-scrim', OPENING_CLASS],
6285
- panelClass: [PANEL_CLASS, OPENING_CLASS],
6286
- positionStrategy: this.overlay.position()
6287
- .global()
6288
- .centerHorizontally()
6289
- .centerVertically(),
6290
- // Scrolling is blocked by the service for the whole stack.
6291
- scrollStrategy: this.overlay.scrollStrategies.noop(),
6292
- // Focus goes back to whatever opened the stack, even when the dialog
6293
- // that was open underneath has been closed in the meantime.
6294
- restoreFocus: this.rootTrigger ?? true,
6295
- // Scrim and Escape handling lives in DialogRef so the dialog can
6296
- // play its exit animation before the overlay is disposed.
6297
- disableClose: true,
6319
+ mergeConfig(config) {
6320
+ const viewportMargin = this.getViewportMargin(config.viewportMargin);
6321
+ return {
6322
+ data: config.data,
6323
+ bindDataToInputs: config.bindDataToInputs ?? false,
6324
+ isSubMenu: config.isSubMenu ?? false,
6325
+ menuColors: config.menuColors ?? 'standard',
6326
+ origin: config.origin,
6327
+ xPosition: config.xPosition ?? 'start',
6328
+ yPosition: config.yPosition ?? 'below',
6329
+ overlapTrigger: config.overlapTrigger ?? false,
6330
+ offsetX: config.offsetX ?? 0,
6331
+ offsetY: config.offsetY ?? 4,
6332
+ viewportMargin,
6333
+ positions: config.positions,
6334
+ scrollStrategy: config.scrollStrategy ?? 'reposition',
6335
+ scheme: config.scheme ?? 'inherit',
6336
+ direction: config.direction ?? null,
6337
+ viewContainerRef: config.viewContainerRef,
6338
+ injector: config.injector ?? this.injector,
6339
+ };
6340
+ }
6341
+ isSelectInput(origin) {
6342
+ return origin?.tagName.toLowerCase() == 'input' && origin?.classList.contains('md3-select-input');
6343
+ }
6344
+ createOverlay(config, fallbackOrigin) {
6345
+ const origin = this.resolveOrigin(config.origin, fallbackOrigin);
6346
+ const positionStrategy = this.overlay.position()
6347
+ .flexibleConnectedTo(origin)
6348
+ .withFlexibleDimensions(false)
6349
+ .withGrowAfterOpen(true)
6350
+ .withPush(true)
6351
+ .withViewportMargin(config.viewportMargin)
6352
+ .withPositions(config.positions ?? this.getInitialConnectedPositions(config))
6353
+ .withTransformOriginOn('.md3-menu-container');
6354
+ const isSelectInput = this.isSelectInput(origin);
6355
+ const originRect = this.getOriginClientRect(origin);
6356
+ const minWidth = !isSelectInput ? undefined : originRect.width;
6357
+ const overlayConfig = new OverlayConfig({
6358
+ hasBackdrop: !isSelectInput,
6359
+ backdropClass: ['md3-menu-scrim', 'md3-menu-opening'],
6360
+ panelClass: ['md3-menu-panel', 'md3-menu-opening', ...(isSelectInput ? ['md3-select-menu'] : [])],
6361
+ positionStrategy,
6362
+ scrollStrategy: this.getScrollStrategy(config.scrollStrategy),
6363
+ minWidth: minWidth,
6298
6364
  });
6299
- this.registerDialog(dialogRef, cdkRef, dialogConfig, previousRef);
6300
- return dialogRef;
6365
+ return {
6366
+ overlayRef: this.overlay.create(overlayConfig),
6367
+ positionStrategy: config.positions ? undefined : positionStrategy,
6368
+ origin,
6369
+ };
6301
6370
  }
6302
- /**
6303
- * Opens a dialog that covers the whole screen. Only one can be open at a
6304
- * time, so it replaces whatever is open, and while it is up the side sheet
6305
- * outlets belong to it: side sheets open inside the dialog, not behind it.
6306
- * `previousDialog` decides what happens to the dialogs it replaces, the
6307
- * same way it does for a regular dialog.
6308
- */
6309
- openFullScreen(component, config = {}) {
6310
- const dialogConfig = this.mergeConfig(config);
6311
- if (this.refs.length === 0) {
6312
- this.rootTrigger = this.getFocusedElement();
6313
- }
6314
- // A full screen dialog is a context of its own: the dialog it replaces
6315
- // and anything stacked on that dialog goes with it.
6316
- const previousRef = this.topRef();
6317
- if (previousRef) {
6318
- // Everything open belongs to the context being replaced, including
6319
- // a full screen dialog that is already up.
6320
- this.dismissPreviousContext(dialogConfig.previousDialog);
6371
+ bindTriggerActiveState(triggerElement, overlayRef, menuRef) {
6372
+ if (!triggerElement) {
6373
+ return;
6321
6374
  }
6322
- let dialogRef;
6323
- const cdkRef = this.cdkDialog.open(component, {
6324
- container: {
6325
- type: FullScreenDialog,
6326
- providers: () => [
6327
- { provide: DIALOG_CONFIG, useValue: dialogConfig },
6328
- ],
6329
- },
6330
- providers: (cdkDialogRef) => {
6331
- dialogRef = new DialogRef(cdkDialogRef, dialogConfig.disableCloseEvents, true);
6332
- return [
6333
- { provide: DialogRef, useValue: dialogRef },
6334
- { provide: DIALOG_DATA, useValue: dialogConfig.data },
6335
- { provide: DIALOG_CONFIG, useValue: dialogConfig },
6336
- { provide: DIALOG_COMPONENT, useValue: component },
6337
- ];
6338
- },
6339
- data: dialogConfig.data,
6340
- role: dialogConfig.role,
6341
- ariaModal: true,
6342
- ariaLabel: dialogConfig.ariaLabel || null,
6343
- ariaLabelledBy: dialogConfig.ariaLabelledBy || null,
6344
- ariaDescribedBy: dialogConfig.ariaDescribedBy || null,
6345
- direction: dialogConfig.direction ?? undefined,
6346
- viewContainerRef: dialogConfig.viewContainerRef,
6347
- injector: dialogConfig.injector,
6348
- // The dialog covers the page on its own, so there is nothing left
6349
- // for a scrim to dim.
6350
- hasBackdrop: false,
6351
- panelClass: [FULL_SCREEN_PANEL_CLASS, OPENING_CLASS],
6352
- // The CDK writes the size on the panel and the global strategy then
6353
- // drops its centering margins, which is how the panel ends up
6354
- // covering the whole wrapper.
6355
- width: '100%',
6356
- height: '100%',
6357
- positionStrategy: this.overlay.position().global(),
6358
- scrollStrategy: this.overlay.scrollStrategies.noop(),
6359
- restoreFocus: this.rootTrigger ?? true,
6360
- disableClose: true,
6361
- });
6362
- this.registerDialog(dialogRef, cdkRef, dialogConfig, previousRef);
6363
- return dialogRef;
6375
+ const removeActiveClass = () => triggerElement.classList.remove('md3-menu-active');
6376
+ triggerElement.classList.add('md3-menu-active');
6377
+ menuRef.afterClosed().pipe(take(1)).subscribe(removeActiveClass);
6378
+ overlayRef.detachments().pipe(take(1)).subscribe(removeActiveClass);
6364
6379
  }
6365
- /** Wires a freshly created dialog into the stack and starts its animation. */
6366
- registerDialog(dialogRef, cdkRef, dialogConfig, previousRef) {
6367
- dialogRef.dialogInstance = cdkRef.containerInstance;
6368
- dialogRef.componentInstance = cdkRef.componentInstance ?? undefined;
6369
- if (cdkRef.componentRef) {
6370
- this.bindDataToInputs(cdkRef.componentRef, dialogConfig);
6380
+ resolveOrigin(origin, fallbackOrigin) {
6381
+ const resolvedOrigin = origin ?? fallbackOrigin;
6382
+ if (resolvedOrigin) {
6383
+ if (this.isMouseEvent(resolvedOrigin)) {
6384
+ if (resolvedOrigin.currentTarget instanceof HTMLElement) {
6385
+ return resolvedOrigin.currentTarget;
6386
+ }
6387
+ return {
6388
+ x: resolvedOrigin.clientX,
6389
+ y: resolvedOrigin.clientY,
6390
+ };
6391
+ }
6392
+ if (this.isElementRef(resolvedOrigin)) {
6393
+ return resolvedOrigin.nativeElement;
6394
+ }
6395
+ return resolvedOrigin;
6396
+ }
6397
+ const viewport = this.document.defaultView;
6398
+ return {
6399
+ x: (viewport?.innerWidth ?? 0) / 2,
6400
+ y: (viewport?.innerHeight ?? 0) / 2,
6401
+ };
6402
+ }
6403
+ resolveTriggerElement(origin, fallbackOrigin) {
6404
+ const resolvedOrigin = origin ?? fallbackOrigin;
6405
+ if (!resolvedOrigin) {
6406
+ return null;
6371
6407
  }
6372
- this.refs.push(dialogRef);
6373
- this.syncPageState();
6374
- if (previousRef) {
6375
- setTimeout(() => this.startReplacingAnimation(dialogRef, previousRef), DIALOG_HANDOVER_DELAY_MS);
6408
+ if (this.isMouseEvent(resolvedOrigin)) {
6409
+ return resolvedOrigin.currentTarget instanceof HTMLElement
6410
+ ? resolvedOrigin.currentTarget
6411
+ : null;
6376
6412
  }
6377
- else {
6378
- this.startOpenAnimation(dialogRef);
6413
+ if (this.isElementRef(resolvedOrigin)) {
6414
+ return resolvedOrigin.nativeElement;
6379
6415
  }
6380
- // Restoring starts with the exit animation, so the dialog underneath is
6381
- // already coming back while this one is fading out.
6382
- dialogRef.beforeClosed().subscribe(() => this.restorePreviousDialog(dialogRef));
6383
- dialogRef.afterClosed().subscribe(() => this.removeRef(dialogRef));
6384
- // Hiding and showing can also come straight from the reference, so the
6385
- // page state is driven by what the dialogs report rather than by the
6386
- // calls that went through this service.
6387
- dialogRef.hiddenChanged().subscribe(() => this.syncPageState());
6388
- }
6389
- /**
6390
- * Hides every dialog that is on screen, the full screen one included, with
6391
- * the closing animation. They stay alive with their content and state, the
6392
- * page underneath becomes usable, and showAll() puts them back. The promise
6393
- * resolves once they are all out of sight.
6394
- */
6395
- hideAll() {
6396
- const visible = this.refs.filter((ref) => !ref.isHidden && !ref.isClosing);
6397
- return Promise.all(visible.map((ref) => ref.hide())).then(() => undefined);
6416
+ return resolvedOrigin instanceof HTMLElement ? resolvedOrigin : null;
6398
6417
  }
6399
- /**
6400
- * Brings the dialogs back on screen, whether they were hidden one by one or
6401
- * all at once: the dialog that was on top returns, along with the full
6402
- * screen dialog it sits in. Dialogs hidden behind another dialog stay
6403
- * hidden, since that is where they belong once the stack is back.
6404
- */
6405
- showAll() {
6406
- const topRef = this.topRef();
6407
- if (!topRef) {
6418
+ flipPositionIfPushBreaksAnchor(menuOverlay, config) {
6419
+ if (!menuOverlay.positionStrategy) {
6408
6420
  return;
6409
6421
  }
6410
- const fullScreenRef = this.fullScreenDialog;
6411
- // The full screen dialog is the context the top one sits in, so it
6412
- // comes back first and the dialog on top ends up on top, with the focus.
6413
- if (fullScreenRef && fullScreenRef !== topRef && fullScreenRef.isHidden) {
6414
- fullScreenRef.show();
6415
- }
6416
- if (topRef.isHidden) {
6417
- topRef.show();
6422
+ menuOverlay.overlayRef.updatePosition();
6423
+ const fallbackPosition = this.getFallbackPositionAfterPush(config, this.getOriginClientRect(menuOverlay.origin), menuOverlay.overlayRef.overlayElement.getBoundingClientRect());
6424
+ if (!fallbackPosition) {
6425
+ return;
6418
6426
  }
6427
+ menuOverlay.positionStrategy.withPositions([fallbackPosition]);
6428
+ menuOverlay.overlayRef.updatePosition();
6419
6429
  }
6420
- /** Closes every open dialog, including the hidden and the full screen ones. */
6421
- closeAll() {
6422
- // Closed from the bottom up so a closing dialog never brings back a
6423
- // hidden dialog that is about to close as well.
6424
- return Promise.all([...this.refs].map((ref) => ref.close())).then(() => undefined);
6430
+ getInitialConnectedPositions(config) {
6431
+ return [this.getPreferredConnectedPosition(config)];
6425
6432
  }
6426
- /** Closes the regular dialogs and leaves a full screen dialog alone. */
6427
- closeRegularDialogs() {
6428
- const closing = this.refs
6429
- .filter((ref) => !ref.isFullScreen)
6430
- .map((ref) => ref.close());
6431
- return Promise.all(closing).then(() => undefined);
6433
+ getPreferredConnectedPosition(config) {
6434
+ if (config.isSubMenu) {
6435
+ return this.createSubMenuConnectedPosition(this.getSubMenuXPosition(config.xPosition), this.getSubMenuVerticalAlignment(config.yPosition), config.offsetX, config.offsetY);
6436
+ }
6437
+ return this.createConnectedPosition(config.xPosition, config.yPosition, config);
6432
6438
  }
6433
- mergeConfig(config) {
6434
- return {
6435
- data: config.data,
6436
- bindDataToInputs: config.bindDataToInputs ?? false,
6437
- disableCloseEvents: config.disableCloseEvents ?? false,
6438
- previousDialog: config.previousDialog ?? 'close',
6439
- role: config.role ?? 'dialog',
6440
- ariaLabel: config.ariaLabel ?? '',
6441
- ariaLabelledBy: config.ariaLabelledBy ?? '',
6442
- ariaDescribedBy: config.ariaDescribedBy ?? '',
6443
- scheme: config.scheme ?? 'inherit',
6444
- direction: config.direction ?? null,
6445
- viewContainerRef: config.viewContainerRef,
6446
- injector: config.injector ?? this.injector,
6447
- };
6439
+ getFallbackPositionAfterPush(config, originRect, overlayRect) {
6440
+ const xPosition = this.shouldFlipXPosition(config, originRect, overlayRect)
6441
+ ? this.getFallbackXPosition(config.xPosition)
6442
+ : config.xPosition;
6443
+ const yPosition = this.shouldFlipYPosition(config, originRect, overlayRect)
6444
+ ? this.getFallbackYPosition(config.yPosition)
6445
+ : config.yPosition;
6446
+ if (xPosition === config.xPosition && yPosition === config.yPosition) {
6447
+ return null;
6448
+ }
6449
+ return this.getPreferredConnectedPosition({
6450
+ ...config,
6451
+ xPosition,
6452
+ yPosition,
6453
+ });
6448
6454
  }
6449
- /**
6450
- * Hides or closes the dialog that is on screen. Its scrim stays up until
6451
- * the dialog taking over is ready for it, so the page behind never
6452
- * brightens between the two dialogs.
6453
- */
6454
- dismissPrevious(previousRef, behavior) {
6455
- previousRef.overlayRef.backdropElement?.classList.add(SCRIM_KEPT_CLASS);
6456
- return behavior === 'hide' ? previousRef.hide() : this.closeRegularDialogs();
6455
+ shouldFlipXPosition(config, originRect, overlayRect) {
6456
+ const tolerance = config.viewportMargin;
6457
+ switch (config.xPosition) {
6458
+ case 'before':
6459
+ return overlayRect.right > originRect.left + tolerance;
6460
+ case 'after':
6461
+ return overlayRect.left < originRect.right - tolerance;
6462
+ case 'start':
6463
+ return originRect.left < overlayRect.left - tolerance
6464
+ || originRect.left > overlayRect.right + tolerance;
6465
+ case 'end':
6466
+ return originRect.right < overlayRect.left - tolerance
6467
+ || originRect.right > overlayRect.right + tolerance;
6468
+ case 'center':
6469
+ default:
6470
+ return false;
6471
+ }
6457
6472
  }
6458
- /**
6459
- * The same choice for a full screen dialog, applied to everything that is
6460
- * on screen: what it takes over from is a whole context, a full screen
6461
- * dialog of its own included. `hide` keeps that context alive, and it comes
6462
- * back as it was once this dialog closes.
6463
- */
6464
- dismissPreviousContext(behavior) {
6465
- return behavior === 'hide' ? this.hideAll() : this.closeAll();
6473
+ shouldFlipYPosition(config, originRect, overlayRect) {
6474
+ const tolerance = config.viewportMargin;
6475
+ if (config.isSubMenu) {
6476
+ const anchorY = config.yPosition === 'above' ? originRect.bottom : originRect.top;
6477
+ return anchorY < overlayRect.top - tolerance
6478
+ || anchorY > overlayRect.bottom + tolerance;
6479
+ }
6480
+ if (config.overlapTrigger) {
6481
+ const anchorY = config.yPosition === 'above' ? originRect.bottom : originRect.top;
6482
+ return anchorY < overlayRect.top - tolerance
6483
+ || anchorY > overlayRect.bottom + tolerance;
6484
+ }
6485
+ switch (config.yPosition) {
6486
+ case 'above':
6487
+ return overlayRect.bottom > originRect.top + tolerance;
6488
+ case 'below':
6489
+ return overlayRect.top < originRect.bottom - tolerance;
6490
+ case 'center':
6491
+ default:
6492
+ return false;
6493
+ }
6466
6494
  }
6467
- startOpenAnimation(dialogRef) {
6468
- const overlayRef = dialogRef.overlayRef;
6469
- const panel = overlayRef.overlayElement;
6470
- const backdrop = overlayRef.backdropElement;
6471
- // The overlay is inserted already rendered. Keep an initial class for
6472
- // one frame, force style calculation, then remove it so CSS transitions
6473
- // have a real from/to state.
6474
- requestAnimationFrame(() => {
6475
- setTimeout(() => {
6476
- panel.getBoundingClientRect();
6477
- panel.classList.remove(OPENING_CLASS);
6478
- backdrop?.classList.remove(OPENING_CLASS);
6479
- }, 100);
6480
- });
6481
- dialogRef.dialogInstance?.startEnterAnimation();
6495
+ createConnectedPosition(xPosition, yPosition, config) {
6496
+ const horizontal = this.getHorizontalPosition(xPosition);
6497
+ const vertical = this.getVerticalPosition(yPosition, config.overlapTrigger);
6498
+ return {
6499
+ ...horizontal,
6500
+ ...vertical,
6501
+ offsetX: config.offsetX,
6502
+ offsetY: this.getOffsetY(yPosition, config),
6503
+ panelClass: [
6504
+ 'md3-menu-position-' + xPosition,
6505
+ 'md3-menu-position-' + yPosition,
6506
+ ],
6507
+ };
6482
6508
  }
6483
- /** Opening animation for a dialog that took the place of another one. */
6484
- startReplacingAnimation(dialogRef, previousRef) {
6485
- if (dialogRef.isClosing) {
6486
- return;
6487
- }
6488
- const panel = dialogRef.overlayRef.overlayElement;
6489
- const backdrop = dialogRef.overlayRef.backdropElement;
6490
- this.beginScrimHandover(previousRef, dialogRef);
6491
- panel.getBoundingClientRect();
6492
- panel.classList.remove(OPENING_CLASS);
6493
- backdrop?.classList.remove(OPENING_CLASS);
6494
- this.endScrimHandover(previousRef, dialogRef);
6495
- dialogRef.dialogInstance?.startEnterAnimation();
6509
+ createSubMenuConnectedPosition(xPosition, yAlignment, offsetX, offsetY) {
6510
+ const horizontal = this.getHorizontalPosition(xPosition);
6511
+ const yPosition = yAlignment === 'top' ? 'below' : 'above';
6512
+ return {
6513
+ ...horizontal,
6514
+ originY: yAlignment,
6515
+ overlayY: yAlignment,
6516
+ offsetX: this.getSubMenuOffsetX(xPosition, offsetX),
6517
+ offsetY: this.getSubMenuOffsetY(yAlignment, offsetY),
6518
+ panelClass: [
6519
+ 'md3-menu-position-' + xPosition,
6520
+ 'md3-menu-position-' + yPosition,
6521
+ 'md3-menu-position-submenu',
6522
+ ],
6523
+ };
6496
6524
  }
6497
- /** Opening animation for a dialog coming back from behind a closing one. */
6498
- startRestoringAnimation(closingRef, previousRef) {
6499
- if (previousRef.isClosing) {
6500
- return;
6525
+ getHorizontalPosition(position) {
6526
+ switch (position) {
6527
+ case 'end':
6528
+ return {
6529
+ originX: 'end',
6530
+ overlayX: 'end',
6531
+ };
6532
+ case 'before':
6533
+ return {
6534
+ originX: 'start',
6535
+ overlayX: 'end',
6536
+ };
6537
+ case 'after':
6538
+ return {
6539
+ originX: 'end',
6540
+ overlayX: 'start',
6541
+ };
6542
+ case 'center':
6543
+ return {
6544
+ originX: 'center',
6545
+ overlayX: 'center',
6546
+ };
6547
+ case 'start':
6548
+ default:
6549
+ return {
6550
+ originX: 'start',
6551
+ overlayX: 'start',
6552
+ };
6501
6553
  }
6502
- this.beginScrimHandover(closingRef, previousRef);
6503
- previousRef.show();
6504
- this.endScrimHandover(closingRef, previousRef);
6505
6554
  }
6506
- /**
6507
- * Moves both scrims to their new value in a single frame, without a
6508
- * transition on either side, so the dim behind the dialogs never changes.
6509
- */
6510
- beginScrimHandover(fromRef, toRef) {
6511
- const from = fromRef.overlayRef.backdropElement;
6512
- const to = toRef.overlayRef.backdropElement;
6513
- // Nothing to hand the scrim over to, e.g. a full screen dialog: the
6514
- // outgoing scrim fades out on its own instead.
6515
- if (!to) {
6516
- return;
6555
+ getVerticalPosition(position, overlapTrigger) {
6556
+ switch (position) {
6557
+ case 'above':
6558
+ return {
6559
+ originY: overlapTrigger ? 'bottom' : 'top',
6560
+ overlayY: 'bottom',
6561
+ };
6562
+ case 'center':
6563
+ return {
6564
+ originY: 'center',
6565
+ overlayY: 'center',
6566
+ };
6567
+ case 'below':
6568
+ default:
6569
+ return {
6570
+ originY: overlapTrigger ? 'top' : 'bottom',
6571
+ overlayY: 'top',
6572
+ };
6517
6573
  }
6518
- // Nothing to hand over either: the dialog leaving carries no scrim, so
6519
- // the one coming in fades its own in rather than switching it on in a
6520
- // single frame. This is a full screen dialog closing over a dialog that
6521
- // was hidden underneath it.
6522
- if (!from) {
6523
- return;
6574
+ }
6575
+ getFallbackXPosition(position) {
6576
+ switch (position) {
6577
+ case 'start':
6578
+ return 'end';
6579
+ case 'end':
6580
+ return 'start';
6581
+ case 'before':
6582
+ return 'after';
6583
+ case 'after':
6584
+ return 'before';
6585
+ case 'center':
6586
+ default:
6587
+ return 'center';
6524
6588
  }
6525
- from?.classList.add(SCRIM_INSTANT_CLASS);
6526
- from?.classList.remove(SCRIM_KEPT_CLASS);
6527
- to?.classList.add(SCRIM_INSTANT_CLASS);
6528
6589
  }
6529
- /** Gives the scrims their transitions back once the handover is painted. */
6530
- endScrimHandover(fromRef, toRef) {
6531
- const from = fromRef.overlayRef.backdropElement;
6532
- const to = toRef.overlayRef.backdropElement;
6533
- requestAnimationFrame(() => {
6534
- from?.classList.remove(SCRIM_INSTANT_CLASS);
6535
- to?.classList.remove(SCRIM_INSTANT_CLASS);
6536
- });
6590
+ getSubMenuXPosition(position) {
6591
+ return position === 'before' ? 'before' : 'after';
6537
6592
  }
6538
- bindDataToInputs(componentRef, config) {
6539
- if (!config.bindDataToInputs || !this.canBindDataToInputs(config.data)) {
6540
- return;
6593
+ getFallbackYPosition(position) {
6594
+ switch (position) {
6595
+ case 'above':
6596
+ return 'below';
6597
+ case 'below':
6598
+ return 'above';
6599
+ case 'center':
6600
+ default:
6601
+ return 'center';
6541
6602
  }
6542
- // setInput runs Angular's normal input pipeline, so input setters,
6543
- // transforms, signal inputs, and change detection are handled for us.
6544
- Object.entries(config.data).forEach(([inputName, inputValue]) => {
6545
- componentRef.setInput(inputName, inputValue);
6546
- });
6547
6603
  }
6548
- canBindDataToInputs(data) {
6549
- return typeof data === 'object' && data !== null && !Array.isArray(data);
6604
+ getSubMenuVerticalAlignment(position) {
6605
+ return position === 'above' ? 'bottom' : 'top';
6550
6606
  }
6551
- /** Last dialog of the stack that is not closing yet. */
6552
- topRef() {
6553
- for (let index = this.refs.length - 1; index >= 0; index--) {
6554
- if (!this.refs[index].isClosing) {
6555
- return this.refs[index];
6556
- }
6607
+ getOriginClientRect(origin) {
6608
+ if ('getBoundingClientRect' in origin) {
6609
+ return origin.getBoundingClientRect();
6557
6610
  }
6558
- return undefined;
6611
+ return {
6612
+ x: origin.x,
6613
+ y: origin.y,
6614
+ top: origin.y,
6615
+ right: origin.x,
6616
+ bottom: origin.y,
6617
+ left: origin.x,
6618
+ width: 0,
6619
+ height: 0,
6620
+ };
6559
6621
  }
6560
- /**
6561
- * Same, without the full screen dialog: it hosts the dialogs opened on top
6562
- * of it, so it is never the one that gets hidden or closed to make room.
6563
- */
6564
- topRegularRef() {
6565
- const ref = this.topRef();
6566
- return ref?.isFullScreen ? undefined : ref;
6622
+ getViewportMargin(configuredMargin) {
6623
+ const minimumMargin = this.getHalfEmInPixels();
6624
+ return Math.max(configuredMargin ?? minimumMargin, minimumMargin);
6567
6625
  }
6568
- restorePreviousDialog(ref) {
6569
- const index = this.refs.indexOf(ref);
6570
- if (index < 1) {
6571
- return;
6572
- }
6573
- for (let previousIndex = index - 1; previousIndex >= 0; previousIndex--) {
6574
- const previousRef = this.refs[previousIndex];
6575
- if (previousRef.isClosing) {
6576
- continue;
6577
- }
6578
- // The closing dialog gets the same head start it was given when it
6579
- // took the screen, and keeps the scrim until then. Nothing to keep
6580
- // when the dialog underneath has no scrim of its own.
6581
- if (previousRef.overlayRef.backdropElement) {
6582
- ref.overlayRef.backdropElement?.classList.add(SCRIM_KEPT_CLASS);
6583
- }
6584
- setTimeout(() => {
6585
- this.showHostFullScreenDialog(previousRef);
6586
- this.startRestoringAnimation(ref, previousRef);
6587
- }, DIALOG_HANDOVER_DELAY_MS);
6588
- return;
6589
- }
6626
+ getHalfEmInPixels() {
6627
+ const viewport = this.document.defaultView;
6628
+ const fontSize = viewport?.getComputedStyle(this.document.documentElement).fontSize;
6629
+ const pixels = fontSize ? Number.parseFloat(fontSize) * 0.5 : 8;
6630
+ return Number.isFinite(pixels) ? pixels : 8;
6590
6631
  }
6591
- /**
6592
- * Brings back the full screen dialog a restored dialog sits in. The two are
6593
- * hidden together when a full screen dialog takes over with
6594
- * `previousDialog: 'hide'`, so the context has to be on screen again before
6595
- * the dialog that sits on top of it, exactly as showAll() puts them back.
6596
- */
6597
- showHostFullScreenDialog(ref) {
6598
- if (ref.isFullScreen) {
6599
- return;
6600
- }
6601
- const hostRef = this.fullScreenDialog;
6602
- if (hostRef?.isHidden && this.refs.indexOf(hostRef) < this.refs.indexOf(ref)) {
6603
- hostRef.show();
6632
+ getOffsetY(position, config) {
6633
+ if (config.overlapTrigger || position === 'center') {
6634
+ return 0;
6604
6635
  }
6636
+ return position === 'above'
6637
+ ? config.offsetY * -1
6638
+ : config.offsetY;
6605
6639
  }
6606
- removeRef(ref) {
6607
- const index = this.refs.indexOf(ref);
6608
- if (index === -1) {
6609
- return;
6610
- }
6611
- this.refs.splice(index, 1);
6612
- if (this.refs.length === 0) {
6613
- this.rootTrigger = null;
6614
- }
6615
- this.syncPageState();
6640
+ getSubMenuOffsetX(position, offsetX) {
6641
+ return position === 'before' ? offsetX * -1 : offsetX;
6616
6642
  }
6617
- /**
6618
- * State that belongs to the dialogs as a whole rather than to one of them:
6619
- * page scrolling, the page being hidden from assistive technology, and
6620
- * where focus sits. Applied in a task of its own, so a dialog handing the
6621
- * screen over to another one does not give the page back and take it again
6622
- * within the same frame.
6623
- */
6624
- syncPageState() {
6625
- if (this.pageStateQueued) {
6626
- return;
6627
- }
6628
- this.pageStateQueued = true;
6629
- setTimeout(() => {
6630
- this.pageStateQueued = false;
6631
- this.applyPageState();
6632
- });
6643
+ getSubMenuOffsetY(alignment, offsetY) {
6644
+ return alignment === 'bottom' ? offsetY * -1 : offsetY;
6633
6645
  }
6634
- applyPageState() {
6635
- const hasVisibleDialog = this.refs.some((ref) => !ref.isHidden);
6636
- if (hasVisibleDialog) {
6637
- this.scrollBlock.enable();
6638
- }
6639
- else {
6640
- this.scrollBlock.disable();
6641
- }
6642
- if (this.refs.length === 0) {
6643
- // Nothing is open anymore: the CDK gives the page back to assistive
6644
- // technology on its own, so there is nothing left to restore here.
6645
- this.pageAriaHidden.clear();
6646
- return;
6647
- }
6648
- if (hasVisibleDialog) {
6649
- this.hidePageFromAssistiveTechnology();
6650
- return;
6646
+ getScrollStrategy(scrollStrategy) {
6647
+ switch (scrollStrategy) {
6648
+ case 'block':
6649
+ return this.overlay.scrollStrategies.block();
6650
+ case 'close':
6651
+ return this.overlay.scrollStrategies.close();
6652
+ case 'noop':
6653
+ return this.overlay.scrollStrategies.noop();
6654
+ case 'reposition':
6655
+ default:
6656
+ return this.overlay.scrollStrategies.reposition();
6651
6657
  }
6652
- this.exposePageToAssistiveTechnology();
6653
- this.releaseFocus();
6654
6658
  }
6655
- /**
6656
- * Gives the page back to assistive technology while every dialog is hidden.
6657
- * The CDK hides it for as long as a dialog is open, which would otherwise
6658
- * leave nothing to read at all once the dialogs are out of sight.
6659
- */
6660
- exposePageToAssistiveTechnology() {
6661
- if (this.pageAriaHidden.size > 0) {
6662
- return;
6663
- }
6664
- const container = this.overlayContainer.getContainerElement();
6665
- Array.from(container.parentElement?.children ?? []).forEach((sibling) => {
6666
- const value = sibling.getAttribute('aria-hidden');
6667
- if (sibling === container || value === null) {
6668
- return;
6669
- }
6670
- this.pageAriaHidden.set(sibling, value);
6671
- sibling.removeAttribute('aria-hidden');
6659
+ isMouseEvent(origin) {
6660
+ return typeof MouseEvent !== 'undefined' && origin instanceof MouseEvent;
6661
+ }
6662
+ isElementRef(origin) {
6663
+ return typeof origin === 'object'
6664
+ && origin !== null
6665
+ && 'nativeElement' in origin
6666
+ && origin.nativeElement instanceof HTMLElement;
6667
+ }
6668
+ startOpenAnimation(overlayRef) {
6669
+ const panel = overlayRef.overlayElement;
6670
+ const backdrop = overlayRef.backdropElement;
6671
+ // The overlay is inserted already rendered. Keep an initial class for
6672
+ // one frame, force style calculation, then remove it so CSS transitions
6673
+ // have a real from/to state.
6674
+ requestAnimationFrame(() => {
6675
+ setTimeout(() => {
6676
+ panel.getBoundingClientRect();
6677
+ panel.classList.remove('md3-menu-opening');
6678
+ backdrop?.classList.remove('md3-menu-opening');
6679
+ }, 100);
6672
6680
  });
6673
6681
  }
6674
- /** Hides the page again as soon as a dialog is back on screen. */
6675
- hidePageFromAssistiveTechnology() {
6676
- this.pageAriaHidden.forEach((value, element) => {
6677
- element.setAttribute('aria-hidden', value);
6682
+ createInjector(component, config, menuRef) {
6683
+ return Injector.create({
6684
+ parent: config.injector,
6685
+ providers: [
6686
+ // These tokens make the menu controllable from the dynamic
6687
+ // component without coupling that component to the service.
6688
+ { provide: MenuRef, useValue: menuRef },
6689
+ { provide: MENU_DATA, useValue: config.data },
6690
+ { provide: MENU_CONFIG, useValue: config },
6691
+ { provide: MENU_COMPONENT, useValue: component },
6692
+ ],
6678
6693
  });
6679
- this.pageAriaHidden.clear();
6680
6694
  }
6681
- /**
6682
- * Focus follows the dialogs off the screen: it goes back to whatever opened
6683
- * the stack, so the page stays usable with the keyboard while the dialogs
6684
- * wait behind it. Focus that already moved somewhere else is left alone.
6685
- */
6686
- releaseFocus() {
6687
- const activeElement = this.document.activeElement;
6688
- if (this.rootTrigger && (!activeElement || activeElement === this.document.body)) {
6689
- this.rootTrigger.focus();
6695
+ bindDataToInputs(componentRef, config) {
6696
+ if (!config.bindDataToInputs || !this.canBindDataToInputs(config.data)) {
6697
+ return;
6698
+ }
6699
+ // setInput runs Angular's normal input pipeline, so input setters,
6700
+ // transforms, signal inputs, and change detection are handled for us.
6701
+ Object.entries(config.data).forEach(([inputName, inputValue]) => {
6702
+ componentRef.setInput(inputName, inputValue);
6703
+ });
6704
+ }
6705
+ connectCloseEvents(overlayRef, menuRef, origin) {
6706
+ overlayRef.backdropClick().pipe(take(1)).subscribe(() => menuRef.close());
6707
+ overlayRef.keydownEvents().pipe(filter((event) => event.key === 'Escape'), take(1)).subscribe(() => menuRef.close());
6708
+ if (this.isSelectInput(origin)) {
6709
+ const originRect = this.getOriginClientRect(origin);
6710
+ overlayRef.outsidePointerEvents().pipe(filter((event) => {
6711
+ const clickedInsideOrigin = event.clientX >= originRect.left &&
6712
+ event.clientX <= originRect.right &&
6713
+ event.clientY >= originRect.top &&
6714
+ event.clientY <= originRect.bottom;
6715
+ return !clickedInsideOrigin;
6716
+ })).subscribe(() => menuRef.close());
6690
6717
  }
6691
6718
  }
6692
6719
  getFocusedElement() {
6693
6720
  const activeElement = this.document.activeElement;
6694
6721
  return activeElement instanceof HTMLElement ? activeElement : null;
6695
6722
  }
6696
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: DialogService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
6697
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: DialogService, providedIn: 'root' });
6723
+ canBindDataToInputs(data) {
6724
+ return typeof data === 'object' && data !== null && !Array.isArray(data);
6725
+ }
6726
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: MenuService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
6727
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: MenuService, providedIn: 'root' });
6698
6728
  }
6699
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: DialogService, decorators: [{
6729
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: MenuService, decorators: [{
6700
6730
  type: Injectable,
6701
6731
  args: [{
6702
6732
  providedIn: 'root',
6703
6733
  }]
6704
6734
  }] });
6705
6735
 
6706
- class DialogHeader {
6707
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: DialogHeader, deps: [], target: i0.ɵɵFactoryTarget.Component });
6708
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.0", type: DialogHeader, isStandalone: true, selector: "md3-dialog-header", ngImport: i0, template: "<ng-content select=\"[md3-icon-element]\"></ng-content>\n<div class=\"md3-dialog-title\">\n <md3-type-headline size=\"small\">\n <ng-content></ng-content>\n </md3-type-headline>\n</div>", styles: [":host{display:block;margin:1.5em 1.5em 1em;text-align:center}:host .md3-dialog-title{text-align:start;color:rgb(var(--md-scheme-on-surface))}:host ::ng-deep [md3-icon-element]{color:rgb(var(--md-scheme-secondary))}:host ::ng-deep [md3-icon-element]+.md3-dialog-title{margin-top:1em;text-align:center}\n"], dependencies: [{ kind: "directive", type: TypeHeadline, selector: "md3-type-headline, [md3-type-headline]", inputs: ["size", "color", "emphasized"] }] });
6709
- }
6710
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: DialogHeader, decorators: [{
6711
- type: Component,
6712
- args: [{ selector: 'md3-dialog-header', imports: [
6713
- TypeHeadline
6714
- ], template: "<ng-content select=\"[md3-icon-element]\"></ng-content>\n<div class=\"md3-dialog-title\">\n <md3-type-headline size=\"small\">\n <ng-content></ng-content>\n </md3-type-headline>\n</div>", styles: [":host{display:block;margin:1.5em 1.5em 1em;text-align:center}:host .md3-dialog-title{text-align:start;color:rgb(var(--md-scheme-on-surface))}:host ::ng-deep [md3-icon-element]{color:rgb(var(--md-scheme-secondary))}:host ::ng-deep [md3-icon-element]+.md3-dialog-title{margin-top:1em;text-align:center}\n"] }]
6715
- }] });
6716
-
6717
- class DialogBody {
6718
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: DialogBody, deps: [], target: i0.ɵɵFactoryTarget.Component });
6719
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.0", type: DialogBody, isStandalone: true, selector: "md3-dialog-body", host: { classAttribute: "md3-scrollable" }, ngImport: i0, template: "<ng-content></ng-content>", styles: [":host{display:block;margin:1em 1.5em 1.5em;color:rgb(var(--md-scheme-on-surface-variant))}:host .md3-text-body.md3-text-medium{letter-spacing:.017857em}\n"] });
6720
- }
6721
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: DialogBody, decorators: [{
6722
- type: Component,
6723
- args: [{ selector: 'md3-dialog-body', imports: [], host: {
6724
- 'class': 'md3-scrollable',
6725
- }, template: "<ng-content></ng-content>", styles: [":host{display:block;margin:1em 1.5em 1.5em;color:rgb(var(--md-scheme-on-surface-variant))}:host .md3-text-body.md3-text-medium{letter-spacing:.017857em}\n"] }]
6726
- }] });
6727
-
6728
- class DialogActions {
6729
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: DialogActions, deps: [], target: i0.ɵɵFactoryTarget.Component });
6730
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.0", type: DialogActions, isStandalone: true, selector: "md3-dialog-actions", ngImport: i0, template: "<div class=\"md3-actions\">\n <ng-content></ng-content>\n</div>", styles: [":host{display:block;margin:1.5em}:host .md3-actions{display:flex;gap:1em;flex-direction:row-reverse}\n"] });
6731
- }
6732
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: DialogActions, decorators: [{
6733
- type: Component,
6734
- args: [{ selector: 'md3-dialog-actions', imports: [], template: "<div class=\"md3-actions\">\n <ng-content></ng-content>\n</div>", styles: [":host{display:block;margin:1.5em}:host .md3-actions{display:flex;gap:1em;flex-direction:row-reverse}\n"] }]
6735
- }] });
6736
-
6737
- const MENU_DATA = new InjectionToken('MD3_MENU_DATA');
6738
- const MENU_CONFIG = new InjectionToken('MD3_MENU_CONFIG');
6739
- const MENU_COMPONENT = new InjectionToken('MD3_MENU_COMPONENT');
6740
- const MENU_EXIT_ANIMATION_FALLBACK_MS = 300;
6741
- class MenuRef {
6742
- overlayRef;
6743
- previouslyFocusedElement;
6744
- closed = new Subject();
6745
- isClosed = false;
6746
- menuInstance;
6736
+ class SelectField {
6737
+ menuService;
6738
+ viewContainerRef;
6747
6739
  /**
6748
- * Filled by MenuService after the user component is attached. Keeping the
6749
- * instance here lets callers imperatively update inputs when that is useful.
6740
+ * Form directive placed on the host element: [formControl], formControlName or [(ngModel)].
6741
+ * The accessor is registered from the constructor instead of through an NG_VALUE_ACCESSOR
6742
+ * provider, which keeps the directive injectable here and gives access to the control
6743
+ * itself for the error state forwarded to md3-text-field.
6750
6744
  */
6751
- componentInstance;
6752
- constructor(overlayRef, previouslyFocusedElement) {
6753
- this.overlayRef = overlayRef;
6754
- this.previouslyFocusedElement = previouslyFocusedElement;
6755
- }
6756
- close(result) {
6757
- if (this.isClosed) {
6758
- return;
6745
+ ngControl = inject(NgControl, { self: true, optional: true });
6746
+ options = input.required(/* @ts-ignore */
6747
+ ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
6748
+ multiple = input(false, { ...(ngDevMode ? { debugName: "multiple" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
6749
+ /**
6750
+ * Lets the user type in the field to filter the options. While the field has focus it
6751
+ * acts as a search box; the selected options are shown again as soon as it loses focus.
6752
+ */
6753
+ searchable = input(false, { ...(ngDevMode ? { debugName: "searchable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
6754
+ label = input(null, { ...(ngDevMode ? { debugName: "label" } : /* istanbul ignore next */ {}), alias: 'label' });
6755
+ /**
6756
+ * Formats the text shown in the field. The default joins the labels of the selected
6757
+ * options with a comma, so the field shows as many labels as its width allows.
6758
+ * The formatter is not called for an empty selection: the field stays empty, which keeps
6759
+ * the floating label in its place.
6760
+ */
6761
+ displayWith = input(null, { ...(ngDevMode ? { debugName: "displayWith" } : /* istanbul ignore next */ {}), alias: 'display-with' });
6762
+ /**
6763
+ * Disables the field outside of a form. With reactive forms prefer control.disable().
6764
+ */
6765
+ disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
6766
+ menuColors = input('standard', { ...(ngDevMode ? { debugName: "menuColors" } : /* istanbul ignore next */ {}), alias: 'menu-colors' });
6767
+ fieldType = input('filled', { ...(ngDevMode ? { debugName: "fieldType" } : /* istanbul ignore next */ {}), alias: 'field-type' });
6768
+ /**
6769
+ * Control passed explicitly, mirroring md3-text-field. Takes precedence over a form
6770
+ * directive on the host element.
6771
+ */
6772
+ control = input(undefined, { ...(ngDevMode ? { debugName: "control" } : /* istanbul ignore next */ {}), alias: 'control' });
6773
+ iconElements = contentChildren(IconElement, { ...(ngDevMode ? { debugName: "iconElements" } : /* istanbul ignore next */ {}), descendants: true });
6774
+ hasLeadingIcon = computed(() => this.iconElements().some(i => i.iconType() === 'leading') ?? false, /* @ts-ignore */
6775
+ ...(ngDevMode ? [{ debugName: "hasLeadingIcon" }] : /* istanbul ignore next */ []));
6776
+ hasArrowIcon = computed(() => this.iconElements().some(i => i.iconType() === 'arrow') ?? false, /* @ts-ignore */
6777
+ ...(ngDevMode ? [{ debugName: "hasArrowIcon" }] : /* istanbul ignore next */ []));
6778
+ isMenuOpen = signal(false, /* @ts-ignore */
6779
+ ...(ngDevMode ? [{ debugName: "isMenuOpen" }] : /* istanbul ignore next */ []));
6780
+ menuRef = null;
6781
+ inputElement = viewChild(InputElement, { ...(ngDevMode ? { debugName: "inputElement" } : /* istanbul ignore next */ {}), read: ElementRef });
6782
+ /** Control resolved from the host form directive, captured when the accessor is wired up. */
6783
+ hostControl = signal(undefined, /* @ts-ignore */
6784
+ ...(ngDevMode ? [{ debugName: "hostControl" }] : /* istanbul ignore next */ []));
6785
+ /** Disabled state pushed by the control, either through setDisabledState or control.disabled. */
6786
+ controlDisabled = signal(false, /* @ts-ignore */
6787
+ ...(ngDevMode ? [{ debugName: "controlDisabled" }] : /* istanbul ignore next */ []));
6788
+ /**
6789
+ * Set as soon as a control writes a value. From then on the control owns the selection and
6790
+ * SelectOption.selected no longer seeds it.
6791
+ */
6792
+ controlOwnsValue = false;
6793
+ onChange = () => { };
6794
+ onTouched = () => { };
6795
+ /** Selection is kept by value so it survives options arriving or being replaced later. */
6796
+ selectedValues = signal([], /* @ts-ignore */
6797
+ ...(ngDevMode ? [{ debugName: "selectedValues" }] : /* istanbul ignore next */ []));
6798
+ searchQuery = signal('', /* @ts-ignore */
6799
+ ...(ngDevMode ? [{ debugName: "searchQuery" }] : /* istanbul ignore next */ []));
6800
+ isInputFocused = signal(false, /* @ts-ignore */
6801
+ ...(ngDevMode ? [{ debugName: "isInputFocused" }] : /* istanbul ignore next */ []));
6802
+ formControl = computed(() => this.control() ?? this.hostControl(), /* @ts-ignore */
6803
+ ...(ngDevMode ? [{ debugName: "formControl" }] : /* istanbul ignore next */ []));
6804
+ isDisabled = computed(() => this.disabled() || this.controlDisabled(), /* @ts-ignore */
6805
+ ...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
6806
+ selectedOptions = computed(() => {
6807
+ const values = this.selectedValues();
6808
+ return this.options().filter((option) => values.includes(option.value));
6809
+ }, /* @ts-ignore */
6810
+ ...(ngDevMode ? [{ debugName: "selectedOptions" }] : /* istanbul ignore next */ []));
6811
+ selectedText = computed(() => {
6812
+ const selected = this.selectedOptions();
6813
+ if (selected.length == 0) {
6814
+ return null;
6759
6815
  }
6760
- this.isClosed = true;
6761
- this.startCloseAnimation().then(() => {
6762
- // Disposing the overlay removes the wrapper, the backdrop, and the
6763
- // dynamically attached content component in one operation.
6764
- this.overlayRef.dispose();
6765
- this.previouslyFocusedElement?.focus();
6766
- this.closed.next(result);
6767
- this.closed.complete();
6768
- });
6769
- }
6770
- startCloseAnimation() {
6771
- let panel = this.overlayRef.overlayElement;
6772
- let animatedElement = panel.querySelector('.md3-menu-container') ?? panel;
6773
- panel.classList.add('md3-menu-closing');
6774
- this.menuInstance?.showMenu(false);
6775
- return new Promise((resolve) => {
6776
- let isResolved = false;
6777
- let timeoutId = setTimeout(done, MENU_EXIT_ANIMATION_FALLBACK_MS);
6778
- function done() {
6779
- if (isResolved) {
6816
+ const format = this.displayWith();
6817
+ if (format) {
6818
+ return format(selected);
6819
+ }
6820
+ return selected.map((option) => option.label).join(', ');
6821
+ }, /* @ts-ignore */
6822
+ ...(ngDevMode ? [{ debugName: "selectedText" }] : /* istanbul ignore next */ []));
6823
+ /** True while the field acts as a search box instead of showing the selection. */
6824
+ isSearching = computed(() => this.searchable() && !this.isDisabled() && this.isInputFocused(), /* @ts-ignore */
6825
+ ...(ngDevMode ? [{ debugName: "isSearching" }] : /* istanbul ignore next */ []));
6826
+ /** What the input element shows: the search query while searching, the selection otherwise. */
6827
+ displayText = computed(() => this.isSearching() ? this.searchQuery() : this.selectedText(), /* @ts-ignore */
6828
+ ...(ngDevMode ? [{ debugName: "displayText" }] : /* istanbul ignore next */ []));
6829
+ /**
6830
+ * Each option paired with the text the search runs against. It is rebuilt only when the
6831
+ * options array changes, so typing does not normalize the same text again and again.
6832
+ * The fields are joined with a character the query can never contain, which stops a query
6833
+ * from matching across two fields.
6834
+ */
6835
+ searchIndex = computed(() => {
6836
+ return this.options().map((option) => ({
6837
+ option,
6838
+ text: [option.label, String(option.value), option.supportingText, option.trailingText]
6839
+ .filter((field) => !!field)
6840
+ .map((field) => this.normalizeSearchText(field))
6841
+ .join('\u0000'),
6842
+ }));
6843
+ }, /* @ts-ignore */
6844
+ ...(ngDevMode ? [{ debugName: "searchIndex" }] : /* istanbul ignore next */ []));
6845
+ /** The options the menu shows. Options that do not match the query are hidden. */
6846
+ filteredOptions = computed(() => {
6847
+ const query = this.normalizeSearchText(this.searchQuery());
6848
+ if (!this.searchable() || query.length == 0) {
6849
+ return this.options();
6850
+ }
6851
+ return this.searchIndex()
6852
+ .filter((entry) => entry.text.includes(query))
6853
+ .map((entry) => entry.option);
6854
+ }, /* @ts-ignore */
6855
+ ...(ngDevMode ? [{ debugName: "filteredOptions" }] : /* istanbul ignore next */ []));
6856
+ constructor(menuService, viewContainerRef) {
6857
+ this.menuService = menuService;
6858
+ this.viewContainerRef = viewContainerRef;
6859
+ if (this.ngControl) {
6860
+ this.ngControl.valueAccessor = this;
6861
+ }
6862
+ // Seeds the selection from SelectOption.selected while no control owns the value.
6863
+ effect(() => {
6864
+ const options = this.options();
6865
+ untracked(() => {
6866
+ if (this.controlOwnsValue) {
6780
6867
  return;
6781
6868
  }
6782
- isResolved = true;
6783
- clearTimeout(timeoutId);
6784
- animatedElement.removeEventListener('transitionend', onTransitionEnd);
6785
- resolve();
6786
- }
6787
- function onTransitionEnd(event) {
6788
- if (event.target === animatedElement) {
6789
- done();
6869
+ let selected = options.filter((option) => option.selected);
6870
+ if (!this.multiple() && selected.length > 1) {
6871
+ selected = [selected[0]];
6790
6872
  }
6873
+ this.setSelectedValues(selected.map((option) => option.value));
6874
+ });
6875
+ });
6876
+ // Mirrors the selection back onto SelectOption.selected so the menu renders it.
6877
+ effect(() => {
6878
+ const values = this.selectedValues();
6879
+ for (const option of this.options()) {
6880
+ option.selected = values.includes(option.value);
6791
6881
  }
6792
- animatedElement.addEventListener('transitionend', onTransitionEnd);
6882
+ });
6883
+ // [control] is bound manually; [formControl]/formControlName go through the accessor API.
6884
+ effect((onCleanup) => {
6885
+ const control = this.control();
6886
+ if (!control) {
6887
+ return;
6888
+ }
6889
+ this.syncFromControl(control);
6890
+ const controlEvents = control.events.subscribe(() => this.syncFromControl(control));
6891
+ onCleanup(() => controlEvents.unsubscribe());
6892
+ });
6893
+ effect(() => {
6894
+ const isOpen = this.isMenuOpen();
6895
+ untracked(() => {
6896
+ if (isOpen) {
6897
+ this.openSelectOptions();
6898
+ return;
6899
+ }
6900
+ this.menuRef?.close();
6901
+ this.menuRef = null;
6902
+ this.searchQuery.set('');
6903
+ });
6904
+ });
6905
+ // Keeps an open menu in step with the query. The menu binds its inputs once, at open.
6906
+ effect(() => {
6907
+ const filtered = this.filteredOptions();
6908
+ untracked(() => this.menuRef?.componentInstance?.visibleOptions.set(filtered));
6793
6909
  });
6794
6910
  }
6795
- afterClosed() {
6796
- return this.closed.asObservable();
6911
+ // Search
6912
+ onSearchFocus() {
6913
+ this.isInputFocused.set(true);
6914
+ }
6915
+ onSearchBlur() {
6916
+ this.isInputFocused.set(false);
6917
+ this.searchQuery.set('');
6918
+ }
6919
+ onSearchInput(event) {
6920
+ if (!this.searchable()) {
6921
+ return;
6922
+ }
6923
+ this.searchQuery.set(event.target.value);
6924
+ this.openMenu();
6925
+ }
6926
+ /** Lower-cases the text and removes everything that is not a letter or a digit. */
6927
+ normalizeSearchText(text) {
6928
+ return text.toLowerCase().replace(/[^\p{L}\p{N}]+/gu, '');
6929
+ }
6930
+ // ControlValueAccessor
6931
+ writeValue(value) {
6932
+ this.captureHostControl();
6933
+ this.controlOwnsValue = true;
6934
+ this.setSelectedValues(this.normalizeValues(value));
6935
+ }
6936
+ registerOnChange(fn) {
6937
+ this.captureHostControl();
6938
+ this.onChange = fn;
6939
+ }
6940
+ registerOnTouched(fn) {
6941
+ this.onTouched = fn;
6942
+ }
6943
+ setDisabledState(isDisabled) {
6944
+ this.controlDisabled.set(isDisabled);
6945
+ }
6946
+ // Menu
6947
+ openMenu() {
6948
+ if (this.isDisabled()) {
6949
+ return;
6950
+ }
6951
+ this.isMenuOpen.set(true);
6952
+ }
6953
+ closeMenu() {
6954
+ this.isMenuOpen.set(false);
6955
+ }
6956
+ toggleMenu() {
6957
+ if (this.isDisabled()) {
6958
+ return;
6959
+ }
6960
+ this.isMenuOpen.update(current => !current);
6961
+ }
6962
+ openSelectOptions() {
6963
+ this.menuRef = this.menuService.open(SelectOptions, {
6964
+ data: {
6965
+ options: this.options(),
6966
+ multiple: this.multiple(),
6967
+ },
6968
+ bindDataToInputs: true,
6969
+ menuColors: this.menuColors(),
6970
+ origin: this.inputElement(),
6971
+ xPosition: 'start',
6972
+ yPosition: 'below',
6973
+ overlapTrigger: false,
6974
+ // scrollStrategy: 'close',
6975
+ viewContainerRef: this.viewContainerRef,
6976
+ });
6977
+ this.menuRef.componentInstance?.visibleOptions.set(this.filteredOptions());
6978
+ this.menuRef.componentInstance?.selectionChange.subscribe((values) => this.selectValues(values));
6979
+ this.menuRef.afterClosed().subscribe(() => {
6980
+ this.isMenuOpen.set(false);
6981
+ this.markAsTouched();
6982
+ });
6983
+ }
6984
+ // Selection
6985
+ selectValues(values) {
6986
+ this.setSelectedValues(this.normalizeValues(values));
6987
+ this.commitValue();
6988
+ }
6989
+ setSelectedValues(values) {
6990
+ const current = untracked(this.selectedValues);
6991
+ if (current.length === values.length && current.every((value, index) => value === values[index])) {
6992
+ return;
6993
+ }
6994
+ this.selectedValues.set(values);
6995
+ }
6996
+ commitValue() {
6997
+ const value = this.currentValue();
6998
+ // No-op unless a form directive registered an accessor callback.
6999
+ this.onChange(value);
7000
+ const control = this.control();
7001
+ if (control && !this.valuesAreEqual(control.value, value)) {
7002
+ control.markAsDirty();
7003
+ control.setValue(value);
7004
+ }
7005
+ }
7006
+ currentValue() {
7007
+ const values = untracked(this.selectedValues);
7008
+ if (this.multiple()) {
7009
+ return [...values];
7010
+ }
7011
+ return values.length > 0 ? values[0] : null;
7012
+ }
7013
+ markAsTouched() {
7014
+ this.onTouched();
7015
+ const control = this.control();
7016
+ if (control && !control.touched) {
7017
+ control.markAsTouched();
7018
+ }
7019
+ }
7020
+ syncFromControl(control) {
7021
+ this.controlOwnsValue = true;
7022
+ this.setSelectedValues(this.normalizeValues(control.value));
7023
+ this.controlDisabled.set(control.disabled);
7024
+ }
7025
+ captureHostControl() {
7026
+ const control = this.ngControl?.control ?? undefined;
7027
+ if (control && untracked(this.hostControl) !== control) {
7028
+ this.hostControl.set(control);
7029
+ }
7030
+ }
7031
+ /**
7032
+ * Accepts whatever a control holds and reduces it to the option values this field can show:
7033
+ * a single value for single selection, a de-duplicated list for multiple selection.
7034
+ */
7035
+ normalizeValues(value) {
7036
+ const values = (Array.isArray(value) ? value : [value])
7037
+ .filter((item) => typeof item === 'string' || typeof item === 'number');
7038
+ const unique = [...new Set(values)];
7039
+ return this.multiple() ? unique : unique.slice(0, 1);
7040
+ }
7041
+ valuesAreEqual(current, next) {
7042
+ if (Array.isArray(current) && Array.isArray(next)) {
7043
+ return current.length === next.length && current.every((value, index) => value === next[index]);
7044
+ }
7045
+ return current === next;
6797
7046
  }
7047
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: SelectField, deps: [{ token: MenuService }, { token: i0.ViewContainerRef }], target: i0.ɵɵFactoryTarget.Component });
7048
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: SelectField, isStandalone: true, selector: "md3-select-field", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, displayWith: { classPropertyName: "displayWith", publicName: "display-with", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, menuColors: { classPropertyName: "menuColors", publicName: "menu-colors", isSignal: true, isRequired: false, transformFunction: null }, fieldType: { classPropertyName: "fieldType", publicName: "field-type", isSignal: true, isRequired: false, transformFunction: null }, control: { classPropertyName: "control", publicName: "control", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "iconElements", predicate: IconElement, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "inputElement", first: true, predicate: InputElement, descendants: true, read: ElementRef, isSignal: true }], ngImport: i0, template: "<md3-text-field [label]=\"label()\" [field-type]=\"fieldType()\" [control]=\"formControl()\">\n <!-- <md3-icon md3-icon-element=\"leading\">search</md3-icon> -->\n @if (hasLeadingIcon()) {\n <div md3-icon-element=\"leading\">\n <ng-content select=\"[md3-icon-element=leading]\"></ng-content>\n </div>\n }\n\n <input md3-input-element type=\"text\" [readonly]=\"!searchable()\" [disabled]=\"isDisabled()\" (click)=\"openMenu()\"\n (focus)=\"onSearchFocus()\" (blur)=\"onSearchBlur()\" (input)=\"onSearchInput($event)\"\n class=\"md3-select-input\" [value]=\"displayText()\">\n\n <button md3-icon-button button-type=\"standard\" [disabled]=\"isDisabled()\" (click)=\"toggleMenu()\" class=\"md3-select-arrow\">\n @if (hasArrowIcon()) {\n <ng-content select=\"[md3-icon-element=arrow]\"></ng-content>\n } @else {\n <md3-icon md3-icon-element>arrow_drop_down</md3-icon>\n }\n </button>\n\n <ng-content select=\"[md3-supporting-text]\" ngProjectAs=\"[md3-supporting-text]\"></ng-content>\n</md3-text-field>\n", styles: [".md3-select-input{text-overflow:ellipsis}.md3-select-input:read-only{cursor:default;-webkit-user-select:none;user-select:none}.md3-select-input~.md3-select-arrow{transition:transform var(--md-motion-standard-fast-spatial-duration) var(--md-motion-standard-fast-spatial-easing)}.md3-select-input.md3-menu-active:dir(ltr)~.md3-select-arrow{transform:rotate(180deg)}.md3-select-input.md3-menu-active:dir(rtl)~.md3-select-arrow{transform:rotate(-180deg)}\n"], dependencies: [{ kind: "component", type: TextField, selector: "md3-text-field", inputs: ["label", "field-type", "input-counter", "control"] }, { kind: "directive", type: IconElement, selector: "[md3-icon-element]", inputs: ["md3-icon-element"] }, { kind: "component", type: IconButton, selector: "button[md3-icon-button], a[md3-icon-button]", inputs: ["button-size", "button-type", "button-width", "button-squared", "selected"], outputs: ["selectedChange"] }, { kind: "directive", type: InputElement, selector: "[md3-input-element]" }, { kind: "component", type: MaterialIcon, selector: "md3-icon", inputs: ["icon-version", "filled", "icon-style", "icon-size", "bi-directional"] }] });
6798
7049
  }
7050
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: SelectField, decorators: [{
7051
+ type: Component,
7052
+ args: [{ selector: 'md3-select-field', imports: [
7053
+ TextField,
7054
+ IconElement,
7055
+ IconButton,
7056
+ InputElement,
7057
+ MaterialIcon,
7058
+ ], template: "<md3-text-field [label]=\"label()\" [field-type]=\"fieldType()\" [control]=\"formControl()\">\n <!-- <md3-icon md3-icon-element=\"leading\">search</md3-icon> -->\n @if (hasLeadingIcon()) {\n <div md3-icon-element=\"leading\">\n <ng-content select=\"[md3-icon-element=leading]\"></ng-content>\n </div>\n }\n\n <input md3-input-element type=\"text\" [readonly]=\"!searchable()\" [disabled]=\"isDisabled()\" (click)=\"openMenu()\"\n (focus)=\"onSearchFocus()\" (blur)=\"onSearchBlur()\" (input)=\"onSearchInput($event)\"\n class=\"md3-select-input\" [value]=\"displayText()\">\n\n <button md3-icon-button button-type=\"standard\" [disabled]=\"isDisabled()\" (click)=\"toggleMenu()\" class=\"md3-select-arrow\">\n @if (hasArrowIcon()) {\n <ng-content select=\"[md3-icon-element=arrow]\"></ng-content>\n } @else {\n <md3-icon md3-icon-element>arrow_drop_down</md3-icon>\n }\n </button>\n\n <ng-content select=\"[md3-supporting-text]\" ngProjectAs=\"[md3-supporting-text]\"></ng-content>\n</md3-text-field>\n", styles: [".md3-select-input{text-overflow:ellipsis}.md3-select-input:read-only{cursor:default;-webkit-user-select:none;user-select:none}.md3-select-input~.md3-select-arrow{transition:transform var(--md-motion-standard-fast-spatial-duration) var(--md-motion-standard-fast-spatial-easing)}.md3-select-input.md3-menu-active:dir(ltr)~.md3-select-arrow{transform:rotate(180deg)}.md3-select-input.md3-menu-active:dir(rtl)~.md3-select-arrow{transform:rotate(-180deg)}\n"] }]
7059
+ }], ctorParameters: () => [{ type: MenuService }, { type: i0.ViewContainerRef }], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: true }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], displayWith: [{ type: i0.Input, args: [{ isSignal: true, alias: "display-with", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], menuColors: [{ type: i0.Input, args: [{ isSignal: true, alias: "menu-colors", required: false }] }], fieldType: [{ type: i0.Input, args: [{ isSignal: true, alias: "field-type", required: false }] }], control: [{ type: i0.Input, args: [{ isSignal: true, alias: "control", required: false }] }], iconElements: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => IconElement), { ...{ descendants: true }, isSignal: true }] }], inputElement: [{ type: i0.ViewChild, args: [i0.forwardRef(() => InputElement), { ...{ read: ElementRef }, isSignal: true }] }] } });
6799
7060
 
6800
- class Menu {
6801
- el;
6802
- portalOutlet = viewChild(CdkPortalOutlet, /* @ts-ignore */
6803
- ...(ngDevMode ? [{ debugName: "portalOutlet" }] : /* istanbul ignore next */ []));
6804
- config = inject(MENU_CONFIG, { optional: true }) ?? {};
6805
- menuColors = signal('standard', /* @ts-ignore */
6806
- ...(ngDevMode ? [{ debugName: "menuColors" }] : /* istanbul ignore next */ []));
6807
- isVisible = signal(false, /* @ts-ignore */
6808
- ...(ngDevMode ? [{ debugName: "isVisible" }] : /* istanbul ignore next */ []));
6809
- scheme = signal('inherit', /* @ts-ignore */
6810
- ...(ngDevMode ? [{ debugName: "scheme" }] : /* istanbul ignore next */ []));
6811
- direction = signal(null, /* @ts-ignore */
6812
- ...(ngDevMode ? [{ debugName: "direction" }] : /* istanbul ignore next */ []));
6813
- isActive = signal(true, /* @ts-ignore */
7061
+ /**
7062
+ * Material 3 dialog shell. It extends the CDK dialog container, so focus
7063
+ * trapping, focus restoration and the ARIA attributes are handled by the CDK
7064
+ * while this component only owns the MD3 surface and its animation.
7065
+ */
7066
+ class Dialog extends CdkDialogContainer {
7067
+ surface = viewChild('surface', /* @ts-ignore */
7068
+ ...(ngDevMode ? [{ debugName: "surface" }] : /* istanbul ignore next */ []));
7069
+ isActive = signal(false, /* @ts-ignore */
6814
7070
  ...(ngDevMode ? [{ debugName: "isActive" }] : /* istanbul ignore next */ []));
6815
- get element() {
6816
- return this.el.nativeElement;
6817
- }
6818
- constructor(el) {
6819
- this.el = el;
6820
- effect((onCleanup) => {
6821
- const color = 'md3-' + this.menuColors();
6822
- this.element.classList.add(color);
6823
- onCleanup(() => {
6824
- this.element.classList.remove(color);
6825
- });
6826
- });
6827
- this.menuColors.set(this.config.menuColors ?? 'standard');
6828
- this.scheme.set(this.config.scheme ?? 'inherit');
6829
- this.direction.set(this.config.direction ?? null);
7071
+ config = inject(DIALOG_CONFIG, { optional: true }) ?? {};
7072
+ get surfaceElement() {
7073
+ return this.surface()?.nativeElement ?? null;
6830
7074
  }
6831
- ngAfterContentInit() {
7075
+ /**
7076
+ * Started by DialogService, either right after the dialog is created or
7077
+ * once the dialog it replaces has had its head start. The animation runs on
7078
+ * the next frame so the surface has a real from/to state.
7079
+ */
7080
+ startEnterAnimation() {
6832
7081
  requestAnimationFrame(() => {
6833
- this.showMenu(true);
7082
+ this.isActive.set(true);
6834
7083
  });
6835
7084
  }
6836
- showMenu(value) {
6837
- this.isVisible.set(value);
7085
+ setActive(value) {
7086
+ this.isActive.set(value);
6838
7087
  }
6839
- /**
6840
- * The service creates this wrapper first, then calls this method to mount
6841
- * the user supplied component into the dialog container.
6842
- */
6843
- attachContent(component, injector) {
6844
- const portal = new ComponentPortal(component, null, injector);
6845
- return this.portalOutlet().attachComponentPortal(portal);
7088
+ recaptureFocus() {
7089
+ this._recaptureFocus();
6846
7090
  }
6847
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Menu, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
6848
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.1.0", type: Menu, isStandalone: true, selector: "md3-menu", host: { attributes: { "role": "menu" }, properties: { "class.md3-visible": "isVisible()", "class.md3-inactive": "!isActive()", "class.md-scheme-dark": "scheme() == \"dark\"", "class.md-scheme-light": "scheme() == \"light\"", "dir": "direction()" }, classAttribute: "md3-menu-container" }, viewQueries: [{ propertyName: "portalOutlet", first: true, predicate: CdkPortalOutlet, descendants: true, isSignal: true }], ngImport: i0, template: "<ng-template cdkPortalOutlet></ng-template>", styles: ["::ng-deep .cdk-overlay-container{position:fixed;z-index:1000;inset:0;pointer-events:none;font-size:1rem}::ng-deep .cdk-overlay-container .md3-menu-scrim{position:absolute;inset:0;pointer-events:auto}::ng-deep .cdk-overlay-container .cdk-overlay-connected-position-bounding-box{pointer-events:none}::ng-deep .cdk-overlay-container .md3-menu-panel{pointer-events:auto}::ng-deep .cdk-overlay-container .md3-menu-panel.md3-menu-closing{pointer-events:none}::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper{position:absolute;display:flex;inset:0;pointer-events:none;padding:3.5rem 0;overflow:hidden}::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper .md3-menu-panel{position:absolute;pointer-events:auto}::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper .md3-menu-panel.md3-menu-closing{pointer-events:none}:host{--background-color: rgb(var(--md-scheme-surface-container-low));--foreground-color: rgb(var(--md-scheme-on-surface));--border-radius: var(--md-border-radius-large);--menu-min-width: 7em;--menu-max-width: 17.5em;--menu-group-radius: var(--md-border-radius-small);--menu-group-shadow: var(--md-shadow-3dp);--menu-group-label-color: rgb(var(--md-scheme-on-surface-variant));--menu-item-label-color: rgb(var(--md-scheme-on-surface));--menu-item-supporting-color: rgb(var(--md-scheme-on-surface-variant));--menu-item-selected-background: rgb(var(--md-scheme-tertiary-container));--menu-item-selected-foreground: rgb(var(--md-scheme-on-tertiary-container));--menu-item-state-color: var(--md-scheme-on-surface);--menu-item-border-radius: var(--md-border-radius-xsmall);--menu-item-outline-color: rgb(var(--md-scheme-secondary));--menu-item-outline-width: .1875em;--menu-item-outline-offset: -.1875em;--menu-item-badge-background: rgb(var(--md-scheme-primary-container));--menu-item-badge-foreground: rgb(var(--md-scheme-on-primary-container));position:relative;display:block;font-size:1rem;min-width:var(--menu-min-width);max-width:min(var(--menu-max-width),100vw - 2rem);max-height:min(32rem,100vh - 2rem);outline:none;transition:all var(--md-motion-expressive-default-spatial-duration) var(--md-motion-expressive-default-spatial-easing);opacity:0;transform:scaleY(.8)}:host.md3-visible{opacity:1;transform:scaleY(1)}:host.md3-vibrant{--background-color: rgb(var(--md-scheme-tertiary-container));--foreground-color: rgb(var(--md-scheme-on-tertiary-container));--menu-group-label-color: rgb(var(--md-scheme-on-tertiary-container));--menu-item-label-color: rgb(var(--md-scheme-on-tertiary-container));--menu-item-supporting-color: rgb(var(--md-scheme-on-tertiary-container));--menu-item-selected-background: rgb(var(--md-scheme-tertiary));--menu-item-selected-foreground: rgb(var(--md-scheme-on-tertiary));--menu-item-state-color: var(--md-scheme-on-tertiary-container)}:host.md3-inactive{--border-radius: var(--md-border-radius-small)}:host ::ng-deep md3-menu-group{margin-top:.125em}:host ::ng-deep md3-menu-group:first-child{margin-top:0}\n"], dependencies: [{ kind: "directive", type: CdkPortalOutlet, selector: "[cdkPortalOutlet]", inputs: ["cdkPortalOutlet"], outputs: ["attached"], exportAs: ["cdkPortalOutlet"] }] });
7091
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Dialog, deps: null, target: i0.ɵɵFactoryTarget.Component });
7092
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.1.0", type: Dialog, isStandalone: true, selector: "md3-dialog", viewQueries: [{ propertyName: "surface", first: true, predicate: ["surface"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<!-- The role and aria attributes live on the host element, applied by the CDK dialog container. -->\n<div\n #surface\n class=\"md3-dialog-container\"\n [class.md3-active]=\"isActive()\"\n [class.md-scheme-dark]=\"config.scheme == 'dark'\"\n [class.md-scheme-light]=\"config.scheme == 'light'\"\n [attr.dir]=\"config.direction ?? null\">\n <ng-template cdkPortalOutlet></ng-template>\n</div>\n", styles: ["::ng-deep .cdk-overlay-container{position:fixed;z-index:1000;inset:0;pointer-events:none;font-size:1rem}::ng-deep .cdk-overlay-container .md3-dialog-scrim{position:absolute;inset:0;pointer-events:auto;background-color:rgb(var(--md-scheme-scrim));transition:all var(--md-motion-expressive-slow-spatial-duration) var(--md-motion-expressive-slow-spatial-easing);opacity:.32}::ng-deep .cdk-overlay-container .md3-dialog-scrim.md3-dialog-opening{opacity:0}::ng-deep .cdk-overlay-container .md3-dialog-scrim.md3-dialog-closing{opacity:0}::ng-deep .cdk-overlay-container .md3-dialog-scrim.md3-dialog-hidden{opacity:0;pointer-events:none}::ng-deep .cdk-overlay-container .md3-dialog-scrim.md3-dialog-scrim-kept{opacity:.32;transition:none}::ng-deep .cdk-overlay-container .md3-dialog-scrim.md3-dialog-scrim-instant{transition:none}::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper{position:absolute;display:flex;inset:0;pointer-events:none;padding:3.5rem 1.5rem;overflow:hidden}::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper .md3-dialog-panel{position:absolute;pointer-events:auto;max-width:35em}::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper .md3-dialog-panel.md3-dialog-closing,::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper .md3-dialog-panel.md3-dialog-hidden{pointer-events:none}:host{display:block;max-width:100%;min-width:17.5em;outline:0}:host .md3-dialog-container{display:block;width:100%;max-height:inherit;overflow:auto;color:rgb(var(--md-scheme-on-surface));background-color:rgb(var(--md-scheme-surface-container-high));border-radius:var(--md-border-radius-xlarge);box-shadow:var(--md-shadow-6dp);transform-origin:center;transition:all var(--md-motion-expressive-default-spatial-duration) var(--md-motion-expressive-default-spatial-easing);opacity:0;transform:scaleY(.8);margin-top:-8rem}:host .md3-dialog-container.md3-active{opacity:1;transform:scaleY(1);margin-top:0}\n"], dependencies: [{ kind: "directive", type: CdkPortalOutlet, selector: "[cdkPortalOutlet]", inputs: ["cdkPortalOutlet"], outputs: ["attached"], exportAs: ["cdkPortalOutlet"] }] });
6849
7093
  }
6850
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Menu, decorators: [{
7094
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: Dialog, decorators: [{
6851
7095
  type: Component,
6852
- args: [{ selector: 'md3-menu', imports: [
7096
+ args: [{ selector: 'md3-dialog', imports: [
6853
7097
  CdkPortalOutlet,
6854
- ], host: {
6855
- 'class': 'md3-menu-container',
6856
- 'role': 'menu',
6857
- '[class.md3-visible]': 'isVisible()',
6858
- '[class.md3-inactive]': '!isActive()',
6859
- '[class.md-scheme-dark]': 'scheme() == "dark"',
6860
- '[class.md-scheme-light]': 'scheme() == "light"',
6861
- '[dir]': 'direction()',
6862
- }, template: "<ng-template cdkPortalOutlet></ng-template>", styles: ["::ng-deep .cdk-overlay-container{position:fixed;z-index:1000;inset:0;pointer-events:none;font-size:1rem}::ng-deep .cdk-overlay-container .md3-menu-scrim{position:absolute;inset:0;pointer-events:auto}::ng-deep .cdk-overlay-container .cdk-overlay-connected-position-bounding-box{pointer-events:none}::ng-deep .cdk-overlay-container .md3-menu-panel{pointer-events:auto}::ng-deep .cdk-overlay-container .md3-menu-panel.md3-menu-closing{pointer-events:none}::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper{position:absolute;display:flex;inset:0;pointer-events:none;padding:3.5rem 0;overflow:hidden}::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper .md3-menu-panel{position:absolute;pointer-events:auto}::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper .md3-menu-panel.md3-menu-closing{pointer-events:none}:host{--background-color: rgb(var(--md-scheme-surface-container-low));--foreground-color: rgb(var(--md-scheme-on-surface));--border-radius: var(--md-border-radius-large);--menu-min-width: 7em;--menu-max-width: 17.5em;--menu-group-radius: var(--md-border-radius-small);--menu-group-shadow: var(--md-shadow-3dp);--menu-group-label-color: rgb(var(--md-scheme-on-surface-variant));--menu-item-label-color: rgb(var(--md-scheme-on-surface));--menu-item-supporting-color: rgb(var(--md-scheme-on-surface-variant));--menu-item-selected-background: rgb(var(--md-scheme-tertiary-container));--menu-item-selected-foreground: rgb(var(--md-scheme-on-tertiary-container));--menu-item-state-color: var(--md-scheme-on-surface);--menu-item-border-radius: var(--md-border-radius-xsmall);--menu-item-outline-color: rgb(var(--md-scheme-secondary));--menu-item-outline-width: .1875em;--menu-item-outline-offset: -.1875em;--menu-item-badge-background: rgb(var(--md-scheme-primary-container));--menu-item-badge-foreground: rgb(var(--md-scheme-on-primary-container));position:relative;display:block;font-size:1rem;min-width:var(--menu-min-width);max-width:min(var(--menu-max-width),100vw - 2rem);max-height:min(32rem,100vh - 2rem);outline:none;transition:all var(--md-motion-expressive-default-spatial-duration) var(--md-motion-expressive-default-spatial-easing);opacity:0;transform:scaleY(.8)}:host.md3-visible{opacity:1;transform:scaleY(1)}:host.md3-vibrant{--background-color: rgb(var(--md-scheme-tertiary-container));--foreground-color: rgb(var(--md-scheme-on-tertiary-container));--menu-group-label-color: rgb(var(--md-scheme-on-tertiary-container));--menu-item-label-color: rgb(var(--md-scheme-on-tertiary-container));--menu-item-supporting-color: rgb(var(--md-scheme-on-tertiary-container));--menu-item-selected-background: rgb(var(--md-scheme-tertiary));--menu-item-selected-foreground: rgb(var(--md-scheme-on-tertiary));--menu-item-state-color: var(--md-scheme-on-tertiary-container)}:host.md3-inactive{--border-radius: var(--md-border-radius-small)}:host ::ng-deep md3-menu-group{margin-top:.125em}:host ::ng-deep md3-menu-group:first-child{margin-top:0}\n"] }]
6863
- }], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { portalOutlet: [{ type: i0.ViewChild, args: [i0.forwardRef(() => CdkPortalOutlet), { isSignal: true }] }] } });
7098
+ ], template: "<!-- The role and aria attributes live on the host element, applied by the CDK dialog container. -->\n<div\n #surface\n class=\"md3-dialog-container\"\n [class.md3-active]=\"isActive()\"\n [class.md-scheme-dark]=\"config.scheme == 'dark'\"\n [class.md-scheme-light]=\"config.scheme == 'light'\"\n [attr.dir]=\"config.direction ?? null\">\n <ng-template cdkPortalOutlet></ng-template>\n</div>\n", styles: ["::ng-deep .cdk-overlay-container{position:fixed;z-index:1000;inset:0;pointer-events:none;font-size:1rem}::ng-deep .cdk-overlay-container .md3-dialog-scrim{position:absolute;inset:0;pointer-events:auto;background-color:rgb(var(--md-scheme-scrim));transition:all var(--md-motion-expressive-slow-spatial-duration) var(--md-motion-expressive-slow-spatial-easing);opacity:.32}::ng-deep .cdk-overlay-container .md3-dialog-scrim.md3-dialog-opening{opacity:0}::ng-deep .cdk-overlay-container .md3-dialog-scrim.md3-dialog-closing{opacity:0}::ng-deep .cdk-overlay-container .md3-dialog-scrim.md3-dialog-hidden{opacity:0;pointer-events:none}::ng-deep .cdk-overlay-container .md3-dialog-scrim.md3-dialog-scrim-kept{opacity:.32;transition:none}::ng-deep .cdk-overlay-container .md3-dialog-scrim.md3-dialog-scrim-instant{transition:none}::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper{position:absolute;display:flex;inset:0;pointer-events:none;padding:3.5rem 1.5rem;overflow:hidden}::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper .md3-dialog-panel{position:absolute;pointer-events:auto;max-width:35em}::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper .md3-dialog-panel.md3-dialog-closing,::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper .md3-dialog-panel.md3-dialog-hidden{pointer-events:none}:host{display:block;max-width:100%;min-width:17.5em;outline:0}:host .md3-dialog-container{display:block;width:100%;max-height:inherit;overflow:auto;color:rgb(var(--md-scheme-on-surface));background-color:rgb(var(--md-scheme-surface-container-high));border-radius:var(--md-border-radius-xlarge);box-shadow:var(--md-shadow-6dp);transform-origin:center;transition:all var(--md-motion-expressive-default-spatial-duration) var(--md-motion-expressive-default-spatial-easing);opacity:0;transform:scaleY(.8);margin-top:-8rem}:host .md3-dialog-container.md3-active{opacity:1;transform:scaleY(1);margin-top:0}\n"] }]
7099
+ }], propDecorators: { surface: [{ type: i0.ViewChild, args: ['surface', { isSignal: true }] }] } });
6864
7100
 
6865
- class MenuService {
6866
- overlay = inject(Overlay);
6867
- injector = inject(Injector);
6868
- document = inject(DOCUMENT);
6869
- open(component, config = {}) {
6870
- const menuConfig = this.mergeConfig(config);
6871
- const previouslyFocusedElement = this.getFocusedElement();
6872
- const triggerElement = this.resolveTriggerElement(menuConfig.origin, previouslyFocusedElement);
6873
- const menuOverlay = this.createOverlay(menuConfig, previouslyFocusedElement);
6874
- const overlayRef = menuOverlay.overlayRef;
6875
- const menuRef = new MenuRef(overlayRef, previouslyFocusedElement);
6876
- const injector = this.createInjector(component, menuConfig, menuRef);
6877
- // The overlay hosts the MD3 menu shell. The shell is responsible for
6878
- // the surface markup and receives the dynamic content through a portal.
6879
- const menuPortal = new ComponentPortal(Menu, menuConfig.viewContainerRef ?? null, injector);
6880
- const menuComponentRef = overlayRef.attach(menuPortal);
6881
- const contentComponentRef = menuComponentRef.instance.attachContent(component, injector);
6882
- this.bindDataToInputs(contentComponentRef, menuConfig);
6883
- this.flipPositionIfPushBreaksAnchor(menuOverlay, menuConfig);
6884
- menuRef.componentInstance = contentComponentRef.instance;
6885
- menuRef.menuInstance = menuComponentRef.instance;
6886
- this.bindTriggerActiveState(triggerElement, overlayRef, menuRef);
6887
- this.startOpenAnimation(overlayRef);
6888
- this.connectCloseEvents(overlayRef, menuRef);
6889
- return menuRef;
6890
- }
6891
- openSubMenu(component, config = {}) {
6892
- const xPosition = this.getSubMenuXPosition(config.xPosition);
6893
- const yPosition = config.yPosition ?? 'below';
6894
- const offsetX = config.offsetX ?? 0;
6895
- const offsetY = config.offsetY ?? 0;
6896
- const subMenuConfig = {
6897
- ...config,
6898
- isSubMenu: true,
6899
- xPosition,
6900
- yPosition,
6901
- overlapTrigger: config.overlapTrigger ?? true,
6902
- offsetX,
6903
- offsetY,
6904
- scheme: config.scheme ?? 'inherit',
6905
- direction: config.direction ?? null,
6906
- };
6907
- return this.open(component, subMenuConfig);
6908
- }
6909
- mergeConfig(config) {
6910
- const viewportMargin = this.getViewportMargin(config.viewportMargin);
6911
- return {
6912
- data: config.data,
6913
- bindDataToInputs: config.bindDataToInputs ?? false,
6914
- isSubMenu: config.isSubMenu ?? false,
6915
- menuColors: config.menuColors ?? 'standard',
6916
- xPosition: config.xPosition ?? 'start',
6917
- yPosition: config.yPosition ?? 'below',
6918
- overlapTrigger: config.overlapTrigger ?? false,
6919
- offsetX: config.offsetX ?? 0,
6920
- offsetY: config.offsetY ?? 4,
6921
- viewportMargin,
6922
- positions: config.positions,
6923
- scrollStrategy: config.scrollStrategy ?? 'reposition',
6924
- scheme: config.scheme ?? 'inherit',
6925
- direction: config.direction ?? null,
6926
- viewContainerRef: config.viewContainerRef,
6927
- injector: config.injector ?? this.injector,
6928
- };
6929
- }
6930
- createOverlay(config, fallbackOrigin) {
6931
- const origin = this.resolveOrigin(config.origin, fallbackOrigin);
6932
- const positionStrategy = this.overlay.position()
6933
- .flexibleConnectedTo(origin)
6934
- .withFlexibleDimensions(false)
6935
- .withGrowAfterOpen(true)
6936
- .withPush(true)
6937
- .withViewportMargin(config.viewportMargin)
6938
- .withPositions(config.positions ?? this.getInitialConnectedPositions(config))
6939
- .withTransformOriginOn('.md3-menu-container');
6940
- const overlayConfig = new OverlayConfig({
6941
- hasBackdrop: true,
6942
- backdropClass: ['md3-menu-scrim', 'md3-menu-opening'],
6943
- panelClass: ['md3-menu-panel', 'md3-menu-opening'],
6944
- positionStrategy,
6945
- scrollStrategy: this.getScrollStrategy(config.scrollStrategy),
6946
- });
6947
- return {
6948
- overlayRef: this.overlay.create(overlayConfig),
6949
- positionStrategy: config.positions ? undefined : positionStrategy,
6950
- origin,
6951
- };
7101
+ /**
7102
+ * Material 3 full screen dialog shell. It covers the viewport and holds the content.
7103
+ *
7104
+ * The content is an `md3-layout`. The layout places the regions — the bar rows for an app bar
7105
+ * or a toolbar, the rail columns, and the panes where side sheets open — so the dialog itself
7106
+ * places nothing but the content. A content root that is not a layout gets a warning in
7107
+ * development mode, and the shell falls back to panes of its own, so a side sheet never opens
7108
+ * behind the dialog where nobody can see it.
7109
+ */
7110
+ class FullScreenDialog extends CdkDialogContainer {
7111
+ surface = viewChild('surface', /* @ts-ignore */
7112
+ ...(ngDevMode ? [{ debugName: "surface" }] : /* istanbul ignore next */ []));
7113
+ mainPane = viewChild('mainPane', /* @ts-ignore */
7114
+ ...(ngDevMode ? [{ debugName: "mainPane" }] : /* istanbul ignore next */ []));
7115
+ startOutlet = viewChild('startOutlet', { ...(ngDevMode ? { debugName: "startOutlet" } : /* istanbul ignore next */ {}), read: CdkPortalOutlet });
7116
+ endOutlet = viewChild('endOutlet', { ...(ngDevMode ? { debugName: "endOutlet" } : /* istanbul ignore next */ {}), read: CdkPortalOutlet });
7117
+ sheets = inject(SheetsService);
7118
+ /** True while the shell holds the side sheet outlets, which happens without a layout only. */
7119
+ ownsSideSheetOutlets = false;
7120
+ isActive = signal(false, /* @ts-ignore */
7121
+ ...(ngDevMode ? [{ debugName: "isActive" }] : /* istanbul ignore next */ []));
7122
+ config = inject(DIALOG_CONFIG, { optional: true }) ?? {};
7123
+ get surfaceElement() {
7124
+ return this.surface()?.nativeElement ?? null;
6952
7125
  }
6953
- bindTriggerActiveState(triggerElement, overlayRef, menuRef) {
6954
- if (!triggerElement) {
7126
+ ngAfterViewInit() {
7127
+ // The content is attached by now, so the layout it brings has already taken the
7128
+ // outlets. Its panes sit under the bar regions, which is where a side sheet belongs.
7129
+ const layout = this.findContentLayout();
7130
+ if (typeof ngDevMode === 'undefined' || ngDevMode) {
7131
+ this.checkContentLayout(layout);
7132
+ }
7133
+ if (layout) {
6955
7134
  return;
6956
7135
  }
6957
- const removeActiveClass = () => triggerElement.classList.remove('md3-menu-active');
6958
- triggerElement.classList.add('md3-menu-active');
6959
- menuRef.afterClosed().pipe(take(1)).subscribe(removeActiveClass);
6960
- overlayRef.detachments().pipe(take(1)).subscribe(removeActiveClass);
7136
+ // No layout, so the shell keeps the dialog self-sufficient. Without this the next side
7137
+ // sheet would open in the outlet underneath — the page scaffold, behind the dialog.
7138
+ const startOutlet = this.startOutlet();
7139
+ const endOutlet = this.endOutlet();
7140
+ if (startOutlet) {
7141
+ this.sheets.registerSideSheetOutlet('start', startOutlet);
7142
+ }
7143
+ if (endOutlet) {
7144
+ this.sheets.registerSideSheetOutlet('end', endOutlet);
7145
+ }
7146
+ this.ownsSideSheetOutlets = !!startOutlet || !!endOutlet;
6961
7147
  }
6962
- resolveOrigin(origin, fallbackOrigin) {
6963
- const resolvedOrigin = origin ?? fallbackOrigin;
6964
- if (resolvedOrigin) {
6965
- if (this.isMouseEvent(resolvedOrigin)) {
6966
- if (resolvedOrigin.currentTarget instanceof HTMLElement) {
6967
- return resolvedOrigin.currentTarget;
6968
- }
6969
- return {
6970
- x: resolvedOrigin.clientX,
6971
- y: resolvedOrigin.clientY,
6972
- };
7148
+ ngOnDestroy() {
7149
+ if (this.ownsSideSheetOutlets) {
7150
+ const startOutlet = this.startOutlet();
7151
+ const endOutlet = this.endOutlet();
7152
+ if (startOutlet) {
7153
+ this.sheets.unregisterSideSheetOutlet('start', startOutlet);
6973
7154
  }
6974
- if (this.isElementRef(resolvedOrigin)) {
6975
- return resolvedOrigin.nativeElement;
7155
+ if (endOutlet) {
7156
+ this.sheets.unregisterSideSheetOutlet('end', endOutlet);
6976
7157
  }
6977
- return resolvedOrigin;
7158
+ this.ownsSideSheetOutlets = false;
6978
7159
  }
6979
- const viewport = this.document.defaultView;
6980
- return {
6981
- x: (viewport?.innerWidth ?? 0) / 2,
6982
- y: (viewport?.innerHeight ?? 0) / 2,
6983
- };
7160
+ super.ngOnDestroy();
6984
7161
  }
6985
- resolveTriggerElement(origin, fallbackOrigin) {
6986
- const resolvedOrigin = origin ?? fallbackOrigin;
6987
- if (!resolvedOrigin) {
7162
+ /**
7163
+ * The layout at the root of the content, if the content brought one.
7164
+ *
7165
+ * The root is either the layout itself, or the component that holds it — a dialog is
7166
+ * normally opened with a component whose own template starts with `md3-layout`.
7167
+ */
7168
+ findContentLayout() {
7169
+ const content = this.mainPane()?.nativeElement.firstElementChild;
7170
+ if (!content) {
6988
7171
  return null;
6989
7172
  }
6990
- if (this.isMouseEvent(resolvedOrigin)) {
6991
- return resolvedOrigin.currentTarget instanceof HTMLElement
6992
- ? resolvedOrigin.currentTarget
6993
- : null;
7173
+ if (content.tagName === 'MD3-LAYOUT') {
7174
+ return content;
6994
7175
  }
6995
- if (this.isElementRef(resolvedOrigin)) {
6996
- return resolvedOrigin.nativeElement;
7176
+ const root = content.firstElementChild;
7177
+ return root?.tagName === 'MD3-LAYOUT' ? root : null;
7178
+ }
7179
+ checkContentLayout(layout) {
7180
+ if (layout) {
7181
+ return;
6997
7182
  }
6998
- return resolvedOrigin instanceof HTMLElement ? resolvedOrigin : null;
7183
+ console.warn('[md3-fullscreen-dialog] the content of a full screen dialog should start with '
7184
+ + 'md3-layout. Without it the dialog has no bar and no rail region, so an app bar or '
7185
+ + 'a toolbar has nowhere to go, and the content does not scroll on its own.');
7186
+ }
7187
+ startEnterAnimation() {
7188
+ requestAnimationFrame(() => {
7189
+ this.isActive.set(true);
7190
+ });
7191
+ }
7192
+ setActive(value) {
7193
+ this.isActive.set(value);
7194
+ }
7195
+ recaptureFocus() {
7196
+ this._recaptureFocus();
7197
+ }
7198
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: FullScreenDialog, deps: null, target: i0.ɵɵFactoryTarget.Component });
7199
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.1.0", type: FullScreenDialog, isStandalone: true, selector: "md3-fullscreen-dialog", viewQueries: [{ propertyName: "surface", first: true, predicate: ["surface"], descendants: true, isSignal: true }, { propertyName: "mainPane", first: true, predicate: ["mainPane"], descendants: true, isSignal: true }, { propertyName: "startOutlet", first: true, predicate: ["startOutlet"], descendants: true, read: CdkPortalOutlet, isSignal: true }, { propertyName: "endOutlet", first: true, predicate: ["endOutlet"], descendants: true, read: CdkPortalOutlet, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<!-- The role and aria attributes live on the host element, applied by the CDK dialog container. -->\n<div\n #surface\n class=\"md3-fullscreen-dialog-container\"\n [class.md3-active]=\"isActive()\"\n [class.md-scheme-dark]=\"config.scheme == 'dark'\"\n [class.md-scheme-light]=\"config.scheme == 'light'\"\n [attr.dir]=\"config.direction ?? null\">\n <div class=\"md3-panes-container md3-scrollable\">\n <!--\n The CDK attaches the dialog content to the first portal outlet of\n this template, so the main pane has to come first. The panes are\n placed by grid area, which makes the DOM order irrelevant visually.\n -->\n <div class=\"md3-pane md3-pane-main\" #mainPane>\n <ng-template cdkPortalOutlet></ng-template>\n </div>\n <div class=\"md3-pane md3-pane-start\">\n <ng-template #startOutlet cdkPortalOutlet></ng-template>\n </div>\n <div class=\"md3-pane md3-pane-end\">\n <ng-template #endOutlet cdkPortalOutlet></ng-template>\n </div>\n </div>\n</div>\n", styles: ["::ng-deep .cdk-overlay-container{position:fixed;z-index:1000;inset:0;pointer-events:none;font-size:1rem}::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper{position:absolute;display:flex;inset:0;pointer-events:none;overflow:hidden}::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper:has(>.md3-fullscreen-dialog-panel){padding:0}::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper .md3-fullscreen-dialog-panel{width:100%;height:100%;max-width:none;pointer-events:auto}::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper .md3-fullscreen-dialog-panel.md3-dialog-closing,::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper .md3-fullscreen-dialog-panel.md3-dialog-hidden{pointer-events:none}:host{display:block;height:100%;width:100%;outline:0}:host .md3-fullscreen-dialog-container{display:grid;grid-template-rows:minmax(0,1fr);height:100%;min-height:0;overflow:hidden;color:rgb(var(--md-scheme-on-surface));background-color:rgb(var(--md-scheme-surface));transition:all var(--md-motion-expressive-default-spatial-duration) var(--md-motion-expressive-default-spatial-easing);opacity:0;transform:translateY(1.5em)}:host .md3-fullscreen-dialog-container.md3-active{opacity:1;transform:none}:host .md3-panes-container{display:grid;grid-template-areas:\"start main end\";grid-template-columns:auto minmax(0,1fr) auto;grid-template-rows:minmax(0,1fr);min-width:0;min-height:0;overflow:hidden}:host .md3-panes-container .md3-pane{justify-self:stretch;align-self:stretch;min-width:0;min-height:0;height:100%;overflow:hidden}:host .md3-panes-container .md3-pane.md3-pane-start{grid-area:start}:host .md3-panes-container .md3-pane.md3-pane-main{position:relative;grid-area:main;z-index:1;display:grid;grid-template-rows:minmax(0,1fr);grid-template-columns:minmax(0,1fr)}:host .md3-panes-container .md3-pane.md3-pane-main>::ng-deep *{min-width:0;min-height:0;height:100%;max-height:100dvh;max-width:100dvw}:host .md3-panes-container .md3-pane.md3-pane-end{grid-area:end}\n"], dependencies: [{ kind: "directive", type: CdkPortalOutlet, selector: "[cdkPortalOutlet]", inputs: ["cdkPortalOutlet"], outputs: ["attached"], exportAs: ["cdkPortalOutlet"] }] });
7200
+ }
7201
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: FullScreenDialog, decorators: [{
7202
+ type: Component,
7203
+ args: [{ selector: 'md3-fullscreen-dialog', imports: [
7204
+ CdkPortalOutlet,
7205
+ ], template: "<!-- The role and aria attributes live on the host element, applied by the CDK dialog container. -->\n<div\n #surface\n class=\"md3-fullscreen-dialog-container\"\n [class.md3-active]=\"isActive()\"\n [class.md-scheme-dark]=\"config.scheme == 'dark'\"\n [class.md-scheme-light]=\"config.scheme == 'light'\"\n [attr.dir]=\"config.direction ?? null\">\n <div class=\"md3-panes-container md3-scrollable\">\n <!--\n The CDK attaches the dialog content to the first portal outlet of\n this template, so the main pane has to come first. The panes are\n placed by grid area, which makes the DOM order irrelevant visually.\n -->\n <div class=\"md3-pane md3-pane-main\" #mainPane>\n <ng-template cdkPortalOutlet></ng-template>\n </div>\n <div class=\"md3-pane md3-pane-start\">\n <ng-template #startOutlet cdkPortalOutlet></ng-template>\n </div>\n <div class=\"md3-pane md3-pane-end\">\n <ng-template #endOutlet cdkPortalOutlet></ng-template>\n </div>\n </div>\n</div>\n", styles: ["::ng-deep .cdk-overlay-container{position:fixed;z-index:1000;inset:0;pointer-events:none;font-size:1rem}::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper{position:absolute;display:flex;inset:0;pointer-events:none;overflow:hidden}::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper:has(>.md3-fullscreen-dialog-panel){padding:0}::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper .md3-fullscreen-dialog-panel{width:100%;height:100%;max-width:none;pointer-events:auto}::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper .md3-fullscreen-dialog-panel.md3-dialog-closing,::ng-deep .cdk-overlay-container .cdk-global-overlay-wrapper .md3-fullscreen-dialog-panel.md3-dialog-hidden{pointer-events:none}:host{display:block;height:100%;width:100%;outline:0}:host .md3-fullscreen-dialog-container{display:grid;grid-template-rows:minmax(0,1fr);height:100%;min-height:0;overflow:hidden;color:rgb(var(--md-scheme-on-surface));background-color:rgb(var(--md-scheme-surface));transition:all var(--md-motion-expressive-default-spatial-duration) var(--md-motion-expressive-default-spatial-easing);opacity:0;transform:translateY(1.5em)}:host .md3-fullscreen-dialog-container.md3-active{opacity:1;transform:none}:host .md3-panes-container{display:grid;grid-template-areas:\"start main end\";grid-template-columns:auto minmax(0,1fr) auto;grid-template-rows:minmax(0,1fr);min-width:0;min-height:0;overflow:hidden}:host .md3-panes-container .md3-pane{justify-self:stretch;align-self:stretch;min-width:0;min-height:0;height:100%;overflow:hidden}:host .md3-panes-container .md3-pane.md3-pane-start{grid-area:start}:host .md3-panes-container .md3-pane.md3-pane-main{position:relative;grid-area:main;z-index:1;display:grid;grid-template-rows:minmax(0,1fr);grid-template-columns:minmax(0,1fr)}:host .md3-panes-container .md3-pane.md3-pane-main>::ng-deep *{min-width:0;min-height:0;height:100%;max-height:100dvh;max-width:100dvw}:host .md3-panes-container .md3-pane.md3-pane-end{grid-area:end}\n"] }]
7206
+ }], propDecorators: { surface: [{ type: i0.ViewChild, args: ['surface', { isSignal: true }] }], mainPane: [{ type: i0.ViewChild, args: ['mainPane', { isSignal: true }] }], startOutlet: [{ type: i0.ViewChild, args: ['startOutlet', { ...{ read: CdkPortalOutlet }, isSignal: true }] }], endOutlet: [{ type: i0.ViewChild, args: ['endOutlet', { ...{ read: CdkPortalOutlet }, isSignal: true }] }] } });
7207
+
7208
+ const PANEL_CLASS = 'md3-dialog-panel';
7209
+ const FULL_SCREEN_PANEL_CLASS = 'md3-fullscreen-dialog-panel';
7210
+ const OPENING_CLASS = 'md3-dialog-opening';
7211
+ const SCRIM_KEPT_CLASS = 'md3-dialog-scrim-kept';
7212
+ const SCRIM_INSTANT_CLASS = 'md3-dialog-scrim-instant';
7213
+ /**
7214
+ * Head start given to a dialog that leaves the screen before the dialog taking
7215
+ * its place animates in. Long enough to read as one dialog replacing another,
7216
+ * short enough to still feel like a single gesture.
7217
+ */
7218
+ const DIALOG_HANDOVER_DELAY_MS = 160;
7219
+ class DialogService {
7220
+ cdkDialog = inject(Dialog$1);
7221
+ overlay = inject(Overlay);
7222
+ overlayContainer = inject(OverlayContainer);
7223
+ injector = inject(Injector);
7224
+ document = inject(DOCUMENT);
7225
+ /** Open dialogs, from the first one opened to the one currently on top. */
7226
+ refs = [];
7227
+ /**
7228
+ * Elements the page was made of that the CDK hid from assistive technology,
7229
+ * kept here while the dialogs are hidden and the page is given back.
7230
+ */
7231
+ pageAriaHidden = new Map();
7232
+ /**
7233
+ * Page scrolling is blocked here instead of per overlay, so stacked dialogs
7234
+ * cannot unblock the page while another dialog is still open.
7235
+ */
7236
+ scrollBlock = this.overlay.scrollStrategies.block();
7237
+ /** Element that was focused before the first dialog of the stack opened. */
7238
+ rootTrigger = null;
7239
+ pageStateQueued = false;
7240
+ get openDialogs() {
7241
+ return this.refs;
6999
7242
  }
7000
- flipPositionIfPushBreaksAnchor(menuOverlay, config) {
7001
- if (!menuOverlay.positionStrategy) {
7002
- return;
7003
- }
7004
- menuOverlay.overlayRef.updatePosition();
7005
- const fallbackPosition = this.getFallbackPositionAfterPush(config, this.getOriginClientRect(menuOverlay.origin), menuOverlay.overlayRef.overlayElement.getBoundingClientRect());
7006
- if (!fallbackPosition) {
7007
- return;
7008
- }
7009
- menuOverlay.positionStrategy.withPositions([fallbackPosition]);
7010
- menuOverlay.overlayRef.updatePosition();
7243
+ /** Open dialogs that are currently on screen, from the bottom one up. */
7244
+ get visibleDialogs() {
7245
+ return this.refs.filter((ref) => !ref.isHidden);
7011
7246
  }
7012
- getInitialConnectedPositions(config) {
7013
- return [this.getPreferredConnectedPosition(config)];
7247
+ /** The full screen dialog that is open, if there is one. */
7248
+ get fullScreenDialog() {
7249
+ return this.refs.find((ref) => ref.isFullScreen && !ref.isClosing);
7014
7250
  }
7015
- getPreferredConnectedPosition(config) {
7016
- if (config.isSubMenu) {
7017
- return this.createSubMenuConnectedPosition(this.getSubMenuXPosition(config.xPosition), this.getSubMenuVerticalAlignment(config.yPosition), config.offsetX, config.offsetY);
7251
+ open(component, config = {}) {
7252
+ const dialogConfig = this.mergeConfig(config);
7253
+ if (this.refs.length === 0) {
7254
+ this.rootTrigger = this.getFocusedElement();
7018
7255
  }
7019
- return this.createConnectedPosition(config.xPosition, config.yPosition, config);
7020
- }
7021
- getFallbackPositionAfterPush(config, originRect, overlayRect) {
7022
- const xPosition = this.shouldFlipXPosition(config, originRect, overlayRect)
7023
- ? this.getFallbackXPosition(config.xPosition)
7024
- : config.xPosition;
7025
- const yPosition = this.shouldFlipYPosition(config, originRect, overlayRect)
7026
- ? this.getFallbackYPosition(config.yPosition)
7027
- : config.yPosition;
7028
- if (xPosition === config.xPosition && yPosition === config.yPosition) {
7029
- return null;
7256
+ // The dialog that is on screen starts leaving first, so the two dialogs
7257
+ // never animate on top of each other. A full screen dialog is not part
7258
+ // of this: regular dialogs open on top of it and leave it alone.
7259
+ const previousRef = this.topRegularRef();
7260
+ if (previousRef) {
7261
+ this.dismissPrevious(previousRef, dialogConfig.previousDialog);
7030
7262
  }
7031
- return this.getPreferredConnectedPosition({
7032
- ...config,
7033
- xPosition,
7034
- yPosition,
7263
+ let dialogRef;
7264
+ const cdkRef = this.cdkDialog.open(component, {
7265
+ // The MD3 shell replaces the default CDK container, so the overlay,
7266
+ // focus trap and focus restoration come from the CDK while the shell
7267
+ // only renders the surface.
7268
+ container: {
7269
+ type: Dialog,
7270
+ providers: () => [
7271
+ { provide: DIALOG_CONFIG, useValue: dialogConfig },
7272
+ ],
7273
+ },
7274
+ // These tokens make the dialog controllable from the dynamic
7275
+ // component without coupling that component to the service.
7276
+ providers: (cdkDialogRef) => {
7277
+ dialogRef = new DialogRef(cdkDialogRef, dialogConfig.disableCloseEvents);
7278
+ return [
7279
+ { provide: DialogRef, useValue: dialogRef },
7280
+ { provide: DIALOG_DATA, useValue: dialogConfig.data },
7281
+ { provide: DIALOG_CONFIG, useValue: dialogConfig },
7282
+ { provide: DIALOG_COMPONENT, useValue: component },
7283
+ ];
7284
+ },
7285
+ data: dialogConfig.data,
7286
+ role: dialogConfig.role,
7287
+ ariaModal: true,
7288
+ ariaLabel: dialogConfig.ariaLabel || null,
7289
+ ariaLabelledBy: dialogConfig.ariaLabelledBy || null,
7290
+ ariaDescribedBy: dialogConfig.ariaDescribedBy || null,
7291
+ direction: dialogConfig.direction ?? undefined,
7292
+ viewContainerRef: dialogConfig.viewContainerRef,
7293
+ injector: dialogConfig.injector,
7294
+ hasBackdrop: true,
7295
+ backdropClass: ['md3-dialog-scrim', OPENING_CLASS],
7296
+ panelClass: [PANEL_CLASS, OPENING_CLASS],
7297
+ positionStrategy: this.overlay.position()
7298
+ .global()
7299
+ .centerHorizontally()
7300
+ .centerVertically(),
7301
+ // Scrolling is blocked by the service for the whole stack.
7302
+ scrollStrategy: this.overlay.scrollStrategies.noop(),
7303
+ // Focus goes back to whatever opened the stack, even when the dialog
7304
+ // that was open underneath has been closed in the meantime.
7305
+ restoreFocus: this.rootTrigger ?? true,
7306
+ // Scrim and Escape handling lives in DialogRef so the dialog can
7307
+ // play its exit animation before the overlay is disposed.
7308
+ disableClose: true,
7035
7309
  });
7310
+ this.registerDialog(dialogRef, cdkRef, dialogConfig, previousRef);
7311
+ return dialogRef;
7036
7312
  }
7037
- shouldFlipXPosition(config, originRect, overlayRect) {
7038
- const tolerance = config.viewportMargin;
7039
- switch (config.xPosition) {
7040
- case 'before':
7041
- return overlayRect.right > originRect.left + tolerance;
7042
- case 'after':
7043
- return overlayRect.left < originRect.right - tolerance;
7044
- case 'start':
7045
- return originRect.left < overlayRect.left - tolerance
7046
- || originRect.left > overlayRect.right + tolerance;
7047
- case 'end':
7048
- return originRect.right < overlayRect.left - tolerance
7049
- || originRect.right > overlayRect.right + tolerance;
7050
- case 'center':
7051
- default:
7052
- return false;
7053
- }
7054
- }
7055
- shouldFlipYPosition(config, originRect, overlayRect) {
7056
- const tolerance = config.viewportMargin;
7057
- if (config.isSubMenu) {
7058
- const anchorY = config.yPosition === 'above' ? originRect.bottom : originRect.top;
7059
- return anchorY < overlayRect.top - tolerance
7060
- || anchorY > overlayRect.bottom + tolerance;
7061
- }
7062
- if (config.overlapTrigger) {
7063
- const anchorY = config.yPosition === 'above' ? originRect.bottom : originRect.top;
7064
- return anchorY < overlayRect.top - tolerance
7065
- || anchorY > overlayRect.bottom + tolerance;
7066
- }
7067
- switch (config.yPosition) {
7068
- case 'above':
7069
- return overlayRect.bottom > originRect.top + tolerance;
7070
- case 'below':
7071
- return overlayRect.top < originRect.bottom - tolerance;
7072
- case 'center':
7073
- default:
7074
- return false;
7313
+ /**
7314
+ * Opens a dialog that covers the whole screen. Only one can be open at a
7315
+ * time, so it replaces whatever is open, and while it is up the side sheet
7316
+ * outlets belong to it: side sheets open inside the dialog, not behind it.
7317
+ * `previousDialog` decides what happens to the dialogs it replaces, the
7318
+ * same way it does for a regular dialog.
7319
+ */
7320
+ openFullScreen(component, config = {}) {
7321
+ const dialogConfig = this.mergeConfig(config);
7322
+ if (this.refs.length === 0) {
7323
+ this.rootTrigger = this.getFocusedElement();
7075
7324
  }
7076
- }
7077
- createConnectedPosition(xPosition, yPosition, config) {
7078
- const horizontal = this.getHorizontalPosition(xPosition);
7079
- const vertical = this.getVerticalPosition(yPosition, config.overlapTrigger);
7080
- return {
7081
- ...horizontal,
7082
- ...vertical,
7083
- offsetX: config.offsetX,
7084
- offsetY: this.getOffsetY(yPosition, config),
7085
- panelClass: [
7086
- 'md3-menu-position-' + xPosition,
7087
- 'md3-menu-position-' + yPosition,
7088
- ],
7089
- };
7090
- }
7091
- createSubMenuConnectedPosition(xPosition, yAlignment, offsetX, offsetY) {
7092
- const horizontal = this.getHorizontalPosition(xPosition);
7093
- const yPosition = yAlignment === 'top' ? 'below' : 'above';
7094
- return {
7095
- ...horizontal,
7096
- originY: yAlignment,
7097
- overlayY: yAlignment,
7098
- offsetX: this.getSubMenuOffsetX(xPosition, offsetX),
7099
- offsetY: this.getSubMenuOffsetY(yAlignment, offsetY),
7100
- panelClass: [
7101
- 'md3-menu-position-' + xPosition,
7102
- 'md3-menu-position-' + yPosition,
7103
- 'md3-menu-position-submenu',
7104
- ],
7105
- };
7106
- }
7107
- getHorizontalPosition(position) {
7108
- switch (position) {
7109
- case 'end':
7110
- return {
7111
- originX: 'end',
7112
- overlayX: 'end',
7113
- };
7114
- case 'before':
7115
- return {
7116
- originX: 'start',
7117
- overlayX: 'end',
7118
- };
7119
- case 'after':
7120
- return {
7121
- originX: 'end',
7122
- overlayX: 'start',
7123
- };
7124
- case 'center':
7125
- return {
7126
- originX: 'center',
7127
- overlayX: 'center',
7128
- };
7129
- case 'start':
7130
- default:
7131
- return {
7132
- originX: 'start',
7133
- overlayX: 'start',
7134
- };
7325
+ // A full screen dialog is a context of its own: the dialog it replaces
7326
+ // and anything stacked on that dialog goes with it.
7327
+ const previousRef = this.topRef();
7328
+ if (previousRef) {
7329
+ // Everything open belongs to the context being replaced, including
7330
+ // a full screen dialog that is already up.
7331
+ this.dismissPreviousContext(dialogConfig.previousDialog);
7135
7332
  }
7333
+ let dialogRef;
7334
+ const cdkRef = this.cdkDialog.open(component, {
7335
+ container: {
7336
+ type: FullScreenDialog,
7337
+ providers: () => [
7338
+ { provide: DIALOG_CONFIG, useValue: dialogConfig },
7339
+ ],
7340
+ },
7341
+ providers: (cdkDialogRef) => {
7342
+ dialogRef = new DialogRef(cdkDialogRef, dialogConfig.disableCloseEvents, true);
7343
+ return [
7344
+ { provide: DialogRef, useValue: dialogRef },
7345
+ { provide: DIALOG_DATA, useValue: dialogConfig.data },
7346
+ { provide: DIALOG_CONFIG, useValue: dialogConfig },
7347
+ { provide: DIALOG_COMPONENT, useValue: component },
7348
+ ];
7349
+ },
7350
+ data: dialogConfig.data,
7351
+ role: dialogConfig.role,
7352
+ ariaModal: true,
7353
+ ariaLabel: dialogConfig.ariaLabel || null,
7354
+ ariaLabelledBy: dialogConfig.ariaLabelledBy || null,
7355
+ ariaDescribedBy: dialogConfig.ariaDescribedBy || null,
7356
+ direction: dialogConfig.direction ?? undefined,
7357
+ viewContainerRef: dialogConfig.viewContainerRef,
7358
+ injector: dialogConfig.injector,
7359
+ // The dialog covers the page on its own, so there is nothing left
7360
+ // for a scrim to dim.
7361
+ hasBackdrop: false,
7362
+ panelClass: [FULL_SCREEN_PANEL_CLASS, OPENING_CLASS],
7363
+ // The CDK writes the size on the panel and the global strategy then
7364
+ // drops its centering margins, which is how the panel ends up
7365
+ // covering the whole wrapper.
7366
+ width: '100%',
7367
+ height: '100%',
7368
+ positionStrategy: this.overlay.position().global(),
7369
+ scrollStrategy: this.overlay.scrollStrategies.noop(),
7370
+ restoreFocus: this.rootTrigger ?? true,
7371
+ disableClose: true,
7372
+ });
7373
+ this.registerDialog(dialogRef, cdkRef, dialogConfig, previousRef);
7374
+ return dialogRef;
7136
7375
  }
7137
- getVerticalPosition(position, overlapTrigger) {
7138
- switch (position) {
7139
- case 'above':
7140
- return {
7141
- originY: overlapTrigger ? 'bottom' : 'top',
7142
- overlayY: 'bottom',
7143
- };
7144
- case 'center':
7145
- return {
7146
- originY: 'center',
7147
- overlayY: 'center',
7148
- };
7149
- case 'below':
7150
- default:
7151
- return {
7152
- originY: overlapTrigger ? 'top' : 'bottom',
7153
- overlayY: 'top',
7154
- };
7376
+ /** Wires a freshly created dialog into the stack and starts its animation. */
7377
+ registerDialog(dialogRef, cdkRef, dialogConfig, previousRef) {
7378
+ dialogRef.dialogInstance = cdkRef.containerInstance;
7379
+ dialogRef.componentInstance = cdkRef.componentInstance ?? undefined;
7380
+ if (cdkRef.componentRef) {
7381
+ this.bindDataToInputs(cdkRef.componentRef, dialogConfig);
7155
7382
  }
7156
- }
7157
- getFallbackXPosition(position) {
7158
- switch (position) {
7159
- case 'start':
7160
- return 'end';
7161
- case 'end':
7162
- return 'start';
7163
- case 'before':
7164
- return 'after';
7165
- case 'after':
7166
- return 'before';
7167
- case 'center':
7168
- default:
7169
- return 'center';
7383
+ this.refs.push(dialogRef);
7384
+ this.syncPageState();
7385
+ if (previousRef) {
7386
+ setTimeout(() => this.startReplacingAnimation(dialogRef, previousRef), DIALOG_HANDOVER_DELAY_MS);
7170
7387
  }
7171
- }
7172
- getSubMenuXPosition(position) {
7173
- return position === 'before' ? 'before' : 'after';
7174
- }
7175
- getFallbackYPosition(position) {
7176
- switch (position) {
7177
- case 'above':
7178
- return 'below';
7179
- case 'below':
7180
- return 'above';
7181
- case 'center':
7182
- default:
7183
- return 'center';
7388
+ else {
7389
+ this.startOpenAnimation(dialogRef);
7184
7390
  }
7391
+ // Restoring starts with the exit animation, so the dialog underneath is
7392
+ // already coming back while this one is fading out.
7393
+ dialogRef.beforeClosed().subscribe(() => this.restorePreviousDialog(dialogRef));
7394
+ dialogRef.afterClosed().subscribe(() => this.removeRef(dialogRef));
7395
+ // Hiding and showing can also come straight from the reference, so the
7396
+ // page state is driven by what the dialogs report rather than by the
7397
+ // calls that went through this service.
7398
+ dialogRef.hiddenChanged().subscribe(() => this.syncPageState());
7185
7399
  }
7186
- getSubMenuVerticalAlignment(position) {
7187
- return position === 'above' ? 'bottom' : 'top';
7400
+ /**
7401
+ * Hides every dialog that is on screen, the full screen one included, with
7402
+ * the closing animation. They stay alive with their content and state, the
7403
+ * page underneath becomes usable, and showAll() puts them back. The promise
7404
+ * resolves once they are all out of sight.
7405
+ */
7406
+ hideAll() {
7407
+ const visible = this.refs.filter((ref) => !ref.isHidden && !ref.isClosing);
7408
+ return Promise.all(visible.map((ref) => ref.hide())).then(() => undefined);
7188
7409
  }
7189
- getOriginClientRect(origin) {
7190
- if ('getBoundingClientRect' in origin) {
7191
- return origin.getBoundingClientRect();
7410
+ /**
7411
+ * Brings the dialogs back on screen, whether they were hidden one by one or
7412
+ * all at once: the dialog that was on top returns, along with the full
7413
+ * screen dialog it sits in. Dialogs hidden behind another dialog stay
7414
+ * hidden, since that is where they belong once the stack is back.
7415
+ */
7416
+ showAll() {
7417
+ const topRef = this.topRef();
7418
+ if (!topRef) {
7419
+ return;
7192
7420
  }
7193
- return {
7194
- x: origin.x,
7195
- y: origin.y,
7196
- top: origin.y,
7197
- right: origin.x,
7198
- bottom: origin.y,
7199
- left: origin.x,
7200
- width: 0,
7201
- height: 0,
7202
- };
7203
- }
7204
- getViewportMargin(configuredMargin) {
7205
- const minimumMargin = this.getHalfEmInPixels();
7206
- return Math.max(configuredMargin ?? minimumMargin, minimumMargin);
7207
- }
7208
- getHalfEmInPixels() {
7209
- const viewport = this.document.defaultView;
7210
- const fontSize = viewport?.getComputedStyle(this.document.documentElement).fontSize;
7211
- const pixels = fontSize ? Number.parseFloat(fontSize) * 0.5 : 8;
7212
- return Number.isFinite(pixels) ? pixels : 8;
7213
- }
7214
- getOffsetY(position, config) {
7215
- if (config.overlapTrigger || position === 'center') {
7216
- return 0;
7421
+ const fullScreenRef = this.fullScreenDialog;
7422
+ // The full screen dialog is the context the top one sits in, so it
7423
+ // comes back first and the dialog on top ends up on top, with the focus.
7424
+ if (fullScreenRef && fullScreenRef !== topRef && fullScreenRef.isHidden) {
7425
+ fullScreenRef.show();
7426
+ }
7427
+ if (topRef.isHidden) {
7428
+ topRef.show();
7217
7429
  }
7218
- return position === 'above'
7219
- ? config.offsetY * -1
7220
- : config.offsetY;
7221
7430
  }
7222
- getSubMenuOffsetX(position, offsetX) {
7223
- return position === 'before' ? offsetX * -1 : offsetX;
7431
+ /** Closes every open dialog, including the hidden and the full screen ones. */
7432
+ closeAll() {
7433
+ // Closed from the bottom up so a closing dialog never brings back a
7434
+ // hidden dialog that is about to close as well.
7435
+ return Promise.all([...this.refs].map((ref) => ref.close())).then(() => undefined);
7224
7436
  }
7225
- getSubMenuOffsetY(alignment, offsetY) {
7226
- return alignment === 'bottom' ? offsetY * -1 : offsetY;
7437
+ /** Closes the regular dialogs and leaves a full screen dialog alone. */
7438
+ closeRegularDialogs() {
7439
+ const closing = this.refs
7440
+ .filter((ref) => !ref.isFullScreen)
7441
+ .map((ref) => ref.close());
7442
+ return Promise.all(closing).then(() => undefined);
7227
7443
  }
7228
- getScrollStrategy(scrollStrategy) {
7229
- switch (scrollStrategy) {
7230
- case 'block':
7231
- return this.overlay.scrollStrategies.block();
7232
- case 'close':
7233
- return this.overlay.scrollStrategies.close();
7234
- case 'noop':
7235
- return this.overlay.scrollStrategies.noop();
7236
- case 'reposition':
7237
- default:
7238
- return this.overlay.scrollStrategies.reposition();
7239
- }
7444
+ mergeConfig(config) {
7445
+ return {
7446
+ data: config.data,
7447
+ bindDataToInputs: config.bindDataToInputs ?? false,
7448
+ disableCloseEvents: config.disableCloseEvents ?? false,
7449
+ previousDialog: config.previousDialog ?? 'close',
7450
+ role: config.role ?? 'dialog',
7451
+ ariaLabel: config.ariaLabel ?? '',
7452
+ ariaLabelledBy: config.ariaLabelledBy ?? '',
7453
+ ariaDescribedBy: config.ariaDescribedBy ?? '',
7454
+ scheme: config.scheme ?? 'inherit',
7455
+ direction: config.direction ?? null,
7456
+ viewContainerRef: config.viewContainerRef,
7457
+ injector: config.injector ?? this.injector,
7458
+ };
7240
7459
  }
7241
- isMouseEvent(origin) {
7242
- return typeof MouseEvent !== 'undefined' && origin instanceof MouseEvent;
7460
+ /**
7461
+ * Hides or closes the dialog that is on screen. Its scrim stays up until
7462
+ * the dialog taking over is ready for it, so the page behind never
7463
+ * brightens between the two dialogs.
7464
+ */
7465
+ dismissPrevious(previousRef, behavior) {
7466
+ previousRef.overlayRef.backdropElement?.classList.add(SCRIM_KEPT_CLASS);
7467
+ return behavior === 'hide' ? previousRef.hide() : this.closeRegularDialogs();
7243
7468
  }
7244
- isElementRef(origin) {
7245
- return typeof origin === 'object'
7246
- && origin !== null
7247
- && 'nativeElement' in origin
7248
- && origin.nativeElement instanceof HTMLElement;
7469
+ /**
7470
+ * The same choice for a full screen dialog, applied to everything that is
7471
+ * on screen: what it takes over from is a whole context, a full screen
7472
+ * dialog of its own included. `hide` keeps that context alive, and it comes
7473
+ * back as it was once this dialog closes.
7474
+ */
7475
+ dismissPreviousContext(behavior) {
7476
+ return behavior === 'hide' ? this.hideAll() : this.closeAll();
7249
7477
  }
7250
- startOpenAnimation(overlayRef) {
7478
+ startOpenAnimation(dialogRef) {
7479
+ const overlayRef = dialogRef.overlayRef;
7251
7480
  const panel = overlayRef.overlayElement;
7252
7481
  const backdrop = overlayRef.backdropElement;
7253
7482
  // The overlay is inserted already rendered. Keep an initial class for
@@ -7256,22 +7485,65 @@ class MenuService {
7256
7485
  requestAnimationFrame(() => {
7257
7486
  setTimeout(() => {
7258
7487
  panel.getBoundingClientRect();
7259
- panel.classList.remove('md3-menu-opening');
7260
- backdrop?.classList.remove('md3-menu-opening');
7488
+ panel.classList.remove(OPENING_CLASS);
7489
+ backdrop?.classList.remove(OPENING_CLASS);
7261
7490
  }, 100);
7262
7491
  });
7492
+ dialogRef.dialogInstance?.startEnterAnimation();
7493
+ }
7494
+ /** Opening animation for a dialog that took the place of another one. */
7495
+ startReplacingAnimation(dialogRef, previousRef) {
7496
+ if (dialogRef.isClosing) {
7497
+ return;
7498
+ }
7499
+ const panel = dialogRef.overlayRef.overlayElement;
7500
+ const backdrop = dialogRef.overlayRef.backdropElement;
7501
+ this.beginScrimHandover(previousRef, dialogRef);
7502
+ panel.getBoundingClientRect();
7503
+ panel.classList.remove(OPENING_CLASS);
7504
+ backdrop?.classList.remove(OPENING_CLASS);
7505
+ this.endScrimHandover(previousRef, dialogRef);
7506
+ dialogRef.dialogInstance?.startEnterAnimation();
7507
+ }
7508
+ /** Opening animation for a dialog coming back from behind a closing one. */
7509
+ startRestoringAnimation(closingRef, previousRef) {
7510
+ if (previousRef.isClosing) {
7511
+ return;
7512
+ }
7513
+ this.beginScrimHandover(closingRef, previousRef);
7514
+ previousRef.show();
7515
+ this.endScrimHandover(closingRef, previousRef);
7516
+ }
7517
+ /**
7518
+ * Moves both scrims to their new value in a single frame, without a
7519
+ * transition on either side, so the dim behind the dialogs never changes.
7520
+ */
7521
+ beginScrimHandover(fromRef, toRef) {
7522
+ const from = fromRef.overlayRef.backdropElement;
7523
+ const to = toRef.overlayRef.backdropElement;
7524
+ // Nothing to hand the scrim over to, e.g. a full screen dialog: the
7525
+ // outgoing scrim fades out on its own instead.
7526
+ if (!to) {
7527
+ return;
7528
+ }
7529
+ // Nothing to hand over either: the dialog leaving carries no scrim, so
7530
+ // the one coming in fades its own in rather than switching it on in a
7531
+ // single frame. This is a full screen dialog closing over a dialog that
7532
+ // was hidden underneath it.
7533
+ if (!from) {
7534
+ return;
7535
+ }
7536
+ from?.classList.add(SCRIM_INSTANT_CLASS);
7537
+ from?.classList.remove(SCRIM_KEPT_CLASS);
7538
+ to?.classList.add(SCRIM_INSTANT_CLASS);
7263
7539
  }
7264
- createInjector(component, config, menuRef) {
7265
- return Injector.create({
7266
- parent: config.injector,
7267
- providers: [
7268
- // These tokens make the menu controllable from the dynamic
7269
- // component without coupling that component to the service.
7270
- { provide: MenuRef, useValue: menuRef },
7271
- { provide: MENU_DATA, useValue: config.data },
7272
- { provide: MENU_CONFIG, useValue: config },
7273
- { provide: MENU_COMPONENT, useValue: component },
7274
- ],
7540
+ /** Gives the scrims their transitions back once the handover is painted. */
7541
+ endScrimHandover(fromRef, toRef) {
7542
+ const from = fromRef.overlayRef.backdropElement;
7543
+ const to = toRef.overlayRef.backdropElement;
7544
+ requestAnimationFrame(() => {
7545
+ from?.classList.remove(SCRIM_INSTANT_CLASS);
7546
+ to?.classList.remove(SCRIM_INSTANT_CLASS);
7275
7547
  });
7276
7548
  }
7277
7549
  bindDataToInputs(componentRef, config) {
@@ -7284,84 +7556,194 @@ class MenuService {
7284
7556
  componentRef.setInput(inputName, inputValue);
7285
7557
  });
7286
7558
  }
7287
- connectCloseEvents(overlayRef, menuRef) {
7288
- overlayRef.backdropClick().pipe(take(1)).subscribe(() => menuRef.close());
7289
- overlayRef.keydownEvents().pipe(filter((event) => event.key === 'Escape'), take(1)).subscribe(() => menuRef.close());
7559
+ canBindDataToInputs(data) {
7560
+ return typeof data === 'object' && data !== null && !Array.isArray(data);
7561
+ }
7562
+ /** Last dialog of the stack that is not closing yet. */
7563
+ topRef() {
7564
+ for (let index = this.refs.length - 1; index >= 0; index--) {
7565
+ if (!this.refs[index].isClosing) {
7566
+ return this.refs[index];
7567
+ }
7568
+ }
7569
+ return undefined;
7570
+ }
7571
+ /**
7572
+ * Same, without the full screen dialog: it hosts the dialogs opened on top
7573
+ * of it, so it is never the one that gets hidden or closed to make room.
7574
+ */
7575
+ topRegularRef() {
7576
+ const ref = this.topRef();
7577
+ return ref?.isFullScreen ? undefined : ref;
7578
+ }
7579
+ restorePreviousDialog(ref) {
7580
+ const index = this.refs.indexOf(ref);
7581
+ if (index < 1) {
7582
+ return;
7583
+ }
7584
+ for (let previousIndex = index - 1; previousIndex >= 0; previousIndex--) {
7585
+ const previousRef = this.refs[previousIndex];
7586
+ if (previousRef.isClosing) {
7587
+ continue;
7588
+ }
7589
+ // The closing dialog gets the same head start it was given when it
7590
+ // took the screen, and keeps the scrim until then. Nothing to keep
7591
+ // when the dialog underneath has no scrim of its own.
7592
+ if (previousRef.overlayRef.backdropElement) {
7593
+ ref.overlayRef.backdropElement?.classList.add(SCRIM_KEPT_CLASS);
7594
+ }
7595
+ setTimeout(() => {
7596
+ this.showHostFullScreenDialog(previousRef);
7597
+ this.startRestoringAnimation(ref, previousRef);
7598
+ }, DIALOG_HANDOVER_DELAY_MS);
7599
+ return;
7600
+ }
7601
+ }
7602
+ /**
7603
+ * Brings back the full screen dialog a restored dialog sits in. The two are
7604
+ * hidden together when a full screen dialog takes over with
7605
+ * `previousDialog: 'hide'`, so the context has to be on screen again before
7606
+ * the dialog that sits on top of it, exactly as showAll() puts them back.
7607
+ */
7608
+ showHostFullScreenDialog(ref) {
7609
+ if (ref.isFullScreen) {
7610
+ return;
7611
+ }
7612
+ const hostRef = this.fullScreenDialog;
7613
+ if (hostRef?.isHidden && this.refs.indexOf(hostRef) < this.refs.indexOf(ref)) {
7614
+ hostRef.show();
7615
+ }
7616
+ }
7617
+ removeRef(ref) {
7618
+ const index = this.refs.indexOf(ref);
7619
+ if (index === -1) {
7620
+ return;
7621
+ }
7622
+ this.refs.splice(index, 1);
7623
+ if (this.refs.length === 0) {
7624
+ this.rootTrigger = null;
7625
+ }
7626
+ this.syncPageState();
7627
+ }
7628
+ /**
7629
+ * State that belongs to the dialogs as a whole rather than to one of them:
7630
+ * page scrolling, the page being hidden from assistive technology, and
7631
+ * where focus sits. Applied in a task of its own, so a dialog handing the
7632
+ * screen over to another one does not give the page back and take it again
7633
+ * within the same frame.
7634
+ */
7635
+ syncPageState() {
7636
+ if (this.pageStateQueued) {
7637
+ return;
7638
+ }
7639
+ this.pageStateQueued = true;
7640
+ setTimeout(() => {
7641
+ this.pageStateQueued = false;
7642
+ this.applyPageState();
7643
+ });
7644
+ }
7645
+ applyPageState() {
7646
+ const hasVisibleDialog = this.refs.some((ref) => !ref.isHidden);
7647
+ if (hasVisibleDialog) {
7648
+ this.scrollBlock.enable();
7649
+ }
7650
+ else {
7651
+ this.scrollBlock.disable();
7652
+ }
7653
+ if (this.refs.length === 0) {
7654
+ // Nothing is open anymore: the CDK gives the page back to assistive
7655
+ // technology on its own, so there is nothing left to restore here.
7656
+ this.pageAriaHidden.clear();
7657
+ return;
7658
+ }
7659
+ if (hasVisibleDialog) {
7660
+ this.hidePageFromAssistiveTechnology();
7661
+ return;
7662
+ }
7663
+ this.exposePageToAssistiveTechnology();
7664
+ this.releaseFocus();
7665
+ }
7666
+ /**
7667
+ * Gives the page back to assistive technology while every dialog is hidden.
7668
+ * The CDK hides it for as long as a dialog is open, which would otherwise
7669
+ * leave nothing to read at all once the dialogs are out of sight.
7670
+ */
7671
+ exposePageToAssistiveTechnology() {
7672
+ if (this.pageAriaHidden.size > 0) {
7673
+ return;
7674
+ }
7675
+ const container = this.overlayContainer.getContainerElement();
7676
+ Array.from(container.parentElement?.children ?? []).forEach((sibling) => {
7677
+ const value = sibling.getAttribute('aria-hidden');
7678
+ if (sibling === container || value === null) {
7679
+ return;
7680
+ }
7681
+ this.pageAriaHidden.set(sibling, value);
7682
+ sibling.removeAttribute('aria-hidden');
7683
+ });
7684
+ }
7685
+ /** Hides the page again as soon as a dialog is back on screen. */
7686
+ hidePageFromAssistiveTechnology() {
7687
+ this.pageAriaHidden.forEach((value, element) => {
7688
+ element.setAttribute('aria-hidden', value);
7689
+ });
7690
+ this.pageAriaHidden.clear();
7691
+ }
7692
+ /**
7693
+ * Focus follows the dialogs off the screen: it goes back to whatever opened
7694
+ * the stack, so the page stays usable with the keyboard while the dialogs
7695
+ * wait behind it. Focus that already moved somewhere else is left alone.
7696
+ */
7697
+ releaseFocus() {
7698
+ const activeElement = this.document.activeElement;
7699
+ if (this.rootTrigger && (!activeElement || activeElement === this.document.body)) {
7700
+ this.rootTrigger.focus();
7701
+ }
7290
7702
  }
7291
7703
  getFocusedElement() {
7292
7704
  const activeElement = this.document.activeElement;
7293
7705
  return activeElement instanceof HTMLElement ? activeElement : null;
7294
7706
  }
7295
- canBindDataToInputs(data) {
7296
- return typeof data === 'object' && data !== null && !Array.isArray(data);
7297
- }
7298
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: MenuService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
7299
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: MenuService, providedIn: 'root' });
7707
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: DialogService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
7708
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: DialogService, providedIn: 'root' });
7300
7709
  }
7301
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: MenuService, decorators: [{
7710
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: DialogService, decorators: [{
7302
7711
  type: Injectable,
7303
7712
  args: [{
7304
7713
  providedIn: 'root',
7305
7714
  }]
7306
7715
  }] });
7307
7716
 
7308
- class MenuGroup {
7309
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: MenuGroup, deps: [], target: i0.ɵɵFactoryTarget.Component });
7310
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.0", type: MenuGroup, isStandalone: true, selector: "md3-menu-group", host: { attributes: { "role": "group" } }, ngImport: i0, template: "<ng-content></ng-content>\n", styles: [":host{display:flex;flex-direction:column;gap:.125em;padding:.25em;background-color:var(--background-color);color:var(--foreground-color);border-radius:var(--menu-group-radius);box-shadow:var(--menu-group-shadow)}:host:first-child{border-start-start-radius:var(--border-radius);border-start-end-radius:var(--border-radius)}:host:last-child{border-end-start-radius:var(--border-radius);border-end-end-radius:var(--border-radius)}:host ::ng-deep md3-divider{margin:.125em .5em}:host ::ng-deep [md3-menu-item]:first-child .md3-menu-item-container{--border-top-start-r: var(--md-border-radius-medium);--border-top-end-r: var(--md-border-radius-medium);border-start-start-radius:var(--border-top-start-r);border-start-end-radius:var(--border-top-end-r)}:host ::ng-deep [md3-menu-item]:last-child .md3-menu-item-container{--border-bottom-start-r: var(--md-border-radius-medium);--border-bottom-end-r: var(--md-border-radius-medium);border-end-start-radius:var(--border-bottom-start-r);border-end-end-radius:var(--border-bottom-end-r)}.md3-menu-group-label{padding:.5rem var(--menu-item-inline-padding, .75rem) .25rem;color:rgb(var(--md-scheme-on-surface-variant));font-size:.75rem;font-weight:500;line-height:1rem;letter-spacing:.03125rem}\n"] });
7717
+ class DialogHeader {
7718
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: DialogHeader, deps: [], target: i0.ɵɵFactoryTarget.Component });
7719
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.0", type: DialogHeader, isStandalone: true, selector: "md3-dialog-header", ngImport: i0, template: "<ng-content select=\"[md3-icon-element]\"></ng-content>\n<div class=\"md3-dialog-title\">\n <md3-type-headline size=\"small\">\n <ng-content></ng-content>\n </md3-type-headline>\n</div>", styles: [":host{display:block;margin:1.5em 1.5em 1em;text-align:center}:host .md3-dialog-title{text-align:start;color:rgb(var(--md-scheme-on-surface))}:host ::ng-deep [md3-icon-element]{color:rgb(var(--md-scheme-secondary))}:host ::ng-deep [md3-icon-element]+.md3-dialog-title{margin-top:1em;text-align:center}\n"], dependencies: [{ kind: "directive", type: TypeHeadline, selector: "md3-type-headline, [md3-type-headline]", inputs: ["size", "color", "emphasized"] }] });
7311
7720
  }
7312
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: MenuGroup, decorators: [{
7721
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: DialogHeader, decorators: [{
7313
7722
  type: Component,
7314
- args: [{ selector: 'md3-menu-group', imports: [], host: {
7315
- 'role': 'group',
7316
- }, template: "<ng-content></ng-content>\n", styles: [":host{display:flex;flex-direction:column;gap:.125em;padding:.25em;background-color:var(--background-color);color:var(--foreground-color);border-radius:var(--menu-group-radius);box-shadow:var(--menu-group-shadow)}:host:first-child{border-start-start-radius:var(--border-radius);border-start-end-radius:var(--border-radius)}:host:last-child{border-end-start-radius:var(--border-radius);border-end-end-radius:var(--border-radius)}:host ::ng-deep md3-divider{margin:.125em .5em}:host ::ng-deep [md3-menu-item]:first-child .md3-menu-item-container{--border-top-start-r: var(--md-border-radius-medium);--border-top-end-r: var(--md-border-radius-medium);border-start-start-radius:var(--border-top-start-r);border-start-end-radius:var(--border-top-end-r)}:host ::ng-deep [md3-menu-item]:last-child .md3-menu-item-container{--border-bottom-start-r: var(--md-border-radius-medium);--border-bottom-end-r: var(--md-border-radius-medium);border-end-start-radius:var(--border-bottom-start-r);border-end-end-radius:var(--border-bottom-end-r)}.md3-menu-group-label{padding:.5rem var(--menu-item-inline-padding, .75rem) .25rem;color:rgb(var(--md-scheme-on-surface-variant));font-size:.75rem;font-weight:500;line-height:1rem;letter-spacing:.03125rem}\n"] }]
7723
+ args: [{ selector: 'md3-dialog-header', imports: [
7724
+ TypeHeadline
7725
+ ], template: "<ng-content select=\"[md3-icon-element]\"></ng-content>\n<div class=\"md3-dialog-title\">\n <md3-type-headline size=\"small\">\n <ng-content></ng-content>\n </md3-type-headline>\n</div>", styles: [":host{display:block;margin:1.5em 1.5em 1em;text-align:center}:host .md3-dialog-title{text-align:start;color:rgb(var(--md-scheme-on-surface))}:host ::ng-deep [md3-icon-element]{color:rgb(var(--md-scheme-secondary))}:host ::ng-deep [md3-icon-element]+.md3-dialog-title{margin-top:1em;text-align:center}\n"] }]
7317
7726
  }] });
7318
7727
 
7319
- class MenuItem {
7320
- el;
7321
- label = input('', { ...(ngDevMode ? { debugName: "label" } : /* istanbul ignore next */ {}), alias: 'label' });
7322
- supportingText = input(null, { ...(ngDevMode ? { debugName: "supportingText" } : /* istanbul ignore next */ {}), alias: 'supporting-text' });
7323
- trailingText = input(null, { ...(ngDevMode ? { debugName: "trailingText" } : /* istanbul ignore next */ {}), alias: 'trailing-text' });
7324
- selected = model(null, { ...(ngDevMode ? { debugName: "selected" } : /* istanbul ignore next */ {}), alias: 'selected' });
7325
- iconElements = contentChildren(IconElement, /* @ts-ignore */
7326
- ...(ngDevMode ? [{ debugName: "iconElements" }] : /* istanbul ignore next */ []));
7327
- get leading() {
7328
- return this.iconElements().find((icon) => icon.iconType() === 'leading');
7329
- }
7330
- get trailing() {
7331
- return this.iconElements().find((icon) => icon.iconType() === 'trailing');
7332
- }
7333
- get showLeadingIcon() {
7334
- return (!!this.leading && !this.selected()) || this.selected() == true;
7335
- }
7336
- onHostClick() {
7337
- const selected = this.selected();
7338
- if (selected === null) {
7339
- return;
7340
- }
7341
- this.selected.set(!selected);
7342
- }
7343
- constructor(el) {
7344
- this.el = el;
7345
- }
7346
- get element() {
7347
- return this.el.nativeElement;
7348
- }
7349
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: MenuItem, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
7350
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: MenuItem, isStandalone: true, selector: "button[md3-menu-item], a[md3-menu-item]", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, supportingText: { classPropertyName: "supportingText", publicName: "supporting-text", isSignal: true, isRequired: false, transformFunction: null }, trailingText: { classPropertyName: "trailingText", publicName: "trailing-text", isSignal: true, isRequired: false, transformFunction: null }, selected: { classPropertyName: "selected", publicName: "selected", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selected: "selectedChange" }, host: { listeners: { "click": "onHostClick()" } }, queries: [{ propertyName: "iconElements", predicate: IconElement, isSignal: true }], ngImport: i0, template: "@if (selected() !== null) {\n <input type=\"checkbox\" md3-input-element [checked]=\"selected()\">\n}\n\n<div class=\"md3-menu-item-container\" md3-state-component>\n <div class=\"md3-menu-item-leading\" [class.has-icon]=\"showLeadingIcon\">\n <ng-content select=\"[md3-icon-element=leading]\"></ng-content>\n @if (!leading) {\n <md3-icon>check</md3-icon>\n }\n </div>\n\n <div class=\"md3-menu-item-content\">\n <md3-type-label size=\"large\">{{ label() }}</md3-type-label>\n @if (supportingText()?.length) {\n <md3-type-body size=\"small\">{{ supportingText() }}</md3-type-body>\n }\n </div>\n\n <div class=\"md3-menu-item-trailing\">\n <ng-content select=\"md3-badge\"></ng-content>\n @if (trailingText()?.length) {\n <md3-type-title size=\"small\">{{ trailingText() }}</md3-type-title>\n }\n <ng-content select=\"[md3-icon-element=trailing]\"></ng-content>\n </div>\n</div>", styles: [":host{--state-color: var(--menu-item-state-color);--border-radius: var(--menu-item-border-radius);--outline-color: var(--menu-item-outline-color);--outline-width: var(--menu-item-outline-width);--outline-offset: var(--menu-item-outline-offset);position:relative;display:block;padding:0;border:0;background:transparent;color:var(--menu-item-label-color);font:inherit;text-align:start;text-decoration:none;cursor:pointer;outline:none;appearance:none;-webkit-user-select:none;user-select:none;z-index:0}:host:focus{z-index:1}:host:focus:not(:hover) .md3-menu-item-container:after{opacity:1}:host:active{z-index:2}:host:active .md3-menu-item-container:after{opacity:0}:host .md3-menu-item-container{display:flex;align-items:center;min-height:3em;padding:0 .75em;border-radius:var(--border-radius);transition:background-color var(--md-motion-standard-fast-spatial-duration) var(--md-motion-standard-fast-spatial-easing),color var(--md-motion-standard-fast-spatial-duration) var(--md-motion-standard-fast-spatial-easing)}:host .md3-menu-item-container .md3-menu-item-leading{display:inline-flex;align-items:center;justify-content:center;font-size:inherit;opacity:0;transform:scale(1.2);width:0;height:1.25em;color:var(--menu-item-supporting-color);transition:all var(--md-motion-standard-fast-spatial-duration) var(--md-motion-standard-fast-spatial-easing)}:host .md3-menu-item-container .md3-menu-item-leading ::ng-deep [md3-icon-element]{font-size:1.25em!important;height:1em!important;width:1em!important;line-height:1em!important}:host .md3-menu-item-container .md3-menu-item-leading.has-icon{margin-inline-end:.5em;opacity:1;transform:scale(1);height:1.25em;width:1.25em}:host .md3-menu-item-container .md3-menu-item-trailing{flex:0 1 content;display:inline-flex;gap:.5em;align-items:center;justify-content:center;font-size:inherit;margin-inline-start:.5em;color:var(--menu-item-supporting-color)}:host .md3-menu-item-container .md3-menu-item-trailing:empty{margin:0}:host .md3-menu-item-container .md3-menu-item-trailing ::ng-deep md3-badge{background-color:var(--menu-item-badge-background);color:var(--menu-item-badge-foreground)}:host .md3-menu-item-container .md3-menu-item-trailing .md3-text-title.md3-text-small{margin:0;padding:0;white-space:nowrap;letter-spacing:.007143em}:host .md3-menu-item-container .md3-menu-item-trailing ::ng-deep [md3-icon-element]{font-size:1.25em!important;height:1em!important;width:1em!important;line-height:1em!important}:host .md3-menu-item-container .md3-menu-item-content{flex:1 1 auto}:host .md3-menu-item-container .md3-menu-item-content .md3-text-label.md3-text-large{display:block;margin:0;padding:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;letter-spacing:.007143em;color:var(--menu-item-label-color)}:host .md3-menu-item-container .md3-menu-item-content .md3-text-body.md3-text-small{display:block;margin:0;padding:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;letter-spacing:.0333334em;color:var(--menu-item-supporting-color)}:host [md3-input-element]{position:absolute;height:0;width:0;opacity:0;pointer-events:none}:host [md3-input-element]+.md3-menu-item-container .md3-menu-item-leading{opacity:0;transform:scale(1.2);width:0}:host [md3-input-element]:checked+.md3-menu-item-container{--menu-item-label-color: var(--menu-item-selected-foreground);--menu-item-supporting-color: var(--menu-item-selected-foreground);--border-radius: var(--md-border-radius-medium);background-color:var(--menu-item-selected-background)}:host [md3-input-element]:checked+.md3-menu-item-container .md3-menu-item-leading{opacity:1;transform:scale(1);width:1.25em}:host:disabled{cursor:default}:host:disabled .md3-menu-item-container{--menu-item-label-color: var(--menu-item-label-color);--menu-item-supporting-color: var(--menu-item-label-color);--menu-item-selected-background: transparent;--menu-item-selected-foreground: var(--menu-item-label-color);pointer-events:none;opacity:.38}:host:disabled .md3-menu-item-container.md3-state-layer,:host:disabled .md3-menu-item-container:after{display:none}\n"], dependencies: [{ kind: "directive", type: InputElement, selector: "[md3-input-element]" }, { kind: "directive", type: StateComponent, selector: "[md3-state-component]" }, { kind: "component", type: MaterialIcon, selector: "md3-icon", inputs: ["icon-version", "filled", "icon-style", "icon-size", "bi-directional"] }, { kind: "directive", type: TypeLabel, selector: "md3-type-label, [md3-type-label]", inputs: ["size", "color", "emphasized"] }, { kind: "directive", type: TypeBody, selector: "md3-type-body, [md3-type-body]", inputs: ["size", "color", "emphasized"] }, { kind: "directive", type: TypeTitle, selector: "md3-type-title, [md3-type-title]", inputs: ["size", "color", "emphasized"] }] });
7728
+ class DialogBody {
7729
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: DialogBody, deps: [], target: i0.ɵɵFactoryTarget.Component });
7730
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.0", type: DialogBody, isStandalone: true, selector: "md3-dialog-body", host: { classAttribute: "md3-scrollable" }, ngImport: i0, template: "<ng-content></ng-content>", styles: [":host{display:block;margin:1em 1.5em 1.5em;color:rgb(var(--md-scheme-on-surface-variant))}:host .md3-text-body.md3-text-medium{letter-spacing:.017857em}\n"] });
7351
7731
  }
7352
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: MenuItem, decorators: [{
7732
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: DialogBody, decorators: [{
7353
7733
  type: Component,
7354
- args: [{ selector: 'button[md3-menu-item], a[md3-menu-item]', imports: [
7355
- InputElement,
7356
- StateComponent,
7357
- MaterialIcon,
7358
- TypeLabel,
7359
- TypeBody,
7360
- TypeTitle,
7361
- ], host: {
7362
- '(click)': 'onHostClick()',
7363
- }, template: "@if (selected() !== null) {\n <input type=\"checkbox\" md3-input-element [checked]=\"selected()\">\n}\n\n<div class=\"md3-menu-item-container\" md3-state-component>\n <div class=\"md3-menu-item-leading\" [class.has-icon]=\"showLeadingIcon\">\n <ng-content select=\"[md3-icon-element=leading]\"></ng-content>\n @if (!leading) {\n <md3-icon>check</md3-icon>\n }\n </div>\n\n <div class=\"md3-menu-item-content\">\n <md3-type-label size=\"large\">{{ label() }}</md3-type-label>\n @if (supportingText()?.length) {\n <md3-type-body size=\"small\">{{ supportingText() }}</md3-type-body>\n }\n </div>\n\n <div class=\"md3-menu-item-trailing\">\n <ng-content select=\"md3-badge\"></ng-content>\n @if (trailingText()?.length) {\n <md3-type-title size=\"small\">{{ trailingText() }}</md3-type-title>\n }\n <ng-content select=\"[md3-icon-element=trailing]\"></ng-content>\n </div>\n</div>", styles: [":host{--state-color: var(--menu-item-state-color);--border-radius: var(--menu-item-border-radius);--outline-color: var(--menu-item-outline-color);--outline-width: var(--menu-item-outline-width);--outline-offset: var(--menu-item-outline-offset);position:relative;display:block;padding:0;border:0;background:transparent;color:var(--menu-item-label-color);font:inherit;text-align:start;text-decoration:none;cursor:pointer;outline:none;appearance:none;-webkit-user-select:none;user-select:none;z-index:0}:host:focus{z-index:1}:host:focus:not(:hover) .md3-menu-item-container:after{opacity:1}:host:active{z-index:2}:host:active .md3-menu-item-container:after{opacity:0}:host .md3-menu-item-container{display:flex;align-items:center;min-height:3em;padding:0 .75em;border-radius:var(--border-radius);transition:background-color var(--md-motion-standard-fast-spatial-duration) var(--md-motion-standard-fast-spatial-easing),color var(--md-motion-standard-fast-spatial-duration) var(--md-motion-standard-fast-spatial-easing)}:host .md3-menu-item-container .md3-menu-item-leading{display:inline-flex;align-items:center;justify-content:center;font-size:inherit;opacity:0;transform:scale(1.2);width:0;height:1.25em;color:var(--menu-item-supporting-color);transition:all var(--md-motion-standard-fast-spatial-duration) var(--md-motion-standard-fast-spatial-easing)}:host .md3-menu-item-container .md3-menu-item-leading ::ng-deep [md3-icon-element]{font-size:1.25em!important;height:1em!important;width:1em!important;line-height:1em!important}:host .md3-menu-item-container .md3-menu-item-leading.has-icon{margin-inline-end:.5em;opacity:1;transform:scale(1);height:1.25em;width:1.25em}:host .md3-menu-item-container .md3-menu-item-trailing{flex:0 1 content;display:inline-flex;gap:.5em;align-items:center;justify-content:center;font-size:inherit;margin-inline-start:.5em;color:var(--menu-item-supporting-color)}:host .md3-menu-item-container .md3-menu-item-trailing:empty{margin:0}:host .md3-menu-item-container .md3-menu-item-trailing ::ng-deep md3-badge{background-color:var(--menu-item-badge-background);color:var(--menu-item-badge-foreground)}:host .md3-menu-item-container .md3-menu-item-trailing .md3-text-title.md3-text-small{margin:0;padding:0;white-space:nowrap;letter-spacing:.007143em}:host .md3-menu-item-container .md3-menu-item-trailing ::ng-deep [md3-icon-element]{font-size:1.25em!important;height:1em!important;width:1em!important;line-height:1em!important}:host .md3-menu-item-container .md3-menu-item-content{flex:1 1 auto}:host .md3-menu-item-container .md3-menu-item-content .md3-text-label.md3-text-large{display:block;margin:0;padding:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;letter-spacing:.007143em;color:var(--menu-item-label-color)}:host .md3-menu-item-container .md3-menu-item-content .md3-text-body.md3-text-small{display:block;margin:0;padding:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;letter-spacing:.0333334em;color:var(--menu-item-supporting-color)}:host [md3-input-element]{position:absolute;height:0;width:0;opacity:0;pointer-events:none}:host [md3-input-element]+.md3-menu-item-container .md3-menu-item-leading{opacity:0;transform:scale(1.2);width:0}:host [md3-input-element]:checked+.md3-menu-item-container{--menu-item-label-color: var(--menu-item-selected-foreground);--menu-item-supporting-color: var(--menu-item-selected-foreground);--border-radius: var(--md-border-radius-medium);background-color:var(--menu-item-selected-background)}:host [md3-input-element]:checked+.md3-menu-item-container .md3-menu-item-leading{opacity:1;transform:scale(1);width:1.25em}:host:disabled{cursor:default}:host:disabled .md3-menu-item-container{--menu-item-label-color: var(--menu-item-label-color);--menu-item-supporting-color: var(--menu-item-label-color);--menu-item-selected-background: transparent;--menu-item-selected-foreground: var(--menu-item-label-color);pointer-events:none;opacity:.38}:host:disabled .md3-menu-item-container.md3-state-layer,:host:disabled .md3-menu-item-container:after{display:none}\n"] }]
7364
- }], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], supportingText: [{ type: i0.Input, args: [{ isSignal: true, alias: "supporting-text", required: false }] }], trailingText: [{ type: i0.Input, args: [{ isSignal: true, alias: "trailing-text", required: false }] }], selected: [{ type: i0.Input, args: [{ isSignal: true, alias: "selected", required: false }] }, { type: i0.Output, args: ["selectedChange"] }], iconElements: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => IconElement), { isSignal: true }] }] } });
7734
+ args: [{ selector: 'md3-dialog-body', imports: [], host: {
7735
+ 'class': 'md3-scrollable',
7736
+ }, template: "<ng-content></ng-content>", styles: [":host{display:block;margin:1em 1.5em 1.5em;color:rgb(var(--md-scheme-on-surface-variant))}:host .md3-text-body.md3-text-medium{letter-spacing:.017857em}\n"] }]
7737
+ }] });
7738
+
7739
+ class DialogActions {
7740
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: DialogActions, deps: [], target: i0.ɵɵFactoryTarget.Component });
7741
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.0", type: DialogActions, isStandalone: true, selector: "md3-dialog-actions", ngImport: i0, template: "<div class=\"md3-actions\">\n <ng-content></ng-content>\n</div>", styles: [":host{display:block;margin:1.5em}:host .md3-actions{display:flex;gap:1em;flex-direction:row-reverse}\n"] });
7742
+ }
7743
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: DialogActions, decorators: [{
7744
+ type: Component,
7745
+ args: [{ selector: 'md3-dialog-actions', imports: [], template: "<div class=\"md3-actions\">\n <ng-content></ng-content>\n</div>", styles: [":host{display:block;margin:1.5em}:host .md3-actions{display:flex;gap:1em;flex-direction:row-reverse}\n"] }]
7746
+ }] });
7365
7747
 
7366
7748
  class SideSheetHeader {
7367
7749
  title = input('', { ...(ngDevMode ? { debugName: "title" } : /* istanbul ignore next */ {}), alias: 'title' });
@@ -7764,5 +8146,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
7764
8146
  * Generated bundle index. Do not edit.
7765
8147
  */
7766
8148
 
7767
- export { AppBar, AppBarLogo, Avatar, BOTTOM_SHEET_COMPONENT, BOTTOM_SHEET_CONFIG, BOTTOM_SHEET_DATA, Badge, BottomSheetRef, BottomSheetService, Button, ButtonGroup, CAROUSEL_STRATEGIES, Card, Carousel, CarouselItem, Checkbox, ChipAvatar, Chips, CircularProgressIndicator, DIALOG_COMPONENT, DIALOG_CONFIG, DIALOG_DATA, Dialog, DialogActions, DialogBody, DialogHeader, DialogRef, DialogService, Divider, FloatingActionButton, FullScreenDialog, Grid, GridItem, IconButton, IconElement, InputElement, Layout, LayoutService, LinearProgressIndicator, List, ListItem, ListItemPrimaryAction, ListLeading, ListSlot, LoadingIndicator, MENU_COMPONENT, MENU_CONFIG, MENU_DATA, MaterialIcon, Menu, MenuGroup, MenuItem, MenuRef, MenuService, ModalBottomSheetRef, NavigationBar, NavigationGroup, NavigationItem, NavigationRail, RadioButton, SIDE_SHEET_COMPONENT, SIDE_SHEET_CONFIG, SIDE_SHEET_DATA, SNACKBAR_ACTION_LABEL, SNACKBAR_CONFIG, SNACKBAR_MESSAGE, Scaffold, ScaffoldBar, ScaffoldPane, ScaffoldRail, SheetsService, SideSheetActions, SideSheetBody, SideSheetHeader, SideSheetRef, Slider, Snackbar, SnackbarRef, SnackbarService, SplitButton, StandardBottomSheetRef, StateComponent, SupportingText, Switch, TextField, Toolbar, ToolbarItem, TypeBody, TypeDisplay, TypeHeadline, TypeLabel, TypeTitle, ViewportService, buildGeometry, clampIndex, indexForScrollOffset, multiBrowseStrategy, parseCarouselAspectRatio, resolveItemGeometry, resolveState, scrollOffsetForIndex, sizeBandFor };
8149
+ export { AppBar, AppBarLogo, Avatar, BOTTOM_SHEET_COMPONENT, BOTTOM_SHEET_CONFIG, BOTTOM_SHEET_DATA, Badge, BottomSheetRef, BottomSheetService, Button, ButtonGroup, CAROUSEL_STRATEGIES, Card, Carousel, CarouselItem, Checkbox, ChipAvatar, Chips, CircularProgressIndicator, DIALOG_COMPONENT, DIALOG_CONFIG, DIALOG_DATA, Dialog, DialogActions, DialogBody, DialogHeader, DialogRef, DialogService, Divider, FloatingActionButton, FullScreenDialog, Grid, GridItem, IconButton, IconElement, InputElement, Layout, LayoutService, LinearProgressIndicator, List, ListItem, ListItemPrimaryAction, ListLeading, ListSlot, LoadingIndicator, MENU_COMPONENT, MENU_CONFIG, MENU_DATA, MaterialIcon, Menu, MenuGroup, MenuItem, MenuRef, MenuService, ModalBottomSheetRef, NavigationBar, NavigationGroup, NavigationItem, NavigationRail, RadioButton, SIDE_SHEET_COMPONENT, SIDE_SHEET_CONFIG, SIDE_SHEET_DATA, SNACKBAR_ACTION_LABEL, SNACKBAR_CONFIG, SNACKBAR_MESSAGE, Scaffold, ScaffoldBar, ScaffoldPane, ScaffoldRail, SelectField, SheetsService, SideSheetActions, SideSheetBody, SideSheetHeader, SideSheetRef, Slider, Snackbar, SnackbarRef, SnackbarService, SplitButton, StandardBottomSheetRef, StateComponent, SupportingText, Switch, TextField, Toolbar, ToolbarItem, TypeBody, TypeDisplay, TypeHeadline, TypeLabel, TypeTitle, ViewportService, buildGeometry, clampIndex, indexForScrollOffset, multiBrowseStrategy, parseCarouselAspectRatio, resolveItemGeometry, resolveState, scrollOffsetForIndex, sizeBandFor };
7768
8150
  //# sourceMappingURL=almoamendev-ngx-md3.mjs.map