@nexterajs/ui 0.0.1 → 0.0.2

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 (44) hide show
  1. package/fesm2022/nexterajs-ui-core.mjs +189 -5
  2. package/fesm2022/nexterajs-ui-core.mjs.map +1 -1
  3. package/fesm2022/nexterajs-ui-dropdowns.mjs +314 -50
  4. package/fesm2022/nexterajs-ui-dropdowns.mjs.map +1 -1
  5. package/fesm2022/nexterajs-ui-file-uploader.mjs +6 -3
  6. package/fesm2022/nexterajs-ui-file-uploader.mjs.map +1 -1
  7. package/fesm2022/nexterajs-ui-forms.mjs +729 -370
  8. package/fesm2022/nexterajs-ui-forms.mjs.map +1 -1
  9. package/fesm2022/nexterajs-ui-grid.mjs +1188 -208
  10. package/fesm2022/nexterajs-ui-grid.mjs.map +1 -1
  11. package/fesm2022/nexterajs-ui-intl.mjs +15 -5
  12. package/fesm2022/nexterajs-ui-intl.mjs.map +1 -1
  13. package/fesm2022/nexterajs-ui-notify.mjs +351 -0
  14. package/fesm2022/nexterajs-ui-notify.mjs.map +1 -0
  15. package/fesm2022/nexterajs-ui-pivot.mjs +146 -116
  16. package/fesm2022/nexterajs-ui-pivot.mjs.map +1 -1
  17. package/fesm2022/nexterajs-ui-styling.mjs +568 -3
  18. package/fesm2022/nexterajs-ui-styling.mjs.map +1 -1
  19. package/fesm2022/nexterajs-ui-tree-view.mjs +147 -18
  20. package/fesm2022/nexterajs-ui-tree-view.mjs.map +1 -1
  21. package/fesm2022/nexterajs-ui-validation.mjs +247 -92
  22. package/fesm2022/nexterajs-ui-validation.mjs.map +1 -1
  23. package/fesm2022/nexterajs-ui.mjs +1 -0
  24. package/fesm2022/nexterajs-ui.mjs.map +1 -1
  25. package/package.json +5 -1
  26. package/styles/_dropdown.scss +149 -0
  27. package/styles/_editor.scss +23 -4
  28. package/styles/_icons.scss +92 -0
  29. package/styles/_mixins.scss +104 -1
  30. package/styles/_scrollbar.scss +134 -0
  31. package/styles/_toast.scss +243 -0
  32. package/styles/_tokens.scss +48 -0
  33. package/styles/index.scss +4 -0
  34. package/types/nexterajs-ui-core.d.ts +142 -22
  35. package/types/nexterajs-ui-dropdowns.d.ts +145 -27
  36. package/types/nexterajs-ui-file-uploader.d.ts +4 -1
  37. package/types/nexterajs-ui-forms.d.ts +103 -19
  38. package/types/nexterajs-ui-grid.d.ts +445 -57
  39. package/types/nexterajs-ui-notify.d.ts +147 -0
  40. package/types/nexterajs-ui-pivot.d.ts +35 -22
  41. package/types/nexterajs-ui-styling.d.ts +163 -3
  42. package/types/nexterajs-ui-tree-view.d.ts +50 -1
  43. package/types/nexterajs-ui-validation.d.ts +94 -15
  44. package/types/nexterajs-ui.d.ts +1 -0
@@ -5,7 +5,7 @@ import * as i0 from '@angular/core';
5
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
6
  import { NG_VALUE_ACCESSOR } from '@angular/forms';
7
7
  import { merge, filter, distinctUntilChanged, Subject } from 'rxjs';
8
- import { NxeFormControlBase, NxeTemplateDirective, nxeOptionalBoolean, NXE_VALIDATION_TARGET } from '@nexterajs/ui/core';
8
+ import { NxeFormControlBase, NxeTemplateDirective, nxeOptionalBoolean, nxeResolveText, nxeResolveExpr, NXE_VALIDATION_TARGET } from '@nexterajs/ui/core';
9
9
  import { nxePane, trackPaneFlip } from '@nexterajs/ui/styling';
10
10
  import { toObservable, takeUntilDestroyed } from '@angular/core/rxjs-interop';
11
11
 
@@ -14,10 +14,11 @@ import { toObservable, takeUntilDestroyed } from '@angular/core/rxjs-interop';
14
14
  *
15
15
  * A signal-CVA single-select dropdown over a plain array (`[dataSource]`). It bakes in the app's
16
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}.
17
+ * `searchEnabled`), so the bound control value is the `valueExpr` field (a string code) and the
18
+ * field shows the `displayExpr` field. The clear button follows dx and is off unless asked for.
19
+ * Dropdown is a CDK overlay with an in-field search filter. Emits `(valueChange)` (the code) and
20
+ * `(onSelectionChanged)` (`{selectedItem}`). Hosts a projected `nxe-validator` via
21
+ * {@link NXE_VALIDATION_TARGET}.
21
22
  */
