@siemens/element-ng 48.1.0 → 48.2.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.
Files changed (104) hide show
  1. package/README.md +5 -0
  2. package/card/index.d.ts +68 -29
  3. package/common/index.d.ts +11 -0
  4. package/dashboard/index.d.ts +1 -0
  5. package/datepicker/index.d.ts +30 -34
  6. package/fesm2022/siemens-element-ng-breadcrumb.mjs +2 -2
  7. package/fesm2022/siemens-element-ng-breadcrumb.mjs.map +1 -1
  8. package/fesm2022/siemens-element-ng-card.mjs +103 -37
  9. package/fesm2022/siemens-element-ng-card.mjs.map +1 -1
  10. package/fesm2022/siemens-element-ng-common.mjs +6 -0
  11. package/fesm2022/siemens-element-ng-common.mjs.map +1 -1
  12. package/fesm2022/siemens-element-ng-connection-strength.mjs +2 -2
  13. package/fesm2022/siemens-element-ng-connection-strength.mjs.map +1 -1
  14. package/fesm2022/siemens-element-ng-content-action-bar.mjs +2 -2
  15. package/fesm2022/siemens-element-ng-content-action-bar.mjs.map +1 -1
  16. package/fesm2022/siemens-element-ng-dashboard.mjs +10 -8
  17. package/fesm2022/siemens-element-ng-dashboard.mjs.map +1 -1
  18. package/fesm2022/siemens-element-ng-datatable.mjs +5 -0
  19. package/fesm2022/siemens-element-ng-datatable.mjs.map +1 -1
  20. package/fesm2022/siemens-element-ng-date-range-filter.mjs +1 -1
  21. package/fesm2022/siemens-element-ng-date-range-filter.mjs.map +1 -1
  22. package/fesm2022/siemens-element-ng-datepicker.mjs +173 -151
  23. package/fesm2022/siemens-element-ng-datepicker.mjs.map +1 -1
  24. package/fesm2022/siemens-element-ng-file-uploader.mjs +2 -2
  25. package/fesm2022/siemens-element-ng-file-uploader.mjs.map +1 -1
  26. package/fesm2022/siemens-element-ng-filter-bar.mjs +5 -5
  27. package/fesm2022/siemens-element-ng-filter-bar.mjs.map +1 -1
  28. package/fesm2022/siemens-element-ng-filtered-search.mjs +15 -3
  29. package/fesm2022/siemens-element-ng-filtered-search.mjs.map +1 -1
  30. package/fesm2022/siemens-element-ng-form.mjs +7 -1
  31. package/fesm2022/siemens-element-ng-form.mjs.map +1 -1
  32. package/fesm2022/siemens-element-ng-formly.mjs +2 -2
  33. package/fesm2022/siemens-element-ng-formly.mjs.map +1 -1
  34. package/fesm2022/siemens-element-ng-header-dropdown.mjs +13 -1
  35. package/fesm2022/siemens-element-ng-header-dropdown.mjs.map +1 -1
  36. package/fesm2022/siemens-element-ng-ip-input.mjs +62 -28
  37. package/fesm2022/siemens-element-ng-ip-input.mjs.map +1 -1
  38. package/fesm2022/siemens-element-ng-language-switcher.mjs +1 -1
  39. package/fesm2022/siemens-element-ng-language-switcher.mjs.map +1 -1
  40. package/fesm2022/siemens-element-ng-list-details.mjs +2 -2
  41. package/fesm2022/siemens-element-ng-list-details.mjs.map +1 -1
  42. package/fesm2022/siemens-element-ng-navbar-vertical.mjs +1 -1
  43. package/fesm2022/siemens-element-ng-navbar-vertical.mjs.map +1 -1
  44. package/fesm2022/siemens-element-ng-pagination.mjs +2 -2
  45. package/fesm2022/siemens-element-ng-pagination.mjs.map +1 -1
  46. package/fesm2022/siemens-element-ng-photo-upload.mjs +1 -1
  47. package/fesm2022/siemens-element-ng-photo-upload.mjs.map +1 -1
  48. package/fesm2022/siemens-element-ng-search-bar.mjs +14 -4
  49. package/fesm2022/siemens-element-ng-search-bar.mjs.map +1 -1
  50. package/fesm2022/siemens-element-ng-side-panel.mjs +2 -2
  51. package/fesm2022/siemens-element-ng-side-panel.mjs.map +1 -1
  52. package/fesm2022/siemens-element-ng-status-bar.mjs +2 -2
  53. package/fesm2022/siemens-element-ng-status-bar.mjs.map +1 -1
  54. package/fesm2022/siemens-element-ng-tabs-legacy.mjs +2 -2
  55. package/fesm2022/siemens-element-ng-tabs-legacy.mjs.map +1 -1
  56. package/fesm2022/siemens-element-ng-tabs.mjs +5 -5
  57. package/fesm2022/siemens-element-ng-tabs.mjs.map +1 -1
  58. package/fesm2022/siemens-element-ng-tooltip.mjs +5 -6
  59. package/fesm2022/siemens-element-ng-tooltip.mjs.map +1 -1
  60. package/fesm2022/siemens-element-ng-translate.mjs.map +1 -1
  61. package/fesm2022/siemens-element-ng-tree-view.mjs +4 -4
  62. package/fesm2022/siemens-element-ng-tree-view.mjs.map +1 -1
  63. package/fesm2022/siemens-element-ng-typeahead.mjs +329 -257
  64. package/fesm2022/siemens-element-ng-typeahead.mjs.map +1 -1
  65. package/filter-bar/index.d.ts +9 -3
  66. package/header-dropdown/index.d.ts +7 -0
  67. package/ip-input/index.d.ts +42 -5
  68. package/package.json +23 -19
  69. package/schematics/collection.json +34 -0
  70. package/schematics/migrations/action-modal-migration/action-modal-migration.js +121 -0
  71. package/schematics/migrations/action-modal-migration/action-modal.mappings.js +98 -0
  72. package/schematics/migrations/action-modal-migration/index.js +5 -0
  73. package/schematics/migrations/index.js +13 -0
  74. package/schematics/migrations/schema.json +16 -0
  75. package/schematics/migrations/to-legacy-migration/to-legacy-migration.js +55 -0
  76. package/schematics/migrations/to-legacy-migration/to-legacy-replacement.js +35 -0
  77. package/schematics/ng-add/index.js +16 -0
  78. package/schematics/ng-add/schema.json +16 -0
  79. package/schematics/scss-import-to-siemens-migration/index.js +101 -0
  80. package/schematics/scss-import-to-siemens-migration/schema.json +16 -0
  81. package/schematics/scss-import-to-siemens-migration/style-mappings.js +46 -0
  82. package/schematics/simpl-siemens-migration/index.js +18 -0
  83. package/schematics/simpl-siemens-migration/schema.json +16 -0
  84. package/schematics/ts-import-to-siemens-migration/index.js +118 -0
  85. package/schematics/ts-import-to-siemens-migration/mappings/charts-ng-mappings.js +70 -0
  86. package/schematics/ts-import-to-siemens-migration/mappings/dashboards-ng-mappings.js +52 -0
  87. package/schematics/ts-import-to-siemens-migration/mappings/element-ng-mappings.js +651 -0
  88. package/schematics/ts-import-to-siemens-migration/mappings/element-translate-ng-mappings.js +21 -0
  89. package/schematics/ts-import-to-siemens-migration/mappings/index.js +9 -0
  90. package/schematics/ts-import-to-siemens-migration/mappings/maps-ng-mappings.js +46 -0
  91. package/schematics/ts-import-to-siemens-migration/model.js +4 -0
  92. package/schematics/ts-import-to-siemens-migration/schema.json +16 -0
  93. package/schematics/utils/html-utils.js +72 -0
  94. package/schematics/utils/index.js +10 -0
  95. package/schematics/utils/project-utils.js +75 -0
  96. package/schematics/utils/schematics-file-system.js +22 -0
  97. package/schematics/utils/template-utils.js +114 -0
  98. package/schematics/utils/testing.js +41 -0
  99. package/schematics/utils/ts-utils.js +195 -0
  100. package/search-bar/index.d.ts +11 -1
  101. package/template-i18n.json +7 -0
  102. package/tooltip/index.d.ts +1 -1
  103. package/translate/index.d.ts +7 -0
  104. package/typeahead/index.d.ts +85 -4
