@flywheel-io/vision 19.3.2 → 19.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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);
@@ -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']