22
23
  class NxeSelectBoxComponent extends NxeFormControlBase {
23
24
  overlay = inject(Overlay);
@@ -41,13 +42,24 @@ class NxeSelectBoxComponent extends NxeFormControlBase {
41
42
  ...(ngDevMode ? [{ debugName: "valueExpr" }] : /* istanbul ignore next */ []));
42
43
  /** Field matched while searching (DevExtreme default `lovName`). */
43
44
  searchExprInput = input(undefined, { ...(ngDevMode ? { debugName: "searchExprInput" } : /* istanbul ignore next */ {}), alias: 'searchExpr' });
44
- searchExpr = computed(() => this.searchExprInput() ?? this.nxeDefaults.selectBox?.searchExpr ?? 'lovName', /* @ts-ignore */
45
+ /**
46
+ * dx searches by `displayExpr` when `searchExpr` is not given. Defaulting to the literal
47
+ * 'lovName' instead meant that any picker with a custom displayExpr searched a field its
48
+ * items do not have, and found nothing - silently, because an empty result looks like no
49
+ * match. Where displayExpr is the default this resolves to 'lovName' exactly as before.
50
+ */
51
+ searchExpr = computed(() => this.searchExprInput() ?? this.nxeDefaults.selectBox?.searchExpr ?? this.displayExpr(), /* @ts-ignore */
45
52
  ...(ngDevMode ? [{ debugName: "searchExpr" }] : /* istanbul ignore next */ []));
46
53
  searchEnabledInput = input(undefined, { ...(ngDevMode ? { debugName: "searchEnabledInput" } : /* istanbul ignore next */ {}), alias: 'searchEnabled', transform: nxeOptionalBoolean });
47
54
  searchEnabled = computed(() => this.searchEnabledInput() ?? this.nxeDefaults.selectBox?.searchEnabled ?? true, /* @ts-ignore */
48
55
  ...(ngDevMode ? [{ debugName: "searchEnabled" }] : /* istanbul ignore next */ []));
49
56
  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 */
57
+ /**
58
+ * dx defaults `showClearButton` to false (m_text_editor.base.js). The library used to default it
59
+ * on, which gave every host that never asked for one a clear button dx would not show. An app
60
+ * that wants it everywhere says so once through `NXE_DEFAULT_OPTIONS.selectBox.showClearButton`.
61
+ */
62
+ showClearButton = computed(() => this.showClearButtonInput() ?? this.nxeDefaults.selectBox?.showClearButton ?? false, /* @ts-ignore */
51
63
  ...(ngDevMode ? [{ debugName: "showClearButton" }] : /* istanbul ignore next */ []));
52
64
  /** dx parity: dx-select-box shows its drop-down arrow unless showDropDownButton=false. */
53
65
  showDropDownButtonInput = input(undefined, { ...(ngDevMode ? { debugName: "showDropDownButtonInput" } : /* istanbul ignore next */ {}), alias: 'showDropDownButton', transform: nxeOptionalBoolean });
@@ -86,15 +98,10 @@ class NxeSelectBoxComponent extends NxeFormControlBase {
86
98
  items = computed(() => this.dataSource() ?? [], /* @ts-ignore */
87
99
  ...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
88
100
  display(item) {
89
- const expr = this.displayExpr();
90
- if (!expr) {
91
- return item == null ? '' : String(item);
92
- }
93
- return item?.[expr] ?? '';
101
+ return nxeResolveText(this.displayExpr(), item);
94
102
  }
95
103
  extractValue(item) {
96
- const expr = this.valueExpr();
97
- return expr ? item?.[expr] : item;
104
+ return nxeResolveExpr(this.valueExpr(), item);
98
105
  }
99
106
  selectedItem = computed(() => {
100
107
  const v = this.value();
@@ -124,12 +131,9 @@ class NxeSelectBoxComponent extends NxeFormControlBase {
124
131
  if (!this.searchEnabled() || !search) {
125
132
  return items;
126
133
  }
127
- const expr = this.searchExpr() || this.displayExpr();
134
+ const expr = this.searchExpr();
128
135
  const needle = search.toLowerCase();
129
- return items.filter((it) => {
130
- const hay = (expr ? it?.[expr] : it) ?? '';
131
- return String(hay).toLowerCase().includes(needle);
132
- });
136
+ return items.filter((it) => nxeResolveText(expr, it).toLowerCase().includes(needle));
133
137
  }, /* @ts-ignore */
134
138
  ...(ngDevMode ? [{ debugName: "filteredItems" }] : /* istanbul ignore next */ []));
135
139
  itemTemplateRef = computed(() => this.templates().find((t) => t.name() === this.itemTemplate())?.templateRef ?? null, /* @ts-ignore */
@@ -267,7 +271,7 @@ class NxeSelectBoxComponent extends NxeFormControlBase {
267
271
  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
272
  { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NxeSelectBoxComponent), multi: true },
269
273
  { 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 });
274
+ ], 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 [attr.tabindex]=\"tabIndex()\"\r\n role=\"combobox\"\r\n aria-haspopup=\"listbox\"\r\n [attr.aria-expanded]=\"open()\"\r\n [attr.aria-controls]=\"open() ? panelId() : null\"\r\n [attr.aria-activedescendant]=\"open() && activeIndex() >= 0 ? optionId(activeIndex()) : null\"\r\n [id]=\"controlId()\"\r\n [attr.aria-label]=\"ariaLabel() || null\"\r\n [attr.aria-labelledby]=\"ariaLabelledBy() || null\"\r\n [attr.aria-invalid]=\"validationError() ? true : null\"\r\n [attr.aria-required]=\"required() ? true : null\"\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 <!-- A \u00D816 disc with a 2px \u00D7 cut out: two 2px bars, each running corner to corner of the\r\n 8px square centred on the disc, drawn as a plus and turned 45\u00B0. -->\r\n <svg viewBox=\"0 0 18 18\" focusable=\"false\">\r\n <path fill-rule=\"evenodd\" transform=\"rotate(45 9 9)\"\r\n d=\"M9 1a8 8 0 1 1 0 16A8 8 0 1 1 9 1ZM8 3.343h2V8h4.657v2H10v4.657H8V10H3.343V8H8Z\" />\r\n </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 <!-- An 8x4 triangle centred in the 18px box. -->\r\n <svg viewBox=\"0 0 18 18\" focusable=\"false\"><path d=\"M5 7h8l-4 4z\" /></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 [id]=\"panelId()\"\r\n role=\"listbox\">\r\n @for (item of filteredItems(); track $index; let i = $index) {\r\n <div\r\n class=\"nxe-list-item\"\r\n [id]=\"optionId(i)\"\r\n role=\"option\"\r\n [attr.aria-selected]=\"isSelected(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{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:#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:#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:#e6e6e6}nxe-select-box.nxe-open .nxe-select-arrow{background-color:#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
275
  }
272
276
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeSelectBoxComponent, decorators: [{
273
277
  type: Component,
@@ -280,15 +284,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImpor
280
284
  }, providers: [
281
285
  { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NxeSelectBoxComponent), multi: true },
282
286
  { 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"] }]
287
+ ], 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 [attr.tabindex]=\"tabIndex()\"\r\n role=\"combobox\"\r\n aria-haspopup=\"listbox\"\r\n [attr.aria-expanded]=\"open()\"\r\n [attr.aria-controls]=\"open() ? panelId() : null\"\r\n [attr.aria-activedescendant]=\"open() && activeIndex() >= 0 ? optionId(activeIndex()) : null\"\r\n [id]=\"controlId()\"\r\n [attr.aria-label]=\"ariaLabel() || null\"\r\n [attr.aria-labelledby]=\"ariaLabelledBy() || null\"\r\n [attr.aria-invalid]=\"validationError() ? true : null\"\r\n [attr.aria-required]=\"required() ? true : null\"\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 <!-- A \u00D816 disc with a 2px \u00D7 cut out: two 2px bars, each running corner to corner of the\r\n 8px square centred on the disc, drawn as a plus and turned 45\u00B0. -->\r\n <svg viewBox=\"0 0 18 18\" focusable=\"false\">\r\n <path fill-rule=\"evenodd\" transform=\"rotate(45 9 9)\"\r\n d=\"M9 1a8 8 0 1 1 0 16A8 8 0 1 1 9 1ZM8 3.343h2V8h4.657v2H10v4.657H8V10H3.343V8H8Z\" />\r\n </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 <!-- An 8x4 triangle centred in the 18px box. -->\r\n <svg viewBox=\"0 0 18 18\" focusable=\"false\"><path d=\"M5 7h8l-4 4z\" /></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 [id]=\"panelId()\"\r\n role=\"listbox\">\r\n @for (item of filteredItems(); track $index; let i = $index) {\r\n <div\r\n class=\"nxe-list-item\"\r\n [id]=\"optionId(i)\"\r\n role=\"option\"\r\n [attr.aria-selected]=\"isSelected(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{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:#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:#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:#e6e6e6}nxe-select-box.nxe-open .nxe-select-arrow{background-color:#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"] }]
284
288
  }], 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
289
 
286
290
  /**
287
291
  * `nxe-tag-box` — drop-in replacement for DevExtreme `dx-tag-box` (multi-select sibling of
288
292
  * `nxe-select-box`). The bound value is an **array of `valueExpr` keys** (`lovCode` strings).
289
293
  * 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`.
294
+ * search + selection-controls on); the clear button follows dx and is off unless asked for.
295
+ * Selected items render as removable chips; with selection controls the dropdown shows a checkbox
296
+ * per row under a tri-state "Select All". Provides {@link NXE_VALIDATION_TARGET} for a projected
297
+ * `nxe-validator`.
292
298
  */
