@nexterajs/ui 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +16 -0
  3. package/README.md +64 -0
  4. package/fesm2022/nexterajs-ui-animations.mjs +184 -0
  5. package/fesm2022/nexterajs-ui-animations.mjs.map +1 -0
  6. package/fesm2022/nexterajs-ui-core.mjs +326 -0
  7. package/fesm2022/nexterajs-ui-core.mjs.map +1 -0
  8. package/fesm2022/nexterajs-ui-dropdowns.mjs +953 -0
  9. package/fesm2022/nexterajs-ui-dropdowns.mjs.map +1 -0
  10. package/fesm2022/nexterajs-ui-excel.mjs +779 -0
  11. package/fesm2022/nexterajs-ui-excel.mjs.map +1 -0
  12. package/fesm2022/nexterajs-ui-file-uploader.mjs +125 -0
  13. package/fesm2022/nexterajs-ui-file-uploader.mjs.map +1 -0
  14. package/fesm2022/nexterajs-ui-forms.mjs +1113 -0
  15. package/fesm2022/nexterajs-ui-forms.mjs.map +1 -0
  16. package/fesm2022/nexterajs-ui-grid.mjs +4145 -0
  17. package/fesm2022/nexterajs-ui-grid.mjs.map +1 -0
  18. package/fesm2022/nexterajs-ui-intl.mjs +377 -0
  19. package/fesm2022/nexterajs-ui-intl.mjs.map +1 -0
  20. package/fesm2022/nexterajs-ui-pivot.mjs +4739 -0
  21. package/fesm2022/nexterajs-ui-pivot.mjs.map +1 -0
  22. package/fesm2022/nexterajs-ui-styling.mjs +59 -0
  23. package/fesm2022/nexterajs-ui-styling.mjs.map +1 -0
  24. package/fesm2022/nexterajs-ui-tree-view.mjs +202 -0
  25. package/fesm2022/nexterajs-ui-tree-view.mjs.map +1 -0
  26. package/fesm2022/nexterajs-ui-validation.mjs +269 -0
  27. package/fesm2022/nexterajs-ui-validation.mjs.map +1 -0
  28. package/fesm2022/nexterajs-ui.mjs +23 -0
  29. package/fesm2022/nexterajs-ui.mjs.map +1 -0
  30. package/package.json +100 -0
  31. package/styles/_button-directives.scss +39 -0
  32. package/styles/_dropdown.scss +113 -0
  33. package/styles/_editor.scss +146 -0
  34. package/styles/_mixins.scss +2 -0
  35. package/styles/_tokens.scss +77 -0
  36. package/styles/_widget.scss +58 -0
  37. package/styles/index.scss +83 -0
  38. package/types/nexterajs-ui-animations.d.ts +22 -0
  39. package/types/nexterajs-ui-core.d.ts +328 -0
  40. package/types/nexterajs-ui-dropdowns.d.ts +334 -0
  41. package/types/nexterajs-ui-excel.d.ts +159 -0
  42. package/types/nexterajs-ui-file-uploader.d.ts +47 -0
  43. package/types/nexterajs-ui-forms.d.ts +296 -0
  44. package/types/nexterajs-ui-grid.d.ts +1485 -0
  45. package/types/nexterajs-ui-intl.d.ts +125 -0
  46. package/types/nexterajs-ui-pivot.d.ts +1470 -0
  47. package/types/nexterajs-ui-styling.d.ts +40 -0
  48. package/types/nexterajs-ui-tree-view.d.ts +69 -0
  49. package/types/nexterajs-ui-validation.d.ts +102 -0
  50. package/types/nexterajs-ui.d.ts +12 -0
