@flywheel-io/vision 21.1.1 → 21.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  import { NgClass, CommonModule, NgStyle, NgTemplateOutlet, SlicePipe, Location } from '@angular/common';
2
2
  import * as i0 from '@angular/core';
3
- import { inject, input, HostBinding, ChangeDetectionStrategy, Component, output, computed, NgModule, model, signal, effect, HostListener, ViewEncapsulation, EventEmitter, Output, ElementRef, ViewContainerRef, Directive, contentChildren, viewChild, ChangeDetectorRef, ContentChildren, Input, ViewChild, forwardRef, DestroyRef, Injectable, ContentChild, contentChild, NgZone, untracked, ViewChildren, viewChildren, linkedSignal, TemplateRef } from '@angular/core';
3
+ import { inject, input, HostBinding, ChangeDetectionStrategy, Component, output, computed, NgModule, model, signal, effect, HostListener, ViewEncapsulation, EventEmitter, Output, ElementRef, ViewContainerRef, Directive, contentChildren, viewChild, ChangeDetectorRef, ContentChildren, Input, ViewChild, forwardRef, DestroyRef, Injectable, ContentChild, contentChild, NgZone, untracked, DOCUMENT, ViewChildren, viewChildren, linkedSignal, TemplateRef } from '@angular/core';
4
4
  import { DomSanitizer } from '@angular/platform-browser';
5
5
  import { BehaviorSubject, debounce, timer, distinctUntilChanged, of, Subscription, min, combineLatest, tap, switchMap, map } from 'rxjs';
6
6
  import { CdkConnectedOverlay, OverlayModule, Overlay, CdkOverlayOrigin, OverlayContainer } from '@angular/cdk/overlay';
@@ -3177,10 +3177,26 @@ class FwDialogComponent {
3177
3177
  return classes;
3178
3178
  }
3179
3179
  ngOnInit() {
3180
- this.dialogRef?.containerInstance?._addAriaLabelledBy(this.headerId);
3180
+ // `<fw-dialog>` is usable outside an overlay, so there may be no dialogRef at all.
3181
+ const container = this.dialogRef?.containerInstance;
3182
+ if (container) {
3183
+ // Deferred by one microtask on purpose: `_addAriaLabelledBy` mutates the container's
3184
+ // `_ariaLabelledByQueue`, which `CdkDialogContainer` exposes as a host binding. A component's
3185
+ // host bindings are evaluated in its parent view *before* Angular descends into child
3186
+ // components, so mutating the queue synchronously here changes a value the container already
3187
+ // read in the same change-detection pass, which throws NG0100 in dev builds. Same approach as
3188
+ // Angular Material's `MatDialogLayoutSection`.
3189
+ Promise.resolve().then(() => container._addAriaLabelledBy(this.headerId));
3190
+ }
3181
3191
  }
3182
3192
  ngOnDestroy() {
3183
- this.dialogRef?.containerInstance?._removeAriaLabelledBy(this.headerId);
3193
+ const container = this.dialogRef?.containerInstance;
3194
+ if (container) {
3195
+ // Deferred to stay ordered behind the deferred add in `ngOnInit` — otherwise a dialog created
3196
+ // and destroyed within the same task would remove the id before it was ever added and leave
3197
+ // it orphaned in the queue.
3198
+ Promise.resolve().then(() => container._removeAriaLabelledBy(this.headerId));
3199
+ }
3184
3200
  }