293
299
  class NxeTagBoxComponent extends NxeFormControlBase {
294
300
  overlay = inject(Overlay);
@@ -306,14 +312,37 @@ class NxeTagBoxComponent extends NxeFormControlBase {
306
312
  valueExpr = computed(() => this.valueExprInput() ?? this.nxeDefaults.tagBox?.valueExpr ?? 'lovCode', /* @ts-ignore */
307
313
  ...(ngDevMode ? [{ debugName: "valueExpr" }] : /* istanbul ignore next */ []));
308
314
  searchExprInput = input(undefined, { ...(ngDevMode ? { debugName: "searchExprInput" } : /* istanbul ignore next */ {}), alias: 'searchExpr' });
309
- searchExpr = computed(() => this.searchExprInput() ?? this.nxeDefaults.tagBox?.searchExpr ?? 'lovName', /* @ts-ignore */
315
+ /**
316
+ * dx searches by `displayExpr` when `searchExpr` is not given. Defaulting to the literal
317
+ * 'lovName' instead meant that any picker with a custom displayExpr searched a field its
318
+ * items do not have, and found nothing - silently, because an empty result looks like no
319
+ * match. Where displayExpr is the default this resolves to 'lovName' exactly as before.
320
+ */
321
+ searchExpr = computed(() => this.searchExprInput() ?? this.nxeDefaults.tagBox?.searchExpr ?? this.displayExpr(), /* @ts-ignore */
310
322
  ...(ngDevMode ? [{ debugName: "searchExpr" }] : /* istanbul ignore next */ []));
311
323
  searchEnabledInput = input(undefined, { ...(ngDevMode ? { debugName: "searchEnabledInput" } : /* istanbul ignore next */ {}), alias: 'searchEnabled', transform: nxeOptionalBoolean });
312
324
  searchEnabled = computed(() => this.searchEnabledInput() ?? this.nxeDefaults.tagBox?.searchEnabled ?? true, /* @ts-ignore */
313
325
  ...(ngDevMode ? [{ debugName: "searchEnabled" }] : /* istanbul ignore next */ []));
314
326
  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 */
327
+ /**
328
+ * dx defaults `showClearButton` to false (m_text_editor.base.js). The library used to default it
329
+ * on, which gave every host that never asked for one a clear button dx would not show. An app
330
+ * that wants it everywhere says so once through `NXE_DEFAULT_OPTIONS.tagBox.showClearButton`.
331
+ */
332
+ showClearButton = computed(() => this.showClearButtonInput() ?? this.nxeDefaults.tagBox?.showClearButton ?? false, /* @ts-ignore */
316
333
  ...(ngDevMode ? [{ debugName: "showClearButton" }] : /* istanbul ignore next */ []));
334
+ showSelectionControlsInput = input(undefined, { ...(ngDevMode ? { debugName: "showSelectionControlsInput" } : /* istanbul ignore next */ {}), alias: 'showSelectionControls', transform: nxeOptionalBoolean });
335
+ /**
336
+ * dx `showSelectionControls`. On (the literal, and what every consumer site passes): a checkbox
337
+ * per row and a tri-state "Select All" row — dx switches its list to `selectionMode: 'all'`
338
+ * (m_tag_box.js `_listConfig`). Off: plain rows whose fill marks the selection
339
+ * (`selectionMode: 'multiple'`).
340
+ */
341
+ showSelectionControls = computed(() => this.showSelectionControlsInput() ?? this.nxeDefaults.tagBox?.showSelectionControls ?? true, /* @ts-ignore */
342
+ ...(ngDevMode ? [{ debugName: "showSelectionControls" }] : /* istanbul ignore next */ []));
343
+ /** dx `selectAllText` — the label of the "Select All" row (dx: `dxList-selectAll`). */
344
+ selectAllText = input('Select All', /* @ts-ignore */
345
+ ...(ngDevMode ? [{ debugName: "selectAllText" }] : /* istanbul ignore next */ []));
317
346
  /** dx parity: dx-tag-box does NOT show a drop-down arrow unless showDropDownButton is set. */
318
347
  showDropDownButtonInput = input(undefined, { ...(ngDevMode ? { debugName: "showDropDownButtonInput" } : /* istanbul ignore next */ {}), alias: 'showDropDownButton', transform: nxeOptionalBoolean });
319
348
  showDropDownButton = computed(() => this.showDropDownButtonInput() ?? this.nxeDefaults.tagBox?.showDropDownButton ?? false, /* @ts-ignore */
@@ -341,15 +370,10 @@ class NxeTagBoxComponent extends NxeFormControlBase {
341
370
  }, /* @ts-ignore */
342
371
  ...(ngDevMode ? [{ debugName: "selectedKeys" }] : /* istanbul ignore next */ []));
343
372
  display(item) {
344
- const expr = this.displayExpr();
345
- if (!expr) {
346
- return item == null ? '' : String(item);
347
- }
348
- return item?.[expr] ?? '';
373
+ return nxeResolveText(this.displayExpr(), item);
349
374
  }
350
375
  extractValue(item) {
351
- const expr = this.valueExpr();
352
- return expr ? item?.[expr] : item;
376
+ return nxeResolveExpr(this.valueExpr(), item);
353
377
  }
354
378
  /** Selected items resolved from the value keys (for chip rendering). */
355
379
  selectedItems = computed(() => {
@@ -365,17 +389,29 @@ class NxeTagBoxComponent extends NxeFormControlBase {
365
389
  if (!this.searchEnabled() || !search) {
366
390
  return items;
367
391
  }
368
- const expr = this.searchExpr() || this.displayExpr();
392
+ const expr = this.searchExpr();
369
393
  const needle = search.toLowerCase();
370
- return items.filter((it) => {
371
- const hay = (expr ? it?.[expr] : it) ?? '';
372
- return String(hay).toLowerCase().includes(needle);
373
- });
394
+ return items.filter((it) => nxeResolveText(expr, it).toLowerCase().includes(needle));
374
395
  }, /* @ts-ignore */
375
396
  ...(ngDevMode ? [{ debugName: "filteredItems" }] : /* istanbul ignore next */ []));
376
397
  isSelected(item) {
377
398
  return this.selectedKeys().includes(this.extractValue(item));
378
399
  }
400
+ /**
401
+ * The "Select All" checkbox value over the rows currently listed — dx `selectAllMode: 'page'`,
402
+ * so a search narrows what it counts: `true` when every listed row is selected, `false` when
403
+ * none is, and `undefined` (indeterminate) in between.
404
+ */
405
+ selectAllState = computed(() => {
406
+ const items = this.filteredItems();
407
+ const keys = this.selectedKeys();
408
+ const selected = items.filter((it) => keys.includes(this.extractValue(it))).length;
409
+ if (selected === 0) {
410
+ return false;
411
+ }
412
+ return selected === items.length ? true : undefined;
413
+ }, /* @ts-ignore */
414
+ ...(ngDevMode ? [{ debugName: "selectAllState" }] : /* istanbul ignore next */ []));
379
415
  onFieldMousedown(event) {
380
416
  if (this.readOnly() || this.disabled()) {
381
417
  return;
@@ -411,7 +447,24 @@ class NxeTagBoxComponent extends NxeFormControlBase {
411
447
  input.value = '';
412
448
  }
413
449
  }
450
+ /**
451
+ * dx `_selectAllHandler`: anything short of all-selected selects every listed row (keeping
452
+ * selections the search hides); all-selected deselects the listed rows only.
453
+ */
454
+ toggleAll(event) {
455
+ event.preventDefault();
456
+ const listed = this.filteredItems().map((it) => this.extractValue(it));
457
+ const keys = this.selectedKeys();
458
+ const next = this.selectAllState() === true
459
+ ? keys.filter((k) => !listed.includes(k))
460
+ : [...keys, ...listed.filter((k) => !keys.includes(k))];
461
+ this.commitValue(next, event);
462
+ }
414
463
  removeTag(item, event) {
464
+ // The × stays rendered in readOnly/disabled, as dx's does; its action is what is withheld.
465
+ if (this.readOnly() || this.disabled()) {
466
+ return;
467
+ }
415
468
  event.preventDefault();
416
469
  event.stopPropagation();
417
470
  const key = this.extractValue(item);
@@ -469,10 +522,10 @@ class NxeTagBoxComponent extends NxeFormControlBase {
469
522
  this.overlayRef?.dispose();
470
523
  }
471
524
  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: [
525
+ 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 } }, 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
526
  { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NxeTagBoxComponent), multi: true },
474
527
  { 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 });
528
+ ], 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: "<!--\r\n Two columns, as dx-tag-box renders them: the wrapping tag container (dx turns\r\n `.dx-texteditor-input-container` into `.dx-tag-container`) and, beside it, the buttons \u2014 never\r\n inside it, so they cannot wrap onto a tag row and they centre on the whole editor height.\r\n-->\r\n<div class=\"nxe-editor-container\" (mousedown)=\"onFieldMousedown($event)\">\r\n <div class=\"nxe-tag-container\">\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 <!-- Rendered in every state, as dx does (m_tag_box.js appends it unconditionally);\r\n readOnly/disabled make it inert rather than removing it. -->\r\n <span class=\"nxe-tag-remove\" (mousedown)=\"removeTag(item, $event)\" aria-hidden=\"true\"></span>\r\n </span>\r\n }\r\n\r\n <input\r\n #input\r\n class=\"nxe-editor-input nxe-tag-input\"\r\n [attr.tabindex]=\"tabIndex()\"\r\n role=\"combobox\"\r\n aria-haspopup=\"listbox\"\r\n [attr.aria-expanded]=\"open()\"\r\n [attr.aria-controls]=\"open() ? panelId() : null\"\r\n\r\n [id]=\"controlId()\"\r\n [attr.aria-label]=\"ariaLabel() || null\"\r\n [attr.aria-labelledby]=\"ariaLabelledBy() || null\"\r\n [attr.aria-invalid]=\"validationError() ? true : null\"\r\n [attr.aria-required]=\"required() ? true : null\"\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 </div>\r\n\r\n @if (showClearButton() && hasValue() && !readOnly() && !disabled()) {\r\n <span class=\"nxe-select-clear\" (mousedown)=\"onClear($event)\" aria-hidden=\"true\">\r\n <!-- A \u00D816 disc with a 2px \u00D7 cut out: two 2px bars, each running corner to corner of the\r\n 8px square centred on the disc, drawn as a plus and turned 45\u00B0. -->\r\n <svg viewBox=\"0 0 18 18\" focusable=\"false\">\r\n <path fill-rule=\"evenodd\" transform=\"rotate(45 9 9)\"\r\n d=\"M9 1a8 8 0 1 1 0 16A8 8 0 1 1 9 1ZM8 3.343h2V8h4.657v2H10v4.657H8V10H3.343V8H8Z\" />\r\n </svg>\r\n </span>\r\n }\r\n @if (showDropDownButton() && !readOnly()) {\r\n <span class=\"nxe-select-arrow\" aria-hidden=\"true\">\r\n <!-- An 8x4 triangle centred in the 18px box. -->\r\n <svg viewBox=\"0 0 18 18\" focusable=\"false\"><path d=\"M5 7h8l-4 4z\" /></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 [id]=\"panelId()\"\r\n role=\"listbox\"\r\n aria-multiselectable=\"true\">\r\n @if (showSelectionControls()) {\r\n <!-- dx `selectionMode: 'all'`, which showSelectionControls switches on\r\n (m_tag_box.js _listConfig): a tri-state \"Select All\" over the rows shown. Hidden\r\n while the list is empty, as `.dx-empty-collection .dx-list-select-all` is. -->\r\n @if (filteredItems().length) {\r\n <div\r\n class=\"nxe-tag-select-all\"\r\n [id]=\"panelId() + '-select-all'\"\r\n role=\"option\"\r\n [attr.aria-selected]=\"selectAllState() === true\"\r\n [attr.aria-checked]=\"selectAllState() === undefined ? 'mixed' : selectAllState()\"\r\n (mousedown)=\"toggleAll($event)\">\r\n <span\r\n class=\"nxe-tag-check\"\r\n [class.nxe-tag-checked]=\"selectAllState() === true\"\r\n [class.nxe-tag-indeterminate]=\"selectAllState() === undefined\"\r\n aria-hidden=\"true\"></span>\r\n <span class=\"nxe-tag-select-all-label\">{{ selectAllText() }}</span>\r\n </div>\r\n }\r\n @for (item of filteredItems(); track $index; let i = $index) {\r\n <div\r\n class=\"nxe-list-item nxe-tag-item\"\r\n [id]=\"optionId(i)\"\r\n role=\"option\"\r\n [attr.aria-selected]=\"isSelected(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 } @else {\r\n <!-- dx `selectionMode: 'multiple'`: plain rows, selection shown by the row fill. -->\r\n @for (item of filteredItems(); track $index; let i = $index) {\r\n <div\r\n class=\"nxe-list-item\"\r\n [id]=\"optionId(i)\"\r\n role=\"option\"\r\n [attr.aria-selected]=\"isSelected(item)\"\r\n [class.nxe-list-item-selected]=\"isSelected(item)\"\r\n (mousedown)=\"toggle(item, $event)\">\r\n {{ display(item) }}\r\n </div>\r\n } @empty {\r\n <div class=\"nxe-list-empty\">No data to display</div>\r\n }\r\n }\r\n </div>\r\n</ng-template>\r\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:#ddd;border-radius:2px;color:#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:#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:#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:#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:#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:#e6e6e6}nxe-tag-box.nxe-open .nxe-select-arrow{background-color:#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
529
  }
477
530
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeTagBoxComponent, decorators: [{
478
531
  type: Component,
@@ -485,8 +538,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImpor
485
538
  }, providers: [
486
539
  { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NxeTagBoxComponent), multi: true },
487
540
  { 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 }] }] } });
