@nexterajs/ui 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +279 -34
- package/fesm2022/nexterajs-ui-charts.mjs +1096 -0
- package/fesm2022/nexterajs-ui-charts.mjs.map +1 -0
- package/fesm2022/nexterajs-ui-core.mjs +835 -11
- package/fesm2022/nexterajs-ui-core.mjs.map +1 -1
- package/fesm2022/nexterajs-ui-display.mjs +1142 -0
- package/fesm2022/nexterajs-ui-display.mjs.map +1 -0
- package/fesm2022/nexterajs-ui-dropdowns.mjs +621 -69
- package/fesm2022/nexterajs-ui-dropdowns.mjs.map +1 -1
- package/fesm2022/nexterajs-ui-file-uploader.mjs +576 -11
- package/fesm2022/nexterajs-ui-file-uploader.mjs.map +1 -1
- package/fesm2022/nexterajs-ui-forms.mjs +2702 -452
- package/fesm2022/nexterajs-ui-forms.mjs.map +1 -1
- package/fesm2022/nexterajs-ui-grid.mjs +2350 -336
- package/fesm2022/nexterajs-ui-grid.mjs.map +1 -1
- package/fesm2022/nexterajs-ui-intl.mjs +117 -15
- package/fesm2022/nexterajs-ui-intl.mjs.map +1 -1
- package/fesm2022/nexterajs-ui-navigation.mjs +1054 -0
- package/fesm2022/nexterajs-ui-navigation.mjs.map +1 -0
- package/fesm2022/nexterajs-ui-notify.mjs +353 -0
- package/fesm2022/nexterajs-ui-notify.mjs.map +1 -0
- package/fesm2022/nexterajs-ui-overlay.mjs +1671 -0
- package/fesm2022/nexterajs-ui-overlay.mjs.map +1 -0
- package/fesm2022/nexterajs-ui-pivot.mjs +191 -136
- package/fesm2022/nexterajs-ui-pivot.mjs.map +1 -1
- package/fesm2022/nexterajs-ui-shell.mjs +542 -0
- package/fesm2022/nexterajs-ui-shell.mjs.map +1 -0
- package/fesm2022/nexterajs-ui-styling.mjs +568 -3
- package/fesm2022/nexterajs-ui-styling.mjs.map +1 -1
- package/fesm2022/nexterajs-ui-tree-view.mjs +150 -18
- package/fesm2022/nexterajs-ui-tree-view.mjs.map +1 -1
- package/fesm2022/nexterajs-ui-validation.mjs +283 -97
- package/fesm2022/nexterajs-ui-validation.mjs.map +1 -1
- package/fesm2022/nexterajs-ui.mjs +6 -0
- package/fesm2022/nexterajs-ui.mjs.map +1 -1
- package/package.json +37 -2
- package/styles/_dropdown.scss +150 -1
- package/styles/_editor.scss +75 -21
- package/styles/_icons.scss +92 -0
- package/styles/_mixins.scss +104 -1
- package/styles/_scrollbar.scss +134 -0
- package/styles/_status.scss +21 -0
- package/styles/_toast.scss +243 -0
- package/styles/_tokens.scss +467 -2
- package/styles/_widget.scss +14 -3
- package/styles/index.scss +10 -4
- package/types/nexterajs-ui-charts.d.ts +446 -0
- package/types/nexterajs-ui-core.d.ts +515 -30
- package/types/nexterajs-ui-display.d.ts +720 -0
- package/types/nexterajs-ui-dropdowns.d.ts +318 -57
- package/types/nexterajs-ui-file-uploader.d.ts +240 -8
- package/types/nexterajs-ui-forms.d.ts +772 -43
- package/types/nexterajs-ui-grid.d.ts +878 -135
- package/types/nexterajs-ui-intl.d.ts +39 -4
- package/types/nexterajs-ui-navigation.d.ts +531 -0
- package/types/nexterajs-ui-notify.d.ts +149 -0
- package/types/nexterajs-ui-overlay.d.ts +703 -0
- package/types/nexterajs-ui-pivot.d.ts +91 -70
- package/types/nexterajs-ui-shell.d.ts +294 -0
- package/types/nexterajs-ui-styling.d.ts +163 -3
- package/types/nexterajs-ui-tree-view.d.ts +56 -4
- package/types/nexterajs-ui-validation.d.ts +110 -22
- package/types/nexterajs-ui.d.ts +6 -0
|
@@ -2,22 +2,167 @@ import { Overlay } from '@angular/cdk/overlay';
|
|
|
2
2
|
import { TemplatePortal } from '@angular/cdk/portal';
|
|
3
3
|
import { NgTemplateOutlet } from '@angular/common';
|
|
4
4
|
import * as i0 from '@angular/core';
|
|
5
|
-
import { inject, ViewContainerRef, viewChild, contentChildren, input, computed, booleanAttribute, output,
|
|
5
|
+
import { signal, inject, DestroyRef, ViewContainerRef, viewChild, contentChildren, input, computed, numberAttribute, booleanAttribute, output, forwardRef, ViewEncapsulation, ChangeDetectionStrategy, Component, contentChild, model, Injector, InjectionToken, ElementRef, TemplateRef, Directive } from '@angular/core';
|
|
6
6
|
import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
7
|
+
import { takeUntilDestroyed, toObservable } from '@angular/core/rxjs-interop';
|
|
8
|
+
import { Subject, switchMap, timer, map, firstValueFrom, from, filter, catchError, EMPTY, merge, mergeMap, distinctUntilChanged } from 'rxjs';
|
|
9
|
+
import { NxeFormControlBase, NxeTemplateDirective, nxeOptionalBoolean, nxeResolveText, nxeResolveExpr, NXE_VALIDATION_TARGET } from '@nexterajs/ui/core';
|
|
9
10
|
import { nxePane, trackPaneFlip } from '@nexterajs/ui/styling';
|
|
10
|
-
|
|
11
|
+
|
|
12
|
+
/** True for a store object (anything with a `load` function that is not an array). */
|
|
13
|
+
function nxeIsPickerStore(value) {
|
|
14
|
+
return (!!value &&
|
|
15
|
+
typeof value === 'object' &&
|
|
16
|
+
!Array.isArray(value) &&
|
|
17
|
+
typeof value.load === 'function');
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The search plumbing shared by the pickers: debounces typing by `searchTimeout`, emits
|
|
21
|
+
* `searchChange` once the text reaches `minSearchLength` (or is cleared), and, when the
|
|
22
|
+
* `dataSource` is a {@link NxePickerStore}, queries it and keeps only the newest response.
|
|
23
|
+
* Must be created in an injection context. It uses no `effect()`: it reacts only to the explicit
|
|
24
|
+
* {@link typed}, {@link open} and {@link loadMore} calls.
|
|
25
|
+
*/
|
|
26
|
+
class NxePickerSearch {
|
|
27
|
+
cfg;
|
|
28
|
+
/** Rows of the store loaded so far (all pages of the current query). */
|
|
29
|
+
items = signal([], /* @ts-ignore */
|
|
30
|
+
...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
|
|
31
|
+
loading = signal(false, /* @ts-ignore */
|
|
32
|
+
...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
|
|
33
|
+
error = signal(null, /* @ts-ignore */
|
|
34
|
+
...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
|
|
35
|
+
hasMore = signal(false, /* @ts-ignore */
|
|
36
|
+
...(ngDevMode ? [{ debugName: "hasMore" }] : /* istanbul ignore next */ []));
|
|
37
|
+
typedText = new Subject();
|
|
38
|
+
loadedFor = null;
|
|
39
|
+
seq = 0;
|
|
40
|
+
controller = null;
|
|
41
|
+
constructor(cfg) {
|
|
42
|
+
this.cfg = cfg;
|
|
43
|
+
const destroyRef = inject(DestroyRef);
|
|
44
|
+
this.typedText
|
|
45
|
+
.pipe(switchMap((text) => timer(Math.max(0, cfg.searchTimeout())).pipe(map(() => text))), takeUntilDestroyed(destroyRef))
|
|
46
|
+
.subscribe((text) => this.settled(text));
|
|
47
|
+
destroyRef.onDestroy(() => this.controller?.abort());
|
|
48
|
+
}
|
|
49
|
+
get hasStore() {
|
|
50
|
+
return this.cfg.store() !== null;
|
|
51
|
+
}
|
|
52
|
+
/** True when `text` is long enough to be searched. */
|
|
53
|
+
reachesMinimum(text) {
|
|
54
|
+
return text.length >= this.cfg.minSearchLength();
|
|
55
|
+
}
|
|
56
|
+
/** The user typed; the search settles after `searchTimeout`. */
|
|
57
|
+
typed(text) {
|
|
58
|
+
this.typedText.next(text);
|
|
59
|
+
}
|
|
60
|
+
/** The panel opened with `text` in the field: load the first page unless that query is current. */
|
|
61
|
+
open(text) {
|
|
62
|
+
if (this.hasStore && this.loadedFor !== this.effective(text)) {
|
|
63
|
+
this.query(text);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/** Loads the next page of the current query (a scroll-to-bottom handler calls this). */
|
|
67
|
+
loadMore() {
|
|
68
|
+
if (this.hasStore && this.hasMore() && !this.loading()) {
|
|
69
|
+
void this.run(this.loadedFor ?? '', true);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/** Fetches one row by key through `byKey`; `null` when the store cannot or does not know it. */
|
|
73
|
+
async lookup(key) {
|
|
74
|
+
const store = this.cfg.store();
|
|
75
|
+
if (!store?.byKey) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
const controller = new AbortController();
|
|
79
|
+
try {
|
|
80
|
+
return (await firstValueFrom(from(store.byKey(key, { signal: controller.signal })))) ?? null;
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
settled(text) {
|
|
87
|
+
if (text === '' || this.reachesMinimum(text)) {
|
|
88
|
+
this.cfg.searchChange({ searchValue: text });
|
|
89
|
+
}
|
|
90
|
+
if (this.hasStore) {
|
|
91
|
+
this.query(text);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/** The text actually sent to the store: below the minimum nothing is searched. */
|
|
95
|
+
effective(text) {
|
|
96
|
+
if (this.reachesMinimum(text)) {
|
|
97
|
+
return text;
|
|
98
|
+
}
|
|
99
|
+
return this.cfg.showDataBeforeSearch() ? '' : null;
|
|
100
|
+
}
|
|
101
|
+
query(text) {
|
|
102
|
+
const effective = this.effective(text);
|
|
103
|
+
if (effective === null) {
|
|
104
|
+
this.cancel();
|
|
105
|
+
this.items.set([]);
|
|
106
|
+
this.hasMore.set(false);
|
|
107
|
+
this.loadedFor = null;
|
|
108
|
+
this.cfg.loaded?.();
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
void this.run(effective, false);
|
|
112
|
+
}
|
|
113
|
+
cancel() {
|
|
114
|
+
this.seq++;
|
|
115
|
+
this.controller?.abort();
|
|
116
|
+
this.controller = null;
|
|
117
|
+
this.loading.set(false);
|
|
118
|
+
}
|
|
119
|
+
async run(text, append) {
|
|
120
|
+
const store = this.cfg.store();
|
|
121
|
+
if (!store) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
this.cancel();
|
|
125
|
+
const seq = this.seq;
|
|
126
|
+
const controller = new AbortController();
|
|
127
|
+
this.controller = controller;
|
|
128
|
+
this.loading.set(true);
|
|
129
|
+
this.error.set(null);
|
|
130
|
+
const take = Math.max(1, this.cfg.pageSize());
|
|
131
|
+
const skip = append ? this.items().length : 0;
|
|
132
|
+
try {
|
|
133
|
+
const raw = await firstValueFrom(from(store.load({ searchValue: text, searchExpr: this.cfg.searchExpr(), searchOperation: 'contains', skip, take }, { signal: controller.signal })));
|
|
134
|
+
if (seq !== this.seq) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const data = Array.isArray(raw) ? raw : (raw.data ?? []);
|
|
138
|
+
const total = Array.isArray(raw) ? undefined : raw.totalCount;
|
|
139
|
+
const next = append ? [...this.items(), ...data] : data;
|
|
140
|
+
this.items.set(next);
|
|
141
|
+
this.hasMore.set(total !== undefined ? next.length < total : data.length >= take);
|
|
142
|
+
this.loadedFor = text;
|
|
143
|
+
this.loading.set(false);
|
|
144
|
+
this.cfg.loaded?.();
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
if (seq !== this.seq) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
this.error.set(error);
|
|
151
|
+
this.loading.set(false);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
11
155
|
|
|
12
156
|
/**
|
|
13
157
|
* `nxe-select-box` — drop-in replacement for DevExtreme `dx-select-box`.
|
|
14
158
|
*
|
|
15
159
|
* A signal-CVA single-select dropdown over a plain array (`[dataSource]`). It bakes in the app's
|
|
16
160
|
* global `dxSelectBox.defaultOptions` (`displayExpr='lovName'`, `valueExpr='lovCode'`,
|
|
17
|
-
* `searchEnabled
|
|
18
|
-
*
|
|
19
|
-
* an in-field search filter. Emits `(valueChange)` (the code) and
|
|
20
|
-
* (`{selectedItem}`). Hosts a projected `nxe-validator` via
|
|
161
|
+
* `searchEnabled`), so the bound control value is the `valueExpr` field (a string code) and the
|
|
162
|
+
* field shows the `displayExpr` field. The clear button follows dx and is off unless asked for.
|
|
163
|
+
* Dropdown is a CDK overlay with an in-field search filter. Emits `(valueChange)` (the code) and
|
|
164
|
+
* `(onSelectionChanged)` (`{selectedItem}`). Hosts a projected `nxe-validator` via
|
|
165
|
+
* {@link NXE_VALIDATION_TARGET}.
|
|
21
166
|
*/
|
|
22
167
|
class NxeSelectBoxComponent extends NxeFormControlBase {
|
|
23
168
|
overlay = inject(Overlay);
|
|
@@ -28,7 +173,10 @@ class NxeSelectBoxComponent extends NxeFormControlBase {
|
|
|
28
173
|
...(ngDevMode ? [{ debugName: "inputEl" }] : /* istanbul ignore next */ []));
|
|
29
174
|
templates = contentChildren(NxeTemplateDirective, /* @ts-ignore */
|
|
30
175
|
...(ngDevMode ? [{ debugName: "templates" }] : /* istanbul ignore next */ []));
|
|
31
|
-
/**
|
|
176
|
+
/**
|
|
177
|
+
* The source: a plain array of `Lov`-shaped objects (or primitives), or an {@link NxePickerStore}
|
|
178
|
+
* whose `load({searchValue, skip, take})` the box calls itself (server-side search).
|
|
179
|
+
*/
|
|
32
180
|
dataSource = input([], /* @ts-ignore */
|
|
33
181
|
...(ngDevMode ? [{ debugName: "dataSource" }] : /* istanbul ignore next */ []));
|
|
34
182
|
/** Field shown in the box/list (DevExtreme default `lovName`). Empty => the item itself. */
|
|
@@ -41,13 +189,24 @@ class NxeSelectBoxComponent extends NxeFormControlBase {
|
|
|
41
189
|
...(ngDevMode ? [{ debugName: "valueExpr" }] : /* istanbul ignore next */ []));
|
|
42
190
|
/** Field matched while searching (DevExtreme default `lovName`). */
|
|
43
191
|
searchExprInput = input(undefined, { ...(ngDevMode ? { debugName: "searchExprInput" } : /* istanbul ignore next */ {}), alias: 'searchExpr' });
|
|
44
|
-
|
|
192
|
+
/**
|
|
193
|
+
* dx searches by `displayExpr` when `searchExpr` is not given. Defaulting to the literal
|
|
194
|
+
* 'lovName' instead meant that any picker with a custom displayExpr searched a field its
|
|
195
|
+
* items do not have, and found nothing - silently, because an empty result looks like no
|
|
196
|
+
* match. Where displayExpr is the default this resolves to 'lovName' exactly as before.
|
|
197
|
+
*/
|
|
198
|
+
searchExpr = computed(() => this.searchExprInput() ?? this.nxeDefaults.selectBox?.searchExpr ?? this.displayExpr(), /* @ts-ignore */
|
|
45
199
|
...(ngDevMode ? [{ debugName: "searchExpr" }] : /* istanbul ignore next */ []));
|
|
46
200
|
searchEnabledInput = input(undefined, { ...(ngDevMode ? { debugName: "searchEnabledInput" } : /* istanbul ignore next */ {}), alias: 'searchEnabled', transform: nxeOptionalBoolean });
|
|
47
201
|
searchEnabled = computed(() => this.searchEnabledInput() ?? this.nxeDefaults.selectBox?.searchEnabled ?? true, /* @ts-ignore */
|
|
48
202
|
...(ngDevMode ? [{ debugName: "searchEnabled" }] : /* istanbul ignore next */ []));
|
|
49
203
|
showClearButtonInput = input(undefined, { ...(ngDevMode ? { debugName: "showClearButtonInput" } : /* istanbul ignore next */ {}), alias: 'showClearButton', transform: nxeOptionalBoolean });
|
|
50
|
-
|
|
204
|
+
/**
|
|
205
|
+
* dx defaults `showClearButton` to false (m_text_editor.base.js). The library used to default it
|
|
206
|
+
* on, which gave every host that never asked for one a clear button dx would not show. An app
|
|
207
|
+
* that wants it everywhere says so once through `NXE_DEFAULT_OPTIONS.selectBox.showClearButton`.
|
|
208
|
+
*/
|
|
209
|
+
showClearButton = computed(() => this.showClearButtonInput() ?? this.nxeDefaults.selectBox?.showClearButton ?? false, /* @ts-ignore */
|
|
51
210
|
...(ngDevMode ? [{ debugName: "showClearButton" }] : /* istanbul ignore next */ []));
|
|
52
211
|
/** dx parity: dx-select-box shows its drop-down arrow unless showDropDownButton=false. */
|
|
53
212
|
showDropDownButtonInput = input(undefined, { ...(ngDevMode ? { debugName: "showDropDownButtonInput" } : /* istanbul ignore next */ {}), alias: 'showDropDownButton', transform: nxeOptionalBoolean });
|
|
@@ -62,9 +221,29 @@ class NxeSelectBoxComponent extends NxeFormControlBase {
|
|
|
62
221
|
/** Named template ('inputField') for the closed field; `$implicit` = the selected item. */
|
|
63
222
|
fieldTemplate = input(/* @ts-ignore */
|
|
64
223
|
...(ngDevMode ? [undefined, { debugName: "fieldTemplate" }] : /* istanbul ignore next */ []));
|
|
224
|
+
/** dx `searchTimeout`: ms of typing pause before `(searchChange)` fires or a store is queried. */
|
|
225
|
+
searchTimeout = input(500, { ...(ngDevMode ? { debugName: "searchTimeout" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
226
|
+
/** dx `minSearchLength`: characters needed before a search is emitted, applied or sent. */
|
|
227
|
+
minSearchLength = input(0, { ...(ngDevMode ? { debugName: "minSearchLength" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
228
|
+
/** dx `showDataBeforeSearch`: list rows while the text is shorter than `minSearchLength`. */
|
|
229
|
+
showDataBeforeSearch = input(false, { ...(ngDevMode ? { debugName: "showDataBeforeSearch" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
230
|
+
/** Rows requested per page from a store `dataSource` (`take`); more load on scroll to the end. */
|
|
231
|
+
pageSize = input(20, { ...(ngDevMode ? { debugName: "pageSize" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
232
|
+
/**
|
|
233
|
+
* dx `displayValue`: the text shown for the current value while its row is not among the loaded
|
|
234
|
+
* ones (a store `dataSource` with the selection outside the current page, and no `byKey`).
|
|
235
|
+
*/
|
|
236
|
+
displayValue = input(undefined, /* @ts-ignore */
|
|
237
|
+
...(ngDevMode ? [{ debugName: "displayValue" }] : /* istanbul ignore next */ []));
|
|
65
238
|
/** DevExtreme parity, cosmetic. */
|
|
66
239
|
hoverStateEnabled = input(true, { ...(ngDevMode ? { debugName: "hoverStateEnabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
67
240
|
onSelectionChanged = output();
|
|
241
|
+
/**
|
|
242
|
+
* The search text settled: fires `searchTimeout` ms after the last keystroke, once the text has
|
|
243
|
+
* `minSearchLength` characters (or was cleared). Use it to query a server and replace
|
|
244
|
+
* `[dataSource]`, or pass a {@link NxePickerStore} and let the box query for you.
|
|
245
|
+
*/
|
|
246
|
+
searchChange = output();
|
|
68
247
|
focused = signal(false, /* @ts-ignore */
|
|
69
248
|
...(ngDevMode ? [{ debugName: "focused" }] : /* istanbul ignore next */ []));
|
|
70
249
|
open = signal(false, /* @ts-ignore */
|
|
@@ -83,25 +262,51 @@ class NxeSelectBoxComponent extends NxeFormControlBase {
|
|
|
83
262
|
overlayRef;
|
|
84
263
|
closeSub;
|
|
85
264
|
flipSub;
|
|
86
|
-
|
|
265
|
+
store = computed(() => {
|
|
266
|
+
const ds = this.dataSource();
|
|
267
|
+
return nxeIsPickerStore(ds) ? ds : null;
|
|
268
|
+
}, /* @ts-ignore */
|
|
269
|
+
...(ngDevMode ? [{ debugName: "store" }] : /* istanbul ignore next */ []));
|
|
270
|
+
search = new NxePickerSearch({
|
|
271
|
+
store: this.store,
|
|
272
|
+
searchExpr: this.searchExpr,
|
|
273
|
+
searchTimeout: this.searchTimeout,
|
|
274
|
+
minSearchLength: this.minSearchLength,
|
|
275
|
+
showDataBeforeSearch: this.showDataBeforeSearch,
|
|
276
|
+
pageSize: this.pageSize,
|
|
277
|
+
searchChange: (event) => this.searchChange.emit(event),
|
|
278
|
+
loaded: () => this.activeIndex.set(this.filteredItems().length ? 0 : -1),
|
|
279
|
+
});
|
|
280
|
+
/** Rows picked or fetched by key from a store, so the label outlives the page that held them. */
|
|
281
|
+
resolved = signal(new Map(), /* @ts-ignore */
|
|
282
|
+
...(ngDevMode ? [{ debugName: "resolved" }] : /* istanbul ignore next */ []));
|
|
283
|
+
items = computed(() => {
|
|
284
|
+
const ds = this.dataSource();
|
|
285
|
+
return nxeIsPickerStore(ds) ? this.search.items() : (ds ?? []);
|
|
286
|
+
}, /* @ts-ignore */
|
|
87
287
|
...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
|
|
288
|
+
/** Fetches the row of an externally set value through `byKey` when no loaded row has it. */
|
|
289
|
+
keyLookup = toObservable(this.value)
|
|
290
|
+
.pipe(filter((v) => v != null && v !== '' && this.search.hasStore && this.findRow(v) === undefined), switchMap((v) => from(this.search.lookup(v)).pipe(catchError(() => EMPTY), filter((row) => row != null), map((row) => [v, row]))), takeUntilDestroyed())
|
|
291
|
+
.subscribe(([key, row]) => this.remember(key, row));
|
|
292
|
+
findRow(key) {
|
|
293
|
+
return this.items().find((it) => this.extractValue(it) === key) ?? this.resolved().get(key);
|
|
294
|
+
}
|
|
295
|
+
remember(key, row) {
|
|
296
|
+
this.resolved.update((m) => new Map(m).set(key, row));
|
|
297
|
+
}
|
|
88
298
|
display(item) {
|
|
89
|
-
|
|
90
|
-
if (!expr) {
|
|
91
|
-
return item == null ? '' : String(item);
|
|
92
|
-
}
|
|
93
|
-
return item?.[expr] ?? '';
|
|
299
|
+
return nxeResolveText(this.displayExpr(), item);
|
|
94
300
|
}
|
|
95
301
|
extractValue(item) {
|
|
96
|
-
|
|
97
|
-
return expr ? item?.[expr] : item;
|
|
302
|
+
return nxeResolveExpr(this.valueExpr(), item);
|
|
98
303
|
}
|
|
99
304
|
selectedItem = computed(() => {
|
|
100
305
|
const v = this.value();
|
|
101
306
|
if (v == null) {
|
|
102
307
|
return null;
|
|
103
308
|
}
|
|
104
|
-
return this.
|
|
309
|
+
return this.findRow(v) ?? null;
|
|
105
310
|
}, /* @ts-ignore */
|
|
106
311
|
...(ngDevMode ? [{ debugName: "selectedItem" }] : /* istanbul ignore next */ []));
|
|
107
312
|
hasValue = computed(() => this.value() != null && this.value() !== '', /* @ts-ignore */
|
|
@@ -109,7 +314,10 @@ class NxeSelectBoxComponent extends NxeFormControlBase {
|
|
|
109
314
|
/** Display text when the field is not being searched. */
|
|
110
315
|
displayText = computed(() => {
|
|
111
316
|
const sel = this.selectedItem();
|
|
112
|
-
|
|
317
|
+
if (sel) {
|
|
318
|
+
return this.display(sel);
|
|
319
|
+
}
|
|
320
|
+
return this.hasValue() ? (this.displayValue() ?? '') : '';
|
|
113
321
|
}, /* @ts-ignore */
|
|
114
322
|
...(ngDevMode ? [{ debugName: "displayText" }] : /* istanbul ignore next */ []));
|
|
115
323
|
/** Value bound to the input element. */
|
|
@@ -121,15 +329,18 @@ class NxeSelectBoxComponent extends NxeFormControlBase {
|
|
|
121
329
|
filteredItems = computed(() => {
|
|
122
330
|
const items = this.items();
|
|
123
331
|
const search = this.searchText();
|
|
124
|
-
if (
|
|
332
|
+
if (this.search.hasStore) {
|
|
125
333
|
return items;
|
|
126
334
|
}
|
|
127
|
-
|
|
335
|
+
if (!this.searchEnabled() || !search) {
|
|
336
|
+
return this.minSearchLength() > 0 && !this.showDataBeforeSearch() ? [] : items;
|
|
337
|
+
}
|
|
338
|
+
if (!this.search.reachesMinimum(search)) {
|
|
339
|
+
return this.showDataBeforeSearch() ? items : [];
|
|
340
|
+
}
|
|
341
|
+
const expr = this.searchExpr();
|
|
128
342
|
const needle = search.toLowerCase();
|
|
129
|
-
return items.filter((it) =>
|
|
130
|
-
const hay = (expr ? it?.[expr] : it) ?? '';
|
|
131
|
-
return String(hay).toLowerCase().includes(needle);
|
|
132
|
-
});
|
|
343
|
+
return items.filter((it) => nxeResolveText(expr, it).toLowerCase().includes(needle));
|
|
133
344
|
}, /* @ts-ignore */
|
|
134
345
|
...(ngDevMode ? [{ debugName: "filteredItems" }] : /* istanbul ignore next */ []));
|
|
135
346
|
itemTemplateRef = computed(() => this.templates().find((t) => t.name() === this.itemTemplate())?.templateRef ?? null, /* @ts-ignore */
|
|
@@ -159,12 +370,21 @@ class NxeSelectBoxComponent extends NxeFormControlBase {
|
|
|
159
370
|
this.openPanel();
|
|
160
371
|
}
|
|
161
372
|
onSearchInput(event) {
|
|
162
|
-
|
|
373
|
+
const text = event.target.value;
|
|
374
|
+
this.searchText.set(text);
|
|
375
|
+
this.search.typed(text);
|
|
163
376
|
this.activeIndex.set(0);
|
|
164
377
|
if (!this.open()) {
|
|
165
378
|
this.openPanel();
|
|
166
379
|
}
|
|
167
380
|
}
|
|
381
|
+
/** Loads the next page of a store `dataSource` when the list is scrolled to its end. */
|
|
382
|
+
onPanelScroll(event) {
|
|
383
|
+
const el = event.target;
|
|
384
|
+
if (el.scrollTop + el.clientHeight >= el.scrollHeight - 8) {
|
|
385
|
+
this.search.loadMore();
|
|
386
|
+
}
|
|
387
|
+
}
|
|
168
388
|
onBlur() {
|
|
169
389
|
this.focused.set(false);
|
|
170
390
|
this.markAsTouched();
|
|
@@ -175,6 +395,9 @@ class NxeSelectBoxComponent extends NxeFormControlBase {
|
|
|
175
395
|
event.preventDefault();
|
|
176
396
|
const previousValue = this.value();
|
|
177
397
|
const next = this.extractValue(item);
|
|
398
|
+
if (this.search.hasStore) {
|
|
399
|
+
this.remember(next, item);
|
|
400
|
+
}
|
|
178
401
|
this.commitValue(next, event);
|
|
179
402
|
this.onSelectionChanged.emit({ selectedItem: item, previousValue, value: next });
|
|
180
403
|
this.searchText.set(null);
|
|
@@ -226,6 +449,7 @@ class NxeSelectBoxComponent extends NxeFormControlBase {
|
|
|
226
449
|
this.panelWidth.set(this.elementRef.nativeElement.offsetWidth);
|
|
227
450
|
const idx = this.items().findIndex((it) => this.extractValue(it) === this.value());
|
|
228
451
|
this.activeIndex.set(idx >= 0 ? idx : 0);
|
|
452
|
+
this.search.open(this.searchText() ?? '');
|
|
229
453
|
if (!this.overlayRef) {
|
|
230
454
|
this.overlayRef = this.overlay.create({
|
|
231
455
|
positionStrategy: this.overlay
|
|
@@ -264,31 +488,33 @@ class NxeSelectBoxComponent extends NxeFormControlBase {
|
|
|
264
488
|
this.overlayRef?.dispose();
|
|
265
489
|
}
|
|
266
490
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeSelectBoxComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
267
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", type: NxeSelectBoxComponent, isStandalone: true, selector: "nxe-select-box", inputs: { dataSource: { classPropertyName: "dataSource", publicName: "dataSource", isSignal: true, isRequired: false, transformFunction: null }, displayExprInput: { classPropertyName: "displayExprInput", publicName: "displayExpr", isSignal: true, isRequired: false, transformFunction: null }, valueExprInput: { classPropertyName: "valueExprInput", publicName: "valueExpr", isSignal: true, isRequired: false, transformFunction: null }, searchExprInput: { classPropertyName: "searchExprInput", publicName: "searchExpr", isSignal: true, isRequired: false, transformFunction: null }, searchEnabledInput: { classPropertyName: "searchEnabledInput", publicName: "searchEnabled", isSignal: true, isRequired: false, transformFunction: null }, showClearButtonInput: { classPropertyName: "showClearButtonInput", publicName: "showClearButton", isSignal: true, isRequired: false, transformFunction: null }, showDropDownButtonInput: { classPropertyName: "showDropDownButtonInput", publicName: "showDropDownButton", isSignal: true, isRequired: false, transformFunction: null }, placeholderInput: { classPropertyName: "placeholderInput", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, itemTemplate: { classPropertyName: "itemTemplate", publicName: "itemTemplate", isSignal: true, isRequired: false, transformFunction: null }, fieldTemplate: { classPropertyName: "fieldTemplate", publicName: "fieldTemplate", isSignal: true, isRequired: false, transformFunction: null }, hoverStateEnabled: { classPropertyName: "hoverStateEnabled", publicName: "hoverStateEnabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSelectionChanged: "onSelectionChanged" }, host: { listeners: { "keydown": "onKeydown($event)" }, properties: { "class.nxe-focused": "focused()", "class.nxe-invalid": "
|
|
491
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", type: NxeSelectBoxComponent, isStandalone: true, selector: "nxe-select-box", inputs: { dataSource: { classPropertyName: "dataSource", publicName: "dataSource", isSignal: true, isRequired: false, transformFunction: null }, displayExprInput: { classPropertyName: "displayExprInput", publicName: "displayExpr", isSignal: true, isRequired: false, transformFunction: null }, valueExprInput: { classPropertyName: "valueExprInput", publicName: "valueExpr", isSignal: true, isRequired: false, transformFunction: null }, searchExprInput: { classPropertyName: "searchExprInput", publicName: "searchExpr", isSignal: true, isRequired: false, transformFunction: null }, searchEnabledInput: { classPropertyName: "searchEnabledInput", publicName: "searchEnabled", isSignal: true, isRequired: false, transformFunction: null }, showClearButtonInput: { classPropertyName: "showClearButtonInput", publicName: "showClearButton", isSignal: true, isRequired: false, transformFunction: null }, showDropDownButtonInput: { classPropertyName: "showDropDownButtonInput", publicName: "showDropDownButton", isSignal: true, isRequired: false, transformFunction: null }, placeholderInput: { classPropertyName: "placeholderInput", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, itemTemplate: { classPropertyName: "itemTemplate", publicName: "itemTemplate", isSignal: true, isRequired: false, transformFunction: null }, fieldTemplate: { classPropertyName: "fieldTemplate", publicName: "fieldTemplate", isSignal: true, isRequired: false, transformFunction: null }, searchTimeout: { classPropertyName: "searchTimeout", publicName: "searchTimeout", isSignal: true, isRequired: false, transformFunction: null }, minSearchLength: { classPropertyName: "minSearchLength", publicName: "minSearchLength", isSignal: true, isRequired: false, transformFunction: null }, showDataBeforeSearch: { classPropertyName: "showDataBeforeSearch", publicName: "showDataBeforeSearch", isSignal: true, isRequired: false, transformFunction: null }, pageSize: { classPropertyName: "pageSize", publicName: "pageSize", isSignal: true, isRequired: false, transformFunction: null }, displayValue: { classPropertyName: "displayValue", publicName: "displayValue", isSignal: true, isRequired: false, transformFunction: null }, hoverStateEnabled: { classPropertyName: "hoverStateEnabled", publicName: "hoverStateEnabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSelectionChanged: "onSelectionChanged", searchChange: "searchChange" }, host: { listeners: { "keydown": "onKeydown($event)" }, properties: { "class.nxe-focused": "focused()", "class.nxe-invalid": "invalidState()", "class.nxe-open": "open()" }, classAttribute: "nxe-widget nxe-editor nxe-selectbox" }, providers: [
|
|
268
492
|
{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NxeSelectBoxComponent), multi: true },
|
|
269
493
|
{ provide: NXE_VALIDATION_TARGET, useExisting: forwardRef(() => NxeSelectBoxComponent) },
|
|
270
|
-
], queries: [{ propertyName: "templates", predicate: NxeTemplateDirective, isSignal: true }], viewQueries: [{ propertyName: "dropdownTemplate", first: true, predicate: ["dropdown"], descendants: true, isSignal: true }, { propertyName: "inputEl", first: true, predicate: ["input"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<div class=\"nxe-editor-container\" (mousedown)=\"onFieldMousedown($event)\">\
|
|
494
|
+
], queries: [{ propertyName: "templates", predicate: NxeTemplateDirective, isSignal: true }], viewQueries: [{ propertyName: "dropdownTemplate", first: true, predicate: ["dropdown"], descendants: true, isSignal: true }, { propertyName: "inputEl", first: true, predicate: ["input"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<div class=\"nxe-editor-container\" (mousedown)=\"onFieldMousedown($event)\">\n @if (fieldTemplateRef(); as tpl) {\n <div class=\"nxe-select-field\">\n <ng-container *ngTemplateOutlet=\"tpl; context: { $implicit: selectedItem() }\" />\n </div>\n } @else {\n <input\n #input\n class=\"nxe-editor-input nxe-select-input\"\n [attr.tabindex]=\"tabIndex()\"\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"open() ? panelId() : null\"\n [attr.aria-activedescendant]=\"open() && activeIndex() >= 0 ? optionId(activeIndex()) : null\"\n [id]=\"controlId()\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-labelledby]=\"ariaLabelledBy() || null\"\n [attr.aria-invalid]=\"invalidState() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n type=\"text\"\n [value]=\"inputDisplay()\"\n [readOnly]=\"readOnly() || !searchEnabled()\"\n [disabled]=\"disabled()\"\n [attr.placeholder]=\"placeholder() || null\"\n autocomplete=\"off\"\n (input)=\"onSearchInput($event)\"\n (focus)=\"focused.set(true)\"\n (blur)=\"onBlur()\" />\n }\n\n @if (showClearButton() && hasValue() && !readOnly() && !disabled()) {\n <span class=\"nxe-select-clear\" (mousedown)=\"onClear($event)\" aria-hidden=\"true\">\n <!-- A \u00D816 disc with a 2px \u00D7 cut out: two 2px bars, each running corner to corner of the\n 8px square centred on the disc, drawn as a plus and turned 45\u00B0. -->\n <svg viewBox=\"0 0 18 18\" focusable=\"false\">\n <path fill-rule=\"evenodd\" transform=\"rotate(45 9 9)\"\n d=\"M9 1a8 8 0 1 1 0 16A8 8 0 1 1 9 1ZM8 3.343h2V8h4.657v2H10v4.657H8V10H3.343V8H8Z\" />\n </svg>\n </span>\n }\n\n @if (showDropDownButton() && !readOnly()) {\n <span class=\"nxe-select-arrow\" aria-hidden=\"true\">\n <!-- An 8x4 triangle centred in the 18px box. -->\n <svg viewBox=\"0 0 18 18\" focusable=\"false\"><path d=\"M5 7h8l-4 4z\" /></svg>\n </span>\n }\n</div>\n\n<ng-content />\n\n@if (validationError(); as error) {\n @if (focused()) {\n <div class=\"nxe-invalid-message\">{{ error }}</div>\n }\n}\n\n<ng-template #dropdown>\n <div class=\"nxe-select-panel\" [style.width.px]=\"panelWidth()\"\n [id]=\"panelId()\"\n role=\"listbox\"\n (scroll)=\"onPanelScroll($event)\">\n @for (item of filteredItems(); track $index; let i = $index) {\n <div\n class=\"nxe-list-item\"\n [id]=\"optionId(i)\"\n role=\"option\"\n [attr.aria-selected]=\"isSelected(item)\"\n [class.nxe-list-item-selected]=\"isSelected(item)\"\n [class.nxe-list-item-active]=\"i === activeIndex()\"\n (mousedown)=\"selectItem(item, $event)\">\n @if (itemTemplateRef(); as tpl) {\n <ng-container *ngTemplateOutlet=\"tpl; context: { $implicit: item }\" />\n } @else {\n {{ display(item) }}\n }\n </div>\n } @empty {\n <div class=\"nxe-list-empty\">{{ nxeMessages.t('dropdown.noData') }}</div>\n }\n </div>\n</ng-template>\n", styles: ["nxe-select-box{cursor:pointer}nxe-select-box .nxe-select-input{cursor:pointer}nxe-select-box .nxe-select-clear{box-sizing:border-box;flex:0 0 34px;align-self:stretch;display:inline-flex;align-items:center;justify-content:center;width:34px;min-width:34px;padding:0;color:var(--nxe-ui-text-muted, #999);cursor:pointer}nxe-select-box .nxe-select-clear svg{display:block;width:18px;height:18px;fill:currentColor}nxe-select-box .nxe-select-arrow{box-sizing:border-box;position:relative;flex:0 0 34px;align-self:stretch;display:inline-flex;align-items:center;justify-content:center;width:34px;min-width:34px;padding:1px;border-radius:4px;background-clip:content-box;color:var(--nxe-ui-text, #333);cursor:pointer}nxe-select-box .nxe-select-arrow svg{display:block;width:18px;height:18px;fill:currentColor}nxe-select-box:hover:not(.nxe-disabled) .nxe-select-arrow,nxe-select-box:active:not(.nxe-disabled) .nxe-select-arrow{background-color:var(--nxe-ui-neutral-150, #e6e6e6)}nxe-select-box.nxe-open .nxe-select-arrow{background-color:var(--nxe-ui-neutral-350, #b3b3b3)}nxe-select-box .nxe-editor-container:has(>.nxe-select-arrow) .nxe-editor-input{padding-inline-end:0}nxe-select-box .nxe-editor-container:has(>.nxe-select-clear),nxe-select-box .nxe-editor-container:has(>.nxe-select-arrow){--nxe-invalid-badge-inset: 38px}nxe-select-box .nxe-editor-container:has(>.nxe-select-clear):has(>.nxe-select-arrow){--nxe-invalid-badge-inset: 72px}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
271
495
|
}
|
|
272
496
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeSelectBoxComponent, decorators: [{
|
|
273
497
|
type: Component,
|
|
274
498
|
args: [{ selector: 'nxe-select-box', standalone: true, imports: [NgTemplateOutlet], host: {
|
|
275
499
|
class: 'nxe-widget nxe-editor nxe-selectbox',
|
|
276
500
|
'[class.nxe-focused]': 'focused()',
|
|
277
|
-
'[class.nxe-invalid]': '
|
|
501
|
+
'[class.nxe-invalid]': 'invalidState()',
|
|
278
502
|
'[class.nxe-open]': 'open()',
|
|
279
503
|
'(keydown)': 'onKeydown($event)',
|
|
280
504
|
}, providers: [
|
|
281
505
|
{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NxeSelectBoxComponent), multi: true },
|
|
282
506
|
{ provide: NXE_VALIDATION_TARGET, useExisting: forwardRef(() => NxeSelectBoxComponent) },
|
|
283
|
-
], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<div class=\"nxe-editor-container\" (mousedown)=\"onFieldMousedown($event)\">\
|
|
284
|
-
}], propDecorators: { dropdownTemplate: [{ type: i0.ViewChild, args: ['dropdown', { isSignal: true }] }], inputEl: [{ type: i0.ViewChild, args: ['input', { isSignal: true }] }], templates: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => NxeTemplateDirective), { isSignal: true }] }], dataSource: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataSource", required: false }] }], displayExprInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "displayExpr", required: false }] }], valueExprInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueExpr", required: false }] }], searchExprInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchExpr", required: false }] }], searchEnabledInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchEnabled", required: false }] }], showClearButtonInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "showClearButton", required: false }] }], showDropDownButtonInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "showDropDownButton", required: false }] }], placeholderInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], itemTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemTemplate", required: false }] }], fieldTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "fieldTemplate", required: false }] }], hoverStateEnabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "hoverStateEnabled", required: false }] }], onSelectionChanged: [{ type: i0.Output, args: ["onSelectionChanged"] }] } });
|
|
507
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<div class=\"nxe-editor-container\" (mousedown)=\"onFieldMousedown($event)\">\n @if (fieldTemplateRef(); as tpl) {\n <div class=\"nxe-select-field\">\n <ng-container *ngTemplateOutlet=\"tpl; context: { $implicit: selectedItem() }\" />\n </div>\n } @else {\n <input\n #input\n class=\"nxe-editor-input nxe-select-input\"\n [attr.tabindex]=\"tabIndex()\"\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"open() ? panelId() : null\"\n [attr.aria-activedescendant]=\"open() && activeIndex() >= 0 ? optionId(activeIndex()) : null\"\n [id]=\"controlId()\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-labelledby]=\"ariaLabelledBy() || null\"\n [attr.aria-invalid]=\"invalidState() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n type=\"text\"\n [value]=\"inputDisplay()\"\n [readOnly]=\"readOnly() || !searchEnabled()\"\n [disabled]=\"disabled()\"\n [attr.placeholder]=\"placeholder() || null\"\n autocomplete=\"off\"\n (input)=\"onSearchInput($event)\"\n (focus)=\"focused.set(true)\"\n (blur)=\"onBlur()\" />\n }\n\n @if (showClearButton() && hasValue() && !readOnly() && !disabled()) {\n <span class=\"nxe-select-clear\" (mousedown)=\"onClear($event)\" aria-hidden=\"true\">\n <!-- A \u00D816 disc with a 2px \u00D7 cut out: two 2px bars, each running corner to corner of the\n 8px square centred on the disc, drawn as a plus and turned 45\u00B0. -->\n <svg viewBox=\"0 0 18 18\" focusable=\"false\">\n <path fill-rule=\"evenodd\" transform=\"rotate(45 9 9)\"\n d=\"M9 1a8 8 0 1 1 0 16A8 8 0 1 1 9 1ZM8 3.343h2V8h4.657v2H10v4.657H8V10H3.343V8H8Z\" />\n </svg>\n </span>\n }\n\n @if (showDropDownButton() && !readOnly()) {\n <span class=\"nxe-select-arrow\" aria-hidden=\"true\">\n <!-- An 8x4 triangle centred in the 18px box. -->\n <svg viewBox=\"0 0 18 18\" focusable=\"false\"><path d=\"M5 7h8l-4 4z\" /></svg>\n </span>\n }\n</div>\n\n<ng-content />\n\n@if (validationError(); as error) {\n @if (focused()) {\n <div class=\"nxe-invalid-message\">{{ error }}</div>\n }\n}\n\n<ng-template #dropdown>\n <div class=\"nxe-select-panel\" [style.width.px]=\"panelWidth()\"\n [id]=\"panelId()\"\n role=\"listbox\"\n (scroll)=\"onPanelScroll($event)\">\n @for (item of filteredItems(); track $index; let i = $index) {\n <div\n class=\"nxe-list-item\"\n [id]=\"optionId(i)\"\n role=\"option\"\n [attr.aria-selected]=\"isSelected(item)\"\n [class.nxe-list-item-selected]=\"isSelected(item)\"\n [class.nxe-list-item-active]=\"i === activeIndex()\"\n (mousedown)=\"selectItem(item, $event)\">\n @if (itemTemplateRef(); as tpl) {\n <ng-container *ngTemplateOutlet=\"tpl; context: { $implicit: item }\" />\n } @else {\n {{ display(item) }}\n }\n </div>\n } @empty {\n <div class=\"nxe-list-empty\">{{ nxeMessages.t('dropdown.noData') }}</div>\n }\n </div>\n</ng-template>\n", styles: ["nxe-select-box{cursor:pointer}nxe-select-box .nxe-select-input{cursor:pointer}nxe-select-box .nxe-select-clear{box-sizing:border-box;flex:0 0 34px;align-self:stretch;display:inline-flex;align-items:center;justify-content:center;width:34px;min-width:34px;padding:0;color:var(--nxe-ui-text-muted, #999);cursor:pointer}nxe-select-box .nxe-select-clear svg{display:block;width:18px;height:18px;fill:currentColor}nxe-select-box .nxe-select-arrow{box-sizing:border-box;position:relative;flex:0 0 34px;align-self:stretch;display:inline-flex;align-items:center;justify-content:center;width:34px;min-width:34px;padding:1px;border-radius:4px;background-clip:content-box;color:var(--nxe-ui-text, #333);cursor:pointer}nxe-select-box .nxe-select-arrow svg{display:block;width:18px;height:18px;fill:currentColor}nxe-select-box:hover:not(.nxe-disabled) .nxe-select-arrow,nxe-select-box:active:not(.nxe-disabled) .nxe-select-arrow{background-color:var(--nxe-ui-neutral-150, #e6e6e6)}nxe-select-box.nxe-open .nxe-select-arrow{background-color:var(--nxe-ui-neutral-350, #b3b3b3)}nxe-select-box .nxe-editor-container:has(>.nxe-select-arrow) .nxe-editor-input{padding-inline-end:0}nxe-select-box .nxe-editor-container:has(>.nxe-select-clear),nxe-select-box .nxe-editor-container:has(>.nxe-select-arrow){--nxe-invalid-badge-inset: 38px}nxe-select-box .nxe-editor-container:has(>.nxe-select-clear):has(>.nxe-select-arrow){--nxe-invalid-badge-inset: 72px}\n"] }]
|
|
508
|
+
}], propDecorators: { dropdownTemplate: [{ type: i0.ViewChild, args: ['dropdown', { isSignal: true }] }], inputEl: [{ type: i0.ViewChild, args: ['input', { isSignal: true }] }], templates: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => NxeTemplateDirective), { isSignal: true }] }], dataSource: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataSource", required: false }] }], displayExprInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "displayExpr", required: false }] }], valueExprInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueExpr", required: false }] }], searchExprInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchExpr", required: false }] }], searchEnabledInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchEnabled", required: false }] }], showClearButtonInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "showClearButton", required: false }] }], showDropDownButtonInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "showDropDownButton", required: false }] }], placeholderInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], itemTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemTemplate", required: false }] }], fieldTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "fieldTemplate", required: false }] }], searchTimeout: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchTimeout", required: false }] }], minSearchLength: [{ type: i0.Input, args: [{ isSignal: true, alias: "minSearchLength", required: false }] }], showDataBeforeSearch: [{ type: i0.Input, args: [{ isSignal: true, alias: "showDataBeforeSearch", required: false }] }], pageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSize", required: false }] }], displayValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "displayValue", required: false }] }], hoverStateEnabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "hoverStateEnabled", required: false }] }], onSelectionChanged: [{ type: i0.Output, args: ["onSelectionChanged"] }], searchChange: [{ type: i0.Output, args: ["searchChange"] }] } });
|
|
285
509
|
|
|
286
510
|
/**
|
|
287
511
|
* `nxe-tag-box` — drop-in replacement for DevExtreme `dx-tag-box` (multi-select sibling of
|
|
288
512
|
* `nxe-select-box`). The bound value is an **array of `valueExpr` keys** (`lovCode` strings).
|
|
289
513
|
* Bakes in the app's global `dxTagBox.defaultOptions` (display/search `lovName`, value `lovCode`,
|
|
290
|
-
* search +
|
|
291
|
-
*
|
|
514
|
+
* search + selection-controls on); the clear button follows dx and is off unless asked for.
|
|
515
|
+
* Selected items render as removable chips; with selection controls the dropdown shows a checkbox
|
|
516
|
+
* per row under a tri-state "Select All". Provides {@link NXE_VALIDATION_TARGET} for a projected
|
|
517
|
+
* `nxe-validator`.
|
|
292
518
|
*/
|
|
293
519
|
class NxeTagBoxComponent extends NxeFormControlBase {
|
|
294
520
|
overlay = inject(Overlay);
|
|
@@ -297,6 +523,7 @@ class NxeTagBoxComponent extends NxeFormControlBase {
|
|
|
297
523
|
...(ngDevMode ? [{ debugName: "dropdownTemplate" }] : /* istanbul ignore next */ []));
|
|
298
524
|
inputEl = viewChild('input', /* @ts-ignore */
|
|
299
525
|
...(ngDevMode ? [{ debugName: "inputEl" }] : /* istanbul ignore next */ []));
|
|
526
|
+
/** A plain array, or an {@link NxePickerStore} the box queries itself (server-side search). */
|
|
300
527
|
dataSource = input([], /* @ts-ignore */
|
|
301
528
|
...(ngDevMode ? [{ debugName: "dataSource" }] : /* istanbul ignore next */ []));
|
|
302
529
|
displayExprInput = input(undefined, { ...(ngDevMode ? { debugName: "displayExprInput" } : /* istanbul ignore next */ {}), alias: 'displayExpr' });
|
|
@@ -306,14 +533,37 @@ class NxeTagBoxComponent extends NxeFormControlBase {
|
|
|
306
533
|
valueExpr = computed(() => this.valueExprInput() ?? this.nxeDefaults.tagBox?.valueExpr ?? 'lovCode', /* @ts-ignore */
|
|
307
534
|
...(ngDevMode ? [{ debugName: "valueExpr" }] : /* istanbul ignore next */ []));
|
|
308
535
|
searchExprInput = input(undefined, { ...(ngDevMode ? { debugName: "searchExprInput" } : /* istanbul ignore next */ {}), alias: 'searchExpr' });
|
|
309
|
-
|
|
536
|
+
/**
|
|
537
|
+
* dx searches by `displayExpr` when `searchExpr` is not given. Defaulting to the literal
|
|
538
|
+
* 'lovName' instead meant that any picker with a custom displayExpr searched a field its
|
|
539
|
+
* items do not have, and found nothing - silently, because an empty result looks like no
|
|
540
|
+
* match. Where displayExpr is the default this resolves to 'lovName' exactly as before.
|
|
541
|
+
*/
|
|
542
|
+
searchExpr = computed(() => this.searchExprInput() ?? this.nxeDefaults.tagBox?.searchExpr ?? this.displayExpr(), /* @ts-ignore */
|
|
310
543
|
...(ngDevMode ? [{ debugName: "searchExpr" }] : /* istanbul ignore next */ []));
|
|
311
544
|
searchEnabledInput = input(undefined, { ...(ngDevMode ? { debugName: "searchEnabledInput" } : /* istanbul ignore next */ {}), alias: 'searchEnabled', transform: nxeOptionalBoolean });
|
|
312
545
|
searchEnabled = computed(() => this.searchEnabledInput() ?? this.nxeDefaults.tagBox?.searchEnabled ?? true, /* @ts-ignore */
|
|
313
546
|
...(ngDevMode ? [{ debugName: "searchEnabled" }] : /* istanbul ignore next */ []));
|
|
314
547
|
showClearButtonInput = input(undefined, { ...(ngDevMode ? { debugName: "showClearButtonInput" } : /* istanbul ignore next */ {}), alias: 'showClearButton', transform: nxeOptionalBoolean });
|
|
315
|
-
|
|
548
|
+
/**
|
|
549
|
+
* dx defaults `showClearButton` to false (m_text_editor.base.js). The library used to default it
|
|
550
|
+
* on, which gave every host that never asked for one a clear button dx would not show. An app
|
|
551
|
+
* that wants it everywhere says so once through `NXE_DEFAULT_OPTIONS.tagBox.showClearButton`.
|
|
552
|
+
*/
|
|
553
|
+
showClearButton = computed(() => this.showClearButtonInput() ?? this.nxeDefaults.tagBox?.showClearButton ?? false, /* @ts-ignore */
|
|
316
554
|
...(ngDevMode ? [{ debugName: "showClearButton" }] : /* istanbul ignore next */ []));
|
|
555
|
+
showSelectionControlsInput = input(undefined, { ...(ngDevMode ? { debugName: "showSelectionControlsInput" } : /* istanbul ignore next */ {}), alias: 'showSelectionControls', transform: nxeOptionalBoolean });
|
|
556
|
+
/**
|
|
557
|
+
* dx `showSelectionControls`. On (the literal, and what every consumer site passes): a checkbox
|
|
558
|
+
* per row and a tri-state "Select All" row — dx switches its list to `selectionMode: 'all'`
|
|
559
|
+
* (m_tag_box.js `_listConfig`). Off: plain rows whose fill marks the selection
|
|
560
|
+
* (`selectionMode: 'multiple'`).
|
|
561
|
+
*/
|
|
562
|
+
showSelectionControls = computed(() => this.showSelectionControlsInput() ?? this.nxeDefaults.tagBox?.showSelectionControls ?? true, /* @ts-ignore */
|
|
563
|
+
...(ngDevMode ? [{ debugName: "showSelectionControls" }] : /* istanbul ignore next */ []));
|
|
564
|
+
/** dx `selectAllText` — the label of the "Select All" row (dx: `dxList-selectAll`). */
|
|
565
|
+
selectAllText = input(undefined, /* @ts-ignore */
|
|
566
|
+
...(ngDevMode ? [{ debugName: "selectAllText" }] : /* istanbul ignore next */ []));
|
|
317
567
|
/** dx parity: dx-tag-box does NOT show a drop-down arrow unless showDropDownButton is set. */
|
|
318
568
|
showDropDownButtonInput = input(undefined, { ...(ngDevMode ? { debugName: "showDropDownButtonInput" } : /* istanbul ignore next */ {}), alias: 'showDropDownButton', transform: nxeOptionalBoolean });
|
|
319
569
|
showDropDownButton = computed(() => this.showDropDownButtonInput() ?? this.nxeDefaults.tagBox?.showDropDownButton ?? false, /* @ts-ignore */
|
|
@@ -321,6 +571,16 @@ class NxeTagBoxComponent extends NxeFormControlBase {
|
|
|
321
571
|
placeholderInput = input(undefined, { ...(ngDevMode ? { debugName: "placeholderInput" } : /* istanbul ignore next */ {}), alias: 'placeholder' });
|
|
322
572
|
placeholder = computed(() => this.placeholderInput() ?? this.nxeDefaults.tagBox?.placeholder ?? '', /* @ts-ignore */
|
|
323
573
|
...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
|
|
574
|
+
/** dx `searchTimeout`: ms of typing pause before `(searchChange)` fires or a store is queried. */
|
|
575
|
+
searchTimeout = input(500, { ...(ngDevMode ? { debugName: "searchTimeout" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
576
|
+
/** dx `minSearchLength`: characters needed before a search is emitted, applied or sent. */
|
|
577
|
+
minSearchLength = input(0, { ...(ngDevMode ? { debugName: "minSearchLength" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
578
|
+
/** dx `showDataBeforeSearch`: list rows while the text is shorter than `minSearchLength`. */
|
|
579
|
+
showDataBeforeSearch = input(false, { ...(ngDevMode ? { debugName: "showDataBeforeSearch" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
580
|
+
/** Rows requested per page from a store `dataSource` (`take`); more load on scroll to the end. */
|
|
581
|
+
pageSize = input(20, { ...(ngDevMode ? { debugName: "pageSize" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
582
|
+
/** The search text settled (after `searchTimeout`, once it has `minSearchLength` characters or was cleared). */
|
|
583
|
+
searchChange = output();
|
|
324
584
|
focused = signal(false, /* @ts-ignore */
|
|
325
585
|
...(ngDevMode ? [{ debugName: "focused" }] : /* istanbul ignore next */ []));
|
|
326
586
|
open = signal(false, /* @ts-ignore */
|
|
@@ -332,8 +592,38 @@ class NxeTagBoxComponent extends NxeFormControlBase {
|
|
|
332
592
|
overlayRef;
|
|
333
593
|
closeSub;
|
|
334
594
|
flipSub;
|
|
335
|
-
|
|
595
|
+
store = computed(() => {
|
|
596
|
+
const ds = this.dataSource();
|
|
597
|
+
return nxeIsPickerStore(ds) ? ds : null;
|
|
598
|
+
}, /* @ts-ignore */
|
|
599
|
+
...(ngDevMode ? [{ debugName: "store" }] : /* istanbul ignore next */ []));
|
|
600
|
+
search = new NxePickerSearch({
|
|
601
|
+
store: this.store,
|
|
602
|
+
searchExpr: this.searchExpr,
|
|
603
|
+
searchTimeout: this.searchTimeout,
|
|
604
|
+
minSearchLength: this.minSearchLength,
|
|
605
|
+
showDataBeforeSearch: this.showDataBeforeSearch,
|
|
606
|
+
pageSize: this.pageSize,
|
|
607
|
+
searchChange: (event) => this.searchChange.emit(event),
|
|
608
|
+
});
|
|
609
|
+
/** Rows picked or fetched by key from a store, so a chip keeps its label off the loaded page. */
|
|
610
|
+
resolved = signal(new Map(), /* @ts-ignore */
|
|
611
|
+
...(ngDevMode ? [{ debugName: "resolved" }] : /* istanbul ignore next */ []));
|
|
612
|
+
items = computed(() => {
|
|
613
|
+
const ds = this.dataSource();
|
|
614
|
+
return nxeIsPickerStore(ds) ? this.search.items() : (ds ?? []);
|
|
615
|
+
}, /* @ts-ignore */
|
|
336
616
|
...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
|
|
617
|
+
/** Fetches, through `byKey`, the rows of externally set keys that no loaded row has. */
|
|
618
|
+
keyLookup = toObservable(this.value)
|
|
619
|
+
.pipe(mergeMap((v) => (Array.isArray(v) && this.search.hasStore ? v : [])), filter((key) => this.findRow(key) === undefined), mergeMap((key) => from(this.search.lookup(key)).pipe(catchError(() => EMPTY), filter((row) => row != null), map((row) => [key, row]))), takeUntilDestroyed())
|
|
620
|
+
.subscribe(([key, row]) => this.remember(key, row));
|
|
621
|
+
findRow(key) {
|
|
622
|
+
return this.items().find((it) => this.extractValue(it) === key) ?? this.resolved().get(key);
|
|
623
|
+
}
|
|
624
|
+
remember(key, row) {
|
|
625
|
+
this.resolved.update((m) => new Map(m).set(key, row));
|
|
626
|
+
}
|
|
337
627
|
/** The selected keys (always an array). */
|
|
338
628
|
selectedKeys = computed(() => {
|
|
339
629
|
const v = this.value();
|
|
@@ -341,20 +631,15 @@ class NxeTagBoxComponent extends NxeFormControlBase {
|
|
|
341
631
|
}, /* @ts-ignore */
|
|
342
632
|
...(ngDevMode ? [{ debugName: "selectedKeys" }] : /* istanbul ignore next */ []));
|
|
343
633
|
display(item) {
|
|
344
|
-
|
|
345
|
-
if (!expr) {
|
|
346
|
-
return item == null ? '' : String(item);
|
|
347
|
-
}
|
|
348
|
-
return item?.[expr] ?? '';
|
|
634
|
+
return nxeResolveText(this.displayExpr(), item);
|
|
349
635
|
}
|
|
350
636
|
extractValue(item) {
|
|
351
|
-
|
|
352
|
-
return expr ? item?.[expr] : item;
|
|
637
|
+
return nxeResolveExpr(this.valueExpr(), item);
|
|
353
638
|
}
|
|
354
639
|
/** Selected items resolved from the value keys (for chip rendering). */
|
|
355
640
|
selectedItems = computed(() => {
|
|
356
641
|
const keys = this.selectedKeys();
|
|
357
|
-
return keys.map((k) => this.
|
|
642
|
+
return keys.map((k) => this.findRow(k) ?? k);
|
|
358
643
|
}, /* @ts-ignore */
|
|
359
644
|
...(ngDevMode ? [{ debugName: "selectedItems" }] : /* istanbul ignore next */ []));
|
|
360
645
|
hasValue = computed(() => this.selectedKeys().length > 0, /* @ts-ignore */
|
|
@@ -362,20 +647,38 @@ class NxeTagBoxComponent extends NxeFormControlBase {
|
|
|
362
647
|
filteredItems = computed(() => {
|
|
363
648
|
const items = this.items();
|
|
364
649
|
const search = this.searchText();
|
|
365
|
-
if (
|
|
650
|
+
if (this.search.hasStore) {
|
|
366
651
|
return items;
|
|
367
652
|
}
|
|
368
|
-
|
|
653
|
+
if (!this.searchEnabled() || !search) {
|
|
654
|
+
return this.minSearchLength() > 0 && !this.showDataBeforeSearch() ? [] : items;
|
|
655
|
+
}
|
|
656
|
+
if (!this.search.reachesMinimum(search)) {
|
|
657
|
+
return this.showDataBeforeSearch() ? items : [];
|
|
658
|
+
}
|
|
659
|
+
const expr = this.searchExpr();
|
|
369
660
|
const needle = search.toLowerCase();
|
|
370
|
-
return items.filter((it) =>
|
|
371
|
-
const hay = (expr ? it?.[expr] : it) ?? '';
|
|
372
|
-
return String(hay).toLowerCase().includes(needle);
|
|
373
|
-
});
|
|
661
|
+
return items.filter((it) => nxeResolveText(expr, it).toLowerCase().includes(needle));
|
|
374
662
|
}, /* @ts-ignore */
|
|
375
663
|
...(ngDevMode ? [{ debugName: "filteredItems" }] : /* istanbul ignore next */ []));
|
|
376
664
|
isSelected(item) {
|
|
377
665
|
return this.selectedKeys().includes(this.extractValue(item));
|
|
378
666
|
}
|
|
667
|
+
/**
|
|
668
|
+
* The "Select All" checkbox value over the rows currently listed — dx `selectAllMode: 'page'`,
|
|
669
|
+
* so a search narrows what it counts: `true` when every listed row is selected, `false` when
|
|
670
|
+
* none is, and `undefined` (indeterminate) in between.
|
|
671
|
+
*/
|
|
672
|
+
selectAllState = computed(() => {
|
|
673
|
+
const items = this.filteredItems();
|
|
674
|
+
const keys = this.selectedKeys();
|
|
675
|
+
const selected = items.filter((it) => keys.includes(this.extractValue(it))).length;
|
|
676
|
+
if (selected === 0) {
|
|
677
|
+
return false;
|
|
678
|
+
}
|
|
679
|
+
return selected === items.length ? true : undefined;
|
|
680
|
+
}, /* @ts-ignore */
|
|
681
|
+
...(ngDevMode ? [{ debugName: "selectAllState" }] : /* istanbul ignore next */ []));
|
|
379
682
|
onFieldMousedown(event) {
|
|
380
683
|
if (this.readOnly() || this.disabled()) {
|
|
381
684
|
return;
|
|
@@ -390,11 +693,20 @@ class NxeTagBoxComponent extends NxeFormControlBase {
|
|
|
390
693
|
this.inputEl()?.nativeElement?.focus();
|
|
391
694
|
}
|
|
392
695
|
onSearchInput(event) {
|
|
393
|
-
|
|
696
|
+
const text = event.target.value;
|
|
697
|
+
this.searchText.set(text);
|
|
698
|
+
this.search.typed(text);
|
|
394
699
|
if (!this.open()) {
|
|
395
700
|
this.openPanel();
|
|
396
701
|
}
|
|
397
702
|
}
|
|
703
|
+
/** Loads the next page of a store `dataSource` when the list is scrolled to its end. */
|
|
704
|
+
onPanelScroll(event) {
|
|
705
|
+
const el = event.target;
|
|
706
|
+
if (el.scrollTop + el.clientHeight >= el.scrollHeight - 8) {
|
|
707
|
+
this.search.loadMore();
|
|
708
|
+
}
|
|
709
|
+
}
|
|
398
710
|
onBlur() {
|
|
399
711
|
this.focused.set(false);
|
|
400
712
|
this.markAsTouched();
|
|
@@ -404,14 +716,42 @@ class NxeTagBoxComponent extends NxeFormControlBase {
|
|
|
404
716
|
const key = this.extractValue(item);
|
|
405
717
|
const keys = this.selectedKeys();
|
|
406
718
|
const next = keys.includes(key) ? keys.filter((k) => k !== key) : [...keys, key];
|
|
719
|
+
if (this.search.hasStore) {
|
|
720
|
+
this.remember(key, item);
|
|
721
|
+
}
|
|
407
722
|
this.commitValue(next, event);
|
|
723
|
+
if (this.searchText()) {
|
|
724
|
+
this.search.typed('');
|
|
725
|
+
}
|
|
408
726
|
this.searchText.set('');
|
|
409
727
|
const input = this.inputEl()?.nativeElement;
|
|
410
728
|
if (input) {
|
|
411
729
|
input.value = '';
|
|
412
730
|
}
|
|
413
731
|
}
|
|
732
|
+
/**
|
|
733
|
+
* dx `_selectAllHandler`: anything short of all-selected selects every listed row (keeping
|
|
734
|
+
* selections the search hides); all-selected deselects the listed rows only.
|
|
735
|
+
*/
|
|
736
|
+
toggleAll(event) {
|
|
737
|
+
event.preventDefault();
|
|
738
|
+
const listed = this.filteredItems().map((it) => this.extractValue(it));
|
|
739
|
+
if (this.search.hasStore) {
|
|
740
|
+
for (const it of this.filteredItems()) {
|
|
741
|
+
this.remember(this.extractValue(it), it);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
const keys = this.selectedKeys();
|
|
745
|
+
const next = this.selectAllState() === true
|
|
746
|
+
? keys.filter((k) => !listed.includes(k))
|
|
747
|
+
: [...keys, ...listed.filter((k) => !keys.includes(k))];
|
|
748
|
+
this.commitValue(next, event);
|
|
749
|
+
}
|
|
414
750
|
removeTag(item, event) {
|
|
751
|
+
// The × stays rendered in readOnly/disabled, as dx's does; its action is what is withheld.
|
|
752
|
+
if (this.readOnly() || this.disabled()) {
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
415
755
|
event.preventDefault();
|
|
416
756
|
event.stopPropagation();
|
|
417
757
|
const key = this.extractValue(item);
|
|
@@ -449,6 +789,7 @@ class NxeTagBoxComponent extends NxeFormControlBase {
|
|
|
449
789
|
if (this.overlayRef.hasAttached()) {
|
|
450
790
|
return;
|
|
451
791
|
}
|
|
792
|
+
this.search.open(this.searchText());
|
|
452
793
|
this.overlayRef.attach(new TemplatePortal(this.dropdownTemplate(), this.viewContainerRef));
|
|
453
794
|
this.closeSub = merge(this.overlayRef.backdropClick(), this.overlayRef.keydownEvents().pipe(filter((e) => e.key === 'Escape'))).subscribe(() => this.close());
|
|
454
795
|
this.open.set(true);
|
|
@@ -469,24 +810,24 @@ class NxeTagBoxComponent extends NxeFormControlBase {
|
|
|
469
810
|
this.overlayRef?.dispose();
|
|
470
811
|
}
|
|
471
812
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeTagBoxComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
472
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", type: NxeTagBoxComponent, isStandalone: true, selector: "nxe-tag-box", inputs: { dataSource: { classPropertyName: "dataSource", publicName: "dataSource", isSignal: true, isRequired: false, transformFunction: null }, displayExprInput: { classPropertyName: "displayExprInput", publicName: "displayExpr", isSignal: true, isRequired: false, transformFunction: null }, valueExprInput: { classPropertyName: "valueExprInput", publicName: "valueExpr", isSignal: true, isRequired: false, transformFunction: null }, searchExprInput: { classPropertyName: "searchExprInput", publicName: "searchExpr", isSignal: true, isRequired: false, transformFunction: null }, searchEnabledInput: { classPropertyName: "searchEnabledInput", publicName: "searchEnabled", isSignal: true, isRequired: false, transformFunction: null }, showClearButtonInput: { classPropertyName: "showClearButtonInput", publicName: "showClearButton", isSignal: true, isRequired: false, transformFunction: null }, showDropDownButtonInput: { classPropertyName: "showDropDownButtonInput", publicName: "showDropDownButton", isSignal: true, isRequired: false, transformFunction: null }, placeholderInput: { classPropertyName: "placeholderInput", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "keydown": "onKeydown($event)" }, properties: { "class.nxe-focused": "focused()", "class.nxe-invalid": "
|
|
813
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", type: NxeTagBoxComponent, isStandalone: true, selector: "nxe-tag-box", inputs: { dataSource: { classPropertyName: "dataSource", publicName: "dataSource", isSignal: true, isRequired: false, transformFunction: null }, displayExprInput: { classPropertyName: "displayExprInput", publicName: "displayExpr", isSignal: true, isRequired: false, transformFunction: null }, valueExprInput: { classPropertyName: "valueExprInput", publicName: "valueExpr", isSignal: true, isRequired: false, transformFunction: null }, searchExprInput: { classPropertyName: "searchExprInput", publicName: "searchExpr", isSignal: true, isRequired: false, transformFunction: null }, searchEnabledInput: { classPropertyName: "searchEnabledInput", publicName: "searchEnabled", isSignal: true, isRequired: false, transformFunction: null }, showClearButtonInput: { classPropertyName: "showClearButtonInput", publicName: "showClearButton", isSignal: true, isRequired: false, transformFunction: null }, showSelectionControlsInput: { classPropertyName: "showSelectionControlsInput", publicName: "showSelectionControls", isSignal: true, isRequired: false, transformFunction: null }, selectAllText: { classPropertyName: "selectAllText", publicName: "selectAllText", isSignal: true, isRequired: false, transformFunction: null }, showDropDownButtonInput: { classPropertyName: "showDropDownButtonInput", publicName: "showDropDownButton", isSignal: true, isRequired: false, transformFunction: null }, placeholderInput: { classPropertyName: "placeholderInput", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, searchTimeout: { classPropertyName: "searchTimeout", publicName: "searchTimeout", isSignal: true, isRequired: false, transformFunction: null }, minSearchLength: { classPropertyName: "minSearchLength", publicName: "minSearchLength", isSignal: true, isRequired: false, transformFunction: null }, showDataBeforeSearch: { classPropertyName: "showDataBeforeSearch", publicName: "showDataBeforeSearch", isSignal: true, isRequired: false, transformFunction: null }, pageSize: { classPropertyName: "pageSize", publicName: "pageSize", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { searchChange: "searchChange" }, host: { listeners: { "keydown": "onKeydown($event)" }, properties: { "class.nxe-focused": "focused()", "class.nxe-invalid": "invalidState()", "class.nxe-open": "open()" }, classAttribute: "nxe-widget nxe-editor nxe-tagbox" }, providers: [
|
|
473
814
|
{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NxeTagBoxComponent), multi: true },
|
|
474
815
|
{ provide: NXE_VALIDATION_TARGET, useExisting: forwardRef(() => NxeTagBoxComponent) },
|
|
475
|
-
], viewQueries: [{ propertyName: "dropdownTemplate", first: true, predicate: ["dropdown"], descendants: true, isSignal: true }, { propertyName: "inputEl", first: true, predicate: ["input"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<div class=\"nxe-editor-container nxe-tag-container\" (mousedown)=\"onFieldMousedown($event)\">\r\n @for (item of selectedItems(); track $index) {\r\n <span class=\"nxe-tag\">\r\n <span class=\"nxe-tag-text\">{{ display(item) }}</span>\r\n @if (!readOnly() && !disabled()) {\r\n <span class=\"nxe-tag-remove\" (mousedown)=\"removeTag(item, $event)\" aria-hidden=\"true\"></span>\r\n }\r\n </span>\r\n }\r\n\r\n <input\r\n #input\r\n class=\"nxe-editor-input nxe-tag-input\"\r\n type=\"text\"\r\n [readOnly]=\"readOnly() || !searchEnabled()\"\r\n [disabled]=\"disabled()\"\r\n [attr.placeholder]=\"selectedItems().length ? null : (placeholder() || null)\"\r\n autocomplete=\"off\"\r\n (input)=\"onSearchInput($event)\"\r\n (focus)=\"focused.set(true)\"\r\n (blur)=\"onBlur()\" />\r\n\r\n @if (showClearButton() && hasValue() && !readOnly() && !disabled()) {\r\n <span class=\"nxe-select-clear\" (mousedown)=\"onClear($event)\" aria-hidden=\"true\">\r\n <svg viewBox=\"0 0 24 24\"><path d=\"M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z\" /></svg>\r\n </span>\r\n }\r\n @if (showDropDownButton() && !readOnly()) {\r\n <span class=\"nxe-select-arrow\" aria-hidden=\"true\">\r\n <svg viewBox=\"0 0 24 24\"><path d=\"M7 10l5 5 5-5z\" /></svg>\r\n </span>\r\n }\r\n</div>\r\n\r\n<ng-content />\r\n\r\n@if (validationError(); as error) {\r\n @if (focused()) {\r\n <div class=\"nxe-invalid-message\">{{ error }}</div>\r\n }\r\n}\r\n\r\n<ng-template #dropdown>\r\n <div class=\"nxe-select-panel\" [style.width.px]=\"panelWidth()\">\r\n @for (item of filteredItems(); track $index) {\r\n <div\r\n class=\"nxe-list-item nxe-tag-item\"\r\n [class.nxe-list-item-selected]=\"isSelected(item)\"\r\n (mousedown)=\"toggle(item, $event)\">\r\n <span class=\"nxe-tag-check\" [class.nxe-tag-checked]=\"isSelected(item)\" aria-hidden=\"true\"></span>\r\n <span class=\"nxe-tag-item-text\">{{ display(item) }}</span>\r\n </div>\r\n } @empty {\r\n <div class=\"nxe-list-empty\">No data to display</div>\r\n }\r\n </div>\r\n</ng-template>\r\n", styles: ["nxe-tag-box{cursor:pointer}nxe-tag-box .nxe-tag-container{flex-wrap:wrap;align-items:center;gap:4px;padding:3px 4px;min-height:36px}nxe-tag-box .nxe-tag-input{flex:1 1 60px;min-width:60px;width:auto;min-height:26px;padding:0 4px}nxe-tag-box .nxe-tag{position:relative;display:inline-flex;align-items:center;max-width:calc(100% - 1px);min-width:40px;padding:3px 25px 4px 6px;background-color:#dcdcdc;border-radius:2px;color:#333;line-height:1.2}nxe-tag-box .nxe-tag-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}nxe-tag-box .nxe-tag-remove{position:absolute;inset-inline-end:0;top:0;width:25px;height:100%;cursor:pointer}nxe-tag-box .nxe-tag-remove:before,nxe-tag-box .nxe-tag-remove:after{position:absolute;top:50%;content:\"\";transform:rotate(45deg);inset-inline-end:9px;margin-top:-5px;width:3px;height:11px;background:#a9a9aa}nxe-tag-box .nxe-tag-remove:after{inset-inline-end:5px;margin-top:-1px;width:11px;height:3px}nxe-tag-box .nxe-select-clear,nxe-tag-box .nxe-select-arrow{flex:0 0 auto;align-self:center;display:inline-flex;align-items:center;justify-content:center;color:#333}nxe-tag-box .nxe-select-clear svg,nxe-tag-box .nxe-select-arrow svg{width:16px;height:16px;fill:currentColor}nxe-tag-box .nxe-select-clear{width:22px;color:#999}nxe-tag-box .nxe-select-clear:hover{color:#333}nxe-tag-box .nxe-select-arrow{width:28px}nxe-tag-box .nxe-select-arrow svg{width:18px;height:18px}nxe-tag-box .nxe-editor-container:has(>.nxe-select-arrow){--nxe-invalid-badge-inset: 32px}nxe-tag-box .nxe-editor-container:has(>.nxe-select-clear){--nxe-invalid-badge-inset: 26px}nxe-tag-box .nxe-editor-container:has(>.nxe-select-arrow):has(>.nxe-select-clear){--nxe-invalid-badge-inset: 54px}.nxe-tag-item{display:flex;align-items:center;gap:8px}.nxe-tag-check{box-sizing:border-box;position:relative;flex-shrink:0;width:18px;height:18px;border:1px solid #ddd;border-radius:2px;background:#fff}.nxe-tag-check.nxe-tag-checked{border-color:#337ab7}.nxe-tag-check.nxe-tag-checked:after{content:\"\";position:absolute;top:1px;inset-inline-start:5px;width:5px;height:9px;border:solid #337ab7;border-width:0 2px 2px 0;transform:rotate(45deg)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
816
|
+
], viewQueries: [{ propertyName: "dropdownTemplate", first: true, predicate: ["dropdown"], descendants: true, isSignal: true }, { propertyName: "inputEl", first: true, predicate: ["input"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<!--\n Two columns, as dx-tag-box renders them: the wrapping tag container (dx turns\n `.dx-texteditor-input-container` into `.dx-tag-container`) and, beside it, the buttons \u2014 never\n inside it, so they cannot wrap onto a tag row and they centre on the whole editor height.\n-->\n<div class=\"nxe-editor-container\" (mousedown)=\"onFieldMousedown($event)\">\n <div class=\"nxe-tag-container\">\n @for (item of selectedItems(); track $index) {\n <span class=\"nxe-tag\">\n <span class=\"nxe-tag-text\">{{ display(item) }}</span>\n <!-- Rendered in every state, as dx does (m_tag_box.js appends it unconditionally);\n readOnly/disabled make it inert rather than removing it. -->\n <span class=\"nxe-tag-remove\" (mousedown)=\"removeTag(item, $event)\" aria-hidden=\"true\"></span>\n </span>\n }\n\n <input\n #input\n class=\"nxe-editor-input nxe-tag-input\"\n [attr.tabindex]=\"tabIndex()\"\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"open() ? panelId() : null\"\n\n [id]=\"controlId()\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-labelledby]=\"ariaLabelledBy() || null\"\n [attr.aria-invalid]=\"invalidState() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n type=\"text\"\n [readOnly]=\"readOnly() || !searchEnabled()\"\n [disabled]=\"disabled()\"\n [attr.placeholder]=\"selectedItems().length ? null : (placeholder() || null)\"\n autocomplete=\"off\"\n (input)=\"onSearchInput($event)\"\n (focus)=\"focused.set(true)\"\n (blur)=\"onBlur()\" />\n </div>\n\n @if (showClearButton() && hasValue() && !readOnly() && !disabled()) {\n <span class=\"nxe-select-clear\" (mousedown)=\"onClear($event)\" aria-hidden=\"true\">\n <!-- A \u00D816 disc with a 2px \u00D7 cut out: two 2px bars, each running corner to corner of the\n 8px square centred on the disc, drawn as a plus and turned 45\u00B0. -->\n <svg viewBox=\"0 0 18 18\" focusable=\"false\">\n <path fill-rule=\"evenodd\" transform=\"rotate(45 9 9)\"\n d=\"M9 1a8 8 0 1 1 0 16A8 8 0 1 1 9 1ZM8 3.343h2V8h4.657v2H10v4.657H8V10H3.343V8H8Z\" />\n </svg>\n </span>\n }\n @if (showDropDownButton() && !readOnly()) {\n <span class=\"nxe-select-arrow\" aria-hidden=\"true\">\n <!-- An 8x4 triangle centred in the 18px box. -->\n <svg viewBox=\"0 0 18 18\" focusable=\"false\"><path d=\"M5 7h8l-4 4z\" /></svg>\n </span>\n }\n</div>\n\n<ng-content />\n\n@if (validationError(); as error) {\n @if (focused()) {\n <div class=\"nxe-invalid-message\">{{ error }}</div>\n }\n}\n\n<ng-template #dropdown>\n <div class=\"nxe-select-panel\" [style.width.px]=\"panelWidth()\"\n [id]=\"panelId()\"\n role=\"listbox\"\n aria-multiselectable=\"true\"\n (scroll)=\"onPanelScroll($event)\">\n @if (showSelectionControls()) {\n <!-- dx `selectionMode: 'all'`, which showSelectionControls switches on\n (m_tag_box.js _listConfig): a tri-state \"Select All\" over the rows shown. Hidden\n while the list is empty, as `.dx-empty-collection .dx-list-select-all` is. -->\n @if (filteredItems().length) {\n <div\n class=\"nxe-tag-select-all\"\n [id]=\"panelId() + '-select-all'\"\n role=\"option\"\n [attr.aria-selected]=\"selectAllState() === true\"\n [attr.aria-checked]=\"selectAllState() === undefined ? 'mixed' : selectAllState()\"\n (mousedown)=\"toggleAll($event)\">\n <span\n class=\"nxe-tag-check\"\n [class.nxe-tag-checked]=\"selectAllState() === true\"\n [class.nxe-tag-indeterminate]=\"selectAllState() === undefined\"\n aria-hidden=\"true\"></span>\n <span class=\"nxe-tag-select-all-label\">{{ selectAllText() ?? nxeMessages.t('dropdown.selectAll') }}</span>\n </div>\n }\n @for (item of filteredItems(); track $index; let i = $index) {\n <div\n class=\"nxe-list-item nxe-tag-item\"\n [id]=\"optionId(i)\"\n role=\"option\"\n [attr.aria-selected]=\"isSelected(item)\"\n [class.nxe-list-item-selected]=\"isSelected(item)\"\n (mousedown)=\"toggle(item, $event)\">\n <span class=\"nxe-tag-check\" [class.nxe-tag-checked]=\"isSelected(item)\" aria-hidden=\"true\"></span>\n <span class=\"nxe-tag-item-text\">{{ display(item) }}</span>\n </div>\n } @empty {\n <div class=\"nxe-list-empty\">{{ nxeMessages.t('dropdown.noData') }}</div>\n }\n } @else {\n <!-- dx `selectionMode: 'multiple'`: plain rows, selection shown by the row fill. -->\n @for (item of filteredItems(); track $index; let i = $index) {\n <div\n class=\"nxe-list-item\"\n [id]=\"optionId(i)\"\n role=\"option\"\n [attr.aria-selected]=\"isSelected(item)\"\n [class.nxe-list-item-selected]=\"isSelected(item)\"\n (mousedown)=\"toggle(item, $event)\">\n {{ display(item) }}\n </div>\n } @empty {\n <div class=\"nxe-list-empty\">{{ nxeMessages.t('dropdown.noData') }}</div>\n }\n }\n </div>\n</ng-template>\n", styles: ["nxe-tag-box{cursor:pointer}nxe-tag-box .nxe-tag-container{box-sizing:border-box;position:relative;display:flex;flex:1 1 auto;flex-wrap:wrap;align-items:stretch;align-self:stretch;min-width:0;padding-bottom:4px}nxe-tag-box .nxe-tag-input{flex:1 1 60px;min-width:60px;width:auto;height:auto;min-height:30px;padding-bottom:4px}nxe-tag-box .nxe-tag-container:has(>.nxe-tag)>.nxe-tag-input{padding-inline-start:0;margin-inline-start:5px}nxe-tag-box.nxe-invalid>.nxe-editor-container:not(:has(>.nxe-select-clear,>.nxe-select-arrow))>.nxe-tag-container:not(:has(>.nxe-tag))>.nxe-tag-input{padding-inline-end:34px}nxe-tag-box .nxe-tag{box-sizing:border-box;position:relative;display:inline-flex;align-self:flex-start;justify-content:center;max-width:calc(100% - 5px);min-width:40px;margin-top:4px;margin-inline-start:4px;padding-block:3px 4px;padding-inline:6px 25px;background-color:var(--nxe-ui-border, #ddd);border-radius:2px;color:var(--nxe-ui-text, #333)}nxe-tag-box .nxe-tag-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}nxe-tag-box .nxe-tag-remove{position:absolute;inset-inline-end:0;top:0;width:25px;height:100%;cursor:pointer}nxe-tag-box .nxe-tag-remove:before,nxe-tag-box .nxe-tag-remove:after{position:absolute;top:50%;content:\"\";transform:rotate(45deg);inset-inline-end:9px;margin-top:-5px;width:3px;height:11px;background:var(--nxe-ui-neutral-400, #aaa)}nxe-tag-box .nxe-tag-remove:after{inset-inline-end:5px;margin-top:-1px;width:11px;height:3px}nxe-tag-box:not(.nxe-disabled) .nxe-tag-remove:active:before,nxe-tag-box:not(.nxe-disabled) .nxe-tag-remove:active:after{background:var(--nxe-ui-border, #ddd)}nxe-tag-box.nxe-disabled .nxe-tag,nxe-tag-box.nxe-disabled .nxe-tag-remove{cursor:default}nxe-tag-box .nxe-select-clear{box-sizing:border-box;flex:0 0 34px;align-self:stretch;display:inline-flex;align-items:center;justify-content:center;width:34px;min-width:34px;padding:0;color:var(--nxe-ui-text-muted, #999);cursor:pointer}nxe-tag-box .nxe-select-clear svg{display:block;width:18px;height:18px;fill:currentColor}nxe-tag-box .nxe-select-arrow{box-sizing:border-box;position:relative;flex:0 0 34px;align-self:stretch;display:inline-flex;align-items:center;justify-content:center;width:34px;min-width:34px;padding:1px;border-radius:4px;background-clip:content-box;color:var(--nxe-ui-text, #333);cursor:pointer}nxe-tag-box .nxe-select-arrow svg{display:block;width:18px;height:18px;fill:currentColor}nxe-tag-box:hover:not(.nxe-disabled) .nxe-select-arrow,nxe-tag-box:active:not(.nxe-disabled) .nxe-select-arrow{background-color:var(--nxe-ui-neutral-150, #e6e6e6)}nxe-tag-box.nxe-open .nxe-select-arrow{background-color:var(--nxe-ui-neutral-350, #b3b3b3)}nxe-tag-box .nxe-editor-container:has(>.nxe-select-arrow) .nxe-editor-input{padding-inline-end:0}nxe-tag-box .nxe-editor-container:has(>.nxe-select-clear),nxe-tag-box .nxe-editor-container:has(>.nxe-select-arrow){--nxe-invalid-badge-inset: 38px}nxe-tag-box .nxe-editor-container:has(>.nxe-select-clear):has(>.nxe-select-arrow){--nxe-invalid-badge-inset: 72px}.nxe-select-panel .nxe-list-item.nxe-tag-item{display:block;position:relative;padding-block:10px;padding-inline:41px 10px}.nxe-select-panel .nxe-list-item.nxe-tag-item:hover,.nxe-select-panel .nxe-list-item.nxe-tag-item:active,.nxe-select-panel .nxe-list-item.nxe-tag-item.nxe-list-item-selected,.nxe-select-panel .nxe-list-item.nxe-tag-item.nxe-list-item-selected:hover:not(.nxe-list-item-active){background-color:transparent;color:var(--nxe-ui-text)}.nxe-tag-check{box-sizing:border-box;position:relative;display:block;width:22px;height:22px;border:1px solid var(--nxe-ui-border);border-radius:2px;background:var(--nxe-ui-surface)}.nxe-tag-check.nxe-tag-checked:after{content:\"\";position:absolute;box-sizing:border-box;top:5px;inset-inline-start:2.5px;width:15px;height:11px;background-color:var(--nxe-ui-accent);clip-path:polygon(0 5.586px,5.414px 11px,15px 1.414px,13.586px 0,5.414px 8.172px,1.414px 4.172px)}@media(forced-colors:active){.nxe-tag-check.nxe-tag-checked:after{forced-color-adjust:none;background-color:CanvasText}}.nxe-tag-check.nxe-tag-indeterminate:after{content:\"\";position:absolute;box-sizing:border-box;top:50%;inset-inline-start:50%;width:12px;height:12px;margin-top:-6px;margin-inline-start:-6px;border-radius:2px;background-color:var(--nxe-ui-accent)}@media(forced-colors:active){.nxe-tag-check.nxe-tag-indeterminate:after{forced-color-adjust:none;background-color:CanvasText}}.nxe-tag-item .nxe-tag-check{position:absolute;top:50%;inset-inline-start:10px;margin-top:-10px}.nxe-tag-item:hover .nxe-tag-check,.nxe-tag-select-all:hover .nxe-tag-check{border-color:var(--nxe-ui-checkbox-hover-border, #265a87)}.nxe-tag-select-all{box-sizing:border-box;padding-block:12px 11px;padding-inline:0;border-bottom:1px solid var(--nxe-ui-border);margin-bottom:3px;white-space:nowrap;cursor:pointer}.nxe-tag-select-all .nxe-tag-check{float:inline-start;margin-top:-1px;margin-bottom:-3px;margin-inline-start:10px}.nxe-tag-select-all-label{display:inline-block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:17px;padding:0 10px;margin-top:1px}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
476
817
|
}
|
|
477
818
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeTagBoxComponent, decorators: [{
|
|
478
819
|
type: Component,
|
|
479
820
|
args: [{ selector: 'nxe-tag-box', standalone: true, host: {
|
|
480
821
|
class: 'nxe-widget nxe-editor nxe-tagbox',
|
|
481
822
|
'[class.nxe-focused]': 'focused()',
|
|
482
|
-
'[class.nxe-invalid]': '
|
|
823
|
+
'[class.nxe-invalid]': 'invalidState()',
|
|
483
824
|
'[class.nxe-open]': 'open()',
|
|
484
825
|
'(keydown)': 'onKeydown($event)',
|
|
485
826
|
}, providers: [
|
|
486
827
|
{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NxeTagBoxComponent), multi: true },
|
|
487
828
|
{ provide: NXE_VALIDATION_TARGET, useExisting: forwardRef(() => NxeTagBoxComponent) },
|
|
488
|
-
], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<div class=\"nxe-editor-container nxe-tag-container\" (mousedown)=\"onFieldMousedown($event)\">\r\n @for (item of selectedItems(); track $index) {\r\n <span class=\"nxe-tag\">\r\n <span class=\"nxe-tag-text\">{{ display(item) }}</span>\r\n @if (!readOnly() && !disabled()) {\r\n <span class=\"nxe-tag-remove\" (mousedown)=\"removeTag(item, $event)\" aria-hidden=\"true\"></span>\r\n }\r\n </span>\r\n }\r\n\r\n <input\r\n #input\r\n class=\"nxe-editor-input nxe-tag-input\"\r\n type=\"text\"\r\n [readOnly]=\"readOnly() || !searchEnabled()\"\r\n [disabled]=\"disabled()\"\r\n [attr.placeholder]=\"selectedItems().length ? null : (placeholder() || null)\"\r\n autocomplete=\"off\"\r\n (input)=\"onSearchInput($event)\"\r\n (focus)=\"focused.set(true)\"\r\n (blur)=\"onBlur()\" />\r\n\r\n @if (showClearButton() && hasValue() && !readOnly() && !disabled()) {\r\n <span class=\"nxe-select-clear\" (mousedown)=\"onClear($event)\" aria-hidden=\"true\">\r\n <svg viewBox=\"0 0 24 24\"><path d=\"M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z\" /></svg>\r\n </span>\r\n }\r\n @if (showDropDownButton() && !readOnly()) {\r\n <span class=\"nxe-select-arrow\" aria-hidden=\"true\">\r\n <svg viewBox=\"0 0 24 24\"><path d=\"M7 10l5 5 5-5z\" /></svg>\r\n </span>\r\n }\r\n</div>\r\n\r\n<ng-content />\r\n\r\n@if (validationError(); as error) {\r\n @if (focused()) {\r\n <div class=\"nxe-invalid-message\">{{ error }}</div>\r\n }\r\n}\r\n\r\n<ng-template #dropdown>\r\n <div class=\"nxe-select-panel\" [style.width.px]=\"panelWidth()\">\r\n @for (item of filteredItems(); track $index) {\r\n <div\r\n class=\"nxe-list-item nxe-tag-item\"\r\n [class.nxe-list-item-selected]=\"isSelected(item)\"\r\n (mousedown)=\"toggle(item, $event)\">\r\n <span class=\"nxe-tag-check\" [class.nxe-tag-checked]=\"isSelected(item)\" aria-hidden=\"true\"></span>\r\n <span class=\"nxe-tag-item-text\">{{ display(item) }}</span>\r\n </div>\r\n } @empty {\r\n <div class=\"nxe-list-empty\">No data to display</div>\r\n }\r\n </div>\r\n</ng-template>\r\n", styles: ["nxe-tag-box{cursor:pointer}nxe-tag-box .nxe-tag-container{flex-wrap:wrap;align-items:center;gap:4px;padding:3px 4px;min-height:36px}nxe-tag-box .nxe-tag-input{flex:1 1 60px;min-width:60px;width:auto;min-height:26px;padding:0 4px}nxe-tag-box .nxe-tag{position:relative;display:inline-flex;align-items:center;max-width:calc(100% - 1px);min-width:40px;padding:3px 25px 4px 6px;background-color:#dcdcdc;border-radius:2px;color:#333;line-height:1.2}nxe-tag-box .nxe-tag-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}nxe-tag-box .nxe-tag-remove{position:absolute;inset-inline-end:0;top:0;width:25px;height:100%;cursor:pointer}nxe-tag-box .nxe-tag-remove:before,nxe-tag-box .nxe-tag-remove:after{position:absolute;top:50%;content:\"\";transform:rotate(45deg);inset-inline-end:9px;margin-top:-5px;width:3px;height:11px;background:#a9a9aa}nxe-tag-box .nxe-tag-remove:after{inset-inline-end:5px;margin-top:-1px;width:11px;height:3px}nxe-tag-box .nxe-select-clear,nxe-tag-box .nxe-select-arrow{flex:0 0 auto;align-self:center;display:inline-flex;align-items:center;justify-content:center;color:#333}nxe-tag-box .nxe-select-clear svg,nxe-tag-box .nxe-select-arrow svg{width:16px;height:16px;fill:currentColor}nxe-tag-box .nxe-select-clear{width:22px;color:#999}nxe-tag-box .nxe-select-clear:hover{color:#333}nxe-tag-box .nxe-select-arrow{width:28px}nxe-tag-box .nxe-select-arrow svg{width:18px;height:18px}nxe-tag-box .nxe-editor-container:has(>.nxe-select-arrow){--nxe-invalid-badge-inset: 32px}nxe-tag-box .nxe-editor-container:has(>.nxe-select-clear){--nxe-invalid-badge-inset: 26px}nxe-tag-box .nxe-editor-container:has(>.nxe-select-arrow):has(>.nxe-select-clear){--nxe-invalid-badge-inset: 54px}.nxe-tag-item{display:flex;align-items:center;gap:8px}.nxe-tag-check{box-sizing:border-box;position:relative;flex-shrink:0;width:18px;height:18px;border:1px solid #ddd;border-radius:2px;background:#fff}.nxe-tag-check.nxe-tag-checked{border-color:#337ab7}.nxe-tag-check.nxe-tag-checked:after{content:\"\";position:absolute;top:1px;inset-inline-start:5px;width:5px;height:9px;border:solid #337ab7;border-width:0 2px 2px 0;transform:rotate(45deg)}\n"] }]
|
|
489
|
-
}], propDecorators: { dropdownTemplate: [{ type: i0.ViewChild, args: ['dropdown', { isSignal: true }] }], inputEl: [{ type: i0.ViewChild, args: ['input', { isSignal: true }] }], dataSource: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataSource", required: false }] }], displayExprInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "displayExpr", required: false }] }], valueExprInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueExpr", required: false }] }], searchExprInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchExpr", required: false }] }], searchEnabledInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchEnabled", required: false }] }], showClearButtonInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "showClearButton", required: false }] }], showDropDownButtonInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "showDropDownButton", required: false }] }], placeholderInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }] } });
|
|
829
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<!--\n Two columns, as dx-tag-box renders them: the wrapping tag container (dx turns\n `.dx-texteditor-input-container` into `.dx-tag-container`) and, beside it, the buttons \u2014 never\n inside it, so they cannot wrap onto a tag row and they centre on the whole editor height.\n-->\n<div class=\"nxe-editor-container\" (mousedown)=\"onFieldMousedown($event)\">\n <div class=\"nxe-tag-container\">\n @for (item of selectedItems(); track $index) {\n <span class=\"nxe-tag\">\n <span class=\"nxe-tag-text\">{{ display(item) }}</span>\n <!-- Rendered in every state, as dx does (m_tag_box.js appends it unconditionally);\n readOnly/disabled make it inert rather than removing it. -->\n <span class=\"nxe-tag-remove\" (mousedown)=\"removeTag(item, $event)\" aria-hidden=\"true\"></span>\n </span>\n }\n\n <input\n #input\n class=\"nxe-editor-input nxe-tag-input\"\n [attr.tabindex]=\"tabIndex()\"\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"open() ? panelId() : null\"\n\n [id]=\"controlId()\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-labelledby]=\"ariaLabelledBy() || null\"\n [attr.aria-invalid]=\"invalidState() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n type=\"text\"\n [readOnly]=\"readOnly() || !searchEnabled()\"\n [disabled]=\"disabled()\"\n [attr.placeholder]=\"selectedItems().length ? null : (placeholder() || null)\"\n autocomplete=\"off\"\n (input)=\"onSearchInput($event)\"\n (focus)=\"focused.set(true)\"\n (blur)=\"onBlur()\" />\n </div>\n\n @if (showClearButton() && hasValue() && !readOnly() && !disabled()) {\n <span class=\"nxe-select-clear\" (mousedown)=\"onClear($event)\" aria-hidden=\"true\">\n <!-- A \u00D816 disc with a 2px \u00D7 cut out: two 2px bars, each running corner to corner of the\n 8px square centred on the disc, drawn as a plus and turned 45\u00B0. -->\n <svg viewBox=\"0 0 18 18\" focusable=\"false\">\n <path fill-rule=\"evenodd\" transform=\"rotate(45 9 9)\"\n d=\"M9 1a8 8 0 1 1 0 16A8 8 0 1 1 9 1ZM8 3.343h2V8h4.657v2H10v4.657H8V10H3.343V8H8Z\" />\n </svg>\n </span>\n }\n @if (showDropDownButton() && !readOnly()) {\n <span class=\"nxe-select-arrow\" aria-hidden=\"true\">\n <!-- An 8x4 triangle centred in the 18px box. -->\n <svg viewBox=\"0 0 18 18\" focusable=\"false\"><path d=\"M5 7h8l-4 4z\" /></svg>\n </span>\n }\n</div>\n\n<ng-content />\n\n@if (validationError(); as error) {\n @if (focused()) {\n <div class=\"nxe-invalid-message\">{{ error }}</div>\n }\n}\n\n<ng-template #dropdown>\n <div class=\"nxe-select-panel\" [style.width.px]=\"panelWidth()\"\n [id]=\"panelId()\"\n role=\"listbox\"\n aria-multiselectable=\"true\"\n (scroll)=\"onPanelScroll($event)\">\n @if (showSelectionControls()) {\n <!-- dx `selectionMode: 'all'`, which showSelectionControls switches on\n (m_tag_box.js _listConfig): a tri-state \"Select All\" over the rows shown. Hidden\n while the list is empty, as `.dx-empty-collection .dx-list-select-all` is. -->\n @if (filteredItems().length) {\n <div\n class=\"nxe-tag-select-all\"\n [id]=\"panelId() + '-select-all'\"\n role=\"option\"\n [attr.aria-selected]=\"selectAllState() === true\"\n [attr.aria-checked]=\"selectAllState() === undefined ? 'mixed' : selectAllState()\"\n (mousedown)=\"toggleAll($event)\">\n <span\n class=\"nxe-tag-check\"\n [class.nxe-tag-checked]=\"selectAllState() === true\"\n [class.nxe-tag-indeterminate]=\"selectAllState() === undefined\"\n aria-hidden=\"true\"></span>\n <span class=\"nxe-tag-select-all-label\">{{ selectAllText() ?? nxeMessages.t('dropdown.selectAll') }}</span>\n </div>\n }\n @for (item of filteredItems(); track $index; let i = $index) {\n <div\n class=\"nxe-list-item nxe-tag-item\"\n [id]=\"optionId(i)\"\n role=\"option\"\n [attr.aria-selected]=\"isSelected(item)\"\n [class.nxe-list-item-selected]=\"isSelected(item)\"\n (mousedown)=\"toggle(item, $event)\">\n <span class=\"nxe-tag-check\" [class.nxe-tag-checked]=\"isSelected(item)\" aria-hidden=\"true\"></span>\n <span class=\"nxe-tag-item-text\">{{ display(item) }}</span>\n </div>\n } @empty {\n <div class=\"nxe-list-empty\">{{ nxeMessages.t('dropdown.noData') }}</div>\n }\n } @else {\n <!-- dx `selectionMode: 'multiple'`: plain rows, selection shown by the row fill. -->\n @for (item of filteredItems(); track $index; let i = $index) {\n <div\n class=\"nxe-list-item\"\n [id]=\"optionId(i)\"\n role=\"option\"\n [attr.aria-selected]=\"isSelected(item)\"\n [class.nxe-list-item-selected]=\"isSelected(item)\"\n (mousedown)=\"toggle(item, $event)\">\n {{ display(item) }}\n </div>\n } @empty {\n <div class=\"nxe-list-empty\">{{ nxeMessages.t('dropdown.noData') }}</div>\n }\n }\n </div>\n</ng-template>\n", styles: ["nxe-tag-box{cursor:pointer}nxe-tag-box .nxe-tag-container{box-sizing:border-box;position:relative;display:flex;flex:1 1 auto;flex-wrap:wrap;align-items:stretch;align-self:stretch;min-width:0;padding-bottom:4px}nxe-tag-box .nxe-tag-input{flex:1 1 60px;min-width:60px;width:auto;height:auto;min-height:30px;padding-bottom:4px}nxe-tag-box .nxe-tag-container:has(>.nxe-tag)>.nxe-tag-input{padding-inline-start:0;margin-inline-start:5px}nxe-tag-box.nxe-invalid>.nxe-editor-container:not(:has(>.nxe-select-clear,>.nxe-select-arrow))>.nxe-tag-container:not(:has(>.nxe-tag))>.nxe-tag-input{padding-inline-end:34px}nxe-tag-box .nxe-tag{box-sizing:border-box;position:relative;display:inline-flex;align-self:flex-start;justify-content:center;max-width:calc(100% - 5px);min-width:40px;margin-top:4px;margin-inline-start:4px;padding-block:3px 4px;padding-inline:6px 25px;background-color:var(--nxe-ui-border, #ddd);border-radius:2px;color:var(--nxe-ui-text, #333)}nxe-tag-box .nxe-tag-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}nxe-tag-box .nxe-tag-remove{position:absolute;inset-inline-end:0;top:0;width:25px;height:100%;cursor:pointer}nxe-tag-box .nxe-tag-remove:before,nxe-tag-box .nxe-tag-remove:after{position:absolute;top:50%;content:\"\";transform:rotate(45deg);inset-inline-end:9px;margin-top:-5px;width:3px;height:11px;background:var(--nxe-ui-neutral-400, #aaa)}nxe-tag-box .nxe-tag-remove:after{inset-inline-end:5px;margin-top:-1px;width:11px;height:3px}nxe-tag-box:not(.nxe-disabled) .nxe-tag-remove:active:before,nxe-tag-box:not(.nxe-disabled) .nxe-tag-remove:active:after{background:var(--nxe-ui-border, #ddd)}nxe-tag-box.nxe-disabled .nxe-tag,nxe-tag-box.nxe-disabled .nxe-tag-remove{cursor:default}nxe-tag-box .nxe-select-clear{box-sizing:border-box;flex:0 0 34px;align-self:stretch;display:inline-flex;align-items:center;justify-content:center;width:34px;min-width:34px;padding:0;color:var(--nxe-ui-text-muted, #999);cursor:pointer}nxe-tag-box .nxe-select-clear svg{display:block;width:18px;height:18px;fill:currentColor}nxe-tag-box .nxe-select-arrow{box-sizing:border-box;position:relative;flex:0 0 34px;align-self:stretch;display:inline-flex;align-items:center;justify-content:center;width:34px;min-width:34px;padding:1px;border-radius:4px;background-clip:content-box;color:var(--nxe-ui-text, #333);cursor:pointer}nxe-tag-box .nxe-select-arrow svg{display:block;width:18px;height:18px;fill:currentColor}nxe-tag-box:hover:not(.nxe-disabled) .nxe-select-arrow,nxe-tag-box:active:not(.nxe-disabled) .nxe-select-arrow{background-color:var(--nxe-ui-neutral-150, #e6e6e6)}nxe-tag-box.nxe-open .nxe-select-arrow{background-color:var(--nxe-ui-neutral-350, #b3b3b3)}nxe-tag-box .nxe-editor-container:has(>.nxe-select-arrow) .nxe-editor-input{padding-inline-end:0}nxe-tag-box .nxe-editor-container:has(>.nxe-select-clear),nxe-tag-box .nxe-editor-container:has(>.nxe-select-arrow){--nxe-invalid-badge-inset: 38px}nxe-tag-box .nxe-editor-container:has(>.nxe-select-clear):has(>.nxe-select-arrow){--nxe-invalid-badge-inset: 72px}.nxe-select-panel .nxe-list-item.nxe-tag-item{display:block;position:relative;padding-block:10px;padding-inline:41px 10px}.nxe-select-panel .nxe-list-item.nxe-tag-item:hover,.nxe-select-panel .nxe-list-item.nxe-tag-item:active,.nxe-select-panel .nxe-list-item.nxe-tag-item.nxe-list-item-selected,.nxe-select-panel .nxe-list-item.nxe-tag-item.nxe-list-item-selected:hover:not(.nxe-list-item-active){background-color:transparent;color:var(--nxe-ui-text)}.nxe-tag-check{box-sizing:border-box;position:relative;display:block;width:22px;height:22px;border:1px solid var(--nxe-ui-border);border-radius:2px;background:var(--nxe-ui-surface)}.nxe-tag-check.nxe-tag-checked:after{content:\"\";position:absolute;box-sizing:border-box;top:5px;inset-inline-start:2.5px;width:15px;height:11px;background-color:var(--nxe-ui-accent);clip-path:polygon(0 5.586px,5.414px 11px,15px 1.414px,13.586px 0,5.414px 8.172px,1.414px 4.172px)}@media(forced-colors:active){.nxe-tag-check.nxe-tag-checked:after{forced-color-adjust:none;background-color:CanvasText}}.nxe-tag-check.nxe-tag-indeterminate:after{content:\"\";position:absolute;box-sizing:border-box;top:50%;inset-inline-start:50%;width:12px;height:12px;margin-top:-6px;margin-inline-start:-6px;border-radius:2px;background-color:var(--nxe-ui-accent)}@media(forced-colors:active){.nxe-tag-check.nxe-tag-indeterminate:after{forced-color-adjust:none;background-color:CanvasText}}.nxe-tag-item .nxe-tag-check{position:absolute;top:50%;inset-inline-start:10px;margin-top:-10px}.nxe-tag-item:hover .nxe-tag-check,.nxe-tag-select-all:hover .nxe-tag-check{border-color:var(--nxe-ui-checkbox-hover-border, #265a87)}.nxe-tag-select-all{box-sizing:border-box;padding-block:12px 11px;padding-inline:0;border-bottom:1px solid var(--nxe-ui-border);margin-bottom:3px;white-space:nowrap;cursor:pointer}.nxe-tag-select-all .nxe-tag-check{float:inline-start;margin-top:-1px;margin-bottom:-3px;margin-inline-start:10px}.nxe-tag-select-all-label{display:inline-block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:17px;padding:0 10px;margin-top:1px}\n"] }]
|
|
830
|
+
}], propDecorators: { dropdownTemplate: [{ type: i0.ViewChild, args: ['dropdown', { isSignal: true }] }], inputEl: [{ type: i0.ViewChild, args: ['input', { isSignal: true }] }], dataSource: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataSource", required: false }] }], displayExprInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "displayExpr", required: false }] }], valueExprInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueExpr", required: false }] }], searchExprInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchExpr", required: false }] }], searchEnabledInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchEnabled", required: false }] }], showClearButtonInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "showClearButton", required: false }] }], showSelectionControlsInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSelectionControls", required: false }] }], selectAllText: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectAllText", required: false }] }], showDropDownButtonInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "showDropDownButton", required: false }] }], placeholderInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], searchTimeout: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchTimeout", required: false }] }], minSearchLength: [{ type: i0.Input, args: [{ isSignal: true, alias: "minSearchLength", required: false }] }], showDataBeforeSearch: [{ type: i0.Input, args: [{ isSignal: true, alias: "showDataBeforeSearch", required: false }] }], pageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSize", required: false }] }], searchChange: [{ type: i0.Output, args: ["searchChange"] }] } });
|
|
490
831
|
|
|
491
832
|
/**
|
|
492
833
|
* `nxe-drop-down-options` — drop-in for DevExtreme `dxo-drop-down-options`, a no-template config
|
|
@@ -621,8 +962,8 @@ class NxeDropDownBoxComponent extends NxeFormControlBase {
|
|
|
621
962
|
}
|
|
622
963
|
// The bound value is the code; match it against valueExpr (default the implicit `lovCode`).
|
|
623
964
|
const ve = this.valueExpr() || 'lovCode';
|
|
624
|
-
const item = (this.dataSource() ?? []).find((it) =>
|
|
625
|
-
return item ?
|
|
965
|
+
const item = (this.dataSource() ?? []).find((it) => nxeResolveExpr(ve, it) === v);
|
|
966
|
+
return item ? nxeResolveText(de, item) : String(v);
|
|
626
967
|
}, /* @ts-ignore */
|
|
627
968
|
...(ngDevMode ? [{ debugName: "displayText" }] : /* istanbul ignore next */ []));
|
|
628
969
|
hasValue = computed(() => {
|
|
@@ -691,22 +1032,22 @@ class NxeDropDownBoxComponent extends NxeFormControlBase {
|
|
|
691
1032
|
this.overlayRef?.dispose();
|
|
692
1033
|
}
|
|
693
1034
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeDropDownBoxComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
694
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", type: NxeDropDownBoxComponent, isStandalone: true, selector: "nxe-drop-down-box", inputs: { dataSource: { classPropertyName: "dataSource", publicName: "dataSource", isSignal: true, isRequired: false, transformFunction: null }, displayExprInput: { classPropertyName: "displayExprInput", publicName: "displayExpr", isSignal: true, isRequired: false, transformFunction: null }, valueExprInput: { classPropertyName: "valueExprInput", publicName: "valueExpr", isSignal: true, isRequired: false, transformFunction: null }, placeholderInput: { classPropertyName: "placeholderInput", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, dropDownOptions: { classPropertyName: "dropDownOptions", publicName: "dropDownOptions", isSignal: true, isRequired: false, transformFunction: null }, showClearButtonInput: { classPropertyName: "showClearButtonInput", publicName: "showClearButton", isSignal: true, isRequired: false, transformFunction: null }, opened: { classPropertyName: "opened", publicName: "opened", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { opened: "openedChange", onClosed: "onClosed" }, host: { properties: { "class.nxe-focused": "focused()", "class.nxe-invalid": "
|
|
1035
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", type: NxeDropDownBoxComponent, isStandalone: true, selector: "nxe-drop-down-box", inputs: { dataSource: { classPropertyName: "dataSource", publicName: "dataSource", isSignal: true, isRequired: false, transformFunction: null }, displayExprInput: { classPropertyName: "displayExprInput", publicName: "displayExpr", isSignal: true, isRequired: false, transformFunction: null }, valueExprInput: { classPropertyName: "valueExprInput", publicName: "valueExpr", isSignal: true, isRequired: false, transformFunction: null }, placeholderInput: { classPropertyName: "placeholderInput", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, dropDownOptions: { classPropertyName: "dropDownOptions", publicName: "dropDownOptions", isSignal: true, isRequired: false, transformFunction: null }, showClearButtonInput: { classPropertyName: "showClearButtonInput", publicName: "showClearButton", isSignal: true, isRequired: false, transformFunction: null }, opened: { classPropertyName: "opened", publicName: "opened", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { opened: "openedChange", onClosed: "onClosed" }, host: { properties: { "class.nxe-focused": "focused()", "class.nxe-invalid": "invalidState()", "class.nxe-open": "open()" }, classAttribute: "nxe-widget nxe-editor nxe-dropdownbox" }, providers: [
|
|
695
1036
|
{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NxeDropDownBoxComponent), multi: true },
|
|
696
1037
|
{ provide: NXE_VALIDATION_TARGET, useExisting: forwardRef(() => NxeDropDownBoxComponent) },
|
|
697
|
-
], queries: [{ propertyName: "templates", predicate: NxeTemplateDirective, isSignal: true }, { propertyName: "options", first: true, predicate: NxeDropDownOptionsComponent, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "panelTemplate", first: true, predicate: ["panel"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<div class=\"nxe-editor-container\" (mousedown)=\"onFieldMousedown($event)\">\
|
|
1038
|
+
], queries: [{ propertyName: "templates", predicate: NxeTemplateDirective, isSignal: true }, { propertyName: "options", first: true, predicate: NxeDropDownOptionsComponent, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "panelTemplate", first: true, predicate: ["panel"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<div class=\"nxe-editor-container\" (mousedown)=\"onFieldMousedown($event)\">\n <input\n class=\"nxe-editor-input nxe-dropdownbox-input\"\n [attr.tabindex]=\"tabIndex()\"\n role=\"combobox\"\n aria-haspopup=\"dialog\"\n [attr.aria-expanded]=\"opened()\"\n [attr.aria-controls]=\"opened() ? panelId() : null\"\n [id]=\"controlId()\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-labelledby]=\"ariaLabelledBy() || null\"\n [attr.aria-invalid]=\"invalidState() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n type=\"text\"\n readonly\n [value]=\"displayText()\"\n [disabled]=\"disabled()\"\n [attr.placeholder]=\"placeholder() || null\"\n (focus)=\"focused.set(true)\"\n (blur)=\"onBlur()\" />\n @if (showClearButton() && hasValue() && !readOnly() && !disabled()) {\n <span class=\"nxe-select-clear\" (mousedown)=\"onClear($event)\" aria-hidden=\"true\">\n <!-- A \u00D816 disc with a 2px \u00D7 cut out: two 2px bars, each running corner to corner of the\n 8px square centred on the disc, drawn as a plus and turned 45\u00B0. -->\n <svg viewBox=\"0 0 18 18\" focusable=\"false\">\n <path fill-rule=\"evenodd\" transform=\"rotate(45 9 9)\"\n d=\"M9 1a8 8 0 1 1 0 16A8 8 0 1 1 9 1ZM8 3.343h2V8h4.657v2H10v4.657H8V10H3.343V8H8Z\" />\n </svg>\n </span>\n }\n\n @if (!readOnly()) {\n <span class=\"nxe-select-arrow\" aria-hidden=\"true\">\n <!-- An 8x4 triangle centred in the 18px box. -->\n <svg viewBox=\"0 0 18 18\" focusable=\"false\"><path d=\"M5 7h8l-4 4z\" /></svg>\n </span>\n }\n</div>\n\n<ng-content />\n\n@if (validationError(); as error) {\n @if (focused()) {\n <div class=\"nxe-invalid-message\">{{ error }}</div>\n }\n}\n\n<ng-template #panel>\n <div class=\"nxe-dropdownbox-panel\" [id]=\"panelId()\" role=\"dialog\" [style.width]=\"panelStyleWidth()\">\n @if (contentTemplateRef(); as tpl) {\n <ng-container *ngTemplateOutlet=\"tpl; context: { $implicit: value() }\" />\n }\n </div>\n</ng-template>\n", styles: ["nxe-drop-down-box{cursor:pointer}nxe-drop-down-box .nxe-dropdownbox-input{cursor:pointer}nxe-drop-down-box .nxe-select-clear{box-sizing:border-box;flex:0 0 34px;align-self:stretch;display:inline-flex;align-items:center;justify-content:center;width:34px;min-width:34px;padding:0;color:var(--nxe-ui-text-muted, #999);cursor:pointer}nxe-drop-down-box .nxe-select-clear svg{display:block;width:18px;height:18px;fill:currentColor}nxe-drop-down-box .nxe-select-arrow{box-sizing:border-box;position:relative;flex:0 0 34px;align-self:stretch;display:inline-flex;align-items:center;justify-content:center;width:34px;min-width:34px;padding:1px;border-radius:4px;background-clip:content-box;color:var(--nxe-ui-text, #333);cursor:pointer}nxe-drop-down-box .nxe-select-arrow svg{display:block;width:18px;height:18px;fill:currentColor}nxe-drop-down-box:hover:not(.nxe-disabled) .nxe-select-arrow,nxe-drop-down-box:active:not(.nxe-disabled) .nxe-select-arrow{background-color:var(--nxe-ui-neutral-150, #e6e6e6)}nxe-drop-down-box.nxe-open .nxe-select-arrow{background-color:var(--nxe-ui-neutral-350, #b3b3b3)}nxe-drop-down-box .nxe-editor-container:has(>.nxe-select-arrow) .nxe-editor-input{padding-inline-end:0}nxe-drop-down-box .nxe-editor-container:has(>.nxe-select-clear),nxe-drop-down-box .nxe-editor-container:has(>.nxe-select-arrow){--nxe-invalid-badge-inset: 38px}nxe-drop-down-box .nxe-editor-container:has(>.nxe-select-clear):has(>.nxe-select-arrow){--nxe-invalid-badge-inset: 72px}.nxe-dropdownbox-panel{box-sizing:border-box;background:var(--nxe-ui-surface, #fff);border:1px solid var(--nxe-ui-border, #ddd);border-radius:6px;box-shadow:var(--nxe-ui-popup-shadow, 0 6px 12px rgba(0, 0, 0, .175));overflow:auto;max-height:420px}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
698
1039
|
}
|
|
699
1040
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeDropDownBoxComponent, decorators: [{
|
|
700
1041
|
type: Component,
|
|
701
1042
|
args: [{ selector: 'nxe-drop-down-box', standalone: true, imports: [NgTemplateOutlet], host: {
|
|
702
1043
|
class: 'nxe-widget nxe-editor nxe-dropdownbox',
|
|
703
1044
|
'[class.nxe-focused]': 'focused()',
|
|
704
|
-
'[class.nxe-invalid]': '
|
|
1045
|
+
'[class.nxe-invalid]': 'invalidState()',
|
|
705
1046
|
'[class.nxe-open]': 'open()',
|
|
706
1047
|
}, providers: [
|
|
707
1048
|
{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NxeDropDownBoxComponent), multi: true },
|
|
708
1049
|
{ provide: NXE_VALIDATION_TARGET, useExisting: forwardRef(() => NxeDropDownBoxComponent) },
|
|
709
|
-
], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<div class=\"nxe-editor-container\" (mousedown)=\"onFieldMousedown($event)\">\
|
|
1050
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<div class=\"nxe-editor-container\" (mousedown)=\"onFieldMousedown($event)\">\n <input\n class=\"nxe-editor-input nxe-dropdownbox-input\"\n [attr.tabindex]=\"tabIndex()\"\n role=\"combobox\"\n aria-haspopup=\"dialog\"\n [attr.aria-expanded]=\"opened()\"\n [attr.aria-controls]=\"opened() ? panelId() : null\"\n [id]=\"controlId()\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-labelledby]=\"ariaLabelledBy() || null\"\n [attr.aria-invalid]=\"invalidState() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n type=\"text\"\n readonly\n [value]=\"displayText()\"\n [disabled]=\"disabled()\"\n [attr.placeholder]=\"placeholder() || null\"\n (focus)=\"focused.set(true)\"\n (blur)=\"onBlur()\" />\n @if (showClearButton() && hasValue() && !readOnly() && !disabled()) {\n <span class=\"nxe-select-clear\" (mousedown)=\"onClear($event)\" aria-hidden=\"true\">\n <!-- A \u00D816 disc with a 2px \u00D7 cut out: two 2px bars, each running corner to corner of the\n 8px square centred on the disc, drawn as a plus and turned 45\u00B0. -->\n <svg viewBox=\"0 0 18 18\" focusable=\"false\">\n <path fill-rule=\"evenodd\" transform=\"rotate(45 9 9)\"\n d=\"M9 1a8 8 0 1 1 0 16A8 8 0 1 1 9 1ZM8 3.343h2V8h4.657v2H10v4.657H8V10H3.343V8H8Z\" />\n </svg>\n </span>\n }\n\n @if (!readOnly()) {\n <span class=\"nxe-select-arrow\" aria-hidden=\"true\">\n <!-- An 8x4 triangle centred in the 18px box. -->\n <svg viewBox=\"0 0 18 18\" focusable=\"false\"><path d=\"M5 7h8l-4 4z\" /></svg>\n </span>\n }\n</div>\n\n<ng-content />\n\n@if (validationError(); as error) {\n @if (focused()) {\n <div class=\"nxe-invalid-message\">{{ error }}</div>\n }\n}\n\n<ng-template #panel>\n <div class=\"nxe-dropdownbox-panel\" [id]=\"panelId()\" role=\"dialog\" [style.width]=\"panelStyleWidth()\">\n @if (contentTemplateRef(); as tpl) {\n <ng-container *ngTemplateOutlet=\"tpl; context: { $implicit: value() }\" />\n }\n </div>\n</ng-template>\n", styles: ["nxe-drop-down-box{cursor:pointer}nxe-drop-down-box .nxe-dropdownbox-input{cursor:pointer}nxe-drop-down-box .nxe-select-clear{box-sizing:border-box;flex:0 0 34px;align-self:stretch;display:inline-flex;align-items:center;justify-content:center;width:34px;min-width:34px;padding:0;color:var(--nxe-ui-text-muted, #999);cursor:pointer}nxe-drop-down-box .nxe-select-clear svg{display:block;width:18px;height:18px;fill:currentColor}nxe-drop-down-box .nxe-select-arrow{box-sizing:border-box;position:relative;flex:0 0 34px;align-self:stretch;display:inline-flex;align-items:center;justify-content:center;width:34px;min-width:34px;padding:1px;border-radius:4px;background-clip:content-box;color:var(--nxe-ui-text, #333);cursor:pointer}nxe-drop-down-box .nxe-select-arrow svg{display:block;width:18px;height:18px;fill:currentColor}nxe-drop-down-box:hover:not(.nxe-disabled) .nxe-select-arrow,nxe-drop-down-box:active:not(.nxe-disabled) .nxe-select-arrow{background-color:var(--nxe-ui-neutral-150, #e6e6e6)}nxe-drop-down-box.nxe-open .nxe-select-arrow{background-color:var(--nxe-ui-neutral-350, #b3b3b3)}nxe-drop-down-box .nxe-editor-container:has(>.nxe-select-arrow) .nxe-editor-input{padding-inline-end:0}nxe-drop-down-box .nxe-editor-container:has(>.nxe-select-clear),nxe-drop-down-box .nxe-editor-container:has(>.nxe-select-arrow){--nxe-invalid-badge-inset: 38px}nxe-drop-down-box .nxe-editor-container:has(>.nxe-select-clear):has(>.nxe-select-arrow){--nxe-invalid-badge-inset: 72px}.nxe-dropdownbox-panel{box-sizing:border-box;background:var(--nxe-ui-surface, #fff);border:1px solid var(--nxe-ui-border, #ddd);border-radius:6px;box-shadow:var(--nxe-ui-popup-shadow, 0 6px 12px rgba(0, 0, 0, .175));overflow:auto;max-height:420px}\n"] }]
|
|
710
1051
|
}], ctorParameters: () => [], propDecorators: { panelTemplate: [{ type: i0.ViewChild, args: ['panel', { isSignal: true }] }], templates: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => NxeTemplateDirective), { isSignal: true }] }], options: [{ type: i0.ContentChild, args: [i0.forwardRef(() => NxeDropDownOptionsComponent), { isSignal: true }] }], dataSource: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataSource", required: false }] }], displayExprInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "displayExpr", required: false }] }], valueExprInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueExpr", required: false }] }], placeholderInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], dropDownOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "dropDownOptions", required: false }] }], showClearButtonInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "showClearButton", required: false }] }], opened: [{ type: i0.Input, args: [{ isSignal: true, alias: "opened", required: false }] }, { type: i0.Output, args: ["openedChange"] }], onClosed: [{ type: i0.Output, args: ["onClosed"] }] } });
|
|
711
1052
|
|
|
712
1053
|
/** DI token the menu provides so projected `nxe-menu-item`s can close it on click. */
|
|
@@ -731,6 +1072,20 @@ class NxeListComponent {
|
|
|
731
1072
|
/** DevExtreme `[elementAttr]` — `{ id, class }` applied to the host. */
|
|
732
1073
|
elementAttr = input(/* @ts-ignore */
|
|
733
1074
|
...(ngDevMode ? [undefined, { debugName: "elementAttr" }] : /* istanbul ignore next */ []));
|
|
1075
|
+
/**
|
|
1076
|
+
* dx `keyExpr` — the field identifying an item. Used for `@for` tracking, so a list whose
|
|
1077
|
+
* items are replaced by equal-but-not-identical objects keeps its DOM instead of rebuilding
|
|
1078
|
+
* every row. Falls back to the index when unset, which is the previous behaviour.
|
|
1079
|
+
*/
|
|
1080
|
+
keyExpr = input(undefined, /* @ts-ignore */
|
|
1081
|
+
...(ngDevMode ? [{ debugName: "keyExpr" }] : /* istanbul ignore next */ []));
|
|
1082
|
+
trackItem(item, index) {
|
|
1083
|
+
const key = this.keyExpr();
|
|
1084
|
+
if (!key || item == null || typeof item !== 'object') {
|
|
1085
|
+
return index;
|
|
1086
|
+
}
|
|
1087
|
+
return item[key] ?? index;
|
|
1088
|
+
}
|
|
734
1089
|
onItemClick = output();
|
|
735
1090
|
items = computed(() => this.dataSource() ?? [], /* @ts-ignore */
|
|
736
1091
|
...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
|
|
@@ -751,8 +1106,8 @@ class NxeListComponent {
|
|
|
751
1106
|
this.menu?.closeMenu();
|
|
752
1107
|
}
|
|
753
1108
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
754
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", type: NxeListComponent, isStandalone: true, selector: "nxe-list", inputs: { dataSource: { classPropertyName: "dataSource", publicName: "dataSource", isSignal: true, isRequired: false, transformFunction: null }, itemTemplate: { classPropertyName: "itemTemplate", publicName: "itemTemplate", isSignal: true, isRequired: false, transformFunction: null }, elementAttr: { classPropertyName: "elementAttr", publicName: "elementAttr", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onItemClick: "onItemClick" }, host: { properties: { "class": "'nxe-widget nxe-list ' + (elementAttr()?.class ?? '')", "attr.id": "elementAttr()?.id ?? null" } }, queries: [{ propertyName: "templates", predicate: NxeTemplateDirective, isSignal: true }], ngImport: i0, template: `
|
|
755
|
-
@for (item of items(); track $index; let last = $last) {
|
|
1109
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", type: NxeListComponent, isStandalone: true, selector: "nxe-list", inputs: { dataSource: { classPropertyName: "dataSource", publicName: "dataSource", isSignal: true, isRequired: false, transformFunction: null }, itemTemplate: { classPropertyName: "itemTemplate", publicName: "itemTemplate", isSignal: true, isRequired: false, transformFunction: null }, elementAttr: { classPropertyName: "elementAttr", publicName: "elementAttr", isSignal: true, isRequired: false, transformFunction: null }, keyExpr: { classPropertyName: "keyExpr", publicName: "keyExpr", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onItemClick: "onItemClick" }, host: { properties: { "class": "'nxe-widget nxe-list ' + (elementAttr()?.class ?? '')", "attr.id": "elementAttr()?.id ?? null" } }, queries: [{ propertyName: "templates", predicate: NxeTemplateDirective, isSignal: true }], ngImport: i0, template: `
|
|
1110
|
+
@for (item of items(); track trackItem(item, $index); let last = $last) {
|
|
756
1111
|
<div
|
|
757
1112
|
class="nxe-list-item-row"
|
|
758
1113
|
[class.nxe-state-disabled]="isDisabled(item)"
|
|
@@ -772,7 +1127,7 @@ class NxeListComponent {
|
|
|
772
1127
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeListComponent, decorators: [{
|
|
773
1128
|
type: Component,
|
|
774
1129
|
args: [{ selector: 'nxe-list', standalone: true, imports: [NgTemplateOutlet], template: `
|
|
775
|
-
@for (item of items(); track $index; let last = $last) {
|
|
1130
|
+
@for (item of items(); track trackItem(item, $index); let last = $last) {
|
|
776
1131
|
<div
|
|
777
1132
|
class="nxe-list-item-row"
|
|
778
1133
|
[class.nxe-state-disabled]="isDisabled(item)"
|
|
@@ -791,7 +1146,204 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImpor
|
|
|
791
1146
|
'[class]': "'nxe-widget nxe-list ' + (elementAttr()?.class ?? '')",
|
|
792
1147
|
'[attr.id]': 'elementAttr()?.id ?? null',
|
|
793
1148
|
}, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, styles: ["nxe-list{display:block}nxe-list .nxe-list-item-row{display:block;cursor:pointer}nxe-list .nxe-list-item-row.nxe-state-disabled{opacity:.5;cursor:default;-webkit-user-select:none;user-select:none}nxe-list .nxe-list-item-content{display:block}\n"] }]
|
|
794
|
-
}], propDecorators: { templates: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => NxeTemplateDirective), { isSignal: true }] }], dataSource: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataSource", required: false }] }], itemTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemTemplate", required: false }] }], elementAttr: [{ type: i0.Input, args: [{ isSignal: true, alias: "elementAttr", required: false }] }], onItemClick: [{ type: i0.Output, args: ["onItemClick"] }] } });
|
|
1149
|
+
}], propDecorators: { templates: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => NxeTemplateDirective), { isSignal: true }] }], dataSource: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataSource", required: false }] }], itemTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemTemplate", required: false }] }], elementAttr: [{ type: i0.Input, args: [{ isSignal: true, alias: "elementAttr", required: false }] }], keyExpr: [{ type: i0.Input, args: [{ isSignal: true, alias: "keyExpr", required: false }] }], onItemClick: [{ type: i0.Output, args: ["onItemClick"] }] } });
|
|
1150
|
+
|
|
1151
|
+
/**
|
|
1152
|
+
* `nxe-auto-complete` — drop-in replacement for DevExtreme `dx-autocomplete`.
|
|
1153
|
+
*
|
|
1154
|
+
* The distinction from `nxe-select-box` is the value contract, and it is the whole reason this
|
|
1155
|
+
* is a separate component: here the control value is the TEXT the user typed. A suggestion is a
|
|
1156
|
+
* shortcut for typing, not a constraint on what may be entered, so free text survives and
|
|
1157
|
+
* `valueExpr` only says which field of a picked item becomes that text.
|
|
1158
|
+
*
|
|
1159
|
+
* Deliberately not merged into the existing Material-shaped `nxe-autocomplete`: that one takes
|
|
1160
|
+
* projected `nxe-option` children rather than a `[dataSource]`, so the two have incompatible
|
|
1161
|
+
* content models and merging them would make every future change to one a risk to the other.
|
|
1162
|
+
*/
|
|
1163
|
+
class NxeAutoCompleteComponent extends NxeFormControlBase {
|
|
1164
|
+
overlay = inject(Overlay);
|
|
1165
|
+
viewContainerRef = inject(ViewContainerRef);
|
|
1166
|
+
dropdownTemplate = viewChild.required('dropdown', /* @ts-ignore */
|
|
1167
|
+
...(ngDevMode ? [{ debugName: "dropdownTemplate" }] : /* istanbul ignore next */ []));
|
|
1168
|
+
inputEl = viewChild('input', /* @ts-ignore */
|
|
1169
|
+
...(ngDevMode ? [{ debugName: "inputEl" }] : /* istanbul ignore next */ []));
|
|
1170
|
+
dataSource = input([], /* @ts-ignore */
|
|
1171
|
+
...(ngDevMode ? [{ debugName: "dataSource" }] : /* istanbul ignore next */ []));
|
|
1172
|
+
/** Field of a picked item that becomes the value (dx default `lovName`). */
|
|
1173
|
+
valueExpr = input('lovName', /* @ts-ignore */
|
|
1174
|
+
...(ngDevMode ? [{ debugName: "valueExpr" }] : /* istanbul ignore next */ []));
|
|
1175
|
+
/** Field matched against the query; defaults to {@link valueExpr}, as dx does. */
|
|
1176
|
+
searchExprInput = input(undefined, { ...(ngDevMode ? { debugName: "searchExprInput" } : /* istanbul ignore next */ {}), alias: 'searchExpr' });
|
|
1177
|
+
searchExpr = computed(() => this.searchExprInput() ?? this.valueExpr(), /* @ts-ignore */
|
|
1178
|
+
...(ngDevMode ? [{ debugName: "searchExpr" }] : /* istanbul ignore next */ []));
|
|
1179
|
+
searchMode = input('contains', /* @ts-ignore */
|
|
1180
|
+
...(ngDevMode ? [{ debugName: "searchMode" }] : /* istanbul ignore next */ []));
|
|
1181
|
+
/** Characters required before suggestions appear. dx defaults to 1; 0 shows all on focus. */
|
|
1182
|
+
minSearchLength = input(1, /* @ts-ignore */
|
|
1183
|
+
...(ngDevMode ? [{ debugName: "minSearchLength" }] : /* istanbul ignore next */ []));
|
|
1184
|
+
/** dx caps the suggestion list; beyond this the user is expected to keep typing. */
|
|
1185
|
+
maxItemCount = input(10, /* @ts-ignore */
|
|
1186
|
+
...(ngDevMode ? [{ debugName: "maxItemCount" }] : /* istanbul ignore next */ []));
|
|
1187
|
+
placeholder = input('', /* @ts-ignore */
|
|
1188
|
+
...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
|
|
1189
|
+
showClearButton = input(false, { ...(ngDevMode ? { debugName: "showClearButton" } : /* istanbul ignore next */ {}), transform: nxeOptionalBoolean });
|
|
1190
|
+
onSelectionChanged = output();
|
|
1191
|
+
focused = signal(false, /* @ts-ignore */
|
|
1192
|
+
...(ngDevMode ? [{ debugName: "focused" }] : /* istanbul ignore next */ []));
|
|
1193
|
+
open = signal(false, /* @ts-ignore */
|
|
1194
|
+
...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
|
|
1195
|
+
activeIndex = signal(-1, /* @ts-ignore */
|
|
1196
|
+
...(ngDevMode ? [{ debugName: "activeIndex" }] : /* istanbul ignore next */ []));
|
|
1197
|
+
panelWidth = signal(0, /* @ts-ignore */
|
|
1198
|
+
...(ngDevMode ? [{ debugName: "panelWidth" }] : /* istanbul ignore next */ []));
|
|
1199
|
+
/** dx `.instance` facade — `focus()` is what the autofocus directive calls. */
|
|
1200
|
+
get instance() {
|
|
1201
|
+
return this;
|
|
1202
|
+
}
|
|
1203
|
+
overlayRef;
|
|
1204
|
+
closeSub;
|
|
1205
|
+
flipSub;
|
|
1206
|
+
text = computed(() => this.value() ?? '', /* @ts-ignore */
|
|
1207
|
+
...(ngDevMode ? [{ debugName: "text" }] : /* istanbul ignore next */ []));
|
|
1208
|
+
itemText(item) {
|
|
1209
|
+
return nxeResolveText(this.valueExpr(), item);
|
|
1210
|
+
}
|
|
1211
|
+
searchText(item) {
|
|
1212
|
+
return nxeResolveText(this.searchExpr(), item);
|
|
1213
|
+
}
|
|
1214
|
+
suggestions = computed(() => {
|
|
1215
|
+
const query = (this.value() ?? '').toLowerCase();
|
|
1216
|
+
if (query.length < this.minSearchLength()) {
|
|
1217
|
+
return [];
|
|
1218
|
+
}
|
|
1219
|
+
const mode = this.searchMode();
|
|
1220
|
+
const matches = (this.dataSource() ?? []).filter((item) => {
|
|
1221
|
+
const candidate = this.searchText(item).toLowerCase();
|
|
1222
|
+
if (mode === 'startswith') {
|
|
1223
|
+
return candidate.startsWith(query);
|
|
1224
|
+
}
|
|
1225
|
+
if (mode === 'equals') {
|
|
1226
|
+
return candidate === query;
|
|
1227
|
+
}
|
|
1228
|
+
return candidate.includes(query);
|
|
1229
|
+
});
|
|
1230
|
+
return matches.slice(0, this.maxItemCount());
|
|
1231
|
+
}, /* @ts-ignore */
|
|
1232
|
+
...(ngDevMode ? [{ debugName: "suggestions" }] : /* istanbul ignore next */ []));
|
|
1233
|
+
onFieldInput(event) {
|
|
1234
|
+
// The typed text IS the value: an autocomplete constrains nothing.
|
|
1235
|
+
this.commitValue(event.target.value, event);
|
|
1236
|
+
this.activeIndex.set(-1);
|
|
1237
|
+
this.openPanel();
|
|
1238
|
+
}
|
|
1239
|
+
onFocus() {
|
|
1240
|
+
this.focused.set(true);
|
|
1241
|
+
this.openPanel();
|
|
1242
|
+
}
|
|
1243
|
+
onBlur() {
|
|
1244
|
+
this.focused.set(false);
|
|
1245
|
+
this.markAsTouched();
|
|
1246
|
+
this.closePanel();
|
|
1247
|
+
}
|
|
1248
|
+
pick(item, event) {
|
|
1249
|
+
event.preventDefault();
|
|
1250
|
+
this.commitValue(this.itemText(item), event);
|
|
1251
|
+
this.onSelectionChanged.emit({ selectedItem: item, value: this.value() });
|
|
1252
|
+
this.closePanel();
|
|
1253
|
+
}
|
|
1254
|
+
onKeydown(event) {
|
|
1255
|
+
if (event.key === 'Escape') {
|
|
1256
|
+
this.closePanel();
|
|
1257
|
+
return;
|
|
1258
|
+
}
|
|
1259
|
+
const items = this.suggestions();
|
|
1260
|
+
if (!items.length) {
|
|
1261
|
+
return;
|
|
1262
|
+
}
|
|
1263
|
+
if (event.key === 'ArrowDown') {
|
|
1264
|
+
event.preventDefault();
|
|
1265
|
+
this.openPanel();
|
|
1266
|
+
this.activeIndex.update((i) => Math.min(i + 1, items.length - 1));
|
|
1267
|
+
}
|
|
1268
|
+
else if (event.key === 'ArrowUp') {
|
|
1269
|
+
event.preventDefault();
|
|
1270
|
+
this.activeIndex.update((i) => Math.max(i - 1, 0));
|
|
1271
|
+
}
|
|
1272
|
+
else if (event.key === 'Enter' && this.activeIndex() >= 0) {
|
|
1273
|
+
this.pick(items[this.activeIndex()], event);
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
onClear(event) {
|
|
1277
|
+
event.preventDefault();
|
|
1278
|
+
this.commitValue('', event);
|
|
1279
|
+
this.closePanel();
|
|
1280
|
+
}
|
|
1281
|
+
focus() {
|
|
1282
|
+
this.inputEl()?.nativeElement.focus();
|
|
1283
|
+
}
|
|
1284
|
+
openPanel() {
|
|
1285
|
+
if (this.readOnly() || this.disabled() || !this.suggestions().length) {
|
|
1286
|
+
this.closePanel();
|
|
1287
|
+
return;
|
|
1288
|
+
}
|
|
1289
|
+
if (this.overlayRef) {
|
|
1290
|
+
return;
|
|
1291
|
+
}
|
|
1292
|
+
const host = this.element;
|
|
1293
|
+
this.panelWidth.set(host.getBoundingClientRect().width);
|
|
1294
|
+
this.overlayRef = this.overlay.create({
|
|
1295
|
+
positionStrategy: this.overlay
|
|
1296
|
+
.position()
|
|
1297
|
+
.flexibleConnectedTo(host)
|
|
1298
|
+
.withPositions([
|
|
1299
|
+
{ originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top' },
|
|
1300
|
+
{ originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom' },
|
|
1301
|
+
]),
|
|
1302
|
+
scrollStrategy: this.overlay.scrollStrategies.reposition(),
|
|
1303
|
+
panelClass: nxePane('nxe-autocomplete-box-pane'),
|
|
1304
|
+
});
|
|
1305
|
+
this.overlayRef.attach(new TemplatePortal(this.dropdownTemplate(), this.viewContainerRef));
|
|
1306
|
+
this.flipSub = trackPaneFlip(this.overlayRef);
|
|
1307
|
+
this.open.set(true);
|
|
1308
|
+
}
|
|
1309
|
+
closePanel() {
|
|
1310
|
+
this.overlayRef?.dispose();
|
|
1311
|
+
this.overlayRef = undefined;
|
|
1312
|
+
this.closeSub?.unsubscribe();
|
|
1313
|
+
this.flipSub?.unsubscribe();
|
|
1314
|
+
this.open.set(false);
|
|
1315
|
+
this.activeIndex.set(-1);
|
|
1316
|
+
}
|
|
1317
|
+
ngOnDestroy() {
|
|
1318
|
+
this.closePanel();
|
|
1319
|
+
}
|
|
1320
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeAutoCompleteComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
1321
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", type: NxeAutoCompleteComponent, isStandalone: true, selector: "nxe-auto-complete", inputs: { dataSource: { classPropertyName: "dataSource", publicName: "dataSource", isSignal: true, isRequired: false, transformFunction: null }, valueExpr: { classPropertyName: "valueExpr", publicName: "valueExpr", isSignal: true, isRequired: false, transformFunction: null }, searchExprInput: { classPropertyName: "searchExprInput", publicName: "searchExpr", isSignal: true, isRequired: false, transformFunction: null }, searchMode: { classPropertyName: "searchMode", publicName: "searchMode", isSignal: true, isRequired: false, transformFunction: null }, minSearchLength: { classPropertyName: "minSearchLength", publicName: "minSearchLength", isSignal: true, isRequired: false, transformFunction: null }, maxItemCount: { classPropertyName: "maxItemCount", publicName: "maxItemCount", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, showClearButton: { classPropertyName: "showClearButton", publicName: "showClearButton", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSelectionChanged: "onSelectionChanged" }, host: { listeners: { "keydown": "onKeydown($event)" }, properties: { "class.nxe-focused": "focused()", "class.nxe-invalid": "invalidState()", "class.nxe-open": "open()" }, classAttribute: "nxe-widget nxe-editor nxe-autocomplete-box" }, providers: [
|
|
1322
|
+
{
|
|
1323
|
+
provide: NG_VALUE_ACCESSOR,
|
|
1324
|
+
useExisting: forwardRef(() => NxeAutoCompleteComponent),
|
|
1325
|
+
multi: true,
|
|
1326
|
+
},
|
|
1327
|
+
{ provide: NXE_VALIDATION_TARGET, useExisting: forwardRef(() => NxeAutoCompleteComponent) },
|
|
1328
|
+
], viewQueries: [{ propertyName: "dropdownTemplate", first: true, predicate: ["dropdown"], descendants: true, isSignal: true }, { propertyName: "inputEl", first: true, predicate: ["input"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<div class=\"nxe-editor-container\">\n <input\n #input\n class=\"nxe-editor-input nxe-autocomplete-input\"\n [attr.tabindex]=\"tabIndex()\"\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"open() ? panelId() : null\"\n [attr.aria-activedescendant]=\"open() && activeIndex() >= 0 ? optionId(activeIndex()) : null\"\n [id]=\"controlId()\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-labelledby]=\"ariaLabelledBy() || null\"\n [attr.aria-invalid]=\"invalidState() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n type=\"text\"\n [value]=\"text()\"\n [readOnly]=\"readOnly()\"\n [disabled]=\"disabled()\"\n [attr.placeholder]=\"placeholder() || null\"\n autocomplete=\"off\"\n (input)=\"onFieldInput($event)\"\n (focus)=\"onFocus()\"\n (blur)=\"onBlur()\" />\n\n @if (showClearButton() && text() && !readOnly() && !disabled()) {\n <span class=\"nxe-select-clear\" (mousedown)=\"onClear($event)\" aria-hidden=\"true\">\n <!-- A \u00D816 disc with a 2px \u00D7 cut out: two 2px bars, each running corner to corner of the\n 8px square centred on the disc, drawn as a plus and turned 45\u00B0. -->\n <svg viewBox=\"0 0 18 18\" focusable=\"false\">\n <path fill-rule=\"evenodd\" transform=\"rotate(45 9 9)\"\n d=\"M9 1a8 8 0 1 1 0 16A8 8 0 1 1 9 1ZM8 3.343h2V8h4.657v2H10v4.657H8V10H3.343V8H8Z\" />\n </svg>\n </span>\n }\n</div>\n\n<ng-content />\n\n@if (validationError(); as error) {\n @if (focused()) {\n <div class=\"nxe-invalid-message\">{{ error }}</div>\n }\n}\n\n<ng-template #dropdown>\n <div class=\"nxe-autocomplete-box-panel\" [style.width.px]=\"panelWidth()\"\n [id]=\"panelId()\"\n role=\"listbox\">\n @for (item of suggestions(); track $index; let i = $index) {\n <div\n class=\"nxe-list-item\"\n [id]=\"optionId(i)\"\n role=\"option\"\n [attr.aria-selected]=\"i === activeIndex()\"\n [class.nxe-list-item-active]=\"i === activeIndex()\"\n (mousedown)=\"pick(item, $event)\">\n {{ itemText(item) }}\n </div>\n }\n </div>\n</ng-template>\n", styles: ["nxe-auto-complete .nxe-select-clear{box-sizing:border-box;flex:0 0 34px;align-self:stretch;display:inline-flex;align-items:center;justify-content:center;width:34px;min-width:34px;padding:0;color:var(--nxe-ui-text-muted, #999);cursor:pointer}nxe-auto-complete .nxe-select-clear svg{display:block;width:18px;height:18px;fill:currentColor}nxe-auto-complete .nxe-select-arrow{box-sizing:border-box;position:relative;flex:0 0 34px;align-self:stretch;display:inline-flex;align-items:center;justify-content:center;width:34px;min-width:34px;padding:1px;border-radius:4px;background-clip:content-box;color:var(--nxe-ui-text, #333);cursor:pointer}nxe-auto-complete .nxe-select-arrow svg{display:block;width:18px;height:18px;fill:currentColor}nxe-auto-complete:hover:not(.nxe-disabled) .nxe-select-arrow,nxe-auto-complete:active:not(.nxe-disabled) .nxe-select-arrow{background-color:var(--nxe-ui-neutral-150, #e6e6e6)}nxe-auto-complete.nxe-open .nxe-select-arrow{background-color:var(--nxe-ui-neutral-350, #b3b3b3)}nxe-auto-complete .nxe-editor-container:has(>.nxe-select-arrow) .nxe-editor-input{padding-inline-end:0}nxe-auto-complete .nxe-editor-container:has(>.nxe-select-clear),nxe-auto-complete .nxe-editor-container:has(>.nxe-select-arrow){--nxe-invalid-badge-inset: 38px}nxe-auto-complete .nxe-editor-container:has(>.nxe-select-clear):has(>.nxe-select-arrow){--nxe-invalid-badge-inset: 72px}.nxe-autocomplete-box-panel{max-height:300px;overflow-y:auto;background:var(--nxe-ui-surface, #fff);border:1px solid var(--nxe-ui-border, #ddd);box-sizing:border-box}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
1329
|
+
}
|
|
1330
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeAutoCompleteComponent, decorators: [{
|
|
1331
|
+
type: Component,
|
|
1332
|
+
args: [{ selector: 'nxe-auto-complete', standalone: true, host: {
|
|
1333
|
+
class: 'nxe-widget nxe-editor nxe-autocomplete-box',
|
|
1334
|
+
'[class.nxe-focused]': 'focused()',
|
|
1335
|
+
'[class.nxe-invalid]': 'invalidState()',
|
|
1336
|
+
'[class.nxe-open]': 'open()',
|
|
1337
|
+
'(keydown)': 'onKeydown($event)',
|
|
1338
|
+
}, providers: [
|
|
1339
|
+
{
|
|
1340
|
+
provide: NG_VALUE_ACCESSOR,
|
|
1341
|
+
useExisting: forwardRef(() => NxeAutoCompleteComponent),
|
|
1342
|
+
multi: true,
|
|
1343
|
+
},
|
|
1344
|
+
{ provide: NXE_VALIDATION_TARGET, useExisting: forwardRef(() => NxeAutoCompleteComponent) },
|
|
1345
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<div class=\"nxe-editor-container\">\n <input\n #input\n class=\"nxe-editor-input nxe-autocomplete-input\"\n [attr.tabindex]=\"tabIndex()\"\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"open() ? panelId() : null\"\n [attr.aria-activedescendant]=\"open() && activeIndex() >= 0 ? optionId(activeIndex()) : null\"\n [id]=\"controlId()\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-labelledby]=\"ariaLabelledBy() || null\"\n [attr.aria-invalid]=\"invalidState() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n type=\"text\"\n [value]=\"text()\"\n [readOnly]=\"readOnly()\"\n [disabled]=\"disabled()\"\n [attr.placeholder]=\"placeholder() || null\"\n autocomplete=\"off\"\n (input)=\"onFieldInput($event)\"\n (focus)=\"onFocus()\"\n (blur)=\"onBlur()\" />\n\n @if (showClearButton() && text() && !readOnly() && !disabled()) {\n <span class=\"nxe-select-clear\" (mousedown)=\"onClear($event)\" aria-hidden=\"true\">\n <!-- A \u00D816 disc with a 2px \u00D7 cut out: two 2px bars, each running corner to corner of the\n 8px square centred on the disc, drawn as a plus and turned 45\u00B0. -->\n <svg viewBox=\"0 0 18 18\" focusable=\"false\">\n <path fill-rule=\"evenodd\" transform=\"rotate(45 9 9)\"\n d=\"M9 1a8 8 0 1 1 0 16A8 8 0 1 1 9 1ZM8 3.343h2V8h4.657v2H10v4.657H8V10H3.343V8H8Z\" />\n </svg>\n </span>\n }\n</div>\n\n<ng-content />\n\n@if (validationError(); as error) {\n @if (focused()) {\n <div class=\"nxe-invalid-message\">{{ error }}</div>\n }\n}\n\n<ng-template #dropdown>\n <div class=\"nxe-autocomplete-box-panel\" [style.width.px]=\"panelWidth()\"\n [id]=\"panelId()\"\n role=\"listbox\">\n @for (item of suggestions(); track $index; let i = $index) {\n <div\n class=\"nxe-list-item\"\n [id]=\"optionId(i)\"\n role=\"option\"\n [attr.aria-selected]=\"i === activeIndex()\"\n [class.nxe-list-item-active]=\"i === activeIndex()\"\n (mousedown)=\"pick(item, $event)\">\n {{ itemText(item) }}\n </div>\n }\n </div>\n</ng-template>\n", styles: ["nxe-auto-complete .nxe-select-clear{box-sizing:border-box;flex:0 0 34px;align-self:stretch;display:inline-flex;align-items:center;justify-content:center;width:34px;min-width:34px;padding:0;color:var(--nxe-ui-text-muted, #999);cursor:pointer}nxe-auto-complete .nxe-select-clear svg{display:block;width:18px;height:18px;fill:currentColor}nxe-auto-complete .nxe-select-arrow{box-sizing:border-box;position:relative;flex:0 0 34px;align-self:stretch;display:inline-flex;align-items:center;justify-content:center;width:34px;min-width:34px;padding:1px;border-radius:4px;background-clip:content-box;color:var(--nxe-ui-text, #333);cursor:pointer}nxe-auto-complete .nxe-select-arrow svg{display:block;width:18px;height:18px;fill:currentColor}nxe-auto-complete:hover:not(.nxe-disabled) .nxe-select-arrow,nxe-auto-complete:active:not(.nxe-disabled) .nxe-select-arrow{background-color:var(--nxe-ui-neutral-150, #e6e6e6)}nxe-auto-complete.nxe-open .nxe-select-arrow{background-color:var(--nxe-ui-neutral-350, #b3b3b3)}nxe-auto-complete .nxe-editor-container:has(>.nxe-select-arrow) .nxe-editor-input{padding-inline-end:0}nxe-auto-complete .nxe-editor-container:has(>.nxe-select-clear),nxe-auto-complete .nxe-editor-container:has(>.nxe-select-arrow){--nxe-invalid-badge-inset: 38px}nxe-auto-complete .nxe-editor-container:has(>.nxe-select-clear):has(>.nxe-select-arrow){--nxe-invalid-badge-inset: 72px}.nxe-autocomplete-box-panel{max-height:300px;overflow-y:auto;background:var(--nxe-ui-surface, #fff);border:1px solid var(--nxe-ui-border, #ddd);box-sizing:border-box}\n"] }]
|
|
1346
|
+
}], propDecorators: { dropdownTemplate: [{ type: i0.ViewChild, args: ['dropdown', { isSignal: true }] }], inputEl: [{ type: i0.ViewChild, args: ['input', { isSignal: true }] }], dataSource: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataSource", required: false }] }], valueExpr: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueExpr", required: false }] }], searchExprInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchExpr", required: false }] }], searchMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchMode", required: false }] }], minSearchLength: [{ type: i0.Input, args: [{ isSignal: true, alias: "minSearchLength", required: false }] }], maxItemCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxItemCount", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], showClearButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "showClearButton", required: false }] }], onSelectionChanged: [{ type: i0.Output, args: ["onSelectionChanged"] }] } });
|
|
795
1347
|
|
|
796
1348
|
/** DI token the autocomplete provides so projected `nxe-option`s can close it on click. */
|
|
797
1349
|
const NXE_AUTOCOMPLETE = new InjectionToken('NXE_AUTOCOMPLETE');
|
|
@@ -949,5 +1501,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImpor
|
|
|
949
1501
|
* Generated bundle index. Do not edit.
|
|
950
1502
|
*/
|
|
951
1503
|
|
|
952
|
-
export { NXE_AUTOCOMPLETE, NxeAutocompleteComponent, NxeAutocompleteTriggerDirective, NxeDropDownBoxComponent, NxeDropDownOptionsComponent, NxeListComponent, NxeOptionComponent, NxeSelectBoxComponent, NxeTagBoxComponent };
|
|
1504
|
+
export { NXE_AUTOCOMPLETE, NXE_MENU, NxeAutoCompleteComponent, NxeAutocompleteComponent, NxeAutocompleteTriggerDirective, NxeDropDownBoxComponent, NxeDropDownOptionsComponent, NxeListComponent, NxeOptionComponent, NxePickerSearch, NxeSelectBoxComponent, NxeTagBoxComponent, nxeIsPickerStore };
|
|
953
1505
|
//# sourceMappingURL=nexterajs-ui-dropdowns.mjs.map
|