@@ -1,15 +1,34 @@
1
1
  import { Overlay } from '@angular/cdk/overlay';
2
2
  import { ComponentPortal } from '@angular/cdk/portal';
3
3
  import * as i0 from '@angular/core';
4
- import { inject, computed, viewChild, ElementRef, HostListener, ChangeDetectionStrategy, Component, input, booleanAttribute, numberAttribute, output, signal, Injector, Directive, NgModule } from '@angular/core';
4
+ import { Directive, inject, computed, viewChild, ElementRef, HostListener, ChangeDetectionStrategy, Component, input, booleanAttribute, numberAttribute, output, signal, Injector, effect, NgModule } from '@angular/core';
5
+ import { toSignal } from '@angular/core/rxjs-interop';
5
6
  import * as i1 from '@siemens/element-ng/autocomplete';
6
7
  import { SiAutocompleteDirective, SiAutocompleteListboxDirective, SiAutocompleteOptionDirective } from '@siemens/element-ng/autocomplete';
7
8
  import { SiTranslatePipe, t } from '@siemens/element-translate-ng/translate';
8
- import { ReplaySubject, isObservable, of } from 'rxjs';
9
+ import { ReplaySubject, isObservable } from 'rxjs';
9
10
  import { map } from 'rxjs/operators';
10
11
  import { NgTemplateOutlet } from '@angular/common';
11
12
  import { SiIconComponent } from '@siemens/element-ng/icon';
12
13
 