3185
3201
  handleCloseButton() {
3186
3202
  this.closeWithAnimation();
@@ -4324,6 +4340,16 @@ class FwMenuItemComponent {
4324
4340
  this.focused = model(false, ...(ngDevMode ? [{ debugName: "focused" }] : /* istanbul ignore next */ []));
4325
4341
  this.selected = model(false, ...(ngDevMode ? [{ debugName: "selected" }] : /* istanbul ignore next */ []));
4326
4342
  this.subscriptions = [];
4343
+ this.isDestroyed = false;
4344
+ }
4345
+ /**
4346
+ * Whether this item's view has been torn down. Owners that iterate their items (`fw-menu`) have to
4347
+ * check this before writing to the item's `model()`s: neither `QueryList` nor a signal query is
4348
+ * emptied by view destruction, so an item can still be reachable from a query after the menu it
4349
+ * lives in was destroyed - writing to it then logs NG0953.
4350
+ */
4351
+ get destroyed() {
4352
+ return this.isDestroyed;
4327
4353
  }
4328
4354
  scrollIntoView(options = { behavior: 'smooth', block: 'nearest' }) {
4329
4355
  // eslint-disable-next-line @rx-angular/prefer-no-layout-sensitive-apis
@@ -4341,6 +4367,7 @@ class FwMenuItemComponent {
4341
4367
  this.updateLayout();
4342
4368
  }
4343
4369
  ngOnDestroy() {
4370
+ this.isDestroyed = true;
4344
4371
  for (const subscription of this.subscriptions) {
4345
4372
  subscription.unsubscribe();
4346
4373
  }
@@ -4487,9 +4514,15 @@ class FwMenuComponent {
4487
4514
  }
4488
4515
  writeValue(value) {
4489
4516
  this.value.set(value);
4517
+ // Lay the items out BEFORE calling out to any listener. `change` is wired to `fw-select`'s
4518
+ // `handleClick`, which closes the options panel and so synchronously destroys every
4519
+ // `fw-menu-item` rendered in it. View teardown does not empty a `QueryList`, so laying out
4520
+ // afterwards writes to `model()`s on already-destroyed nodes, which logs NG0953 once per item
4521
+ // whose `selected` flag changed. Everything `updateLayout()` reads derives from `this.value()`,
4522
+ // set on the line above, so the resulting layout is identical either way.
4523
+ this.updateLayout();
4490
4524
  this.onChange(value);
4491
4525
  this.change.emit(value);
4492
- this.updateLayout();
4493
4526
  }
4494
4527
  registerOnChange(fn) {
4495
4528
  this.onChange = fn;
@@ -4528,6 +4561,12 @@ class FwMenuComponent {
4528
4561
  if (this.menuItems) {
4529
4562
  const itemRole = this.computeItemRole();
4530
4563
  this.menuItems.forEach((item) => {
4564
+ // A destroyed item is still reachable through the QueryList; writing to its models would
4565
+ // emit on a dead OutputRef (NG0953). Belt and braces for any other listener that tears the
4566
+ // menu down from underneath a layout pass.
4567
+ if (item.destroyed) {
4568
+ return;
4569
+ }
4531
4570
  item.itemRole.set(itemRole);
4532
4571
  const size = this.size();
4533
4572
  if (size !== undefined) {
@@ -4884,7 +4923,7 @@ class FwTextInputComponent {
4884
4923
  useExisting: FwTextInputComponent,
4885
4924
  multi: true,
4886
4925
  },
4887
- ], queries: [{ propertyName: "contentChildInputElement", first: true, predicate: ["textInput"], descendants: true, isSignal: true }], viewQueries: [{ propertyName: "inputRef", first: true, predicate: ["input"], descendants: true }], ngImport: i0, template: "<div class=\"full-container\" [ngClass]=\"{ disabled: disabled() }\">\n <div class=\"input-container\" [class]=\"size()\">\n @if (!!leftIcon()) {\n @if (useActionableIcons()) {\n <div fwClickableDiv (click)=\"onLeftIconClick()\" class=\"actionable\" [attr.aria-label]=\"leftIconLabel()\">\n <fw-icon>{{ leftIcon() }}</fw-icon>\n </div>\n } @else {\n <fw-icon>{{ leftIcon() }}</fw-icon>\n }\n }\n @if (!!prefix()) {\n <p class=\"vision-p2 context\">{{ prefix() }}</p>\n }\n\n @if (!contentChildInputElement()) {\n <input\n #input\n [type]=\"type()\"\n (input)=\"changeHandler($event)\"\n (blur)=\"blurHandler()\"\n [attr.maxlength]=\"maxLength()\"\n [placeholder]=\"placeholder() || ''\"\n [readOnly]=\"readOnly()\"\n [disabled]=\"disabled()\"\n [autofocus]=\"autofocus()\"\n [autocomplete]=\"autocomplete()\"\n [required]=\"required()\"\n [attr.aria-required]=\"required()\"\n [attr.aria-invalid]=\"error() ? true : null\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-describedby]=\"computedDescribedBy()\"\n [attr.role]=\"role() || null\"\n [attr.aria-expanded]=\"ariaExpanded() === undefined ? null : ariaExpanded()\"\n [attr.aria-controls]=\"ariaControls() || null\"\n [attr.aria-autocomplete]=\"ariaAutocomplete() || null\"\n [attr.aria-activedescendant]=\"ariaActiveDescendant() || null\"\n />\n }\n <ng-content select=\"input\"></ng-content>\n @if (!!context()) {\n <p class=\"vision-p2 context\">{{ context() }}</p>\n }\n\n <fw-icon class=\"error-icon\" [fwTooltip]=\"errorInIconTooltip() ? errorText() || '' : ''\">warning-circle</fw-icon>\n @if (!!rightIcon()) {\n @if (useActionableIcons()) {\n <div fwClickableDiv (click)=\"onRightIconClick()\" class=\"actionable\" [attr.aria-label]=\"rightIconLabel()\">\n <fw-icon>{{ rightIcon() }}</fw-icon>\n </div>\n } @else {\n <fw-icon>{{ rightIcon() }}</fw-icon>\n }\n }\n <ng-content></ng-content>\n </div>\n @if (!!helperText()) {\n <p class=\"vision-p4 helper-text\" [id]=\"helperTextId\">{{ helperText() }}</p>\n }\n @if (!!errorText() && !errorInIconTooltip()) {\n <p class=\"vision-p4 error-text\" [id]=\"errorTextId\">{{ errorText() }}</p>\n }\n</div>\n", styles: ["@import\"https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700\";.vision-h1{font-family:Inter,sans-serif;color:var(--typography-base);font-weight:500;font-size:22px}.vision-h2{font-family:Inter,sans-serif;color:var(--typography-base);font-weight:500;font-size:18px}.vision-h3{font-family:Inter,sans-serif;color:var(--typography-base);font-weight:500;font-size:16px}.vision-h4{font-family:Inter,sans-serif;color:var(--typography-base);font-weight:500;font-size:14px}.vision-h5{font-family:Inter,sans-serif;color:var(--typography-base);font-weight:500;font-size:12px;line-height:130%}.vision-h6{font-family:Inter,sans-serif;color:var(--typography-base);font-weight:500;font-size:10px;line-height:120%}.vision-p1{font-size:18px;font-family:Inter,sans-serif;color:var(--typography-base);font-weight:400}.vision-p2{font-size:14px;font-family:Inter,sans-serif;color:var(--typography-base);font-weight:400}.vision-p3{font-size:12px;font-family:Inter,sans-serif;color:var(--typography-base);font-weight:400}.vision-p4{font-size:10px;font-family:Inter,sans-serif;color:var(--typography-base);font-weight:400}.vision-p5{font-size:8px;font-family:Inter,sans-serif;color:var(--typography-base);font-weight:400}.vision-link{text-decoration:underline;color:var(--primary-base);cursor:pointer}.vision-link:hover{text-decoration:none}.vision-link:active{text-decoration:none;outline:2px solid var(--primary-dark);border-radius:4px}.vision-link:visited{color:var(--secondary-base)}.vision-link-inherited{text-decoration:underline;color:var(--primary-base);cursor:pointer}.vision-link-inherited:hover{text-decoration:none}.vision-link-inherited:active{text-decoration:none;outline:2px solid var(--primary-dark);border-radius:4px}.vision-link-inherited:visited{color:var(--secondary-base)}.vision-link-inherited,.vision-link-inherited:visited{color:inherit}.vision-link-no-visited{text-decoration:underline;color:var(--primary-base);cursor:pointer}.vision-link-no-visited:hover{text-decoration:none}.vision-link-no-visited:active{text-decoration:none;outline:2px solid var(--primary-dark);border-radius:4px}.vision-link-no-visited:visited{color:var(--secondary-base)}.vision-link-no-visited:visited{color:var(--primary-base)}.full-container.disabled{cursor:not-allowed}.full-container.disabled fw-icon{cursor:not-allowed!important}.full-container{display:flex;flex-direction:column;line-height:21px}.full-container .input-container{box-sizing:border-box;color:var(--typography-light);background:var(--page-light);display:flex;padding:8px;align-items:center;gap:5px;border-radius:6px;border:1px solid var(--separations-input);font-family:Inter,sans-serif}.full-container .input-container:focus-within{border:1px solid var(--primary-base)}.full-container .input-container input{min-width:0;font-size:14px;flex-grow:1;color:var(--typography-base);background:var(--page-light);border:none}.full-container .input-container input:focus{outline:none;border:none}.full-container .input-container input::placeholder{color:var(--typography-light)}.full-container .input-container .context{color:var(--typography-light)}.full-container .error-icon{display:none}.full-container .helper-text,.full-container .error-text{margin-top:4px;color:var(--typography-light);line-height:13px;margin-left:6px;margin-bottom:0}.full-container .error-text{text-align:left;color:var(--red-base);display:none}.actionable{cursor:pointer}.actionable:hover{color:var(--primary-base);background-color:var(--primary-hover);border-radius:50%}.small{height:30px}.small fw-icon{font-size:18px;min-width:18px;width:18px}.medium{height:36px}.medium fw-icon{font-size:20px;min-width:20px;width:20px}.large{height:40px}.large fw-icon{font-size:24px;min-width:24px;width:24px}:host.errored .input-container,:host.ng-touched.ng-invalid .input-container{border:1px solid var(--red-base)}:host.errored .error-icon,:host.ng-touched.ng-invalid .error-icon{color:var(--red-base);display:inline!important}:host.errored .helper-text,:host.errored .full-container .error-text,:host.ng-touched.ng-invalid .helper-text,:host.ng-touched.ng-invalid .full-container .error-text{display:none}:host.errored .error-text,:host.ng-touched.ng-invalid .error-text{display:block!important}:disabled{opacity:.4;cursor:not-allowed}.disabled .actionable:hover{color:var(--typography-light);background-color:transparent}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: FwIconComponent, selector: "fw-icon", inputs: ["ariaLabel", "size", "color"] }, { kind: "directive", type: FwTooltipDirective, selector: "[fwTooltip]", inputs: ["fwTooltip", "fwTooltipPosition", "fwTooltipMaxWidthPx", "fwTooltipClass", "fwTooltipDelay", "fwTooltipCaret", "fwTooltipEnabled"], outputs: ["fwTooltipChange", "fwTooltipPositionChange", "fwTooltipMaxWidthPxChange", "fwTooltipClassChange", "fwTooltipEnabledChange"] }, { kind: "directive", type: FwClickableDivDirective, selector: "[fwClickableDiv]", inputs: ["fwClickableDivDisabled", "fwClickableDivRole"] }] }); }
4926
+ ], queries: [{ propertyName: "contentChildInputElement", first: true, predicate: ["textInput"], descendants: true, isSignal: true }], viewQueries: [{ propertyName: "inputRef", first: true, predicate: ["input"], descendants: true }], ngImport: i0, template: "<div class=\"full-container\" [ngClass]=\"{ disabled: disabled() }\">\n <div class=\"input-container\" [class]=\"size()\">\n @if (!!leftIcon()) {\n @if (useActionableIcons()) {\n <div fwClickableDiv (click)=\"onLeftIconClick()\" class=\"actionable\" [attr.aria-label]=\"leftIconLabel()\">\n <fw-icon>{{ leftIcon() }}</fw-icon>\n </div>\n } @else {\n <fw-icon>{{ leftIcon() }}</fw-icon>\n }\n }\n @if (!!prefix()) {\n <p class=\"vision-p2 context\">{{ prefix() }}</p>\n }\n\n @if (!contentChildInputElement()) {\n <input\n #input\n [type]=\"type()\"\n (input)=\"changeHandler($event)\"\n (blur)=\"blurHandler()\"\n [attr.maxlength]=\"maxLength()\"\n [placeholder]=\"placeholder() || ''\"\n [readOnly]=\"readOnly()\"\n [disabled]=\"disabled()\"\n [autofocus]=\"autofocus()\"\n [autocomplete]=\"autocomplete()\"\n [required]=\"required()\"\n [attr.aria-required]=\"required()\"\n [attr.aria-invalid]=\"error() ? true : null\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-describedby]=\"computedDescribedBy()\"\n [attr.role]=\"role() || null\"\n [attr.aria-expanded]=\"ariaExpanded() === undefined ? null : ariaExpanded()\"\n [attr.aria-controls]=\"ariaControls() || null\"\n [attr.aria-autocomplete]=\"ariaAutocomplete() || null\"\n [attr.aria-activedescendant]=\"ariaActiveDescendant() || null\"\n />\n }\n <ng-content select=\"input\"></ng-content>\n @if (!!context()) {\n <p class=\"vision-p2 context\">{{ context() }}</p>\n }\n\n <fw-icon class=\"error-icon\" [fwTooltip]=\"errorInIconTooltip() ? errorText() || '' : ''\">warning-circle</fw-icon>\n @if (!!rightIcon()) {\n @if (useActionableIcons()) {\n <div fwClickableDiv (click)=\"onRightIconClick()\" class=\"actionable\" [attr.aria-label]=\"rightIconLabel()\">\n <fw-icon>{{ rightIcon() }}</fw-icon>\n </div>\n } @else {\n <fw-icon>{{ rightIcon() }}</fw-icon>\n }\n }\n <ng-content></ng-content>\n </div>\n @if (!!helperText()) {\n <p class=\"vision-p4 helper-text\" [id]=\"helperTextId\">{{ helperText() }}</p>\n }\n @if (!!errorText() && !errorInIconTooltip()) {\n <p class=\"vision-p4 error-text\" [id]=\"errorTextId\">{{ errorText() }}</p>\n }\n</div>\n", styles: ["@import\"https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700\";.vision-h1{font-family:Inter,sans-serif;color:var(--typography-base);font-weight:500;font-size:22px}.vision-h2{font-family:Inter,sans-serif;color:var(--typography-base);font-weight:500;font-size:18px}.vision-h3{font-family:Inter,sans-serif;color:var(--typography-base);font-weight:500;font-size:16px}.vision-h4{font-family:Inter,sans-serif;color:var(--typography-base);font-weight:500;font-size:14px}.vision-h5{font-family:Inter,sans-serif;color:var(--typography-base);font-weight:500;font-size:12px;line-height:130%}.vision-h6{font-family:Inter,sans-serif;color:var(--typography-base);font-weight:500;font-size:10px;line-height:120%}.vision-p1{font-size:18px;font-family:Inter,sans-serif;color:var(--typography-base);font-weight:400}.vision-p2{font-size:14px;font-family:Inter,sans-serif;color:var(--typography-base);font-weight:400}.vision-p3{font-size:12px;font-family:Inter,sans-serif;color:var(--typography-base);font-weight:400}.vision-p4{font-size:10px;font-family:Inter,sans-serif;color:var(--typography-base);font-weight:400}.vision-p5{font-size:8px;font-family:Inter,sans-serif;color:var(--typography-base);font-weight:400}.vision-link{text-decoration:underline;color:var(--primary-base);cursor:pointer}.vision-link:hover{text-decoration:none}.vision-link:active{text-decoration:none;outline:2px solid var(--primary-dark);border-radius:4px}.vision-link:visited{color:var(--secondary-base)}.vision-link-inherited{text-decoration:underline;color:var(--primary-base);cursor:pointer}.vision-link-inherited:hover{text-decoration:none}.vision-link-inherited:active{text-decoration:none;outline:2px solid var(--primary-dark);border-radius:4px}.vision-link-inherited:visited{color:var(--secondary-base)}.vision-link-inherited,.vision-link-inherited:visited{color:inherit}.vision-link-no-visited{text-decoration:underline;color:var(--primary-base);cursor:pointer}.vision-link-no-visited:hover{text-decoration:none}.vision-link-no-visited:active{text-decoration:none;outline:2px solid var(--primary-dark);border-radius:4px}.vision-link-no-visited:visited{color:var(--secondary-base)}.vision-link-no-visited:visited{color:var(--primary-base)}.full-container.disabled{cursor:not-allowed}.full-container.disabled fw-icon{cursor:not-allowed!important}.full-container{display:flex;flex-direction:column;line-height:21px}.full-container .input-container{box-sizing:border-box;color:var(--typography-light);background:var(--page-light);display:flex;padding:8px;align-items:center;gap:5px;border-radius:6px;border:1px solid var(--separations-input);font-family:Inter,sans-serif}.full-container .input-container:focus-within{border:1px solid var(--primary-base)}.full-container .input-container input{min-width:0;font-size:14px;flex-grow:1;color:var(--typography-base);background:var(--page-light);border:none}.full-container .input-container input:focus{outline:none;border:none}.full-container .input-container input::placeholder{color:var(--typography-light)}.full-container .input-container .context{color:var(--typography-light)}.full-container .error-icon{display:none}.full-container .helper-text,.full-container .error-text{margin-top:4px;color:var(--typography-light);line-height:13px;margin-left:6px;margin-bottom:0}.full-container .error-text{text-align:left;color:var(--red-base);display:none}.actionable{cursor:pointer}.actionable:hover{color:var(--primary-base);background-color:var(--primary-hover);border-radius:50%}.small{height:30px}.small fw-icon{font-size:18px;min-width:18px;width:18px}.medium{height:36px}.medium fw-icon{font-size:20px;min-width:20px;width:20px}.large{height:40px}.large fw-icon{font-size:24px;min-width:24px;width:24px}:host.errored .input-container,:host.ng-touched.ng-invalid .input-container{border:1px solid var(--red-base)}:host.errored .input-container:focus-within,:host.ng-touched.ng-invalid .input-container:focus-within{border:1px solid var(--red-base)}:host.errored .error-icon,:host.ng-touched.ng-invalid .error-icon{color:var(--red-base);display:inline!important}:host.errored .helper-text,:host.errored .full-container .error-text,:host.ng-touched.ng-invalid .helper-text,:host.ng-touched.ng-invalid .full-container .error-text{display:none}:host.errored .error-text,:host.ng-touched.ng-invalid .error-text{display:block!important}:disabled{opacity:.4;cursor:not-allowed}.disabled .actionable:hover{color:var(--typography-light);background-color:transparent}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: FwIconComponent, selector: "fw-icon", inputs: ["ariaLabel", "size", "color"] }, { kind: "directive", type: FwTooltipDirective, selector: "[fwTooltip]", inputs: ["fwTooltip", "fwTooltipPosition", "fwTooltipMaxWidthPx", "fwTooltipClass", "fwTooltipDelay", "fwTooltipCaret", "fwTooltipEnabled"], outputs: ["fwTooltipChange", "fwTooltipPositionChange", "fwTooltipMaxWidthPxChange", "fwTooltipClassChange", "fwTooltipEnabledChange"] }, { kind: "directive", type: FwClickableDivDirective, selector: "[fwClickableDiv]", inputs: ["fwClickableDivDisabled", "fwClickableDivRole"] }] }); }
4888
4927
  }
4889
4928
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: FwTextInputComponent, decorators: [{
4890
4929
  type: Component,
@@ -4897,7 +4936,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImpo
4897
4936
  ], host: {
4898
4937
  '[class.errored]': 'error()',
4899
4938
  '[style.width]': 'width()',
4900
- }, imports: [NgClass, FwIconComponent, FwTooltipDirective, FwClickableDivDirective], template: "<div class=\"full-container\" [ngClass]=\"{ disabled: disabled() }\">\n <div class=\"input-container\" [class]=\"size()\">\n @if (!!leftIcon()) {\n @if (useActionableIcons()) {\n <div fwClickableDiv (click)=\"onLeftIconClick()\" class=\"actionable\" [attr.aria-label]=\"leftIconLabel()\">\n <fw-icon>{{ leftIcon() }}</fw-icon>\n </div>\n } @else {\n <fw-icon>{{ leftIcon() }}</fw-icon>\n }\n }\n @if (!!prefix()) {\n <p class=\"vision-p2 context\">{{ prefix() }}</p>\n }\n\n @if (!contentChildInputElement()) {\n <input\n #input\n [type]=\"type()\"\n (input)=\"changeHandler($event)\"\n (blur)=\"blurHandler()\"\n [attr.maxlength]=\"maxLength()\"\n [placeholder]=\"placeholder() || ''\"\n [readOnly]=\"readOnly()\"\n [disabled]=\"disabled()\"\n [autofocus]=\"autofocus()\"\n [autocomplete]=\"autocomplete()\"\n [required]=\"required()\"\n [attr.aria-required]=\"required()\"\n [attr.aria-invalid]=\"error() ? true : null\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-describedby]=\"computedDescribedBy()\"\n [attr.role]=\"role() || null\"\n [attr.aria-expanded]=\"ariaExpanded() === undefined ? null : ariaExpanded()\"\n [attr.aria-controls]=\"ariaControls() || null\"\n [attr.aria-autocomplete]=\"ariaAutocomplete() || null\"\n [attr.aria-activedescendant]=\"ariaActiveDescendant() || null\"\n />\n }\n <ng-content select=\"input\"></ng-content>\n @if (!!context()) {\n <p class=\"vision-p2 context\">{{ context() }}</p>\n }\n\n <fw-icon class=\"error-icon\" [fwTooltip]=\"errorInIconTooltip() ? errorText() || '' : ''\">warning-circle</fw-icon>\n @if (!!rightIcon()) {\n @if (useActionableIcons()) {\n <div fwClickableDiv (click)=\"onRightIconClick()\" class=\"actionable\" [attr.aria-label]=\"rightIconLabel()\">\n <fw-icon>{{ rightIcon() }}</fw-icon>\n </div>\n } @else {\n <fw-icon>{{ rightIcon() }}</fw-icon>\n }\n }\n <ng-content></ng-content>\n </div>\n @if (!!helperText()) {\n <p class=\"vision-p4 helper-text\" [id]=\"helperTextId\">{{ helperText() }}</p>\n }\n @if (!!errorText() && !errorInIconTooltip()) {\n <p class=\"vision-p4 error-text\" [id]=\"errorTextId\">{{ errorText() }}</p>\n }\n</div>\n", styles: ["@import\"https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700\";.vision-h1{font-family:Inter,sans-serif;color:var(--typography-base);font-weight:500;font-size:22px}.vision-h2{font-family:Inter,sans-serif;color:var(--typography-base);font-weight:500;font-size:18px}.vision-h3{font-family:Inter,sans-serif;color:var(--typography-base);font-weight:500;font-size:16px}.vision-h4{font-family:Inter,sans-serif;color:var(--typography-base);font-weight:500;font-size:14px}.vision-h5{font-family:Inter,sans-serif;color:var(--typography-base);font-weight:500;font-size:12px;line-height:130%}.vision-h6{font-family:Inter,sans-serif;color:var(--typography-base);font-weight:500;font-size:10px;line-height:120%}.vision-p1{font-size:18px;font-family:Inter,sans-serif;color:var(--typography-base);font-weight:400}.vision-p2{font-size:14px;font-family:Inter,sans-serif;color:var(--typography-base);font-weight:400}.vision-p3{font-size:12px;font-family:Inter,sans-serif;color:var(--typography-base);font-weight:400}.vision-p4{font-size:10px;font-family:Inter,sans-serif;color:var(--typography-base);font-weight:400}.vision-p5{font-size:8px;font-family:Inter,sans-serif;color:var(--typography-base);font-weight:400}.vision-link{text-decoration:underline;color:var(--primary-base);cursor:pointer}.vision-link:hover{text-decoration:none}.vision-link:active{text-decoration:none;outline:2px solid var(--primary-dark);border-radius:4px}.vision-link:visited{color:var(--secondary-base)}.vision-link-inherited{text-decoration:underline;color:var(--primary-base);cursor:pointer}.vision-link-inherited:hover{text-decoration:none}.vision-link-inherited:active{text-decoration:none;outline:2px solid var(--primary-dark);border-radius:4px}.vision-link-inherited:visited{color:var(--secondary-base)}.vision-link-inherited,.vision-link-inherited:visited{color:inherit}.vision-link-no-visited{text-decoration:underline;color:var(--primary-base);cursor:pointer}.vision-link-no-visited:hover{text-decoration:none}.vision-link-no-visited:active{text-decoration:none;outline:2px solid var(--primary-dark);border-radius:4px}.vision-link-no-visited:visited{color:var(--secondary-base)}.vision-link-no-visited:visited{color:var(--primary-base)}.full-container.disabled{cursor:not-allowed}.full-container.disabled fw-icon{cursor:not-allowed!important}.full-container{display:flex;flex-direction:column;line-height:21px}.full-container .input-container{box-sizing:border-box;color:var(--typography-light);background:var(--page-light);display:flex;padding:8px;align-items:center;gap:5px;border-radius:6px;border:1px solid var(--separations-input);font-family:Inter,sans-serif}.full-container .input-container:focus-within{border:1px solid var(--primary-base)}.full-container .input-container input{min-width:0;font-size:14px;flex-grow:1;color:var(--typography-base);background:var(--page-light);border:none}.full-container .input-container input:focus{outline:none;border:none}.full-container .input-container input::placeholder{color:var(--typography-light)}.full-container .input-container .context{color:var(--typography-light)}.full-container .error-icon{display:none}.full-container .helper-text,.full-container .error-text{margin-top:4px;color:var(--typography-light);line-height:13px;margin-left:6px;margin-bottom:0}.full-container .error-text{text-align:left;color:var(--red-base);display:none}.actionable{cursor:pointer}.actionable:hover{color:var(--primary-base);background-color:var(--primary-hover);border-radius:50%}.small{height:30px}.small fw-icon{font-size:18px;min-width:18px;width:18px}.medium{height:36px}.medium fw-icon{font-size:20px;min-width:20px;width:20px}.large{height:40px}.large fw-icon{font-size:24px;min-width:24px;width:24px}:host.errored .input-container,:host.ng-touched.ng-invalid .input-container{border:1px solid var(--red-base)}:host.errored .error-icon,:host.ng-touched.ng-invalid .error-icon{color:var(--red-base);display:inline!important}:host.errored .helper-text,:host.errored .full-container .error-text,:host.ng-touched.ng-invalid .helper-text,:host.ng-touched.ng-invalid .full-container .error-text{display:none}:host.errored .error-text,:host.ng-touched.ng-invalid .error-text{display:block!important}:disabled{opacity:.4;cursor:not-allowed}.disabled .actionable:hover{color:var(--typography-light);background-color:transparent}\n"] }]
4939
+ }, imports: [NgClass, FwIconComponent, FwTooltipDirective, FwClickableDivDirective], template: "<div class=\"full-container\" [ngClass]=\"{ disabled: disabled() }\">\n <div class=\"input-container\" [class]=\"size()\">\n @if (!!leftIcon()) {\n @if (useActionableIcons()) {\n <div fwClickableDiv (click)=\"onLeftIconClick()\" class=\"actionable\" [attr.aria-label]=\"leftIconLabel()\">\n <fw-icon>{{ leftIcon() }}</fw-icon>\n </div>\n } @else {\n <fw-icon>{{ leftIcon() }}</fw-icon>\n }\n }\n @if (!!prefix()) {\n <p class=\"vision-p2 context\">{{ prefix() }}</p>\n }\n\n @if (!contentChildInputElement()) {\n <input\n #input\n [type]=\"type()\"\n (input)=\"changeHandler($event)\"\n (blur)=\"blurHandler()\"\n [attr.maxlength]=\"maxLength()\"\n [placeholder]=\"placeholder() || ''\"\n [readOnly]=\"readOnly()\"\n [disabled]=\"disabled()\"\n [autofocus]=\"autofocus()\"\n [autocomplete]=\"autocomplete()\"\n [required]=\"required()\"\n [attr.aria-required]=\"required()\"\n [attr.aria-invalid]=\"error() ? true : null\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-describedby]=\"computedDescribedBy()\"\n [attr.role]=\"role() || null\"\n [attr.aria-expanded]=\"ariaExpanded() === undefined ? null : ariaExpanded()\"\n [attr.aria-controls]=\"ariaControls() || null\"\n [attr.aria-autocomplete]=\"ariaAutocomplete() || null\"\n [attr.aria-activedescendant]=\"ariaActiveDescendant() || null\"\n />\n }\n <ng-content select=\"input\"></ng-content>\n @if (!!context()) {\n <p class=\"vision-p2 context\">{{ context() }}</p>\n }\n\n <fw-icon class=\"error-icon\" [fwTooltip]=\"errorInIconTooltip() ? errorText() || '' : ''\">warning-circle</fw-icon>\n @if (!!rightIcon()) {\n @if (useActionableIcons()) {\n <div fwClickableDiv (click)=\"onRightIconClick()\" class=\"actionable\" [attr.aria-label]=\"rightIconLabel()\">\n <fw-icon>{{ rightIcon() }}</fw-icon>\n </div>\n } @else {\n <fw-icon>{{ rightIcon() }}</fw-icon>\n }\n }\n <ng-content></ng-content>\n </div>\n @if (!!helperText()) {\n <p class=\"vision-p4 helper-text\" [id]=\"helperTextId\">{{ helperText() }}</p>\n }\n @if (!!errorText() && !errorInIconTooltip()) {\n <p class=\"vision-p4 error-text\" [id]=\"errorTextId\">{{ errorText() }}</p>\n }\n</div>\n", styles: ["@import\"https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700\";.vision-h1{font-family:Inter,sans-serif;color:var(--typography-base);font-weight:500;font-size:22px}.vision-h2{font-family:Inter,sans-serif;color:var(--typography-base);font-weight:500;font-size:18px}.vision-h3{font-family:Inter,sans-serif;color:var(--typography-base);font-weight:500;font-size:16px}.vision-h4{font-family:Inter,sans-serif;color:var(--typography-base);font-weight:500;font-size:14px}.vision-h5{font-family:Inter,sans-serif;color:var(--typography-base);font-weight:500;font-size:12px;line-height:130%}.vision-h6{font-family:Inter,sans-serif;color:var(--typography-base);font-weight:500;font-size:10px;line-height:120%}.vision-p1{font-size:18px;font-family:Inter,sans-serif;color:var(--typography-base);font-weight:400}.vision-p2{font-size:14px;font-family:Inter,sans-serif;color:var(--typography-base);font-weight:400}.vision-p3{font-size:12px;font-family:Inter,sans-serif;color:var(--typography-base);font-weight:400}.vision-p4{font-size:10px;font-family:Inter,sans-serif;color:var(--typography-base);font-weight:400}.vision-p5{font-size:8px;font-family:Inter,sans-serif;color:var(--typography-base);font-weight:400}.vision-link{text-decoration:underline;color:var(--primary-base);cursor:pointer}.vision-link:hover{text-decoration:none}.vision-link:active{text-decoration:none;outline:2px solid var(--primary-dark);border-radius:4px}.vision-link:visited{color:var(--secondary-base)}.vision-link-inherited{text-decoration:underline;color:var(--primary-base);cursor:pointer}.vision-link-inherited:hover{text-decoration:none}.vision-link-inherited:active{text-decoration:none;outline:2px solid var(--primary-dark);border-radius:4px}.vision-link-inherited:visited{color:var(--secondary-base)}.vision-link-inherited,.vision-link-inherited:visited{color:inherit}.vision-link-no-visited{text-decoration:underline;color:var(--primary-base);cursor:pointer}.vision-link-no-visited:hover{text-decoration:none}.vision-link-no-visited:active{text-decoration:none;outline:2px solid var(--primary-dark);border-radius:4px}.vision-link-no-visited:visited{color:var(--secondary-base)}.vision-link-no-visited:visited{color:var(--primary-base)}.full-container.disabled{cursor:not-allowed}.full-container.disabled fw-icon{cursor:not-allowed!important}.full-container{display:flex;flex-direction:column;line-height:21px}.full-container .input-container{box-sizing:border-box;color:var(--typography-light);background:var(--page-light);display:flex;padding:8px;align-items:center;gap:5px;border-radius:6px;border:1px solid var(--separations-input);font-family:Inter,sans-serif}.full-container .input-container:focus-within{border:1px solid var(--primary-base)}.full-container .input-container input{min-width:0;font-size:14px;flex-grow:1;color:var(--typography-base);background:var(--page-light);border:none}.full-container .input-container input:focus{outline:none;border:none}.full-container .input-container input::placeholder{color:var(--typography-light)}.full-container .input-container .context{color:var(--typography-light)}.full-container .error-icon{display:none}.full-container .helper-text,.full-container .error-text{margin-top:4px;color:var(--typography-light);line-height:13px;margin-left:6px;margin-bottom:0}.full-container .error-text{text-align:left;color:var(--red-base);display:none}.actionable{cursor:pointer}.actionable:hover{color:var(--primary-base);background-color:var(--primary-hover);border-radius:50%}.small{height:30px}.small fw-icon{font-size:18px;min-width:18px;width:18px}.medium{height:36px}.medium fw-icon{font-size:20px;min-width:20px;width:20px}.large{height:40px}.large fw-icon{font-size:24px;min-width:24px;width:24px}:host.errored .input-container,:host.ng-touched.ng-invalid .input-container{border:1px solid var(--red-base)}:host.errored .input-container:focus-within,:host.ng-touched.ng-invalid .input-container:focus-within{border:1px solid var(--red-base)}:host.errored .error-icon,:host.ng-touched.ng-invalid .error-icon{color:var(--red-base);display:inline!important}:host.errored .helper-text,:host.errored .full-container .error-text,:host.ng-touched.ng-invalid .helper-text,:host.ng-touched.ng-invalid .full-container .error-text{display:none}:host.errored .error-text,:host.ng-touched.ng-invalid .error-text{display:block!important}:disabled{opacity:.4;cursor:not-allowed}.disabled .actionable:hover{color:var(--typography-light);background-color:transparent}\n"] }]
4901
4940
  }], propDecorators: { disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }, { type: i0.Output, args: ["disabledChange"] }], useActionableIcons: [{ type: i0.Input, args: [{ isSignal: true, alias: "useActionableIcons", required: false }] }], leftIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "leftIcon", required: false }] }], rightIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "rightIcon", required: false }] }], leftIconLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "leftIconLabel", required: false }] }], rightIconLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "rightIconLabel", required: false }] }], prefix: [{ type: i0.Input, args: [{ isSignal: true, alias: "prefix", required: false }] }], context: [{ type: i0.Input, args: [{ isSignal: true, alias: "context", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], role: [{ type: i0.Input, args: [{ isSignal: true, alias: "role", required: false }] }], ariaExpanded: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaExpanded", required: false }] }], ariaControls: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaControls", required: false }] }], ariaAutocomplete: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaAutocomplete", required: false }] }], ariaActiveDescendant: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaActiveDescendant", required: false }] }], helperText: [{ type: i0.Input, args: [{ isSignal: true, alias: "helperText", required: false }] }], errorText: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorText", required: false }] }], errorInIconTooltip: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorInIconTooltip", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], readOnly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readOnly", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], maxLength: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxLength", required: false }] }], autofocus: [{ type: i0.Input, args: [{ isSignal: true, alias: "autofocus", required: false }] }], autocomplete: [{ type: i0.Input, args: [{ isSignal: true, alias: "autocomplete", required: false }] }], value: [{
4902
4941
  type: Input
4903
4942
  }], inputRef: [{
@@ -5059,6 +5098,11 @@ class FwMenuContainerComponent {
5059
5098
  constructor() {
5060
5099
  this.sanitizer = inject(DomSanitizer);
5061
5100
  this.ngZone = inject(NgZone);
5101
+ /**
5102
+ * The container's own element. Exposed because this component is usually rendered into a cdk
5103
+ * overlay, so a consumer holding the instance has no other way to reach the rendered panel.
5104
+ */
5105
+ this.hostElement = inject(ElementRef).nativeElement;
5062
5106
  this.menuWrapperRef = viewChild('menuWrapper', { ...(ngDevMode ? { debugName: "menuWrapperRef" } : /* istanbul ignore next */ {}), read: HTMLElement });
5063
5107
  this.canScrollUp = signal(false, ...(ngDevMode ? [{ debugName: "canScrollUp" }] : /* istanbul ignore next */ []));
5064
5108
  this.canScrollDown = signal(false, ...(ngDevMode ? [{ debugName: "canScrollDown" }] : /* istanbul ignore next */ []));
@@ -5831,6 +5875,31 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImpo
5831
5875
  args: ['click']
5832
5876
  }] } });
