@flywheel-io/vision 19.3.2 → 19.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -920,25 +920,24 @@ class FwTooltipPanelComponent {
920
920
  return unparsed;
921
921
  },
922
922
  });
923
- this.title = '';
923
+ this.title = input('');
924
924
  this.isOpen = input(false);
925
925
  this.position = input('below');
926
926
  this.connectedPosition = computed(() => [positionMap$1[this.position()]]);
927
- this.color = 'dark';
928
- this.maxWidth = 200;
929
- this.displayCaret = true;
930
- this.mouseLeave = new EventEmitter();
927
+ this.positionClass = computed(() => `fw-tooltip-${this.position()}`);
928
+ this.color = input('dark');
929
+ this.maxWidth = input(200);
930
+ this.displayCaret = input(true);
931
931
  this.delay = input('short');
932
932
  this.delayMs = computed(() => delayMap$1[this.delay()]);
933
- }
934
- get classes() {
935
- return ['fw-tooltip-panel', 'fw-tooltip-' + this.position].join(' ');
933
+ this.contentClass = input('');
934
+ this.mouseLeave = new EventEmitter();
936
935
  }
937
936
  hidePopover(e) {
938
937
  this.mouseLeave.emit(e);
939
938
  }
940
939
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: FwTooltipPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
941
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.18", type: FwTooltipPanelComponent, isStandalone: true, selector: "fw-tooltip-panel", inputs: { trigger: { classPropertyName: "trigger", publicName: "trigger", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: false, isRequired: false, transformFunction: null }, isOpen: { classPropertyName: "isOpen", publicName: "isOpen", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: false, isRequired: false, transformFunction: null }, maxWidth: { classPropertyName: "maxWidth", publicName: "maxWidth", isSignal: false, isRequired: false, transformFunction: null }, displayCaret: { classPropertyName: "displayCaret", publicName: "displayCaret", isSignal: false, isRequired: false, transformFunction: null }, delay: { classPropertyName: "delay", publicName: "delay", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { mouseLeave: "mouseLeave" }, host: { listeners: { "mouseleave": "hidePopover($event)" }, properties: { "@fadeInFadeOut": "this.animation", "attr.class": "this.classes" } }, ngImport: i0, template: "<div\n class=\"fw-tooltip-content-wrapper vision-dark-theme\"\n [ngClass]=\"'fw-tooltip-' + position()\"\n [ngStyle]=\"{'maxWidth': maxWidth+'px'}\">\n <ng-content></ng-content>\n <h5 class=\"vision-h5\">{{ title }}</h5>\n @if (displayCaret) {\n <div class=\"fw-tooltip-caret\"></div>\n }\n</div>\n", styles: [":host{position:absolute;height:0;width:0}.fw-tooltip-content-wrapper{position:relative;border-radius:8px;border:1px solid var(--separations-border);min-width:60px;box-sizing:border-box;padding:4px 8px;background-color:var(--card-header);text-align:center;overflow-wrap:break-word;white-space:pre-wrap}.fw-tooltip-content-wrapper h5{margin:0!important;color:var(--typography-muted)}.fw-tooltip-content-wrapper .fw-tooltip-caret{position:absolute;overflow:hidden;width:16px;height:16px}.fw-tooltip-content-wrapper .fw-tooltip-caret:after{display:block;content:\"\";width:10px;height:10px;background:var(--separations-border);border:1px solid var(--separations-border);transform:rotate(45deg);position:relative}.fw-tooltip-content-wrapper.fw-tooltip-none{margin-bottom:10px}.fw-tooltip-content-wrapper.fw-tooltip-none .fw-tooltip-caret{display:none}.fw-tooltip-content-wrapper.fw-tooltip-above{margin-bottom:10px}.fw-tooltip-content-wrapper.fw-tooltip-above .fw-tooltip-caret{left:calc(50% - 8px);bottom:-10px;height:10px!important}.fw-tooltip-content-wrapper.fw-tooltip-above .fw-tooltip-caret:after{margin:-8px auto;width:10px}.fw-tooltip-content-wrapper.fw-tooltip-below{margin-top:10px}.fw-tooltip-content-wrapper.fw-tooltip-below .fw-tooltip-caret{left:calc(50% - 8px);top:-10px;height:10px!important}.fw-tooltip-content-wrapper.fw-tooltip-below .fw-tooltip-caret:after{top:10px;margin:-4px auto;width:10px}.fw-tooltip-content-wrapper.fw-tooltip-left{margin-right:10px}.fw-tooltip-content-wrapper.fw-tooltip-left .fw-tooltip-caret{right:-10px;top:max(50% - 10px,3px);width:10px!important}.fw-tooltip-content-wrapper.fw-tooltip-left .fw-tooltip-caret:after{top:calc(50% - 5px);left:-8px;width:10px}.fw-tooltip-content-wrapper.fw-tooltip-right{margin-left:10px}.fw-tooltip-content-wrapper.fw-tooltip-right .fw-tooltip-caret{left:-10px;top:max(50% - 10px,3px);width:10px!important}.fw-tooltip-content-wrapper.fw-tooltip-right .fw-tooltip-caret:after{top:calc(50% - 5px);right:-6px;width:10px}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }], animations: [
940
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.18", type: FwTooltipPanelComponent, isStandalone: true, selector: "fw-tooltip-panel", inputs: { trigger: { classPropertyName: "trigger", publicName: "trigger", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, isOpen: { classPropertyName: "isOpen", publicName: "isOpen", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, maxWidth: { classPropertyName: "maxWidth", publicName: "maxWidth", isSignal: true, isRequired: false, transformFunction: null }, displayCaret: { classPropertyName: "displayCaret", publicName: "displayCaret", isSignal: true, isRequired: false, transformFunction: null }, delay: { classPropertyName: "delay", publicName: "delay", isSignal: true, isRequired: false, transformFunction: null }, contentClass: { classPropertyName: "contentClass", publicName: "contentClass", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { mouseLeave: "mouseLeave" }, host: { listeners: { "mouseleave": "hidePopover($event)" }, properties: { "class.fw-tooltip-panel": "true", "class": "positionClass()", "@fadeInFadeOut": "this.animation" } }, ngImport: i0, template: "<div\n class=\"fw-tooltip-content-wrapper vision-dark-theme\"\n [ngClass]=\"'fw-tooltip-' + position()\"\n [ngStyle]=\"{'maxWidth': maxWidth()+'px'}\">\n <ng-content></ng-content>\n <h5 [ngClass]=\"contentClass()\" class=\"vision-h5\">{{ title() }}</h5>\n @if (displayCaret()) {\n <div class=\"fw-tooltip-caret\"></div>\n }\n</div>\n", styles: [".fw-tooltip-content-wrapper{display:inline-block;position:relative;border-radius:8px;border:1px solid var(--separations-border);min-width:60px;box-sizing:border-box;padding:4px 8px;background-color:var(--card-header);text-align:center;overflow-wrap:break-word;white-space:pre-wrap}.fw-tooltip-content-wrapper h5{margin:0!important;color:var(--typography-muted)}.fw-tooltip-content-wrapper .fw-tooltip-caret{position:absolute;overflow:hidden;width:16px;height:16px}.fw-tooltip-content-wrapper .fw-tooltip-caret:after{display:block;content:\"\";width:10px;height:10px;background:var(--separations-border);border:1px solid var(--separations-border);transform:rotate(45deg);position:relative}.fw-tooltip-content-wrapper.fw-tooltip-none{margin-bottom:10px}.fw-tooltip-content-wrapper.fw-tooltip-none .fw-tooltip-caret{display:none}.fw-tooltip-content-wrapper.fw-tooltip-above{margin-bottom:10px}.fw-tooltip-content-wrapper.fw-tooltip-above .fw-tooltip-caret{left:calc(50% - 8px);bottom:-10px;height:10px!important}.fw-tooltip-content-wrapper.fw-tooltip-above .fw-tooltip-caret:after{margin:-8px auto;width:10px}.fw-tooltip-content-wrapper.fw-tooltip-below{margin-top:10px}.fw-tooltip-content-wrapper.fw-tooltip-below .fw-tooltip-caret{left:calc(50% - 8px);top:-10px;height:10px!important}.fw-tooltip-content-wrapper.fw-tooltip-below .fw-tooltip-caret:after{top:10px;margin:-4px auto;width:10px}.fw-tooltip-content-wrapper.fw-tooltip-left{margin-right:10px}.fw-tooltip-content-wrapper.fw-tooltip-left .fw-tooltip-caret{right:-10px;top:max(50% - 10px,3px);width:10px!important}.fw-tooltip-content-wrapper.fw-tooltip-left .fw-tooltip-caret:after{top:calc(50% - 5px);left:-8px;width:10px}.fw-tooltip-content-wrapper.fw-tooltip-right{margin-left:10px}.fw-tooltip-content-wrapper.fw-tooltip-right .fw-tooltip-caret{left:-10px;top:max(50% - 10px,3px);width:10px!important}.fw-tooltip-content-wrapper.fw-tooltip-right .fw-tooltip-caret:after{top:calc(50% - 5px);right:-6px;width:10px}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }], animations: [
942
941
  trigger('fadeInFadeOut', [
943
942
  transition(':enter', [
944
943
  style({ opacity: 0 }),
@@ -948,11 +947,11 @@ class FwTooltipPanelComponent {
948
947
  animate('200ms', style({ opacity: 0 })),
949
948
  ]),
950
949
  ]),
951
- ], encapsulation: i0.ViewEncapsulation.None }); }
950
+ ], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
952
951
  }