14
+ /**
15
+ * Copyright (c) Siemens 2016 - 2025
16
+ * SPDX-License-Identifier: MIT
17
+ */
18
+ class SiTypeaheadItemTemplateDirective {
19
+ static ngTemplateContextGuard(dir, ctx) {
20
+ return true;
21
+ }
22
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: SiTypeaheadItemTemplateDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
23
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.0.6", type: SiTypeaheadItemTemplateDirective, isStandalone: true, selector: "[siTypeaheadItemTemplate]", ngImport: i0 });
24
+ }
25
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: SiTypeaheadItemTemplateDirective, decorators: [{
26
+ type: Directive,
27
+ args: [{
28
+ selector: '[siTypeaheadItemTemplate]'
29
+ }]
30
+ }] });
31
+
13
32
  /**
14
33
  * Copyright (c) Siemens 2016 - 2025
15
34
  * SPDX-License-Identifier: MIT
@@ -56,7 +75,7 @@ class SiTypeaheadComponent {
56
75
  this.parent.selectMatch(match);
57
76
  }
58
77
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: SiTypeaheadComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
59
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.6", type: SiTypeaheadComponent, isStandalone: true, selector: "si-typeahead", host: { listeners: { "mousedown": "onMouseDown($event)" }, classAttribute: "w-100" }, viewQueries: [{ propertyName: "typeaheadElement", first: true, predicate: ["typeahead"], descendants: true, read: ElementRef, isSignal: true }], ngImport: i0, template: "<!-- Template to be used for every match, can be replaced using an input. -->\n<ng-template #defaultItemTemplate let-match=\"match\" siTypeaheadTemplate>\n @if (multiselect()) {\n <div class=\"d-flex pe-4\" aria-hidden=\"true\">\n <span class=\"form-check-input si-form-checkbox\" [class.checked]=\"match.itemSelected\"></span>\n </div>\n }\n @if (match.iconClass) {\n <si-icon class=\"icon me-2\" [icon]=\"match.iconClass\" />\n }\n @for (segment of match.result; track $index) {\n <span [class.typeahead-match-segment-matching]=\"segment.isMatching\">{{ segment.text }}</span>\n }\n</ng-template>\n\n<!-- Only display the component if there are any matches and set the CSS transform to properly position the typeahead -->\n<ul\n #typeahead\n class=\"typeahead dropdown-menu\"\n [siAutocompleteListboxFor]=\"autocompleteDirective\"\n [siAutocompleteDefaultIndex]=\"parent.typeaheadAutoSelectIndex()\"\n [attr.aria-label]=\"parent.typeaheadAutocompleteListLabel() | translate\"\n [class.d-none]=\"!matches().length\"\n (siAutocompleteOptionSubmitted)=\"selectMatch($event)\"\n>\n <!-- Loop through every match and bind events, the mousedown prevent default is to prevent the host from losing focus on click -->\n @for (match of matches(); track $index) {\n <li\n #typeaheadMatch\n class=\"dropdown-item me-4\"\n [siAutocompleteOption]=\"match\"\n [attr.aria-label]=\"match.text\"\n [attr.aria-selected]=\"multiselect() ? match.itemSelected : null\"\n (click)=\"$event.stopPropagation()\"\n (mousedown)=\"$event.preventDefault()\"\n >\n <!-- Display either a template set as the input or the template above -->\n <ng-template\n [ngTemplateOutlet]=\"parent.typeaheadItemTemplate() || defaultItemTemplate\"\n [ngTemplateOutletContext]=\"{\n item: match.option,\n index: $index,\n match: match,\n query: parent.query()\n }\"\n />\n </li>\n }\n</ul>\n", styles: [".dropdown-menu{display:block;position:relative;inset-block-start:0;inset-inline-start:0;margin-block-start:1px;overflow-y:auto;overflow-x:hidden;max-block-size:100%}.typeahead-match-segment-matching{font-weight:600}.dropdown-item *{flex-shrink:0}.dropdown-item span{white-space:pre-wrap}\n"], dependencies: [{ kind: "directive", type: SiAutocompleteListboxDirective, selector: "[siAutocompleteListboxFor]", inputs: ["id", "siAutocompleteListboxFor", "siAutocompleteDefaultIndex"], outputs: ["siAutocompleteOptionSubmitted"], exportAs: ["siAutocompleteListbox"] }, { kind: "directive", type: SiAutocompleteOptionDirective, selector: "[siAutocompleteOption]", inputs: ["id", "disabled", "siAutocompleteOption"], exportAs: ["siAutocompleteOption"] }, { kind: "component", type: SiIconComponent, selector: "si-icon", inputs: ["icon"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "pipe", type: SiTranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
78
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.6", type: SiTypeaheadComponent, isStandalone: true, selector: "si-typeahead", host: { listeners: { "mousedown": "onMouseDown($event)" }, classAttribute: "w-100" }, viewQueries: [{ propertyName: "typeaheadElement", first: true, predicate: ["typeahead"], descendants: true, read: ElementRef, isSignal: true }], ngImport: i0, template: "<!-- Template to be used for every match, can be replaced using an input. -->\n<ng-template #defaultItemTemplate let-match=\"match\" siTypeaheadItemTemplate>\n @if (multiselect()) {\n <div class=\"d-flex pe-4\" aria-hidden=\"true\">\n <span class=\"form-check-input si-form-checkbox\" [class.checked]=\"match.itemSelected\"></span>\n </div>\n }\n @if (match.iconClass) {\n <si-icon class=\"icon me-2\" [icon]=\"match.iconClass\" />\n }\n @for (segment of match.result; track $index) {\n <span [class.typeahead-match-segment-matching]=\"segment.isMatching\">{{ segment.text }}</span>\n }\n</ng-template>\n\n<!-- Only display the component if there are any matches and set the CSS transform to properly position the typeahead -->\n<ul\n #typeahead\n class=\"typeahead dropdown-menu\"\n [siAutocompleteListboxFor]=\"autocompleteDirective\"\n [siAutocompleteDefaultIndex]=\"parent.typeaheadAutoSelectIndex()\"\n [attr.aria-label]=\"parent.typeaheadAutocompleteListLabel() | translate\"\n [class.d-none]=\"!matches().length\"\n (siAutocompleteOptionSubmitted)=\"selectMatch($event)\"\n>\n <!-- Loop through every match and bind events, the mousedown prevent default is to prevent the host from losing focus on click -->\n @for (match of matches(); track $index) {\n <li\n #typeaheadMatch\n class=\"dropdown-item\"\n [siAutocompleteOption]=\"match\"\n [attr.aria-label]=\"match.text\"\n [attr.aria-selected]=\"multiselect() ? match.itemSelected : null\"\n (click)=\"$event.stopPropagation()\"\n (mousedown)=\"$event.preventDefault()\"\n >\n <!-- Display either a template set as the input or the template above -->\n <ng-template\n [ngTemplateOutlet]=\"parent.typeaheadItemTemplate() || defaultItemTemplate\"\n [ngTemplateOutletContext]=\"{\n item: match.option,\n index: $index,\n match: match,\n query: parent.query()\n }\"\n />\n </li>\n }\n</ul>\n", styles: [".dropdown-menu{display:block;position:relative;inset-block-start:0;inset-inline-start:0;margin-block-start:1px;overflow-y:auto;overflow-x:hidden;max-block-size:100%}.typeahead-match-segment-matching{font-weight:600}.dropdown-item *{flex-shrink:0}.dropdown-item span{white-space:pre-wrap}\n"], dependencies: [{ kind: "directive", type: SiAutocompleteListboxDirective, selector: "[siAutocompleteListboxFor]", inputs: ["id", "siAutocompleteListboxFor", "siAutocompleteDefaultIndex"], outputs: ["siAutocompleteOptionSubmitted"], exportAs: ["siAutocompleteListbox"] }, { kind: "directive", type: SiAutocompleteOptionDirective, selector: "[siAutocompleteOption]", inputs: ["id", "disabled", "siAutocompleteOption"], exportAs: ["siAutocompleteOption"] }, { kind: "component", type: SiIconComponent, selector: "si-icon", inputs: ["icon"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "pipe", type: SiTranslatePipe, name: "translate" }, { kind: "directive", type: SiTypeaheadItemTemplateDirective, selector: "[siTypeaheadItemTemplate]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
60
79
  }
61
80
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: SiTypeaheadComponent, decorators: [{
62
81
  type: Component,
@@ -65,13 +84,202 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImpor
65
84
  SiAutocompleteOptionDirective,
66
85
  SiIconComponent,
67
86
  NgTemplateOutlet,
68
- SiTranslatePipe
69
- ], changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'w-100' }, template: "<!-- Template to be used for every match, can be replaced using an input. -->\n<ng-template #defaultItemTemplate let-match=\"match\" siTypeaheadTemplate>\n @if (multiselect()) {\n <div class=\"d-flex pe-4\" aria-hidden=\"true\">\n <span class=\"form-check-input si-form-checkbox\" [class.checked]=\"match.itemSelected\"></span>\n </div>\n }\n @if (match.iconClass) {\n <si-icon class=\"icon me-2\" [icon]=\"match.iconClass\" />\n }\n @for (segment of match.result; track $index) {\n <span [class.typeahead-match-segment-matching]=\"segment.isMatching\">{{ segment.text }}</span>\n }\n</ng-template>\n\n<!-- Only display the component if there are any matches and set the CSS transform to properly position the typeahead -->\n<ul\n #typeahead\n class=\"typeahead dropdown-menu\"\n [siAutocompleteListboxFor]=\"autocompleteDirective\"\n [siAutocompleteDefaultIndex]=\"parent.typeaheadAutoSelectIndex()\"\n [attr.aria-label]=\"parent.typeaheadAutocompleteListLabel() | translate\"\n [class.d-none]=\"!matches().length\"\n (siAutocompleteOptionSubmitted)=\"selectMatch($event)\"\n>\n <!-- Loop through every match and bind events, the mousedown prevent default is to prevent the host from losing focus on click -->\n @for (match of matches(); track $index) {\n <li\n #typeaheadMatch\n class=\"dropdown-item me-4\"\n [siAutocompleteOption]=\"match\"\n [attr.aria-label]=\"match.text\"\n [attr.aria-selected]=\"multiselect() ? match.itemSelected : null\"\n (click)=\"$event.stopPropagation()\"\n (mousedown)=\"$event.preventDefault()\"\n >\n <!-- Display either a template set as the input or the template above -->\n <ng-template\n [ngTemplateOutlet]=\"parent.typeaheadItemTemplate() || defaultItemTemplate\"\n [ngTemplateOutletContext]=\"{\n item: match.option,\n index: $index,\n match: match,\n query: parent.query()\n }\"\n />\n </li>\n }\n</ul>\n", styles: [".dropdown-menu{display:block;position:relative;inset-block-start:0;inset-inline-start:0;margin-block-start:1px;overflow-y:auto;overflow-x:hidden;max-block-size:100%}.typeahead-match-segment-matching{font-weight:600}.dropdown-item *{flex-shrink:0}.dropdown-item span{white-space:pre-wrap}\n"] }]
87
+ SiTranslatePipe,
88
+ SiTypeaheadItemTemplateDirective
89
+ ], changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'w-100' }, template: "<!-- Template to be used for every match, can be replaced using an input. -->\n<ng-template #defaultItemTemplate let-match=\"match\" siTypeaheadItemTemplate>\n @if (multiselect()) {\n <div class=\"d-flex pe-4\" aria-hidden=\"true\">\n <span class=\"form-check-input si-form-checkbox\" [class.checked]=\"match.itemSelected\"></span>\n </div>\n }\n @if (match.iconClass) {\n <si-icon class=\"icon me-2\" [icon]=\"match.iconClass\" />\n }\n @for (segment of match.result; track $index) {\n <span [class.typeahead-match-segment-matching]=\"segment.isMatching\">{{ segment.text }}</span>\n }\n</ng-template>\n\n<!-- Only display the component if there are any matches and set the CSS transform to properly position the typeahead -->\n<ul\n #typeahead\n class=\"typeahead dropdown-menu\"\n [siAutocompleteListboxFor]=\"autocompleteDirective\"\n [siAutocompleteDefaultIndex]=\"parent.typeaheadAutoSelectIndex()\"\n [attr.aria-label]=\"parent.typeaheadAutocompleteListLabel() | translate\"\n [class.d-none]=\"!matches().length\"\n (siAutocompleteOptionSubmitted)=\"selectMatch($event)\"\n>\n <!-- Loop through every match and bind events, the mousedown prevent default is to prevent the host from losing focus on click -->\n @for (match of matches(); track $index) {\n <li\n #typeaheadMatch\n class=\"dropdown-item\"\n [siAutocompleteOption]=\"match\"\n [attr.aria-label]=\"match.text\"\n [attr.aria-selected]=\"multiselect() ? match.itemSelected : null\"\n (click)=\"$event.stopPropagation()\"\n (mousedown)=\"$event.preventDefault()\"\n >\n <!-- Display either a template set as the input or the template above -->\n <ng-template\n [ngTemplateOutlet]=\"parent.typeaheadItemTemplate() || defaultItemTemplate\"\n [ngTemplateOutletContext]=\"{\n item: match.option,\n index: $index,\n match: match,\n query: parent.query()\n }\"\n />\n </li>\n }\n</ul>\n", styles: [".dropdown-menu{display:block;position:relative;inset-block-start:0;inset-inline-start:0;margin-block-start:1px;overflow-y:auto;overflow-x:hidden;max-block-size:100%}.typeahead-match-segment-matching{font-weight:600}.dropdown-item *{flex-shrink:0}.dropdown-item span{white-space:pre-wrap}\n"] }]
70
90
  }], propDecorators: { onMouseDown: [{
71
91
  type: HostListener,
72
92
  args: ['mousedown', ['$event']]
73
93
  }] } });
74
94
 
95
+ /**
96
+ * Copyright (c) Siemens 2016 - 2025
97
+ * SPDX-License-Identifier: MIT
98
+ */
99
+ /**
100
+ * Constructs a typeahead search and provides the matches as a signal.
101
+ *
102
+ * @param options - Factory function that should return the array of options to search in.
103
+ * Is run in a reactive context.
104
+ * @param query - Factory function that should return the current search query. Is run in a reactive context.
105
+ * @param config - Configuration for the search. Is run in a reactive context.
106
+ *
107
+ * @example
108
+ * In a real world, myOptions and mayQuery would be signals.
109
+ * ```ts
110
+ * const search = typeaheadSearch(
111
+ * () => myOptions().map(...),
112
+ * () => myQuery().toLowerCase(),
113
+ * () => ({ matchAllTokens: 'separately' })
114
+ * )
115
+ * ```
116
+ */
117
+ const typeaheadSearch = (options, query, config) => computed(() => new TypeaheadSearch(options, query, config()).matches());
118
+ class TypeaheadSearch {
119
+ datasource;
120
+ query;
121
+ options;
122
+ matches = computed(() => this.search(this.datasource(), this.query()));
123
+ constructor(datasource, query, options) {
124
+ this.datasource = datasource;
125
+ this.query = query;
126
+ this.options = options;
127
+ }
128
+ escapeRegex(query) {
129
+ return query.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&');
130
+ }
131
+ search(options, query) {
132
+ try {
133
+ const entireQueryRegex = new RegExp(this.escapeRegex(query), 'gi');
134
+ const queryParts = !this.options.disableTokenizing
135
+ ? query.split(/\s+/g).filter(queryPart => queryPart)
136
+ : query
137
+ ? [query]
138
+ : [];
139
+ const queryRegexes = queryParts.map(queryPart => new RegExp(this.escapeRegex(queryPart), 'gi'));
140
+ // Process the options.
141
+ const matches = [];
142
+ options.forEach(option => {
143
+ const optionValue = option.text;
144
+ const stringMatch = optionValue.toLocaleLowerCase().trim() === query.toLocaleLowerCase().trim();
145
+ const candidate = {
146
+ option: option.option,
147
+ text: optionValue,
148
+ result: [],
149
+ stringMatch,
150
+ atBeginning: false,
151
+ matches: 0,
152
+ uniqueMatches: 0,
153
+ uniqueSeparateMatches: 0,
154
+ matchesEntireQuery: false,
155
+ matchesAllParts: false,
156
+ matchesAllPartsSeparately: false
157
+ };
158
+ // Only search the options if a part of the query is at least one character long to prevent an endless loop.
159
+ if (queryParts.length === 0) {
160
+ if (optionValue) {
161
+ candidate.result.push({
162
+ text: optionValue,
163
+ isMatching: false,
164
+ matches: 0,
165
+ uniqueMatches: 0
166
+ });
167
+ }
168
+ matches.push(candidate);
169
+ }
170
+ else {
171
+ const allResults = [];
172
+ const allIndexes = [];
173
+ candidate.matchesEntireQuery = !!optionValue.match(entireQueryRegex);
174
+ // Loop through the option value to find multiple matches, then store every segment (matching or non-matching) in the results.
175
+ queryRegexes.forEach((queryRegex, index) => {
176
+ let regexMatch = queryRegex.exec(optionValue);
177
+ while (regexMatch) {
178
+ allResults.push({
179
+ index,
180
+ start: regexMatch.index,
181
+ end: regexMatch.index + regexMatch[0].length,
182
+ result: regexMatch[0]
183
+ });
184
+ if (!regexMatch.index) {
185
+ candidate.atBeginning = true;
186
+ }
187
+ if (!allIndexes.includes(index)) {
188
+ allIndexes.push(index);
189
+ }
190
+ regexMatch = queryRegex.exec(optionValue);
191
+ }
192
+ });
193
+ candidate.matchesAllParts = allIndexes.length === queryParts.length;
194
+ // Check if all parts of the query match at least once (if required).
195
+ if (this.options.matchAllTokens === 'no' || candidate.matchesAllParts) {
196
+ const combinedResults = [];
197
+ // First combine intersecting (or if set to independently adjacent) results to combined results.
198
+ // We achieve this by first sorting them by the starting index, then by the ending index and then looking for overlaps.
199
+ allResults
200
+ .sort((a, b) => a.start - b.start || a.end - b.end)
201
+ .forEach(result => {
202
+ if (combinedResults.length) {
203
+ const foundPreviousResult = combinedResults.find(previousResult => this.options.matchAllTokens === 'independently'
204
+ ? result.start <= previousResult.end
205
+ : result.start < previousResult.end);
206
+ if (foundPreviousResult) {
207
+ foundPreviousResult.result += result.result.slice(foundPreviousResult.end - result.start, result.result.length);
208
+ if (result.end > foundPreviousResult.end) {
209
+ foundPreviousResult.end = result.end;
210
+ }
211
+ foundPreviousResult.indexes.push(result.index);
212
+ if (!foundPreviousResult.uniqueIndexes.includes(result.index)) {
213
+ foundPreviousResult.uniqueIndexes.push(result.index);
214
+ }
215
+ return;
216
+ }
217
+ }
218
+ combinedResults.push({
219
+ ...result,
220
+ indexes: [result.index],
221
+ uniqueIndexes: [result.index]
222
+ });
223
+ });
224
+ // Recursively go through all unique combinations of the unique indexes to get the option which has the most indexes.
225
+ const countUniqueSubindexes = (indexIndex = 0, previousIndexes = []) => indexIndex === combinedResults.length
226
+ ? previousIndexes.length
227
+ : Math.max(previousIndexes.length, ...combinedResults[indexIndex].uniqueIndexes
228
+ .filter(index => !previousIndexes.includes(index))
229
+ .map(index => countUniqueSubindexes(indexIndex + 1, [index, ...previousIndexes])));
230
+ candidate.uniqueSeparateMatches = countUniqueSubindexes();
231
+ candidate.matchesAllPartsSeparately =
232
+ candidate.uniqueSeparateMatches === queryParts.length;
233
+ let currentPreviousEnd = 0;
234
+ // Add the combined results to the candidate including the non-matching parts in between.
235
+ combinedResults.forEach(result => {
236
+ const textBefore = optionValue.slice(currentPreviousEnd, result.start);
237
+ if (textBefore) {
238
+ candidate.result.push({
239
+ text: textBefore,
240
+ isMatching: false,
241
+ matches: 0,
242
+ uniqueMatches: 0
243
+ });
244
+ }
245
+ candidate.result.push({
246
+ text: result.result,
247
+ isMatching: true,
248
+ matches: result.indexes.length,
249
+ uniqueMatches: result.uniqueIndexes.length
250
+ });
251
+ currentPreviousEnd = result.end;
252
+ candidate.matches += result.indexes.length;
253
+ candidate.uniqueMatches += result.uniqueIndexes.length;
254
+ });
255
+ // Check if there are result segments and all parts are matched independently (if required).
256
+ if (candidate.result.length !== 0 &&
257
+ ((this.options.matchAllTokens !== 'separately' &&
258
+ this.options.matchAllTokens !== 'independently') ||
259
+ candidate.matchesAllPartsSeparately)) {
260
+ const textAtEnd = optionValue.slice(currentPreviousEnd);
261
+ if (textAtEnd) {
262
+ candidate.result.push({
263
+ text: textAtEnd,
264
+ isMatching: false,
265
+ matches: 0,
266
+ uniqueMatches: 0
267
+ });
268
+ }
269
+ matches.push(candidate);
270
+ }
271
+ }
272
+ }
273
+ });
274
+ return matches;
275
+ }
276
+ catch {
277
+ // Could not create regex (only in extremely rare cases, maybe even impossible), so return an empty array.
278
+ return [];
279
+ }
280
+ }
281
+ }
282
+
75
283
  class SiTypeaheadSorting {
76
284
  sortMatches(matches) {
77
285
  // Sort the matches,
@@ -290,7 +498,7 @@ class SiTypeaheadDirective {
290
498
  /** Emits whenever the typeahead overlay is opened or closed. */
291
499
  typeaheadOpenChange = output();
292
500
  /** @internal */
293
- foundMatches = signal([]);
501
+ foundMatches = computed(() => this.typeaheadProcess() ? this.processedSearch() : this.unprocessedSearch());
294
502
  /** @internal */
295
503
  query = signal('');
296
504
  /**
@@ -305,12 +513,92 @@ class SiTypeaheadDirective {
305
513
  autoComplete = inject(SiAutocompleteDirective);
306
514
  $typeahead = new ReplaySubject(1);
307
515
  componentRef;
308
- component;
309
516
  inputTimer;
310
517
  sourceSubscription;
311
- subscription;
312
518
  matchSorter = new SiTypeaheadSorting();
313
519
  overlayRef;
520
+ /**
521
+ * Indicates that the typeahead can be potentially open.
522
+ * This signal is typically `true` when the input is focussed.
523
+ * It may be overridden and set to `false` when escape is pressed
524
+ * or when an option was selected.
525
+ */
526
+ canBeOpen = signal(false);
527
+ selectionCounter = signal(0);
528
+ typeaheadOptions = toSignal(this.$typeahead.pipe(map(options => options.map(option => ({
529
+ text: this.getOptionValue(option),
530
+ option
531
+ })))), { initialValue: [] });
532
+ typeaheadSearch = typeaheadSearch(this.typeaheadOptions, this.query, computed(() => ({
533
+ matchAllTokens: this.typeaheadMatchAllTokens(),
534
+ disableTokenizing: !this.typeaheadTokenize(),
535
+ skipProcessing: !this.typeaheadProcess()
536
+ })));
537
+ processedSearch = computed(() => {
538
+ this.selectionCounter(); // This is a workaround for the multi-select which needs to trigger a change detection in the typeahead component.
539
+ const matches = this.typeaheadSearch().map(match => ({
540
+ ...match,
541
+ itemSelected: this.typeaheadMultiSelect()
542
+ ? match.option.selected
543
+ : false,
544
+ iconClass: this.getOptionField(match.option, 'iconClass')
545
+ }));
546
+ if (this.typeaheadSkipSortingMatches()) {
547
+ return matches;
548
+ }
549
+ else {
550
+ return this.matchSorter.sortMatches(matches);
551
+ }
552
+ });
553
+ unprocessedSearch = computed(() => {
554
+ this.selectionCounter(); // This is a workaround for the multi-select which needs to trigger a change detection in the typeahead component.
555
+ return this.typeaheadOptions().map(option => {
556
+ const itemSelected = this.typeaheadMultiSelect()
557
+ ? option.selected
558
+ : false;
559
+ return {
560
+ option,
561
+ text: option.text,
562
+ result: option.text
563
+ ? [{ text: option.text, isMatching: false, matches: 0, uniqueMatches: 0 }]
564
+ : [],
565
+ itemSelected,
566
+ iconClass: this.getOptionField(option.option, 'iconClass'),
567
+ stringMatch: false,
568
+ atBeginning: false,
569
+ matches: 0,
570
+ uniqueMatches: 0,
571
+ uniqueSeparateMatches: 0,
572
+ matchesEntireQuery: false,
573
+ matchesAllParts: false,
574
+ matchesAllPartsSeparately: false,
575
+ active: false
576
+ };
577
+ });
578
+ });
579
+ constructor() {
580
+ effect(() => {
581
+ // The value needs to fulfil the minimum length requirement set.
582
+ if (this.canBeOpen() && this.query().length >= this.typeaheadMinLength()) {
583
+ const matches = this.foundMatches();
584
+ const escapedQuery = this.escapeRegex(this.query());
585
+ const equalsExp = new RegExp(`^${escapedQuery}$`, 'i');
586
+ const fullMatches = matches.filter(match => match.result.length === 1 && equalsExp.test(match.text));
587
+ if (fullMatches.length > 0) {
588
+ this.typeaheadOnFullMatch.emit(fullMatches[0]);
589
+ }
590
+ if (matches.length) {
591
+ this.loadComponent();
592
+ }
593
+ else {
594
+ this.removeComponent();
595
+ }
596
+ }
597
+ else {
598
+ this.removeComponent();
599
+ }
600
+ });
601
+ }
314
602
  // Every time the main input changes, detect whether it is async and if it is not make an observable out of the array.
315
603
  ngOnChanges(changes) {
316
604
  if (changes.siTypeahead) {
@@ -327,10 +615,7 @@ class SiTypeaheadDirective {
327
615
  // Clear the current input timeout (if set) and remove the component when the focus of the host is lost.
328
616
  onBlur() {
329
617
  this.clearTimer();
330
- if (this.component) {
331
- this.removeComponent();
332
- }
333
- this.subscription?.unsubscribe();
618
+ this.canBeOpen.set(false);
334
619
  }
335
620
  // Start the input timeout to display the typeahead when the host is focussed or a value is inputted into it.
336
621
  onInput(event) {
@@ -344,36 +629,14 @@ class SiTypeaheadDirective {
344
629
  this.inputTimer = undefined;
345
630
  const value = (target.value || target.textContent) ?? firstValue ?? '';
346
631
  this.query.set(value);
347
- this.subscription?.unsubscribe();
348
- // The value needs to fulfil the minimum length requirement set.
349
- if (value.length >= this.typeaheadMinLength()) {
350
- this.subscription = this.getMatches(this.$typeahead, value).subscribe(matches => {
351
- this.foundMatches.set(matches);
352
- const escapedQuery = this.escapeRegex(value);
353
- const equalsExp = new RegExp(`^${escapedQuery}$`, 'i');
354
- const fullMatches = matches.filter(match => match.result.length === 1 && equalsExp.test(match.text));
355
- if (fullMatches.length > 0) {
356
- this.typeaheadOnFullMatch.emit(fullMatches[0]);
357
- }
358
- if (matches.length) {
359
- this.loadComponent();
360
- }
361
- else {
362
- this.removeComponent();
363
- }
364
- });
365
- }
366
- else {
367
- this.removeComponent();
368
- }
369
632
  this.typeaheadOnInput.emit(value ?? '');
633
+ this.canBeOpen.set(true);
370
634
  }, this.typeaheadWaitMs());
371
635
  }
372
636
  onKeydownEscape() {
373
637
  if (this.typeaheadCloseOnEsc()) {
374
- this.subscription?.unsubscribe();
375
638
  this.clearTimer();
376
- this.removeComponent();
639
+ this.canBeOpen.set(false);
377
640
  }
378
641
  }
379
642
  onKeydownSpace(event) {
@@ -384,14 +647,13 @@ class SiTypeaheadDirective {
384
647
  if (value) {
385
648
  this.selectMatch(value);
386
649
  // this forces change detection in the typeahead component.
387
- this.foundMatches.update(matches => [...matches]);
650
+ this.selectionCounter.update(v => v + 1);
388
651
  }
389
652
  }
390
653
  }
391
654
  ngOnDestroy() {
392
655
  this.clearTimer();
393
656
  this.sourceSubscription?.unsubscribe();
394
- this.subscription?.unsubscribe();
395
657
  this.overlayRef?.dispose();
396
658
  }
397
659
  // Dynamically create the typeahead component and then set the matches and the query.
@@ -413,207 +675,36 @@ class SiTypeaheadDirective {
413
675
  }
414
676
  const typeaheadPortal = new ComponentPortal(SiTypeaheadComponent, null, this.injector);
415
677
  this.componentRef = this.overlayRef.attach(typeaheadPortal);
416
- this.component = this.componentRef.instance;
417
678
  this.typeaheadOpenChange.emit(true);
418
679
  }
419
- // Get the matches and push them to the subject of the component, then set the query of the component.
420
- // If the typeahead options are objects, pick the specified field/property.
421
- getOptionValue(option, field) {
422
- return typeof option !== 'object' ? option.toString() : (option[field] ?? '');
680
+ /**
681
+ * Extracts the display value from a typeahead option.
682
+ *
683
+ * For string options, returns the string value directly.
684
+ * For object options, returns the value of the field specified by {@link typeaheadOptionField}
685
+ * (defaults to 'name'), or an empty string if the field doesn't exist.
686
+ *
687
+ * @param option - The typeahead option to extract the value from
688
+ * @returns The string representation of the option for display purposes
689
+ */
690
+ getOptionValue(option) {
691
+ return typeof option !== 'object'
692
+ ? option.toString()
693
+ : (option[this.typeaheadOptionField()] ?? '');
423
694
  }
424
- // If enabled, process the matches and sort through them.
425
- getMatches(observableList, query) {
426
- try {
427
- const entireQueryRegex = new RegExp(this.escapeRegex(query), 'gi');
428
- const queryParts = this.typeaheadTokenize()
429
- ? query.split(/\s+/g).filter(queryPart => queryPart)
430
- : query
431
- ? [query]
432
- : [];
433
- const queryRegexes = queryParts.map(queryPart => new RegExp(this.escapeRegex(queryPart), 'gi'));
434
- return observableList.pipe(map(options => {
435
- // Check if the options need to be processed, if not just return an unprocessed object.
436
- if (!this.typeaheadProcess()) {
437
- return options.map(option => {
438
- const optionValue = this.getOptionValue(option, this.typeaheadOptionField());
439
- const itemSelected = this.typeaheadMultiSelect()
440
- ? this.getOptionValue(option, 'selected')
441
- : false;
442
- const iconClass = this.getOptionValue(option, 'iconClass');
443
- return {
444
- option,
445
- itemSelected,
446
- text: optionValue,
447
- iconClass,
448
- result: optionValue
449
- ? [{ text: optionValue, isMatching: false, matches: 0, uniqueMatches: 0 }]
450
- : [],
451
- stringMatch: false,
452
- atBeginning: false,
453
- matches: 0,
454
- uniqueMatches: 0,
455
- uniqueSeparateMatches: 0,
456
- matchesEntireQuery: false,
457
- matchesAllParts: false,
458
- matchesAllPartsSeparately: false,
459
- active: false
460
- };
461
- });
462
- }
463
- else {
464
- // Process the options.
465
- const matches = [];
466
- options.forEach(option => {
467
- const optionValue = this.getOptionValue(option, this.typeaheadOptionField());
468
- const stringMatch = optionValue.toLocaleLowerCase().trim() === query.toLocaleLowerCase().trim();
469
- const itemSelected = this.typeaheadMultiSelect()
470
- ? option['selected']
471
- : false;
472
- const iconClass = this.getOptionValue(option, 'iconClass');
473
- const candidate = {
474
- option,
475
- itemSelected,
476
- text: optionValue,
477
- iconClass,
478
- result: [],
479
- stringMatch,
480
- atBeginning: false,
481
- matches: 0,
482
- uniqueMatches: 0,
483
- uniqueSeparateMatches: 0,
484
- matchesEntireQuery: false,
485
- matchesAllParts: false,
486
- matchesAllPartsSeparately: false
487
- };
488
- // Only search the options if a part of the query is at least one character long to prevent an endless loop.
489
- if (queryParts.length === 0) {
490
- if (optionValue) {
491
- candidate.result.push({
492
- text: optionValue,
493
- isMatching: false,
494
- matches: 0,
495
- uniqueMatches: 0
496
- });
497
- }
498
- matches.push(candidate);
499
- }
500
- else {
501
- const allResults = [];
502
- const allIndexes = [];
503
- candidate.matchesEntireQuery = !!optionValue.match(entireQueryRegex);
504
- // Loop through the option value to find multiple matches, then store every segment (matching or non-matching) in the results.
505
- queryRegexes.forEach((queryRegex, index) => {
506
- let regexMatch = queryRegex.exec(optionValue);
507
- while (regexMatch) {
508
- allResults.push({
509
- index,
510
- start: regexMatch.index,
511
- end: regexMatch.index + regexMatch[0].length,
512
- result: regexMatch[0]
513
- });
514
- if (!regexMatch.index) {
515
- candidate.atBeginning = true;
516
- }
517
- if (!allIndexes.includes(index)) {
518
- allIndexes.push(index);
519
- }
520
- regexMatch = queryRegex.exec(optionValue);
521
- }
522
- });
523
- candidate.matchesAllParts = allIndexes.length === queryParts.length;
524
- // Check if all parts of the query match at least once (if required).
525
- if (this.typeaheadMatchAllTokens() === 'no' || candidate.matchesAllParts) {
526
- const combinedResults = [];
527
- // First combine intersecting (or if set to independently adjacent) results to combined results.
528
- // We achieve this by first sorting them by the starting index, then by the ending index and then looking for overlaps.
529
- allResults
530
- .sort((a, b) => a.start - b.start || a.end - b.end)
531
- .forEach(result => {
532
- if (combinedResults.length) {
533
- const foundPreviousResult = combinedResults.find(previousResult => this.typeaheadMatchAllTokens() === 'independently'
534
- ? result.start <= previousResult.end
535
- : result.start < previousResult.end);
536
- if (foundPreviousResult) {
537
- foundPreviousResult.result += result.result.slice(foundPreviousResult.end - result.start, result.result.length);
538
- if (result.end > foundPreviousResult.end) {
539
- foundPreviousResult.end = result.end;
540
- }
541
- foundPreviousResult.indexes.push(result.index);
542
- if (!foundPreviousResult.uniqueIndexes.includes(result.index)) {
543
- foundPreviousResult.uniqueIndexes.push(result.index);
544
- }
545
- return;
546
- }
547
- }
548
- combinedResults.push({
549
- ...result,
550
- indexes: [result.index],
551
- uniqueIndexes: [result.index]
552
- });
553
- });
554
- // Recursively go through all unique combinations of the unique indexes to get the option which has the most indexes.
555
- const countUniqueSubindexes = (indexIndex = 0, previousIndexes = []) => indexIndex === combinedResults.length
556
- ? previousIndexes.length
557
- : Math.max(previousIndexes.length, ...combinedResults[indexIndex].uniqueIndexes
558
- .filter(index => !previousIndexes.includes(index))
559
- .map(index => countUniqueSubindexes(indexIndex + 1, [index, ...previousIndexes])));
560
- candidate.uniqueSeparateMatches = countUniqueSubindexes();
561
- candidate.matchesAllPartsSeparately =
562
- candidate.uniqueSeparateMatches === queryParts.length;
563
- let currentPreviousEnd = 0;
564
- // Add the combined results to the candidate including the non-matching parts in between.
565
- combinedResults.forEach(result => {
566
- const textBefore = optionValue.slice(currentPreviousEnd, result.start);
567
- if (textBefore) {
568
- candidate.result.push({
569
- text: textBefore,
570
- isMatching: false,
571
- matches: 0,
572
- uniqueMatches: 0
573
- });
574
- }
575
- candidate.result.push({
576
- text: result.result,
577
- isMatching: true,
578
- matches: result.indexes.length,
579
- uniqueMatches: result.uniqueIndexes.length
580
- });
581
- currentPreviousEnd = result.end;
582
- candidate.matches += result.indexes.length;
583
- candidate.uniqueMatches += result.uniqueIndexes.length;
584
- });
585
- // Check if there are result segments and all parts are matched independently (if required).
586
- if (candidate.result.length !== 0 &&
587
- ((this.typeaheadMatchAllTokens() !== 'separately' &&
588
- this.typeaheadMatchAllTokens() !== 'independently') ||
589
- candidate.matchesAllPartsSeparately)) {
590
- const textAtEnd = optionValue.slice(currentPreviousEnd);
591
- if (textAtEnd) {
592
- candidate.result.push({
593
- text: textAtEnd,
594
- isMatching: false,
595
- matches: 0,
596
- uniqueMatches: 0
597
- });
598
- }
599
- matches.push(candidate);
600
- }
601
- }
602
- }
603
- });
604
- if (this.typeaheadSkipSortingMatches()) {
605
- return matches;
606
- }
607
- else {
608
- return this.matchSorter.sortMatches(matches);
609
- }
610
- }
611
- }));
612
- }
613
- catch {
614
- // Could not create regex (only in extremely rare cases, maybe even impossible), so return an empty array.
615
- return of([]);
616
- }
695
+ /**
696
+ * Extracts a specific field value from a typeahead option.
697
+ *
698
+ * This method is used to access additional properties of object-type options,
699
+ * such as 'selected' for multi-select functionality or 'iconClass' for displaying icons.
700
+ *
701
+ * @param option - The typeahead option to extract the field from
702
+ * @param field - The name of the field to extract
703
+ * @returns The field value as a string if the option is an object and the field exists,
704
+ * otherwise undefined
705
+ */
706
+ getOptionField(option, field) {
707
+ return typeof option !== 'object' ? undefined : option[field];
617
708
  }
618
709
  // Select a match, either gets called due to a enter keypress or from the component due to a click.
619
710
  /** @internal */
@@ -628,7 +719,7 @@ class SiTypeaheadDirective {
628
719
  this.clearTimer();
629
720
  this.typeaheadOnSelect.emit(match);
630
721
  if (!this.typeaheadMultiSelect()) {
631
- this.removeComponent();
722
+ this.canBeOpen.set(false);
632
723
  }
633
724
  }
634
725
  // Remove the component by clearing the viewContainerRef
@@ -639,7 +730,6 @@ class SiTypeaheadDirective {
639
730
  }
640
731
  this.componentRef?.destroy();
641
732
  this.componentRef = undefined;
642
- this.component = undefined;
643
733
  }
644
734
  clearTimer() {
645
735
  if (this.inputTimer) {
@@ -663,7 +753,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImpor
663
753
  hostDirectives: [SiAutocompleteDirective],
664
754
  exportAs: 'si-typeahead'
665
755
  }]
666
- }], propDecorators: { onBlur: [{
756
+ }], ctorParameters: () => [], propDecorators: { onBlur: [{
667
757
  type: HostListener,
668
758
  args: ['focusout']
669
759
  }], onInput: [{
@@ -680,24 +770,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImpor
680
770
  args: ['keydown.space', ['$event']]
681
771
  }] } });
682
772
 
683
- /**
684
- * Copyright (c) Siemens 2016 - 2025
685
- * SPDX-License-Identifier: MIT
686
- */
687
- class SiTypeaheadItemTemplateDirective {
688
- static ngTemplateContextGuard(dir, ctx) {
689
- return true;
690
- }
691
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: SiTypeaheadItemTemplateDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
692
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.0.6", type: SiTypeaheadItemTemplateDirective, isStandalone: true, selector: "[siTypeaheadItemTemplate]", ngImport: i0 });
693
- }
694
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: SiTypeaheadItemTemplateDirective, decorators: [{
695
- type: Directive,
696
- args: [{
697
- selector: '[siTypeaheadItemTemplate]'
698
- }]
699
- }] });
700
-
701
773
  /**
702
774
  * Copyright (c) Siemens 2016 - 2025
703
775
  * SPDX-License-Identifier: MIT