5833
5877
 
5878
+ /**
5879
+ * Whether an event originated on, or inside, any of the given elements.
5880
+ *
5881
+ * Shared by `fw-select` and `fw-multi-select` so that both decide "inside vs outside" identically -
5882
+ * the trigger lives in the component's own view while the options panel is rendered into a cdk
5883
+ * overlay somewhere else in the document, so containment has to be checked against both.
5884
+ */
5885
+ function isEventInside(event, ...elements) {
5886
+ const candidates = elements.filter((element) => Boolean(element));
5887
+ if (candidates.length === 0) {
5888
+ return false;
5889
+ }
5890
+ // The path is resolved at dispatch time, so it still holds up for targets that a handler earlier
5891
+ // in the chain has already detached from the DOM
5892
+ const path = typeof event.composedPath === 'function' ? event.composedPath() : [];
5893
+ if (path.length > 0) {
5894
+ return candidates.some((element) => path.includes(element));
5895
+ }
5896
+ const target = event.target;
5897
+ if (!(target instanceof Node)) {
5898
+ return false;
5899
+ }
5900
+ return candidates.some((element) => element.contains(target));
5901
+ }
5902
+
5834
5903
  /* eslint-disable @typescript-eslint/no-explicit-any */
5835
5904
  /**
5836
5905
  * Form control component for selecting multiple options from a dropdown, displaying selected values as chips
@@ -5839,7 +5908,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImpo
5839
5908
  class FwMultiSelectMenuComponent {
5840
5909
  constructor() {
5841
5910
  this.elementRef = inject(ElementRef);
5911
+ this.document = inject(DOCUMENT);
5842
5912
  this.listboxId = inject(_IdGenerator).getId('fw-multi-select-listbox-');
5913
+ this.documentClickListener = (event) => this.outsideClick(event);
5843
5914
  // options
5844
5915
  this.options = input([], ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
5845
5916
  this.valueProperty = input('value', ...(ngDevMode ? [{ debugName: "valueProperty" }] : /* istanbul ignore next */ []));