541
+ ], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<!--\r\n Two columns, as dx-tag-box renders them: the wrapping tag container (dx turns\r\n `.dx-texteditor-input-container` into `.dx-tag-container`) and, beside it, the buttons \u2014 never\r\n inside it, so they cannot wrap onto a tag row and they centre on the whole editor height.\r\n-->\r\n<div class=\"nxe-editor-container\" (mousedown)=\"onFieldMousedown($event)\">\r\n <div class=\"nxe-tag-container\">\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 <!-- Rendered in every state, as dx does (m_tag_box.js appends it unconditionally);\r\n readOnly/disabled make it inert rather than removing it. -->\r\n <span class=\"nxe-tag-remove\" (mousedown)=\"removeTag(item, $event)\" aria-hidden=\"true\"></span>\r\n </span>\r\n }\r\n\r\n <input\r\n #input\r\n class=\"nxe-editor-input nxe-tag-input\"\r\n [attr.tabindex]=\"tabIndex()\"\r\n role=\"combobox\"\r\n aria-haspopup=\"listbox\"\r\n [attr.aria-expanded]=\"open()\"\r\n [attr.aria-controls]=\"open() ? panelId() : null\"\r\n\r\n [id]=\"controlId()\"\r\n [attr.aria-label]=\"ariaLabel() || null\"\r\n [attr.aria-labelledby]=\"ariaLabelledBy() || null\"\r\n [attr.aria-invalid]=\"validationError() ? true : null\"\r\n [attr.aria-required]=\"required() ? true : null\"\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 </div>\r\n\r\n @if (showClearButton() && hasValue() && !readOnly() && !disabled()) {\r\n <span class=\"nxe-select-clear\" (mousedown)=\"onClear($event)\" aria-hidden=\"true\">\r\n <!-- A \u00D816 disc with a 2px \u00D7 cut out: two 2px bars, each running corner to corner of the\r\n 8px square centred on the disc, drawn as a plus and turned 45\u00B0. -->\r\n <svg viewBox=\"0 0 18 18\" focusable=\"false\">\r\n <path fill-rule=\"evenodd\" transform=\"rotate(45 9 9)\"\r\n d=\"M9 1a8 8 0 1 1 0 16A8 8 0 1 1 9 1ZM8 3.343h2V8h4.657v2H10v4.657H8V10H3.343V8H8Z\" />\r\n </svg>\r\n </span>\r\n }\r\n @if (showDropDownButton() && !readOnly()) {\r\n <span class=\"nxe-select-arrow\" aria-hidden=\"true\">\r\n <!-- An 8x4 triangle centred in the 18px box. -->\r\n <svg viewBox=\"0 0 18 18\" focusable=\"false\"><path d=\"M5 7h8l-4 4z\" /></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 [id]=\"panelId()\"\r\n role=\"listbox\"\r\n aria-multiselectable=\"true\">\r\n @if (showSelectionControls()) {\r\n <!-- dx `selectionMode: 'all'`, which showSelectionControls switches on\r\n (m_tag_box.js _listConfig): a tri-state \"Select All\" over the rows shown. Hidden\r\n while the list is empty, as `.dx-empty-collection .dx-list-select-all` is. -->\r\n @if (filteredItems().length) {\r\n <div\r\n class=\"nxe-tag-select-all\"\r\n [id]=\"panelId() + '-select-all'\"\r\n role=\"option\"\r\n [attr.aria-selected]=\"selectAllState() === true\"\r\n [attr.aria-checked]=\"selectAllState() === undefined ? 'mixed' : selectAllState()\"\r\n (mousedown)=\"toggleAll($event)\">\r\n <span\r\n class=\"nxe-tag-check\"\r\n [class.nxe-tag-checked]=\"selectAllState() === true\"\r\n [class.nxe-tag-indeterminate]=\"selectAllState() === undefined\"\r\n aria-hidden=\"true\"></span>\r\n <span class=\"nxe-tag-select-all-label\">{{ selectAllText() }}</span>\r\n </div>\r\n }\r\n @for (item of filteredItems(); track $index; let i = $index) {\r\n <div\r\n class=\"nxe-list-item nxe-tag-item\"\r\n [id]=\"optionId(i)\"\r\n role=\"option\"\r\n [attr.aria-selected]=\"isSelected(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 } @else {\r\n <!-- dx `selectionMode: 'multiple'`: plain rows, selection shown by the row fill. -->\r\n @for (item of filteredItems(); track $index; let i = $index) {\r\n <div\r\n class=\"nxe-list-item\"\r\n [id]=\"optionId(i)\"\r\n role=\"option\"\r\n [attr.aria-selected]=\"isSelected(item)\"\r\n [class.nxe-list-item-selected]=\"isSelected(item)\"\r\n (mousedown)=\"toggle(item, $event)\">\r\n {{ display(item) }}\r\n </div>\r\n } @empty {\r\n <div class=\"nxe-list-empty\">No data to display</div>\r\n }\r\n }\r\n </div>\r\n</ng-template>\r\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:#ddd;border-radius:2px;color:#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:#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:#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:#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:#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:#e6e6e6}nxe-tag-box.nxe-open .nxe-select-arrow{background-color:#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"] }]
542
+ }], 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 }] }] } });
490
543
 
