@siemens/element-ng 47.4.0 → 47.5.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/common/models/status-type.model.d.ts +2 -0
- package/datatable/index.d.ts +42 -0
- package/datatable/package.json +3 -0
- package/datatable/si-datatable-interaction.directive.d.ts +34 -0
- package/datatable/si-datatable.module.d.ts +7 -0
- package/fesm2022/siemens-element-ng-common.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-datatable.mjs +173 -0
- package/fesm2022/siemens-element-ng-datatable.mjs.map +1 -0
- package/fesm2022/siemens-element-ng-filtered-search.mjs +1139 -0
- package/fesm2022/siemens-element-ng-filtered-search.mjs.map +1 -0
- package/fesm2022/siemens-element-ng-formly.mjs +935 -0
- package/fesm2022/siemens-element-ng-formly.mjs.map +1 -0
- package/fesm2022/siemens-element-ng-icon.mjs +42 -14
- package/fesm2022/siemens-element-ng-icon.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-list-details.mjs +390 -0
- package/fesm2022/siemens-element-ng-list-details.mjs.map +1 -0
- package/fesm2022/siemens-element-ng-loading-spinner.mjs +15 -12
- package/fesm2022/siemens-element-ng-loading-spinner.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-modal.mjs +4 -1
- package/fesm2022/siemens-element-ng-modal.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-password-strength.mjs +22 -16
- package/fesm2022/siemens-element-ng-password-strength.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-phone-number.mjs +426 -0
- package/fesm2022/siemens-element-ng-phone-number.mjs.map +1 -0
- package/fesm2022/siemens-element-ng-result-details-list.mjs +74 -0
- package/fesm2022/siemens-element-ng-result-details-list.mjs.map +1 -0
- package/fesm2022/siemens-element-ng-shadow-root.mjs +70 -0
- package/fesm2022/siemens-element-ng-shadow-root.mjs.map +1 -0
- package/fesm2022/siemens-element-ng-side-panel.mjs +554 -0
- package/fesm2022/siemens-element-ng-side-panel.mjs.map +1 -0
- package/fesm2022/siemens-element-ng-status-bar.mjs +348 -0
- package/fesm2022/siemens-element-ng-status-bar.mjs.map +1 -0
- package/fesm2022/siemens-element-ng-tabs-next.mjs +491 -0
- package/fesm2022/siemens-element-ng-tabs-next.mjs.map +1 -0
- package/fesm2022/siemens-element-ng-translate.mjs.map +1 -1
- package/fesm2022/siemens-element-ng-tree-view.mjs +2936 -0
- package/fesm2022/siemens-element-ng-tree-view.mjs.map +1 -0
- package/fesm2022/siemens-element-ng-wizard.mjs +2 -2
- package/fesm2022/siemens-element-ng-wizard.mjs.map +1 -1
- package/filtered-search/index.d.ts +7 -0
- package/filtered-search/package.json +3 -0
- package/filtered-search/si-filtered-search-helper.d.ts +22 -0
- package/filtered-search/si-filtered-search-value.component.d.ts +53 -0
- package/filtered-search/si-filtered-search.component.d.ts +329 -0
- package/filtered-search/si-filtered-search.model.d.ts +139 -0
- package/filtered-search/si-filtered-search.module.d.ts +7 -0
- package/filtered-search/values/date-value/si-filtered-search-date-value.component.d.ts +57 -0
- package/filtered-search/values/si-filtered-search-value.base.d.ts +27 -0
- package/filtered-search/values/typeahead/si-filtered-search-typeahead.component.d.ts +45 -0
- package/formly/fields/button/si-formly-button.component.d.ts +7 -0
- package/formly/fields/date-range/si-formly-date-range.component.d.ts +6 -0
- package/formly/fields/datetime/si-formly-datetime.component.d.ts +13 -0
- package/formly/fields/email/si-formly-email.component.d.ts +6 -0
- package/formly/fields/ip-input/si-formly-ip-input.component.d.ts +6 -0
- package/formly/fields/number/si-formly-number.component.d.ts +6 -0
- package/formly/fields/password/si-formly-password.component.d.ts +6 -0
- package/formly/fields/select/si-formly-select.component.d.ts +6 -0
- package/formly/fields/text/si-formly-text-display.component.d.ts +7 -0
- package/formly/fields/textarea/si-formly-textarea.component.d.ts +18 -0
- package/formly/fields/time/si-formly-time.component.d.ts +13 -0
- package/formly/index.d.ts +6 -0
- package/formly/package.json +3 -0
- package/formly/si-formly-translate.extension.d.ts +11 -0
- package/formly/si-formly.component.d.ts +62 -0
- package/formly/si-formly.module.d.ts +35 -0
- package/formly/structural/si-formly-accordion/si-formly-accordion.component.d.ts +13 -0
- package/formly/structural/si-formly-array/si-formly-array.component.d.ts +6 -0
- package/formly/structural/si-formly-object/si-formly-object.component.d.ts +6 -0
- package/formly/structural/si-formly-object-grid/si-formly-object-grid.component.d.ts +22 -0
- package/formly/structural/si-formly-object-grid/si-formly-object-grid.model.d.ts +21 -0
- package/formly/structural/si-formly-object-plain/si-formly-object-plain.component.d.ts +6 -0
- package/formly/structural/si-formly-tabset/si-formly-object-tabset.component.d.ts +7 -0
- package/formly/utils.d.ts +6 -0
- package/formly/wrapper/si-formly-fieldset.component.d.ts +8 -0
- package/formly/wrapper/si-formly-form-field-provider.directive.d.ts +19 -0
- package/formly/wrapper/si-formly-horizontal-wrapper.component.d.ts +6 -0
- package/formly/wrapper/si-formly-icon-wrapper.component.d.ts +6 -0
- package/formly/wrapper/si-formly-wrapper.component.d.ts +8 -0
- package/icon/element-icons.d.ts +5 -0
- package/icon/si-status-icon.component.d.ts +6 -1
- package/list-details/index.d.ts +12 -0
- package/list-details/package.json +3 -0
- package/list-details/si-details-pane/si-details-pane.component.d.ts +8 -0
- package/list-details/si-details-pane-body/si-details-pane-body.component.d.ts +6 -0
- package/list-details/si-details-pane-footer/si-details-pane-footer.component.d.ts +6 -0
- package/list-details/si-details-pane-header/si-details-pane-header.component.d.ts +38 -0
- package/list-details/si-list-details.component.d.ts +100 -0
- package/list-details/si-list-pane/si-list-pane.component.d.ts +10 -0
- package/list-details/si-list-pane-body/si-list-pane-body.component.d.ts +6 -0
- package/list-details/si-list-pane-header/si-list-pane-header.component.d.ts +6 -0
- package/loading-spinner/si-loading-spinner.directive.d.ts +3 -2
- package/package.json +73 -9
- package/password-strength/si-password-strength.directive.d.ts +11 -0
- package/phone-number/index.d.ts +7 -0
- package/phone-number/package.json +3 -0
- package/phone-number/si-phone-number-input-select.directive.d.ts +10 -0
- package/phone-number/si-phone-number-input.component.d.ts +137 -0
- package/phone-number/si-phone-number-input.models.d.ts +48 -0
- package/phone-number/si-phone-number-input.module.d.ts +7 -0
- package/result-details-list/index.d.ts +7 -0
- package/result-details-list/package.json +3 -0
- package/result-details-list/si-result-details-list.component.d.ts +14 -0
- package/result-details-list/si-result-details-list.datamodel.d.ts +48 -0
- package/result-details-list/si-result-details-list.module.d.ts +7 -0
- package/shadow-root/index.d.ts +5 -0
- package/shadow-root/package.json +3 -0
- package/shadow-root/si-shadow-root.directive.d.ts +39 -0
- package/side-panel/index.d.ts +9 -0
- package/side-panel/package.json +3 -0
- package/side-panel/si-side-panel-content.component.d.ts +105 -0
- package/side-panel/si-side-panel.component.d.ts +108 -0
- package/side-panel/si-side-panel.module.d.ts +8 -0
- package/side-panel/si-side-panel.service.d.ts +45 -0
- package/side-panel/side-panel.model.d.ts +16 -0
- package/status-bar/index.d.ts +7 -0
- package/status-bar/package.json +3 -0
- package/status-bar/si-status-bar-item/index.d.ts +6 -0
- package/status-bar/si-status-bar-item/si-status-bar-item.component.d.ts +22 -0
- package/status-bar/si-status-bar-item/si-status-bar-item.model.d.ts +33 -0
- package/status-bar/si-status-bar.component.d.ts +116 -0
- package/status-bar/si-status-bar.module.d.ts +7 -0
- package/tabs-next/index.d.ts +7 -0
- package/tabs-next/package.json +3 -0
- package/tabs-next/si-tab-next-base.directive.d.ts +66 -0
- package/tabs-next/si-tab-next-link.component.d.ts +18 -0
- package/tabs-next/si-tab-next.component.d.ts +16 -0
- package/tabs-next/si-tabs-tokens.d.ts +7 -0
- package/tabs-next/si-tabset-next.component.d.ts +72 -0
- package/template-i18n.json +29 -0
- package/translate/si-translatable-keys.interface.d.ts +29 -0
- package/tree-view/drag-drop.util.d.ts +32 -0
- package/tree-view/index.d.ts +12 -0
- package/tree-view/package.json +3 -0
- package/tree-view/si-tree-view-converter.service.d.ts +41 -0
- package/tree-view/si-tree-view-item/si-tree-view-item.component.d.ts +105 -0
- package/tree-view/si-tree-view-item/si-tree-view-item.directive.d.ts +24 -0
- package/tree-view/si-tree-view-item-context.d.ts +11 -0
- package/tree-view/si-tree-view-item-height.service.d.ts +49 -0
- package/tree-view/si-tree-view-item-template.directive.d.ts +18 -0
- package/tree-view/si-tree-view-virtualization.service.d.ts +150 -0
- package/tree-view/si-tree-view.component.d.ts +466 -0
- package/tree-view/si-tree-view.model.d.ts +146 -0
- package/tree-view/si-tree-view.module.d.ts +10 -0
- package/tree-view/si-tree-view.service.d.ts +55 -0
- package/tree-view/si-tree-view.utils.d.ts +46 -0
|
@@ -0,0 +1,1139 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { model, input, output, signal, Directive, inject, LOCALE_ID, viewChild, computed, ChangeDetectionStrategy, Component, DestroyRef, untracked, booleanAttribute, ElementRef, viewChildren, ChangeDetectorRef, NgModule } from '@angular/core';
|
|
3
|
+
import * as i1 from '@angular/forms';
|
|
4
|
+
import { FormsModule } from '@angular/forms';
|
|
5
|
+
import { isRTL } from '@siemens/element-ng/common';
|
|
6
|
+
import { addIcons, elementCancel, SiIconNextComponent, elementSearch } from '@siemens/element-ng/icon';
|
|
7
|
+
import { SiTypeaheadDirective } from '@siemens/element-ng/typeahead';
|
|
8
|
+
import * as i2 from '@siemens/element-translate-ng/translate';
|
|
9
|
+
import { SiTranslateModule, SiTranslateService } from '@siemens/element-translate-ng/translate';
|
|
10
|
+
import { BehaviorSubject, switchMap, of, Subject } from 'rxjs';
|
|
11
|
+
import { debounceTime, map, tap, first, takeUntil } from 'rxjs/operators';
|
|
12
|
+
import { formatDate, DatePipe } from '@angular/common';
|
|
13
|
+
import { isValid, SiDatepickerOverlayDirective, getDatepickerFormat, getNamedFormat, SiDatepickerDirective } from '@siemens/element-ng/datepicker';
|
|
14
|
+
import { CdkMonitorFocus } from '@angular/cdk/a11y';
|
|
15
|
+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Copyright Siemens 2016 - 2025.
|
|
19
|
+
* SPDX-License-Identifier: MIT
|
|
20
|
+
*/
|
|
21
|
+
/** Convert options to option criterions */
|
|
22
|
+
const toOptionCriteria = (values) => values?.map(v => typeof v === 'string'
|
|
23
|
+
? { value: v }
|
|
24
|
+
: { label: v.label, value: v.value, iconClass: v.iconClass }) ?? [];
|
|
25
|
+
/*
|
|
26
|
+
* Update selected state the matching is based on value since plain
|
|
27
|
+
* string options will automatically fill the value attribute with the
|
|
28
|
+
* actual string value.
|
|
29
|
+
*/
|
|
30
|
+
const selectOptions = (options, toSelect) => options.forEach(val => (val.selected = toSelect.includes(val.value)));
|
|
31
|
+
/**
|
|
32
|
+
* Difference by name, create an array that contains those elements of criteria's a that are not in criteria's b.
|
|
33
|
+
* This operation is also sometimes called minus (-).
|
|
34
|
+
*/
|
|
35
|
+
const differenceByName = (a, b) => a.filter(x => b.filter(y => y.config.name === x.name).length === 0);
|
|
36
|
+
/** Convert criteria to internal model criteria */
|
|
37
|
+
const toInternalCriteria = (crit) => {
|
|
38
|
+
return {
|
|
39
|
+
...crit,
|
|
40
|
+
label: crit.label ?? crit.name,
|
|
41
|
+
translatedLabel: crit.label ?? crit.name
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
const getISODateString = (date, format, locale) => {
|
|
45
|
+
if (!isValid(date)) {
|
|
46
|
+
return '';
|
|
47
|
+
}
|
|
48
|
+
switch (format) {
|
|
49
|
+
case 'date':
|
|
50
|
+
return formatDate(date, 'yyyy-MM-dd', locale);
|
|
51
|
+
case 'date-time':
|
|
52
|
+
return date.toISOString();
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Copyright Siemens 2016 - 2025.
|
|
58
|
+
* SPDX-License-Identifier: MIT
|
|
59
|
+
*/
|
|
60
|
+
class SiFilteredSearchValueBase {
|
|
61
|
+
active = model.required();
|
|
62
|
+
criterionValue = model.required();
|
|
63
|
+
definition = input.required();
|
|
64
|
+
disabled = input.required();
|
|
65
|
+
searchLabel = input.required();
|
|
66
|
+
submitValue = output();
|
|
67
|
+
editValue = output();
|
|
68
|
+
backspaceOverflow = output();
|
|
69
|
+
focusInOverlay = signal(false).asReadonly();
|
|
70
|
+
focus() {
|
|
71
|
+
this.valueInput()?.nativeElement.focus();
|
|
72
|
+
}
|
|
73
|
+
valueEnter() {
|
|
74
|
+
if (!this.definition().multiSelect &&
|
|
75
|
+
(this.criterionValue().value || this.criterionValue().dateValue)) {
|
|
76
|
+
this.active.set(false);
|
|
77
|
+
this.submitValue.emit();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
valueBackspace() {
|
|
81
|
+
if (!this.valueInput().nativeElement.value) {
|
|
82
|
+
this.backspaceOverflow.emit();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: SiFilteredSearchValueBase, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
86
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.0.6", type: SiFilteredSearchValueBase, isStandalone: true, inputs: { active: { classPropertyName: "active", publicName: "active", isSignal: true, isRequired: true, transformFunction: null }, criterionValue: { classPropertyName: "criterionValue", publicName: "criterionValue", isSignal: true, isRequired: true, transformFunction: null }, definition: { classPropertyName: "definition", publicName: "definition", isSignal: true, isRequired: true, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: true, transformFunction: null }, searchLabel: { classPropertyName: "searchLabel", publicName: "searchLabel", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { active: "activeChange", criterionValue: "criterionValueChange", submitValue: "submitValue", editValue: "editValue", backspaceOverflow: "backspaceOverflow" }, host: { properties: { "class.invalid-criterion": "!validValue()" }, classAttribute: "pill pill-interactive px-0 criterion-value-section" }, ngImport: i0 });
|
|
87
|
+
}
|
|
88
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: SiFilteredSearchValueBase, decorators: [{
|
|
89
|
+
type: Directive,
|
|
90
|
+
args: [{
|
|
91
|
+
host: {
|
|
92
|
+
'[class.invalid-criterion]': '!validValue()',
|
|
93
|
+
'class': 'pill pill-interactive px-0 criterion-value-section'
|
|
94
|
+
}
|
|
95
|
+
}]
|
|
96
|
+
}] });
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Copyright Siemens 2016 - 2025.
|
|
100
|
+
* SPDX-License-Identifier: MIT
|
|
101
|
+
*/
|
|
102
|
+
class SiFilteredSearchDateValueComponent extends SiFilteredSearchValueBase {
|
|
103
|
+
locale = inject(LOCALE_ID).toString();
|
|
104
|
+
valueInput = viewChild('valueInput');
|
|
105
|
+
datepickerOverlay = viewChild(SiDatepickerOverlayDirective);
|
|
106
|
+
disableTime = signal(false);
|
|
107
|
+
shortDateFormat;
|
|
108
|
+
focusInOverlay = computed(() => !!this.datepickerOverlay()?.isShown());
|
|
109
|
+
validFormat = computed(() => isValid(this.criterionValue().dateValue));
|
|
110
|
+
// The information if the time is currently disabled is only present in the
|
|
111
|
+
// current search criterion instance and not in the generic configuration.
|
|
112
|
+
// So we need to merge the initial config with the current instance config.
|
|
113
|
+
dateConfig = computed(() => ({
|
|
114
|
+
...this.definition().datepickerConfig,
|
|
115
|
+
disabledTime: this.disableTime()
|
|
116
|
+
}));
|
|
117
|
+
dateFormat = computed(() => getDatepickerFormat(this.locale, this.dateConfig()));
|
|
118
|
+
validValue = computed(() => {
|
|
119
|
+
const dateConfig = this.dateConfig();
|
|
120
|
+
const minDate = dateConfig?.minDate ?? false;
|
|
121
|
+
const maxDate = dateConfig?.maxDate ?? false;
|
|
122
|
+
const dateValue = this.criterionValue().dateValue ?? false;
|
|
123
|
+
return ((!minDate || (minDate && dateValue && dateValue >= minDate)) &&
|
|
124
|
+
(!maxDate || (maxDate && dateValue && dateValue <= maxDate)));
|
|
125
|
+
});
|
|
126
|
+
constructor() {
|
|
127
|
+
super();
|
|
128
|
+
this.shortDateFormat = getNamedFormat(this.locale, 'shortDate');
|
|
129
|
+
if (!this.shortDateFormat.includes('yyyy')) {
|
|
130
|
+
this.shortDateFormat = this.shortDateFormat.replace('yy', 'yyyy');
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
valueDateSelect(date) {
|
|
134
|
+
// In case the user type an illegal date into the date input,
|
|
135
|
+
// our directive emits a new undefined value and keeps
|
|
136
|
+
if (!date && this.criterionValue().dateValue) {
|
|
137
|
+
date = new Date(this.criterionValue().dateValue);
|
|
138
|
+
}
|
|
139
|
+
let value;
|
|
140
|
+
const validationType = this.definition().validationType;
|
|
141
|
+
if (validationType === 'date') {
|
|
142
|
+
value = getISODateString(date, 'date', this.locale);
|
|
143
|
+
}
|
|
144
|
+
else if (validationType === 'date-time') {
|
|
145
|
+
if (this.disableTime()) {
|
|
146
|
+
value = getISODateString(date, 'date', this.locale);
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
value = getISODateString(date, 'date-time', this.locale);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
this.criterionValue.update(v => ({ ...v, value, dateValue: date }));
|
|
153
|
+
}
|
|
154
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: SiFilteredSearchDateValueComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
155
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.0.6", type: SiFilteredSearchDateValueComponent, isStandalone: true, selector: "si-filtered-search-date-value", providers: [
|
|
156
|
+
{ provide: SiFilteredSearchValueBase, useExisting: SiFilteredSearchDateValueComponent }
|
|
157
|
+
], viewQueries: [{ propertyName: "valueInput", first: true, predicate: ["valueInput"], descendants: true, isSignal: true }, { propertyName: "datepickerOverlay", first: true, predicate: SiDatepickerOverlayDirective, descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "@let dateValue = criterionValue().dateValue;\n@if (!active()) {\n <div\n class=\"criterion-value-text focus-inside px-4\"\n [tabindex]=\"disabled() ? -1 : 0\"\n (keydown.enter)=\"editValue.emit()\"\n (click)=\"editValue.emit()\"\n >\n @if (!validFormat()) {\n <!-- DatePipe throws an error if the date is invalid, so we have to do it on our own. -->\n <!-- We may should have a better solution here. Ideally we would keep the broken string so that a user can fix it later. -->\n <!-- eslint-disable-next-line @angular-eslint/template/no-any -->\n {{ $any(dateValue).toString() }}\n } @else if (!disableTime() || definition().validationType === 'date-time') {\n {{ dateValue | date: dateFormat() }}\n } @else {\n {{ dateValue | date: shortDateFormat }}\n }\n </div>\n} @else {\n <input\n #valueInput\n type=\"text\"\n siDatepicker\n class=\"px-4 py-0 border-0 focus-inside\"\n [attr.aria-label]=\"searchLabel() | translate\"\n [siDatepickerConfig]=\"dateConfig()\"\n [ngModel]=\"dateValue\"\n (keydown.backspace)=\"valueBackspace()\"\n (keydown.enter)=\"valueEnter()\"\n (siDatepickerDisabledTime)=\"disableTime.set($event)\"\n (ngModelChange)=\"valueDateSelect($event)\"\n />\n}\n", styles: ["input{background:transparent}\n"], dependencies: [{ kind: "pipe", type: DatePipe, name: "date" }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: SiDatepickerDirective, selector: "[siDatepicker]", inputs: ["autoClose", "triggeringInput"], exportAs: ["siDatepicker"] }, { kind: "ngmodule", type: SiTranslateModule }, { kind: "pipe", type: i2.SiTranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
158
|
+
}
|
|
159
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: SiFilteredSearchDateValueComponent, decorators: [{
|
|
160
|
+
type: Component,
|
|
161
|
+
args: [{ selector: 'si-filtered-search-date-value', imports: [DatePipe, FormsModule, SiDatepickerDirective, SiTranslateModule], providers: [
|
|
162
|
+
{ provide: SiFilteredSearchValueBase, useExisting: SiFilteredSearchDateValueComponent }
|
|
163
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, template: "@let dateValue = criterionValue().dateValue;\n@if (!active()) {\n <div\n class=\"criterion-value-text focus-inside px-4\"\n [tabindex]=\"disabled() ? -1 : 0\"\n (keydown.enter)=\"editValue.emit()\"\n (click)=\"editValue.emit()\"\n >\n @if (!validFormat()) {\n <!-- DatePipe throws an error if the date is invalid, so we have to do it on our own. -->\n <!-- We may should have a better solution here. Ideally we would keep the broken string so that a user can fix it later. -->\n <!-- eslint-disable-next-line @angular-eslint/template/no-any -->\n {{ $any(dateValue).toString() }}\n } @else if (!disableTime() || definition().validationType === 'date-time') {\n {{ dateValue | date: dateFormat() }}\n } @else {\n {{ dateValue | date: shortDateFormat }}\n }\n </div>\n} @else {\n <input\n #valueInput\n type=\"text\"\n siDatepicker\n class=\"px-4 py-0 border-0 focus-inside\"\n [attr.aria-label]=\"searchLabel() | translate\"\n [siDatepickerConfig]=\"dateConfig()\"\n [ngModel]=\"dateValue\"\n (keydown.backspace)=\"valueBackspace()\"\n (keydown.enter)=\"valueEnter()\"\n (siDatepickerDisabledTime)=\"disableTime.set($event)\"\n (ngModelChange)=\"valueDateSelect($event)\"\n />\n}\n", styles: ["input{background:transparent}\n"] }]
|
|
164
|
+
}], ctorParameters: () => [] });
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Copyright Siemens 2016 - 2025.
|
|
168
|
+
* SPDX-License-Identifier: MIT
|
|
169
|
+
*/
|
|
170
|
+
class SiFilteredSearchTypeaheadComponent extends SiFilteredSearchValueBase {
|
|
171
|
+
lazyValueProvider = input();
|
|
172
|
+
searchDebounceTime = input.required();
|
|
173
|
+
itemCountText = input.required();
|
|
174
|
+
items = input.required();
|
|
175
|
+
onlySelectValue = input.required();
|
|
176
|
+
maxCriteriaOptions = input.required();
|
|
177
|
+
optionsInScrollableView = input.required();
|
|
178
|
+
readonly = input.required();
|
|
179
|
+
disableSelectionByColonAndSemicolon = input.required();
|
|
180
|
+
isStrictOrOnlySelectValue = input.required();
|
|
181
|
+
inputChange = new BehaviorSubject('');
|
|
182
|
+
selectionChange = new BehaviorSubject([]);
|
|
183
|
+
valueInput = viewChild('valueInput');
|
|
184
|
+
optionValue = signal([]);
|
|
185
|
+
destroyRef = inject(DestroyRef);
|
|
186
|
+
translateService = inject(SiTranslateService);
|
|
187
|
+
inputType = computed(() => this.definition().validationType === 'integer' || this.definition().validationType === 'float'
|
|
188
|
+
? 'number'
|
|
189
|
+
: 'text');
|
|
190
|
+
step = computed(() => (this.definition().validationType === 'integer' ? '1' : 'any'));
|
|
191
|
+
options = computed(() => this.buildOptions());
|
|
192
|
+
hasMultiSelections = computed(() => this.definition().multiSelect &&
|
|
193
|
+
Array.isArray(this.criterionValue().value) &&
|
|
194
|
+
this.criterionValue().value.length > 1);
|
|
195
|
+
validValue = computed(() => {
|
|
196
|
+
const config = this.definition();
|
|
197
|
+
if (!this.isStrictOrOnlySelectValue() && !config.strictValue && !config.onlySelectValue) {
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
// TODO: this never worked with lazy options. We should fix that.
|
|
201
|
+
// TODO: checking if options are empty is also questionable. Should be changed v47.
|
|
202
|
+
return !!((config.options?.length && this.optionValue().length) ||
|
|
203
|
+
(!config.options?.length && !!this.criterionValue().value));
|
|
204
|
+
});
|
|
205
|
+
// This MUST only be updated if the active state changes.
|
|
206
|
+
// Otherwise, user values might be overridden.
|
|
207
|
+
// It is only used to pass the initial input value if the user starts editing the input.
|
|
208
|
+
valueLabel = computed(() => {
|
|
209
|
+
if (this.active()) {
|
|
210
|
+
const optionValue = untracked(() => this.optionValue());
|
|
211
|
+
const definition = untracked(() => this.definition());
|
|
212
|
+
if (optionValue.length && !definition.multiSelect) {
|
|
213
|
+
const [option] = optionValue;
|
|
214
|
+
return option.label ? this.translateService.translateSync(option.label) : option.value;
|
|
215
|
+
}
|
|
216
|
+
else if (!definition.multiSelect) {
|
|
217
|
+
return untracked(() => this.criterionValue().value);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return '';
|
|
221
|
+
});
|
|
222
|
+
ngOnChanges(changes) {
|
|
223
|
+
if (this.definition().multiSelect &&
|
|
224
|
+
changes.criterionValue &&
|
|
225
|
+
this.criterionValue().value?.length !== this.optionValue().length) {
|
|
226
|
+
this.optionValue.set([]);
|
|
227
|
+
this.selectionChange.next([]);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
ngOnInit() {
|
|
231
|
+
this.inputChange.next(this.definition().multiSelect ? '' : (this.criterionValue().value ?? ''));
|
|
232
|
+
if (this.definition().multiSelect) {
|
|
233
|
+
this.selectionChange.next(this.criterionValue().value);
|
|
234
|
+
}
|
|
235
|
+
this.buildOptionValue();
|
|
236
|
+
}
|
|
237
|
+
valueFilterKeys(event) {
|
|
238
|
+
if (this.definition().validationType === 'integer' &&
|
|
239
|
+
(event.key === '.' || event.key === ',')) {
|
|
240
|
+
event.preventDefault();
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
valueChange(newValue) {
|
|
244
|
+
if (!this.definition().multiSelect && typeof newValue === 'string') {
|
|
245
|
+
const match = newValue.match(/(.+?);(.*)$/);
|
|
246
|
+
let value;
|
|
247
|
+
if (!this.disableSelectionByColonAndSemicolon() && match) {
|
|
248
|
+
value = match[1];
|
|
249
|
+
this.submitValue.emit({ freeText: match[2] });
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
value = newValue;
|
|
253
|
+
}
|
|
254
|
+
this.optionValue.set([]);
|
|
255
|
+
this.criterionValue.update(v => ({ ...v, value }));
|
|
256
|
+
this.inputChange.next(newValue);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
valueTypeaheadFullMatch(match) {
|
|
260
|
+
if (this.definition().multiSelect) {
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
const option = match.option;
|
|
264
|
+
this.optionValue.set([option]);
|
|
265
|
+
// Usually, we already emitted a change in onCriterionValueInputChange using the text entered by the user.
|
|
266
|
+
// In case of a fullMatch, we should check if the value is different from label.
|
|
267
|
+
// If it is different, we must emit another event using the value instead of the label.
|
|
268
|
+
// TODO: prevent the emit of the label matching the option. This is currently not possible due to the order events.
|
|
269
|
+
if (option.value !== option.translatedLabel) {
|
|
270
|
+
this.criterionValue.update(v => ({ ...v, value: option.value }));
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
valueTypeaheadSelect(match) {
|
|
274
|
+
if (!this.definition().multiSelect) {
|
|
275
|
+
this.valueTypeaheadFullMatch(match);
|
|
276
|
+
this.submitValue.emit();
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
// In multi-select scenarios, the internal model value is always of type array
|
|
280
|
+
const option = match.option;
|
|
281
|
+
if (match.itemSelected) {
|
|
282
|
+
this.criterionValue.update(v => ({ ...v, value: [...v.value, option.value] }));
|
|
283
|
+
this.optionValue.update(v => [...v, option]);
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
const value = this.criterionValue();
|
|
287
|
+
if (typeof value.value !== 'string') {
|
|
288
|
+
this.criterionValue.set({
|
|
289
|
+
...value,
|
|
290
|
+
value: value.value?.filter(elem => elem !== option.value)
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
this.optionValue.update(v => v.filter(elem => elem.value !== option.value));
|
|
294
|
+
}
|
|
295
|
+
this.selectionChange.next(this.criterionValue().value);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
buildOptions() {
|
|
299
|
+
let optionsStream;
|
|
300
|
+
if (this.lazyValueProvider()) {
|
|
301
|
+
optionsStream = this.inputChange.pipe(debounceTime(this.searchDebounceTime()), takeUntilDestroyed(this.destroyRef), switchMap(value => {
|
|
302
|
+
return this.lazyValueProvider()(this.definition().name,
|
|
303
|
+
// TODO: fix lazy loading for multi-select. Seems to be not needed, but it should work.
|
|
304
|
+
this.definition().multiSelect ? '' : (value ?? '')).pipe(map(options => toOptionCriteria(options)), tap(options => ((this.definition() ?? {}).options = options)));
|
|
305
|
+
}));
|
|
306
|
+
}
|
|
307
|
+
else if (this.definition()) {
|
|
308
|
+
optionsStream = of(toOptionCriteria(this.definition().options));
|
|
309
|
+
}
|
|
310
|
+
let translatedOptionsStream = optionsStream?.pipe(switchMap(options => {
|
|
311
|
+
const keys = options.map(option => option.label).filter(label => !!label);
|
|
312
|
+
return this.translateService.translateAsync(keys).pipe(map(translations => options.map(option => ({
|
|
313
|
+
...option,
|
|
314
|
+
translatedLabel: translations[option.label] ?? option.label ?? option.value
|
|
315
|
+
}))));
|
|
316
|
+
}));
|
|
317
|
+
if (this.definition().multiSelect && this.definition().options) {
|
|
318
|
+
translatedOptionsStream = translatedOptionsStream?.pipe(switchMap(options => this.selectionChange.pipe(tap(value => selectOptions(options, value)), map(() => options))));
|
|
319
|
+
}
|
|
320
|
+
return translatedOptionsStream;
|
|
321
|
+
}
|
|
322
|
+
buildOptionValue() {
|
|
323
|
+
if (!this.criterionValue().value?.length) {
|
|
324
|
+
this.optionValue.set([]);
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
// resolve options for initial values
|
|
328
|
+
this.options()
|
|
329
|
+
.pipe(first())
|
|
330
|
+
.subscribe(options => {
|
|
331
|
+
if (this.definition().multiSelect) {
|
|
332
|
+
const value = this.criterionValue().value;
|
|
333
|
+
this.optionValue.set(options.filter(option => value.includes(option.value) ||
|
|
334
|
+
// TODO: remove this. I don't know why, but it seems like previously FS accepted labels as well
|
|
335
|
+
value.includes(option.translatedLabel)));
|
|
336
|
+
// Sneaky patch the value.
|
|
337
|
+
// We did not emit a change, as no user interaction happened.
|
|
338
|
+
// We should consider dropping this, but there is currently a unit test checking this behavior.
|
|
339
|
+
value.splice(0, value.length, ...this.optionValue().map(option => option.value));
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
this.optionValue.set(options.filter(option => option.value === this.criterionValue().value ||
|
|
343
|
+
// TODO: remove this. I don't know why, but it seems like previously FS accepted labels as well
|
|
344
|
+
option.translatedLabel === this.criterionValue().value));
|
|
345
|
+
// Sneaky patch the value.
|
|
346
|
+
// We did not emit a change, as no user interaction happened.
|
|
347
|
+
// We should consider dropping this, but there is currently a unit test checking this behavior.
|
|
348
|
+
const [optionValue] = this.optionValue();
|
|
349
|
+
if (optionValue) {
|
|
350
|
+
// TODO: The last ?? optionValue.label is non-sense, but we have a unit test checking this behavior.
|
|
351
|
+
this.criterionValue().value = optionValue.value ?? optionValue.label;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: SiFilteredSearchTypeaheadComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
358
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.0.6", type: SiFilteredSearchTypeaheadComponent, isStandalone: true, selector: "si-filtered-search-typeahead", inputs: { lazyValueProvider: { classPropertyName: "lazyValueProvider", publicName: "lazyValueProvider", isSignal: true, isRequired: false, transformFunction: null }, searchDebounceTime: { classPropertyName: "searchDebounceTime", publicName: "searchDebounceTime", isSignal: true, isRequired: true, transformFunction: null }, itemCountText: { classPropertyName: "itemCountText", publicName: "itemCountText", isSignal: true, isRequired: true, transformFunction: null }, items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, onlySelectValue: { classPropertyName: "onlySelectValue", publicName: "onlySelectValue", isSignal: true, isRequired: true, transformFunction: null }, maxCriteriaOptions: { classPropertyName: "maxCriteriaOptions", publicName: "maxCriteriaOptions", isSignal: true, isRequired: true, transformFunction: null }, optionsInScrollableView: { classPropertyName: "optionsInScrollableView", publicName: "optionsInScrollableView", isSignal: true, isRequired: true, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: true, transformFunction: null }, disableSelectionByColonAndSemicolon: { classPropertyName: "disableSelectionByColonAndSemicolon", publicName: "disableSelectionByColonAndSemicolon", isSignal: true, isRequired: true, transformFunction: null }, isStrictOrOnlySelectValue: { classPropertyName: "isStrictOrOnlySelectValue", publicName: "isStrictOrOnlySelectValue", isSignal: true, isRequired: true, transformFunction: null } }, providers: [
|
|
359
|
+
{ provide: SiFilteredSearchValueBase, useExisting: SiFilteredSearchTypeaheadComponent }
|
|
360
|
+
], viewQueries: [{ propertyName: "valueInput", first: true, predicate: ["valueInput"], descendants: true, isSignal: true }], usesInheritance: true, usesOnChanges: true, ngImport: i0, template: "@let value = criterionValue().value;\n@if (!active()) {\n <div\n class=\"criterion-value-text focus-inside px-4\"\n [tabindex]=\"disabled() ? -1 : 0\"\n (keydown.enter)=\"editValue.emit()\"\n (click)=\"editValue.emit()\"\n >\n @if (!!value && hasMultiSelections()) {\n @if (itemCountText()) {\n {{ itemCountText() | translate: { itemCount: value.length } }}\n } @else {\n {{ value!.length }} {{ items() | translate }}\n }\n } @else if (optionValue()[0]) {\n {{ optionValue()[0].label ?? optionValue()[0].value | translate }}\n } @else {\n {{ value }}\n }\n </div>\n} @else {\n <input\n #valueInput\n typeaheadOptionField=\"translatedLabel\"\n class=\"px-4 py-0 border-0 focus-inside\"\n typeaheadScrollable\n [type]=\"inputType()\"\n [step]=\"step()\"\n [ngModel]=\"valueLabel()\"\n [siTypeahead]=\"options() ?? []\"\n [typeaheadProcess]=\"definition().multiSelect || !onlySelectValue()\"\n [typeaheadMultiSelect]=\"definition().multiSelect\"\n [typeaheadMinLength]=\"0\"\n [typeaheadOptionsLimit]=\"maxCriteriaOptions()\"\n [typeaheadAutoSelectIndex]=\"definition().multiSelect || value?.length ? 0 : -1\"\n [readOnly]=\"\n readonly() || onlySelectValue() || (definition().onlySelectValue && !definition().multiSelect)\n \"\n [typeaheadOptionsInScrollableView]=\"optionsInScrollableView()\"\n [attr.aria-label]=\"searchLabel() | translate\"\n (keydown)=\"valueFilterKeys($event)\"\n (keydown.backspace)=\"valueBackspace()\"\n (keydown.enter)=\"valueEnter()\"\n (ngModelChange)=\"valueChange($event)\"\n (typeaheadOnFullMatch)=\"valueTypeaheadFullMatch($event)\"\n (typeaheadOnSelect)=\"valueTypeaheadSelect($event)\"\n />\n}\n", styles: ["input{background:transparent;-moz-appearance:textfield}input::-webkit-inner-spin-button,input::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}\n"], dependencies: [{ kind: "directive", type: SiTypeaheadDirective, selector: "[siTypeahead]", inputs: ["siTypeahead", "typeaheadProcess", "typeaheadScrollable", "typeaheadOptionsInScrollableView", "typeaheadOptionsLimit", "typeaheadScrollableAdditionalHeight", "typeaheadAutoSelectIndex", "typeaheadCloseOnEsc", "typeaheadClearValueOnSelect", "typeaheadWaitMs", "typeaheadMinLength", "typeaheadOptionField", "typeaheadMultiSelect", "typeaheadTokenize", "typeaheadMatchAllTokens", "typeaheadItemTemplate", "typeaheadSkipSortingMatches", "typeaheadAutocompleteListLabel", "typeaheadFullWidth"], outputs: ["typeaheadOnInput", "typeaheadOnSelect", "typeaheadOnMultiselectClose", "typeaheadOnFullMatch", "typeaheadClosed", "typeaheadOpenChange"], exportAs: ["si-typeahead"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: SiTranslateModule }, { kind: "pipe", type: i2.SiTranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
361
|
+
}
|
|
362
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: SiFilteredSearchTypeaheadComponent, decorators: [{
|
|
363
|
+
type: Component,
|
|
364
|
+
args: [{ selector: 'si-filtered-search-typeahead', imports: [SiTypeaheadDirective, FormsModule, SiTranslateModule], providers: [
|
|
365
|
+
{ provide: SiFilteredSearchValueBase, useExisting: SiFilteredSearchTypeaheadComponent }
|
|
366
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, template: "@let value = criterionValue().value;\n@if (!active()) {\n <div\n class=\"criterion-value-text focus-inside px-4\"\n [tabindex]=\"disabled() ? -1 : 0\"\n (keydown.enter)=\"editValue.emit()\"\n (click)=\"editValue.emit()\"\n >\n @if (!!value && hasMultiSelections()) {\n @if (itemCountText()) {\n {{ itemCountText() | translate: { itemCount: value.length } }}\n } @else {\n {{ value!.length }} {{ items() | translate }}\n }\n } @else if (optionValue()[0]) {\n {{ optionValue()[0].label ?? optionValue()[0].value | translate }}\n } @else {\n {{ value }}\n }\n </div>\n} @else {\n <input\n #valueInput\n typeaheadOptionField=\"translatedLabel\"\n class=\"px-4 py-0 border-0 focus-inside\"\n typeaheadScrollable\n [type]=\"inputType()\"\n [step]=\"step()\"\n [ngModel]=\"valueLabel()\"\n [siTypeahead]=\"options() ?? []\"\n [typeaheadProcess]=\"definition().multiSelect || !onlySelectValue()\"\n [typeaheadMultiSelect]=\"definition().multiSelect\"\n [typeaheadMinLength]=\"0\"\n [typeaheadOptionsLimit]=\"maxCriteriaOptions()\"\n [typeaheadAutoSelectIndex]=\"definition().multiSelect || value?.length ? 0 : -1\"\n [readOnly]=\"\n readonly() || onlySelectValue() || (definition().onlySelectValue && !definition().multiSelect)\n \"\n [typeaheadOptionsInScrollableView]=\"optionsInScrollableView()\"\n [attr.aria-label]=\"searchLabel() | translate\"\n (keydown)=\"valueFilterKeys($event)\"\n (keydown.backspace)=\"valueBackspace()\"\n (keydown.enter)=\"valueEnter()\"\n (ngModelChange)=\"valueChange($event)\"\n (typeaheadOnFullMatch)=\"valueTypeaheadFullMatch($event)\"\n (typeaheadOnSelect)=\"valueTypeaheadSelect($event)\"\n />\n}\n", styles: ["input{background:transparent;-moz-appearance:textfield}input::-webkit-inner-spin-button,input::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}\n"] }]
|
|
367
|
+
}] });
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Copyright Siemens 2016 - 2025.
|
|
371
|
+
* SPDX-License-Identifier: MIT
|
|
372
|
+
*/
|
|
373
|
+
class SiFilteredSearchValueComponent {
|
|
374
|
+
value = model.required();
|
|
375
|
+
definition = input.required();
|
|
376
|
+
disabled = input.required();
|
|
377
|
+
readonly = input.required();
|
|
378
|
+
onlySelectValue = input.required();
|
|
379
|
+
maxCriteriaOptions = input.required();
|
|
380
|
+
optionsInScrollableView = input.required();
|
|
381
|
+
clearButtonLabel = input.required();
|
|
382
|
+
lazyValueProvider = input();
|
|
383
|
+
searchDebounceTime = input.required();
|
|
384
|
+
itemCountText = input.required();
|
|
385
|
+
items = input.required();
|
|
386
|
+
disableSelectionByColonAndSemicolon = input.required();
|
|
387
|
+
searchLabel = input.required();
|
|
388
|
+
invalidCriterion = input.required();
|
|
389
|
+
isStrictOrOnlySelectValue = input.required();
|
|
390
|
+
editOnCreation = input.required();
|
|
391
|
+
deleteCriterion = output();
|
|
392
|
+
submitCriterion = output();
|
|
393
|
+
active = signal(false);
|
|
394
|
+
icons = addIcons({ elementCancel });
|
|
395
|
+
hasPendingFocus = false;
|
|
396
|
+
operatorInput = viewChild('operatorInput');
|
|
397
|
+
valueInput = viewChild(SiFilteredSearchValueBase);
|
|
398
|
+
type = computed(() => {
|
|
399
|
+
switch (this.definition().validationType) {
|
|
400
|
+
case 'date':
|
|
401
|
+
case 'date-time':
|
|
402
|
+
return 'date';
|
|
403
|
+
default:
|
|
404
|
+
return 'typeahead';
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
selectedOperatorIndex = computed(() => {
|
|
408
|
+
const operator = this.value().operator;
|
|
409
|
+
const operators = this.definition().operators;
|
|
410
|
+
if (!operator || !operators) {
|
|
411
|
+
return -1;
|
|
412
|
+
}
|
|
413
|
+
return operators.findIndex(op => op.includes(operator));
|
|
414
|
+
});
|
|
415
|
+
longestOperatorLength = computed(() => {
|
|
416
|
+
const operators = this.definition().operators;
|
|
417
|
+
if (!operators) {
|
|
418
|
+
return 0;
|
|
419
|
+
}
|
|
420
|
+
return Math.max(...operators.map(a => a.length));
|
|
421
|
+
});
|
|
422
|
+
ngOnInit() {
|
|
423
|
+
if (this.editOnCreation()) {
|
|
424
|
+
this.edit();
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
edit(field) {
|
|
428
|
+
if (this.readonly()) {
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
this.active.set(true);
|
|
432
|
+
this.hasPendingFocus = true;
|
|
433
|
+
setTimeout(() => {
|
|
434
|
+
if (field === 'value') {
|
|
435
|
+
this.valueInput()?.focus();
|
|
436
|
+
}
|
|
437
|
+
else if (field === 'operator') {
|
|
438
|
+
this.operatorInput()?.nativeElement.focus();
|
|
439
|
+
}
|
|
440
|
+
else {
|
|
441
|
+
(this.operatorInput()?.nativeElement ?? this.valueInput())?.focus();
|
|
442
|
+
}
|
|
443
|
+
this.hasPendingFocus = false;
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
backspaceOverflow() {
|
|
447
|
+
const operatorInput = this.operatorInput()?.nativeElement;
|
|
448
|
+
if (operatorInput) {
|
|
449
|
+
operatorInput.focus();
|
|
450
|
+
}
|
|
451
|
+
else {
|
|
452
|
+
this.deleteCriterion.emit();
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
focusChange(focusOrigin) {
|
|
456
|
+
if (this.hasPendingFocus) {
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
setTimeout(() => {
|
|
460
|
+
// We need to wait for the overlay, that might be shown and now close as well on onside click.
|
|
461
|
+
if (this.active() && !focusOrigin && !this.valueInput()?.focusInOverlay()) {
|
|
462
|
+
this.active.set(false);
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
operatorUpdate(operator) {
|
|
467
|
+
this.value.update(value => ({ ...value, operator }));
|
|
468
|
+
}
|
|
469
|
+
operatorBackspace() {
|
|
470
|
+
if (!this.operatorInput()?.nativeElement.value) {
|
|
471
|
+
this.deleteCriterion.emit();
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
operatorEnter() {
|
|
475
|
+
this.valueInput().focus();
|
|
476
|
+
}
|
|
477
|
+
operatorBlur() {
|
|
478
|
+
const operators = this.definition().operators;
|
|
479
|
+
if (operators && !operators.includes(this.value().operator)) {
|
|
480
|
+
this.operatorUpdate(operators.includes('=') ? '=' : operators[0]);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
clear() {
|
|
484
|
+
if (!this.active() || !this.value().value?.length) {
|
|
485
|
+
this.deleteCriterion.emit({ triggerSearch: true });
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
this.value.update(v => ({
|
|
489
|
+
...v,
|
|
490
|
+
dateValue: undefined,
|
|
491
|
+
value: this.definition().multiSelect ? [] : ''
|
|
492
|
+
}));
|
|
493
|
+
}
|
|
494
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: SiFilteredSearchValueComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
495
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.0.6", type: SiFilteredSearchValueComponent, isStandalone: true, selector: "si-filtered-search-value", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null }, definition: { classPropertyName: "definition", publicName: "definition", isSignal: true, isRequired: true, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: true, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: true, transformFunction: null }, onlySelectValue: { classPropertyName: "onlySelectValue", publicName: "onlySelectValue", isSignal: true, isRequired: true, transformFunction: null }, maxCriteriaOptions: { classPropertyName: "maxCriteriaOptions", publicName: "maxCriteriaOptions", isSignal: true, isRequired: true, transformFunction: null }, optionsInScrollableView: { classPropertyName: "optionsInScrollableView", publicName: "optionsInScrollableView", isSignal: true, isRequired: true, transformFunction: null }, clearButtonLabel: { classPropertyName: "clearButtonLabel", publicName: "clearButtonLabel", isSignal: true, isRequired: true, transformFunction: null }, lazyValueProvider: { classPropertyName: "lazyValueProvider", publicName: "lazyValueProvider", isSignal: true, isRequired: false, transformFunction: null }, searchDebounceTime: { classPropertyName: "searchDebounceTime", publicName: "searchDebounceTime", isSignal: true, isRequired: true, transformFunction: null }, itemCountText: { classPropertyName: "itemCountText", publicName: "itemCountText", isSignal: true, isRequired: true, transformFunction: null }, items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, disableSelectionByColonAndSemicolon: { classPropertyName: "disableSelectionByColonAndSemicolon", publicName: "disableSelectionByColonAndSemicolon", isSignal: true, isRequired: true, transformFunction: null }, searchLabel: { classPropertyName: "searchLabel", publicName: "searchLabel", isSignal: true, isRequired: true, transformFunction: null }, invalidCriterion: { classPropertyName: "invalidCriterion", publicName: "invalidCriterion", isSignal: true, isRequired: true, transformFunction: null }, isStrictOrOnlySelectValue: { classPropertyName: "isStrictOrOnlySelectValue", publicName: "isStrictOrOnlySelectValue", isSignal: true, isRequired: true, transformFunction: null }, editOnCreation: { classPropertyName: "editOnCreation", publicName: "editOnCreation", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { value: "valueChange", deleteCriterion: "deleteCriterion", submitCriterion: "submitCriterion" }, viewQueries: [{ propertyName: "operatorInput", first: true, predicate: ["operatorInput"], descendants: true, isSignal: true }, { propertyName: "valueInput", first: true, predicate: SiFilteredSearchValueBase, descendants: true, isSignal: true }], ngImport: i0, template: "<div\n cdkMonitorSubtreeFocus\n class=\"criteria pill-group\"\n [class.disabled]=\"disabled()\"\n [attr.aria-disabled]=\"disabled()\"\n [class.invalid-criterion]=\"invalidCriterion()\"\n (cdkFocusChange)=\"focusChange($event)\"\n>\n <div class=\"pill pill-interactive criterion-label si-title-2\" (click)=\"edit()\">\n {{ definition().label | translate }}\n </div>\n @if (definition().operators?.length) {\n <div class=\"pill pill-interactive px-0 criterion-operator-section\">\n <!-- criterion operator input -->\n @if (active()) {\n <input\n #operatorInput\n type=\"text\"\n class=\"focus-inside operator-input py-0 border-0\"\n [siTypeahead]=\"definition().operators!\"\n [typeaheadProcess]=\"false\"\n [typeaheadAutoSelectIndex]=\"selectedOperatorIndex()\"\n [typeaheadMinLength]=\"0\"\n [typeaheadOptionsLimit]=\"0\"\n [readOnly]=\"false\"\n [typeaheadScrollable]=\"true\"\n [typeaheadOptionsInScrollableView]=\"optionsInScrollableView()\"\n [attr.aria-label]=\"searchLabel() | translate\"\n [attr.size]=\"longestOperatorLength()\"\n [ngModel]=\"value().operator\"\n (ngModelChange)=\"operatorUpdate($event)\"\n (keydown.backspace)=\"operatorBackspace()\"\n (keydown.enter)=\"operatorEnter()\"\n (typeaheadOnSelect)=\"operatorEnter()\"\n (blur)=\"operatorBlur()\"\n />\n } @else {\n <!-- criterion operator field -->\n <div\n class=\"criterion-operator-text px-2 focus-inside\"\n [tabindex]=\"!disabled() ? 0 : -1\"\n (keydown.enter)=\"edit('operator')\"\n (click)=\"edit('operator')\"\n >\n {{ value().operator }}\n </div>\n }\n </div>\n }\n @switch (type()) {\n @case ('date') {\n <si-filtered-search-date-value\n [definition]=\"definition()\"\n [disabled]=\"disabled()\"\n [searchLabel]=\"searchLabel()\"\n [(active)]=\"active\"\n [(criterionValue)]=\"value\"\n (backspaceOverflow)=\"backspaceOverflow()\"\n (editValue)=\"edit('value')\"\n (submitValue)=\"submitCriterion.emit($event)\"\n />\n }\n @case ('typeahead') {\n <si-filtered-search-typeahead\n [definition]=\"definition()\"\n [disabled]=\"disabled()\"\n [searchLabel]=\"searchLabel()\"\n [searchDebounceTime]=\"searchDebounceTime()\"\n [onlySelectValue]=\"onlySelectValue()\"\n [optionsInScrollableView]=\"optionsInScrollableView()\"\n [maxCriteriaOptions]=\"maxCriteriaOptions()\"\n [lazyValueProvider]=\"lazyValueProvider()\"\n [itemCountText]=\"itemCountText()\"\n [isStrictOrOnlySelectValue]=\"isStrictOrOnlySelectValue()\"\n [disableSelectionByColonAndSemicolon]=\"disableSelectionByColonAndSemicolon()\"\n [items]=\"items()\"\n [readonly]=\"readonly()\"\n [(active)]=\"active\"\n [(criterionValue)]=\"value\"\n (backspaceOverflow)=\"backspaceOverflow()\"\n (editValue)=\"edit('value')\"\n (submitValue)=\"submitCriterion.emit($event)\"\n />\n }\n }\n @if (!readonly()) {\n <div class=\"pill px-0 ms-n1\">\n <button\n type=\"button\"\n class=\"btn btn-circle btn-xs btn-ghost focus-inside\"\n [attr.aria-label]=\"clearButtonLabel() | translate\"\n [disabled]=\"disabled()\"\n (click)=\"clear()\"\n (mousedown)=\"$event.preventDefault()\"\n >\n <si-icon-next [icon]=\"icons.elementCancel\" />\n </button>\n </div>\n }\n</div>\n", styles: [":host-context(.disabled) input,:host-context(.disabled) ::placeholder{color:var(--element-text-disabled)}.pill{background:var(--filter-pill-background-color)}.criteria{display:flex;cursor:pointer;white-space:nowrap;border-radius:12px}.criteria.invalid-criterion{box-shadow:0 0 0 1px var(--element-status-danger)}.criteria:not(.invalid-criterion) .invalid-criterion{box-shadow:inset 0 0 0 1px var(--element-status-danger)}.criteria.disabled{cursor:inherit;pointer-events:none}.operator-input{background:var(--filter-pill-background-color);padding-inline:4px;inline-size:calc(1ch + 8px)}input{background:transparent}\n"], dependencies: [{ kind: "directive", type: CdkMonitorFocus, selector: "[cdkMonitorElementFocus], [cdkMonitorSubtreeFocus]", outputs: ["cdkFocusChange"], exportAs: ["cdkMonitorFocus"] }, { kind: "directive", type: SiTypeaheadDirective, selector: "[siTypeahead]", inputs: ["siTypeahead", "typeaheadProcess", "typeaheadScrollable", "typeaheadOptionsInScrollableView", "typeaheadOptionsLimit", "typeaheadScrollableAdditionalHeight", "typeaheadAutoSelectIndex", "typeaheadCloseOnEsc", "typeaheadClearValueOnSelect", "typeaheadWaitMs", "typeaheadMinLength", "typeaheadOptionField", "typeaheadMultiSelect", "typeaheadTokenize", "typeaheadMatchAllTokens", "typeaheadItemTemplate", "typeaheadSkipSortingMatches", "typeaheadAutocompleteListLabel", "typeaheadFullWidth"], outputs: ["typeaheadOnInput", "typeaheadOnSelect", "typeaheadOnMultiselectClose", "typeaheadOnFullMatch", "typeaheadClosed", "typeaheadOpenChange"], exportAs: ["si-typeahead"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: SiTranslateModule }, { kind: "pipe", type: i2.SiTranslatePipe, name: "translate" }, { kind: "component", type: SiFilteredSearchDateValueComponent, selector: "si-filtered-search-date-value" }, { kind: "component", type: SiFilteredSearchTypeaheadComponent, selector: "si-filtered-search-typeahead", inputs: ["lazyValueProvider", "searchDebounceTime", "itemCountText", "items", "onlySelectValue", "maxCriteriaOptions", "optionsInScrollableView", "readonly", "disableSelectionByColonAndSemicolon", "isStrictOrOnlySelectValue"] }, { kind: "component", type: SiIconNextComponent, selector: "si-icon-next", inputs: ["icon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
496
|
+
}
|
|
497
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: SiFilteredSearchValueComponent, decorators: [{
|
|
498
|
+
type: Component,
|
|
499
|
+
args: [{ selector: 'si-filtered-search-value', imports: [
|
|
500
|
+
CdkMonitorFocus,
|
|
501
|
+
SiTypeaheadDirective,
|
|
502
|
+
FormsModule,
|
|
503
|
+
SiTranslateModule,
|
|
504
|
+
SiFilteredSearchDateValueComponent,
|
|
505
|
+
SiFilteredSearchTypeaheadComponent,
|
|
506
|
+
SiIconNextComponent
|
|
507
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n cdkMonitorSubtreeFocus\n class=\"criteria pill-group\"\n [class.disabled]=\"disabled()\"\n [attr.aria-disabled]=\"disabled()\"\n [class.invalid-criterion]=\"invalidCriterion()\"\n (cdkFocusChange)=\"focusChange($event)\"\n>\n <div class=\"pill pill-interactive criterion-label si-title-2\" (click)=\"edit()\">\n {{ definition().label | translate }}\n </div>\n @if (definition().operators?.length) {\n <div class=\"pill pill-interactive px-0 criterion-operator-section\">\n <!-- criterion operator input -->\n @if (active()) {\n <input\n #operatorInput\n type=\"text\"\n class=\"focus-inside operator-input py-0 border-0\"\n [siTypeahead]=\"definition().operators!\"\n [typeaheadProcess]=\"false\"\n [typeaheadAutoSelectIndex]=\"selectedOperatorIndex()\"\n [typeaheadMinLength]=\"0\"\n [typeaheadOptionsLimit]=\"0\"\n [readOnly]=\"false\"\n [typeaheadScrollable]=\"true\"\n [typeaheadOptionsInScrollableView]=\"optionsInScrollableView()\"\n [attr.aria-label]=\"searchLabel() | translate\"\n [attr.size]=\"longestOperatorLength()\"\n [ngModel]=\"value().operator\"\n (ngModelChange)=\"operatorUpdate($event)\"\n (keydown.backspace)=\"operatorBackspace()\"\n (keydown.enter)=\"operatorEnter()\"\n (typeaheadOnSelect)=\"operatorEnter()\"\n (blur)=\"operatorBlur()\"\n />\n } @else {\n <!-- criterion operator field -->\n <div\n class=\"criterion-operator-text px-2 focus-inside\"\n [tabindex]=\"!disabled() ? 0 : -1\"\n (keydown.enter)=\"edit('operator')\"\n (click)=\"edit('operator')\"\n >\n {{ value().operator }}\n </div>\n }\n </div>\n }\n @switch (type()) {\n @case ('date') {\n <si-filtered-search-date-value\n [definition]=\"definition()\"\n [disabled]=\"disabled()\"\n [searchLabel]=\"searchLabel()\"\n [(active)]=\"active\"\n [(criterionValue)]=\"value\"\n (backspaceOverflow)=\"backspaceOverflow()\"\n (editValue)=\"edit('value')\"\n (submitValue)=\"submitCriterion.emit($event)\"\n />\n }\n @case ('typeahead') {\n <si-filtered-search-typeahead\n [definition]=\"definition()\"\n [disabled]=\"disabled()\"\n [searchLabel]=\"searchLabel()\"\n [searchDebounceTime]=\"searchDebounceTime()\"\n [onlySelectValue]=\"onlySelectValue()\"\n [optionsInScrollableView]=\"optionsInScrollableView()\"\n [maxCriteriaOptions]=\"maxCriteriaOptions()\"\n [lazyValueProvider]=\"lazyValueProvider()\"\n [itemCountText]=\"itemCountText()\"\n [isStrictOrOnlySelectValue]=\"isStrictOrOnlySelectValue()\"\n [disableSelectionByColonAndSemicolon]=\"disableSelectionByColonAndSemicolon()\"\n [items]=\"items()\"\n [readonly]=\"readonly()\"\n [(active)]=\"active\"\n [(criterionValue)]=\"value\"\n (backspaceOverflow)=\"backspaceOverflow()\"\n (editValue)=\"edit('value')\"\n (submitValue)=\"submitCriterion.emit($event)\"\n />\n }\n }\n @if (!readonly()) {\n <div class=\"pill px-0 ms-n1\">\n <button\n type=\"button\"\n class=\"btn btn-circle btn-xs btn-ghost focus-inside\"\n [attr.aria-label]=\"clearButtonLabel() | translate\"\n [disabled]=\"disabled()\"\n (click)=\"clear()\"\n (mousedown)=\"$event.preventDefault()\"\n >\n <si-icon-next [icon]=\"icons.elementCancel\" />\n </button>\n </div>\n }\n</div>\n", styles: [":host-context(.disabled) input,:host-context(.disabled) ::placeholder{color:var(--element-text-disabled)}.pill{background:var(--filter-pill-background-color)}.criteria{display:flex;cursor:pointer;white-space:nowrap;border-radius:12px}.criteria.invalid-criterion{box-shadow:0 0 0 1px var(--element-status-danger)}.criteria:not(.invalid-criterion) .invalid-criterion{box-shadow:inset 0 0 0 1px var(--element-status-danger)}.criteria.disabled{cursor:inherit;pointer-events:none}.operator-input{background:var(--filter-pill-background-color);padding-inline:4px;inline-size:calc(1ch + 8px)}input{background:transparent}\n"] }]
|
|
508
|
+
}] });
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Copyright Siemens 2016 - 2025.
|
|
512
|
+
* SPDX-License-Identifier: MIT
|
|
513
|
+
*/
|
|
514
|
+
class SiFilteredSearchComponent {
|
|
515
|
+
static criterionRegex = /(.+?):(.*)$/;
|
|
516
|
+
/**
|
|
517
|
+
* Output callback event that provides an object describing the
|
|
518
|
+
* selected criteria and additional filter text.
|
|
519
|
+
*/
|
|
520
|
+
doSearch = output();
|
|
521
|
+
/**
|
|
522
|
+
* If this is set to `true`, the search triggers for each input (implicit search).
|
|
523
|
+
* By default, the search is triggered when the user submits by pressing the
|
|
524
|
+
* search button or by pressing enter.
|
|
525
|
+
*
|
|
526
|
+
* @defaultValue false
|
|
527
|
+
*/
|
|
528
|
+
doSearchOnInputChange = input(false, {
|
|
529
|
+
transform: booleanAttribute
|
|
530
|
+
});
|
|
531
|
+
/**
|
|
532
|
+
* In addition to lazy loaded value, you can also lazy load the criteria itself
|
|
533
|
+
*/
|
|
534
|
+
lazyCriterionProvider = input();
|
|
535
|
+
/**
|
|
536
|
+
* In many cases, your application defines the criteria, but the values need
|
|
537
|
+
* to be loaded from a server. In this case you can provide a function that
|
|
538
|
+
* returns the possible criterion options as an Observable.
|
|
539
|
+
*/
|
|
540
|
+
lazyValueProvider = input();
|
|
541
|
+
/**
|
|
542
|
+
* Disable any interactivity.
|
|
543
|
+
*
|
|
544
|
+
* @defaultValue false
|
|
545
|
+
*/
|
|
546
|
+
disabled = input(false, { transform: booleanAttribute });
|
|
547
|
+
/**
|
|
548
|
+
* Do not allow changes. Search can still be triggered.
|
|
549
|
+
*
|
|
550
|
+
* @deprecated Use {@link disabled} instead.
|
|
551
|
+
*
|
|
552
|
+
* @defaultValue false
|
|
553
|
+
*/
|
|
554
|
+
readonly = input(false, { transform: booleanAttribute });
|
|
555
|
+
/**
|
|
556
|
+
* Limit criteria to the predefined ones.
|
|
557
|
+
*
|
|
558
|
+
* @defaultValue false
|
|
559
|
+
*/
|
|
560
|
+
strictCriterion = input(false, { transform: booleanAttribute });
|
|
561
|
+
/**
|
|
562
|
+
* Limit criterion options to the predefined ones. `[strictValue]`
|
|
563
|
+
* enforces `[strictCriterion]` to true automatically.
|
|
564
|
+
*
|
|
565
|
+
* @defaultValue false
|
|
566
|
+
*/
|
|
567
|
+
strictValue = input(false, { transform: booleanAttribute });
|
|
568
|
+
/**
|
|
569
|
+
* Limit criterion options to the predefined ones and prevent typing. `[onlySelectValue]`
|
|
570
|
+
* enforces `[strictValue]` and `[strictCriterion]` to true automatically.
|
|
571
|
+
*
|
|
572
|
+
* @defaultValue false
|
|
573
|
+
*/
|
|
574
|
+
onlySelectValue = input(false, { transform: booleanAttribute });
|
|
575
|
+
/**
|
|
576
|
+
* Custom debounce time for lazy loading of criteria data.
|
|
577
|
+
*
|
|
578
|
+
* @defaultValue 500
|
|
579
|
+
*/
|
|
580
|
+
lazyLoadingDebounceTime = input(500);
|
|
581
|
+
/**
|
|
582
|
+
* Custom debounce time (in mills) to delay the search emission.
|
|
583
|
+
* (Default is 0 as in most cases a users manually triggers a search.
|
|
584
|
+
* Recommended to increase a bit when using doSearchOnInputChange=true)
|
|
585
|
+
*
|
|
586
|
+
* @defaultValue 0
|
|
587
|
+
*/
|
|
588
|
+
searchDebounceTime = input(0);
|
|
589
|
+
/**
|
|
590
|
+
* The placeholder for input field.
|
|
591
|
+
*
|
|
592
|
+
* @defaultValue ''
|
|
593
|
+
*/
|
|
594
|
+
placeholder = input('');
|
|
595
|
+
/**
|
|
596
|
+
* @deprecated This property is unused and will be removed without a replacement.
|
|
597
|
+
*
|
|
598
|
+
* @defaultValue false
|
|
599
|
+
*/
|
|
600
|
+
showIcon = input(false, { transform: booleanAttribute });
|
|
601
|
+
/**
|
|
602
|
+
* @deprecated This property is unused and will be removed without a replacement.
|
|
603
|
+
* To provide translation for the new search button, use the {@link submitButtonLabel} input.
|
|
604
|
+
*
|
|
605
|
+
* @defaultValue
|
|
606
|
+
* ```
|
|
607
|
+
* $localize`:@@SI_FILTERED_SEARCH.SUBMIT:Apply search criteria`
|
|
608
|
+
* ```
|
|
609
|
+
*/
|
|
610
|
+
submitText = input($localize `:@@SI_FILTERED_SEARCH.SUBMIT:Apply search criteria`);
|
|
611
|
+
/**
|
|
612
|
+
* @deprecated Setting this property will make it harder for user to submit a search.
|
|
613
|
+
* Instead of using this property to preselect to most relevant option, sort the options by relevance.
|
|
614
|
+
*/
|
|
615
|
+
selectedCriteriaIndex = input();
|
|
616
|
+
/**
|
|
617
|
+
* Defines the number of criteria, criteria values and operators visible at once.
|
|
618
|
+
*
|
|
619
|
+
* @defaultValue 10
|
|
620
|
+
*/
|
|
621
|
+
optionsInScrollableView = input(10);
|
|
622
|
+
/**
|
|
623
|
+
* The current selected search criteria and entered search text.
|
|
624
|
+
*
|
|
625
|
+
* @defaultValue
|
|
626
|
+
* ```
|
|
627
|
+
* { criteria: [], value: '' }
|
|
628
|
+
* ```
|
|
629
|
+
*/
|
|
630
|
+
searchCriteria = model({ criteria: [], value: '' });
|
|
631
|
+
/**
|
|
632
|
+
* Predefine criteria options.
|
|
633
|
+
*
|
|
634
|
+
* @defaultValue []
|
|
635
|
+
*/
|
|
636
|
+
criteria = input([]);
|
|
637
|
+
/**
|
|
638
|
+
* Opt-in to search for each criterion only once.
|
|
639
|
+
*
|
|
640
|
+
* @defaultValue false
|
|
641
|
+
*/
|
|
642
|
+
exclusiveCriteria = input(false, {
|
|
643
|
+
transform: booleanAttribute
|
|
644
|
+
});
|
|
645
|
+
/**
|
|
646
|
+
* Limit the number of possible criteria. The default is undefined so that any number of criteria can be used.
|
|
647
|
+
* For example, setting the value to 1 let you only select one criterion that you need to remove before being
|
|
648
|
+
* able to set another one.
|
|
649
|
+
*
|
|
650
|
+
* @defaultValue undefined
|
|
651
|
+
*/
|
|
652
|
+
maxCriteria = input(undefined);
|
|
653
|
+
/**
|
|
654
|
+
* Defines the maximum options within one criterion. The default is 20 and 0 means unlimited.
|
|
655
|
+
*
|
|
656
|
+
* @defaultValue 20
|
|
657
|
+
*/
|
|
658
|
+
maxCriteriaOptions = input(20);
|
|
659
|
+
/**
|
|
660
|
+
* Search input aria label, Needed by a11y
|
|
661
|
+
*
|
|
662
|
+
* @defaultValue
|
|
663
|
+
* ```
|
|
664
|
+
* $localize`:@@SI_FILTERED_SEARCH.SEARCH:Search`
|
|
665
|
+
* ```
|
|
666
|
+
*/
|
|
667
|
+
searchLabel = input($localize `:@@SI_FILTERED_SEARCH.SEARCH:Search`);
|
|
668
|
+
/**
|
|
669
|
+
* Clear button aria label. Needed for a11y
|
|
670
|
+
*
|
|
671
|
+
* @defaultValue
|
|
672
|
+
* ```
|
|
673
|
+
* $localize`:@@SI_FILTERED_SEARCH.CLEAR:Clear`
|
|
674
|
+
* ```
|
|
675
|
+
*/
|
|
676
|
+
clearButtonLabel = input($localize `:@@SI_FILTERED_SEARCH.CLEAR:Clear`);
|
|
677
|
+
/**
|
|
678
|
+
* The accessible label of the search button.
|
|
679
|
+
*
|
|
680
|
+
* @defaultValue
|
|
681
|
+
* ```
|
|
682
|
+
* $localize`:@@SI_FILTERED_SEARCH.SUBMIT_BUTTON:Submit search`
|
|
683
|
+
* ```
|
|
684
|
+
*/
|
|
685
|
+
submitButtonLabel = input($localize `:@@SI_FILTERED_SEARCH.SUBMIT_BUTTON:Submit search`);
|
|
686
|
+
/**
|
|
687
|
+
* Items count text appended to the count in case of multi-selection of values.
|
|
688
|
+
* Translation key, `{{itemCount}}` in the translation will be replaced with the actual value.
|
|
689
|
+
*
|
|
690
|
+
* @defaultValue ''
|
|
691
|
+
*/
|
|
692
|
+
itemCountText = input('');
|
|
693
|
+
/**
|
|
694
|
+
* Color variant to determine component background
|
|
695
|
+
*
|
|
696
|
+
* @defaultValue 'base-1'
|
|
697
|
+
*/
|
|
698
|
+
colorVariant = input('base-1');
|
|
699
|
+
/**
|
|
700
|
+
* Text or translate key for multi selection pills text.
|
|
701
|
+
*
|
|
702
|
+
* @deprecated Use the new input {@link itemCountText} instead.
|
|
703
|
+
*
|
|
704
|
+
* @defaultValue
|
|
705
|
+
* ```
|
|
706
|
+
* $localize`:@@SI_FILTERED_SEARCH.ITEMS:items`
|
|
707
|
+
* ```
|
|
708
|
+
*/
|
|
709
|
+
items = input($localize `:@@SI_FILTERED_SEARCH.ITEMS:items`);
|
|
710
|
+
/**
|
|
711
|
+
* Disables the free text search to only use the criterion for filtering.
|
|
712
|
+
*
|
|
713
|
+
* @defaultValue false
|
|
714
|
+
*/
|
|
715
|
+
disableFreeTextSearch = input(false, { transform: booleanAttribute });
|
|
716
|
+
/**
|
|
717
|
+
* Limit on the number of criteria/criteria value to be displayed by the typeahead
|
|
718
|
+
*
|
|
719
|
+
* @defaultValue 20
|
|
720
|
+
*/
|
|
721
|
+
typeaheadOptionsLimit = input(20);
|
|
722
|
+
/**
|
|
723
|
+
* @deprecated This property is unused and will be removed without a replacement.
|
|
724
|
+
*
|
|
725
|
+
* @defaultValue
|
|
726
|
+
* ```
|
|
727
|
+
* $localize`:@@SI_FILTERED_SEARCH.NO_MATCHING_CRITERIA:No matching criteria`
|
|
728
|
+
* ```
|
|
729
|
+
*/
|
|
730
|
+
noMatchingCriteriaText = input($localize `:@@SI_FILTERED_SEARCH.NO_MATCHING_CRITERIA:No matching criteria`);
|
|
731
|
+
/**
|
|
732
|
+
* By default, the Filtered Search will treat `:` as a special character
|
|
733
|
+
* to submit the current input in the freetext and immediately create a criterion.
|
|
734
|
+
* Use this input to disable this behavior.
|
|
735
|
+
*
|
|
736
|
+
* @defaultValue false
|
|
737
|
+
*/
|
|
738
|
+
disableSelectionByColonAndSemicolon = input(false, { transform: booleanAttribute });
|
|
739
|
+
/**
|
|
740
|
+
* The interceptor is called when the list of criteria is shown as soon as the user starts typing in the input field.
|
|
741
|
+
* The interceptor's {@link DisplayedCriteriaEventArgs.allow} method can be used to filter the list of displayed criteria.
|
|
742
|
+
*
|
|
743
|
+
* **Note:** The interceptor is called as long as the {@link searchCriteria} does not exceed {@link maxCriteria}.
|
|
744
|
+
* Further, the interceptor is not called when using the {@link lazyCriterionProvider}.
|
|
745
|
+
*
|
|
746
|
+
* @example
|
|
747
|
+
* ```
|
|
748
|
+
* <si-filtered-search
|
|
749
|
+
* [criteria]="[{ name: 'foo', label: 'Foo' }, { name: 'bar', label: 'Bar' }]"
|
|
750
|
+
* (interceptDisplayedCriteria)="$event.allow(
|
|
751
|
+
* $event.searchCriteria.criteria.some(s => s.name === 'foo')
|
|
752
|
+
* ? $event.criteria.filter(c => c !== 'foo')
|
|
753
|
+
* : $event.criteria
|
|
754
|
+
* )">
|
|
755
|
+
* </si-filtered-search>
|
|
756
|
+
* ```
|
|
757
|
+
*/
|
|
758
|
+
interceptDisplayedCriteria = output();
|
|
759
|
+
freeTextInputElement = viewChild.required('freeTextInputElement');
|
|
760
|
+
scrollContainer = viewChild.required('scrollContainer', { read: ElementRef });
|
|
761
|
+
valueComponents = viewChildren(SiFilteredSearchValueComponent);
|
|
762
|
+
dataSource;
|
|
763
|
+
autoEditCriteria = false;
|
|
764
|
+
values = [];
|
|
765
|
+
searchValue = '';
|
|
766
|
+
/** Internal criteria model */
|
|
767
|
+
internalCriterionDefinitions = [];
|
|
768
|
+
icons = addIcons({ elementCancel, elementSearch });
|
|
769
|
+
/** Used to trigger a renewed search */
|
|
770
|
+
typeaheadInputChange = new BehaviorSubject('');
|
|
771
|
+
/** Used to debounce the Search emissions */
|
|
772
|
+
searchEmitQueue = new Subject();
|
|
773
|
+
destroySubscriptions = new Subject();
|
|
774
|
+
cdRef = inject(ChangeDetectorRef);
|
|
775
|
+
translateService = inject(SiTranslateService);
|
|
776
|
+
locale = inject(LOCALE_ID).toString();
|
|
777
|
+
/**
|
|
778
|
+
* The cache is used to control when the interceptDisplayedCriteria event needs to be called.
|
|
779
|
+
* Every time a criteria gain the focus we have to reset the cache to call the interceptor.
|
|
780
|
+
*/
|
|
781
|
+
allowedCriteriaCache;
|
|
782
|
+
// Angular also calls ngOnChanges if we emitted a change and then two-way-databinding writes back our own change.
|
|
783
|
+
// We use this to ensure that we do not write our own change back to the input.
|
|
784
|
+
lastEmittedSearchCriteria;
|
|
785
|
+
isStrictOrOnlySelectValue = computed(() => {
|
|
786
|
+
return this.strictValue() || this.onlySelectValue();
|
|
787
|
+
});
|
|
788
|
+
strictCriterionOrValue = computed(() => {
|
|
789
|
+
return this.strictCriterion() || this.isStrictOrOnlySelectValue();
|
|
790
|
+
});
|
|
791
|
+
lazyLoadedCriteria = signal(undefined);
|
|
792
|
+
loadedCriteria = computed(() => {
|
|
793
|
+
const lazyLoadedCriteria = this.lazyLoadedCriteria();
|
|
794
|
+
if (lazyLoadedCriteria) {
|
|
795
|
+
return lazyLoadedCriteria;
|
|
796
|
+
}
|
|
797
|
+
else {
|
|
798
|
+
return this.criteria() ?? [];
|
|
799
|
+
}
|
|
800
|
+
});
|
|
801
|
+
isReadOnly = computed(() => this.readonly() || this.disabled());
|
|
802
|
+
constructor() {
|
|
803
|
+
this.dataSource = this.typeaheadInputChange.pipe(switchMap(value => {
|
|
804
|
+
if (this.lazyCriterionProvider()) {
|
|
805
|
+
return this.lazyCriterionProvider()(value, this.searchCriteria()).pipe(debounceTime(this.lazyLoadingDebounceTime()), map(result => this.getCriteriaToDisplayFromSubscription(result)));
|
|
806
|
+
}
|
|
807
|
+
else {
|
|
808
|
+
return of(this.getFilteredTypeaheadCriteria(value));
|
|
809
|
+
}
|
|
810
|
+
}), switchMap(criteria => {
|
|
811
|
+
return this.translateService.translateAsync(criteria.map(c => c.label)).pipe(map(translations => {
|
|
812
|
+
criteria.forEach(c => (c.translatedLabel = translations[c.label] ?? c.label ?? c.name));
|
|
813
|
+
return criteria;
|
|
814
|
+
}));
|
|
815
|
+
}));
|
|
816
|
+
}
|
|
817
|
+
ngOnChanges(changes) {
|
|
818
|
+
if (changes.criteria) {
|
|
819
|
+
this.initCriteria();
|
|
820
|
+
}
|
|
821
|
+
if ((changes.searchCriteria && this.searchCriteria() !== this.lastEmittedSearchCriteria) ||
|
|
822
|
+
changes.criteria) {
|
|
823
|
+
this.initValue();
|
|
824
|
+
// Update typeahead since the criteria input can change while the free text input is focused.
|
|
825
|
+
// This is necessary since the criteria are set as a result of an API call response.
|
|
826
|
+
this.typeaheadInputChange.next(this.freeTextInputElement().nativeElement.value ?? '');
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
ngOnInit() {
|
|
830
|
+
if (this.strictCriterionOrValue() && this.internalCriterionDefinitions.length === 0) {
|
|
831
|
+
throw new Error('strict criterion mode activated without predefined criteria!');
|
|
832
|
+
}
|
|
833
|
+
this.searchEmitQueue
|
|
834
|
+
.pipe(debounceTime(this.searchDebounceTime()), takeUntil(this.destroySubscriptions))
|
|
835
|
+
.subscribe(searchCriteria => this.doSearch.emit(searchCriteria));
|
|
836
|
+
}
|
|
837
|
+
ngOnDestroy() {
|
|
838
|
+
this.destroySubscriptions.next(true);
|
|
839
|
+
this.destroySubscriptions.unsubscribe();
|
|
840
|
+
}
|
|
841
|
+
initCriteria() {
|
|
842
|
+
this.internalCriterionDefinitions = this.loadedCriteria().map(c => toInternalCriteria(c));
|
|
843
|
+
}
|
|
844
|
+
initValue() {
|
|
845
|
+
this.autoEditCriteria = false;
|
|
846
|
+
this.searchValue = this.searchCriteria()?.value ?? '';
|
|
847
|
+
this.values =
|
|
848
|
+
this.searchCriteria()?.criteria.map(c => {
|
|
849
|
+
const config = this.internalCriterionDefinitions.find(ic => ic.name === c.name) ??
|
|
850
|
+
{
|
|
851
|
+
name: c.name,
|
|
852
|
+
label: c.label ?? c.name,
|
|
853
|
+
translatedLabel: c.label ?? c.name
|
|
854
|
+
};
|
|
855
|
+
let value = c.value ?? '';
|
|
856
|
+
// Fix input, in case the user provided the value as string for the multi-select use case.
|
|
857
|
+
if (config.multiSelect && typeof value === 'string') {
|
|
858
|
+
value = value !== '' ? [value] : [];
|
|
859
|
+
}
|
|
860
|
+
let dateValue;
|
|
861
|
+
if (config.validationType === 'date' || config.validationType === 'date-time') {
|
|
862
|
+
dateValue = value ? new Date(value.toString()) : new Date();
|
|
863
|
+
value ??= getISODateString(dateValue, config.validationType === 'date' || config.datepickerConfig?.disabledTime
|
|
864
|
+
? 'date'
|
|
865
|
+
: 'date-time', this.locale);
|
|
866
|
+
}
|
|
867
|
+
return {
|
|
868
|
+
value: {
|
|
869
|
+
name: c.name,
|
|
870
|
+
value,
|
|
871
|
+
...(dateValue ? { dateValue } : {}),
|
|
872
|
+
...(c.operator ? { operator: c.operator } : {})
|
|
873
|
+
},
|
|
874
|
+
config
|
|
875
|
+
};
|
|
876
|
+
}) ?? [];
|
|
877
|
+
this.lastEmittedSearchCriteria = this.searchCriteria();
|
|
878
|
+
this.allowedCriteriaCache = undefined;
|
|
879
|
+
}
|
|
880
|
+
/**
|
|
881
|
+
* Deletes all currently selected criteria and effectively resets the filtered search.
|
|
882
|
+
*/
|
|
883
|
+
deleteAllCriteria(event) {
|
|
884
|
+
if (this.isReadOnly()) {
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
887
|
+
event?.stopPropagation();
|
|
888
|
+
// Reset search criteria
|
|
889
|
+
this.values = [];
|
|
890
|
+
this.searchValue = '';
|
|
891
|
+
this.emitChangeEvent();
|
|
892
|
+
this.allowedCriteriaCache = undefined;
|
|
893
|
+
this.typeaheadInputChange.next(this.searchValue);
|
|
894
|
+
this.submit();
|
|
895
|
+
}
|
|
896
|
+
deleteCriterion(criterion, index, event) {
|
|
897
|
+
if (this.isReadOnly()) {
|
|
898
|
+
return;
|
|
899
|
+
}
|
|
900
|
+
this.values = this.values.filter(v => v.value !== criterion);
|
|
901
|
+
this.emitChangeEvent();
|
|
902
|
+
this.allowedCriteriaCache = undefined;
|
|
903
|
+
if (this.values.length !== index) {
|
|
904
|
+
this.valueComponents()[index + 1].edit('value');
|
|
905
|
+
}
|
|
906
|
+
else {
|
|
907
|
+
this.freeTextInputElement().nativeElement.focus();
|
|
908
|
+
}
|
|
909
|
+
this.typeaheadInputChange.next(this.searchValue);
|
|
910
|
+
if (event?.triggerSearch) {
|
|
911
|
+
this.submit();
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
submit() {
|
|
915
|
+
if (!this.doSearchOnInputChange()) {
|
|
916
|
+
this.doSearch.emit(this.searchCriteria());
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
typeaheadOnSelectCriterion(event) {
|
|
920
|
+
const criterion = event;
|
|
921
|
+
// Timeout is needed otherwise siTypeahead will overwrite the value
|
|
922
|
+
setTimeout(() => {
|
|
923
|
+
// Removes the focus border before creating a new criterion to prevent the impression of jumping content.
|
|
924
|
+
this.freeTextInputElement().nativeElement.blur();
|
|
925
|
+
this.searchValue = '';
|
|
926
|
+
this.addCriterion(criterion);
|
|
927
|
+
// The user selected a criterion so we remove the free text search value and add the criterion.
|
|
928
|
+
this.allowedCriteriaCache = undefined;
|
|
929
|
+
this.typeaheadInputChange.next('');
|
|
930
|
+
}, 0);
|
|
931
|
+
}
|
|
932
|
+
validateCriterionLabel(criterion) {
|
|
933
|
+
if (!this.strictCriterionOrValue()) {
|
|
934
|
+
return true;
|
|
935
|
+
}
|
|
936
|
+
return this.internalCriterionDefinitions.includes(criterion);
|
|
937
|
+
}
|
|
938
|
+
/**
|
|
939
|
+
* Converts the internally used data model to the external model.
|
|
940
|
+
* In case options for Criterion is Option[] map to the value from the label.
|
|
941
|
+
*/
|
|
942
|
+
convertToExternalModel() {
|
|
943
|
+
const correctedCriteria = {
|
|
944
|
+
value: this.searchValue,
|
|
945
|
+
criteria: this.values.map(v => v.value)
|
|
946
|
+
};
|
|
947
|
+
if (this.disableFreeTextSearch()) {
|
|
948
|
+
correctedCriteria.value = '';
|
|
949
|
+
}
|
|
950
|
+
return correctedCriteria;
|
|
951
|
+
}
|
|
952
|
+
addCriterion(config, value) {
|
|
953
|
+
if (config.multiSelect) {
|
|
954
|
+
this.values = [...this.values, { value: { value: [], name: config.name }, config }];
|
|
955
|
+
}
|
|
956
|
+
else if (config.validationType === 'date' || config.validationType === 'date-time') {
|
|
957
|
+
this.values = [
|
|
958
|
+
...this.values,
|
|
959
|
+
{
|
|
960
|
+
value: {
|
|
961
|
+
dateValue: new Date(),
|
|
962
|
+
value: getISODateString(new Date(), config.validationType, this.locale),
|
|
963
|
+
name: config.name
|
|
964
|
+
},
|
|
965
|
+
config
|
|
966
|
+
}
|
|
967
|
+
];
|
|
968
|
+
}
|
|
969
|
+
else {
|
|
970
|
+
this.values = [...this.values, { value: { value: value ?? '', name: config.name }, config }];
|
|
971
|
+
}
|
|
972
|
+
this.autoEditCriteria = true;
|
|
973
|
+
this.emitChangeEvent();
|
|
974
|
+
}
|
|
975
|
+
/**
|
|
976
|
+
* Get criteria list to be shown in typeahead.
|
|
977
|
+
* @param token - input field value.
|
|
978
|
+
* @returns list of criteria to be shown in typeahead.
|
|
979
|
+
*/
|
|
980
|
+
getFilteredTypeaheadCriteria(token) {
|
|
981
|
+
if (this.maxCriteria() === undefined || this.values.length < this.maxCriteria()) {
|
|
982
|
+
const allowedCriteria = !this.exclusiveCriteria()
|
|
983
|
+
? this.internalCriterionDefinitions
|
|
984
|
+
: differenceByName(this.internalCriterionDefinitions, this.values);
|
|
985
|
+
if (allowedCriteria.length > 0 && !this.allowedCriteriaCache) {
|
|
986
|
+
// Call interceptor to allow applications to customize the list of available criteria
|
|
987
|
+
const available = allowedCriteria.map(c => c.name);
|
|
988
|
+
// Ensure that all entries are allowed in case the consumer doesn't use the allow callback
|
|
989
|
+
this.allowedCriteriaCache = available;
|
|
990
|
+
this.interceptDisplayedCriteria.emit({
|
|
991
|
+
criteria: available,
|
|
992
|
+
searchCriteria: this.convertToExternalModel(),
|
|
993
|
+
allow: criteriaNamesToDisplay => {
|
|
994
|
+
if (criteriaNamesToDisplay) {
|
|
995
|
+
this.allowedCriteriaCache = criteriaNamesToDisplay;
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
});
|
|
999
|
+
}
|
|
1000
|
+
return allowedCriteria.filter(c => this.allowedCriteriaCache?.includes(c.name));
|
|
1001
|
+
}
|
|
1002
|
+
else {
|
|
1003
|
+
return [];
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
getCriteriaToDisplayFromSubscription(result) {
|
|
1007
|
+
this.lazyLoadedCriteria.set(result);
|
|
1008
|
+
this.internalCriterionDefinitions = this.loadedCriteria().map(c => toInternalCriteria(c));
|
|
1009
|
+
// It's necessary to update the criteria configuration since consuming applications may change the options.
|
|
1010
|
+
// A common case are criteria which depend on each other like Country, Site and Building. If the user change
|
|
1011
|
+
// the site the building options might become invalid.
|
|
1012
|
+
// KEEP THE REFERENCE, otherwise CD is broken
|
|
1013
|
+
this.values = this.values.map(v => Object.assign(v, {
|
|
1014
|
+
value: v.value,
|
|
1015
|
+
config: this.internalCriterionDefinitions.find(ic => ic.name === v.config.name) ?? v.config
|
|
1016
|
+
}));
|
|
1017
|
+
if (this.maxCriteria() === undefined || this.values.length < this.maxCriteria()) {
|
|
1018
|
+
return !this.exclusiveCriteria()
|
|
1019
|
+
? this.internalCriterionDefinitions
|
|
1020
|
+
: differenceByName(this.internalCriterionDefinitions, this.values);
|
|
1021
|
+
}
|
|
1022
|
+
return [];
|
|
1023
|
+
}
|
|
1024
|
+
freeTextFocus() {
|
|
1025
|
+
// Ensure that the free text input is fully visible in the scroll container
|
|
1026
|
+
const scrollDirection = isRTL() ? -1 : 1;
|
|
1027
|
+
const position = scrollDirection * this.scrollContainer().nativeElement.scrollWidth;
|
|
1028
|
+
this.scrollContainer().nativeElement.scrollLeft = position;
|
|
1029
|
+
this.typeaheadInputChange.next(this.freeTextInputElement().nativeElement.value);
|
|
1030
|
+
}
|
|
1031
|
+
freeTextBackspace(event) {
|
|
1032
|
+
if (!event.target.value) {
|
|
1033
|
+
// edit last criterion if a user presses backspace in empty search input.
|
|
1034
|
+
const valueComponents = this.valueComponents();
|
|
1035
|
+
if (valueComponents.length) {
|
|
1036
|
+
valueComponents[valueComponents.length - 1].edit('value');
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
freeTextInput(event) {
|
|
1041
|
+
const value = event.target.value;
|
|
1042
|
+
const match = value.match(SiFilteredSearchComponent.criterionRegex);
|
|
1043
|
+
if (!this.disableSelectionByColonAndSemicolon() && !this.onlySelectValue() && match) {
|
|
1044
|
+
const criterionName = match[1];
|
|
1045
|
+
if (this.searchValue === '') {
|
|
1046
|
+
// The value was empty before, so we must make angular detect a change here.
|
|
1047
|
+
// Otherwise, the entire value which was pasted will remain in the input.
|
|
1048
|
+
// This happens if the user pasts something like: 'key:value'
|
|
1049
|
+
this.searchValue = value;
|
|
1050
|
+
this.cdRef.detectChanges();
|
|
1051
|
+
}
|
|
1052
|
+
this.searchValue = '';
|
|
1053
|
+
this.allowedCriteriaCache = undefined;
|
|
1054
|
+
const nameLowerCase = criterionName.toLocaleLowerCase();
|
|
1055
|
+
const criterion = this.internalCriterionDefinitions.find(ic => ic.translatedLabel.toLocaleLowerCase() === nameLowerCase) ?? {
|
|
1056
|
+
name: criterionName,
|
|
1057
|
+
label: criterionName,
|
|
1058
|
+
translatedLabel: criterionName
|
|
1059
|
+
};
|
|
1060
|
+
this.typeaheadInputChange.next('');
|
|
1061
|
+
this.addCriterion(criterion, match[2]);
|
|
1062
|
+
}
|
|
1063
|
+
else {
|
|
1064
|
+
this.searchValue = value;
|
|
1065
|
+
if (!this.disableFreeTextSearch()) {
|
|
1066
|
+
this.emitChangeEvent();
|
|
1067
|
+
}
|
|
1068
|
+
this.typeaheadInputChange.next(value);
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
valueChange(value, criterion) {
|
|
1072
|
+
criterion.value = value;
|
|
1073
|
+
this.emitChangeEvent();
|
|
1074
|
+
}
|
|
1075
|
+
focusNext(index, event) {
|
|
1076
|
+
const next = this.valueComponents()[index + 1];
|
|
1077
|
+
if (next) {
|
|
1078
|
+
next.edit();
|
|
1079
|
+
}
|
|
1080
|
+
else {
|
|
1081
|
+
this.searchValue = event?.freeText ?? this.searchValue;
|
|
1082
|
+
this.freeTextInputElement().nativeElement.focus();
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
updateAndEmitSearchCriteria() {
|
|
1086
|
+
this.searchCriteria.set(this.convertToExternalModel());
|
|
1087
|
+
this.lastEmittedSearchCriteria = this.searchCriteria();
|
|
1088
|
+
}
|
|
1089
|
+
emitChangeEvent() {
|
|
1090
|
+
this.updateAndEmitSearchCriteria();
|
|
1091
|
+
if (this.doSearchOnInputChange()) {
|
|
1092
|
+
this.searchEmitQueue.next(this.searchCriteria());
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: SiFilteredSearchComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1096
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.0.6", type: SiFilteredSearchComponent, isStandalone: true, selector: "si-filtered-search", inputs: { doSearchOnInputChange: { classPropertyName: "doSearchOnInputChange", publicName: "doSearchOnInputChange", isSignal: true, isRequired: false, transformFunction: null }, lazyCriterionProvider: { classPropertyName: "lazyCriterionProvider", publicName: "lazyCriterionProvider", isSignal: true, isRequired: false, transformFunction: null }, lazyValueProvider: { classPropertyName: "lazyValueProvider", publicName: "lazyValueProvider", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, strictCriterion: { classPropertyName: "strictCriterion", publicName: "strictCriterion", isSignal: true, isRequired: false, transformFunction: null }, strictValue: { classPropertyName: "strictValue", publicName: "strictValue", isSignal: true, isRequired: false, transformFunction: null }, onlySelectValue: { classPropertyName: "onlySelectValue", publicName: "onlySelectValue", isSignal: true, isRequired: false, transformFunction: null }, lazyLoadingDebounceTime: { classPropertyName: "lazyLoadingDebounceTime", publicName: "lazyLoadingDebounceTime", isSignal: true, isRequired: false, transformFunction: null }, searchDebounceTime: { classPropertyName: "searchDebounceTime", publicName: "searchDebounceTime", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, showIcon: { classPropertyName: "showIcon", publicName: "showIcon", isSignal: true, isRequired: false, transformFunction: null }, submitText: { classPropertyName: "submitText", publicName: "submitText", isSignal: true, isRequired: false, transformFunction: null }, selectedCriteriaIndex: { classPropertyName: "selectedCriteriaIndex", publicName: "selectedCriteriaIndex", isSignal: true, isRequired: false, transformFunction: null }, optionsInScrollableView: { classPropertyName: "optionsInScrollableView", publicName: "optionsInScrollableView", isSignal: true, isRequired: false, transformFunction: null }, searchCriteria: { classPropertyName: "searchCriteria", publicName: "searchCriteria", isSignal: true, isRequired: false, transformFunction: null }, criteria: { classPropertyName: "criteria", publicName: "criteria", isSignal: true, isRequired: false, transformFunction: null }, exclusiveCriteria: { classPropertyName: "exclusiveCriteria", publicName: "exclusiveCriteria", isSignal: true, isRequired: false, transformFunction: null }, maxCriteria: { classPropertyName: "maxCriteria", publicName: "maxCriteria", isSignal: true, isRequired: false, transformFunction: null }, maxCriteriaOptions: { classPropertyName: "maxCriteriaOptions", publicName: "maxCriteriaOptions", isSignal: true, isRequired: false, transformFunction: null }, searchLabel: { classPropertyName: "searchLabel", publicName: "searchLabel", isSignal: true, isRequired: false, transformFunction: null }, clearButtonLabel: { classPropertyName: "clearButtonLabel", publicName: "clearButtonLabel", isSignal: true, isRequired: false, transformFunction: null }, submitButtonLabel: { classPropertyName: "submitButtonLabel", publicName: "submitButtonLabel", isSignal: true, isRequired: false, transformFunction: null }, itemCountText: { classPropertyName: "itemCountText", publicName: "itemCountText", isSignal: true, isRequired: false, transformFunction: null }, colorVariant: { classPropertyName: "colorVariant", publicName: "colorVariant", isSignal: true, isRequired: false, transformFunction: null }, items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, disableFreeTextSearch: { classPropertyName: "disableFreeTextSearch", publicName: "disableFreeTextSearch", isSignal: true, isRequired: false, transformFunction: null }, typeaheadOptionsLimit: { classPropertyName: "typeaheadOptionsLimit", publicName: "typeaheadOptionsLimit", isSignal: true, isRequired: false, transformFunction: null }, noMatchingCriteriaText: { classPropertyName: "noMatchingCriteriaText", publicName: "noMatchingCriteriaText", isSignal: true, isRequired: false, transformFunction: null }, disableSelectionByColonAndSemicolon: { classPropertyName: "disableSelectionByColonAndSemicolon", publicName: "disableSelectionByColonAndSemicolon", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { doSearch: "doSearch", searchCriteria: "searchCriteriaChange", interceptDisplayedCriteria: "interceptDisplayedCriteria" }, host: { properties: { "class.disabled": "disabled()", "class.dark-background": "colorVariant() === 'base-0'" } }, viewQueries: [{ propertyName: "freeTextInputElement", first: true, predicate: ["freeTextInputElement"], descendants: true, isSignal: true }, { propertyName: "scrollContainer", first: true, predicate: ["scrollContainer"], descendants: true, read: ElementRef, isSignal: true }, { propertyName: "valueComponents", predicate: SiFilteredSearchValueComponent, descendants: true, isSignal: true }], usesOnChanges: true, ngImport: i0, template: "<div\n #scrollContainer\n class=\"search-container d-flex align-items-center overflow-auto py-2 w-100 ps-2 pe-2\"\n>\n @for (criterion of values; track criterion) {\n <si-filtered-search-value\n [editOnCreation]=\"autoEditCriteria\"\n [definition]=\"criterion.config\"\n [value]=\"criterion.value\"\n [invalidCriterion]=\"!validateCriterionLabel(criterion.config)\"\n [disabled]=\"disabled()\"\n [items]=\"items()\"\n [clearButtonLabel]=\"clearButtonLabel()\"\n [disableSelectionByColonAndSemicolon]=\"disableSelectionByColonAndSemicolon()\"\n [isStrictOrOnlySelectValue]=\"isStrictOrOnlySelectValue()\"\n [itemCountText]=\"itemCountText()\"\n [lazyValueProvider]=\"lazyValueProvider()\"\n [maxCriteriaOptions]=\"maxCriteriaOptions()\"\n [optionsInScrollableView]=\"optionsInScrollableView()\"\n [readonly]=\"readonly()\"\n [searchLabel]=\"searchLabel()\"\n [onlySelectValue]=\"onlySelectValue()\"\n [searchDebounceTime]=\"searchDebounceTime()\"\n (submitCriterion)=\"focusNext($index, $event)\"\n (deleteCriterion)=\"deleteCriterion(criterion.value, $index, $event)\"\n (valueChange)=\"valueChange($event, criterion)\"\n />\n }\n <!-- criterion input -->\n <input\n #freeTextInputElement\n type=\"text\"\n class=\"value-input ps-2 me-2 flex-grow-1\"\n typeaheadOptionField=\"translatedLabel\"\n [siTypeahead]=\"dataSource\"\n [typeaheadMinLength]=\"readonly() ? 1 : 0\"\n [typeaheadOptionsLimit]=\"typeaheadOptionsLimit()\"\n [typeaheadScrollable]=\"true\"\n [typeaheadOptionsInScrollableView]=\"optionsInScrollableView()\"\n [typeaheadAutoSelectIndex]=\"selectedCriteriaIndex() ?? (searchValue ? 0 : -1)\"\n [readonly]=\"readonly()\"\n [disabled]=\"disabled()\"\n [placeholder]=\"placeholder()\"\n [attr.aria-label]=\"searchLabel() | translate\"\n [value]=\"searchValue\"\n (focus)=\"freeTextFocus()\"\n (input)=\"freeTextInput($event)\"\n (keydown.backspace)=\"freeTextBackspace($event)\"\n (keydown.enter)=\"submit()\"\n (typeaheadOnSelect)=\"typeaheadOnSelectCriterion($event.option)\"\n />\n</div>\n<!-- clear all searchCriteria -->\n@if ((values.length || searchValue.length) && !readonly()) {\n <div class=\"clear-button-container\">\n <button\n type=\"button\"\n class=\"btn btn-circle btn-xs btn-ghost mx-2\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"clearButtonLabel() | translate\"\n (click)=\"deleteAllCriteria($event)\"\n >\n <si-icon-next [icon]=\"icons.elementCancel\" />\n </button>\n </div>\n}\n\n@if (!disabled()) {\n <button\n type=\"button\"\n class=\"search-button btn btn-circle btn-xs btn-secondary focus-inside\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"submitButtonLabel() | translate\"\n (click)=\"submit()\"\n >\n <si-icon-next [icon]=\"icons.elementSearch\" />\n </button>\n}\n", styles: [":host{--search-bar-icon-color: var(--element-text-primary);--input-background-color: var(--element-base-1);--input-background-hover-color: var(--element-base-1);--filter-search-background-color: var(--element-base-1);--filter-pill-background-color: var(--element-base-0);display:flex;align-items:center;line-height:24px;min-inline-size:0}:host.dark-background{--input-background-color: var(--element-base-0);--input-background-hover-color: var(--element-ui-4);--filter-search-background-color: var(--element-base-0);--filter-pill-background-color: var(--element-base-1)}:host.disabled{color:var(--element-text-disabled)}:host.disabled input,:host.disabled ::placeholder{color:var(--element-text-disabled)}:host si-filtered-search-value+input::placeholder{color:transparent}.search-container{border-start-start-radius:4px;border-end-start-radius:4px;background-color:var(--filter-search-background-color);gap:4px}input{background:transparent;border:0;padding-block:0}input:hover::placeholder{opacity:1}:host(.disabled) input::placeholder{opacity:var(--element-action-disabled-opacity)}.clear-button-container{display:flex;align-items:flex-start;align-self:stretch;padding-block:4px;background-color:var(--filter-search-background-color)}.search-button{align-self:stretch;background-color:var(--filter-search-background-color);border:0;border-radius:0;border-end-end-radius:4px;border-start-end-radius:4px;margin-block:0;margin-inline:1px 0;block-size:auto;inline-size:auto;padding-inline:4px!important}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "component", type: SiIconNextComponent, selector: "si-icon-next", inputs: ["icon"] }, { kind: "directive", type: SiTypeaheadDirective, selector: "[siTypeahead]", inputs: ["siTypeahead", "typeaheadProcess", "typeaheadScrollable", "typeaheadOptionsInScrollableView", "typeaheadOptionsLimit", "typeaheadScrollableAdditionalHeight", "typeaheadAutoSelectIndex", "typeaheadCloseOnEsc", "typeaheadClearValueOnSelect", "typeaheadWaitMs", "typeaheadMinLength", "typeaheadOptionField", "typeaheadMultiSelect", "typeaheadTokenize", "typeaheadMatchAllTokens", "typeaheadItemTemplate", "typeaheadSkipSortingMatches", "typeaheadAutocompleteListLabel", "typeaheadFullWidth"], outputs: ["typeaheadOnInput", "typeaheadOnSelect", "typeaheadOnMultiselectClose", "typeaheadOnFullMatch", "typeaheadClosed", "typeaheadOpenChange"], exportAs: ["si-typeahead"] }, { kind: "ngmodule", type: SiTranslateModule }, { kind: "pipe", type: i2.SiTranslatePipe, name: "translate" }, { kind: "component", type: SiFilteredSearchValueComponent, selector: "si-filtered-search-value", inputs: ["value", "definition", "disabled", "readonly", "onlySelectValue", "maxCriteriaOptions", "optionsInScrollableView", "clearButtonLabel", "lazyValueProvider", "searchDebounceTime", "itemCountText", "items", "disableSelectionByColonAndSemicolon", "searchLabel", "invalidCriterion", "isStrictOrOnlySelectValue", "editOnCreation"], outputs: ["valueChange", "deleteCriterion", "submitCriterion"] }] });
|
|
1097
|
+
}
|
|
1098
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: SiFilteredSearchComponent, decorators: [{
|
|
1099
|
+
type: Component,
|
|
1100
|
+
args: [{ selector: 'si-filtered-search', imports: [
|
|
1101
|
+
FormsModule,
|
|
1102
|
+
SiIconNextComponent,
|
|
1103
|
+
SiTypeaheadDirective,
|
|
1104
|
+
SiTranslateModule,
|
|
1105
|
+
SiFilteredSearchValueComponent
|
|
1106
|
+
], host: {
|
|
1107
|
+
'[class.disabled]': 'disabled()',
|
|
1108
|
+
'[class.dark-background]': "colorVariant() === 'base-0'"
|
|
1109
|
+
}, template: "<div\n #scrollContainer\n class=\"search-container d-flex align-items-center overflow-auto py-2 w-100 ps-2 pe-2\"\n>\n @for (criterion of values; track criterion) {\n <si-filtered-search-value\n [editOnCreation]=\"autoEditCriteria\"\n [definition]=\"criterion.config\"\n [value]=\"criterion.value\"\n [invalidCriterion]=\"!validateCriterionLabel(criterion.config)\"\n [disabled]=\"disabled()\"\n [items]=\"items()\"\n [clearButtonLabel]=\"clearButtonLabel()\"\n [disableSelectionByColonAndSemicolon]=\"disableSelectionByColonAndSemicolon()\"\n [isStrictOrOnlySelectValue]=\"isStrictOrOnlySelectValue()\"\n [itemCountText]=\"itemCountText()\"\n [lazyValueProvider]=\"lazyValueProvider()\"\n [maxCriteriaOptions]=\"maxCriteriaOptions()\"\n [optionsInScrollableView]=\"optionsInScrollableView()\"\n [readonly]=\"readonly()\"\n [searchLabel]=\"searchLabel()\"\n [onlySelectValue]=\"onlySelectValue()\"\n [searchDebounceTime]=\"searchDebounceTime()\"\n (submitCriterion)=\"focusNext($index, $event)\"\n (deleteCriterion)=\"deleteCriterion(criterion.value, $index, $event)\"\n (valueChange)=\"valueChange($event, criterion)\"\n />\n }\n <!-- criterion input -->\n <input\n #freeTextInputElement\n type=\"text\"\n class=\"value-input ps-2 me-2 flex-grow-1\"\n typeaheadOptionField=\"translatedLabel\"\n [siTypeahead]=\"dataSource\"\n [typeaheadMinLength]=\"readonly() ? 1 : 0\"\n [typeaheadOptionsLimit]=\"typeaheadOptionsLimit()\"\n [typeaheadScrollable]=\"true\"\n [typeaheadOptionsInScrollableView]=\"optionsInScrollableView()\"\n [typeaheadAutoSelectIndex]=\"selectedCriteriaIndex() ?? (searchValue ? 0 : -1)\"\n [readonly]=\"readonly()\"\n [disabled]=\"disabled()\"\n [placeholder]=\"placeholder()\"\n [attr.aria-label]=\"searchLabel() | translate\"\n [value]=\"searchValue\"\n (focus)=\"freeTextFocus()\"\n (input)=\"freeTextInput($event)\"\n (keydown.backspace)=\"freeTextBackspace($event)\"\n (keydown.enter)=\"submit()\"\n (typeaheadOnSelect)=\"typeaheadOnSelectCriterion($event.option)\"\n />\n</div>\n<!-- clear all searchCriteria -->\n@if ((values.length || searchValue.length) && !readonly()) {\n <div class=\"clear-button-container\">\n <button\n type=\"button\"\n class=\"btn btn-circle btn-xs btn-ghost mx-2\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"clearButtonLabel() | translate\"\n (click)=\"deleteAllCriteria($event)\"\n >\n <si-icon-next [icon]=\"icons.elementCancel\" />\n </button>\n </div>\n}\n\n@if (!disabled()) {\n <button\n type=\"button\"\n class=\"search-button btn btn-circle btn-xs btn-secondary focus-inside\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"submitButtonLabel() | translate\"\n (click)=\"submit()\"\n >\n <si-icon-next [icon]=\"icons.elementSearch\" />\n </button>\n}\n", styles: [":host{--search-bar-icon-color: var(--element-text-primary);--input-background-color: var(--element-base-1);--input-background-hover-color: var(--element-base-1);--filter-search-background-color: var(--element-base-1);--filter-pill-background-color: var(--element-base-0);display:flex;align-items:center;line-height:24px;min-inline-size:0}:host.dark-background{--input-background-color: var(--element-base-0);--input-background-hover-color: var(--element-ui-4);--filter-search-background-color: var(--element-base-0);--filter-pill-background-color: var(--element-base-1)}:host.disabled{color:var(--element-text-disabled)}:host.disabled input,:host.disabled ::placeholder{color:var(--element-text-disabled)}:host si-filtered-search-value+input::placeholder{color:transparent}.search-container{border-start-start-radius:4px;border-end-start-radius:4px;background-color:var(--filter-search-background-color);gap:4px}input{background:transparent;border:0;padding-block:0}input:hover::placeholder{opacity:1}:host(.disabled) input::placeholder{opacity:var(--element-action-disabled-opacity)}.clear-button-container{display:flex;align-items:flex-start;align-self:stretch;padding-block:4px;background-color:var(--filter-search-background-color)}.search-button{align-self:stretch;background-color:var(--filter-search-background-color);border:0;border-radius:0;border-end-end-radius:4px;border-start-end-radius:4px;margin-block:0;margin-inline:1px 0;block-size:auto;inline-size:auto;padding-inline:4px!important}\n"] }]
|
|
1110
|
+
}], ctorParameters: () => [] });
|
|
1111
|
+
|
|
1112
|
+
/**
|
|
1113
|
+
* Copyright Siemens 2016 - 2025.
|
|
1114
|
+
* SPDX-License-Identifier: MIT
|
|
1115
|
+
*/
|
|
1116
|
+
class SiFilteredSearchModule {
|
|
1117
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: SiFilteredSearchModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
1118
|
+
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.0.6", ngImport: i0, type: SiFilteredSearchModule, imports: [SiFilteredSearchComponent], exports: [SiFilteredSearchComponent] });
|
|
1119
|
+
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: SiFilteredSearchModule, imports: [SiFilteredSearchComponent] });
|
|
1120
|
+
}
|
|
1121
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: SiFilteredSearchModule, decorators: [{
|
|
1122
|
+
type: NgModule,
|
|
1123
|
+
args: [{
|
|
1124
|
+
imports: [SiFilteredSearchComponent],
|
|
1125
|
+
exports: [SiFilteredSearchComponent]
|
|
1126
|
+
}]
|
|
1127
|
+
}] });
|
|
1128
|
+
|
|
1129
|
+
/**
|
|
1130
|
+
* Copyright Siemens 2016 - 2025.
|
|
1131
|
+
* SPDX-License-Identifier: MIT
|
|
1132
|
+
*/
|
|
1133
|
+
|
|
1134
|
+
/**
|
|
1135
|
+
* Generated bundle index. Do not edit.
|
|
1136
|
+
*/
|
|
1137
|
+
|
|
1138
|
+
export { SiFilteredSearchComponent, SiFilteredSearchModule };
|
|
1139
|
+
//# sourceMappingURL=siemens-element-ng-filtered-search.mjs.map
|