@@ -5864,6 +5935,12 @@ class FwMultiSelectMenuComponent {
5864
5935
  this.minOptionsHeight = input(...(ngDevMode ? [undefined, { debugName: "minOptionsHeight" }] : /* istanbul ignore next */ []));
5865
5936
  this.maxOptionsHeight = input('400px', ...(ngDevMode ? [{ debugName: "maxOptionsHeight" }] : /* istanbul ignore next */ []));
5866
5937
  this.size = input('medium', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
5938
+ /**
5939
+ * The control itself, which is only as wide as `width()`. The host element is a block element and
5940
+ * so can be considerably wider, and a click in that gap is an outside click.
5941
+ */
5942
+ this.wrapper = viewChild('wrapper', { ...(ngDevMode ? { debugName: "wrapper" } : /* istanbul ignore next */ {}), read: ElementRef });
5943
+ /** The options panel, `undefined` while it is closed since it only exists inside the cdk overlay */
5867
5944
  this.menuFilter = viewChild(FwMenuContainerComponent, ...(ngDevMode ? [{ debugName: "menuFilter" }] : /* istanbul ignore next */ []));
5868
5945
  // need this for the template
5869
5946
  this.createArray = (d) => Array.from(d);
@@ -5874,7 +5951,6 @@ class FwMultiSelectMenuComponent {
5874
5951
  this.focusedIndex = signal(0, ...(ngDevMode ? [{ debugName: "focusedIndex" }] : /* istanbul ignore next */ []));
5875
5952
  this.touched = false;
5876
5953
  this.subscriptions = [];
5877
- this._isOpen = false;
5878
5954
  // eslint-disable-next-line @angular-eslint/no-output-native
5879
5955
  this.change = new EventEmitter();
5880
5956
  this.defaultFilter = (filter, menuItems) => {
@@ -5935,7 +6011,7 @@ class FwMultiSelectMenuComponent {
5935
6011
  /* eslint-enable */
5936
6012
  // needs to be an arrow function to lock the scope
5937
6013
  this.handleKeyDown = (event) => {
5938
- const handler = this.trigger.isOpen()
6014
+ const handler = this.isMenuOpen()
5939
6015
  ? this.keyboardActionMap[event.key]
5940
6016
  : this.closedTriggerKeyboardActionsMap[event.key];
5941
6017
  if (handler) {
@@ -5944,18 +6020,35 @@ class FwMultiSelectMenuComponent {
5944
6020
  }
5945
6021
  };
5946
6022
  }
6023
+ /**
6024
+ * Closes the options panel when a click lands outside of both the trigger and the panel.
6025
+ *
6026
+ * Registered by hand instead of through `@HostListener('document:click')` so that it runs in the
6027
+ * capture phase: a number of components in the library (`fw-menu-item`, `fw-chip`, `fw-navbar-item`,
6028
+ * the dialog backdrop, ...) call `stopPropagation()` on click, which would otherwise keep this
6029
+ * listener from ever seeing the click and leave the panel open.
6030
+ */
5947
6031
  outsideClick(event) {
5948
- if (this._isOpen) {
5949
- const clickedInside = this.elementRef.nativeElement.contains(event.target);
5950
- if (clickedInside) {
5951
- return;
5952
- }
5953
- this.trigger.close();
5954
- this._isOpen = false;
5955
- }
5956
- if (this.trigger && this.trigger.isOpen()) {
5957
- this._isOpen = true;
6032
+ if (!this.isMenuOpen() || this.isEventInsideSelect(event)) {
6033
+ return;
5958
6034
  }
6035
+ this.onTouched();
6036
+ this.trigger.close();
6037
+ }
6038
+ /**
6039
+ * Whether the options panel is currently open. The single place this class asks that question -
6040
+ * the trigger owns the state, so there is no local flag to keep in sync.
6041
+ *
6042
+ * ! Deliberately a plain method, not a `computed()` ! The trigger reports its state from the
6043
+ * overlay ref rather than from a signal, so a computed would memoize the first answer and stop
6044
+ * tracking, silently freezing `aria-expanded`.
6045
+ */
6046
+ isMenuOpen() {
6047
+ return this.trigger?.isOpen() ?? false;
6048
+ }
6049
+ /** Whether an event originated on this select's control box or inside its rendered options panel */
6050
+ isEventInsideSelect(event) {
6051
+ return isEventInside(event, this.wrapper()?.nativeElement, this.menuFilter()?.hostElement);
5959
6052
  }
5960
6053
  get value() {
5961
6054
  return this._value;
@@ -5978,7 +6071,11 @@ class FwMultiSelectMenuComponent {
5978
6071
  writeValue(value) {
5979
6072
  this.value = value;
5980
6073
  }
6074
+ ngOnInit() {
6075
+ this.document.addEventListener('click', this.documentClickListener, true);
6076
+ }
5981
6077
  ngOnDestroy() {
6078
+ this.document.removeEventListener('click', this.documentClickListener, true);
5982
6079
  for (const subscription of this.subscriptions) {
5983
6080
  subscription.unsubscribe();
5984
6081
  }
@@ -6117,13 +6214,13 @@ class FwMultiSelectMenuComponent {
6117
6214
  }
6118
6215
  }
6119
6216
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: FwMultiSelectMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
6120
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.18", type: FwMultiSelectMenuComponent, isStandalone: true, selector: "fw-multi-select", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, valueProperty: { classPropertyName: "valueProperty", publicName: "valueProperty", isSignal: true, isRequired: false, transformFunction: null }, titleProperty: { classPropertyName: "titleProperty", publicName: "titleProperty", isSignal: true, isRequired: false, transformFunction: null }, iconProperty: { classPropertyName: "iconProperty", publicName: "iconProperty", isSignal: true, isRequired: false, transformFunction: null }, emptyText: { classPropertyName: "emptyText", publicName: "emptyText", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, useCheckbox: { classPropertyName: "useCheckbox", publicName: "useCheckbox", isSignal: true, isRequired: false, transformFunction: null }, closeOnSelect: { classPropertyName: "closeOnSelect", publicName: "closeOnSelect", isSignal: true, isRequired: false, transformFunction: null }, maxSelectedShown: { classPropertyName: "maxSelectedShown", publicName: "maxSelectedShown", isSignal: true, isRequired: false, transformFunction: null }, showClear: { classPropertyName: "showClear", publicName: "showClear", isSignal: true, isRequired: false, transformFunction: null }, showFilter: { classPropertyName: "showFilter", publicName: "showFilter", isSignal: true, isRequired: false, transformFunction: null }, showSelectionInfo: { classPropertyName: "showSelectionInfo", publicName: "showSelectionInfo", isSignal: true, isRequired: false, transformFunction: null }, filterItemsOnSelect: { classPropertyName: "filterItemsOnSelect", publicName: "filterItemsOnSelect", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, minHeight: { classPropertyName: "minHeight", publicName: "minHeight", isSignal: true, isRequired: false, transformFunction: null }, maxHeight: { classPropertyName: "maxHeight", publicName: "maxHeight", isSignal: true, isRequired: false, transformFunction: null }, optionsWidth: { classPropertyName: "optionsWidth", publicName: "optionsWidth", isSignal: true, isRequired: false, transformFunction: null }, minOptionsHeight: { classPropertyName: "minOptionsHeight", publicName: "minOptionsHeight", isSignal: true, isRequired: false, transformFunction: null }, maxOptionsHeight: { classPropertyName: "maxOptionsHeight", publicName: "maxOptionsHeight", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, filterFn: { classPropertyName: "filterFn", publicName: "filterFn", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: false, isRequired: false, transformFunction: null } }, outputs: { disabled: "disabledChange", change: "change" }, host: { listeners: { "document:click": "outsideClick($event)", "keydown": "handleKeyDown($event)" }, properties: { "tabIndex": "this.tabIndex", "class.disabled": "this.disabledClass" } }, providers: [
6217
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.18", type: FwMultiSelectMenuComponent, isStandalone: true, selector: "fw-multi-select", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, valueProperty: { classPropertyName: "valueProperty", publicName: "valueProperty", isSignal: true, isRequired: false, transformFunction: null }, titleProperty: { classPropertyName: "titleProperty", publicName: "titleProperty", isSignal: true, isRequired: false, transformFunction: null }, iconProperty: { classPropertyName: "iconProperty", publicName: "iconProperty", isSignal: true, isRequired: false, transformFunction: null }, emptyText: { classPropertyName: "emptyText", publicName: "emptyText", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, useCheckbox: { classPropertyName: "useCheckbox", publicName: "useCheckbox", isSignal: true, isRequired: false, transformFunction: null }, closeOnSelect: { classPropertyName: "closeOnSelect", publicName: "closeOnSelect", isSignal: true, isRequired: false, transformFunction: null }, maxSelectedShown: { classPropertyName: "maxSelectedShown", publicName: "maxSelectedShown", isSignal: true, isRequired: false, transformFunction: null }, showClear: { classPropertyName: "showClear", publicName: "showClear", isSignal: true, isRequired: false, transformFunction: null }, showFilter: { classPropertyName: "showFilter", publicName: "showFilter", isSignal: true, isRequired: false, transformFunction: null }, showSelectionInfo: { classPropertyName: "showSelectionInfo", publicName: "showSelectionInfo", isSignal: true, isRequired: false, transformFunction: null }, filterItemsOnSelect: { classPropertyName: "filterItemsOnSelect", publicName: "filterItemsOnSelect", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, minHeight: { classPropertyName: "minHeight", publicName: "minHeight", isSignal: true, isRequired: false, transformFunction: null }, maxHeight: { classPropertyName: "maxHeight", publicName: "maxHeight", isSignal: true, isRequired: false, transformFunction: null }, optionsWidth: { classPropertyName: "optionsWidth", publicName: "optionsWidth", isSignal: true, isRequired: false, transformFunction: null }, minOptionsHeight: { classPropertyName: "minOptionsHeight", publicName: "minOptionsHeight", isSignal: true, isRequired: false, transformFunction: null }, maxOptionsHeight: { classPropertyName: "maxOptionsHeight", publicName: "maxOptionsHeight", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, filterFn: { classPropertyName: "filterFn", publicName: "filterFn", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: false, isRequired: false, transformFunction: null } }, outputs: { disabled: "disabledChange", change: "change" }, host: { listeners: { "keydown": "handleKeyDown($event)" }, properties: { "tabIndex": "this.tabIndex", "class.disabled": "this.disabledClass" } }, providers: [
6121
6218
  {
6122
6219
  provide: NG_VALUE_ACCESSOR,
6123
6220
  useExisting: forwardRef(() => FwMultiSelectMenuComponent),
6124
6221
  multi: true,
6125
6222
  },
6126
- ], queries: [{ propertyName: "customMenuItems", predicate: FwMenuItemComponent, descendants: true }], viewQueries: [{ propertyName: "menuFilter", first: true, predicate: FwMenuContainerComponent, descendants: true, isSignal: true }, { propertyName: "trigger", first: true, predicate: CdkMenuTrigger, descendants: true }, { propertyName: "menu", first: true, predicate: FwMenuComponent, descendants: true }, { propertyName: "renderedMenuItems", predicate: FwMenuItemComponent, descendants: true }], ngImport: i0, template: "<div [ngStyle]=\"{ width: width() }\" #wrapper>\n <div\n fwMenuRegister\n class=\"chip-grid\"\n [ngClass]=\"[size()]\"\n [ngStyle]=\"{ minHeight: minHeight(), maxHeight: maxHeight() }\"\n [cdkMenuTriggerFor]=\"selectMenu\"\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n [attr.aria-expanded]=\"trigger?.isOpen() || false\"\n [attr.aria-controls]=\"listboxId\"\n >\n @if (value.length === 0) {\n <span class=\"placeholder\">{{ placeholder() }}</span>\n }\n\n <!-- CHIPS -->\n @if (value && value.length > 0) {\n <div class=\"inner-chip-grid\">\n @for (chip of selectedOptions | slice: 0 : maxSelectedShown(); track chip) {\n <fw-chip\n [title]=\"chip[titleProperty()]\"\n [value]=\"chip[valueProperty()]\"\n [icon]=\"chip[iconProperty()]\"\n color=\"primary\"\n [showClose]=\"true\"\n (close)=\"handleChipClose(chip)\"\n [selectable]=\"false\"\n >\n </fw-chip>\n }\n @if (maxSelectedShown() === 0 && value.length > 0) {\n <span class=\"selected-text\"> {{ value.length }} selected </span>\n }\n @if (value.length > maxSelectedShown() && maxSelectedShown() > 0) {\n <span class=\"max-exceeded\"> +{{ value.length - maxSelectedShown() }} more </span>\n }\n </div>\n }\n\n @if (showClear() && value.length > 0) {\n <fw-icon (click)=\"updateValue([])\">close</fw-icon>\n }\n <fw-icon>chevron-down</fw-icon>\n </div>\n\n <!-- MENU -->\n <ng-template #selectMenu>\n <fw-menu-filter\n [keyHandler]=\"handleKeyDown\"\n [showFilter]=\"showFilter()\"\n [focusFilterOnMount]=\"showFilter()\"\n [width]=\"optionsWidth() || wrapper.offsetWidth - 2 + 'px'\"\n [additionalMenuItems]=\"this.customMenuItems.toArray()\"\n [emptyText]=\"emptyText()\"\n [maxHeight]=\"maxOptionsHeight()\"\n [minHeight]=\"minOptionsHeight()\"\n [filterFn]=\"filterFn()\"\n (filteredMenuItemChange)=\"displayedOptions.set($event)\"\n >\n @if (showSelectionInfo()) {\n <div class=\"filter-content\">\n <p>{{ value.length }} selections</p>\n <fw-button variant=\"ghost\" (click)=\"updateValue([])\">Clear</fw-button>\n </div>\n }\n <fw-menu\n role=\"listbox\"\n [id]=\"listboxId\"\n [multiSelect]=\"true\"\n [disabled]=\"disabled()\"\n [value]=\"createArray(selectedValues)\"\n (change)=\"handleMenuChange($event)\"\n >\n @if (customMenuItems.length === 0) {\n @for (item of options(); track item) {\n <fw-menu-item\n [title]=\"item[titleProperty()]\"\n [value]=\"item[valueProperty()]\"\n [icon]=\"item[iconProperty()]\"\n [multiSelect]=\"true\"\n [selected]=\"value.includes(item[valueProperty()])\"\n [showCheckbox]=\"useCheckbox()\"\n (mouseenter)=\"setFocusedIndex(item)\"\n >\n </fw-menu-item>\n }\n }\n <ng-content\n ngProjectAs=\"custom-menu-items\"\n select=\"[fw-menu-item, fw-menu-separator, fw-menu-item-group, fw-menu-header]\"\n ></ng-content>\n </fw-menu>\n </fw-menu-filter>\n </ng-template>\n</div>\n", styles: [":host{pointer-events:auto}:host.disabled{opacity:.4;cursor:not-allowed}:host.disabled>div{pointer-events:none}:host .placeholder{color:var(--typography-light);font-size:14px;padding-left:4px;text-wrap:nowrap;text-overflow:ellipsis;overflow:hidden}:host .chip-grid{min-height:36px;width:100%;box-sizing:border-box;color:var(--typography-light);background:var(--page-light);display:flex;justify-content:space-between;padding:4px;align-items:center;border-radius:6px;overflow:hidden;border:1px solid var(--separations-input);cursor:pointer;font-family:Inter,sans-serif}:host .chip-grid.small{min-height:30px;padding:2px}:host .chip-grid.large{min-height:40px;padding:6px}:host .chip-grid:focus-within{border-color:var(--primary-base)}:host .chip-grid .inner-chip-grid{display:flex;flex-wrap:wrap;gap:4px;overflow:hidden auto;align-items:center;flex:1}:host .chip-grid .inner-chip-grid input{display:inline-flex;width:10px;flex-grow:1}:host .chip-grid fw-icon{font-size:20px}:host .chip-grid fw-icon:hover{color:var(--primary-base)}:host .chip-grid .selected-text{color:var(--typography-base);font-size:14px;padding:0 2px 0 4px;text-wrap:nowrap;text-overflow:ellipsis;overflow:hidden}:host .chip-grid .max-exceeded{font-size:12px}.filter-content{display:flex;align-items:center;justify-content:space-between;padding-left:4px;padding-top:4px}.filter-content p{color:var(--typography-muted);width:fit-content;margin:0}\n"], dependencies: [{ kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: MenuRegisterDirective, selector: "[fwMenuRegister]" }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: CdkMenuTrigger, selector: "[cdkMenuTriggerFor]", inputs: ["cdkMenuTriggerFor", "cdkMenuPosition", "cdkMenuTriggerData", "cdkMenuTriggerTransformOriginOn"], outputs: ["cdkMenuOpened", "cdkMenuClosed"], exportAs: ["cdkMenuTriggerFor"] }, { kind: "component", type: FwChipComponent, selector: "fw-chip", inputs: ["maxWidth", "value", "variant", "color", "icon", "title", "description", "showClose", "closeLabel", "disabled", "selected", "textWrap", "selectable"], outputs: ["close", "select"] }, { kind: "component", type: FwIconComponent, selector: "fw-icon", inputs: ["ariaLabel", "size", "color"] }, { kind: "component", type: FwMenuContainerComponent, selector: "fw-menu-container, fw-menu-filter", inputs: ["width", "maxHeight", "minHeight", "border", "shadow", "showFilter", "filterText", "focusFilterOnMount", "offset", "emptyText", "filterFn", "additionalMenuItems", "additionalGroups", "additionalSeparators", "keyHandler"], outputs: ["filteredMenuItemChange", "filterChanged"] }, { kind: "component", type: FwButtonComponent, selector: "fw-button", inputs: ["color", "size", "variant", "type", "disabled", "fullWidth", "leftIcon", "rightIcon", "focusInitial"] }, { kind: "component", type: FwMenuComponent, selector: "fw-menu", inputs: ["disabled", "size", "multiSelect", "useCheckbox", "value", "role", "id"], outputs: ["disabledChange", "valueChange", "change"] }, { kind: "component", type: FwMenuItemComponent, selector: "fw-menu-item", inputs: ["itemRole", "value", "size", "title", "description", "icon", "iconColor", "disabled", "showCheckbox", "checkboxColor", "multiSelect", "hidden", "collapsed", "href", "target", "subItemsOpen", "mouseEnterHandler", "focused", "selected"], outputs: ["itemRoleChange", "sizeChange", "disabledChange", "showCheckboxChange", "multiSelectChange", "hiddenChange", "subItemsOpenChange", "mouseEnterHandlerChange", "click", "focusedChange", "selectedChange"] }, { kind: "pipe", type: SlicePipe, name: "slice" }] }); }
6223
+ ], queries: [{ propertyName: "customMenuItems", predicate: FwMenuItemComponent, descendants: true }], viewQueries: [{ propertyName: "wrapper", first: true, predicate: ["wrapper"], descendants: true, read: ElementRef, isSignal: true }, { propertyName: "menuFilter", first: true, predicate: FwMenuContainerComponent, descendants: true, isSignal: true }, { propertyName: "trigger", first: true, predicate: CdkMenuTrigger, descendants: true }, { propertyName: "menu", first: true, predicate: FwMenuComponent, descendants: true }, { propertyName: "renderedMenuItems", predicate: FwMenuItemComponent, descendants: true }], ngImport: i0, template: "<div [ngStyle]=\"{ width: width() }\" #wrapper>\n <div\n fwMenuRegister\n class=\"chip-grid\"\n [ngClass]=\"[size()]\"\n [ngStyle]=\"{ minHeight: minHeight(), maxHeight: maxHeight() }\"\n [cdkMenuTriggerFor]=\"selectMenu\"\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n [attr.aria-expanded]=\"trigger?.isOpen() || false\"\n [attr.aria-controls]=\"listboxId\"\n >\n @if (value.length === 0) {\n <span class=\"placeholder\">{{ placeholder() }}</span>\n }\n\n <!-- CHIPS -->\n @if (value && value.length > 0) {\n <div class=\"inner-chip-grid\">\n @for (chip of selectedOptions | slice: 0 : maxSelectedShown(); track chip) {\n <fw-chip\n [title]=\"chip[titleProperty()]\"\n [value]=\"chip[valueProperty()]\"\n [icon]=\"chip[iconProperty()]\"\n color=\"primary\"\n [showClose]=\"true\"\n (close)=\"handleChipClose(chip)\"\n [selectable]=\"false\"\n >\n </fw-chip>\n }\n @if (maxSelectedShown() === 0 && value.length > 0) {\n <span class=\"selected-text\"> {{ value.length }} selected </span>\n }\n @if (value.length > maxSelectedShown() && maxSelectedShown() > 0) {\n <span class=\"max-exceeded\"> +{{ value.length - maxSelectedShown() }} more </span>\n }\n </div>\n }\n\n @if (showClear() && value.length > 0) {\n <fw-icon (click)=\"updateValue([])\">close</fw-icon>\n }\n <fw-icon>chevron-down</fw-icon>\n </div>\n\n <!-- MENU -->\n <ng-template #selectMenu>\n <fw-menu-filter\n [keyHandler]=\"handleKeyDown\"\n [showFilter]=\"showFilter()\"\n [focusFilterOnMount]=\"showFilter()\"\n [width]=\"optionsWidth() || wrapper.offsetWidth - 2 + 'px'\"\n [additionalMenuItems]=\"this.customMenuItems.toArray()\"\n [emptyText]=\"emptyText()\"\n [maxHeight]=\"maxOptionsHeight()\"\n [minHeight]=\"minOptionsHeight()\"\n [filterFn]=\"filterFn()\"\n (filteredMenuItemChange)=\"displayedOptions.set($event)\"\n >\n @if (showSelectionInfo()) {\n <div class=\"filter-content\">\n <p>{{ value.length }} selections</p>\n <fw-button variant=\"ghost\" (click)=\"updateValue([])\">Clear</fw-button>\n </div>\n }\n <fw-menu\n role=\"listbox\"\n [id]=\"listboxId\"\n [multiSelect]=\"true\"\n [disabled]=\"disabled()\"\n [value]=\"createArray(selectedValues)\"\n (change)=\"handleMenuChange($event)\"\n >\n @if (customMenuItems.length === 0) {\n @for (item of options(); track item) {\n <fw-menu-item\n [title]=\"item[titleProperty()]\"\n [value]=\"item[valueProperty()]\"\n [icon]=\"item[iconProperty()]\"\n [multiSelect]=\"true\"\n [selected]=\"value.includes(item[valueProperty()])\"\n [showCheckbox]=\"useCheckbox()\"\n (mouseenter)=\"setFocusedIndex(item)\"\n >\n </fw-menu-item>\n }\n }\n <ng-content\n ngProjectAs=\"custom-menu-items\"\n select=\"[fw-menu-item, fw-menu-separator, fw-menu-item-group, fw-menu-header]\"\n ></ng-content>\n </fw-menu>\n </fw-menu-filter>\n </ng-template>\n</div>\n", styles: [":host{pointer-events:auto}:host.disabled{opacity:.4;cursor:not-allowed}:host.disabled>div{pointer-events:none}:host .placeholder{color:var(--typography-light);font-size:14px;padding-left:4px;text-wrap:nowrap;text-overflow:ellipsis;overflow:hidden}:host .chip-grid{min-height:36px;width:100%;box-sizing:border-box;color:var(--typography-light);background:var(--page-light);display:flex;justify-content:space-between;padding:4px;align-items:center;border-radius:6px;overflow:hidden;border:1px solid var(--separations-input);cursor:pointer;font-family:Inter,sans-serif}:host .chip-grid.small{min-height:30px;padding:2px}:host .chip-grid.large{min-height:40px;padding:6px}:host .chip-grid:focus-within{border-color:var(--primary-base)}:host .chip-grid .inner-chip-grid{display:flex;flex-wrap:wrap;gap:4px;overflow:hidden auto;align-items:center;flex:1}:host .chip-grid .inner-chip-grid input{display:inline-flex;width:10px;flex-grow:1}:host .chip-grid fw-icon{font-size:20px}:host .chip-grid fw-icon:hover{color:var(--primary-base)}:host .chip-grid .selected-text{color:var(--typography-base);font-size:14px;padding:0 2px 0 4px;text-wrap:nowrap;text-overflow:ellipsis;overflow:hidden}:host .chip-grid .max-exceeded{font-size:12px}.filter-content{display:flex;align-items:center;justify-content:space-between;padding-left:4px;padding-top:4px}.filter-content p{color:var(--typography-muted);width:fit-content;margin:0}\n"], dependencies: [{ kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: MenuRegisterDirective, selector: "[fwMenuRegister]" }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: CdkMenuTrigger, selector: "[cdkMenuTriggerFor]", inputs: ["cdkMenuTriggerFor", "cdkMenuPosition", "cdkMenuTriggerData", "cdkMenuTriggerTransformOriginOn"], outputs: ["cdkMenuOpened", "cdkMenuClosed"], exportAs: ["cdkMenuTriggerFor"] }, { kind: "component", type: FwChipComponent, selector: "fw-chip", inputs: ["maxWidth", "value", "variant", "color", "icon", "title", "description", "showClose", "closeLabel", "disabled", "selected", "textWrap", "selectable"], outputs: ["close", "select"] }, { kind: "component", type: FwIconComponent, selector: "fw-icon", inputs: ["ariaLabel", "size", "color"] }, { kind: "component", type: FwMenuContainerComponent, selector: "fw-menu-container, fw-menu-filter", inputs: ["width", "maxHeight", "minHeight", "border", "shadow", "showFilter", "filterText", "focusFilterOnMount", "offset", "emptyText", "filterFn", "additionalMenuItems", "additionalGroups", "additionalSeparators", "keyHandler"], outputs: ["filteredMenuItemChange", "filterChanged"] }, { kind: "component", type: FwButtonComponent, selector: "fw-button", inputs: ["color", "size", "variant", "type", "disabled", "fullWidth", "leftIcon", "rightIcon", "focusInitial"] }, { kind: "component", type: FwMenuComponent, selector: "fw-menu", inputs: ["disabled", "size", "multiSelect", "useCheckbox", "value", "role", "id"], outputs: ["disabledChange", "valueChange", "change"] }, { kind: "component", type: FwMenuItemComponent, selector: "fw-menu-item", inputs: ["itemRole", "value", "size", "title", "description", "icon", "iconColor", "disabled", "showCheckbox", "checkboxColor", "multiSelect", "hidden", "collapsed", "href", "target", "subItemsOpen", "mouseEnterHandler", "focused", "selected"], outputs: ["itemRoleChange", "sizeChange", "disabledChange", "showCheckboxChange", "multiSelectChange", "hiddenChange", "subItemsOpenChange", "mouseEnterHandlerChange", "click", "focusedChange", "selectedChange"] }, { kind: "pipe", type: SlicePipe, name: "slice" }] }); }
6127
6224
  }