491
544
  /**
492
545
  * `nxe-drop-down-options` — drop-in for DevExtreme `dxo-drop-down-options`, a no-template config
@@ -621,8 +674,8 @@ class NxeDropDownBoxComponent extends NxeFormControlBase {
621
674
  }
622
675
  // The bound value is the code; match it against valueExpr (default the implicit `lovCode`).
623
676
  const ve = this.valueExpr() || 'lovCode';
624
- const item = (this.dataSource() ?? []).find((it) => it?.[ve] === v);
625
- return item ? String(item[de] ?? '') : String(v);
677
+ const item = (this.dataSource() ?? []).find((it) => nxeResolveExpr(ve, it) === v);
678
+ return item ? nxeResolveText(de, item) : String(v);
626
679
  }, /* @ts-ignore */
627
680
  ...(ngDevMode ? [{ debugName: "displayText" }] : /* istanbul ignore next */ []));
628
681
  hasValue = computed(() => {
@@ -694,7 +747,7 @@ class NxeDropDownBoxComponent extends NxeFormControlBase {
694
747
  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
748
  { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NxeDropDownBoxComponent), multi: true },
696
749
  { 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 });
750
+ ], 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 [attr.tabindex]=\"tabIndex()\"\r\n role=\"combobox\"\r\n aria-haspopup=\"dialog\"\r\n [attr.aria-expanded]=\"opened()\"\r\n [attr.aria-controls]=\"opened() ? panelId() : null\"\r\n [id]=\"controlId()\"\r\n [attr.aria-label]=\"ariaLabel() || null\"\r\n [attr.aria-labelledby]=\"ariaLabelledBy() || null\"\r\n [attr.aria-invalid]=\"validationError() ? true : null\"\r\n [attr.aria-required]=\"required() ? true : null\"\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 <!-- A \u00D816 disc with a 2px \u00D7 cut out: two 2px bars, each running corner to corner of the\r\n 8px square centred on the disc, drawn as a plus and turned 45\u00B0. -->\r\n <svg viewBox=\"0 0 18 18\" focusable=\"false\">\r\n <path fill-rule=\"evenodd\" transform=\"rotate(45 9 9)\"\r\n d=\"M9 1a8 8 0 1 1 0 16A8 8 0 1 1 9 1ZM8 3.343h2V8h4.657v2H10v4.657H8V10H3.343V8H8Z\" />\r\n </svg>\r\n </span>\r\n }\r\n\r\n @if (!readOnly()) {\r\n <span class=\"nxe-select-arrow\" aria-hidden=\"true\">\r\n <!-- An 8x4 triangle centred in the 18px box. -->\r\n <svg viewBox=\"0 0 18 18\" focusable=\"false\"><path d=\"M5 7h8l-4 4z\" /></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\" [id]=\"panelId()\" role=\"dialog\" [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{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:#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:#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:#e6e6e6}nxe-drop-down-box.nxe-open .nxe-select-arrow{background-color:#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:#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
751
  }