@@ -0,0 +1,953 @@
1
+ import { Overlay } from '@angular/cdk/overlay';
2
+ import { TemplatePortal } from '@angular/cdk/portal';
3
+ import { NgTemplateOutlet } from '@angular/common';
4
+ import * as i0 from '@angular/core';
5
+ import { inject, ViewContainerRef, viewChild, contentChildren, input, computed, booleanAttribute, output, signal, forwardRef, ViewEncapsulation, ChangeDetectionStrategy, Component, contentChild, model, Injector, InjectionToken, ElementRef, TemplateRef, Directive } from '@angular/core';
6
+ import { NG_VALUE_ACCESSOR } from '@angular/forms';
7
+ import { merge, filter, distinctUntilChanged, Subject } from 'rxjs';
8
+ import { NxeFormControlBase, NxeTemplateDirective, nxeOptionalBoolean, NXE_VALIDATION_TARGET } from '@nexterajs/ui/core';
9
+ import { nxePane, trackPaneFlip } from '@nexterajs/ui/styling';
10
+ import { toObservable, takeUntilDestroyed } from '@angular/core/rxjs-interop';
11
+
12
+ /**
13
+ * `nxe-select-box` — drop-in replacement for DevExtreme `dx-select-box`.
14
+ *
15
+ * A signal-CVA single-select dropdown over a plain array (`[dataSource]`). It bakes in the app's
16
+ * global `dxSelectBox.defaultOptions` (`displayExpr='lovName'`, `valueExpr='lovCode'`,
17
+ * `searchEnabled`, `showClearButton`), so the bound control value is the `valueExpr` field
18
+ * (a string code) and the field shows the `displayExpr` field. Dropdown is a CDK overlay with
19
+ * an in-field search filter. Emits `(valueChange)` (the code) and `(onSelectionChanged)`
20
+ * (`{selectedItem}`). Hosts a projected `nxe-validator` via {@link NXE_VALIDATION_TARGET}.
21
+ */
22
+ class NxeSelectBoxComponent extends NxeFormControlBase {
23
+ overlay = inject(Overlay);
24
+ viewContainerRef = inject(ViewContainerRef);
25
+ dropdownTemplate = viewChild.required('dropdown', /* @ts-ignore */
26
+ ...(ngDevMode ? [{ debugName: "dropdownTemplate" }] : /* istanbul ignore next */ []));
27
+ inputEl = viewChild('input', /* @ts-ignore */
28
+ ...(ngDevMode ? [{ debugName: "inputEl" }] : /* istanbul ignore next */ []));
29
+ templates = contentChildren(NxeTemplateDirective, /* @ts-ignore */
30
+ ...(ngDevMode ? [{ debugName: "templates" }] : /* istanbul ignore next */ []));
31
+ /** The source array (plain JS array of `Lov`-shaped objects, or primitives). */
32
+ dataSource = input([], /* @ts-ignore */
33
+ ...(ngDevMode ? [{ debugName: "dataSource" }] : /* istanbul ignore next */ []));
34
+ /** Field shown in the box/list (DevExtreme default `lovName`). Empty => the item itself. */
35
+ displayExprInput = input(undefined, { ...(ngDevMode ? { debugName: "displayExprInput" } : /* istanbul ignore next */ {}), alias: 'displayExpr' });
36
+ displayExpr = computed(() => this.displayExprInput() ?? this.nxeDefaults.selectBox?.displayExpr ?? 'lovName', /* @ts-ignore */
37
+ ...(ngDevMode ? [{ debugName: "displayExpr" }] : /* istanbul ignore next */ []));
38
+ /** Field bound as the control value (DevExtreme default `lovCode`). Empty => the whole item. */
39
+ valueExprInput = input(undefined, { ...(ngDevMode ? { debugName: "valueExprInput" } : /* istanbul ignore next */ {}), alias: 'valueExpr' });
40
+ valueExpr = computed(() => this.valueExprInput() ?? this.nxeDefaults.selectBox?.valueExpr ?? 'lovCode', /* @ts-ignore */
41
+ ...(ngDevMode ? [{ debugName: "valueExpr" }] : /* istanbul ignore next */ []));
42
+ /** Field matched while searching (DevExtreme default `lovName`). */
43
+ searchExprInput = input(undefined, { ...(ngDevMode ? { debugName: "searchExprInput" } : /* istanbul ignore next */ {}), alias: 'searchExpr' });
44
+ searchExpr = computed(() => this.searchExprInput() ?? this.nxeDefaults.selectBox?.searchExpr ?? 'lovName', /* @ts-ignore */
45
+ ...(ngDevMode ? [{ debugName: "searchExpr" }] : /* istanbul ignore next */ []));
46
+ searchEnabledInput = input(undefined, { ...(ngDevMode ? { debugName: "searchEnabledInput" } : /* istanbul ignore next */ {}), alias: 'searchEnabled', transform: nxeOptionalBoolean });
47
+ searchEnabled = computed(() => this.searchEnabledInput() ?? this.nxeDefaults.selectBox?.searchEnabled ?? true, /* @ts-ignore */
48
+ ...(ngDevMode ? [{ debugName: "searchEnabled" }] : /* istanbul ignore next */ []));
49
+ showClearButtonInput = input(undefined, { ...(ngDevMode ? { debugName: "showClearButtonInput" } : /* istanbul ignore next */ {}), alias: 'showClearButton', transform: nxeOptionalBoolean });
50
+ showClearButton = computed(() => this.showClearButtonInput() ?? this.nxeDefaults.selectBox?.showClearButton ?? true, /* @ts-ignore */
51
+ ...(ngDevMode ? [{ debugName: "showClearButton" }] : /* istanbul ignore next */ []));
52
+ /** dx parity: dx-select-box shows its drop-down arrow unless showDropDownButton=false. */
53
+ showDropDownButtonInput = input(undefined, { ...(ngDevMode ? { debugName: "showDropDownButtonInput" } : /* istanbul ignore next */ {}), alias: 'showDropDownButton', transform: nxeOptionalBoolean });
54
+ showDropDownButton = computed(() => this.showDropDownButtonInput() ?? this.nxeDefaults.selectBox?.showDropDownButton ?? true, /* @ts-ignore */
55
+ ...(ngDevMode ? [{ debugName: "showDropDownButton" }] : /* istanbul ignore next */ []));
56
+ placeholderInput = input(undefined, { ...(ngDevMode ? { debugName: "placeholderInput" } : /* istanbul ignore next */ {}), alias: 'placeholder' });
57
+ placeholder = computed(() => this.placeholderInput() ?? this.nxeDefaults.selectBox?.placeholder ?? '', /* @ts-ignore */
58
+ ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
59
+ /** Named template ('displayItem') for dropdown rows; `$implicit` = the item. */
60
+ itemTemplate = input(/* @ts-ignore */
61
+ ...(ngDevMode ? [undefined, { debugName: "itemTemplate" }] : /* istanbul ignore next */ []));
62
+ /** Named template ('inputField') for the closed field; `$implicit` = the selected item. */
63
+ fieldTemplate = input(/* @ts-ignore */
64
+ ...(ngDevMode ? [undefined, { debugName: "fieldTemplate" }] : /* istanbul ignore next */ []));
65
+ /** DevExtreme parity, cosmetic. */
66
+ hoverStateEnabled = input(true, { ...(ngDevMode ? { debugName: "hoverStateEnabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
67
+ onSelectionChanged = output();
68
+ focused = signal(false, /* @ts-ignore */
69
+ ...(ngDevMode ? [{ debugName: "focused" }] : /* istanbul ignore next */ []));
70
+ open = signal(false, /* @ts-ignore */
71
+ ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
72
+ /** What the user has typed to filter (null = not actively searching → show display text). */
73
+ searchText = signal(null, /* @ts-ignore */
74
+ ...(ngDevMode ? [{ debugName: "searchText" }] : /* istanbul ignore next */ []));
75
+ activeIndex = signal(-1, /* @ts-ignore */
76
+ ...(ngDevMode ? [{ debugName: "activeIndex" }] : /* istanbul ignore next */ []));
77
+ panelWidth = signal(0, /* @ts-ignore */
78
+ ...(ngDevMode ? [{ debugName: "panelWidth" }] : /* istanbul ignore next */ []));
79
+ /** DevExtreme `.instance` facade — only `focus()` is used (via the customAutofocus directive). */
80
+ get instance() {
81
+ return this;
82
+ }
83
+ overlayRef;
84
+ closeSub;
85
+ flipSub;
86
+ items = computed(() => this.dataSource() ?? [], /* @ts-ignore */
87
+ ...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
88
+ display(item) {
89
+ const expr = this.displayExpr();
90
+ if (!expr) {
91
+ return item == null ? '' : String(item);
92
+ }
93
+ return item?.[expr] ?? '';
94
+ }
95
+ extractValue(item) {
96
+ const expr = this.valueExpr();
97
+ return expr ? item?.[expr] : item;
98
+ }
99
+ selectedItem = computed(() => {
100
+ const v = this.value();
101
+ if (v == null) {
102
+ return null;
103
+ }
104
+ return this.items().find((it) => this.extractValue(it) === v) ?? null;
105
+ }, /* @ts-ignore */
106
+ ...(ngDevMode ? [{ debugName: "selectedItem" }] : /* istanbul ignore next */ []));
107
+ hasValue = computed(() => this.value() != null && this.value() !== '', /* @ts-ignore */
108
+ ...(ngDevMode ? [{ debugName: "hasValue" }] : /* istanbul ignore next */ []));
109
+ /** Display text when the field is not being searched. */
110
+ displayText = computed(() => {
111
+ const sel = this.selectedItem();
112
+ return sel ? this.display(sel) : '';
113
+ }, /* @ts-ignore */
114
+ ...(ngDevMode ? [{ debugName: "displayText" }] : /* istanbul ignore next */ []));
115
+ /** Value bound to the input element. */
116
+ inputDisplay = computed(() => {
117
+ const search = this.searchText();
118
+ return this.open() && search !== null ? search : this.displayText();
119
+ }, /* @ts-ignore */
120
+ ...(ngDevMode ? [{ debugName: "inputDisplay" }] : /* istanbul ignore next */ []));
121
+ filteredItems = computed(() => {
122
+ const items = this.items();
123
+ const search = this.searchText();
124
+ if (!this.searchEnabled() || !search) {
125
+ return items;
126
+ }
127
+ const expr = this.searchExpr() || this.displayExpr();
128
+ const needle = search.toLowerCase();
129
+ return items.filter((it) => {
130
+ const hay = (expr ? it?.[expr] : it) ?? '';
131
+ return String(hay).toLowerCase().includes(needle);
132
+ });
133
+ }, /* @ts-ignore */
134
+ ...(ngDevMode ? [{ debugName: "filteredItems" }] : /* istanbul ignore next */ []));
135
+ itemTemplateRef = computed(() => this.templates().find((t) => t.name() === this.itemTemplate())?.templateRef ?? null, /* @ts-ignore */
136
+ ...(ngDevMode ? [{ debugName: "itemTemplateRef" }] : /* istanbul ignore next */ []));
137
+ fieldTemplateRef = computed(() => this.templates().find((t) => t.name() === this.fieldTemplate())?.templateRef ?? null, /* @ts-ignore */
138
+ ...(ngDevMode ? [{ debugName: "fieldTemplateRef" }] : /* istanbul ignore next */ []));
139
+ isSelected(item) {
140
+ return this.value() != null && this.extractValue(item) === this.value();
141
+ }
142
+ /** Imperative focus (DevExtreme `.instance.focus()` via customAutofocus). */
143
+ focus() {
144
+ this.inputEl()?.nativeElement?.focus();
145
+ }
146
+ onFieldMousedown(event) {
147
+ if (this.readOnly() || this.disabled()) {
148
+ return;
149
+ }
150
+ // Let the clear/arrow handlers run; otherwise toggle.
151
+ const target = event.target;
152
+ if (target.closest('.nxe-select-clear')) {
153
+ return;
154
+ }
155
+ if (this.open()) {
156
+ // clicking the field while open keeps it open for typing
157
+ return;
158
+ }
159
+ this.openPanel();
160
+ }
161
+ onSearchInput(event) {
162
+ this.searchText.set(event.target.value);
163
+ this.activeIndex.set(0);
164
+ if (!this.open()) {
165
+ this.openPanel();
166
+ }
167
+ }
168
+ onBlur() {
169
+ this.focused.set(false);
170
+ this.markAsTouched();
171
+ // committing happens on selection; on blur just reset the search text
172
+ this.searchText.set(null);
173
+ }
174
+ selectItem(item, event) {
175
+ event.preventDefault();
176
+ const previousValue = this.value();
177
+ const next = this.extractValue(item);
178
+ this.commitValue(next, event);
179
+ this.onSelectionChanged.emit({ selectedItem: item, previousValue, value: next });
180
+ this.searchText.set(null);
181
+ this.close();
182
+ }
183
+ onClear(event) {
184
+ event.preventDefault();
185
+ event.stopPropagation();
186
+ const previousValue = this.value();
187
+ this.commitValue(null, event);
188
+ this.onSelectionChanged.emit({ selectedItem: null, previousValue, value: null });
189
+ this.searchText.set(null);
190
+ }
191
+ onKeydown(event) {
192
+ if (this.readOnly() || this.disabled()) {
193
+ return;
194
+ }
195
+ const items = this.filteredItems();
196
+ switch (event.key) {
197
+ case 'ArrowDown':
198
+ event.preventDefault();
199
+ if (!this.open()) {
200
+ this.openPanel();
201
+ }
202
+ this.activeIndex.update((i) => Math.min(i + 1, items.length - 1));
203
+ break;
204
+ case 'ArrowUp':
205
+ event.preventDefault();
206
+ this.activeIndex.update((i) => Math.max(i - 1, 0));
207
+ break;
208
+ case 'Enter': {
209
+ const item = items[this.activeIndex()];
210
+ if (this.open() && item !== undefined) {
211
+ event.preventDefault();
212
+ this.selectItem(item, event);
213
+ }
214
+ break;
215
+ }
216
+ case 'Escape':
217
+ if (this.open()) {
218
+ event.preventDefault();
219
+ this.searchText.set(null);
220
+ this.close();
221
+ }
222
+ break;
223
+ }
224
+ }
225
+ openPanel() {
226
+ this.panelWidth.set(this.elementRef.nativeElement.offsetWidth);
227
+ const idx = this.items().findIndex((it) => this.extractValue(it) === this.value());
228
+ this.activeIndex.set(idx >= 0 ? idx : 0);
229
+ if (!this.overlayRef) {
230
+ this.overlayRef = this.overlay.create({
231
+ positionStrategy: this.overlay
232
+ .position()
233
+ .flexibleConnectedTo(this.elementRef)
234
+ .withPositions([
235
+ { originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top' },
236
+ { originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom' },
237
+ ]),
238
+ scrollStrategy: this.overlay.scrollStrategies.reposition(),
239
+ hasBackdrop: true,
240
+ backdropClass: 'cdk-overlay-transparent-backdrop',
241
+ panelClass: nxePane('nxe-select-pane'),
242
+ });
243
+ this.flipSub = trackPaneFlip(this.overlayRef);
244
+ }
245
+ if (this.overlayRef.hasAttached()) {
246
+ return;
247
+ }
248
+ this.overlayRef.attach(new TemplatePortal(this.dropdownTemplate(), this.viewContainerRef));
249
+ this.closeSub = merge(this.overlayRef.backdropClick(), this.overlayRef.keydownEvents().pipe(filter((e) => e.key === 'Escape'))).subscribe(() => this.close());
250
+ this.open.set(true);
251
+ }
252
+ close() {
253
+ if (!this.overlayRef?.hasAttached()) {
254
+ return;
255
+ }
256
+ this.overlayRef.detach();
257
+ this.closeSub?.unsubscribe();
258
+ this.flipSub?.unsubscribe();
259
+ this.open.set(false);
260
+ }
261
+ ngOnDestroy() {
262
+ this.closeSub?.unsubscribe();
263
+ this.flipSub?.unsubscribe();
264
+ this.overlayRef?.dispose();
265
+ }
266
+ 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": "!!validationError()", "class.nxe-open": "open()" }, classAttribute: "nxe-widget nxe-editor nxe-selectbox" }, providers: [
268
+ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NxeSelectBoxComponent), multi: true },
269
+ { 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)\">\r\n @if (fieldTemplateRef(); as tpl) {\r\n <div class=\"nxe-select-field\">\r\n <ng-container *ngTemplateOutlet=\"tpl; context: { $implicit: selectedItem() }\" />\r\n </div>\r\n } @else {\r\n <input\r\n #input\r\n class=\"nxe-editor-input nxe-select-input\"\r\n type=\"text\"\r\n [value]=\"inputDisplay()\"\r\n [readOnly]=\"readOnly() || !searchEnabled()\"\r\n [disabled]=\"disabled()\"\r\n [attr.placeholder]=\"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\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\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; let i = $index) {\r\n <div\r\n class=\"nxe-list-item\"\r\n [class.nxe-list-item-selected]=\"isSelected(item)\"\r\n [class.nxe-list-item-active]=\"i === activeIndex()\"\r\n (mousedown)=\"selectItem(item, $event)\">\r\n @if (itemTemplateRef(); as tpl) {\r\n <ng-container *ngTemplateOutlet=\"tpl; context: { $implicit: item }\" />\r\n } @else {\r\n {{ display(item) }}\r\n }\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-select-box{cursor:pointer}nxe-select-box .nxe-select-input{cursor:pointer}nxe-select-box .nxe-select-clear,nxe-select-box .nxe-select-arrow{flex:0 0 auto;align-self:stretch;display:inline-flex;align-items:center;justify-content:center;color:#333}nxe-select-box .nxe-select-clear svg,nxe-select-box .nxe-select-arrow svg{width:16px;height:16px;fill:currentColor}nxe-select-box .nxe-select-clear{width:22px;color:#999;cursor:pointer}nxe-select-box .nxe-select-clear:hover{color:#333}nxe-select-box .nxe-editor-container:has(.nxe-select-arrow) .nxe-select-input{padding-inline-end:0}nxe-select-box .nxe-select-arrow{position:relative;width:34px;min-width:34px;padding:1px;border-radius:4px}nxe-select-box .nxe-select-arrow svg{width:18px;height:18px}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){--nxe-invalid-badge-inset: 26px}nxe-select-box .nxe-editor-container:has(>.nxe-select-arrow):has(>.nxe-select-clear){--nxe-invalid-badge-inset: 60px}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
271
+ }
272
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeSelectBoxComponent, decorators: [{
273
+ type: Component,
274
+ args: [{ selector: 'nxe-select-box', standalone: true, imports: [NgTemplateOutlet], host: {
275
+ class: 'nxe-widget nxe-editor nxe-selectbox',
276
+ '[class.nxe-focused]': 'focused()',
277
+ '[class.nxe-invalid]': '!!validationError()',
278
+ '[class.nxe-open]': 'open()',
279
+ '(keydown)': 'onKeydown($event)',
280
+ }, providers: [
281
+ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NxeSelectBoxComponent), multi: true },
282
+ { provide: NXE_VALIDATION_TARGET, useExisting: forwardRef(() => NxeSelectBoxComponent) },
283
+ ], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<div class=\"nxe-editor-container\" (mousedown)=\"onFieldMousedown($event)\">\r\n @if (fieldTemplateRef(); as tpl) {\r\n <div class=\"nxe-select-field\">\r\n <ng-container *ngTemplateOutlet=\"tpl; context: { $implicit: selectedItem() }\" />\r\n </div>\r\n } @else {\r\n <input\r\n #input\r\n class=\"nxe-editor-input nxe-select-input\"\r\n type=\"text\"\r\n [value]=\"inputDisplay()\"\r\n [readOnly]=\"readOnly() || !searchEnabled()\"\r\n [disabled]=\"disabled()\"\r\n [attr.placeholder]=\"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\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\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; let i = $index) {\r\n <div\r\n class=\"nxe-list-item\"\r\n [class.nxe-list-item-selected]=\"isSelected(item)\"\r\n [class.nxe-list-item-active]=\"i === activeIndex()\"\r\n (mousedown)=\"selectItem(item, $event)\">\r\n @if (itemTemplateRef(); as tpl) {\r\n <ng-container *ngTemplateOutlet=\"tpl; context: { $implicit: item }\" />\r\n } @else {\r\n {{ display(item) }}\r\n }\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-select-box{cursor:pointer}nxe-select-box .nxe-select-input{cursor:pointer}nxe-select-box .nxe-select-clear,nxe-select-box .nxe-select-arrow{flex:0 0 auto;align-self:stretch;display:inline-flex;align-items:center;justify-content:center;color:#333}nxe-select-box .nxe-select-clear svg,nxe-select-box .nxe-select-arrow svg{width:16px;height:16px;fill:currentColor}nxe-select-box .nxe-select-clear{width:22px;color:#999;cursor:pointer}nxe-select-box .nxe-select-clear:hover{color:#333}nxe-select-box .nxe-editor-container:has(.nxe-select-arrow) .nxe-select-input{padding-inline-end:0}nxe-select-box .nxe-select-arrow{position:relative;width:34px;min-width:34px;padding:1px;border-radius:4px}nxe-select-box .nxe-select-arrow svg{width:18px;height:18px}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){--nxe-invalid-badge-inset: 26px}nxe-select-box .nxe-editor-container:has(>.nxe-select-arrow):has(>.nxe-select-clear){--nxe-invalid-badge-inset: 60px}\n"] }]
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"] }] } });
285
+
286
+ /**
287
+ * `nxe-tag-box` — drop-in replacement for DevExtreme `dx-tag-box` (multi-select sibling of
288
+ * `nxe-select-box`). The bound value is an **array of `valueExpr` keys** (`lovCode` strings).
289
+ * Bakes in the app's global `dxTagBox.defaultOptions` (display/search `lovName`, value `lovCode`,
290
+ * search + clear + selection-controls on). Selected items render as removable chips; the dropdown
291
+ * shows a checkbox per row. Provides {@link NXE_VALIDATION_TARGET} for a projected `nxe-validator`.
292
+ */
293
+ class NxeTagBoxComponent extends NxeFormControlBase {
294
+ overlay = inject(Overlay);
295
+ viewContainerRef = inject(ViewContainerRef);
296
+ dropdownTemplate = viewChild.required('dropdown', /* @ts-ignore */
297
+ ...(ngDevMode ? [{ debugName: "dropdownTemplate" }] : /* istanbul ignore next */ []));
298
+ inputEl = viewChild('input', /* @ts-ignore */
299
+ ...(ngDevMode ? [{ debugName: "inputEl" }] : /* istanbul ignore next */ []));
300
+ dataSource = input([], /* @ts-ignore */
301
+ ...(ngDevMode ? [{ debugName: "dataSource" }] : /* istanbul ignore next */ []));
302
+ displayExprInput = input(undefined, { ...(ngDevMode ? { debugName: "displayExprInput" } : /* istanbul ignore next */ {}), alias: 'displayExpr' });
303
+ displayExpr = computed(() => this.displayExprInput() ?? this.nxeDefaults.tagBox?.displayExpr ?? 'lovName', /* @ts-ignore */
304
+ ...(ngDevMode ? [{ debugName: "displayExpr" }] : /* istanbul ignore next */ []));
305
+ valueExprInput = input(undefined, { ...(ngDevMode ? { debugName: "valueExprInput" } : /* istanbul ignore next */ {}), alias: 'valueExpr' });
306
+ valueExpr = computed(() => this.valueExprInput() ?? this.nxeDefaults.tagBox?.valueExpr ?? 'lovCode', /* @ts-ignore */
307
+ ...(ngDevMode ? [{ debugName: "valueExpr" }] : /* istanbul ignore next */ []));
308
+ searchExprInput = input(undefined, { ...(ngDevMode ? { debugName: "searchExprInput" } : /* istanbul ignore next */ {}), alias: 'searchExpr' });
309
+ searchExpr = computed(() => this.searchExprInput() ?? this.nxeDefaults.tagBox?.searchExpr ?? 'lovName', /* @ts-ignore */
310
+ ...(ngDevMode ? [{ debugName: "searchExpr" }] : /* istanbul ignore next */ []));
311
+ searchEnabledInput = input(undefined, { ...(ngDevMode ? { debugName: "searchEnabledInput" } : /* istanbul ignore next */ {}), alias: 'searchEnabled', transform: nxeOptionalBoolean });
312
+ searchEnabled = computed(() => this.searchEnabledInput() ?? this.nxeDefaults.tagBox?.searchEnabled ?? true, /* @ts-ignore */
313
+ ...(ngDevMode ? [{ debugName: "searchEnabled" }] : /* istanbul ignore next */ []));
314
+ showClearButtonInput = input(undefined, { ...(ngDevMode ? { debugName: "showClearButtonInput" } : /* istanbul ignore next */ {}), alias: 'showClearButton', transform: nxeOptionalBoolean });
315
+ showClearButton = computed(() => this.showClearButtonInput() ?? this.nxeDefaults.tagBox?.showClearButton ?? true, /* @ts-ignore */
316
+ ...(ngDevMode ? [{ debugName: "showClearButton" }] : /* istanbul ignore next */ []));
317
+ /** dx parity: dx-tag-box does NOT show a drop-down arrow unless showDropDownButton is set. */
318
+ showDropDownButtonInput = input(undefined, { ...(ngDevMode ? { debugName: "showDropDownButtonInput" } : /* istanbul ignore next */ {}), alias: 'showDropDownButton', transform: nxeOptionalBoolean });
319
+ showDropDownButton = computed(() => this.showDropDownButtonInput() ?? this.nxeDefaults.tagBox?.showDropDownButton ?? false, /* @ts-ignore */
320
+ ...(ngDevMode ? [{ debugName: "showDropDownButton" }] : /* istanbul ignore next */ []));
321
+ placeholderInput = input(undefined, { ...(ngDevMode ? { debugName: "placeholderInput" } : /* istanbul ignore next */ {}), alias: 'placeholder' });
322
+ placeholder = computed(() => this.placeholderInput() ?? this.nxeDefaults.tagBox?.placeholder ?? '', /* @ts-ignore */
323
+ ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
324
+ focused = signal(false, /* @ts-ignore */
325
+ ...(ngDevMode ? [{ debugName: "focused" }] : /* istanbul ignore next */ []));
326
+ open = signal(false, /* @ts-ignore */
327
+ ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
328
+ searchText = signal('', /* @ts-ignore */
329
+ ...(ngDevMode ? [{ debugName: "searchText" }] : /* istanbul ignore next */ []));
330
+ panelWidth = signal(0, /* @ts-ignore */
331
+ ...(ngDevMode ? [{ debugName: "panelWidth" }] : /* istanbul ignore next */ []));
332
+ overlayRef;
333
+ closeSub;
334
+ flipSub;
335
+ items = computed(() => this.dataSource() ?? [], /* @ts-ignore */
336
+ ...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
337
+ /** The selected keys (always an array). */
338
+ selectedKeys = computed(() => {
339
+ const v = this.value();
340
+ return Array.isArray(v) ? v : [];
341
+ }, /* @ts-ignore */
342
+ ...(ngDevMode ? [{ debugName: "selectedKeys" }] : /* istanbul ignore next */ []));
343
+ display(item) {
344
+ const expr = this.displayExpr();
345
+ if (!expr) {
346
+ return item == null ? '' : String(item);
347
+ }
348
+ return item?.[expr] ?? '';
349
+ }
350
+ extractValue(item) {
351
+ const expr = this.valueExpr();
352
+ return expr ? item?.[expr] : item;
353
+ }
354
+ /** Selected items resolved from the value keys (for chip rendering). */
355
+ selectedItems = computed(() => {
356
+ const keys = this.selectedKeys();
357
+ return keys.map((k) => this.items().find((it) => this.extractValue(it) === k) ?? k);
358
+ }, /* @ts-ignore */
359
+ ...(ngDevMode ? [{ debugName: "selectedItems" }] : /* istanbul ignore next */ []));
360
+ hasValue = computed(() => this.selectedKeys().length > 0, /* @ts-ignore */
361
+ ...(ngDevMode ? [{ debugName: "hasValue" }] : /* istanbul ignore next */ []));
362
+ filteredItems = computed(() => {
363
+ const items = this.items();
364
+ const search = this.searchText();
365
+ if (!this.searchEnabled() || !search) {
366
+ return items;
367
+ }
368
+ const expr = this.searchExpr() || this.displayExpr();
369
+ const needle = search.toLowerCase();
370
+ return items.filter((it) => {
371
+ const hay = (expr ? it?.[expr] : it) ?? '';
372
+ return String(hay).toLowerCase().includes(needle);
373
+ });
374
+ }, /* @ts-ignore */
375
+ ...(ngDevMode ? [{ debugName: "filteredItems" }] : /* istanbul ignore next */ []));
376
+ isSelected(item) {
377
+ return this.selectedKeys().includes(this.extractValue(item));
378
+ }
379
+ onFieldMousedown(event) {
380
+ if (this.readOnly() || this.disabled()) {
381
+ return;
382
+ }
383
+ const target = event.target;
384
+ if (target.closest('.nxe-tag-remove') || target.closest('.nxe-select-clear')) {
385
+ return;
386
+ }
387
+ if (!this.open()) {
388
+ this.openPanel();
389
+ }
390
+ this.inputEl()?.nativeElement?.focus();
391
+ }
392
+ onSearchInput(event) {
393
+ this.searchText.set(event.target.value);
394
+ if (!this.open()) {
395
+ this.openPanel();
396
+ }
397
+ }
398
+ onBlur() {
399
+ this.focused.set(false);
400
+ this.markAsTouched();
401
+ }
402
+ toggle(item, event) {
403
+ event.preventDefault();
404
+ const key = this.extractValue(item);
405
+ const keys = this.selectedKeys();
406
+ const next = keys.includes(key) ? keys.filter((k) => k !== key) : [...keys, key];
407
+ this.commitValue(next, event);
408
+ this.searchText.set('');
409
+ const input = this.inputEl()?.nativeElement;
410
+ if (input) {
411
+ input.value = '';
412
+ }
413
+ }
414
+ removeTag(item, event) {
415
+ event.preventDefault();
416
+ event.stopPropagation();
417
+ const key = this.extractValue(item);
418
+ this.commitValue(this.selectedKeys().filter((k) => k !== key), event);
419
+ }
420
+ onClear(event) {
421
+ event.preventDefault();
422
+ event.stopPropagation();
423
+ this.commitValue([], event);
424
+ }
425
+ onKeydown(event) {
426
+ if (event.key === 'Escape' && this.open()) {
427
+ event.preventDefault();
428
+ this.close();
429
+ }
430
+ }
431
+ openPanel() {
432
+ this.panelWidth.set(this.elementRef.nativeElement.offsetWidth);
433
+ if (!this.overlayRef) {
434
+ this.overlayRef = this.overlay.create({
435
+ positionStrategy: this.overlay
436
+ .position()
437
+ .flexibleConnectedTo(this.elementRef)
438
+ .withPositions([
439
+ { originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top' },
440
+ { originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom' },
441
+ ]),
442
+ scrollStrategy: this.overlay.scrollStrategies.reposition(),
443
+ hasBackdrop: true,
444
+ backdropClass: 'cdk-overlay-transparent-backdrop',
445
+ panelClass: nxePane('nxe-select-pane'),
446
+ });
447
+ this.flipSub = trackPaneFlip(this.overlayRef);
448
+ }
449
+ if (this.overlayRef.hasAttached()) {
450
+ return;
451
+ }
452
+ this.overlayRef.attach(new TemplatePortal(this.dropdownTemplate(), this.viewContainerRef));
453
+ this.closeSub = merge(this.overlayRef.backdropClick(), this.overlayRef.keydownEvents().pipe(filter((e) => e.key === 'Escape'))).subscribe(() => this.close());
454
+ this.open.set(true);
455
+ }
456
+ close() {
457
+ if (!this.overlayRef?.hasAttached()) {
458
+ return;
459
+ }
460
+ this.overlayRef.detach();
461
+ this.closeSub?.unsubscribe();
462
+ this.flipSub?.unsubscribe();
463
+ this.open.set(false);
464
+ this.searchText.set('');
465
+ }
466
+ ngOnDestroy() {
467
+ this.closeSub?.unsubscribe();
468
+ this.flipSub?.unsubscribe();
469
+ this.overlayRef?.dispose();
470
+ }
471
+ 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": "!!validationError()", "class.nxe-open": "open()" }, classAttribute: "nxe-widget nxe-editor nxe-tagbox" }, providers: [
473
+ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NxeTagBoxComponent), multi: true },
474
+ { 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 });
476
+ }
477
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeTagBoxComponent, decorators: [{
478
+ type: Component,
479
+ args: [{ selector: 'nxe-tag-box', standalone: true, host: {
480
+ class: 'nxe-widget nxe-editor nxe-tagbox',
481
+ '[class.nxe-focused]': 'focused()',
482
+ '[class.nxe-invalid]': '!!validationError()',
483
+ '[class.nxe-open]': 'open()',
484
+ '(keydown)': 'onKeydown($event)',
485
+ }, providers: [
486
+ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NxeTagBoxComponent), multi: true },
487
+ { 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 }] }] } });
490
+
491
+ /**
492
+ * `nxe-drop-down-options` — drop-in for DevExtreme `dxo-drop-down-options`, a no-template config
493
+ * child of `nxe-drop-down-box`. Only `wrapperAttr` (a `{class}` applied to the overlay panel) and
494
+ * `width` are used in this codebase.
495
+ */
496
+ class NxeDropDownOptionsComponent {
497
+ wrapperAttr = input(/* @ts-ignore */
498
+ ...(ngDevMode ? [undefined, { debugName: "wrapperAttr" }] : /* istanbul ignore next */ []));
499
+ width = input(/* @ts-ignore */
500
+ ...(ngDevMode ? [undefined, { debugName: "width" }] : /* istanbul ignore next */ []));
501
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeDropDownOptionsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
502
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.5", type: NxeDropDownOptionsComponent, isStandalone: true, selector: "nxe-drop-down-options", inputs: { wrapperAttr: { classPropertyName: "wrapperAttr", publicName: "wrapperAttr", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: '', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
503
+ }
504
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeDropDownOptionsComponent, decorators: [{
505
+ type: Component,
506
+ args: [{
507
+ selector: 'nxe-drop-down-options',
508
+ standalone: true,
509
+ template: '',
510
+ changeDetection: ChangeDetectionStrategy.OnPush,
511
+ encapsulation: ViewEncapsulation.None,
512
+ }]
513
+ }], propDecorators: { wrapperAttr: [{ type: i0.Input, args: [{ isSignal: true, alias: "wrapperAttr", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }] } });
514
+
515
+ /**
516
+ * `nxe-drop-down-box` — drop-in replacement for DevExtreme `dx-drop-down-box`.
517
+ *
518
+ * A signal-CVA editor whose dropdown hosts ARBITRARY projected content (a `nxeTemplate="content"`
519
+ * block — usually a data-grid picker) in a CDK overlay. The closed field shows the value's
520
+ * `displayExpr` (looked up in `[dataSource]`), or the raw value when no `displayExpr`. The inner
521
+ * grid sets the bound form value and closes the popup via `#ref.instance.close()` (16 call sites),
522
+ * so this exposes an `.instance` facade + `close()`, and an `(onClosed)` output. Projects
523
+ * `nxe-validator` via {@link NXE_VALIDATION_TARGET}.
524
+ */
525
+ class NxeDropDownBoxComponent extends NxeFormControlBase {
526
+ overlay = inject(Overlay);
527
+ viewContainerRef = inject(ViewContainerRef);
528
+ panelTemplate = viewChild.required('panel', /* @ts-ignore */
529
+ ...(ngDevMode ? [{ debugName: "panelTemplate" }] : /* istanbul ignore next */ []));
530
+ templates = contentChildren(NxeTemplateDirective, /* @ts-ignore */
531
+ ...(ngDevMode ? [{ debugName: "templates" }] : /* istanbul ignore next */ []));
532
+ options = contentChild(NxeDropDownOptionsComponent, /* @ts-ignore */
533
+ ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
534
+ dataSource = input([], /* @ts-ignore */
535
+ ...(ngDevMode ? [{ debugName: "dataSource" }] : /* istanbul ignore next */ []));
536
+ /** Field name shown in the closed box; empty => the raw value string. */
537
+ displayExprInput = input(undefined, { ...(ngDevMode ? { debugName: "displayExprInput" } : /* istanbul ignore next */ {}), alias: 'displayExpr' });
538
+ displayExpr = computed(() => this.displayExprInput() ?? this.nxeDefaults.dropDownBox?.displayExpr ?? '', /* @ts-ignore */
539
+ ...(ngDevMode ? [{ debugName: "displayExpr" }] : /* istanbul ignore next */ []));
540
+ valueExprInput = input(undefined, { ...(ngDevMode ? { debugName: "valueExprInput" } : /* istanbul ignore next */ {}), alias: 'valueExpr' });
541
+ valueExpr = computed(() => this.valueExprInput() ?? this.nxeDefaults.dropDownBox?.valueExpr ?? '', /* @ts-ignore */
542
+ ...(ngDevMode ? [{ debugName: "valueExpr" }] : /* istanbul ignore next */ []));
543
+ placeholderInput = input(undefined, { ...(ngDevMode ? { debugName: "placeholderInput" } : /* istanbul ignore next */ {}), alias: 'placeholder' });
544
+ placeholder = computed(() => this.placeholderInput() ?? this.nxeDefaults.dropDownBox?.placeholder ?? '', /* @ts-ignore */
545
+ ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
546
+ /** DevExtreme `[dropDownOptions]` — accepted for parity (popup-class hook). */
547
+ dropDownOptions = input(/* @ts-ignore */
548
+ ...(ngDevMode ? [undefined, { debugName: "dropDownOptions" }] : /* istanbul ignore next */ []));
549
+ /**
550
+ * DevExtreme `showClearButton` — a × left of the arrow that sets the value to null, matching
551
+ * {@link NxeSelectBoxComponent}/`nxe-tag-box`.
552
+ *
553
+ * Defaults to **false**, unlike those two: this component's dropdown hosts arbitrary content, and
554
+ * every existing call site picks from a grid where clearing was never offered. Opt in where null
555
+ * is a value the field can legitimately hold.
556
+ */
557
+ showClearButtonInput = input(undefined, { ...(ngDevMode ? { debugName: "showClearButtonInput" } : /* istanbul ignore next */ {}), alias: 'showClearButton', transform: nxeOptionalBoolean });
558
+ showClearButton = computed(() => this.showClearButtonInput() ?? this.nxeDefaults.dropDownBox?.showClearButton ?? false, /* @ts-ignore */
559
+ ...(ngDevMode ? [{ debugName: "showClearButton" }] : /* istanbul ignore next */ []));
560
+ /**
561
+ * DevExtreme `[opened]` (orbit addition): programmatic open/close. Reflects the panel state
562
+ * outward and opens/closes the panel when the binding changes.
563
+ */
564
+ opened = model(false, /* @ts-ignore */
565
+ ...(ngDevMode ? [{ debugName: "opened" }] : /* istanbul ignore next */ []));
566
+ onClosed = output();
567
+ focused = signal(false, /* @ts-ignore */
568
+ ...(ngDevMode ? [{ debugName: "focused" }] : /* istanbul ignore next */ []));
569
+ open = signal(false, /* @ts-ignore */
570
+ ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
571
+ panelWidth = signal(0, /* @ts-ignore */
572
+ ...(ngDevMode ? [{ debugName: "panelWidth" }] : /* istanbul ignore next */ []));
573
+ /** DevExtreme `.instance` facade — only `close()` is used (from inner-grid selection handlers). */
574
+ get instance() {
575
+ return this;
576
+ }
577
+ constructor() {
578
+ super();
579
+ // `[opened]` binding drives the panel; internal open/close writes back through the model.
580
+ /* ⚠ NOT an effect - UI-48. It takes the ONE input that asks for the panel; an effect
581
+ would also have depended on `open()`, which openPanel() and close() both WRITE — a
582
+ reaction that feeds itself, fenced only by the equality test above it. */
583
+ toObservable(this.opened, { injector: inject(Injector) })
584
+ .pipe(distinctUntilChanged(), takeUntilDestroyed())
585
+ .subscribe(wanted => {
586
+ if (wanted !== this.open()) {
587
+ wanted ? this.openPanel() : this.close();
588
+ }
589
+ });
590
+ }
591
+ overlayRef;
592
+ closeSub;
593
+ flipSub;
594
+ contentTemplateRef = computed(() => this.templates().find((t) => t.name() === 'content')?.templateRef ?? null, /* @ts-ignore */
595
+ ...(ngDevMode ? [{ debugName: "contentTemplateRef" }] : /* istanbul ignore next */ []));
596
+ /**
597
+ * DevExtreme sizes the popup to the editor, and every call site that says anything says
598
+ * `width="100%"` — which means that same thing. So the panel is fixed at the field's width and
599
+ * the content fits inside it (a grid picker lays its columns out to the panel) rather than the
600
+ * panel growing to whatever the content wants. Only an explicit px/em/rem width overrides.
601
+ */
602
+ panelStyleWidth = computed(() => {
603
+ const width = this.options()?.width();
604
+ if (typeof width === 'number') {
605
+ return `${width}px`;
606
+ }
607
+ if (width && width !== '100%') {
608
+ return width;
609
+ }
610
+ return `${this.panelWidth()}px`;
611
+ }, /* @ts-ignore */
612
+ ...(ngDevMode ? [{ debugName: "panelStyleWidth" }] : /* istanbul ignore next */ []));
613
+ displayText = computed(() => {
614
+ const v = this.value();
615
+ if (v == null || v === '') {
616
+ return '';
617
+ }
618
+ const de = this.displayExpr();
619
+ if (!de) {
620
+ return String(v);
621
+ }
622
+ // The bound value is the code; match it against valueExpr (default the implicit `lovCode`).
623
+ const ve = this.valueExpr() || 'lovCode';
624
+ const item = (this.dataSource() ?? []).find((it) => it?.[ve] === v);
625
+ return item ? String(item[de] ?? '') : String(v);
626
+ }, /* @ts-ignore */
627
+ ...(ngDevMode ? [{ debugName: "displayText" }] : /* istanbul ignore next */ []));
628
+ hasValue = computed(() => {
629
+ const v = this.value();
630
+ return v != null && v !== '';
631
+ }, /* @ts-ignore */
632
+ ...(ngDevMode ? [{ debugName: "hasValue" }] : /* istanbul ignore next */ []));
633
+ onFieldMousedown(event) {
634
+ if (this.readOnly() || this.disabled()) {
635
+ return;
636
+ }
637
+ event.preventDefault();
638
+ this.open() ? this.close() : this.openPanel();
639
+ }
640
+ /** Stops the mousedown reaching the field, which would otherwise toggle the panel open. */
641
+ onClear(event) {
642
+ event.preventDefault();
643
+ event.stopPropagation();
644
+ this.commitValue(null, event);
645
+ }
646
+ onBlur() {
647
+ this.focused.set(false);
648
+ this.markAsTouched();
649
+ }
650
+ /** Imperative close (DevExtreme `.instance.close()`). */
651
+ close() {
652
+ if (!this.overlayRef?.hasAttached()) {
653
+ return;
654
+ }
655
+ this.overlayRef.detach();
656
+ this.closeSub?.unsubscribe();
657
+ this.flipSub?.unsubscribe();
658
+ this.open.set(false);
659
+ this.opened.set(false);
660
+ this.onClosed.emit();
661
+ }
662
+ openPanel() {
663
+ this.panelWidth.set(this.elementRef.nativeElement.offsetWidth);
664
+ const wrapperClass = this.options()?.wrapperAttr()?.class ?? '';
665
+ if (this.overlayRef) {
666
+ this.flipSub?.unsubscribe();
667
+ this.overlayRef.dispose();
668
+ }
669
+ this.overlayRef = this.overlay.create({
670
+ positionStrategy: this.overlay
671
+ .position()
672
+ .flexibleConnectedTo(this.elementRef)
673
+ .withPositions([
674
+ { originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top' },
675
+ { originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom' },
676
+ ]),
677
+ scrollStrategy: this.overlay.scrollStrategies.reposition(),
678
+ hasBackdrop: true,
679
+ backdropClass: 'cdk-overlay-transparent-backdrop',
680
+ panelClass: nxePane('nxe-dropdownbox-pane', ...wrapperClass.split(/\s+/)),
681
+ });
682
+ this.flipSub = trackPaneFlip(this.overlayRef);
683
+ this.overlayRef.attach(new TemplatePortal(this.panelTemplate(), this.viewContainerRef));
684
+ this.closeSub = this.overlayRef.backdropClick().subscribe(() => this.close());
685
+ this.open.set(true);
686
+ this.opened.set(true);
687
+ }
688
+ ngOnDestroy() {
689
+ this.closeSub?.unsubscribe();
690
+ this.flipSub?.unsubscribe();
691
+ this.overlayRef?.dispose();
692
+ }
693
+ 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": "!!validationError()", "class.nxe-open": "open()" }, classAttribute: "nxe-widget nxe-editor nxe-dropdownbox" }, providers: [
695
+ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NxeDropDownBoxComponent), multi: true },
696
+ { 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)\">\r\n <input\r\n class=\"nxe-editor-input nxe-dropdownbox-input\"\r\n type=\"text\"\r\n readonly\r\n [value]=\"displayText()\"\r\n [disabled]=\"disabled()\"\r\n [attr.placeholder]=\"placeholder() || null\"\r\n (focus)=\"focused.set(true)\"\r\n (blur)=\"onBlur()\" />\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\r\n @if (!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 #panel>\r\n <div class=\"nxe-dropdownbox-panel\" [style.width]=\"panelStyleWidth()\">\r\n @if (contentTemplateRef(); as tpl) {\r\n <ng-container *ngTemplateOutlet=\"tpl; context: { $implicit: value() }\" />\r\n }\r\n </div>\r\n</ng-template>\r\n", styles: ["nxe-drop-down-box{cursor:pointer}nxe-drop-down-box .nxe-dropdownbox-input{cursor:pointer}nxe-drop-down-box .nxe-select-clear{flex:0 0 22px;align-self:stretch;display:inline-flex;align-items:center;justify-content:center;width:22px;color:#999;cursor:pointer}nxe-drop-down-box .nxe-select-clear svg{width:16px;height:16px;fill:currentColor}nxe-drop-down-box .nxe-select-clear:hover{color:#333}nxe-drop-down-box .nxe-select-arrow{flex:0 0 34px;align-self:stretch;display:inline-flex;align-items:center;justify-content:center;width:34px;color:#333;border-radius:4px}nxe-drop-down-box .nxe-select-arrow svg{width:18px;height:18px;fill:currentColor}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){--nxe-invalid-badge-inset: 26px}nxe-drop-down-box .nxe-editor-container:has(>.nxe-select-arrow):has(>.nxe-select-clear){--nxe-invalid-badge-inset: 60px}.nxe-dropdownbox-panel{box-sizing:border-box;background:#fff;border:1px solid #ddd;border-radius:6px;box-shadow:0 6px 12px #0000002d;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
+ }
699
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeDropDownBoxComponent, decorators: [{
700
+ type: Component,
701
+ args: [{ selector: 'nxe-drop-down-box', standalone: true, imports: [NgTemplateOutlet], host: {
702
+ class: 'nxe-widget nxe-editor nxe-dropdownbox',
703
+ '[class.nxe-focused]': 'focused()',
704
+ '[class.nxe-invalid]': '!!validationError()',
705
+ '[class.nxe-open]': 'open()',
706
+ }, providers: [
707
+ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NxeDropDownBoxComponent), multi: true },
708
+ { provide: NXE_VALIDATION_TARGET, useExisting: forwardRef(() => NxeDropDownBoxComponent) },
709
+ ], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<div class=\"nxe-editor-container\" (mousedown)=\"onFieldMousedown($event)\">\r\n <input\r\n class=\"nxe-editor-input nxe-dropdownbox-input\"\r\n type=\"text\"\r\n readonly\r\n [value]=\"displayText()\"\r\n [disabled]=\"disabled()\"\r\n [attr.placeholder]=\"placeholder() || null\"\r\n (focus)=\"focused.set(true)\"\r\n (blur)=\"onBlur()\" />\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\r\n @if (!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 #panel>\r\n <div class=\"nxe-dropdownbox-panel\" [style.width]=\"panelStyleWidth()\">\r\n @if (contentTemplateRef(); as tpl) {\r\n <ng-container *ngTemplateOutlet=\"tpl; context: { $implicit: value() }\" />\r\n }\r\n </div>\r\n</ng-template>\r\n", styles: ["nxe-drop-down-box{cursor:pointer}nxe-drop-down-box .nxe-dropdownbox-input{cursor:pointer}nxe-drop-down-box .nxe-select-clear{flex:0 0 22px;align-self:stretch;display:inline-flex;align-items:center;justify-content:center;width:22px;color:#999;cursor:pointer}nxe-drop-down-box .nxe-select-clear svg{width:16px;height:16px;fill:currentColor}nxe-drop-down-box .nxe-select-clear:hover{color:#333}nxe-drop-down-box .nxe-select-arrow{flex:0 0 34px;align-self:stretch;display:inline-flex;align-items:center;justify-content:center;width:34px;color:#333;border-radius:4px}nxe-drop-down-box .nxe-select-arrow svg{width:18px;height:18px;fill:currentColor}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){--nxe-invalid-badge-inset: 26px}nxe-drop-down-box .nxe-editor-container:has(>.nxe-select-arrow):has(>.nxe-select-clear){--nxe-invalid-badge-inset: 60px}.nxe-dropdownbox-panel{box-sizing:border-box;background:#fff;border:1px solid #ddd;border-radius:6px;box-shadow:0 6px 12px #0000002d;overflow:auto;max-height:420px}\n"] }]
710
+ }], 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
+
712
+ /** DI token the menu provides so projected `nxe-menu-item`s can close it on click. */
713
+ const NXE_MENU = new InjectionToken('NXE_MENU');
714
+
715
+ /**
716
+ * `nxe-list` — drop-in replacement for DevExtreme `dx-list` as used in this codebase: an action
717
+ * menu rendered inside `nxe-menu` (100% of usages). Renders each `[dataSource]` item with the
718
+ * named `itemTemplate` (`*nxeTemplate`), emits `(onItemClick)` (`{itemData}`), and closes the
719
+ * parent `nxe-menu` on click. Emits `.nxe-list`/`.nxe-list-item-row`/`.nxe-list-item-content` classes so
720
+ * the app's existing menu/list CSS applies unchanged.
721
+ */
722
+ class NxeListComponent {
723
+ menu = inject(NXE_MENU, { optional: true });
724
+ templates = contentChildren(NxeTemplateDirective, /* @ts-ignore */
725
+ ...(ngDevMode ? [{ debugName: "templates" }] : /* istanbul ignore next */ []));
726
+ dataSource = input([], /* @ts-ignore */
727
+ ...(ngDevMode ? [{ debugName: "dataSource" }] : /* istanbul ignore next */ []));
728
+ /** Named template ('listItem'/'actionButton') for each row; `$implicit` = the item. */
729
+ itemTemplate = input(/* @ts-ignore */
730
+ ...(ngDevMode ? [undefined, { debugName: "itemTemplate" }] : /* istanbul ignore next */ []));
731
+ /** DevExtreme `[elementAttr]` — `{ id, class }` applied to the host. */
732
+ elementAttr = input(/* @ts-ignore */
733
+ ...(ngDevMode ? [undefined, { debugName: "elementAttr" }] : /* istanbul ignore next */ []));
734
+ onItemClick = output();
735
+ items = computed(() => this.dataSource() ?? [], /* @ts-ignore */
736
+ ...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
737
+ itemTemplateRef = computed(() => this.templates().find((t) => t.name() === this.itemTemplate())?.templateRef ?? null, /* @ts-ignore */
738
+ ...(ngDevMode ? [{ debugName: "itemTemplateRef" }] : /* istanbul ignore next */ []));
739
+ text(item) {
740
+ return item?.['text'] ?? '';
741
+ }
742
+ /** dx parity: dx-list honors `item.disabled` (grayed, non-interactive). */
743
+ isDisabled(item) {
744
+ return item?.['disabled'] === true;
745
+ }
746
+ select(item) {
747
+ if (this.isDisabled(item)) {
748
+ return;
749
+ }
750
+ this.onItemClick.emit({ itemData: item });
751
+ this.menu?.closeMenu();
752
+ }
753
+ 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) {
756
+ <div
757
+ class="nxe-list-item-row"
758
+ [class.nxe-state-disabled]="isDisabled(item)"
759
+ (click)="select(item)">
760
+ <div class="nxe-list-item-content">
761
+ @if (itemTemplateRef(); as tpl) {
762
+ <ng-container
763
+ *ngTemplateOutlet="tpl; context: { $implicit: item, last: last }" />
764
+ } @else {
765
+ {{ text(item) }}
766
+ }
767
+ </div>
768
+ </div>
769
+ }
770
+ `, isInline: true, 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"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
771
+ }
772
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeListComponent, decorators: [{
773
+ type: Component,
774
+ args: [{ selector: 'nxe-list', standalone: true, imports: [NgTemplateOutlet], template: `
775
+ @for (item of items(); track $index; let last = $last) {
776
+ <div
777
+ class="nxe-list-item-row"
778
+ [class.nxe-state-disabled]="isDisabled(item)"
779
+ (click)="select(item)">
780
+ <div class="nxe-list-item-content">
781
+ @if (itemTemplateRef(); as tpl) {
782
+ <ng-container
783
+ *ngTemplateOutlet="tpl; context: { $implicit: item, last: last }" />
784
+ } @else {
785
+ {{ text(item) }}
786
+ }
787
+ </div>
788
+ </div>
789
+ }
790
+ `, host: {
791
+ '[class]': "'nxe-widget nxe-list ' + (elementAttr()?.class ?? '')",
792
+ '[attr.id]': 'elementAttr()?.id ?? null',
793
+ }, 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"] }] } });
795
+
796
+ /** DI token the autocomplete provides so projected `nxe-option`s can close it on click. */
797
+ const NXE_AUTOCOMPLETE = new InjectionToken('NXE_AUTOCOMPLETE');
798
+ /**
799
+ * `nxe-autocomplete` — drop-in replacement for Material's `mat-autocomplete` (as used by the global
800
+ * search). Holds the dropdown panel as a `TemplateRef` that the `[nxeAutocomplete]` trigger renders
801
+ * in a CDK overlay; projects `nxe-option`s. Emits the Material panel/option classes so Material's
802
+ * loaded autocomplete CSS + the consumer's classes apply. Exposes `isOpen` (read by the search).
803
+ */
804
+ class NxeAutocompleteComponent {
805
+ elementRef = inject(ElementRef);
806
+ templateRef = viewChild.required(TemplateRef, /* @ts-ignore */
807
+ ...(ngDevMode ? [{ debugName: "templateRef" }] : /* istanbul ignore next */ []));
808
+ /** Open state (the `[nxeAutocomplete]` trigger drives this; the search reads `isOpen`). */
809
+ opened = signal(false, /* @ts-ignore */
810
+ ...(ngDevMode ? [{ debugName: "opened" }] : /* istanbul ignore next */ []));
811
+ /** Fires when a projected `nxe-option` is selected, so the trigger closes the panel. */
812
+ optionSelected = new Subject();
813
+ /** Panel classes: Material's + the consumer's class on `<nxe-autocomplete>`. */
814
+ panelClass = signal('nxe-autocomplete-panel mat-mdc-autocomplete-panel', /* @ts-ignore */
815
+ ...(ngDevMode ? [{ debugName: "panelClass" }] : /* istanbul ignore next */ []));
816
+ get isOpen() {
817
+ return this.opened();
818
+ }
819
+ ngOnInit() {
820
+ const consumer = this.elementRef.nativeElement.getAttribute('class') ?? '';
821
+ this.panelClass.set(`nxe-autocomplete-panel mat-mdc-autocomplete-panel ${consumer}`.trim());
822
+ }
823
+ closeFromOption() {
824
+ this.optionSelected.next();
825
+ }
826
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeAutocompleteComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
827
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.1.5", type: NxeAutocompleteComponent, isStandalone: true, selector: "nxe-autocomplete", providers: [{ provide: NXE_AUTOCOMPLETE, useExisting: forwardRef(() => NxeAutocompleteComponent) }], viewQueries: [{ propertyName: "templateRef", first: true, predicate: TemplateRef, descendants: true, isSignal: true }], exportAs: ["nxeAutocomplete"], ngImport: i0, template: `
828
+ <ng-template>
829
+ <div [class]="panelClass()" role="listbox" (mousedown)="$event.preventDefault()">
830
+ <ng-content />
831
+ </div>
832
+ </ng-template>
833
+ `, isInline: true, styles: ["nxe-autocomplete{display:none}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
834
+ }
835
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeAutocompleteComponent, decorators: [{
836
+ type: Component,
837
+ args: [{ selector: 'nxe-autocomplete', exportAs: 'nxeAutocomplete', standalone: true, template: `
838
+ <ng-template>
839
+ <div [class]="panelClass()" role="listbox" (mousedown)="$event.preventDefault()">
840
+ <ng-content />
841
+ </div>
842
+ </ng-template>
843
+ `, providers: [{ provide: NXE_AUTOCOMPLETE, useExisting: forwardRef(() => NxeAutocompleteComponent) }], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, styles: ["nxe-autocomplete{display:none}\n"] }]
844
+ }], propDecorators: { templateRef: [{ type: i0.ViewChild, args: [i0.forwardRef(() => TemplateRef), { isSignal: true }] }] } });
845
+
846
+ /**
847
+ * `[nxeAutocomplete]` — drop-in for Material's `matAutocomplete` trigger. Applied to a text input;
848
+ * opens the referenced {@link NxeAutocompleteComponent}'s panel in a CDK overlay (width-matched to
849
+ * the input) on focus/input, closes on blur/Escape/option-select.
850
+ */
851
+ class NxeAutocompleteTriggerDirective {
852
+ autocomplete = input.required({ ...(ngDevMode ? { debugName: "autocomplete" } : /* istanbul ignore next */ {}), alias: 'nxeAutocomplete' });
853
+ overlay = inject(Overlay);
854
+ elementRef = inject(ElementRef);
855
+ viewContainerRef = inject(ViewContainerRef);
856
+ overlayRef;
857
+ sub;
858
+ openPanel() {
859
+ const ac = this.autocomplete();
860
+ const width = this.elementRef.nativeElement.offsetWidth;
861
+ if (!this.overlayRef) {
862
+ this.overlayRef = this.overlay.create({
863
+ positionStrategy: this.overlay
864
+ .position()
865
+ .flexibleConnectedTo(this.elementRef)
866
+ .withPositions([
867
+ { originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top' },
868
+ { originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom' },
869
+ ]),
870
+ scrollStrategy: this.overlay.scrollStrategies.reposition(),
871
+ width,
872
+ panelClass: nxePane('nxe-autocomplete-pane'),
873
+ });
874
+ }
875
+ else {
876
+ this.overlayRef.updateSize({ width });
877
+ }
878
+ if (this.overlayRef.hasAttached()) {
879
+ return;
880
+ }
881
+ this.overlayRef.attach(new TemplatePortal(ac.templateRef(), this.viewContainerRef));
882
+ ac.opened.set(true);
883
+ this.sub = ac.optionSelected.subscribe(() => this.closePanel());
884
+ }
885
+ closePanel() {
886
+ if (!this.overlayRef?.hasAttached()) {
887
+ return;
888
+ }
889
+ this.overlayRef.detach();
890
+ this.sub?.unsubscribe();
891
+ this.autocomplete().opened.set(false);
892
+ }
893
+ ngOnDestroy() {
894
+ this.sub?.unsubscribe();
895
+ this.overlayRef?.dispose();
896
+ }
897
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeAutocompleteTriggerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
898
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.5", type: NxeAutocompleteTriggerDirective, isStandalone: true, selector: "input[nxeAutocomplete], textarea[nxeAutocomplete]", inputs: { autocomplete: { classPropertyName: "autocomplete", publicName: "nxeAutocomplete", isSignal: true, isRequired: true, transformFunction: null } }, host: { attributes: { "autocomplete": "off" }, listeners: { "focusin": "openPanel()", "input": "openPanel()", "blur": "closePanel()", "keydown.escape": "closePanel()" } }, exportAs: ["nxeAutocompleteTrigger"], ngImport: i0 });
899
+ }
900
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeAutocompleteTriggerDirective, decorators: [{
901
+ type: Directive,
902
+ args: [{
903
+ selector: 'input[nxeAutocomplete], textarea[nxeAutocomplete]',
904
+ exportAs: 'nxeAutocompleteTrigger',
905
+ standalone: true,
906
+ host: {
907
+ '(focusin)': 'openPanel()',
908
+ '(input)': 'openPanel()',
909
+ '(blur)': 'closePanel()',
910
+ '(keydown.escape)': 'closePanel()',
911
+ autocomplete: 'off',
912
+ },
913
+ }]
914
+ }], propDecorators: { autocomplete: [{ type: i0.Input, args: [{ isSignal: true, alias: "nxeAutocomplete", required: true }] }] } });
915
+
916
+ /**
917
+ * `nxe-option` — drop-in for Material's `mat-option` inside an `nxe-autocomplete`. Emits the MDC
918
+ * option classes (so Material's option CSS applies) and closes the parent autocomplete on click;
919
+ * the consumer's own `(click)` (e.g. navigate) still fires.
920
+ */
921
+ class NxeOptionComponent {
922
+ autocomplete = inject(NXE_AUTOCOMPLETE, { optional: true });
923
+ select() {
924
+ this.autocomplete?.closeFromOption();
925
+ }
926
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeOptionComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
927
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.5", type: NxeOptionComponent, isStandalone: true, selector: "nxe-option", host: { attributes: { "role": "option" }, listeners: { "click": "select()" }, classAttribute: "nxe-option mat-mdc-option mdc-list-item" }, exportAs: ["nxeOption"], ngImport: i0, template: '<ng-content />', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
928
+ }
929
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeOptionComponent, decorators: [{
930
+ type: Component,
931
+ args: [{
932
+ selector: 'nxe-option',
933
+ exportAs: 'nxeOption',
934
+ standalone: true,
935
+ template: '<ng-content />',
936
+ host: {
937
+ class: 'nxe-option mat-mdc-option mdc-list-item',
938
+ role: 'option',
939
+ '(click)': 'select()',
940
+ },
941
+ changeDetection: ChangeDetectionStrategy.OnPush,
942
+ encapsulation: ViewEncapsulation.None,
943
+ }]
944
+ }] });
945
+
946
+ // @nexterajs/ui/dropdowns — public API.
947
+
948
+ /**
949
+ * Generated bundle index. Do not edit.
950
+ */
951
+
952
+ export { NXE_AUTOCOMPLETE, NxeAutocompleteComponent, NxeAutocompleteTriggerDirective, NxeDropDownBoxComponent, NxeDropDownOptionsComponent, NxeListComponent, NxeOptionComponent, NxeSelectBoxComponent, NxeTagBoxComponent };
953
+ //# sourceMappingURL=nexterajs-ui-dropdowns.mjs.map