6128
6225
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: FwMultiSelectMenuComponent, decorators: [{
6129
6226
  type: Component,
@@ -6146,16 +6243,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImpo
6146
6243
  FwMenuItemComponent,
6147
6244
  SlicePipe,
6148
6245
  ], template: "<div [ngStyle]=\"{ width: width() }\" #wrapper>\n <div\n fwMenuRegister\n class=\"chip-grid\"\n [ngClass]=\"[size()]\"\n [ngStyle]=\"{ minHeight: minHeight(), maxHeight: maxHeight() }\"\n [cdkMenuTriggerFor]=\"selectMenu\"\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n [attr.aria-expanded]=\"trigger?.isOpen() || false\"\n [attr.aria-controls]=\"listboxId\"\n >\n @if (value.length === 0) {\n <span class=\"placeholder\">{{ placeholder() }}</span>\n }\n\n <!-- CHIPS -->\n @if (value && value.length > 0) {\n <div class=\"inner-chip-grid\">\n @for (chip of selectedOptions | slice: 0 : maxSelectedShown(); track chip) {\n <fw-chip\n [title]=\"chip[titleProperty()]\"\n [value]=\"chip[valueProperty()]\"\n [icon]=\"chip[iconProperty()]\"\n color=\"primary\"\n [showClose]=\"true\"\n (close)=\"handleChipClose(chip)\"\n [selectable]=\"false\"\n >\n </fw-chip>\n }\n @if (maxSelectedShown() === 0 && value.length > 0) {\n <span class=\"selected-text\"> {{ value.length }} selected </span>\n }\n @if (value.length > maxSelectedShown() && maxSelectedShown() > 0) {\n <span class=\"max-exceeded\"> +{{ value.length - maxSelectedShown() }} more </span>\n }\n </div>\n }\n\n @if (showClear() && value.length > 0) {\n <fw-icon (click)=\"updateValue([])\">close</fw-icon>\n }\n <fw-icon>chevron-down</fw-icon>\n </div>\n\n <!-- MENU -->\n <ng-template #selectMenu>\n <fw-menu-filter\n [keyHandler]=\"handleKeyDown\"\n [showFilter]=\"showFilter()\"\n [focusFilterOnMount]=\"showFilter()\"\n [width]=\"optionsWidth() || wrapper.offsetWidth - 2 + 'px'\"\n [additionalMenuItems]=\"this.customMenuItems.toArray()\"\n [emptyText]=\"emptyText()\"\n [maxHeight]=\"maxOptionsHeight()\"\n [minHeight]=\"minOptionsHeight()\"\n [filterFn]=\"filterFn()\"\n (filteredMenuItemChange)=\"displayedOptions.set($event)\"\n >\n @if (showSelectionInfo()) {\n <div class=\"filter-content\">\n <p>{{ value.length }} selections</p>\n <fw-button variant=\"ghost\" (click)=\"updateValue([])\">Clear</fw-button>\n </div>\n }\n <fw-menu\n role=\"listbox\"\n [id]=\"listboxId\"\n [multiSelect]=\"true\"\n [disabled]=\"disabled()\"\n [value]=\"createArray(selectedValues)\"\n (change)=\"handleMenuChange($event)\"\n >\n @if (customMenuItems.length === 0) {\n @for (item of options(); track item) {\n <fw-menu-item\n [title]=\"item[titleProperty()]\"\n [value]=\"item[valueProperty()]\"\n [icon]=\"item[iconProperty()]\"\n [multiSelect]=\"true\"\n [selected]=\"value.includes(item[valueProperty()])\"\n [showCheckbox]=\"useCheckbox()\"\n (mouseenter)=\"setFocusedIndex(item)\"\n >\n </fw-menu-item>\n }\n }\n <ng-content\n ngProjectAs=\"custom-menu-items\"\n select=\"[fw-menu-item, fw-menu-separator, fw-menu-item-group, fw-menu-header]\"\n ></ng-content>\n </fw-menu>\n </fw-menu-filter>\n </ng-template>\n</div>\n", styles: [":host{pointer-events:auto}:host.disabled{opacity:.4;cursor:not-allowed}:host.disabled>div{pointer-events:none}:host .placeholder{color:var(--typography-light);font-size:14px;padding-left:4px;text-wrap:nowrap;text-overflow:ellipsis;overflow:hidden}:host .chip-grid{min-height:36px;width:100%;box-sizing:border-box;color:var(--typography-light);background:var(--page-light);display:flex;justify-content:space-between;padding:4px;align-items:center;border-radius:6px;overflow:hidden;border:1px solid var(--separations-input);cursor:pointer;font-family:Inter,sans-serif}:host .chip-grid.small{min-height:30px;padding:2px}:host .chip-grid.large{min-height:40px;padding:6px}:host .chip-grid:focus-within{border-color:var(--primary-base)}:host .chip-grid .inner-chip-grid{display:flex;flex-wrap:wrap;gap:4px;overflow:hidden auto;align-items:center;flex:1}:host .chip-grid .inner-chip-grid input{display:inline-flex;width:10px;flex-grow:1}:host .chip-grid fw-icon{font-size:20px}:host .chip-grid fw-icon:hover{color:var(--primary-base)}:host .chip-grid .selected-text{color:var(--typography-base);font-size:14px;padding:0 2px 0 4px;text-wrap:nowrap;text-overflow:ellipsis;overflow:hidden}:host .chip-grid .max-exceeded{font-size:12px}.filter-content{display:flex;align-items:center;justify-content:space-between;padding-left:4px;padding-top:4px}.filter-content p{color:var(--typography-muted);width:fit-content;margin:0}\n"] }]