699
752
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeDropDownBoxComponent, decorators: [{
700
753
  type: Component,
@@ -706,7 +759,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImpor
706
759
  }, providers: [
707
760
  { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NxeDropDownBoxComponent), multi: true },
708
761
  { 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"] }]
762
+ ], 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 [attr.tabindex]=\"tabIndex()\"\r\n role=\"combobox\"\r\n aria-haspopup=\"dialog\"\r\n [attr.aria-expanded]=\"opened()\"\r\n [attr.aria-controls]=\"opened() ? panelId() : null\"\r\n [id]=\"controlId()\"\r\n [attr.aria-label]=\"ariaLabel() || null\"\r\n [attr.aria-labelledby]=\"ariaLabelledBy() || null\"\r\n [attr.aria-invalid]=\"validationError() ? true : null\"\r\n [attr.aria-required]=\"required() ? true : null\"\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 <!-- A \u00D816 disc with a 2px \u00D7 cut out: two 2px bars, each running corner to corner of the\r\n 8px square centred on the disc, drawn as a plus and turned 45\u00B0. -->\r\n <svg viewBox=\"0 0 18 18\" focusable=\"false\">\r\n <path fill-rule=\"evenodd\" transform=\"rotate(45 9 9)\"\r\n d=\"M9 1a8 8 0 1 1 0 16A8 8 0 1 1 9 1ZM8 3.343h2V8h4.657v2H10v4.657H8V10H3.343V8H8Z\" />\r\n </svg>\r\n </span>\r\n }\r\n\r\n @if (!readOnly()) {\r\n <span class=\"nxe-select-arrow\" aria-hidden=\"true\">\r\n <!-- An 8x4 triangle centred in the 18px box. -->\r\n <svg viewBox=\"0 0 18 18\" focusable=\"false\"><path d=\"M5 7h8l-4 4z\" /></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\" [id]=\"panelId()\" role=\"dialog\" [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{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:#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:#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:#e6e6e6}nxe-drop-down-box.nxe-open .nxe-select-arrow{background-color:#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:#fff;border:1px solid #ddd;border-radius:6px;box-shadow:0 6px 12px #0000002d;overflow:auto;max-height:420px}\n"] }]
710
763
  }], 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
764
 
712
765
  /** DI token the menu provides so projected `nxe-menu-item`s can close it on click. */