953
952
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: FwTooltipPanelComponent, decorators: [{
954
953
  type: Component,
955
- args: [{ selector: 'fw-tooltip-panel', encapsulation: ViewEncapsulation.None, animations: [
954
+ args: [{ selector: 'fw-tooltip-panel', encapsulation: ViewEncapsulation.Emulated, animations: [
956
955
  trigger('fadeInFadeOut', [
957
956
  transition(':enter', [
958
957
  style({ opacity: 0 }),
@@ -962,23 +961,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImpo
962
961
  animate('200ms', style({ opacity: 0 })),
963
962
  ]),
964
963
  ]),
965
- ], imports: [NgClass, NgStyle], template: "<div\n class=\"fw-tooltip-content-wrapper vision-dark-theme\"\n [ngClass]=\"'fw-tooltip-' + position()\"\n [ngStyle]=\"{'maxWidth': maxWidth+'px'}\">\n <ng-content></ng-content>\n <h5 class=\"vision-h5\">{{ title }}</h5>\n @if (displayCaret) {\n <div class=\"fw-tooltip-caret\"></div>\n }\n</div>\n", styles: [":host{position:absolute;height:0;width:0}.fw-tooltip-content-wrapper{position:relative;border-radius:8px;border:1px solid var(--separations-border);min-width:60px;box-sizing:border-box;padding:4px 8px;background-color:var(--card-header);text-align:center;overflow-wrap:break-word;white-space:pre-wrap}.fw-tooltip-content-wrapper h5{margin:0!important;color:var(--typography-muted)}.fw-tooltip-content-wrapper .fw-tooltip-caret{position:absolute;overflow:hidden;width:16px;height:16px}.fw-tooltip-content-wrapper .fw-tooltip-caret:after{display:block;content:\"\";width:10px;height:10px;background:var(--separations-border);border:1px solid var(--separations-border);transform:rotate(45deg);position:relative}.fw-tooltip-content-wrapper.fw-tooltip-none{margin-bottom:10px}.fw-tooltip-content-wrapper.fw-tooltip-none .fw-tooltip-caret{display:none}.fw-tooltip-content-wrapper.fw-tooltip-above{margin-bottom:10px}.fw-tooltip-content-wrapper.fw-tooltip-above .fw-tooltip-caret{left:calc(50% - 8px);bottom:-10px;height:10px!important}.fw-tooltip-content-wrapper.fw-tooltip-above .fw-tooltip-caret:after{margin:-8px auto;width:10px}.fw-tooltip-content-wrapper.fw-tooltip-below{margin-top:10px}.fw-tooltip-content-wrapper.fw-tooltip-below .fw-tooltip-caret{left:calc(50% - 8px);top:-10px;height:10px!important}.fw-tooltip-content-wrapper.fw-tooltip-below .fw-tooltip-caret:after{top:10px;margin:-4px auto;width:10px}.fw-tooltip-content-wrapper.fw-tooltip-left{margin-right:10px}.fw-tooltip-content-wrapper.fw-tooltip-left .fw-tooltip-caret{right:-10px;top:max(50% - 10px,3px);width:10px!important}.fw-tooltip-content-wrapper.fw-tooltip-left .fw-tooltip-caret:after{top:calc(50% - 5px);left:-8px;width:10px}.fw-tooltip-content-wrapper.fw-tooltip-right{margin-left:10px}.fw-tooltip-content-wrapper.fw-tooltip-right .fw-tooltip-caret{left:-10px;top:max(50% - 10px,3px);width:10px!important}.fw-tooltip-content-wrapper.fw-tooltip-right .fw-tooltip-caret:after{top:calc(50% - 5px);right:-6px;width:10px}\n"] }]
964
+ ], host: {
965
+ '[class.fw-tooltip-panel]': 'true',
966
+ '[class]': 'positionClass()',
967
+ }, imports: [NgClass, NgStyle], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n class=\"fw-tooltip-content-wrapper vision-dark-theme\"\n [ngClass]=\"'fw-tooltip-' + position()\"\n [ngStyle]=\"{'maxWidth': maxWidth()+'px'}\">\n <ng-content></ng-content>\n <h5 [ngClass]=\"contentClass()\" class=\"vision-h5\">{{ title() }}</h5>\n @if (displayCaret()) {\n <div class=\"fw-tooltip-caret\"></div>\n }\n</div>\n", styles: [".fw-tooltip-content-wrapper{display:inline-block;position:relative;border-radius:8px;border:1px solid var(--separations-border);min-width:60px;box-sizing:border-box;padding:4px 8px;background-color:var(--card-header);text-align:center;overflow-wrap:break-word;white-space:pre-wrap}.fw-tooltip-content-wrapper h5{margin:0!important;color:var(--typography-muted)}.fw-tooltip-content-wrapper .fw-tooltip-caret{position:absolute;overflow:hidden;width:16px;height:16px}.fw-tooltip-content-wrapper .fw-tooltip-caret:after{display:block;content:\"\";width:10px;height:10px;background:var(--separations-border);border:1px solid var(--separations-border);transform:rotate(45deg);position:relative}.fw-tooltip-content-wrapper.fw-tooltip-none{margin-bottom:10px}.fw-tooltip-content-wrapper.fw-tooltip-none .fw-tooltip-caret{display:none}.fw-tooltip-content-wrapper.fw-tooltip-above{margin-bottom:10px}.fw-tooltip-content-wrapper.fw-tooltip-above .fw-tooltip-caret{left:calc(50% - 8px);bottom:-10px;height:10px!important}.fw-tooltip-content-wrapper.fw-tooltip-above .fw-tooltip-caret:after{margin:-8px auto;width:10px}.fw-tooltip-content-wrapper.fw-tooltip-below{margin-top:10px}.fw-tooltip-content-wrapper.fw-tooltip-below .fw-tooltip-caret{left:calc(50% - 8px);top:-10px;height:10px!important}.fw-tooltip-content-wrapper.fw-tooltip-below .fw-tooltip-caret:after{top:10px;margin:-4px auto;width:10px}.fw-tooltip-content-wrapper.fw-tooltip-left{margin-right:10px}.fw-tooltip-content-wrapper.fw-tooltip-left .fw-tooltip-caret{right:-10px;top:max(50% - 10px,3px);width:10px!important}.fw-tooltip-content-wrapper.fw-tooltip-left .fw-tooltip-caret:after{top:calc(50% - 5px);left:-8px;width:10px}.fw-tooltip-content-wrapper.fw-tooltip-right{margin-left:10px}.fw-tooltip-content-wrapper.fw-tooltip-right .fw-tooltip-caret{left:-10px;top:max(50% - 10px,3px);width:10px!important}.fw-tooltip-content-wrapper.fw-tooltip-right .fw-tooltip-caret:after{top:calc(50% - 5px);right:-6px;width:10px}\n"] }]
966
968
  }], propDecorators: { animation: [{
967
969
  type: HostBinding,
968
970
  args: ['@fadeInFadeOut']
969
- }], title: [{
970
- type: Input
971
- }], color: [{
972
- type: Input
973
- }], maxWidth: [{
974
- type: Input
975
- }], displayCaret: [{
976
- type: Input
977
971
  }], mouseLeave: [{
978
972
  type: Output
979
- }], classes: [{
980
- type: HostBinding,
981
- args: ['attr.class']
982
973
  }], hidePopover: [{
983
974
  type: HostListener,
984
975
  args: ['mouseleave', ['$event']]
@@ -1004,7 +995,7 @@ const positionMap = {
1004
995
  /* eslint-enable @rx-angular/prefer-no-layout-sensitive-apis */
1005
996
  /**
1006
997
  * Directive for displaying extra context on hover
1007
- * @see [Vision Docs](https://cdn.flywheel.io/docs/vision/master/?path=/docs/components-tooltip--docs)
998
+ * @see [Vision Storybook](https://cdn.flywheel.io/docs/vision/master/?path=/docs/components-tooltip--docs)
1008
999
  */
1009
1000
  class FwTooltipDirective {
1010
1001
  constructor() {
@@ -1015,6 +1006,7 @@ class FwTooltipDirective {
1015
1006
  /***** Internal Refs *****/
1016
1007
  this.overlayRef = signal(undefined);
1017
1008
  this.tooltipRef = signal(undefined);
1009
+ this.tooltipPortal = new ComponentPortal(FwTooltipPanelComponent, this.viewContainerRef);
1018
1010
  /***** Inputs *****/
1019
1011
  // a lot of these ideally wouldn't be models,
1020
1012
  // but it makes it easier to interact with this from derivative directives
@@ -1039,6 +1031,12 @@ class FwTooltipDirective {
1039
1031
  this.maxWidth = model(200, {
1040
1032
  alias: 'fwTooltipMaxWidthPx',
1041
1033
  });
1034
+ /**
1035
+ * Custom class to apply to the content of the tooltip
1036
+ */
1037
+ this.contentClass = model('', {
1038
+ alias: 'fwTooltipClass',
1039
+ });
1042
1040
  /**
1043
1041
  * amount of delay before the tooltip displays
1044
1042
  *
@@ -1095,6 +1093,7 @@ class FwTooltipDirective {
1095
1093
  this.hideTooltip();
1096
1094
  });
1097
1095
  });
1096
+ this.openDelayTimer = 0;
1098
1097
  // updates the tooltip panel's caret position if a fallback position is used
1099
1098
  this.handlePositionChange = effect(() => {
1100
1099
  this.positionChangeSub?.unsubscribe();
@@ -1110,13 +1109,11 @@ class FwTooltipDirective {
1110
1109
  });
1111
1110
  if (newPositionName) {
1112
1111
  this.tooltipRef()?.setInput('position', newPositionName);
1113
- this.tooltipRef()?.changeDetectorRef.detectChanges();
1114
1112
  }
1115
1113
  });
1116
1114
  });
1117
1115
  }
1118
1116
  ngOnInit() {
1119
- this.tooltipPortal = new ComponentPortal(FwTooltipPanelComponent, this.viewContainerRef);
1120
1117
  this.overlayRef.set(this.overlayService.create());
1121
1118
  }
1122
1119
  showTooltip() {
@@ -1124,6 +1121,9 @@ class FwTooltipDirective {
1124
1121
  return;
1125
1122
  }
1126
1123
  const overlayRef = this.overlayRef();
1124
+ if (!overlayRef) {
1125
+ return;
1126
+ }
1127
1127
  overlayRef.updatePositionStrategy(this.positionStrategy());
1128
1128
  if (this.openDelayTimer) {
1129
1129
  return;
@@ -1135,12 +1135,12 @@ class FwTooltipDirective {
1135
1135
  tooltipRef.setInput('position', this.position());
1136
1136
  tooltipRef.setInput('maxWidth', this.maxWidth());
1137
1137
  tooltipRef.setInput('displayCaret', this.displayCaret());
1138
- tooltipRef.changeDetectorRef.detectChanges();
1138
+ tooltipRef.setInput('contentClass', this.contentClass());
1139
1139
  }, this.delayMs());
1140
1140
  }
1141
1141
  hideTooltip() {
1142
1142
  window.clearTimeout(this.openDelayTimer);
1143
- this.openDelayTimer = undefined;
1143
+ this.openDelayTimer = 0;
1144
1144
  this.overlayRef()?.detach();
1145
1145
  this.tooltipRef.set(undefined);
1146
1146
  }
@@ -1149,7 +1149,7 @@ class FwTooltipDirective {
1149
1149
  this.hideTooltip();
1150
1150
  }
1151
1151
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: FwTooltipDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
1152
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.18", type: FwTooltipDirective, isStandalone: true, selector: "[fwTooltip]", inputs: { title: { classPropertyName: "title", publicName: "fwTooltip", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "fwTooltipPosition", isSignal: true, isRequired: false, transformFunction: null }, maxWidth: { classPropertyName: "maxWidth", publicName: "fwTooltipMaxWidthPx", isSignal: true, isRequired: false, transformFunction: null }, delayMs: { classPropertyName: "delayMs", publicName: "fwTooltipDelay", isSignal: true, isRequired: false, transformFunction: null }, displayCaret: { classPropertyName: "displayCaret", publicName: "fwTooltipCaret", isSignal: true, isRequired: false, transformFunction: null }, enabled: { classPropertyName: "enabled", publicName: "fwTooltipEnabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { title: "fwTooltipChange", position: "fwTooltipPositionChange", maxWidth: "fwTooltipMaxWidthPxChange", enabled: "fwTooltipEnabledChange" }, ngImport: i0 }); }
1152
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.18", type: FwTooltipDirective, isStandalone: true, selector: "[fwTooltip]", inputs: { title: { classPropertyName: "title", publicName: "fwTooltip", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "fwTooltipPosition", isSignal: true, isRequired: false, transformFunction: null }, maxWidth: { classPropertyName: "maxWidth", publicName: "fwTooltipMaxWidthPx", isSignal: true, isRequired: false, transformFunction: null }, contentClass: { classPropertyName: "contentClass", publicName: "fwTooltipClass", isSignal: true, isRequired: false, transformFunction: null }, delayMs: { classPropertyName: "delayMs", publicName: "fwTooltipDelay", isSignal: true, isRequired: false, transformFunction: null }, displayCaret: { classPropertyName: "displayCaret", publicName: "fwTooltipCaret", isSignal: true, isRequired: false, transformFunction: null }, enabled: { classPropertyName: "enabled", publicName: "fwTooltipEnabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { title: "fwTooltipChange", position: "fwTooltipPositionChange", maxWidth: "fwTooltipMaxWidthPxChange", contentClass: "fwTooltipClassChange", enabled: "fwTooltipEnabledChange" }, ngImport: i0 }); }
1153
1153
  }
1154
1154
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: FwTooltipDirective, decorators: [{
1155
1155
  type: Directive,
@@ -1186,7 +1186,7 @@ class FwTooltipComponent {
1186
1186
  }
1187
1187
  ;
1188
1188
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: FwTooltipComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1189
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.2.18", type: FwTooltipComponent, isStandalone: true, selector: "fw-tooltip", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: false, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: false, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: false, isRequired: false, transformFunction: null }, maxWidth: { classPropertyName: "maxWidth", publicName: "maxWidth", isSignal: false, isRequired: false, transformFunction: null }, fullWidth: { classPropertyName: "fullWidth", publicName: "fullWidth", isSignal: false, isRequired: false, transformFunction: null }, isOpen: { classPropertyName: "isOpen", publicName: "isOpen", isSignal: false, isRequired: false, transformFunction: null }, trigger: { classPropertyName: "trigger", publicName: "trigger", isSignal: false, isRequired: false, transformFunction: null }, delay: { classPropertyName: "delay", publicName: "delay", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.class": "this.classes", "attr.title": "this.attrTitle" } }, ngImport: i0, template: "<div\n [ngClass]=\"['tooltip-trigger',fullWidth?'full-width':'']\"\n [fwTooltip]=\"title\"\n [fwTooltipDelay]=\"delay()\"\n [fwTooltipPosition]=\"position\">\n <ng-content></ng-content>\n</div>\n", styles: [":host.full-width{flex:1;display:flex;width:stretch}:host.full-width button{flex:1}:host .tooltip-trigger{width:fit-content;display:inline-block}:host .tooltip-trigger.full-width{flex:1;display:flex;width:stretch}:host .tooltip-trigger.full-width button{flex:1}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: FwTooltipDirective, selector: "[fwTooltip]", inputs: ["fwTooltip", "fwTooltipPosition", "fwTooltipMaxWidthPx", "fwTooltipDelay", "fwTooltipCaret", "fwTooltipEnabled"], outputs: ["fwTooltipChange", "fwTooltipPositionChange", "fwTooltipMaxWidthPxChange", "fwTooltipEnabledChange"] }] }); }
1189
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.2.18", type: FwTooltipComponent, isStandalone: true, selector: "fw-tooltip", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: false, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: false, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: false, isRequired: false, transformFunction: null }, maxWidth: { classPropertyName: "maxWidth", publicName: "maxWidth", isSignal: false, isRequired: false, transformFunction: null }, fullWidth: { classPropertyName: "fullWidth", publicName: "fullWidth", isSignal: false, isRequired: false, transformFunction: null }, isOpen: { classPropertyName: "isOpen", publicName: "isOpen", isSignal: false, isRequired: false, transformFunction: null }, trigger: { classPropertyName: "trigger", publicName: "trigger", isSignal: false, isRequired: false, transformFunction: null }, delay: { classPropertyName: "delay", publicName: "delay", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.class": "this.classes", "attr.title": "this.attrTitle" } }, ngImport: i0, template: "<div\n [ngClass]=\"['tooltip-trigger',fullWidth?'full-width':'']\"\n [fwTooltip]=\"title\"\n [fwTooltipDelay]=\"delay()\"\n [fwTooltipPosition]=\"position\">\n <ng-content></ng-content>\n</div>\n", styles: [":host.full-width{flex:1;display:flex;width:stretch}:host.full-width button{flex:1}:host .tooltip-trigger{width:fit-content;display:inline-block}:host .tooltip-trigger.full-width{flex:1;display:flex;width:stretch}:host .tooltip-trigger.full-width button{flex:1}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: FwTooltipDirective, selector: "[fwTooltip]", inputs: ["fwTooltip", "fwTooltipPosition", "fwTooltipMaxWidthPx", "fwTooltipClass", "fwTooltipDelay", "fwTooltipCaret", "fwTooltipEnabled"], outputs: ["fwTooltipChange", "fwTooltipPositionChange", "fwTooltipMaxWidthPxChange", "fwTooltipClassChange", "fwTooltipEnabledChange"] }] }); }
1190
1190
  }
1191
1191
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: FwTooltipComponent, decorators: [{
1192
1192
  type: Component,
@@ -4417,7 +4417,7 @@ class FwMenuItemComponent {
4417
4417
  evt.stopPropagation();
4418
4418
  return;
4419
4419
  }
4420
- if (this.value) {
4420
+ if (this.value !== undefined && this.value !== null) {
4421
4421
  this.click.emit(this.value);
4422
4422
  evt.stopPropagation();
4423
4423
  }
@@ -4515,6 +4515,7 @@ class FwMenuComponent {
4515
4515
  this.onTouched = () => {
4516
4516
  };
4517
4517
  this.subscriptions = [];
4518
+ this.menuItemSubscriptions = [];
4518
4519
  }
4519
4520
  ngOnChanges() {
4520
4521
  this.updateLayout();
@@ -4523,17 +4524,27 @@ class FwMenuComponent {
4523
4524
  for (const subscription of this.subscriptions) {
4524
4525
  subscription.unsubscribe();
4525
4526
  }
4527
+ for (const subscription of this.menuItemSubscriptions) {
4528
+ subscription.unsubscribe();
4529
+ }
4526
4530
  }
4527
- ngAfterContentInit() {
4528
- this.menuItems.forEach(item => {
4531
+ subscribeToMenuItems(items) {
4532
+ // Clear old menu item subscriptions
4533
+ for (const subscription of this.menuItemSubscriptions) {
4534
+ subscription.unsubscribe();
4535
+ }
4536
+ this.menuItemSubscriptions = [];
4537
+ items.forEach(item => {
4529
4538
  const sub = item.click.subscribe(value => this.handleSelect(value));
4530
- this.subscriptions.push(sub);
4539
+ this.menuItemSubscriptions.push(sub);
4531
4540
  });
4541
+ }
4542
+ ngAfterContentInit() {
4543
+ // Initial subscription
4544
+ this.subscribeToMenuItems(this.menuItems);
4545
+ // Re-subscribe when menu items change
4532
4546
  const menuItemChangeSub = this.menuItems.changes.subscribe((newMenuItems) => {
4533
- newMenuItems.forEach(item => {
4534
- const sub = item.click.subscribe(value => this.handleSelect(value));
4535
- this.subscriptions.push(sub);
4536
- });
4547
+ this.subscribeToMenuItems(newMenuItems);
4537
4548
  this.updateLayout();
4538
4549
  });
4539
4550
  this.subscriptions.push(menuItemChangeSub);
@@ -4830,7 +4841,7 @@ class FwTextInputComponent {
4830
4841
  provide: NG_VALUE_ACCESSOR,
4831
4842
  useExisting: FwTextInputComponent,
4832
4843
  multi: true,
4833
- }], queries: [{ propertyName: "textInput", first: true, predicate: ["textInput"], descendants: 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 <fw-icon\n *ngIf=\"!!leftIcon\"\n (click)=\"useActionableIcons?onLeftIconClick():null\"\n [ngClass]=\"useActionableIcons?'actionable':''\">{{ leftIcon }}\n </fw-icon>\n <p class=\"vision-p2 context\" *ngIf=\"!!prefix\">{{ prefix }}</p>\n\n <input\n #input\n *ngIf=\"!textInput\"\n [type]=\"type\"\n (keyup)=\"changeHandler($event)\"\n (blur)=\"blurHandler()\"\n [value]=\"value\"\n [attr.maxlength]=\"maxLength\"\n [placeholder]=\"placeholder || ''\"\n [readOnly]=\"readOnly\"\n [disabled]=\"disabled\"\n [autofocus]=\"autofocus\"\n [autocomplete]=\"autocomplete\"\n >\n <ng-content select=\"input\"></ng-content>\n <p class=\"vision-p2 context\" *ngIf=\"!!context\">{{ context }}</p>\n\n <fw-icon class=\"error-icon\" [fwTooltip]=\"errorInIconTooltip ? errorText : ''\">warning-circle</fw-icon>\n <fw-icon\n *ngIf=\"!!rightIcon\"\n (click)=\"useActionableIcons?onRightIconClick():null\"\n [ngClass]=\"useActionableIcons?'actionable':''\">{{ rightIcon }}\n </fw-icon>\n <ng-content></ng-content>\n </div>\n <p class=\"vision-p4 helper-text\" *ngIf=\"!!helperText\">{{ helperText }}</p>\n <p class=\"vision-p4 error-text\" *ngIf=\"!!errorText && !errorInIconTooltip\">{{ errorText }}</p>\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}fw-icon.actionable{cursor:pointer}fw-icon.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: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: FwIconComponent, selector: "fw-icon", inputs: ["size", "color"] }, { kind: "directive", type: FwTooltipDirective, selector: "[fwTooltip]", inputs: ["fwTooltip", "fwTooltipPosition", "fwTooltipMaxWidthPx", "fwTooltipDelay", "fwTooltipCaret", "fwTooltipEnabled"], outputs: ["fwTooltipChange", "fwTooltipPositionChange", "fwTooltipMaxWidthPxChange", "fwTooltipEnabledChange"] }] }); }
4844
+ }], queries: [{ propertyName: "textInput", first: true, predicate: ["textInput"], descendants: 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 <fw-icon\n *ngIf=\"!!leftIcon\"\n (click)=\"useActionableIcons?onLeftIconClick():null\"\n [ngClass]=\"useActionableIcons?'actionable':''\">{{ leftIcon }}\n </fw-icon>\n <p class=\"vision-p2 context\" *ngIf=\"!!prefix\">{{ prefix }}</p>\n\n <input\n #input\n *ngIf=\"!textInput\"\n [type]=\"type\"\n (keyup)=\"changeHandler($event)\"\n (blur)=\"blurHandler()\"\n [value]=\"value\"\n [attr.maxlength]=\"maxLength\"\n [placeholder]=\"placeholder || ''\"\n [readOnly]=\"readOnly\"\n [disabled]=\"disabled\"\n [autofocus]=\"autofocus\"\n [autocomplete]=\"autocomplete\"\n >\n <ng-content select=\"input\"></ng-content>\n <p class=\"vision-p2 context\" *ngIf=\"!!context\">{{ context }}</p>\n\n <fw-icon class=\"error-icon\" [fwTooltip]=\"errorInIconTooltip ? errorText : ''\">warning-circle</fw-icon>\n <fw-icon\n *ngIf=\"!!rightIcon\"\n (click)=\"useActionableIcons?onRightIconClick():null\"\n [ngClass]=\"useActionableIcons?'actionable':''\">{{ rightIcon }}\n </fw-icon>\n <ng-content></ng-content>\n </div>\n <p class=\"vision-p4 helper-text\" *ngIf=\"!!helperText\">{{ helperText }}</p>\n <p class=\"vision-p4 error-text\" *ngIf=\"!!errorText && !errorInIconTooltip\">{{ errorText }}</p>\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}fw-icon.actionable{cursor:pointer}fw-icon.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: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: FwIconComponent, selector: "fw-icon", inputs: ["size", "color"] }, { kind: "directive", type: FwTooltipDirective, selector: "[fwTooltip]", inputs: ["fwTooltip", "fwTooltipPosition", "fwTooltipMaxWidthPx", "fwTooltipClass", "fwTooltipDelay", "fwTooltipCaret", "fwTooltipEnabled"], outputs: ["fwTooltipChange", "fwTooltipPositionChange", "fwTooltipMaxWidthPxChange", "fwTooltipClassChange", "fwTooltipEnabledChange"] }] }); }
4834
4845
  }
4835
4846
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: FwTextInputComponent, decorators: [{
4836
4847
  type: Component,
@@ -6267,14 +6278,26 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImpo
6267
6278
  /* eslint-disable @typescript-eslint/no-explicit-any */
6268
6279
  class FwSelectMenuComponent {
6269
6280
  outsideClick(evt) {
6270
- if (this._isOpen && evt.nodeName !== 'INPUT') {
6281
+ // Check if click is inside the overlay (menu items) but NOT on the input trigger
6282
+ const clickedElement = evt.target;
6283
+ const overlayPane = clickedElement.closest('.cdk-overlay-pane');
6284
+ const clickedInsideOverlay = overlayPane && !clickedElement.closest('fw-text-input');
6285
+ // Don't process outside clicks when clicking on menu items (but not the trigger input)
6286
+ if (clickedInsideOverlay) {
6287
+ return;
6288
+ }
6289
+ if (this._isOpen && evt.target.nodeName !== 'INPUT') {
6271
6290
  this.onTouched();
6272
6291
  this.close();
6273
6292
  this._isOpen = false;
6274
6293
  }
6294
+ // Sync _isOpen state with actual trigger state
6275
6295
  if (this.trigger && this.trigger.isOpen()) {
6276
6296
  this._isOpen = true;
6277
6297
  }
6298
+ else {
6299
+ this._isOpen = false;
6300
+ }
6278
6301
  }
6279
6302
  get disabledClass() {
6280
6303
  return this.disabled();
@@ -6321,8 +6344,33 @@ class FwSelectMenuComponent {
6321
6344
  this.inFocusOpen = false;
6322
6345
  this.isTyping = signal(false);
6323
6346
  // Computed signal for the input display value
6324
- this.inputDisplayValue = computed(() => this.isTyping() ? this.filterValue() : this.selectTitle());
6347
+ // Don't show the title if it's the same as the value (means we haven't resolved it yet)
6348
+ this.inputDisplayValue = computed(() => {
6349
+ if (this.isTyping()) {
6350
+ return this.filterValue();
6351
+ }
6352
+ const title = this.selectTitle();
6353
+ const value = this.selectValue;
6354
+ // If title equals value, it means we haven't resolved the proper title yet, show empty
6355
+ if (title === value && this.menuItems().length > 0) {
6356
+ return '';
6357
+ }
6358
+ return title;
6359
+ });
6325
6360
  this._value = '';
6361
+ // Watch for menu items changes and re-subscribe
6362
+ this.menuItemsWatcher = effect(() => {
6363
+ const items = this.menuItems();
6364
+ // Only subscribe if we have items
6365
+ // Use setTimeout to defer until after Angular has set the input properties
6366
+ if (items.length > 0) {
6367
+ untracked(() => {
6368
+ setTimeout(() => {
6369
+ this.subscribeToMenuItems();
6370
+ }, 0);
6371
+ });
6372
+ }
6373
+ });
6326
6374
  this.filteredOptions = computed(() => {
6327
6375
  const filter = this.filterValue();
6328
6376
  const opts = this.options();
@@ -6363,18 +6411,26 @@ class FwSelectMenuComponent {
6363
6411
  }
6364
6412
  });
6365
6413
  }
6366
- ngAfterContentInit() {
6367
- // When using content projection with <fw-menu-item> components,
6368
- // subscribe to their click events and set initial selected state
6369
- if (this.menuItems().length > 0) {
6370
- this.menuItems().forEach(item => {
6371
- const sub = item.click.subscribe(value => this.menu()?.writeValue(value));
6372
- this.subscriptions.push(sub);
6373
- if (item.value.toString() === this.selectValue) {
6374
- this.selectTitle.set(item.title.toString());
6375
- this.selectIcon = item.icon;
6376
- }
6414
+ subscribeToMenuItems() {
6415
+ // Clear old subscriptions
6416
+ this.subscriptions.forEach(sub => sub.unsubscribe());
6417
+ this.subscriptions = [];
6418
+ const items = this.menuItems();
6419
+ // Subscribe to current menu items
6420
+ for (const item of items) {
6421
+ const sub = item.click.subscribe(value => {
6422
+ this.menu()?.writeValue(value);
6377
6423
  });
6424
+ this.subscriptions.push(sub);
6425
+ }
6426
+ // Update title/icon if we have a selectValue but no title (or wrong title)
6427
+ // This handles the case where writeValue was called before menu items existed
6428
+ if (this.selectValue && items.length > 0) {
6429
+ const matchingItem = items.find(item => item.value?.toString() === this.selectValue);
6430
+ if (matchingItem) {
6431
+ this.selectTitle.set(matchingItem.title.toString());
6432
+ this.selectIcon = matchingItem.icon;
6433
+ }
6378
6434
  }
6379
6435
  }
6380
6436
  ngOnDestroy() {
@@ -6456,15 +6512,9 @@ class FwSelectMenuComponent {
6456
6512
  updateHighlighting() {
6457
6513
  const currentValue = this.selectValue;
6458
6514
  // Update highlighting for content-projected menu items
6459
- if (this.menuItems().length > 0) {
6460
- this.menuItems().forEach(item => {
6461
- item.selected = item.value === currentValue;
6462
- });
6463
- }
6464
- // Update highlighting for options-based menu items via menu component
6465
- if (this.menu() && this.options().length > 0) {
6466
- this.menu().value = currentValue;
6467
- this.menu()?.updateLayout();
6515
+ const projectedItems = this.menuItems();
6516
+ for (let i = 0; i < projectedItems.length; i++) {
6517
+ projectedItems[i].selected = projectedItems[i].value === currentValue;
6468
6518
  }
6469
6519
  }
6470
6520
  /**
@@ -6652,7 +6702,13 @@ class FwSelectMenuComponent {
6652
6702
  this.menuItems().forEach(item => {
6653
6703
  item.selected = item.value === this.selectValue;
6654
6704
  });
6655
- this.updateValue(selectedItem.value);
6705
+ // Scroll the focused item into view
6706
+ // eslint-disable-next-line @rx-angular/prefer-no-layout-sensitive-apis
6707
+ selectedItem.scrollIntoView();
6708
+ // Only update value if not in typing mode to avoid re-rendering all options
6709
+ if (!this.isTyping()) {
6710
+ this.updateValue(selectedItem.value);
6711
+ }
6656
6712
  }
6657
6713
  else {
6658
6714
  // For options array items, set selectValue first for immediate highlighting
@@ -6660,11 +6716,13 @@ class FwSelectMenuComponent {
6660
6716
  this.selectValue = this.useFullOptionAsValue()
6661
6717
  ? JSON.stringify(selectedItem)
6662
6718
  : selectedItem?.[vProp]?.toString();
6663
- if (this.menu()) {
6664
- this.menu().value = this.selectValue;
6665
- this.menu()?.updateLayout();
6719
+ // Update highlighting to show the focused item
6720
+ this.updateHighlighting();
6721
+ // Don't call updateValue during arrow navigation while typing
6722
+ // Just update selectValue for highlighting, actual value update happens on Enter
6723
+ if (!this.isTyping()) {
6724
+ this.updateValue(selectedItem);
6666
6725
  }
6667
- this.updateValue(selectedItem);
6668
6726
  }
6669
6727
  }
6670
6728
  }, 0);
@@ -6684,7 +6742,13 @@ class FwSelectMenuComponent {
6684
6742
  this.menuItems().forEach(item => {
6685
6743
  item.selected = item.value === this.selectValue;
6686
6744
  });
6687
- this.updateValue(selectedItem.value);
6745
+ // Scroll the focused item into view
6746
+ // eslint-disable-next-line @rx-angular/prefer-no-layout-sensitive-apis
6747
+ selectedItem.scrollIntoView();
6748
+ // Only update value if not in typing mode to avoid re-rendering all options
6749
+ if (!this.isTyping()) {
6750
+ this.updateValue(selectedItem.value);
6751
+ }
6688
6752
  }
6689
6753
  else {
6690
6754
  // For options array items, set selectValue first for immediate highlighting
@@ -6692,11 +6756,13 @@ class FwSelectMenuComponent {
6692
6756
  this.selectValue = this.useFullOptionAsValue()
6693
6757
  ? JSON.stringify(selectedItem)
6694
6758
  : selectedItem?.[vProp]?.toString();
6695
- if (this.menu()) {
6696
- this.menu().value = this.selectValue;
6697
- this.menu()?.updateLayout();
6759
+ // Update highlighting to show the focused item
6760
+ this.updateHighlighting();
6761
+ // Don't call updateValue during arrow navigation while typing
6762
+ // Just update selectValue for highlighting, actual value update happens on Enter
6763
+ if (!this.isTyping()) {
6764
+ this.updateValue(selectedItem);
6698
6765
  }
6699
- this.updateValue(selectedItem);
6700
6766
  }
6701
6767
  }
6702
6768
  }, 0);
@@ -6857,7 +6923,7 @@ class FwSelectMenuComponent {
6857
6923
  }
6858
6924
  }
6859
6925
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: FwSelectMenuComponent, deps: [{ token: i1$4.NgControl, optional: true, self: true }], target: i0.ɵɵFactoryTarget.Component }); }
6860
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.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 }, value: { classPropertyName: "value", publicName: "value", isSignal: false, isRequired: false, transformFunction: null } }, outputs: { disabled: "disabledChange", change: "change", filterChanged: "filterChanged" }, host: { listeners: { "document:click": "outsideClick($event.target)" }, 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: "trigger", first: true, predicate: CdkMenuTrigger, descendants: 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 </fw-text-input>\n <ng-template #selectMenu>\n @if (!disabled()) {\n <fw-menu-container\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 [disabled]=\"disabled()\" [value]=\"selectValue\" (change)=\"handleClick($any($event))\">\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]=\"item.raw[descriptionProperty()]\"\n [value]=\"item.value\"\n [icon]=\"item.raw[iconProperty()]\"\n [disabled]=\"$any(item.raw).disabled\"\n >\n </fw-menu-item>\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", "prefix", "context", "helperText", "errorText", "errorInIconTooltip", "placeholder", "readOnly", "size", "type", "maxLength", "autofocus", "autocomplete", "value", "error", "width"], outputs: ["leftIconAction", "rightIconAction"] }, { kind: "directive", type: MenuRegisterDirective, selector: "[fwMenuRegister]" }, { kind: "directive", type: CdkMenuTrigger, selector: "[cdkMenuTriggerFor]", inputs: ["cdkMenuTriggerFor", "cdkMenuPosition", "cdkMenuTriggerData"], 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"], outputs: ["change"] }, { kind: "component", type: FwMenuItemComponent, selector: "fw-menu-item", inputs: ["value", "size", "title", "description", "icon", "iconColor", "disabled", "showCheckbox", "checkboxColor", "multiSelect", "hidden", "collapsed", "href", "target", "subItemsOpen", "mouseEnterHandler", "focused", "selected"], outputs: ["mouseEnterHandlerChange", "click"] }] }); }
6926
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.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 }, 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: "trigger", first: true, predicate: CdkMenuTrigger, descendants: 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 </fw-text-input>\n <ng-template #selectMenu>\n @if (!disabled()) {\n <fw-menu-container\n [filterText]=\"menuItems().length > 0 ? '' : 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 [disabled]=\"disabled()\" [value]=\"selectValue\" (change)=\"handleClick($any($event))\">\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]=\"item.raw[descriptionProperty()]\"\n [value]=\"item.value\"\n [icon]=\"item.raw[iconProperty()]\"\n [disabled]=\"$any(item.raw).disabled\"\n >\n </fw-menu-item>\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", "prefix", "context", "helperText", "errorText", "errorInIconTooltip", "placeholder", "readOnly", "size", "type", "maxLength", "autofocus", "autocomplete", "value", "error", "width"], outputs: ["leftIconAction", "rightIconAction"] }, { kind: "directive", type: MenuRegisterDirective, selector: "[fwMenuRegister]" }, { kind: "directive", type: CdkMenuTrigger, selector: "[cdkMenuTriggerFor]", inputs: ["cdkMenuTriggerFor", "cdkMenuPosition", "cdkMenuTriggerData"], 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"], outputs: ["change"] }, { kind: "component", type: FwMenuItemComponent, selector: "fw-menu-item", inputs: ["value", "size", "title", "description", "icon", "iconColor", "disabled", "showCheckbox", "checkboxColor", "multiSelect", "hidden", "collapsed", "href", "target", "subItemsOpen", "mouseEnterHandler", "focused", "selected"], outputs: ["mouseEnterHandlerChange", "click"] }] }); }
6861
6927
  }
6862
6928
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: FwSelectMenuComponent, decorators: [{
6863
6929
  type: Component,
@@ -6871,14 +6937,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImpo
6871
6937
  FwMenuComponent,
6872
6938
  NgFor,
6873
6939
  FwMenuItemComponent,
6874
- ], 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 </fw-text-input>\n <ng-template #selectMenu>\n @if (!disabled()) {\n <fw-menu-container\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 [disabled]=\"disabled()\" [value]=\"selectValue\" (change)=\"handleClick($any($event))\">\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]=\"item.raw[descriptionProperty()]\"\n [value]=\"item.value\"\n [icon]=\"item.raw[iconProperty()]\"\n [disabled]=\"$any(item.raw).disabled\"\n >\n </fw-menu-item>\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"] }]
6940
+ ], 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 </fw-text-input>\n <ng-template #selectMenu>\n @if (!disabled()) {\n <fw-menu-container\n [filterText]=\"menuItems().length > 0 ? '' : 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 [disabled]=\"disabled()\" [value]=\"selectValue\" (change)=\"handleClick($any($event))\">\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]=\"item.raw[descriptionProperty()]\"\n [value]=\"item.value\"\n [icon]=\"item.raw[iconProperty()]\"\n [disabled]=\"$any(item.raw).disabled\"\n >\n </fw-menu-item>\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"] }]
6875
6941
  }], ctorParameters: () => [{ type: i1$4.NgControl, decorators: [{
6876
6942
  type: Optional
6877
6943
  }, {
6878
6944
  type: Self
6879
6945
  }] }], propDecorators: { outsideClick: [{
6880
6946
  type: HostListener,
6881
- args: ['document:click', ['$event.target']]
6947
+ args: ['document:click', ['$event']]
6882
6948
  }], disabledClass: [{
6883
6949
  type: HostBinding,
6884
6950
  args: ['class.disabled']
@@ -9450,13 +9516,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImpo
9450
9516
  */
9451
9517
  class FwTypeaheadComponent {
9452
9518
  constructor() {
9453
- this.trigger = viewChild(CdkMenuTrigger);
9519
+ this.trigger = viewChild.required(CdkMenuTrigger);
9454
9520
  this.changeDetector = inject(ChangeDetectorRef);
9455
9521
  this.loading = model(false);
9456
9522
  this.disabled = model(false);
9523
+ this.touched = signal(false);
9524
+ this.hasAmbiguousInput = signal(false);
9457
9525
  this.onChange = (_) => {
9458
9526
  };
9459
9527
  this.onTouched = () => {
9528
+ this.touched.set(true);
9460
9529
  };
9461
9530
  this.value = model([]);
9462
9531
  this.value$ = toObservable(this.value);
@@ -9467,6 +9536,7 @@ class FwTypeaheadComponent {
9467
9536
  this.minOptionsHeight = input('0px');
9468
9537
  this.optionsWidth = input('');
9469
9538
  this.maxHeight = input('');
9539
+ this.selectType = input('multiple');
9470
9540
  this.notifyOnValueChanges = effect(() => {
9471
9541
  this.onChange(this.value());
9472
9542
  });
@@ -9474,7 +9544,16 @@ class FwTypeaheadComponent {
9474
9544
  this.searchValueChanges$ = toObservable(this.searchValue);
9475
9545
  this.allowNew = input(true);
9476
9546
  this.placeholder = input('Enter tags...');
9477
- this.showPlaceholder = computed(() => this.value().length === 0);
9547
+ this.errorText = signal('');
9548
+ this.displayedPlaceholder = computed(() => {
9549
+ if (this.selectType() === 'single') {
9550
+ return this.value()[0] || this.placeholder();
9551
+ }
9552
+ else {
9553
+ return this.value().length === 0 ? this.placeholder() : '';
9554
+ }
9555
+ });
9556
+ this.highlightPlaceholder = computed(() => this.selectType() === 'single' && this.value().length === 1);
9478
9557
  /**
9479
9558
  * Options after they've been both filtered for matching the search and already selected
9480
9559
  */
@@ -9516,22 +9595,31 @@ class FwTypeaheadComponent {
9516
9595
  return this.allowNew() && !alreadySelected && (!directMatch || loading);
9517
9596
  });
9518
9597
  this.addValue = (newValue) => {
9519
- const isInOptions = this.filteredOptions().includes(newValue);
9598
+ const isInOptions = this.filteredOptions()?.includes(newValue);
9520
9599
  const isNewWhileDisallowed = !this.allowNew() && !isInOptions;
9521
9600
  const isDuplicate = this.value().includes(newValue.toLowerCase());
9522
9601
  if (isNewWhileDisallowed || isDuplicate) {
9523
9602
  return;
9524
9603
  }
9525
- this.value.update(prevValue => [
9526
- ...prevValue || [],
9527
- newValue,
9528
- ]);
9604
+ if (this.selectType() === 'single') {
9605
+ this.value.set([newValue]);
9606
+ this.trigger().close();
9607
+ }
9608
+ else {
9609
+ this.value.update(prevValue => [
9610
+ ...prevValue || [],
9611
+ newValue,
9612
+ ]);
9613
+ }
9529
9614
  };
9530
9615
  this.handleOptionClick = (optionValue) => {
9531
9616
  this.onTouched();
9532
- this.addValue(optionValue);
9533
- this.focusInput();
9534
9617
  this.searchValue.set('');
9618
+ // Clear error state when an option is selected
9619
+ this.hasAmbiguousInput.set(false);
9620
+ this.errorText.set('');
9621
+ this.focusInput();
9622
+ this.addValue(optionValue);
9535
9623
  };
9536
9624
  this.resetFocusOnOptionsChange = effect(() => {
9537
9625
  if (this.displayedOptions() || this.searchValue()) {
@@ -9547,9 +9635,12 @@ class FwTypeaheadComponent {
9547
9635
  return onlyOne ? this.searchValue() : options[focused]?.value;
9548
9636
  });
9549
9637
  this.inputRef = viewChild.required('input');
9638
+ // eslint doesn't like keynames as object properties
9550
9639
  /* eslint-disable @typescript-eslint/naming-convention */
9551
9640
  this.keyHandlerMap = {
9552
- 'Enter': () => {
9641
+ 'Enter': (event) => {
9642
+ event.preventDefault();
9643
+ event.stopPropagation();
9553
9644
  this.onTouched();
9554
9645
  const newValue = this.focusedOption() || this.searchValue();
9555
9646
  const duplicate = this.value().find(val => val.toLowerCase() === newValue.toLowerCase());
@@ -9558,6 +9649,9 @@ class FwTypeaheadComponent {
9558
9649
  }
9559
9650
  this.addValue(newValue);
9560
9651
  this.searchValue.set('');
9652
+ // Clear error state when Enter is pressed to commit input
9653
+ this.hasAmbiguousInput.set(false);
9654
+ this.errorText.set('');
9561
9655
  },
9562
9656
  'ArrowDown': () => {
9563
9657
  this.moveFocused('down');
@@ -9573,7 +9667,7 @@ class FwTypeaheadComponent {
9573
9667
  return !searchIsEmpty;
9574
9668
  },
9575
9669
  'Tab': () => {
9576
- if (this.trigger().isOpen) {
9670
+ if (this.trigger().isOpen()) {
9577
9671
  this.trigger().close();
9578
9672
  }
9579
9673
  },
@@ -9588,22 +9682,34 @@ class FwTypeaheadComponent {
9588
9682
  writeValue(incomingValue) {
9589
9683
  this.value.set(incomingValue);
9590
9684
  }
9685
+ clearValue() {
9686
+ this.value.set([]);
9687
+ }
9591
9688
  registerOnChange(onChangeFn) {
9592
9689
  this.onChange = onChangeFn;
9593
9690
  }
9594
9691
  registerOnTouched(onTouchedFn) {
9595
- this.onTouched = onTouchedFn;
9692
+ this.onTouched = () => {
9693
+ this.touched.set(true);
9694
+ onTouchedFn();
9695
+ };
9596
9696
  }
9597
9697
  onClick(event) {
9598
9698
  event.preventDefault();
9599
9699
  event.stopPropagation();
9600
9700
  }
9601
9701
  handleSearchChange(event) {
9602
- if (this.trigger().closed) {
9702
+ const keyIsAlphanumeric = event.key.length === 1;
9703
+ if (this.trigger().closed && keyIsAlphanumeric) {
9603
9704
  this.trigger().open();
9604
9705
  }
9605
9706
  const value = event.target.value;
9606
9707
  this.searchValue.set(value);
9708
+ // Clear error state when user starts typing or clears the input
9709
+ if (!value.trim() || this.hasAmbiguousInput()) {
9710
+ this.hasAmbiguousInput.set(false);
9711
+ this.errorText.set('');
9712
+ }
9607
9713
  }
9608
9714
  closeChip(chipValue) {
9609
9715
  const currentValue = this.value();
@@ -9613,7 +9719,7 @@ class FwTypeaheadComponent {
9613
9719
  // eslint-disable-next-line @rx-angular/no-explicit-change-detection-apis
9614
9720
  this.changeDetector.detectChanges();
9615
9721
  }
9616
- focusInput(e = null) {
9722
+ focusInput(e) {
9617
9723
  e?.preventDefault();
9618
9724
  e?.stopPropagation();
9619
9725
  e?.stopImmediatePropagation();
@@ -9622,6 +9728,12 @@ class FwTypeaheadComponent {
9622
9728
  }
9623
9729
  onFocusLoss(_) {
9624
9730
  this.onTouched();
9731
+ // Check if there's text in the search field that wasn't committed
9732
+ const hasUncommittedText = this.searchValue().trim().length > 0;
9733
+ if (hasUncommittedText) {
9734
+ this.hasAmbiguousInput.set(true);
9735
+ this.errorText.set('Field contains ambiguous input, please either clear, select from available options, or press enter to include input.');
9736
+ }
9625
9737
  }
9626
9738
  /* eslint-enable */
9627
9739
  handleKey(e) {
@@ -9654,13 +9766,13 @@ class FwTypeaheadComponent {
9654
9766
  newlyFocused.scrollIntoView();
9655
9767
  }
9656
9768
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: FwTypeaheadComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
9657
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.18", type: FwTypeaheadComponent, isStandalone: true, selector: "fw-typeahead", inputs: { loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, optionsInput: { classPropertyName: "optionsInput", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, maxOptionsHeight: { classPropertyName: "maxOptionsHeight", publicName: "maxOptionsHeight", isSignal: true, isRequired: false, transformFunction: null }, minOptionsHeight: { classPropertyName: "minOptionsHeight", publicName: "minOptionsHeight", isSignal: true, isRequired: false, transformFunction: null }, optionsWidth: { classPropertyName: "optionsWidth", publicName: "optionsWidth", isSignal: true, isRequired: false, transformFunction: null }, maxHeight: { classPropertyName: "maxHeight", publicName: "maxHeight", isSignal: true, isRequired: false, transformFunction: null }, searchValue: { classPropertyName: "searchValue", publicName: "searchValue", isSignal: true, isRequired: false, transformFunction: null }, allowNew: { classPropertyName: "allowNew", publicName: "allowNew", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { loading: "loadingChange", disabled: "disabledChange", value: "valueChange", searchValue: "searchValueChange" }, host: { listeners: { "document:click": "outsideClick()", "click": "onClick($event)" } }, providers: [
9769
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.18", type: FwTypeaheadComponent, isStandalone: true, selector: "fw-typeahead", inputs: { loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, optionsInput: { classPropertyName: "optionsInput", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, maxOptionsHeight: { classPropertyName: "maxOptionsHeight", publicName: "maxOptionsHeight", isSignal: true, isRequired: false, transformFunction: null }, minOptionsHeight: { classPropertyName: "minOptionsHeight", publicName: "minOptionsHeight", isSignal: true, isRequired: false, transformFunction: null }, optionsWidth: { classPropertyName: "optionsWidth", publicName: "optionsWidth", isSignal: true, isRequired: false, transformFunction: null }, maxHeight: { classPropertyName: "maxHeight", publicName: "maxHeight", isSignal: true, isRequired: false, transformFunction: null }, selectType: { classPropertyName: "selectType", publicName: "selectType", isSignal: true, isRequired: false, transformFunction: null }, searchValue: { classPropertyName: "searchValue", publicName: "searchValue", isSignal: true, isRequired: false, transformFunction: null }, allowNew: { classPropertyName: "allowNew", publicName: "allowNew", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { loading: "loadingChange", disabled: "disabledChange", value: "valueChange", searchValue: "searchValueChange" }, host: { listeners: { "document:click": "outsideClick()", "click": "onClick($event)" }, properties: { "class.errored": "hasAmbiguousInput()", "class.fw-touched": "touched()" } }, providers: [
9658
9770
  {
9659
9771
  provide: NG_VALUE_ACCESSOR,
9660
9772
  useExisting: forwardRef(() => FwTypeaheadComponent),
9661
9773
  multi: true,
9662
9774
  },
9663
- ], viewQueries: [{ propertyName: "trigger", first: true, predicate: CdkMenuTrigger, descendants: true, isSignal: true }, { propertyName: "displayedOptions", predicate: FwMenuItemComponent, descendants: true, isSignal: true }, { propertyName: "inputRef", first: true, predicate: ["input"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\n (click)=\"focusInput($event)\"\n class=\"input-container\"\n [ngClass]=\"{ 'disabled': disabled() }\"\n [style.max-height]=\"maxHeight()\"\n [cdkMenuTriggerFor]=\"menuContent\"\n fwMenuRegister\n #inputContainer>\n <fw-chip-list class=\"chips\" [disableOverflow]=\"!!maxHeight()\">\n @for(val of value(); track val) {\n <fw-chip\n color=\"primary\"\n [showClose]=\"true\"\n [title]=\"val\"\n [selectable]=\"false\"\n (close)=\"closeChip(val)\"\n />\n }\n </fw-chip-list>\n <input\n test-id=\"typeahead-input\"\n [placeholder]=\"showPlaceholder() ? placeholder() : ''\"\n [disabled]=\"disabled()\"\n [value]=\"searchValue()\"\n (keyup)=\"handleSearchChange($event)\"\n (keydown)=\"handleKey($event)\"\n (focusout)=\"onFocusLoss($event)\"\n #input\n type=\"text\"\n />\n @if(loading()) {\n <fw-progress-spinner size=\"small\"/>\n }\n</div>\n<ng-template #menuContent>\n <fw-menu-container\n [maxHeight]=\"maxOptionsHeight()\"\n [minHeight]=\"minOptionsHeight()\"\n [width]=\"optionsWidth() || inputContainer.offsetWidth - 2 + 'px'\"\n >\n <fw-menu>\n @if(loading() && !displayNewOption()) {\n <fw-menu-item title=\"Searching...\" [disabled]=\"true\"/>\n } @else if(!loading()) {\n @for (option of filteredOptions(); track option) {\n <fw-menu-item\n (click)=\"handleOptionClick($event)\"\n (mouseenter)=\"setFocusByValue(option)\"\n [title]=\"option\"\n [focused]=\"focusedOption() === option\"\n [value]=\"option\"\n />\n }\n @empty {\n @if (!displayNewOption()) {\n <fw-menu-item title=\"No tag suggestions\" [disabled]=\"true\"/>\n }\n }\n }\n @if(displayNewOption()) {\n <fw-menu-item\n (click)=\"handleOptionClick($event)\"\n (mouseenter)=\"setFocusByValue(searchValue())\"\n [title]=\"searchValue()\"\n [value]=\"searchValue()\"\n [focused]=\"focusedOption() === searchValue()\">\n <p class=\"new-tag\">New</p>\n </fw-menu-item>\n }\n </fw-menu>\n </fw-menu-container>\n</ng-template>\n", styles: [".new-tag{margin:0;color:var(--typography-light)}:host.disabled{cursor:not-allowed}:host.disabled fw-icon{cursor:not-allowed!important}:host{display:flex;flex-direction:column;width:100%;line-height:21px}:host .chips,:host fw-progress-spinner{margin:-4px 0}:host .input-container{width:100%;box-sizing:border-box;color:var(--typography-light);background:var(--page-light);display:flex;padding:8px;row-gap:8px;align-items:center;border-radius:6px;border:1px solid var(--separations-input);font-family:Inter,sans-serif;flex-wrap:wrap;overflow-y:auto}:host .input-container:focus-within{border:1px solid var(--primary-base)}:host .input-container input{min-width:80px;font-size:14px;flex:1 1 80px;color:var(--typography-base);background:var(--page-light);border:none}:host .input-container input:focus{outline:none;border:none}:host .input-container input::placeholder{color:var(--typography-light)}:host .input-container .context{color:var(--typography-light)}:host.errored .input-container,:host.ng-touched.ng-invalid .input-container{border:1px solid var(--red-base)}.disabled{opacity:.4;pointer-events:none}\n"], dependencies: [{ kind: "ngmodule", type: FwTextInputModule }, { kind: "ngmodule", type: FwChipModule }, { kind: "component", type: FwChipComponent, selector: "fw-chip", inputs: ["value", "variant", "color", "icon", "title", "description", "showClose", "disabled", "selected", "textWrap", "selectable"], outputs: ["close", "select"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "ngmodule", type: FwMenuModule }, { kind: "component", type: FwMenuComponent, selector: "fw-menu", inputs: ["disabled", "size", "multiSelect", "useCheckbox", "value"], outputs: ["change"] }, { 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: FwMenuItemComponent, selector: "fw-menu-item", inputs: ["value", "size", "title", "description", "icon", "iconColor", "disabled", "showCheckbox", "checkboxColor", "multiSelect", "hidden", "collapsed", "href", "target", "subItemsOpen", "mouseEnterHandler", "focused", "selected"], outputs: ["mouseEnterHandlerChange", "click"] }, { kind: "ngmodule", type: CdkMenuModule }, { kind: "directive", type: i1$3.CdkMenuTrigger, selector: "[cdkMenuTriggerFor]", inputs: ["cdkMenuTriggerFor", "cdkMenuPosition", "cdkMenuTriggerData"], outputs: ["cdkMenuOpened", "cdkMenuClosed"], exportAs: ["cdkMenuTriggerFor"] }, { kind: "directive", type: MenuRegisterDirective, selector: "[fwMenuRegister]" }, { kind: "ngmodule", type: FwProgressModule }, { kind: "component", type: FwProgressSpinnerComponent, selector: "fw-progress-spinner", inputs: ["mode", "size", "color", "showValue", "value"] }, { kind: "component", type: FwChipListComponent, selector: "fw-chip-list", inputs: ["resizeDebounceMs", "disableOverflow"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
9775
+ ], viewQueries: [{ propertyName: "trigger", first: true, predicate: CdkMenuTrigger, descendants: true, isSignal: true }, { propertyName: "displayedOptions", predicate: FwMenuItemComponent, descendants: true, isSignal: true }, { propertyName: "inputRef", first: true, predicate: ["input"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"full-container\">\n <div\n (click)=\"focusInput($event)\"\n class=\"input-container\"\n [class.disabled]=\"disabled()\"\n [style.max-height]=\"maxHeight()\"\n [cdkMenuTriggerFor]=\"menuContent\"\n fwMenuRegister\n #inputContainer>\n @if (selectType() === 'multiple') {\n <fw-chip-list class=\"chips\" [disableOverflow]=\"!!maxHeight()\">\n @for(val of value(); track val) {\n <fw-chip\n color=\"primary\"\n [showClose]=\"true\"\n [title]=\"val\"\n [selectable]=\"false\"\n (close)=\"closeChip(val)\"\n />\n }\n </fw-chip-list>\n }\n <input\n test-id=\"typeahead-input\"\n [class.highlight-placeholder]=\"highlightPlaceholder()\"\n [placeholder]=\"displayedPlaceholder()\"\n [disabled]=\"disabled()\"\n [value]=\"searchValue()\"\n (keyup)=\"handleSearchChange($event)\"\n (keydown)=\"handleKey($event)\"\n (focusout)=\"onFocusLoss($event)\"\n #input\n type=\"text\"\n />\n @if(loading()) {\n <fw-progress-spinner size=\"small\"/>\n }\n @if(selectType() === 'single' && value().length === 1) {\n <fw-icon class=\"clear-icon\" (click)=\"clearValue()\">close</fw-icon>\n }\n </div>\n @if(errorText()) {\n <p class=\"vision-p4 error-text\">{{ errorText() }}</p>\n }\n</div>\n<ng-template #menuContent>\n <fw-menu-container\n [maxHeight]=\"maxOptionsHeight()\"\n [minHeight]=\"minOptionsHeight()\"\n [width]=\"optionsWidth() || inputContainer.offsetWidth - 2 + 'px'\"\n >\n <fw-menu>\n @if(loading() && !displayNewOption()) {\n <fw-menu-item title=\"Searching...\" [disabled]=\"true\"/>\n } @else if(!loading()) {\n @for (option of filteredOptions(); track option) {\n <fw-menu-item\n (click)=\"handleOptionClick($event)\"\n (mouseenter)=\"setFocusByValue(option)\"\n [title]=\"option\"\n [focused]=\"focusedOption() === option\"\n [value]=\"option\"\n />\n }\n @empty {\n @if (!displayNewOption()) {\n <fw-menu-item title=\"No tag suggestions\" [disabled]=\"true\"/>\n }\n }\n }\n @if(displayNewOption()) {\n <fw-menu-item\n (click)=\"handleOptionClick($event)\"\n (mouseenter)=\"setFocusByValue(searchValue())\"\n [title]=\"searchValue()\"\n [value]=\"searchValue()\"\n [focused]=\"focusedOption() === searchValue()\">\n <p class=\"new-tag\">New</p>\n </fw-menu-item>\n }\n </fw-menu>\n </fw-menu-container>\n</ng-template>\n", styles: [".new-tag{margin:0;color:var(--typography-light)}:host.disabled{cursor:not-allowed}:host.disabled fw-icon{cursor:not-allowed!important}:host{display:flex;flex-direction:column;width:100%;line-height:21px}:host .chips,:host fw-progress-spinner{margin:-4px 0}:host .full-container{display:flex;flex-direction:column;width:100%}:host .highlight-placeholder::placeholder{color:var(--typography-base)!important}:host .clear-icon{cursor:pointer}:host .input-container{width:100%;box-sizing:border-box;color:var(--typography-light);background:var(--page-light);display:flex;padding:8px;row-gap:8px;align-items:center;border-radius:6px;border:1px solid var(--separations-input);font-family:Inter,sans-serif;flex-wrap:wrap;overflow-y:auto}:host .input-container:focus-within{border:1px solid var(--primary-base)}:host .input-container input{min-width:80px;font-size:14px;flex:1 1 80px;color:var(--typography-base);background:var(--page-light);border:none}:host .input-container input:focus{outline:none;border:none}:host .input-container input::placeholder{color:var(--typography-light)}:host .input-container .context{color:var(--typography-light)}:host .error-text{color:var(--red-base);margin:4px 0 0}:host.errored .input-container{border:1px solid var(--red-base)}.disabled{opacity:.4;pointer-events:none}\n"], dependencies: [{ kind: "ngmodule", type: FwTextInputModule }, { kind: "ngmodule", type: FwChipModule }, { kind: "component", type: FwChipComponent, selector: "fw-chip", inputs: ["value", "variant", "color", "icon", "title", "description", "showClose", "disabled", "selected", "textWrap", "selectable"], outputs: ["close", "select"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "ngmodule", type: FwMenuModule }, { kind: "component", type: FwMenuComponent, selector: "fw-menu", inputs: ["disabled", "size", "multiSelect", "useCheckbox", "value"], outputs: ["change"] }, { 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: FwMenuItemComponent, selector: "fw-menu-item", inputs: ["value", "size", "title", "description", "icon", "iconColor", "disabled", "showCheckbox", "checkboxColor", "multiSelect", "hidden", "collapsed", "href", "target", "subItemsOpen", "mouseEnterHandler", "focused", "selected"], outputs: ["mouseEnterHandlerChange", "click"] }, { kind: "ngmodule", type: CdkMenuModule }, { kind: "directive", type: i1$3.CdkMenuTrigger, selector: "[cdkMenuTriggerFor]", inputs: ["cdkMenuTriggerFor", "cdkMenuPosition", "cdkMenuTriggerData"], outputs: ["cdkMenuOpened", "cdkMenuClosed"], exportAs: ["cdkMenuTriggerFor"] }, { kind: "directive", type: MenuRegisterDirective, selector: "[fwMenuRegister]" }, { kind: "ngmodule", type: FwProgressModule }, { kind: "component", type: FwProgressSpinnerComponent, selector: "fw-progress-spinner", inputs: ["mode", "size", "color", "showValue", "value"] }, { kind: "component", type: FwChipListComponent, selector: "fw-chip-list", inputs: ["resizeDebounceMs", "disableOverflow"] }, { kind: "component", type: FwIconComponent, selector: "fw-icon", inputs: ["size", "color"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
9664
9776
  }
9665
9777
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: FwTypeaheadComponent, decorators: [{
9666
9778
  type: Component,
@@ -9673,14 +9785,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImpo
9673
9785
  MenuRegisterDirective,
9674
9786
  FwProgressModule,
9675
9787
  FwChipListComponent,
9676
- NgClass,
9788
+ FwIconComponent,
9677
9789
  ], providers: [
9678
9790
  {
9679
9791
  provide: NG_VALUE_ACCESSOR,
9680
9792
  useExisting: forwardRef(() => FwTypeaheadComponent),
9681
9793
  multi: true,
9682
9794
  },
9683
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n (click)=\"focusInput($event)\"\n class=\"input-container\"\n [ngClass]=\"{ 'disabled': disabled() }\"\n [style.max-height]=\"maxHeight()\"\n [cdkMenuTriggerFor]=\"menuContent\"\n fwMenuRegister\n #inputContainer>\n <fw-chip-list class=\"chips\" [disableOverflow]=\"!!maxHeight()\">\n @for(val of value(); track val) {\n <fw-chip\n color=\"primary\"\n [showClose]=\"true\"\n [title]=\"val\"\n [selectable]=\"false\"\n (close)=\"closeChip(val)\"\n />\n }\n </fw-chip-list>\n <input\n test-id=\"typeahead-input\"\n [placeholder]=\"showPlaceholder() ? placeholder() : ''\"\n [disabled]=\"disabled()\"\n [value]=\"searchValue()\"\n (keyup)=\"handleSearchChange($event)\"\n (keydown)=\"handleKey($event)\"\n (focusout)=\"onFocusLoss($event)\"\n #input\n type=\"text\"\n />\n @if(loading()) {\n <fw-progress-spinner size=\"small\"/>\n }\n</div>\n<ng-template #menuContent>\n <fw-menu-container\n [maxHeight]=\"maxOptionsHeight()\"\n [minHeight]=\"minOptionsHeight()\"\n [width]=\"optionsWidth() || inputContainer.offsetWidth - 2 + 'px'\"\n >\n <fw-menu>\n @if(loading() && !displayNewOption()) {\n <fw-menu-item title=\"Searching...\" [disabled]=\"true\"/>\n } @else if(!loading()) {\n @for (option of filteredOptions(); track option) {\n <fw-menu-item\n (click)=\"handleOptionClick($event)\"\n (mouseenter)=\"setFocusByValue(option)\"\n [title]=\"option\"\n [focused]=\"focusedOption() === option\"\n [value]=\"option\"\n />\n }\n @empty {\n @if (!displayNewOption()) {\n <fw-menu-item title=\"No tag suggestions\" [disabled]=\"true\"/>\n }\n }\n }\n @if(displayNewOption()) {\n <fw-menu-item\n (click)=\"handleOptionClick($event)\"\n (mouseenter)=\"setFocusByValue(searchValue())\"\n [title]=\"searchValue()\"\n [value]=\"searchValue()\"\n [focused]=\"focusedOption() === searchValue()\">\n <p class=\"new-tag\">New</p>\n </fw-menu-item>\n }\n </fw-menu>\n </fw-menu-container>\n</ng-template>\n", styles: [".new-tag{margin:0;color:var(--typography-light)}:host.disabled{cursor:not-allowed}:host.disabled fw-icon{cursor:not-allowed!important}:host{display:flex;flex-direction:column;width:100%;line-height:21px}:host .chips,:host fw-progress-spinner{margin:-4px 0}:host .input-container{width:100%;box-sizing:border-box;color:var(--typography-light);background:var(--page-light);display:flex;padding:8px;row-gap:8px;align-items:center;border-radius:6px;border:1px solid var(--separations-input);font-family:Inter,sans-serif;flex-wrap:wrap;overflow-y:auto}:host .input-container:focus-within{border:1px solid var(--primary-base)}:host .input-container input{min-width:80px;font-size:14px;flex:1 1 80px;color:var(--typography-base);background:var(--page-light);border:none}:host .input-container input:focus{outline:none;border:none}:host .input-container input::placeholder{color:var(--typography-light)}:host .input-container .context{color:var(--typography-light)}:host.errored .input-container,:host.ng-touched.ng-invalid .input-container{border:1px solid var(--red-base)}.disabled{opacity:.4;pointer-events:none}\n"] }]
9795
+ ], host: {
9796
+ '[class.errored]': 'hasAmbiguousInput()',
9797
+ '[class.fw-touched]': 'touched()',
9798
+ }, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"full-container\">\n <div\n (click)=\"focusInput($event)\"\n class=\"input-container\"\n [class.disabled]=\"disabled()\"\n [style.max-height]=\"maxHeight()\"\n [cdkMenuTriggerFor]=\"menuContent\"\n fwMenuRegister\n #inputContainer>\n @if (selectType() === 'multiple') {\n <fw-chip-list class=\"chips\" [disableOverflow]=\"!!maxHeight()\">\n @for(val of value(); track val) {\n <fw-chip\n color=\"primary\"\n [showClose]=\"true\"\n [title]=\"val\"\n [selectable]=\"false\"\n (close)=\"closeChip(val)\"\n />\n }\n </fw-chip-list>\n }\n <input\n test-id=\"typeahead-input\"\n [class.highlight-placeholder]=\"highlightPlaceholder()\"\n [placeholder]=\"displayedPlaceholder()\"\n [disabled]=\"disabled()\"\n [value]=\"searchValue()\"\n (keyup)=\"handleSearchChange($event)\"\n (keydown)=\"handleKey($event)\"\n (focusout)=\"onFocusLoss($event)\"\n #input\n type=\"text\"\n />\n @if(loading()) {\n <fw-progress-spinner size=\"small\"/>\n }\n @if(selectType() === 'single' && value().length === 1) {\n <fw-icon class=\"clear-icon\" (click)=\"clearValue()\">close</fw-icon>\n }\n </div>\n @if(errorText()) {\n <p class=\"vision-p4 error-text\">{{ errorText() }}</p>\n }\n</div>\n<ng-template #menuContent>\n <fw-menu-container\n [maxHeight]=\"maxOptionsHeight()\"\n [minHeight]=\"minOptionsHeight()\"\n [width]=\"optionsWidth() || inputContainer.offsetWidth - 2 + 'px'\"\n >\n <fw-menu>\n @if(loading() && !displayNewOption()) {\n <fw-menu-item title=\"Searching...\" [disabled]=\"true\"/>\n } @else if(!loading()) {\n @for (option of filteredOptions(); track option) {\n <fw-menu-item\n (click)=\"handleOptionClick($event)\"\n (mouseenter)=\"setFocusByValue(option)\"\n [title]=\"option\"\n [focused]=\"focusedOption() === option\"\n [value]=\"option\"\n />\n }\n @empty {\n @if (!displayNewOption()) {\n <fw-menu-item title=\"No tag suggestions\" [disabled]=\"true\"/>\n }\n }\n }\n @if(displayNewOption()) {\n <fw-menu-item\n (click)=\"handleOptionClick($event)\"\n (mouseenter)=\"setFocusByValue(searchValue())\"\n [title]=\"searchValue()\"\n [value]=\"searchValue()\"\n [focused]=\"focusedOption() === searchValue()\">\n <p class=\"new-tag\">New</p>\n </fw-menu-item>\n }\n </fw-menu>\n </fw-menu-container>\n</ng-template>\n", styles: [".new-tag{margin:0;color:var(--typography-light)}:host.disabled{cursor:not-allowed}:host.disabled fw-icon{cursor:not-allowed!important}:host{display:flex;flex-direction:column;width:100%;line-height:21px}:host .chips,:host fw-progress-spinner{margin:-4px 0}:host .full-container{display:flex;flex-direction:column;width:100%}:host .highlight-placeholder::placeholder{color:var(--typography-base)!important}:host .clear-icon{cursor:pointer}:host .input-container{width:100%;box-sizing:border-box;color:var(--typography-light);background:var(--page-light);display:flex;padding:8px;row-gap:8px;align-items:center;border-radius:6px;border:1px solid var(--separations-input);font-family:Inter,sans-serif;flex-wrap:wrap;overflow-y:auto}:host .input-container:focus-within{border:1px solid var(--primary-base)}:host .input-container input{min-width:80px;font-size:14px;flex:1 1 80px;color:var(--typography-base);background:var(--page-light);border:none}:host .input-container input:focus{outline:none;border:none}:host .input-container input::placeholder{color:var(--typography-light)}:host .input-container .context{color:var(--typography-light)}:host .error-text{color:var(--red-base);margin:4px 0 0}:host.errored .input-container{border:1px solid var(--red-base)}.disabled{opacity:.4;pointer-events:none}\n"] }]
9684
9799
  }], propDecorators: { outsideClick: [{
9685
9800
  type: HostListener,
9686
9801
  args: ['document:click']