6149
- }], propDecorators: { outsideClick: [{
6150
- type: HostListener,
6151
- args: ['document:click', ['$event']]
6152
- }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], valueProperty: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueProperty", required: false }] }], titleProperty: [{ type: i0.Input, args: [{ isSignal: true, alias: "titleProperty", required: false }] }], iconProperty: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconProperty", required: false }] }], emptyText: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyText", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }, { type: i0.Output, args: ["disabledChange"] }], useCheckbox: [{ type: i0.Input, args: [{ isSignal: true, alias: "useCheckbox", required: false }] }], closeOnSelect: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnSelect", required: false }] }], maxSelectedShown: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxSelectedShown", required: false }] }], showClear: [{ type: i0.Input, args: [{ isSignal: true, alias: "showClear", required: false }] }], showFilter: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFilter", required: false }] }], showSelectionInfo: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSelectionInfo", required: false }] }], filterItemsOnSelect: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterItemsOnSelect", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], minHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "minHeight", required: false }] }], maxHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxHeight", required: false }] }], optionsWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionsWidth", required: false }] }], minOptionsHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "minOptionsHeight", required: false }] }], maxOptionsHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxOptionsHeight", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], trigger: [{
6246
+ }], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], valueProperty: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueProperty", required: false }] }], titleProperty: [{ type: i0.Input, args: [{ isSignal: true, alias: "titleProperty", required: false }] }], iconProperty: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconProperty", required: false }] }], emptyText: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyText", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }, { type: i0.Output, args: ["disabledChange"] }], useCheckbox: [{ type: i0.Input, args: [{ isSignal: true, alias: "useCheckbox", required: false }] }], closeOnSelect: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnSelect", required: false }] }], maxSelectedShown: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxSelectedShown", required: false }] }], showClear: [{ type: i0.Input, args: [{ isSignal: true, alias: "showClear", required: false }] }], showFilter: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFilter", required: false }] }], showSelectionInfo: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSelectionInfo", required: false }] }], filterItemsOnSelect: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterItemsOnSelect", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], minHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "minHeight", required: false }] }], maxHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxHeight", required: false }] }], optionsWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionsWidth", required: false }] }], minOptionsHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "minOptionsHeight", required: false }] }], maxOptionsHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxOptionsHeight", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], trigger: [{
6153
6247
  type: ViewChild,
6154
6248
  args: [CdkMenuTrigger]