@@ -731,6 +784,20 @@ class NxeListComponent {
731
784
  /** DevExtreme `[elementAttr]` — `{ id, class }` applied to the host. */
732
785
  elementAttr = input(/* @ts-ignore */
733
786
  ...(ngDevMode ? [undefined, { debugName: "elementAttr" }] : /* istanbul ignore next */ []));
787
+ /**
788
+ * dx `keyExpr` — the field identifying an item. Used for `@for` tracking, so a list whose
789
+ * items are replaced by equal-but-not-identical objects keeps its DOM instead of rebuilding
790
+ * every row. Falls back to the index when unset, which is the previous behaviour.
791
+ */
792
+ keyExpr = input(undefined, /* @ts-ignore */
793
+ ...(ngDevMode ? [{ debugName: "keyExpr" }] : /* istanbul ignore next */ []));
794
+ trackItem(item, index) {
795
+ const key = this.keyExpr();
796
+ if (!key || item == null || typeof item !== 'object') {
797
+ return index;
798
+ }
799
+ return item[key] ?? index;
800
+ }
734
801
  onItemClick = output();
735
802
  items = computed(() => this.dataSource() ?? [], /* @ts-ignore */
736
803
  ...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
@@ -751,8 +818,8 @@ class NxeListComponent {
751
818
  this.menu?.closeMenu();
752
819
  }
753
820
  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) {
821
+ 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: `
822
+ @for (item of items(); track trackItem(item, $index); let last = $last) {
756
823
  <div
757
824
  class="nxe-list-item-row"
758
825
  [class.nxe-state-disabled]="isDisabled(item)"
@@ -772,7 +839,7 @@ class NxeListComponent {
772
839
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeListComponent, decorators: [{
773
840
  type: Component,
774
841
  args: [{ selector: 'nxe-list', standalone: true, imports: [NgTemplateOutlet], template: `
775
- @for (item of items(); track $index; let last = $last) {
842
+ @for (item of items(); track trackItem(item, $index); let last = $last) {
776
843
  <div
777
844
  class="nxe-list-item-row"
778
845
  [class.nxe-state-disabled]="isDisabled(item)"
@@ -791,7 +858,204 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImpor
791
858
  '[class]': "'nxe-widget nxe-list ' + (elementAttr()?.class ?? '')",
792
859
  '[attr.id]': 'elementAttr()?.id ?? null',
793
860
  }, 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"] }] } });
861
+ }], 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"] }] } });
862
+
863
+ /**
864
+ * `nxe-auto-complete` — drop-in replacement for DevExtreme `dx-autocomplete`.
865
+ *
866
+ * The distinction from `nxe-select-box` is the value contract, and it is the whole reason this
867
+ * is a separate component: here the control value is the TEXT the user typed. A suggestion is a
868
+ * shortcut for typing, not a constraint on what may be entered, so free text survives and
869
+ * `valueExpr` only says which field of a picked item becomes that text.
870
+ *
871
+ * Deliberately not merged into the existing Material-shaped `nxe-autocomplete`: that one takes
872
+ * projected `nxe-option` children rather than a `[dataSource]`, so the two have incompatible
873
+ * content models and merging them would make every future change to one a risk to the other.
874
+ */
875
+ class NxeAutoCompleteComponent extends NxeFormControlBase {
876
+ overlay = inject(Overlay);
877
+ viewContainerRef = inject(ViewContainerRef);
878
+ dropdownTemplate = viewChild.required('dropdown', /* @ts-ignore */
879
+ ...(ngDevMode ? [{ debugName: "dropdownTemplate" }] : /* istanbul ignore next */ []));
880
+ inputEl = viewChild('input', /* @ts-ignore */
881
+ ...(ngDevMode ? [{ debugName: "inputEl" }] : /* istanbul ignore next */ []));
882
+ dataSource = input([], /* @ts-ignore */
883
+ ...(ngDevMode ? [{ debugName: "dataSource" }] : /* istanbul ignore next */ []));
884
+ /** Field of a picked item that becomes the value (dx default `lovName`). */
885
+ valueExpr = input('lovName', /* @ts-ignore */
886
+ ...(ngDevMode ? [{ debugName: "valueExpr" }] : /* istanbul ignore next */ []));
887
+ /** Field matched against the query; defaults to {@link valueExpr}, as dx does. */
888
+ searchExprInput = input(undefined, { ...(ngDevMode ? { debugName: "searchExprInput" } : /* istanbul ignore next */ {}), alias: 'searchExpr' });
889
+ searchExpr = computed(() => this.searchExprInput() ?? this.valueExpr(), /* @ts-ignore */
890
+ ...(ngDevMode ? [{ debugName: "searchExpr" }] : /* istanbul ignore next */ []));
891
+ searchMode = input('contains', /* @ts-ignore */
892
+ ...(ngDevMode ? [{ debugName: "searchMode" }] : /* istanbul ignore next */ []));
893
+ /** Characters required before suggestions appear. dx defaults to 1; 0 shows all on focus. */
894
+ minSearchLength = input(1, /* @ts-ignore */
895
+ ...(ngDevMode ? [{ debugName: "minSearchLength" }] : /* istanbul ignore next */ []));
896
+ /** dx caps the suggestion list; beyond this the user is expected to keep typing. */
897
+ maxItemCount = input(10, /* @ts-ignore */
898
+ ...(ngDevMode ? [{ debugName: "maxItemCount" }] : /* istanbul ignore next */ []));
899
+ placeholder = input('', /* @ts-ignore */
900
+ ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
901
+ showClearButton = input(false, { ...(ngDevMode ? { debugName: "showClearButton" } : /* istanbul ignore next */ {}), transform: nxeOptionalBoolean });
902
+ onSelectionChanged = output();
903
+ focused = signal(false, /* @ts-ignore */
904
+ ...(ngDevMode ? [{ debugName: "focused" }] : /* istanbul ignore next */ []));
905
+ open = signal(false, /* @ts-ignore */
906
+ ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
907
+ activeIndex = signal(-1, /* @ts-ignore */
908
+ ...(ngDevMode ? [{ debugName: "activeIndex" }] : /* istanbul ignore next */ []));
909
+ panelWidth = signal(0, /* @ts-ignore */
910
+ ...(ngDevMode ? [{ debugName: "panelWidth" }] : /* istanbul ignore next */ []));
911
+ /** dx `.instance` facade — `focus()` is what the autofocus directive calls. */
912
+ get instance() {
913
+ return this;
914
+ }
915
+ overlayRef;
916
+ closeSub;
917
+ flipSub;
918
+ text = computed(() => this.value() ?? '', /* @ts-ignore */
919
+ ...(ngDevMode ? [{ debugName: "text" }] : /* istanbul ignore next */ []));
920
+ itemText(item) {
921
+ return nxeResolveText(this.valueExpr(), item);
922
+ }
923
+ searchText(item) {
924
+ return nxeResolveText(this.searchExpr(), item);
925
+ }
926
+ suggestions = computed(() => {
927
+ const query = (this.value() ?? '').toLowerCase();
928
+ if (query.length < this.minSearchLength()) {
929
+ return [];
930
+ }
931
+ const mode = this.searchMode();
932
+ const matches = (this.dataSource() ?? []).filter((item) => {
933
+ const candidate = this.searchText(item).toLowerCase();
934
+ if (mode === 'startswith') {
935
+ return candidate.startsWith(query);
936
+ }
937
+ if (mode === 'equals') {
938
+ return candidate === query;
939
+ }
940
+ return candidate.includes(query);
941
+ });
942
+ return matches.slice(0, this.maxItemCount());
943
+ }, /* @ts-ignore */
944
+ ...(ngDevMode ? [{ debugName: "suggestions" }] : /* istanbul ignore next */ []));
945
+ onFieldInput(event) {
946
+ // The typed text IS the value: an autocomplete constrains nothing.
947
+ this.commitValue(event.target.value, event);
948
+ this.activeIndex.set(-1);
949
+ this.openPanel();
950
+ }
951
+ onFocus() {
952
+ this.focused.set(true);
953
+ this.openPanel();
954
+ }
955
+ onBlur() {
956
+ this.focused.set(false);
957
+ this.markAsTouched();
958
+ this.closePanel();
959
+ }
960
+ pick(item, event) {
961
+ event.preventDefault();
962
+ this.commitValue(this.itemText(item), event);
963
+ this.onSelectionChanged.emit({ selectedItem: item, value: this.value() });
964
+ this.closePanel();
965
+ }
966
+ onKeydown(event) {
967
+ if (event.key === 'Escape') {
968
+ this.closePanel();
969
+ return;
970
+ }
971
+ const items = this.suggestions();
972
+ if (!items.length) {
973
+ return;
974
+ }
975
+ if (event.key === 'ArrowDown') {
976
+ event.preventDefault();
977
+ this.openPanel();
978
+ this.activeIndex.update((i) => Math.min(i + 1, items.length - 1));
979
+ }
980
+ else if (event.key === 'ArrowUp') {
981
+ event.preventDefault();
982
+ this.activeIndex.update((i) => Math.max(i - 1, 0));
983
+ }
984
+ else if (event.key === 'Enter' && this.activeIndex() >= 0) {
985
+ this.pick(items[this.activeIndex()], event);
986
+ }
987
+ }
988
+ onClear(event) {
989
+ event.preventDefault();
990
+ this.commitValue('', event);
991
+ this.closePanel();
992
+ }
993
+ focus() {
994
+ this.inputEl()?.nativeElement.focus();
995
+ }
996
+ openPanel() {
997
+ if (this.readOnly() || this.disabled() || !this.suggestions().length) {
998
+ this.closePanel();
999
+ return;
1000
+ }
1001
+ if (this.overlayRef) {
1002
+ return;
1003
+ }
1004
+ const host = this.element;
1005
+ this.panelWidth.set(host.getBoundingClientRect().width);
1006
+ this.overlayRef = this.overlay.create({
1007
+ positionStrategy: this.overlay
1008
+ .position()
1009
+ .flexibleConnectedTo(host)
1010
+ .withPositions([
1011
+ { originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top' },
1012
+ { originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom' },
1013
+ ]),
1014
+ scrollStrategy: this.overlay.scrollStrategies.reposition(),
1015
+ panelClass: nxePane('nxe-autocomplete-box-pane'),
1016
+ });
1017
+ this.overlayRef.attach(new TemplatePortal(this.dropdownTemplate(), this.viewContainerRef));
1018
+ this.flipSub = trackPaneFlip(this.overlayRef);
1019
+ this.open.set(true);
1020
+ }
1021
+ closePanel() {
1022
+ this.overlayRef?.dispose();
1023
+ this.overlayRef = undefined;
1024
+ this.closeSub?.unsubscribe();
1025
+ this.flipSub?.unsubscribe();
1026
+ this.open.set(false);
1027
+ this.activeIndex.set(-1);
1028
+ }
1029
+ ngOnDestroy() {
1030
+ this.closePanel();
1031
+ }
1032
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeAutoCompleteComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
1033
+ 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": "!!validationError()", "class.nxe-open": "open()" }, classAttribute: "nxe-widget nxe-editor nxe-autocomplete-box" }, providers: [
1034
+ {
1035
+ provide: NG_VALUE_ACCESSOR,
1036
+ useExisting: forwardRef(() => NxeAutoCompleteComponent),
1037
+ multi: true,
1038
+ },
1039
+ { provide: NXE_VALIDATION_TARGET, useExisting: forwardRef(() => NxeAutoCompleteComponent) },
1040
+ ], 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\">\r\n <input\r\n #input\r\n class=\"nxe-editor-input nxe-autocomplete-input\"\r\n [attr.tabindex]=\"tabIndex()\"\r\n role=\"combobox\"\r\n aria-haspopup=\"listbox\"\r\n [attr.aria-expanded]=\"open()\"\r\n [attr.aria-controls]=\"open() ? panelId() : null\"\r\n [attr.aria-activedescendant]=\"open() && activeIndex() >= 0 ? optionId(activeIndex()) : null\"\r\n [id]=\"controlId()\"\r\n [attr.aria-label]=\"ariaLabel() || null\"\r\n [attr.aria-labelledby]=\"ariaLabelledBy() || null\"\r\n [attr.aria-invalid]=\"validationError() ? true : null\"\r\n [attr.aria-required]=\"required() ? true : null\"\r\n type=\"text\"\r\n [value]=\"text()\"\r\n [readOnly]=\"readOnly()\"\r\n [disabled]=\"disabled()\"\r\n [attr.placeholder]=\"placeholder() || null\"\r\n autocomplete=\"off\"\r\n (input)=\"onFieldInput($event)\"\r\n (focus)=\"onFocus()\"\r\n (blur)=\"onBlur()\" />\r\n\r\n @if (showClearButton() && text() && !readOnly() && !disabled()) {\r\n <span class=\"nxe-select-clear\" (mousedown)=\"onClear($event)\" aria-hidden=\"true\">\r\n <!-- A \u00D816 disc with a 2px \u00D7 cut out: two 2px bars, each running corner to corner of the\r\n 8px square centred on the disc, drawn as a plus and turned 45\u00B0. -->\r\n <svg viewBox=\"0 0 18 18\" focusable=\"false\">\r\n <path fill-rule=\"evenodd\" transform=\"rotate(45 9 9)\"\r\n d=\"M9 1a8 8 0 1 1 0 16A8 8 0 1 1 9 1ZM8 3.343h2V8h4.657v2H10v4.657H8V10H3.343V8H8Z\" />\r\n </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-autocomplete-box-panel\" [style.width.px]=\"panelWidth()\"\r\n [id]=\"panelId()\"\r\n role=\"listbox\">\r\n @for (item of suggestions(); track $index; let i = $index) {\r\n <div\r\n class=\"nxe-list-item\"\r\n [id]=\"optionId(i)\"\r\n role=\"option\"\r\n [attr.aria-selected]=\"i === activeIndex()\"\r\n [class.nxe-list-item-active]=\"i === activeIndex()\"\r\n (mousedown)=\"pick(item, $event)\">\r\n {{ itemText(item) }}\r\n </div>\r\n }\r\n </div>\r\n</ng-template>\r\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:#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:#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:#e6e6e6}nxe-auto-complete.nxe-open .nxe-select-arrow{background-color:#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 });
1041
+ }
1042
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeAutoCompleteComponent, decorators: [{
1043
+ type: Component,
1044
+ args: [{ selector: 'nxe-auto-complete', standalone: true, host: {
1045
+ class: 'nxe-widget nxe-editor nxe-autocomplete-box',
1046
+ '[class.nxe-focused]': 'focused()',
1047
+ '[class.nxe-invalid]': '!!validationError()',
1048
+ '[class.nxe-open]': 'open()',
1049
+ '(keydown)': 'onKeydown($event)',
1050
+ }, providers: [
1051
+ {
1052
+ provide: NG_VALUE_ACCESSOR,
1053
+ useExisting: forwardRef(() => NxeAutoCompleteComponent),
1054
+ multi: true,
1055
+ },
1056
+ { provide: NXE_VALIDATION_TARGET, useExisting: forwardRef(() => NxeAutoCompleteComponent) },
1057
+ ], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<div class=\"nxe-editor-container\">\r\n <input\r\n #input\r\n class=\"nxe-editor-input nxe-autocomplete-input\"\r\n [attr.tabindex]=\"tabIndex()\"\r\n role=\"combobox\"\r\n aria-haspopup=\"listbox\"\r\n [attr.aria-expanded]=\"open()\"\r\n [attr.aria-controls]=\"open() ? panelId() : null\"\r\n [attr.aria-activedescendant]=\"open() && activeIndex() >= 0 ? optionId(activeIndex()) : null\"\r\n [id]=\"controlId()\"\r\n [attr.aria-label]=\"ariaLabel() || null\"\r\n [attr.aria-labelledby]=\"ariaLabelledBy() || null\"\r\n [attr.aria-invalid]=\"validationError() ? true : null\"\r\n [attr.aria-required]=\"required() ? true : null\"\r\n type=\"text\"\r\n [value]=\"text()\"\r\n [readOnly]=\"readOnly()\"\r\n [disabled]=\"disabled()\"\r\n [attr.placeholder]=\"placeholder() || null\"\r\n autocomplete=\"off\"\r\n (input)=\"onFieldInput($event)\"\r\n (focus)=\"onFocus()\"\r\n (blur)=\"onBlur()\" />\r\n\r\n @if (showClearButton() && text() && !readOnly() && !disabled()) {\r\n <span class=\"nxe-select-clear\" (mousedown)=\"onClear($event)\" aria-hidden=\"true\">\r\n <!-- A \u00D816 disc with a 2px \u00D7 cut out: two 2px bars, each running corner to corner of the\r\n 8px square centred on the disc, drawn as a plus and turned 45\u00B0. -->\r\n <svg viewBox=\"0 0 18 18\" focusable=\"false\">\r\n <path fill-rule=\"evenodd\" transform=\"rotate(45 9 9)\"\r\n d=\"M9 1a8 8 0 1 1 0 16A8 8 0 1 1 9 1ZM8 3.343h2V8h4.657v2H10v4.657H8V10H3.343V8H8Z\" />\r\n </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-autocomplete-box-panel\" [style.width.px]=\"panelWidth()\"\r\n [id]=\"panelId()\"\r\n role=\"listbox\">\r\n @for (item of suggestions(); track $index; let i = $index) {\r\n <div\r\n class=\"nxe-list-item\"\r\n [id]=\"optionId(i)\"\r\n role=\"option\"\r\n [attr.aria-selected]=\"i === activeIndex()\"\r\n [class.nxe-list-item-active]=\"i === activeIndex()\"\r\n (mousedown)=\"pick(item, $event)\">\r\n {{ itemText(item) }}\r\n </div>\r\n }\r\n </div>\r\n</ng-template>\r\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:#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:#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:#e6e6e6}nxe-auto-complete.nxe-open .nxe-select-arrow{background-color:#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"] }]
1058
+ }], 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
1059
 
796
1060
  /** DI token the autocomplete provides so projected `nxe-option`s can close it on click. */
797
1061
  const NXE_AUTOCOMPLETE = new InjectionToken('NXE_AUTOCOMPLETE');
@@ -949,5 +1213,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImpor
949
1213
  * Generated bundle index. Do not edit.
950
1214
  */
951
1215
 
952
- export { NXE_AUTOCOMPLETE, NxeAutocompleteComponent, NxeAutocompleteTriggerDirective, NxeDropDownBoxComponent, NxeDropDownOptionsComponent, NxeListComponent, NxeOptionComponent, NxeSelectBoxComponent, NxeTagBoxComponent };
1216
+ export { NXE_AUTOCOMPLETE, NxeAutoCompleteComponent, NxeAutocompleteComponent, NxeAutocompleteTriggerDirective, NxeDropDownBoxComponent, NxeDropDownOptionsComponent, NxeListComponent, NxeOptionComponent, NxeSelectBoxComponent, NxeTagBoxComponent };
953
1217
  //# sourceMappingURL=nexterajs-ui-dropdowns.mjs.map