@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.
- package/README.md +5 -0
- package/card/index.d.ts +68 -29
- package/common/index.d.ts +11 -0
- package/dashboard/index.d.ts +1 -0
- package/datepicker/index.d.ts +30 -34
- package/fesm2022/siemens-element-ng-breadcrumb.mjs +2 -2
- package/fesm2022/siemens-element-ng-breadcrumb.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-card.mjs +103 -37
- package/fesm2022/siemens-element-ng-card.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-common.mjs +6 -0
- package/fesm2022/siemens-element-ng-common.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-connection-strength.mjs +2 -2
- package/fesm2022/siemens-element-ng-connection-strength.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-content-action-bar.mjs +2 -2
- package/fesm2022/siemens-element-ng-content-action-bar.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-dashboard.mjs +10 -8
- package/fesm2022/siemens-element-ng-dashboard.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-datatable.mjs +5 -0
- package/fesm2022/siemens-element-ng-datatable.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-date-range-filter.mjs +1 -1
- package/fesm2022/siemens-element-ng-date-range-filter.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-datepicker.mjs +173 -151
- package/fesm2022/siemens-element-ng-datepicker.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-file-uploader.mjs +2 -2
- package/fesm2022/siemens-element-ng-file-uploader.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-filter-bar.mjs +5 -5
- package/fesm2022/siemens-element-ng-filter-bar.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-filtered-search.mjs +15 -3
- package/fesm2022/siemens-element-ng-filtered-search.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-form.mjs +7 -1
- package/fesm2022/siemens-element-ng-form.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-formly.mjs +2 -2
- package/fesm2022/siemens-element-ng-formly.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-header-dropdown.mjs +13 -1
- package/fesm2022/siemens-element-ng-header-dropdown.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-ip-input.mjs +62 -28
- package/fesm2022/siemens-element-ng-ip-input.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-language-switcher.mjs +1 -1
- package/fesm2022/siemens-element-ng-language-switcher.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-list-details.mjs +2 -2
- package/fesm2022/siemens-element-ng-list-details.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-navbar-vertical.mjs +1 -1
- package/fesm2022/siemens-element-ng-navbar-vertical.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-pagination.mjs +2 -2
- package/fesm2022/siemens-element-ng-pagination.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-photo-upload.mjs +1 -1
- package/fesm2022/siemens-element-ng-photo-upload.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-search-bar.mjs +14 -4
- package/fesm2022/siemens-element-ng-search-bar.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-side-panel.mjs +2 -2
- package/fesm2022/siemens-element-ng-side-panel.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-status-bar.mjs +2 -2
- package/fesm2022/siemens-element-ng-status-bar.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-tabs-legacy.mjs +2 -2
- package/fesm2022/siemens-element-ng-tabs-legacy.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-tabs.mjs +5 -5
- package/fesm2022/siemens-element-ng-tabs.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-tooltip.mjs +5 -6
- package/fesm2022/siemens-element-ng-tooltip.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-translate.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-tree-view.mjs +4 -4
- package/fesm2022/siemens-element-ng-tree-view.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-typeahead.mjs +329 -257
- package/fesm2022/siemens-element-ng-typeahead.mjs.map +1 -1
- package/filter-bar/index.d.ts +9 -3
- package/header-dropdown/index.d.ts +7 -0
- package/ip-input/index.d.ts +42 -5
- package/package.json +23 -19
- package/schematics/collection.json +34 -0
- package/schematics/migrations/action-modal-migration/action-modal-migration.js +121 -0
- package/schematics/migrations/action-modal-migration/action-modal.mappings.js +98 -0
- package/schematics/migrations/action-modal-migration/index.js +5 -0
- package/schematics/migrations/index.js +13 -0
- package/schematics/migrations/schema.json +16 -0
- package/schematics/migrations/to-legacy-migration/to-legacy-migration.js +55 -0
- package/schematics/migrations/to-legacy-migration/to-legacy-replacement.js +35 -0
- package/schematics/ng-add/index.js +16 -0
- package/schematics/ng-add/schema.json +16 -0
- package/schematics/scss-import-to-siemens-migration/index.js +101 -0
- package/schematics/scss-import-to-siemens-migration/schema.json +16 -0
- package/schematics/scss-import-to-siemens-migration/style-mappings.js +46 -0
- package/schematics/simpl-siemens-migration/index.js +18 -0
- package/schematics/simpl-siemens-migration/schema.json +16 -0
- package/schematics/ts-import-to-siemens-migration/index.js +118 -0
- package/schematics/ts-import-to-siemens-migration/mappings/charts-ng-mappings.js +70 -0
- package/schematics/ts-import-to-siemens-migration/mappings/dashboards-ng-mappings.js +52 -0
- package/schematics/ts-import-to-siemens-migration/mappings/element-ng-mappings.js +651 -0
- package/schematics/ts-import-to-siemens-migration/mappings/element-translate-ng-mappings.js +21 -0
- package/schematics/ts-import-to-siemens-migration/mappings/index.js +9 -0
- package/schematics/ts-import-to-siemens-migration/mappings/maps-ng-mappings.js +46 -0
- package/schematics/ts-import-to-siemens-migration/model.js +4 -0
- package/schematics/ts-import-to-siemens-migration/schema.json +16 -0
- package/schematics/utils/html-utils.js +72 -0
- package/schematics/utils/index.js +10 -0
- package/schematics/utils/project-utils.js +75 -0
- package/schematics/utils/schematics-file-system.js +22 -0
- package/schematics/utils/template-utils.js +114 -0
- package/schematics/utils/testing.js +41 -0
- package/schematics/utils/ts-utils.js +195 -0
- package/search-bar/index.d.ts +11 -1
- package/template-i18n.json +7 -0
- package/tooltip/index.d.ts +1 -1
- package/translate/index.d.ts +7 -0
- 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,
|
|
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
|
|
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\"
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
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
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
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.
|
|
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
|