6155
6249
  }], menu: [{
6156
6250
  type: ViewChild,
6157
6251
  args: [FwMenuComponent]
6158
- }], menuFilter: [{ type: i0.ViewChild, args: [i0.forwardRef(() => FwMenuContainerComponent), { isSignal: true }] }], renderedMenuItems: [{
6252
+ }], wrapper: [{ type: i0.ViewChild, args: ['wrapper', { ...{ read: ElementRef }, isSignal: true }] }], menuFilter: [{ type: i0.ViewChild, args: [i0.forwardRef(() => FwMenuContainerComponent), { isSignal: true }] }], renderedMenuItems: [{
6159
6253
  type: ViewChildren,
6160
6254
  args: [FwMenuItemComponent]
6161
6255
  }], customMenuItems: [{
@@ -6182,28 +6276,34 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImpo
6182
6276
  * @see [Vision storybook](https://cdn.flywheel.io/docs/vision/master/?path=/docs/form-controls-select--docs)
6183
6277
  */
6184
6278
  class FwSelectMenuComponent {
6185
- outsideClick(evt) {
6186
- // Check if click is inside the overlay (menu items) but NOT on the input trigger
6187
- const clickedElement = evt.target;
6188
- const overlayPane = clickedElement.closest('.cdk-overlay-pane');
6189
- const clickedInsideOverlay = overlayPane && !clickedElement.closest('fw-text-input');
6190
- // Don't process outside clicks when clicking on menu items (but not the trigger input)
6191
- if (clickedInsideOverlay) {
6279
+ /**
6280
+ * Closes the options panel when a click lands outside of both the trigger and the panel.
6281
+ *
6282
+ * Registered by hand instead of through `@HostListener('document:click')` so that it runs in the
6283
+ * capture phase: a number of components in the library (`fw-menu-item`, `fw-chip`, `fw-navbar-item`,
6284
+ * the dialog backdrop, ...) call `stopPropagation()` on click, which would otherwise keep this
6285
+ * listener from ever seeing the click and leave the panel open.
6286
+ */
6287
+ outsideClick(event) {
6288
+ if (!this.isMenuOpen() || this.isEventInsideSelect(event)) {
6192
6289
  return;
6193
6290
  }
6194
- if (this._isOpen && evt.target.nodeName !== 'INPUT') {
6195
- this.onTouched();
6196
- this.close();
6197
- this._isOpen = false;
6198
- }
6199
- // Sync _isOpen state with actual trigger state
6200
- if (this.trigger && this.trigger.isOpen()) {
6201
- this._isOpen = true;
6202
- }
6203
- else {
6204
- this._isOpen = false;
6291
+ // Typing moves `selectValue` onto the highlighted match without committing it, so a dismissed
6292
+ // filter has to be rolled back or the input ends up displaying an option that was never picked
6293
+ const hadUncommittedFilter = this.isTyping();
6294
+ this.onTouched();
6295
+ this.close();
6296
+ this.inFocusOpen = false;
6297
+ if (hadUncommittedFilter) {
6298
+ this.selectValue.set(this.displayValueOnOpen);
6299
+ this.selectTitle.set(this.displayTitleOnOpen);
6300
+ this.updateHighlighting();
6205
6301
  }
6206
6302
  }
6303
+ /** Whether an event originated on this select's control box or inside its rendered options panel */
6304
+ isEventInsideSelect(event) {
6305
+ return isEventInside(event, this.wrapper()?.nativeElement, this.panel()?.hostElement);
6306
+ }
6207
6307
  get disabledClass() {
6208
6308
  return this.disabled();
6209
6309
  }
@@ -6218,7 +6318,9 @@ class FwSelectMenuComponent {
6218
6318
  }
6219
6319
  constructor() {
6220
6320
  this.ngControl = inject(NgControl, { optional: true, self: true });
6321
+ this.document = inject(DOCUMENT);
6221
6322
  this.listboxId = inject(_IdGenerator).getId('fw-select-listbox-');
6323
+ this.documentClickListener = (event) => this.outsideClick(event);
6222
6324
  this.options = input([], ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
6223
6325
  this.valueProperty = input('value', ...(ngDevMode ? [{ debugName: "valueProperty" }] : /* istanbul ignore next */ []));
6224
6326
  this.useFullOptionAsValue = input(false, ...(ngDevMode ? [{ debugName: "useFullOptionAsValue" }] : /* istanbul ignore next */ []));
@@ -6237,6 +6339,13 @@ class FwSelectMenuComponent {
6237
6339
  this.size = input('medium', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
6238
6340
  this.placeholder = input('Select something...', ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
6239
6341
  this.menu = viewChild(FwMenuComponent, ...(ngDevMode ? [{ debugName: "menu" }] : /* istanbul ignore next */ []));
6342
+ /**
6343
+ * The control itself, which is only as wide as `width()`. The host element is a block element and
6344
+ * so can be considerably wider, and a click in that gap is an outside click.
6345
+ */
6346
+ this.wrapper = viewChild('wrapper', { ...(ngDevMode ? { debugName: "wrapper" } : /* istanbul ignore next */ {}), read: ElementRef });
6347
+ /** The options panel, `undefined` while it is closed since it only exists inside the cdk overlay */
6348
+ this.panel = viewChild(FwMenuContainerComponent, ...(ngDevMode ? [{ debugName: "panel" }] : /* istanbul ignore next */ []));
6240
6349
  this.menuItems = contentChildren(FwMenuItemComponent, { ...(ngDevMode ? { debugName: "menuItems" } : /* istanbul ignore next */ {}), descendants: true });
6241
6350
  this.viewMenuItems = viewChildren(FwMenuItemComponent, ...(ngDevMode ? [{ debugName: "viewMenuItems" }] : /* istanbul ignore next */ []));
6242
6351
  this.menuItemGroups = contentChildren(FwMenuItemGroupComponent, { ...(ngDevMode ? { debugName: "menuItemGroups" } : /* istanbul ignore next */ {}), descendants: true });
@@ -6248,9 +6357,11 @@ class FwSelectMenuComponent {
6248
6357
  this.filterValue = signal('', ...(ngDevMode ? [{ debugName: "filterValue" }] : /* istanbul ignore next */ []));
6249
6358
  this.menuItemClickSubscriptions = [];
6250
6359
  this.subscriptions = [];
6251
- this._isOpen = false;
6252
6360
  this.focused = 0;
6253
6361
  this.inFocusOpen = false;
6362
+ /** Displayed value and title as of the last time the panel was opened, used to undo a dismissed filter */
6363
+ this.displayValueOnOpen = '';
6364
+ this.displayTitleOnOpen = '';
6254
6365
  this.isTyping = signal(false, ...(ngDevMode ? [{ debugName: "isTyping" }] : /* istanbul ignore next */ []));
6255
6366
  this.valueDisplayFn = input((menuItem) => menuItem.title() || '', ...(ngDevMode ? [{ debugName: "valueDisplayFn" }] : /* istanbul ignore next */ []));
6256
6367
  this.defaultFilterFn = (filter, items) => items.filter((item) => {
@@ -6289,7 +6400,9 @@ class FwSelectMenuComponent {
6289
6400
  return displayFn(selectedMenuItem);
6290
6401
  }
6291
6402
  const selectedOption = options.find((opt) => opt[this.valueProperty()]?.toString() === currentValue);
6292
- return selectedOption?.[this.titleProperty()] || '';
6403
+ // `||` would swallow a legitimate `0` title and leave the input showing its placeholder, and it
6404
+ // would also let a non-string title through into this `linkedSignal<string>` untouched
6405
+ return String(selectedOption?.[this.titleProperty()] ?? '');
6293
6406
  }, ...(ngDevMode ? [{ debugName: "selectTitle" }] : /* istanbul ignore next */ []));
6294
6407
  // Watch for menu items changes and re-subscribe
6295
6408
  this.menuItemsWatcher = effect(() => {
@@ -6331,11 +6444,14 @@ class FwSelectMenuComponent {
6331
6444
  }
6332
6445
  }
6333
6446
  ngOnInit() {
6447
+ this.document.addEventListener('click', this.documentClickListener, true);
6334
6448
  const onOpenSub = this.trigger.opened.subscribe(() => {
6335
6449
  // Initialize navigation state synchronously so the first arrow key press
6336
6450
  // navigates relative to the selected item rather than being swallowed by setup
6337
6451
  this.inFocusOpen = true;
6338
6452
  this.preFocusValue = this.value;
6453
+ this.displayValueOnOpen = this.selectValue();
6454
+ this.displayTitleOnOpen = this.selectTitle();
6339
6455
  this.initializeFocusedIndex();
6340
6456
  const currentValue = this.selectValue();
6341
6457
  setTimeout(() => {
@@ -6371,6 +6487,7 @@ class FwSelectMenuComponent {
6371
6487
  }
6372
6488
  }
6373
6489
  ngOnDestroy() {
6490
+ this.document.removeEventListener('click', this.documentClickListener, true);
6374
6491
  this.menuItemClickSubscriptions.forEach((sub) => sub.unsubscribe());
6375
6492
  this.subscriptions.forEach((sub) => sub.unsubscribe());
6376
6493
  }
@@ -6418,14 +6535,22 @@ class FwSelectMenuComponent {
6418
6535
  this.close();
6419
6536
  this.inFocusOpen = false;
6420
6537
  }
6538
+ /**
6539
+ * Whether the options panel is currently open. The single place this class asks that question -
6540
+ * the trigger owns the state, so there is no local flag to keep in sync.
6541
+ *
6542
+ * ! Deliberately a plain method, not a `computed()` ! The trigger reports its state from the
6543
+ * overlay ref rather than from a signal, so a computed would memoize the first answer and stop
6544
+ * tracking, silently freezing `aria-expanded`.
6545
+ */
6421
6546
  isMenuOpen() {
6422
- return this.trigger.isOpen();
6547
+ return this.trigger?.isOpen() ?? false;
6423
6548
  }
6424
6549
  /**
6425
6550
  * The DOM id of the currently active/highlighted option, for aria-activedescendant on the combobox input.
6426
6551
  */
6427
6552
  getActiveDescendantId() {
6428
- if (!this.trigger.isOpen()) {
6553
+ if (!this.isMenuOpen()) {
6429
6554
  return undefined;
6430
6555
  }
6431
6556
  const availableItems = this.getAvailableItems();
@@ -6446,7 +6571,7 @@ class FwSelectMenuComponent {
6446
6571
  getAvailableItems() {
6447
6572
  // If using options input, return filtered options excluding disabled ones
6448
6573
  if (this.options().length > 0) {
6449
- return this.filteredOptions().filter(opt => !opt['disabled']);
6574
+ return this.filteredOptions().filter((opt) => !opt['disabled']);
6450
6575
  }
6451
6576
  // If using content projection, filter by typeahead text and exclude disabled items
6452
6577
  if (this.menuItems().length > 0) {
@@ -6589,7 +6714,6 @@ class FwSelectMenuComponent {
6589
6714
  if (hasSelection) {
6590
6715
  this.isTyping.set(true);
6591
6716
  this.trigger.open();
6592
- this._isOpen = true;
6593
6717
  this.inFocusOpen = true;
6594
6718
  this.preFocusValue = this.value;
6595
6719
  this.initializeFocusedIndex();
@@ -6615,7 +6739,7 @@ class FwSelectMenuComponent {
6615
6739
  'PageDown',
6616
6740
  'Delete',
6617
6741
  ].includes(event.key);
6618
- if (!this.trigger.isOpen() &&
6742
+ if (!this.isMenuOpen() &&
6619
6743
  !this.isTyping() &&
6620
6744
  this.selectTitle() &&
6621
6745
  event.key.length === 1 &&
@@ -6627,7 +6751,6 @@ class FwSelectMenuComponent {
6627
6751
  // Just switch to typing mode and the input event will update filterValue
6628
6752
  this.isTyping.set(true);
6629
6753
  this.trigger.open();
6630
- this._isOpen = true;
6631
6754
  this.inFocusOpen = true;
6632
6755
  this.preFocusValue = this.value;
6633
6756
  this.initializeFocusedIndex();
@@ -6640,7 +6763,7 @@ class FwSelectMenuComponent {
6640
6763
  // Let default behavior happen, onInputChange will handle it
6641
6764
  return;
6642
6765
  }
6643
- if (this.trigger.isOpen()) {
6766
+ if (this.isMenuOpen()) {
6644
6767
  if (this.inFocusOpen) {
6645
6768
  if (event.key === 'ArrowDown') {
6646
6769
  event.preventDefault();
@@ -6752,7 +6875,7 @@ class FwSelectMenuComponent {
6752
6875
  }
6753
6876
  }
6754
6877
  handleKeyUp(event) {
6755
- if (this.trigger.isOpen()) {
6878
+ if (this.isMenuOpen()) {
6756
6879
  if (event.key === 'Escape') {
6757
6880
  this.isTyping.set(false);
6758
6881
  this.close();
@@ -6824,7 +6947,6 @@ class FwSelectMenuComponent {
6824
6947
  this.trigger.close();
6825
6948
  this.filterValue.set('');
6826
6949
  this.filterChanged.emit(this.filterValue());
6827
- this._isOpen = false;
6828
6950
  this.isTyping.set(false);
6829
6951
  }
6830
6952
  onFilterChanged(value) {
@@ -6878,13 +7000,12 @@ class FwSelectMenuComponent {
6878
7000
  }, 0);
6879
7001
  this.filterChanged.emit(this.filterValue());
6880
7002
  // Auto-open dropdown when user starts typing
6881
- if (this.filterValue() && !this.trigger.isOpen()) {
7003
+ if (this.filterValue() && !this.isMenuOpen()) {
6882
7004
  this.trigger.open();
6883
- this._isOpen = true;
6884
7005
  }
6885
7006
  }
6886
7007
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: FwSelectMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
6887
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.18", type: FwSelectMenuComponent, isStandalone: true, selector: "fw-select", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, valueProperty: { classPropertyName: "valueProperty", publicName: "valueProperty", isSignal: true, isRequired: false, transformFunction: null }, useFullOptionAsValue: { classPropertyName: "useFullOptionAsValue", publicName: "useFullOptionAsValue", isSignal: true, isRequired: false, transformFunction: null }, titleProperty: { classPropertyName: "titleProperty", publicName: "titleProperty", isSignal: true, isRequired: false, transformFunction: null }, iconProperty: { classPropertyName: "iconProperty", publicName: "iconProperty", isSignal: true, isRequired: false, transformFunction: null }, staticIcon: { classPropertyName: "staticIcon", publicName: "staticIcon", isSignal: true, isRequired: false, transformFunction: null }, descriptionProperty: { classPropertyName: "descriptionProperty", publicName: "descriptionProperty", isSignal: true, isRequired: false, transformFunction: null }, showFilter: { classPropertyName: "showFilter", publicName: "showFilter", isSignal: true, isRequired: false, transformFunction: null }, showReset: { classPropertyName: "showReset", publicName: "showReset", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, errored: { classPropertyName: "errored", publicName: "errored", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, optionsWidth: { classPropertyName: "optionsWidth", publicName: "optionsWidth", isSignal: true, isRequired: false, transformFunction: null }, minOptionsHeight: { classPropertyName: "minOptionsHeight", publicName: "minOptionsHeight", isSignal: true, isRequired: false, transformFunction: null }, maxOptionsHeight: { classPropertyName: "maxOptionsHeight", publicName: "maxOptionsHeight", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, valueDisplayFn: { classPropertyName: "valueDisplayFn", publicName: "valueDisplayFn", isSignal: true, isRequired: false, transformFunction: null }, filterFn: { classPropertyName: "filterFn", publicName: "filterFn", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: false, isRequired: false, transformFunction: null } }, outputs: { disabled: "disabledChange", change: "change", filterChanged: "filterChanged" }, host: { listeners: { "document:click": "outsideClick($event)" }, properties: { "class.disabled": "this.disabledClass" } }, queries: [{ propertyName: "menuItems", predicate: FwMenuItemComponent, descendants: true, isSignal: true }, { propertyName: "menuItemGroups", predicate: FwMenuItemGroupComponent, descendants: true, isSignal: true }, { propertyName: "menuSeparators", predicate: FwMenuSeparatorComponent, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "menu", first: true, predicate: FwMenuComponent, descendants: true, isSignal: true }, { propertyName: "viewMenuItems", predicate: FwMenuItemComponent, descendants: true, isSignal: true }, { propertyName: "trigger", first: true, predicate: CdkMenuTrigger, descendants: true, static: true }, { propertyName: "textInput", first: true, predicate: FwTextInputComponent, descendants: true }], ngImport: i0, template: "<div #wrapper [style.width]=\"width()\">\n <fw-text-input\n fwMenuRegister\n [cdkMenuTriggerFor]=\"selectMenu\"\n [value]=\"inputDisplayValue()\"\n [leftIcon]=\"staticIcon() || selectIcon || null\"\n [rightIcon]=\"(selectTitle()&&showReset())?'close-circled':'chevron-down'\"\n (rightIconAction)=\"handleReset()\"\n [useActionableIcons]=\"true\"\n [placeholder]=\"placeholder()\"\n [size]=\"size()\"\n [error]=\"errored() || (invalid && touched)\"\n (input)=\"onInputChange($event)\"\n (keyup)=\"handleKeyUp($event)\"\n (keydown)=\"handleKeyDown($event)\"\n (focus)=\"handleFocus()\"\n (click)=\"handleInputClick()\"\n [readOnly]=\"false\"\n role=\"combobox\"\n [ariaExpanded]=\"isMenuOpen()\"\n [ariaControls]=\"listboxId\"\n ariaAutocomplete=\"list\"\n [ariaActiveDescendant]=\"getActiveDescendantId()\">\n </fw-text-input>\n <ng-template #selectMenu>\n @if (!disabled()) {\n <fw-menu-container\n [filterFn]=\"filterFn()\"\n [filterText]=\"filterValue()\"\n [additionalMenuItems]=\"menuItems()\"\n [additionalGroups]=\"menuItemGroups()\"\n [additionalSeparators]=\"menuSeparators()\"\n [showFilter]=\"showFilter()\" [width]=\"optionsWidth() || wrapper.offsetWidth + 'px'\"\n [maxHeight]=\"maxOptionsHeight()\" [minHeight]=\"minOptionsHeight()\" (filterChanged)=\"onFilterChanged($event)\">\n <fw-menu\n role=\"listbox\"\n [id]=\"listboxId\"\n [disabled]=\"disabled()\"\n [value]=\"selectValue()\"\n (change)=\"handleClick($any($event))\"\n >\n @if (menuItems().length === 0) {\n @for (item of optionsWithValues(); track item.trackingId) {\n <fw-menu-item\n [title]=\"item.raw[titleProperty()]?.toString()\"\n [description]=\"$any(item.raw[descriptionProperty()])\"\n [value]=\"item.value\"\n [icon]=\"$any(item.raw[iconProperty()])\"\n [disabled]=\"$any(item.raw).disabled\"\n />\n }\n }\n <div #menuContentWrapper>\n <ng-content select=\"[fw-menu-item, fw-menu-separator, fw-menu-item-group]\"></ng-content>\n </div>\n </fw-menu>\n </fw-menu-container>\n }\n </ng-template>\n</div>\n", styles: [":host{box-sizing:border-box;max-width:100%}:host>div{cursor:pointer}:host.disabled{opacity:.4;cursor:not-allowed}:host.disabled>div{pointer-events:none}\n"], dependencies: [{ kind: "component", type: FwTextInputComponent, selector: "fw-text-input", inputs: ["disabled", "useActionableIcons", "leftIcon", "rightIcon", "leftIconLabel", "rightIconLabel", "prefix", "context", "ariaLabel", "required", "role", "ariaExpanded", "ariaControls", "ariaAutocomplete", "ariaActiveDescendant", "helperText", "errorText", "errorInIconTooltip", "placeholder", "readOnly", "size", "type", "maxLength", "autofocus", "autocomplete", "value", "error", "width"], outputs: ["disabledChange", "leftIconAction", "rightIconAction"] }, { kind: "directive", type: MenuRegisterDirective, selector: "[fwMenuRegister]" }, { kind: "directive", type: CdkMenuTrigger, selector: "[cdkMenuTriggerFor]", inputs: ["cdkMenuTriggerFor", "cdkMenuPosition", "cdkMenuTriggerData", "cdkMenuTriggerTransformOriginOn"], outputs: ["cdkMenuOpened", "cdkMenuClosed"], exportAs: ["cdkMenuTriggerFor"] }, { kind: "component", type: FwMenuContainerComponent, selector: "fw-menu-container, fw-menu-filter", inputs: ["width", "maxHeight", "minHeight", "border", "shadow", "showFilter", "filterText", "focusFilterOnMount", "offset", "emptyText", "filterFn", "additionalMenuItems", "additionalGroups", "additionalSeparators", "keyHandler"], outputs: ["filteredMenuItemChange", "filterChanged"] }, { kind: "component", type: FwMenuComponent, selector: "fw-menu", inputs: ["disabled", "size", "multiSelect", "useCheckbox", "value", "role", "id"], outputs: ["disabledChange", "valueChange", "change"] }, { kind: "component", type: FwMenuItemComponent, selector: "fw-menu-item", inputs: ["itemRole", "value", "size", "title", "description", "icon", "iconColor", "disabled", "showCheckbox", "checkboxColor", "multiSelect", "hidden", "collapsed", "href", "target", "subItemsOpen", "mouseEnterHandler", "focused", "selected"], outputs: ["itemRoleChange", "sizeChange", "disabledChange", "showCheckboxChange", "multiSelectChange", "hiddenChange", "subItemsOpenChange", "mouseEnterHandlerChange", "click", "focusedChange", "selectedChange"] }] }); }
7008
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.18", type: FwSelectMenuComponent, isStandalone: true, selector: "fw-select", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, valueProperty: { classPropertyName: "valueProperty", publicName: "valueProperty", isSignal: true, isRequired: false, transformFunction: null }, useFullOptionAsValue: { classPropertyName: "useFullOptionAsValue", publicName: "useFullOptionAsValue", isSignal: true, isRequired: false, transformFunction: null }, titleProperty: { classPropertyName: "titleProperty", publicName: "titleProperty", isSignal: true, isRequired: false, transformFunction: null }, iconProperty: { classPropertyName: "iconProperty", publicName: "iconProperty", isSignal: true, isRequired: false, transformFunction: null }, staticIcon: { classPropertyName: "staticIcon", publicName: "staticIcon", isSignal: true, isRequired: false, transformFunction: null }, descriptionProperty: { classPropertyName: "descriptionProperty", publicName: "descriptionProperty", isSignal: true, isRequired: false, transformFunction: null }, showFilter: { classPropertyName: "showFilter", publicName: "showFilter", isSignal: true, isRequired: false, transformFunction: null }, showReset: { classPropertyName: "showReset", publicName: "showReset", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, errored: { classPropertyName: "errored", publicName: "errored", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, optionsWidth: { classPropertyName: "optionsWidth", publicName: "optionsWidth", isSignal: true, isRequired: false, transformFunction: null }, minOptionsHeight: { classPropertyName: "minOptionsHeight", publicName: "minOptionsHeight", isSignal: true, isRequired: false, transformFunction: null }, maxOptionsHeight: { classPropertyName: "maxOptionsHeight", publicName: "maxOptionsHeight", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, valueDisplayFn: { classPropertyName: "valueDisplayFn", publicName: "valueDisplayFn", isSignal: true, isRequired: false, transformFunction: null }, filterFn: { classPropertyName: "filterFn", publicName: "filterFn", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: false, isRequired: false, transformFunction: null } }, outputs: { disabled: "disabledChange", change: "change", filterChanged: "filterChanged" }, host: { properties: { "class.disabled": "this.disabledClass" } }, queries: [{ propertyName: "menuItems", predicate: FwMenuItemComponent, descendants: true, isSignal: true }, { propertyName: "menuItemGroups", predicate: FwMenuItemGroupComponent, descendants: true, isSignal: true }, { propertyName: "menuSeparators", predicate: FwMenuSeparatorComponent, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "menu", first: true, predicate: FwMenuComponent, descendants: true, isSignal: true }, { propertyName: "wrapper", first: true, predicate: ["wrapper"], descendants: true, read: ElementRef, isSignal: true }, { propertyName: "panel", first: true, predicate: FwMenuContainerComponent, descendants: true, isSignal: true }, { propertyName: "viewMenuItems", predicate: FwMenuItemComponent, descendants: true, isSignal: true }, { propertyName: "trigger", first: true, predicate: CdkMenuTrigger, descendants: true, static: true }, { propertyName: "textInput", first: true, predicate: FwTextInputComponent, descendants: true }], ngImport: i0, template: "<div #wrapper [style.width]=\"width()\">\n <fw-text-input\n fwMenuRegister\n [cdkMenuTriggerFor]=\"selectMenu\"\n [value]=\"inputDisplayValue()\"\n [leftIcon]=\"staticIcon() || selectIcon || null\"\n [rightIcon]=\"(selectTitle()&&showReset())?'close-circled':'chevron-down'\"\n (rightIconAction)=\"handleReset()\"\n [useActionableIcons]=\"true\"\n [placeholder]=\"placeholder()\"\n [size]=\"size()\"\n [error]=\"errored() || (invalid && touched)\"\n (input)=\"onInputChange($event)\"\n (keyup)=\"handleKeyUp($event)\"\n (keydown)=\"handleKeyDown($event)\"\n (focus)=\"handleFocus()\"\n (click)=\"handleInputClick()\"\n [readOnly]=\"false\"\n role=\"combobox\"\n [ariaExpanded]=\"isMenuOpen()\"\n [ariaControls]=\"listboxId\"\n ariaAutocomplete=\"list\"\n [ariaActiveDescendant]=\"getActiveDescendantId()\">\n </fw-text-input>\n <ng-template #selectMenu>\n @if (!disabled()) {\n <fw-menu-container\n [filterFn]=\"filterFn()\"\n [filterText]=\"filterValue()\"\n [additionalMenuItems]=\"menuItems()\"\n [additionalGroups]=\"menuItemGroups()\"\n [additionalSeparators]=\"menuSeparators()\"\n [showFilter]=\"showFilter()\" [width]=\"optionsWidth() || wrapper.offsetWidth + 'px'\"\n [maxHeight]=\"maxOptionsHeight()\" [minHeight]=\"minOptionsHeight()\" (filterChanged)=\"onFilterChanged($event)\">\n <fw-menu\n role=\"listbox\"\n [id]=\"listboxId\"\n [disabled]=\"disabled()\"\n [value]=\"selectValue()\"\n (change)=\"handleClick($any($event))\"\n >\n @if (menuItems().length === 0) {\n @for (item of optionsWithValues(); track item.trackingId) {\n <fw-menu-item\n [title]=\"item.raw[titleProperty()]?.toString()\"\n [description]=\"$any(item.raw[descriptionProperty()])\"\n [value]=\"item.value\"\n [icon]=\"$any(item.raw[iconProperty()])\"\n [disabled]=\"$any(item.raw).disabled\"\n />\n }\n }\n <div #menuContentWrapper>\n <ng-content select=\"[fw-menu-item, fw-menu-separator, fw-menu-item-group]\"></ng-content>\n </div>\n </fw-menu>\n </fw-menu-container>\n }\n </ng-template>\n</div>\n", styles: [":host{box-sizing:border-box;max-width:100%}:host>div{cursor:pointer}:host.disabled{opacity:.4;cursor:not-allowed}:host.disabled>div{pointer-events:none}\n"], dependencies: [{ kind: "component", type: FwTextInputComponent, selector: "fw-text-input", inputs: ["disabled", "useActionableIcons", "leftIcon", "rightIcon", "leftIconLabel", "rightIconLabel", "prefix", "context", "ariaLabel", "required", "role", "ariaExpanded", "ariaControls", "ariaAutocomplete", "ariaActiveDescendant", "helperText", "errorText", "errorInIconTooltip", "placeholder", "readOnly", "size", "type", "maxLength", "autofocus", "autocomplete", "value", "error", "width"], outputs: ["disabledChange", "leftIconAction", "rightIconAction"] }, { kind: "directive", type: MenuRegisterDirective, selector: "[fwMenuRegister]" }, { kind: "directive", type: CdkMenuTrigger, selector: "[cdkMenuTriggerFor]", inputs: ["cdkMenuTriggerFor", "cdkMenuPosition", "cdkMenuTriggerData", "cdkMenuTriggerTransformOriginOn"], outputs: ["cdkMenuOpened", "cdkMenuClosed"], exportAs: ["cdkMenuTriggerFor"] }, { kind: "component", type: FwMenuContainerComponent, selector: "fw-menu-container, fw-menu-filter", inputs: ["width", "maxHeight", "minHeight", "border", "shadow", "showFilter", "filterText", "focusFilterOnMount", "offset", "emptyText", "filterFn", "additionalMenuItems", "additionalGroups", "additionalSeparators", "keyHandler"], outputs: ["filteredMenuItemChange", "filterChanged"] }, { kind: "component", type: FwMenuComponent, selector: "fw-menu", inputs: ["disabled", "size", "multiSelect", "useCheckbox", "value", "role", "id"], outputs: ["disabledChange", "valueChange", "change"] }, { kind: "component", type: FwMenuItemComponent, selector: "fw-menu-item", inputs: ["itemRole", "value", "size", "title", "description", "icon", "iconColor", "disabled", "showCheckbox", "checkboxColor", "multiSelect", "hidden", "collapsed", "href", "target", "subItemsOpen", "mouseEnterHandler", "focused", "selected"], outputs: ["itemRoleChange", "sizeChange", "disabledChange", "showCheckboxChange", "multiSelectChange", "hiddenChange", "subItemsOpenChange", "mouseEnterHandlerChange", "click", "focusedChange", "selectedChange"] }] }); }
6888
7009
  }
6889
7010
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: FwSelectMenuComponent, decorators: [{
6890
7011
  type: Component,
@@ -6896,10 +7017,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImpo
6896
7017
  FwMenuComponent,
6897
7018
  FwMenuItemComponent,
6898
7019
  ], template: "<div #wrapper [style.width]=\"width()\">\n <fw-text-input\n fwMenuRegister\n [cdkMenuTriggerFor]=\"selectMenu\"\n [value]=\"inputDisplayValue()\"\n [leftIcon]=\"staticIcon() || selectIcon || null\"\n [rightIcon]=\"(selectTitle()&&showReset())?'close-circled':'chevron-down'\"\n (rightIconAction)=\"handleReset()\"\n [useActionableIcons]=\"true\"\n [placeholder]=\"placeholder()\"\n [size]=\"size()\"\n [error]=\"errored() || (invalid && touched)\"\n (input)=\"onInputChange($event)\"\n (keyup)=\"handleKeyUp($event)\"\n (keydown)=\"handleKeyDown($event)\"\n (focus)=\"handleFocus()\"\n (click)=\"handleInputClick()\"\n [readOnly]=\"false\"\n role=\"combobox\"\n [ariaExpanded]=\"isMenuOpen()\"\n [ariaControls]=\"listboxId\"\n ariaAutocomplete=\"list\"\n [ariaActiveDescendant]=\"getActiveDescendantId()\">\n </fw-text-input>\n <ng-template #selectMenu>\n @if (!disabled()) {\n <fw-menu-container\n [filterFn]=\"filterFn()\"\n [filterText]=\"filterValue()\"\n [additionalMenuItems]=\"menuItems()\"\n [additionalGroups]=\"menuItemGroups()\"\n [additionalSeparators]=\"menuSeparators()\"\n [showFilter]=\"showFilter()\" [width]=\"optionsWidth() || wrapper.offsetWidth + 'px'\"\n [maxHeight]=\"maxOptionsHeight()\" [minHeight]=\"minOptionsHeight()\" (filterChanged)=\"onFilterChanged($event)\">\n <fw-menu\n role=\"listbox\"\n [id]=\"listboxId\"\n [disabled]=\"disabled()\"\n [value]=\"selectValue()\"\n (change)=\"handleClick($any($event))\"\n >\n @if (menuItems().length === 0) {\n @for (item of optionsWithValues(); track item.trackingId) {\n <fw-menu-item\n [title]=\"item.raw[titleProperty()]?.toString()\"\n [description]=\"$any(item.raw[descriptionProperty()])\"\n [value]=\"item.value\"\n [icon]=\"$any(item.raw[iconProperty()])\"\n [disabled]=\"$any(item.raw).disabled\"\n />\n }\n }\n <div #menuContentWrapper>\n <ng-content select=\"[fw-menu-item, fw-menu-separator, fw-menu-item-group]\"></ng-content>\n </div>\n </fw-menu>\n </fw-menu-container>\n }\n </ng-template>\n</div>\n", styles: [":host{box-sizing:border-box;max-width:100%}:host>div{cursor:pointer}:host.disabled{opacity:.4;cursor:not-allowed}:host.disabled>div{pointer-events:none}\n"] }]
6899
- }], ctorParameters: () => [], propDecorators: { outsideClick: [{
6900
- type: HostListener,
6901
- args: ['document:click', ['$event']]
6902
- }], disabledClass: [{
7020
+ }], ctorParameters: () => [], propDecorators: { disabledClass: [{
6903
7021
  type: HostBinding,
6904
7022
  args: ['class.disabled']
6905
7023
  }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], valueProperty: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueProperty", required: false }] }], useFullOptionAsValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "useFullOptionAsValue", required: false }] }], titleProperty: [{ type: i0.Input, args: [{ isSignal: true, alias: "titleProperty", required: false }] }], iconProperty: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconProperty", required: false }] }], staticIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "staticIcon", required: false }] }], descriptionProperty: [{ type: i0.Input, args: [{ isSignal: true, alias: "descriptionProperty", required: false }] }], showFilter: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFilter", required: false }] }], showReset: [{ type: i0.Input, args: [{ isSignal: true, alias: "showReset", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }, { type: i0.Output, args: ["disabledChange"] }], errored: [{ type: i0.Input, args: [{ isSignal: true, alias: "errored", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], optionsWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionsWidth", required: false }] }], minOptionsHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "minOptionsHeight", required: false }] }], maxOptionsHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxOptionsHeight", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], trigger: [{
@@ -6908,7 +7026,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImpo
6908
7026
  }], textInput: [{
6909
7027
  type: ViewChild,
6910
7028
  args: [FwTextInputComponent]
6911
- }], menu: [{ type: i0.ViewChild, args: [i0.forwardRef(() => FwMenuComponent), { isSignal: true }] }], menuItems: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => FwMenuItemComponent), { ...{ descendants: true }, isSignal: true }] }], viewMenuItems: [{ type: i0.ViewChildren, args: [i0.forwardRef(() => FwMenuItemComponent), { isSignal: true }] }], menuItemGroups: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => FwMenuItemGroupComponent), { ...{ descendants: true }, isSignal: true }] }], menuSeparators: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => FwMenuSeparatorComponent), { ...{ descendants: true }, isSignal: true }] }], change: [{
7029
+ }], menu: [{ type: i0.ViewChild, args: [i0.forwardRef(() => FwMenuComponent), { isSignal: true }] }], wrapper: [{ type: i0.ViewChild, args: ['wrapper', { ...{ read: ElementRef }, isSignal: true }] }], panel: [{ type: i0.ViewChild, args: [i0.forwardRef(() => FwMenuContainerComponent), { isSignal: true }] }], menuItems: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => FwMenuItemComponent), { ...{ descendants: true }, isSignal: true }] }], viewMenuItems: [{ type: i0.ViewChildren, args: [i0.forwardRef(() => FwMenuItemComponent), { isSignal: true }] }], menuItemGroups: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => FwMenuItemGroupComponent), { ...{ descendants: true }, isSignal: true }] }], menuSeparators: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => FwMenuSeparatorComponent), { ...{ descendants: true }, isSignal: true }] }], change: [{
6912
7030
  type: Output
6913
7031
  }], filterChanged: [{
6914
7032
  type: Output
@@ -8449,8 +8567,12 @@ class FwStepperComponent {
8449
8567
  return;
8450
8568
  }
8451
8569
  this.activeStep.set(step);
8452
- this.stepChange.emit(parseInt(step?.toString())); // weirdly passing out step directly fails the eqeqeq
8570
+ // Same ordering rule as FwMenuComponent.writeValue: lay the steps out before calling out to a
8571
+ // listener, so a handler that removes the stepper cannot leave `updateSteps()` writing to
8572
+ // `model()`s on destroyed `fw-step`s (NG0953). `updateSteps()` only reads `activeStep()`, set
8573
+ // on the line above, so the layout is unchanged.
8453
8574
  this.updateSteps();
8575
+ this.stepChange.emit(parseInt(step?.toString())); // weirdly passing out step directly fails the eqeqeq
8454
8576
  }
8455
8577
  updateSteps() {
8456
8578
  if (this.steps && this.steps.length > 0) {