@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
@@ -1,6 +1,6 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, ElementRef, input, booleanAttribute, computed, output, ViewEncapsulation, ChangeDetectionStrategy, Component, signal, forwardRef, ViewContainerRef, viewChild } from '@angular/core';
3
- import { NxeValidationGroupRegistry, NxeFormControlBase, nxeOptionalBoolean, NXE_VALIDATION_TARGET } from '@nexterajs/ui/core';
2
+ import { inject, ElementRef, input, booleanAttribute, computed, output, ViewEncapsulation, ChangeDetectionStrategy, Component, signal, viewChild, linkedSignal, forwardRef, ViewContainerRef } from '@angular/core';
3
+ import { NxeValidationGroupRegistry, NXE_VALIDATION_GROUP, NxeFormControlBase, nxeOptionalBoolean, NXE_VALIDATION_TARGET } from '@nexterajs/ui/core';
4
4
  import { NG_VALUE_ACCESSOR } from '@angular/forms';
5
5
  import { parseNumberFormat, NXE_NUMBER_SEPARATORS, formatNumber, parseNumber, roundHalfAwayFromZero, parseDateValue, NXE_DEFAULT_DISPLAY_FORMAT, NXE_DEFAULT_DATETIME_DISPLAY_FORMAT, NXE_DEFAULT_TIME_DISPLAY_FORMAT, formatDisplay, buildCalendarWeeks, buildPeriodCells, parseDisplay, serializeDate } from '@nexterajs/ui/intl';
6
6
  import { Overlay } from '@angular/cdk/overlay';
@@ -22,21 +22,22 @@ function cssSize(value) {
22
22
  /**
23
23
  * `nxe-button` — drop-in replacement for DevExtreme `dx-button`.
24
24
  *
25
- * The host element IS the button (mirrors DevExtreme's `dx-button` DOM: the host carries the
26
- * styling classes and contains a single `.dx-button-content` child). This keeps every existing
27
- * `.nxe-btn*` / `.dx-button-content` global rule matching verbatim, and — critically — keeps the
28
- * shared `buttonLoading` directive working, which swaps `.dx-button-content` innerHTML for a
29
- * spinner (`getElementsByClassName('dx-button-content')[0]`).
25
+ * The host element IS the button (mirrors DevExtreme's `nxe-button` DOM: the host carries the
26
+ * styling classes and contains a single `.nxe-button-content` child). This keeps every existing
27
+ * `.nxe-btn*` / `.nxe-button-content` global rule matching verbatim, and — critically — keeps the
28
+ * shared `buttonLoading` directive working, which swaps `.nxe-button-content` innerHTML for a
29
+ * spinner (`getElementsByClassName('nxe-button-content')[0]`).
30
30
  *
31
31
  * Content is projected (`<nxe-icon> + <span>`, the dominant pattern). The legacy `text`/`type`
32
- * inputs render a `.dx-button-text` label for the ~25 attribute-styled buttons.
32
+ * inputs render a `.nxe-button-text` label for the ~25 attribute-styled buttons.
33
33
  *
34
34
  * `onClick` emits {@link NxeButtonClickEvent}; native `(click)` also fires (27 sites bind it).
35
35
  */
36
36
  class NxeButtonComponent {
37
37
  registry = inject(NxeValidationGroupRegistry);
38
+ groupScope = inject(NXE_VALIDATION_GROUP, { optional: true });
38
39
  elementRef = inject(ElementRef);
39
- /** Legacy `text` label (rendered as `.dx-button-text` when set, no projected content). */
40
+ /** Legacy `text` label (rendered as `.nxe-button-text` when set, no projected content). */
40
41
  text = input(/* @ts-ignore */
41
42
  ...(ngDevMode ? [undefined, { debugName: "text" }] : /* istanbul ignore next */ []));
42
43
  /** Style intent (`default`=blue, `success`=green, `danger`=red, `normal`=white). */
@@ -50,8 +51,8 @@ class NxeButtonComponent {
50
51
  /** DevExtreme parity input; only ever `false` in this codebase (suppresses tab focus). */
51
52
  focusStateEnabled = input(true, { ...(ngDevMode ? { debugName: "focusStateEnabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
52
53
  /**
53
- * dx `icon` — renders `<i class="dx-icon dx-icon-NAME">` as the first child of
54
- * `.dx-button-content`, before any projected content. Projection is the dominant pattern
54
+ * dx `icon` — renders `<i class="nxe-icon nxe-icon-NAME">` as the first child of
55
+ * `.nxe-button-content`, before any projected content. Projection is the dominant pattern
55
56
  * here (a `mat-icon` plus a `span`), so the two must coexist rather than compete.
56
57
  */
57
58
  icon = input(undefined, /* @ts-ignore */
@@ -79,7 +80,10 @@ class NxeButtonComponent {
79
80
  event.stopImmediatePropagation();
80
81
  return;
81
82
  }
82
- const group = this.validationGroup() ?? '';
83
+ // A named group wins; otherwise a surrounding nxe-validation-group; otherwise dx's
84
+ // implicit global group. Same precedence the validator applies, so a button and the
85
+ // fields beside it always resolve to the same group.
86
+ const group = this.validationGroup() || this.groupScope?.groupKey || '';
83
87
  this.onClick.emit({
84
88
  event,
85
89
  validationGroup: { validate: () => this.registry.validate(group) },
@@ -88,28 +92,28 @@ class NxeButtonComponent {
88
92
  });
89
93
  }
90
94
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
91
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", type: NxeButtonComponent, isStandalone: true, selector: "nxe-button", inputs: { text: { classPropertyName: "text", publicName: "text", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, validationGroup: { classPropertyName: "validationGroup", publicName: "validationGroup", isSignal: true, isRequired: false, transformFunction: null }, focusStateEnabled: { classPropertyName: "focusStateEnabled", publicName: "focusStateEnabled", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onClick: "onClick" }, host: { attributes: { "role": "button" }, listeners: { "click": "handleClick($event)", "keydown.enter": "handleKeydown($event)", "keydown.space": "handleKeydown($event)" }, properties: { "attr.tabindex": "disabled() || !focusStateEnabled() ? null : 0", "attr.aria-disabled": "disabled() ? true : null", "class.nxe-button-default": "type() === 'default'", "class.nxe-button-success": "type() === 'success'", "class.nxe-button-danger": "type() === 'danger'", "class.nxe-button-has-text": "!!text()", "class.dx-button-has-text": "!!text()", "class.dx-button-has-icon": "!!icon()", "style.height": "heightStyle()", "style.width": "widthStyle()", "class.nxe-disabled": "disabled()" }, classAttribute: "nxe-widget" }, ngImport: i0, template: `
92
- <span class="dx-button-content">
95
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", type: NxeButtonComponent, isStandalone: true, selector: "nxe-button", inputs: { text: { classPropertyName: "text", publicName: "text", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, validationGroup: { classPropertyName: "validationGroup", publicName: "validationGroup", isSignal: true, isRequired: false, transformFunction: null }, focusStateEnabled: { classPropertyName: "focusStateEnabled", publicName: "focusStateEnabled", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onClick: "onClick" }, host: { attributes: { "role": "button" }, listeners: { "click": "handleClick($event)", "keydown.enter": "handleKeydown($event)", "keydown.space": "handleKeydown($event)" }, properties: { "attr.tabindex": "disabled() || !focusStateEnabled() ? null : 0", "attr.aria-disabled": "disabled() ? true : null", "class.nxe-button-default": "type() === 'default'", "class.nxe-button-success": "type() === 'success'", "class.nxe-button-danger": "type() === 'danger'", "class.nxe-button-has-text": "!!text()", "class.nxe-button-has-icon": "!!icon()", "style.height": "heightStyle()", "style.width": "widthStyle()", "class.nxe-disabled": "disabled()" }, classAttribute: "nxe-widget" }, ngImport: i0, template: `
96
+ <span class="nxe-button-content">
93
97
  @if (icon()) {
94
- <i class="dx-icon dx-icon-{{ icon() }}"></i>
98
+ <i class="nxe-icon nxe-icon-{{ icon() }}"></i>
95
99
  }
96
100
  <ng-content />
97
101
  @if (text()) {
98
- <span class="dx-button-text">{{ text() }}</span>
102
+ <span class="nxe-button-text">{{ text() }}</span>
99
103
  }
100
104
  </span>
101
- `, isInline: true, styles: ["nxe-button{display:inline-flex;box-sizing:border-box;cursor:pointer;max-width:100%;overflow:hidden;text-align:center;vertical-align:middle;-webkit-user-select:none;user-select:none;border:1px solid #ddd;border-radius:4px;background-color:#fff;color:#333;font-weight:400;line-height:1.35715}nxe-button:focus{outline:none}nxe-button .dx-button-content{flex:1 1 auto;display:flex;align-items:center;justify-content:center;gap:4px;line-height:1.35715;padding:8px}nxe-button.nxe-button-has-text .dx-button-content{padding-inline:12px}nxe-button .dx-button-text{line-height:1.2858;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}nxe-button:hover{background-color:#f4f4f4}nxe-button:active{background-color:#c2c2c2}nxe-button.nxe-button-default{background-color:#337ab7;border-color:transparent;color:#fff}nxe-button.nxe-button-default:hover{background-color:#2d6ca3}nxe-button.nxe-button-default:active{background-color:#19405b}nxe-button.nxe-button-success{background-color:#5cb85c;border-color:transparent;color:#fff}nxe-button.nxe-button-success:hover{background-color:#46a046}nxe-button.nxe-button-success:active{background-color:#306e30}nxe-button.nxe-button-danger{background-color:#d9534f;border-color:transparent;color:#fff}nxe-button.nxe-button-danger:hover{background-color:#cd312c}nxe-button.nxe-button-danger:active{background-color:#932320}nxe-button[disabled]{opacity:.5;cursor:default}nxe-button.nxe-disabled{opacity:.5;cursor:default;pointer-events:none}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
105
+ `, isInline: true, styles: ["nxe-button{display:inline-flex;box-sizing:border-box;cursor:pointer;max-width:100%;overflow:hidden;text-align:center;vertical-align:middle;-webkit-user-select:none;user-select:none;border:1px solid #ddd;border-radius:4px;background-color:#fff;color:#333;font-weight:400;line-height:1.35715}nxe-button:focus{outline:none}nxe-button .nxe-button-content{flex:1 1 auto;display:flex;align-items:center;justify-content:center;gap:var(--nxe-ui-button-gap, 0px);line-height:1.35715;padding:8px}nxe-button.nxe-button-has-text .nxe-button-content{padding-inline:12px}nxe-button.nxe-button-has-text .nxe-button-content>i.nxe-icon{margin-inline-start:0;margin-inline-end:calc(9px - var(--nxe-ui-button-gap, 0px))}nxe-button .nxe-button-text{line-height:1.2858;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}nxe-button:hover{background-color:#f4f4f4}nxe-button:active{background-color:#c2c2c2}nxe-button.nxe-button-default{background-color:#337ab7;border-color:transparent;color:#fff}nxe-button.nxe-button-default:hover{background-color:#2d6ca3}nxe-button.nxe-button-default:active{background-color:#19405b}nxe-button.nxe-button-success{background-color:#5cb85c;border-color:transparent;color:#fff}nxe-button.nxe-button-success:hover{background-color:#46a046}nxe-button.nxe-button-success:active{background-color:#306e30}nxe-button.nxe-button-danger{background-color:#d9534f;border-color:transparent;color:#fff}nxe-button.nxe-button-danger:hover{background-color:#cd312c}nxe-button.nxe-button-danger:active{background-color:#932320}nxe-button[disabled]{opacity:.5;cursor:default}nxe-button.nxe-disabled{opacity:.5;cursor:default;pointer-events:none}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
102
106
  }
103
107
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeButtonComponent, decorators: [{
104
108
  type: Component,
105
109
  args: [{ selector: 'nxe-button', standalone: true, template: `
106
- <span class="dx-button-content">
110
+ <span class="nxe-button-content">
107
111
  @if (icon()) {
108
- <i class="dx-icon dx-icon-{{ icon() }}"></i>
112
+ <i class="nxe-icon nxe-icon-{{ icon() }}"></i>
109
113
  }
110
114
  <ng-content />
111
115
  @if (text()) {
112
- <span class="dx-button-text">{{ text() }}</span>
116
+ <span class="nxe-button-text">{{ text() }}</span>
113
117
  }
114
118
  </span>
115
119
  `, host: {
@@ -121,15 +125,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImpor
121
125
  '[class.nxe-button-success]': "type() === 'success'",
122
126
  '[class.nxe-button-danger]': "type() === 'danger'",
123
127
  '[class.nxe-button-has-text]': '!!text()',
124
- '[class.dx-button-has-text]': '!!text()',
125
- '[class.dx-button-has-icon]': '!!icon()',
128
+ '[class.nxe-button-has-icon]': '!!icon()',
126
129
  '[style.height]': 'heightStyle()',
127
130
  '[style.width]': 'widthStyle()',
128
131
  '[class.nxe-disabled]': 'disabled()',
129
132
  '(click)': 'handleClick($event)',
130
133
  '(keydown.enter)': 'handleKeydown($event)',
131
134
  '(keydown.space)': 'handleKeydown($event)',
132
- }, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, styles: ["nxe-button{display:inline-flex;box-sizing:border-box;cursor:pointer;max-width:100%;overflow:hidden;text-align:center;vertical-align:middle;-webkit-user-select:none;user-select:none;border:1px solid #ddd;border-radius:4px;background-color:#fff;color:#333;font-weight:400;line-height:1.35715}nxe-button:focus{outline:none}nxe-button .dx-button-content{flex:1 1 auto;display:flex;align-items:center;justify-content:center;gap:4px;line-height:1.35715;padding:8px}nxe-button.nxe-button-has-text .dx-button-content{padding-inline:12px}nxe-button .dx-button-text{line-height:1.2858;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}nxe-button:hover{background-color:#f4f4f4}nxe-button:active{background-color:#c2c2c2}nxe-button.nxe-button-default{background-color:#337ab7;border-color:transparent;color:#fff}nxe-button.nxe-button-default:hover{background-color:#2d6ca3}nxe-button.nxe-button-default:active{background-color:#19405b}nxe-button.nxe-button-success{background-color:#5cb85c;border-color:transparent;color:#fff}nxe-button.nxe-button-success:hover{background-color:#46a046}nxe-button.nxe-button-success:active{background-color:#306e30}nxe-button.nxe-button-danger{background-color:#d9534f;border-color:transparent;color:#fff}nxe-button.nxe-button-danger:hover{background-color:#cd312c}nxe-button.nxe-button-danger:active{background-color:#932320}nxe-button[disabled]{opacity:.5;cursor:default}nxe-button.nxe-disabled{opacity:.5;cursor:default;pointer-events:none}\n"] }]
135
+ }, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, styles: ["nxe-button{display:inline-flex;box-sizing:border-box;cursor:pointer;max-width:100%;overflow:hidden;text-align:center;vertical-align:middle;-webkit-user-select:none;user-select:none;border:1px solid #ddd;border-radius:4px;background-color:#fff;color:#333;font-weight:400;line-height:1.35715}nxe-button:focus{outline:none}nxe-button .nxe-button-content{flex:1 1 auto;display:flex;align-items:center;justify-content:center;gap:var(--nxe-ui-button-gap, 0px);line-height:1.35715;padding:8px}nxe-button.nxe-button-has-text .nxe-button-content{padding-inline:12px}nxe-button.nxe-button-has-text .nxe-button-content>i.nxe-icon{margin-inline-start:0;margin-inline-end:calc(9px - var(--nxe-ui-button-gap, 0px))}nxe-button .nxe-button-text{line-height:1.2858;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}nxe-button:hover{background-color:#f4f4f4}nxe-button:active{background-color:#c2c2c2}nxe-button.nxe-button-default{background-color:#337ab7;border-color:transparent;color:#fff}nxe-button.nxe-button-default:hover{background-color:#2d6ca3}nxe-button.nxe-button-default:active{background-color:#19405b}nxe-button.nxe-button-success{background-color:#5cb85c;border-color:transparent;color:#fff}nxe-button.nxe-button-success:hover{background-color:#46a046}nxe-button.nxe-button-success:active{background-color:#306e30}nxe-button.nxe-button-danger{background-color:#d9534f;border-color:transparent;color:#fff}nxe-button.nxe-button-danger:hover{background-color:#cd312c}nxe-button.nxe-button-danger:active{background-color:#932320}nxe-button[disabled]{opacity:.5;cursor:default}nxe-button.nxe-disabled{opacity:.5;cursor:default;pointer-events:none}\n"] }]
133
136
  }], propDecorators: { text: [{ type: i0.Input, args: [{ isSignal: true, alias: "text", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], validationGroup: [{ type: i0.Input, args: [{ isSignal: true, alias: "validationGroup", required: false }] }], focusStateEnabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "focusStateEnabled", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], onClick: [{ type: i0.Output, args: ["onClick"] }] } });
134
137
 
135
138
  /**
@@ -139,6 +142,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImpor
139
142
  * `[(ngModel)]`, one-way `[value]`, and `[readOnly]`. Like DevExtreme's default
140
143
  * (`valueChangeEvent='change'`), the value commits on **blur** — not on every keystroke.
141
144
  *
145
+ * `showClearButton` (dx default `false`) adds dx's clear area after the input while the field
146
+ * holds text and is neither read-only nor disabled; it sets the value to `''`, dx's own empty
147
+ * value for a text box, and focuses the input.
148
+ *
142
149
  * It provides {@link NXE_VALIDATION_TARGET} so a projected `nxe-validator` can read its value
143
150
  * and push back the invalid message, which renders as the red `.nxe-invalid-message` bubble.
144
151
  */
@@ -152,38 +159,99 @@ class NxeTextBoxComponent extends NxeFormControlBase {
152
159
  hoverStateEnabledInput = input(undefined, { ...(ngDevMode ? { debugName: "hoverStateEnabledInput" } : /* istanbul ignore next */ {}), alias: 'hoverStateEnabled', transform: nxeOptionalBoolean });
153
160
  hoverStateEnabled = computed(() => this.hoverStateEnabledInput() ?? this.nxeDefaults.textBox?.hoverStateEnabled ?? false, /* @ts-ignore */
154
161
  ...(ngDevMode ? [{ debugName: "hoverStateEnabled" }] : /* istanbul ignore next */ []));
162
+ /** dx `showClearButton` — off by default, as in dx; `NXE_DEFAULT_OPTIONS.textBox` can turn it on app-wide. */
163
+ showClearButtonInput = input(undefined, { ...(ngDevMode ? { debugName: "showClearButtonInput" } : /* istanbul ignore next */ {}), alias: 'showClearButton', transform: nxeOptionalBoolean });
164
+ showClearButton = computed(() => this.showClearButtonInput() ?? this.nxeDefaults.textBox?.showClearButton ?? false, /* @ts-ignore */
165
+ ...(ngDevMode ? [{ debugName: "showClearButton" }] : /* istanbul ignore next */ []));
155
166
  focused = signal(false, /* @ts-ignore */
156
167
  ...(ngDevMode ? [{ debugName: "focused" }] : /* istanbul ignore next */ []));
157
168
  displayValue = computed(() => this.value() ?? '', /* @ts-ignore */
158
169
  ...(ngDevMode ? [{ debugName: "displayValue" }] : /* istanbul ignore next */ []));
170
+ fieldRef = viewChild.required('field', /* @ts-ignore */
171
+ ...(ngDevMode ? [{ debugName: "fieldRef" }] : /* istanbul ignore next */ []));
172
+ /**
173
+ * The text in the field right now, typed or not. dx shows the clear button by the field's
174
+ * emptiness (`.dx-texteditor-empty`), which follows every keystroke, not the committed value.
175
+ */
176
+ fieldText = linkedSignal(() => this.displayValue(), /* @ts-ignore */
177
+ ...(ngDevMode ? [{ debugName: "fieldText" }] : /* istanbul ignore next */ []));
178
+ clearButtonVisible = computed(() => this.showClearButton() && !this.readOnly() && !this.disabled() && this.fieldText() !== '', /* @ts-ignore */
179
+ ...(ngDevMode ? [{ debugName: "clearButtonVisible" }] : /* istanbul ignore next */ []));
180
+ /** dx `valueChangeEvent: 'input'` — the search-as-you-type case. */
181
+ onTypedInput(event) {
182
+ const text = event.target.value;
183
+ this.fieldText.set(text);
184
+ this.commitOn('input', text, event);
185
+ }
186
+ /**
187
+ * dx `_clearValueHandler`: value back to `''`, then focus on the input. The field is emptied by
188
+ * hand as well — typed-but-uncommitted text over an already-empty value would otherwise survive,
189
+ * because the `[value]` binding has not changed.
190
+ */
191
+ onClear(event) {
192
+ const field = this.fieldRef().nativeElement;
193
+ this.commitValue('', event);
194
+ field.value = '';
195
+ this.fieldText.set('');
196
+ if (!this.focused()) {
197
+ field.focus();
198
+ }
199
+ }
200
+ onTypedKeyup(event) {
201
+ this.commitOn('keyup', event.target.value, event);
202
+ }
159
203
  onBlur(event) {
204
+ // Blur commits whatever the mode: a value typed and left must not be discarded because
205
+ // the configured event never fired.
160
206
  this.commitValue(event.target.value, event);
161
207
  this.focused.set(false);
162
208
  this.markAsTouched();
163
209
  }
164
210
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeTextBoxComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
165
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", type: NxeTextBoxComponent, isStandalone: true, selector: "nxe-text-box", inputs: { mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, placeholderInput: { classPropertyName: "placeholderInput", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, hoverStateEnabledInput: { classPropertyName: "hoverStateEnabledInput", publicName: "hoverStateEnabled", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.nxe-focused": "focused()", "class.nxe-invalid": "!!validationError()" }, classAttribute: "nxe-widget nxe-editor nxe-textbox" }, providers: [
211
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", type: NxeTextBoxComponent, isStandalone: true, selector: "nxe-text-box", inputs: { mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, placeholderInput: { classPropertyName: "placeholderInput", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, hoverStateEnabledInput: { classPropertyName: "hoverStateEnabledInput", publicName: "hoverStateEnabled", isSignal: true, isRequired: false, transformFunction: null }, showClearButtonInput: { classPropertyName: "showClearButtonInput", publicName: "showClearButton", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.nxe-focused": "focused()", "class.nxe-invalid": "!!validationError()" }, classAttribute: "nxe-widget nxe-editor nxe-textbox" }, providers: [
166
212
  { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NxeTextBoxComponent), multi: true },
167
213
  { provide: NXE_VALIDATION_TARGET, useExisting: forwardRef(() => NxeTextBoxComponent) },
168
- ], usesInheritance: true, ngImport: i0, template: `
169
- <div class="nxe-editor-container">
170
- <input
171
- class="nxe-editor-input nxe-textbox-input"
172
- [type]="mode()"
173
- [value]="displayValue()"
174
- [readOnly]="readOnly()"
175
- [disabled]="disabled()"
176
- [attr.placeholder]="placeholder() || null"
177
- autocomplete="off"
178
- (blur)="onBlur($event)"
179
- (focus)="focused.set(true)" />
180
- </div>
181
- <ng-content />
182
- @if (validationError(); as error) {
183
- @if (focused()) {
184
- <div class="nxe-invalid-message">{{ error }}</div>
185
- }
186
- }
214
+ ], viewQueries: [{ propertyName: "fieldRef", first: true, predicate: ["field"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: `
215
+ <div class="nxe-editor-container">
216
+ <input
217
+ #field
218
+ class="nxe-editor-input nxe-textbox-input"
219
+ [attr.tabindex]="tabIndex()"
220
+ [id]="controlId()"
221
+ [attr.aria-label]="ariaLabel() || null"
222
+ [attr.aria-labelledby]="ariaLabelledBy() || null"
223
+ [attr.aria-invalid]="validationError() ? true : null"
224
+ [attr.aria-required]="required() ? true : null"
225
+ [type]="mode()"
226
+ [value]="displayValue()"
227
+ [readOnly]="readOnly()"
228
+ [disabled]="disabled()"
229
+ [attr.placeholder]="placeholder() || null"
230
+ autocomplete="off"
231
+ (input)="onTypedInput($event)"
232
+ (keyup)="onTypedKeyup($event)"
233
+ (blur)="onBlur($event)"
234
+ (focus)="focused.set(true)" />
235
+ @if (clearButtonVisible()) {
236
+ <span
237
+ class="nxe-clear-button-area"
238
+ (mousedown)="$event.preventDefault()"
239
+ (click)="onClear($event)"
240
+ aria-hidden="true">
241
+ <!-- A Ø16 disc with a 2px × cut out (the dropdown editors' clear glyph). -->
242
+ <svg viewBox="0 0 18 18" focusable="false">
243
+ <path fill-rule="evenodd" transform="rotate(45 9 9)"
244
+ d="M9 1a8 8 0 1 1 0 16A8 8 0 1 1 9 1ZM8 3.343h2V8h4.657v2H10v4.657H8V10H3.343V8H8Z" />
245
+ </svg>
246
+ </span>
247
+ }
248
+ </div>
249
+ <ng-content />
250
+ @if (validationError(); as error) {
251
+ @if (focused()) {
252
+ <div class="nxe-invalid-message">{{ error }}</div>
253
+ }
254
+ }
187
255
  `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
188
256
  }
189
257
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeTextBoxComponent, decorators: [{
@@ -191,25 +259,47 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImpor
191
259
  args: [{
192
260
  selector: 'nxe-text-box',
193
261
  standalone: true,
194
- template: `
195
- <div class="nxe-editor-container">
196
- <input
197
- class="nxe-editor-input nxe-textbox-input"
198
- [type]="mode()"
199
- [value]="displayValue()"
200
- [readOnly]="readOnly()"
201
- [disabled]="disabled()"
202
- [attr.placeholder]="placeholder() || null"
203
- autocomplete="off"
204
- (blur)="onBlur($event)"
205
- (focus)="focused.set(true)" />
206
- </div>
207
- <ng-content />
208
- @if (validationError(); as error) {
209
- @if (focused()) {
210
- <div class="nxe-invalid-message">{{ error }}</div>
211
- }
212
- }
262
+ template: `
263
+ <div class="nxe-editor-container">
264
+ <input
265
+ #field
266
+ class="nxe-editor-input nxe-textbox-input"
267
+ [attr.tabindex]="tabIndex()"
268
+ [id]="controlId()"
269
+ [attr.aria-label]="ariaLabel() || null"
270
+ [attr.aria-labelledby]="ariaLabelledBy() || null"
271
+ [attr.aria-invalid]="validationError() ? true : null"
272
+ [attr.aria-required]="required() ? true : null"
273
+ [type]="mode()"
274
+ [value]="displayValue()"
275
+ [readOnly]="readOnly()"
276
+ [disabled]="disabled()"
277
+ [attr.placeholder]="placeholder() || null"
278
+ autocomplete="off"
279
+ (input)="onTypedInput($event)"
280
+ (keyup)="onTypedKeyup($event)"
281
+ (blur)="onBlur($event)"
282
+ (focus)="focused.set(true)" />
283
+ @if (clearButtonVisible()) {
284
+ <span
285
+ class="nxe-clear-button-area"
286
+ (mousedown)="$event.preventDefault()"
287
+ (click)="onClear($event)"
288
+ aria-hidden="true">
289
+ <!-- A Ø16 disc with a 2px × cut out (the dropdown editors' clear glyph). -->
290
+ <svg viewBox="0 0 18 18" focusable="false">
291
+ <path fill-rule="evenodd" transform="rotate(45 9 9)"
292
+ d="M9 1a8 8 0 1 1 0 16A8 8 0 1 1 9 1ZM8 3.343h2V8h4.657v2H10v4.657H8V10H3.343V8H8Z" />
293
+ </svg>
294
+ </span>
295
+ }
296
+ </div>
297
+ <ng-content />
298
+ @if (validationError(); as error) {
299
+ @if (focused()) {
300
+ <div class="nxe-invalid-message">{{ error }}</div>
301
+ }
302
+ }
213
303
  `,
214
304
  host: {
215
305
  class: 'nxe-widget nxe-editor nxe-textbox',
@@ -223,7 +313,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImpor
223
313
  changeDetection: ChangeDetectionStrategy.OnPush,
224
314
  encapsulation: ViewEncapsulation.None,
225
315
  }]
226
- }], propDecorators: { mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], placeholderInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], hoverStateEnabledInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "hoverStateEnabled", required: false }] }] } });
316
+ }], propDecorators: { mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], placeholderInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], hoverStateEnabledInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "hoverStateEnabled", required: false }] }], showClearButtonInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "showClearButton", required: false }] }], fieldRef: [{ type: i0.ViewChild, args: ['field', { isSignal: true }] }] } });
227
317
 
228
318
  /**
229
319
  * `nxe-text-area` — drop-in replacement for DevExtreme `dx-text-area`.
@@ -244,7 +334,16 @@ class NxeTextAreaComponent extends NxeFormControlBase {
244
334
  ...(ngDevMode ? [{ debugName: "focused" }] : /* istanbul ignore next */ []));
245
335
  displayValue = computed(() => this.value() ?? '', /* @ts-ignore */
246
336
  ...(ngDevMode ? [{ debugName: "displayValue" }] : /* istanbul ignore next */ []));
337
+ /** dx `valueChangeEvent: 'input'` — the search-as-you-type case. */
338
+ onTypedInput(event) {
339
+ this.commitOn('input', event.target.value, event);
340
+ }
341
+ onTypedKeyup(event) {
342
+ this.commitOn('keyup', event.target.value, event);
343
+ }
247
344
  onBlur(event) {
345
+ // Blur commits whatever the mode: a value typed and left must not be discarded because
346
+ // the configured event never fired.
248
347
  this.commitValue(event.target.value, event);
249
348
  this.focused.set(false);
250
349
  this.markAsTouched();
@@ -257,11 +356,19 @@ class NxeTextAreaComponent extends NxeFormControlBase {
257
356
  <div class="nxe-editor-container">
258
357
  <textarea
259
358
  class="nxe-editor-input nxe-textarea-input"
359
+ [attr.tabindex]="tabIndex()"
360
+ [id]="controlId()"
361
+ [attr.aria-label]="ariaLabel() || null"
362
+ [attr.aria-labelledby]="ariaLabelledBy() || null"
363
+ [attr.aria-invalid]="validationError() ? true : null"
364
+ [attr.aria-required]="required() ? true : null"
260
365
  [value]="displayValue()"
261
366
  [readOnly]="readOnly()"
262
367
  [disabled]="disabled()"
263
368
  [attr.placeholder]="placeholder() || null"
264
369
  [style.height.px]="height() || null"
370
+ (input)="onTypedInput($event)"
371
+ (keyup)="onTypedKeyup($event)"
265
372
  (blur)="onBlur($event)"
266
373
  (focus)="focused.set(true)"></textarea>
267
374
  </div>
@@ -279,11 +386,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImpor
279
386
  <div class="nxe-editor-container">
280
387
  <textarea
281
388
  class="nxe-editor-input nxe-textarea-input"
389
+ [attr.tabindex]="tabIndex()"
390
+ [id]="controlId()"
391
+ [attr.aria-label]="ariaLabel() || null"
392
+ [attr.aria-labelledby]="ariaLabelledBy() || null"
393
+ [attr.aria-invalid]="validationError() ? true : null"
394
+ [attr.aria-required]="required() ? true : null"
282
395
  [value]="displayValue()"
283
396
  [readOnly]="readOnly()"
284
397
  [disabled]="disabled()"
285
398
  [attr.placeholder]="placeholder() || null"
286
399
  [style.height.px]="height() || null"
400
+ (input)="onTypedInput($event)"
401
+ (keyup)="onTypedKeyup($event)"
287
402
  (blur)="onBlur($event)"
288
403
  (focus)="focused.set(true)"></textarea>
289
404
  </div>
@@ -306,16 +421,43 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImpor
306
421
  /**
307
422
  * `nxe-check-box` — drop-in replacement for DevExtreme `dx-check-box`.
308
423
  *
309
- * A boolean signal-CVA editor (extends {@link NxeFormControlBase}) — no tri-state, matching all
310
- * usage. Supports `formControlName`/`[(ngModel)]`/`[(value)]`/`[value]`, `[readOnly]`,
311
- * `[disabled]`, an inline `text` label, `(onValueChanged)` (`{value}`) and `(valueChange)` (raw
312
- * boolean). Provides {@link NXE_VALIDATION_TARGET} for a projected `nxe-validator` (a `required`
424
+ * A signal-CVA editor (extends {@link NxeFormControlBase}) over `true` / `false` / `null`. As in
425
+ * dx, a `null` value is the indeterminate state: the box shows a 12px square and reports
426
+ * `aria-checked="mixed"`. That is what a `new FormControl()` (or a `reset()`) looks like, and what
427
+ * `[value]="null"` looks like; an unbound box starts unchecked, like dx's default `false`. A click
428
+ * or Space always lands on `true` or `false` — there is no `enableThreeStateBehavior` cycling back
429
+ * to `null`.
430
+ *
431
+ * Supports `formControlName`/`[(ngModel)]`/`[(value)]`/`[value]`, `[readOnly]`, `[disabled]`, an
432
+ * inline `text` label, `(onValueChanged)` (`{value}`) and `(valueChange)` (raw value). A read-only
433
+ * box stays focusable (tabindex 0) and still shows hover and focus, as in dx; it just never
434
+ * toggles. Provides {@link NXE_VALIDATION_TARGET} for a projected `nxe-validator` (a `required`
313
435
  * rule is invalid while unchecked, matching DevExtreme's RequiredRule treating `false` as empty).
314
436
  */
315
437
  class NxeCheckBoxComponent extends NxeFormControlBase {
316
438
  /** Inline label rendered next to the box. */
317
439
  text = input(/* @ts-ignore */
318
440
  ...(ngDevMode ? [undefined, { debugName: "text" }] : /* istanbul ignore next */ []));
441
+ /**
442
+ * Whether a value has been handed in at all — by the forms API or a `[value]` binding.
443
+ *
444
+ * The base class starts `value` at `null`, which is also the indeterminate value, so `null`
445
+ * alone cannot tell "given null" from "given nothing". dx can: its unset value is `false`.
446
+ */
447
+ valueProvided = signal(false, /* @ts-ignore */
448
+ ...(ngDevMode ? [{ debugName: "valueProvided" }] : /* istanbul ignore next */ []));
449
+ /** dx `.dx-checkbox-indeterminate`: the value was given, and it is `null`. */
450
+ indeterminate = computed(() => this.valueProvided() && this.value() === null, /* @ts-ignore */
451
+ ...(ngDevMode ? [{ debugName: "indeterminate" }] : /* istanbul ignore next */ []));
452
+ writeValue(value) {
453
+ super.writeValue(value);
454
+ this.valueProvided.set(true);
455
+ }
456
+ ngOnChanges(changes) {
457
+ if (changes['value']) {
458
+ this.valueProvided.set(true);
459
+ }
460
+ }
319
461
  toggle(event) {
320
462
  if (this.disabled() || this.readOnly()) {
321
463
  return;
@@ -328,10 +470,10 @@ class NxeCheckBoxComponent extends NxeFormControlBase {
328
470
  this.toggle(event);
329
471
  }
330
472
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeCheckBoxComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
331
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", type: NxeCheckBoxComponent, isStandalone: true, selector: "nxe-check-box", inputs: { text: { classPropertyName: "text", publicName: "text", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "checkbox" }, listeners: { "click": "toggle($event)", "keydown.space": "onSpace($event)", "blur": "markAsTouched()" }, properties: { "attr.aria-checked": "!!value()", "attr.aria-disabled": "disabled() ? true : null", "attr.aria-readonly": "readOnly() ? true : null", "attr.tabindex": "disabled() || readOnly() ? null : 0", "class.nxe-checkbox-checked": "!!value()", "class.nxe-disabled": "disabled()", "class.nxe-readonly": "readOnly()", "class.nxe-invalid": "!!validationError()" }, classAttribute: "nxe-widget nxe-checkbox" }, providers: [
473
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", type: NxeCheckBoxComponent, isStandalone: true, selector: "nxe-check-box", inputs: { text: { classPropertyName: "text", publicName: "text", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "checkbox" }, listeners: { "click": "toggle($event)", "keydown.space": "onSpace($event)", "blur": "markAsTouched()" }, properties: { "attr.aria-checked": "indeterminate() ? 'mixed' : !!value()", "attr.aria-disabled": "disabled() ? true : null", "attr.aria-readonly": "readOnly() ? true : null", "attr.aria-invalid": "validationError() ? true : null", "attr.aria-required": "required() ? true : null", "attr.id": "inputId() ? controlId() : null", "attr.aria-label": "ariaLabel() || null", "attr.aria-labelledby": "ariaLabelledBy() || null", "attr.tabindex": "disabled() ? null : 0", "class.nxe-checkbox-checked": "!!value()", "class.nxe-checkbox-indeterminate": "indeterminate()", "class.nxe-disabled": "disabled()", "class.nxe-readonly": "readOnly()", "class.nxe-invalid": "!!validationError()" }, classAttribute: "nxe-widget nxe-checkbox" }, providers: [
332
474
  { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NxeCheckBoxComponent), multi: true },
333
475
  { provide: NXE_VALIDATION_TARGET, useExisting: forwardRef(() => NxeCheckBoxComponent) },
334
- ], usesInheritance: true, ngImport: i0, template: `
476
+ ], usesInheritance: true, usesOnChanges: true, ngImport: i0, template: `
335
477
  <span class="nxe-checkbox-container">
336
478
  <span class="nxe-checkbox-icon" aria-hidden="true"></span>
337
479
  @if (text()) {
@@ -339,7 +481,7 @@ class NxeCheckBoxComponent extends NxeFormControlBase {
339
481
  }
340
482
  </span>
341
483
  <ng-content />
342
- `, isInline: true, styles: ["nxe-check-box{display:inline-flex;align-items:center;box-sizing:border-box;cursor:pointer;vertical-align:middle;-webkit-user-select:none;user-select:none;line-height:normal}nxe-check-box:focus{outline:none}nxe-check-box .nxe-checkbox-container{display:inline-flex;align-items:center;max-width:100%;overflow:hidden}nxe-check-box .nxe-checkbox-icon{box-sizing:border-box;position:relative;flex-shrink:0;width:22px;height:22px;border:1px solid #ddd;border-radius:2px;background-color:#fff;transition:border-color .12s ease}nxe-check-box .nxe-checkbox-text{padding-inline-start:5px;white-space:pre-wrap;word-wrap:break-word;line-height:normal}nxe-check-box.nxe-checkbox-checked .nxe-checkbox-icon:after{content:\"\";position:absolute;top:3px;inset-inline-start:7px;width:6px;height:11px;border:solid #337ab7;border-width:0 2px 2px 0;transform:rotate(45deg)}nxe-check-box:hover .nxe-checkbox-icon{border-color:#265a87}nxe-check-box:focus .nxe-checkbox-icon{border-color:#337ab7}nxe-check-box.nxe-readonly{cursor:default}nxe-check-box.nxe-readonly .nxe-checkbox-icon{border-color:#f4f4f4;background-color:#fff}nxe-check-box.nxe-disabled{cursor:default}nxe-check-box.nxe-disabled .nxe-checkbox-icon,nxe-check-box.nxe-disabled .nxe-checkbox-text{opacity:.4}nxe-check-box.nxe-invalid .nxe-checkbox-icon{border-color:#d9534f66}nxe-check-box.nxe-invalid:focus .nxe-checkbox-icon{border-color:#d9534f}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
484
+ `, isInline: true, styles: ["nxe-check-box{display:inline-flex;align-items:center;box-sizing:border-box;cursor:pointer;vertical-align:middle;-webkit-user-select:none;user-select:none;line-height:normal}nxe-check-box:focus{outline:none}nxe-check-box .nxe-checkbox-container{display:inline-flex;align-items:center;max-width:100%;overflow:hidden}nxe-check-box .nxe-checkbox-icon{box-sizing:border-box;position:relative;flex-shrink:0;width:22px;height:22px;border:1px solid #ddd;border-radius:2px;background-color:#fff}nxe-check-box .nxe-checkbox-text{padding-inline-start:5px;white-space:pre-wrap;word-wrap:break-word;line-height:normal}nxe-check-box.nxe-checkbox-checked .nxe-checkbox-icon: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, #337ab7);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-check-box.nxe-checkbox-checked .nxe-checkbox-icon:after{forced-color-adjust:none;background-color:CanvasText}}nxe-check-box.nxe-checkbox-indeterminate .nxe-checkbox-icon: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, #337ab7)}@media(forced-colors:active){nxe-check-box.nxe-checkbox-indeterminate .nxe-checkbox-icon:after{forced-color-adjust:none;background-color:CanvasText}}nxe-check-box.nxe-readonly{cursor:default}nxe-check-box.nxe-readonly .nxe-checkbox-icon{border-color:#f4f4f4;background-color:#fff}nxe-check-box:not(.nxe-disabled):hover .nxe-checkbox-icon{border-color:var(--nxe-ui-checkbox-hover-border, #265a87)}nxe-check-box:not(.nxe-disabled):focus .nxe-checkbox-icon{border-color:var(--nxe-ui-accent, #337ab7)}nxe-check-box:not(.nxe-disabled):active .nxe-checkbox-icon{background-color:#60606033}nxe-check-box.nxe-disabled{cursor:default}nxe-check-box.nxe-disabled .nxe-checkbox-icon,nxe-check-box.nxe-disabled .nxe-checkbox-text{opacity:.4}nxe-check-box.nxe-invalid .nxe-checkbox-icon,nxe-check-box.nxe-invalid:hover .nxe-checkbox-icon{border-color:#d9534f66}nxe-check-box.nxe-invalid:focus .nxe-checkbox-icon{border-color:#d9534f}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
343
485
  }
344
486
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeCheckBoxComponent, decorators: [{
345
487
  type: Component,
@@ -354,11 +496,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImpor
354
496
  `, host: {
355
497
  class: 'nxe-widget nxe-checkbox',
356
498
  role: 'checkbox',
357
- '[attr.aria-checked]': '!!value()',
499
+ '[attr.aria-checked]': "indeterminate() ? 'mixed' : !!value()",
358
500
  '[attr.aria-disabled]': 'disabled() ? true : null',
359
501
  '[attr.aria-readonly]': 'readOnly() ? true : null',
360
- '[attr.tabindex]': 'disabled() || readOnly() ? null : 0',
502
+ '[attr.aria-invalid]': 'validationError() ? true : null',
503
+ '[attr.aria-required]': 'required() ? true : null',
504
+ // The host IS the control here, so the id and name belong on it rather than on a child.
505
+ '[attr.id]': 'inputId() ? controlId() : null',
506
+ '[attr.aria-label]': 'ariaLabel() || null',
507
+ '[attr.aria-labelledby]': 'ariaLabelledBy() || null',
508
+ // dx keeps a read-only box in the tab order (tabindex 0); only disabled takes it out.
509
+ '[attr.tabindex]': 'disabled() ? null : 0',
361
510
  '[class.nxe-checkbox-checked]': '!!value()',
511
+ '[class.nxe-checkbox-indeterminate]': 'indeterminate()',
362
512
  '[class.nxe-disabled]': 'disabled()',
363
513
  '[class.nxe-readonly]': 'readOnly()',
364
514
  '[class.nxe-invalid]': '!!validationError()',
@@ -368,7 +518,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImpor
368
518
  }, providers: [
369
519
  { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NxeCheckBoxComponent), multi: true },
370
520
  { provide: NXE_VALIDATION_TARGET, useExisting: forwardRef(() => NxeCheckBoxComponent) },
371
- ], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, styles: ["nxe-check-box{display:inline-flex;align-items:center;box-sizing:border-box;cursor:pointer;vertical-align:middle;-webkit-user-select:none;user-select:none;line-height:normal}nxe-check-box:focus{outline:none}nxe-check-box .nxe-checkbox-container{display:inline-flex;align-items:center;max-width:100%;overflow:hidden}nxe-check-box .nxe-checkbox-icon{box-sizing:border-box;position:relative;flex-shrink:0;width:22px;height:22px;border:1px solid #ddd;border-radius:2px;background-color:#fff;transition:border-color .12s ease}nxe-check-box .nxe-checkbox-text{padding-inline-start:5px;white-space:pre-wrap;word-wrap:break-word;line-height:normal}nxe-check-box.nxe-checkbox-checked .nxe-checkbox-icon:after{content:\"\";position:absolute;top:3px;inset-inline-start:7px;width:6px;height:11px;border:solid #337ab7;border-width:0 2px 2px 0;transform:rotate(45deg)}nxe-check-box:hover .nxe-checkbox-icon{border-color:#265a87}nxe-check-box:focus .nxe-checkbox-icon{border-color:#337ab7}nxe-check-box.nxe-readonly{cursor:default}nxe-check-box.nxe-readonly .nxe-checkbox-icon{border-color:#f4f4f4;background-color:#fff}nxe-check-box.nxe-disabled{cursor:default}nxe-check-box.nxe-disabled .nxe-checkbox-icon,nxe-check-box.nxe-disabled .nxe-checkbox-text{opacity:.4}nxe-check-box.nxe-invalid .nxe-checkbox-icon{border-color:#d9534f66}nxe-check-box.nxe-invalid:focus .nxe-checkbox-icon{border-color:#d9534f}\n"] }]
521
+ ], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, styles: ["nxe-check-box{display:inline-flex;align-items:center;box-sizing:border-box;cursor:pointer;vertical-align:middle;-webkit-user-select:none;user-select:none;line-height:normal}nxe-check-box:focus{outline:none}nxe-check-box .nxe-checkbox-container{display:inline-flex;align-items:center;max-width:100%;overflow:hidden}nxe-check-box .nxe-checkbox-icon{box-sizing:border-box;position:relative;flex-shrink:0;width:22px;height:22px;border:1px solid #ddd;border-radius:2px;background-color:#fff}nxe-check-box .nxe-checkbox-text{padding-inline-start:5px;white-space:pre-wrap;word-wrap:break-word;line-height:normal}nxe-check-box.nxe-checkbox-checked .nxe-checkbox-icon: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, #337ab7);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-check-box.nxe-checkbox-checked .nxe-checkbox-icon:after{forced-color-adjust:none;background-color:CanvasText}}nxe-check-box.nxe-checkbox-indeterminate .nxe-checkbox-icon: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, #337ab7)}@media(forced-colors:active){nxe-check-box.nxe-checkbox-indeterminate .nxe-checkbox-icon:after{forced-color-adjust:none;background-color:CanvasText}}nxe-check-box.nxe-readonly{cursor:default}nxe-check-box.nxe-readonly .nxe-checkbox-icon{border-color:#f4f4f4;background-color:#fff}nxe-check-box:not(.nxe-disabled):hover .nxe-checkbox-icon{border-color:var(--nxe-ui-checkbox-hover-border, #265a87)}nxe-check-box:not(.nxe-disabled):focus .nxe-checkbox-icon{border-color:var(--nxe-ui-accent, #337ab7)}nxe-check-box:not(.nxe-disabled):active .nxe-checkbox-icon{background-color:#60606033}nxe-check-box.nxe-disabled{cursor:default}nxe-check-box.nxe-disabled .nxe-checkbox-icon,nxe-check-box.nxe-disabled .nxe-checkbox-text{opacity:.4}nxe-check-box.nxe-invalid .nxe-checkbox-icon,nxe-check-box.nxe-invalid:hover .nxe-checkbox-icon{border-color:#d9534f66}nxe-check-box.nxe-invalid:focus .nxe-checkbox-icon{border-color:#d9534f}\n"] }]
372
522
  }], propDecorators: { text: [{ type: i0.Input, args: [{ isSignal: true, alias: "text", required: false }] }] } });
373
523
 
374
524
  /** Coerce a static (`min="1"`) or bound (`[min]="0"`) attribute to a number, or undefined. */
@@ -389,6 +539,10 @@ function toNumber(value) {
389
539
  *
390
540
  * Supports `formControlName`/`[(ngModel)]`/`[(value)]`/`[value]`, `[readOnly]`, `[min]`/`[max]`,
391
541
  * `format` (string LDML pattern), `(valueChange)` (numeric), and a projected `nxe-validator`.
542
+ *
543
+ * `showClearButton` (dx default `false`) adds dx's clear area after the input while the field
544
+ * holds text and is neither read-only nor disabled; it sets the value to `null`, as dx's
545
+ * `clear()` does for a number box, and focuses the input.
392
546
  */
393
547
  class NxeNumberBoxComponent extends NxeFormControlBase {
394
548
  /** LDML format pattern (e.g. `#,##0.00`); empty = raw number. */
@@ -397,10 +551,16 @@ class NxeNumberBoxComponent extends NxeFormControlBase {
397
551
  ...(ngDevMode ? [{ debugName: "format" }] : /* istanbul ignore next */ []));
398
552
  min = input(undefined, { ...(ngDevMode ? { debugName: "min" } : /* istanbul ignore next */ {}), transform: toNumber });
399
553
  max = input(undefined, { ...(ngDevMode ? { debugName: "max" } : /* istanbul ignore next */ {}), transform: toNumber });
554
+ /** dx `showClearButton` — off by default, as in dx; `NXE_DEFAULT_OPTIONS.numberBox` can turn it on app-wide. */
555
+ showClearButtonInput = input(undefined, { ...(ngDevMode ? { debugName: "showClearButtonInput" } : /* istanbul ignore next */ {}), alias: 'showClearButton', transform: nxeOptionalBoolean });
556
+ showClearButton = computed(() => this.showClearButtonInput() ?? this.nxeDefaults.numberBox?.showClearButton ?? false, /* @ts-ignore */
557
+ ...(ngDevMode ? [{ debugName: "showClearButton" }] : /* istanbul ignore next */ []));
400
558
  focused = signal(false, /* @ts-ignore */
401
559
  ...(ngDevMode ? [{ debugName: "focused" }] : /* istanbul ignore next */ []));
402
560
  parsedFormat = computed(() => parseNumberFormat(this.format()), /* @ts-ignore */
403
561
  ...(ngDevMode ? [{ debugName: "parsedFormat" }] : /* istanbul ignore next */ []));
562
+ fieldRef = viewChild.required('field', /* @ts-ignore */
563
+ ...(ngDevMode ? [{ debugName: "fieldRef" }] : /* istanbul ignore next */ []));
404
564
  /** Locale characters, configured once by the application. Defaults to en-US. */
405
565
  separators = inject(NXE_NUMBER_SEPARATORS);
406
566
  displayValue = computed(() => {
@@ -413,6 +573,27 @@ class NxeNumberBoxComponent extends NxeFormControlBase {
413
573
  return formatNumber(value, this.parsedFormat(), this.separators);
414
574
  }, /* @ts-ignore */
415
575
  ...(ngDevMode ? [{ debugName: "displayValue" }] : /* istanbul ignore next */ []));
576
+ /** The text in the field right now; dx shows the clear button by the field's emptiness. */
577
+ fieldText = linkedSignal(() => this.displayValue(), /* @ts-ignore */
578
+ ...(ngDevMode ? [{ debugName: "fieldText" }] : /* istanbul ignore next */ []));
579
+ clearButtonVisible = computed(() => this.showClearButton() && !this.readOnly() && !this.disabled() && this.fieldText() !== '', /* @ts-ignore */
580
+ ...(ngDevMode ? [{ debugName: "clearButtonVisible" }] : /* istanbul ignore next */ []));
581
+ onFieldInput(event) {
582
+ this.fieldText.set(event.target.value);
583
+ }
584
+ /**
585
+ * dx `_clearValueHandler`: value to `null`, then focus on the input. The field is emptied by hand
586
+ * too, for typed-but-uncommitted text over a value that was already `null`.
587
+ */
588
+ onClear(event) {
589
+ const field = this.fieldRef().nativeElement;
590
+ this.commitValue(null, event);
591
+ field.value = '';
592
+ this.fieldText.set('');
593
+ if (!this.focused()) {
594
+ field.focus();
595
+ }
596
+ }
416
597
  onBlur(event) {
417
598
  let num = parseNumber(event.target.value, this.separators);
418
599
  if (num != null) {
@@ -434,51 +615,93 @@ class NxeNumberBoxComponent extends NxeFormControlBase {
434
615
  this.markAsTouched();
435
616
  }
436
617
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeNumberBoxComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
437
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", type: NxeNumberBoxComponent, isStandalone: true, selector: "nxe-number-box", inputs: { formatInput: { classPropertyName: "formatInput", publicName: "format", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.nxe-focused": "focused()", "class.nxe-invalid": "!!validationError()" }, classAttribute: "nxe-widget nxe-editor nxe-numberbox" }, providers: [
618
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", type: NxeNumberBoxComponent, isStandalone: true, selector: "nxe-number-box", inputs: { formatInput: { classPropertyName: "formatInput", publicName: "format", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, showClearButtonInput: { classPropertyName: "showClearButtonInput", publicName: "showClearButton", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.nxe-focused": "focused()", "class.nxe-invalid": "!!validationError()" }, classAttribute: "nxe-widget nxe-editor nxe-numberbox" }, providers: [
438
619
  { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NxeNumberBoxComponent), multi: true },
439
620
  { provide: NXE_VALIDATION_TARGET, useExisting: forwardRef(() => NxeNumberBoxComponent) },
440
- ], usesInheritance: true, ngImport: i0, template: `
441
- <div class="nxe-editor-container">
442
- <input
443
- class="nxe-editor-input nxe-numberbox-input"
444
- type="text"
445
- inputmode="decimal"
446
- [value]="displayValue()"
447
- [readOnly]="readOnly()"
448
- [disabled]="disabled()"
449
- autocomplete="off"
450
- (focus)="focused.set(true)"
451
- (blur)="onBlur($event)" />
452
- </div>
453
- <ng-content />
454
- @if (validationError(); as error) {
455
- @if (focused()) {
456
- <div class="nxe-invalid-message">{{ error }}</div>
457
- }
458
- }
621
+ ], viewQueries: [{ propertyName: "fieldRef", first: true, predicate: ["field"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: `
622
+ <div class="nxe-editor-container">
623
+ <input
624
+ #field
625
+ class="nxe-editor-input nxe-numberbox-input"
626
+ [attr.tabindex]="tabIndex()"
627
+ [id]="controlId()"
628
+ [attr.aria-label]="ariaLabel() || null"
629
+ [attr.aria-labelledby]="ariaLabelledBy() || null"
630
+ [attr.aria-invalid]="validationError() ? true : null"
631
+ [attr.aria-required]="required() ? true : null"
632
+ type="text"
633
+ inputmode="decimal"
634
+ [value]="displayValue()"
635
+ [readOnly]="readOnly()"
636
+ [disabled]="disabled()"
637
+ autocomplete="off"
638
+ (input)="onFieldInput($event)"
639
+ (focus)="focused.set(true)"
640
+ (blur)="onBlur($event)" />
641
+ @if (clearButtonVisible()) {
642
+ <span
643
+ class="nxe-clear-button-area"
644
+ (mousedown)="$event.preventDefault()"
645
+ (click)="onClear($event)"
646
+ aria-hidden="true">
647
+ <!-- A Ø16 disc with a 2px × cut out (the dropdown editors' clear glyph). -->
648
+ <svg viewBox="0 0 18 18" focusable="false">
649
+ <path fill-rule="evenodd" transform="rotate(45 9 9)"
650
+ d="M9 1a8 8 0 1 1 0 16A8 8 0 1 1 9 1ZM8 3.343h2V8h4.657v2H10v4.657H8V10H3.343V8H8Z" />
651
+ </svg>
652
+ </span>
653
+ }
654
+ </div>
655
+ <ng-content />
656
+ @if (validationError(); as error) {
657
+ @if (focused()) {
658
+ <div class="nxe-invalid-message">{{ error }}</div>
659
+ }
660
+ }
459
661
  `, isInline: true, styles: ["nxe-number-box.text-right .nxe-numberbox-input{text-align:right}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
460
662
  }
461
663
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeNumberBoxComponent, decorators: [{
462
664
  type: Component,
463
- args: [{ selector: 'nxe-number-box', standalone: true, template: `
464
- <div class="nxe-editor-container">
465
- <input
466
- class="nxe-editor-input nxe-numberbox-input"
467
- type="text"
468
- inputmode="decimal"
469
- [value]="displayValue()"
470
- [readOnly]="readOnly()"
471
- [disabled]="disabled()"
472
- autocomplete="off"
473
- (focus)="focused.set(true)"
474
- (blur)="onBlur($event)" />
475
- </div>
476
- <ng-content />
477
- @if (validationError(); as error) {
478
- @if (focused()) {
479
- <div class="nxe-invalid-message">{{ error }}</div>
480
- }
481
- }
665
+ args: [{ selector: 'nxe-number-box', standalone: true, template: `
666
+ <div class="nxe-editor-container">
667
+ <input
668
+ #field
669
+ class="nxe-editor-input nxe-numberbox-input"
670
+ [attr.tabindex]="tabIndex()"
671
+ [id]="controlId()"
672
+ [attr.aria-label]="ariaLabel() || null"
673
+ [attr.aria-labelledby]="ariaLabelledBy() || null"
674
+ [attr.aria-invalid]="validationError() ? true : null"
675
+ [attr.aria-required]="required() ? true : null"
676
+ type="text"
677
+ inputmode="decimal"
678
+ [value]="displayValue()"
679
+ [readOnly]="readOnly()"
680
+ [disabled]="disabled()"
681
+ autocomplete="off"
682
+ (input)="onFieldInput($event)"
683
+ (focus)="focused.set(true)"
684
+ (blur)="onBlur($event)" />
685
+ @if (clearButtonVisible()) {
686
+ <span
687
+ class="nxe-clear-button-area"
688
+ (mousedown)="$event.preventDefault()"
689
+ (click)="onClear($event)"
690
+ aria-hidden="true">
691
+ <!-- A Ø16 disc with a 2px × cut out (the dropdown editors' clear glyph). -->
692
+ <svg viewBox="0 0 18 18" focusable="false">
693
+ <path fill-rule="evenodd" transform="rotate(45 9 9)"
694
+ d="M9 1a8 8 0 1 1 0 16A8 8 0 1 1 9 1ZM8 3.343h2V8h4.657v2H10v4.657H8V10H3.343V8H8Z" />
695
+ </svg>
696
+ </span>
697
+ }
698
+ </div>
699
+ <ng-content />
700
+ @if (validationError(); as error) {
701
+ @if (focused()) {
702
+ <div class="nxe-invalid-message">{{ error }}</div>
703
+ }
704
+ }
482
705
  `, host: {
483
706
  class: 'nxe-widget nxe-editor nxe-numberbox',
484
707
  '[class.nxe-focused]': 'focused()',
@@ -487,7 +710,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImpor
487
710
  { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NxeNumberBoxComponent), multi: true },
488
711
  { provide: NXE_VALIDATION_TARGET, useExisting: forwardRef(() => NxeNumberBoxComponent) },
489
712
  ], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, styles: ["nxe-number-box.text-right .nxe-numberbox-input{text-align:right}\n"] }]
490
- }], propDecorators: { formatInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "format", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }] } });
713
+ }], propDecorators: { formatInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "format", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], showClearButtonInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "showClearButton", required: false }] }], fieldRef: [{ type: i0.ViewChild, args: ['field', { isSignal: true }] }] } });
491
714
 
492
715
  /** Normalize a `[min]`/`[max]` binding (Date object or serialized string) to a DateTime. */
493
716
  function toBoundDate(bound) {
@@ -508,6 +731,15 @@ function toBoundDate(bound) {
508
731
  * typeable (parsed on blur per `displayFormat`) and opens a CDK-overlay calendar from the icon
509
732
  * button. Built on Luxon. Default `displayFormat` is `dd-MMM-yyyy`. Hosts a projected
510
733
  * `nxe-validator` via {@link NXE_VALIDATION_TARGET}.
734
+ *
735
+ * `showClearButton` (dx default `false`) adds dx's clear area between the input and the calendar
736
+ * button while the field holds text and is neither read-only nor disabled; it sets the value to
737
+ * `null` and focuses the input.
738
+ *
739
+ * The button and calendar glyphs are drawn in an 18-unit viewBox (1 unit = 1px) to the ink measured
740
+ * off dx's `event` (`date`), `eventall` (`datetime`) and `clock` (`time`) glyphs and its chevrons,
741
+ * each laid out from those metrics in rects, stroked rects and circles, and straight-edged polygons;
742
+ * the templates state each construction.
511
743
  */
512
744
  class NxeDateBoxComponent extends NxeFormControlBase {
513
745
  overlay = inject(Overlay);
@@ -529,6 +761,10 @@ class NxeDateBoxComponent extends NxeFormControlBase {
529
761
  ...(ngDevMode ? [{ debugName: "min" }] : /* istanbul ignore next */ []));
530
762
  max = input(null, /* @ts-ignore */
531
763
  ...(ngDevMode ? [{ debugName: "max" }] : /* istanbul ignore next */ []));
764
+ /** dx `showClearButton` — off by default, as in dx; `NXE_DEFAULT_OPTIONS.dateBox` can turn it on app-wide. */
765
+ showClearButtonInput = input(undefined, { ...(ngDevMode ? { debugName: "showClearButtonInput" } : /* istanbul ignore next */ {}), alias: 'showClearButton', transform: nxeOptionalBoolean });
766
+ showClearButton = computed(() => this.showClearButtonInput() ?? this.nxeDefaults.dateBox?.showClearButton ?? false, /* @ts-ignore */
767
+ ...(ngDevMode ? [{ debugName: "showClearButton" }] : /* istanbul ignore next */ []));
532
768
  focused = signal(false, /* @ts-ignore */
533
769
  ...(ngDevMode ? [{ debugName: "focused" }] : /* istanbul ignore next */ []));
534
770
  opened = signal(false, /* @ts-ignore */
@@ -562,6 +798,11 @@ class NxeDateBoxComponent extends NxeFormControlBase {
562
798
  ...(ngDevMode ? [{ debugName: "selected" }] : /* istanbul ignore next */ []));
563
799
  displayValue = computed(() => formatDisplay(this.selected(), this.effectiveDisplayFormat()), /* @ts-ignore */
564
800
  ...(ngDevMode ? [{ debugName: "displayValue" }] : /* istanbul ignore next */ []));
801
+ /** The text in the field right now; dx shows the clear button by the field's emptiness. */
802
+ fieldText = linkedSignal(() => this.displayValue(), /* @ts-ignore */
803
+ ...(ngDevMode ? [{ debugName: "fieldText" }] : /* istanbul ignore next */ []));
804
+ clearButtonVisible = computed(() => this.showClearButton() && !this.readOnly() && !this.disabled() && this.fieldText() !== '', /* @ts-ignore */
805
+ ...(ngDevMode ? [{ debugName: "clearButtonVisible" }] : /* istanbul ignore next */ []));
565
806
  weeks = computed(() => buildCalendarWeeks(this.viewMonth()), /* @ts-ignore */
566
807
  ...(ngDevMode ? [{ debugName: "weeks" }] : /* istanbul ignore next */ []));
567
808
  /** Two-digit hour/minute for the popup's time strip; blank when nothing is selected. */
@@ -670,6 +911,21 @@ class NxeDateBoxComponent extends NxeFormControlBase {
670
911
  }
671
912
  this.opened() ? this.close() : this.open();
672
913
  }
914
+ onFieldInput(event) {
915
+ this.fieldText.set(event.target.value);
916
+ }
917
+ /**
918
+ * dx `_clearValueHandler`: value to `null` (dx's `clear()` for a date box), then focus on the
919
+ * input. `syncField` repaints the field, so typed-but-uncommitted text over a value that was
920
+ * already `null` goes too.
921
+ */
922
+ onClear(event) {
923
+ this.commitValue(null, event);
924
+ this.syncField();
925
+ if (!this.focused()) {
926
+ this.fieldRef().nativeElement.focus();
927
+ }
928
+ }
673
929
  onFocus(event) {
674
930
  this.textAtFocus = event.target.value;
675
931
  this.focused.set(true);
@@ -721,6 +977,7 @@ class NxeDateBoxComponent extends NxeFormControlBase {
721
977
  const element = this.fieldRef().nativeElement;
722
978
  element.value = this.displayValue();
723
979
  this.textAtFocus = element.value;
980
+ this.fieldText.set(element.value);
724
981
  }
725
982
  selectDay(day) {
726
983
  if (this.isDisabledDay(day)) {
@@ -809,263 +1066,365 @@ class NxeDateBoxComponent extends NxeFormControlBase {
809
1066
  this.overlayRef?.dispose();
810
1067
  }
811
1068
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeDateBoxComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
812
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", type: NxeDateBoxComponent, isStandalone: true, selector: "nxe-date-box", inputs: { type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, displayFormat: { classPropertyName: "displayFormat", publicName: "displayFormat", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.nxe-focused": "focused()", "class.nxe-invalid": "!!validationError()" }, classAttribute: "nxe-widget nxe-editor nxe-datebox" }, providers: [
1069
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", type: NxeDateBoxComponent, isStandalone: true, selector: "nxe-date-box", inputs: { type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, displayFormat: { classPropertyName: "displayFormat", publicName: "displayFormat", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, showClearButtonInput: { classPropertyName: "showClearButtonInput", publicName: "showClearButton", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.nxe-focused": "focused()", "class.nxe-invalid": "!!validationError()" }, classAttribute: "nxe-widget nxe-editor nxe-datebox" }, providers: [
813
1070
  { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NxeDateBoxComponent), multi: true },
814
1071
  { provide: NXE_VALIDATION_TARGET, useExisting: forwardRef(() => NxeDateBoxComponent) },
815
- ], viewQueries: [{ propertyName: "calendarTemplate", first: true, predicate: ["calendarTemplate"], descendants: true, isSignal: true }, { propertyName: "fieldRef", first: true, predicate: ["field"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: `
816
- <div class="nxe-editor-container">
817
- <input
818
- #field
819
- class="nxe-editor-input nxe-datebox-input"
820
- type="text"
821
- [value]="displayValue()"
822
- [readOnly]="readOnly()"
823
- [disabled]="disabled()"
824
- [attr.placeholder]="effectivePlaceholder() || null"
825
- autocomplete="off"
826
- (focus)="onFocus($event)"
827
- (blur)="onBlur($event)" />
828
- @if (!readOnly()) {
829
- <span
830
- class="nxe-datebox-button"
831
- [class.nxe-datebox-open]="opened()"
832
- (mousedown)="onButtonMousedown($event)"
833
- aria-hidden="true">
834
- @if (type() === 'datetime' || type() === 'time') {
835
- <svg class="nxe-datebox-icon" viewBox="0 0 24 24">
836
- <path
837
- d="M12 2a10 10 0 100 20 10 10 0 000-20zm0 18a8 8 0 110-16 8 8 0 010 16zm.5-13H11v6l5.2 3.1.8-1.3-4.5-2.7V7z" />
838
- </svg>
839
- } @else {
840
- <svg class="nxe-datebox-icon" viewBox="0 0 24 24">
841
- <path
842
- d="M19 3h-1V1h-2v2H8V1H6v2H5a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2V5a2 2 0 00-2-2zm0 16H5V8h14v11z" />
843
- </svg>
844
- }
845
- </span>
846
- }
847
- </div>
848
- <ng-content />
849
- @if (validationError(); as error) {
850
- @if (focused()) {
851
- <div class="nxe-invalid-message">{{ error }}</div>
852
- }
853
- }
854
-
855
- <ng-template #calendarTemplate>
856
- <div class="nxe-calendar" (mousedown)="$event.preventDefault()">
857
- @if (type() !== 'time') {
858
- <div class="nxe-calendar-navigator">
859
- <button type="button" class="nxe-calendar-nav" (click)="navigate(-1)">
860
- <svg class="nxe-calendar-chevron" viewBox="0 0 24 24">
861
- <path d="M15.4 7.4 14 6l-6 6 6 6 1.4-1.4L10.8 12z" />
862
- </svg>
863
- </button>
864
- <button type="button" class="nxe-calendar-caption" (click)="zoomOut()">
865
- {{ caption() }}
866
- </button>
867
- <button type="button" class="nxe-calendar-nav" (click)="navigate(1)">
868
- <svg class="nxe-calendar-chevron" viewBox="0 0 24 24">
869
- <path d="M8.6 16.6 10 18l6-6-6-6-1.4 1.4 4.6 4.6z" />
870
- </svg>
871
- </button>
872
- </div>
873
- @if (zoomLevel() === 'month') {
874
- <div class="nxe-calendar-weekdays">
875
- @for (wd of weekdays; track wd) {
876
- <span>{{ wd }}</span>
877
- }
878
- </div>
879
- <div class="nxe-calendar-grid">
880
- @for (week of weeks(); track $index) {
881
- @for (day of week; track day.toMillis()) {
882
- <button
883
- type="button"
884
- class="nxe-calendar-day"
885
- [class.nxe-calendar-other]="day.month !== viewMonth().month"
886
- [class.nxe-calendar-selected]="isSelected(day)"
887
- [class.nxe-calendar-today]="isToday(day)"
888
- [disabled]="isDisabledDay(day)"
889
- (click)="selectDay(day)">
890
- <span>{{ day.day }}</span>
891
- </button>
892
- }
893
- }
894
- </div>
895
- } @else {
896
- <div
897
- class="nxe-calendar-periods"
898
- [class.nxe-calendar-periods-century]="zoomLevel() === 'century'">
899
- @for (cell of periodCells(); track cell.anchor.toMillis()) {
900
- <button
901
- type="button"
902
- class="nxe-calendar-period"
903
- [class.nxe-calendar-other]="cell.other"
904
- [class.nxe-calendar-today]="cell.isNow"
905
- [disabled]="cell.disabled"
906
- (click)="selectPeriod(cell)">
907
- <span>{{ cell.label }}</span>
908
- </button>
909
- }
910
- </div>
911
- }
912
- }
913
- @if (type() !== 'date') {
914
- <div class="nxe-calendar-time">
915
- <input
916
- class="nxe-calendar-time-part"
917
- type="text"
918
- inputmode="numeric"
919
- maxlength="2"
920
- aria-label="Hour"
921
- [value]="hourText()"
922
- (change)="setTimePart('hour', $event)" />
923
- <span class="nxe-calendar-time-sep" aria-hidden="true">:</span>
924
- <input
925
- class="nxe-calendar-time-part"
926
- type="text"
927
- inputmode="numeric"
928
- maxlength="2"
929
- aria-label="Minute"
930
- [value]="minuteText()"
931
- (change)="setTimePart('minute', $event)" />
932
- <button type="button" class="nxe-calendar-time-btn" (click)="setNow()">Now</button>
933
- <button type="button" class="nxe-calendar-time-btn nxe-calendar-time-done" (click)="close()">
934
- Done
935
- </button>
936
- </div>
937
- }
938
- </div>
939
- </ng-template>
940
- `, isInline: true, styles: ["nxe-date-box .nxe-datebox-button{flex:0 0 34px;align-self:stretch;display:inline-flex;align-items:center;justify-content:center;width:34px;cursor:pointer;color:#333;border-radius:4px}nxe-date-box .nxe-datebox-icon{width:18px;height:18px;fill:currentColor}nxe-date-box .nxe-editor-container:has(>.nxe-datebox-button){--nxe-invalid-badge-inset: 38px}nxe-date-box.nxe-readonly .nxe-datebox-button,nxe-date-box.nxe-disabled .nxe-datebox-button{cursor:default}nxe-date-box.nxe-readonly .nxe-datebox-button:hover,nxe-date-box.nxe-disabled .nxe-datebox-button:hover{background-color:transparent}.nxe-calendar{width:298px;box-sizing:border-box;padding:8px;background:#fff;border:1px solid #ddd;border-radius:6px;box-shadow:0 6px 12px #0000002e;color:#333;font-size:12px;-webkit-user-select:none;user-select:none}.nxe-calendar-navigator{display:flex;height:36px;margin-bottom:8px}.nxe-calendar-nav,.nxe-calendar-caption{display:inline-flex;align-items:center;justify-content:center;border:1px solid #ddd;background:transparent;color:#333;cursor:pointer;padding:0}.nxe-calendar-nav:hover,.nxe-calendar-caption:hover{background:#0000000a}.nxe-calendar-nav{flex:0 0 36px}.nxe-calendar-nav:first-child{border-radius:4px 0 0 4px}.nxe-calendar-nav:last-child{border-radius:0 4px 4px 0}.nxe-calendar-caption{flex:1;margin:0 -1px;font-size:12px;font-weight:600}.nxe-calendar-chevron{width:18px;height:18px;fill:currentColor}.nxe-calendar-weekdays{display:grid;grid-template-columns:repeat(7,1fr);align-items:center;height:24px;text-align:center;color:#333;font-size:10px;font-weight:500}.nxe-calendar-grid{display:grid;grid-template-columns:repeat(7,1fr)}.nxe-calendar-day,.nxe-calendar-period{display:flex;align-items:center;justify-content:center;border:0;background:transparent;cursor:pointer;padding:0;font-size:14px;line-height:1;color:#333}.nxe-calendar-day span,.nxe-calendar-period span{display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;border-radius:4px}.nxe-calendar-day:hover:not(:disabled) span,.nxe-calendar-period:hover:not(:disabled) span{background:#337ab73d}.nxe-calendar-day:disabled,.nxe-calendar-period:disabled{cursor:default;color:#33333361}.nxe-calendar-day{height:33px}.nxe-calendar-day span{width:28px;height:28px}.nxe-calendar-periods{display:grid;grid-template-columns:repeat(4,1fr);grid-auto-rows:74px}.nxe-calendar-period span{min-width:54px;height:54px;padding:0 4px}.nxe-calendar-periods-century .nxe-calendar-period span{width:40px;min-width:0;height:auto;min-height:56px;padding:4px 0;white-space:normal;text-align:center;line-height:1.4}.nxe-calendar-other{color:#33333361}.nxe-calendar-today span{border:2px solid #337ab7}.nxe-calendar-selected span,.nxe-calendar-selected:hover:not(:disabled) span{background:#337ab7;color:#fff;box-shadow:0 0 0 2px #337ab780;border:0}.nxe-calendar-time{display:flex;align-items:center;gap:4px;margin-top:8px;padding-top:8px;border-top:1px solid #ddd}.nxe-calendar-time-part{width:2.6em;height:2.2em;box-sizing:border-box;padding:0 .25em;border:1px solid #ddd;border-radius:4px;background:#fff;color:#333;font-size:inherit;font-variant-numeric:tabular-nums;text-align:center}.nxe-calendar-time-part:focus{outline:2px solid #337ab7;outline-offset:-2px}.nxe-calendar-time-sep{font-weight:600}.nxe-calendar-time-btn{height:2.2em;padding:0 .75em;border:1px solid #ddd;border-radius:4px;background:transparent;color:#333;font-size:inherit;cursor:pointer}.nxe-calendar-time-btn:hover{background:#0000000a}.nxe-calendar-time-btn:focus-visible{outline:2px solid #337ab7;outline-offset:-2px}.nxe-calendar-time-done{margin-left:auto}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
1072
+ ], viewQueries: [{ propertyName: "calendarTemplate", first: true, predicate: ["calendarTemplate"], descendants: true, isSignal: true }, { propertyName: "fieldRef", first: true, predicate: ["field"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: `
1073
+ <div class="nxe-editor-container">
1074
+ <input
1075
+ #field
1076
+ class="nxe-editor-input nxe-datebox-input"
1077
+ [attr.tabindex]="tabIndex()"
1078
+ [id]="controlId()"
1079
+ [attr.aria-label]="ariaLabel() || null"
1080
+ [attr.aria-labelledby]="ariaLabelledBy() || null"
1081
+ [attr.aria-invalid]="validationError() ? true : null"
1082
+ [attr.aria-required]="required() ? true : null"
1083
+ type="text"
1084
+ [value]="displayValue()"
1085
+ [readOnly]="readOnly()"
1086
+ [disabled]="disabled()"
1087
+ [attr.placeholder]="effectivePlaceholder() || null"
1088
+ autocomplete="off"
1089
+ (input)="onFieldInput($event)"
1090
+ (focus)="onFocus($event)"
1091
+ (blur)="onBlur($event)" />
1092
+ @if (clearButtonVisible()) {
1093
+ <span
1094
+ class="nxe-clear-button-area"
1095
+ (mousedown)="$event.preventDefault()"
1096
+ (click)="onClear($event)"
1097
+ aria-hidden="true">
1098
+ <!-- A Ø16 disc with a 2px × cut out (the dropdown editors' clear glyph). -->
1099
+ <svg viewBox="0 0 18 18" focusable="false">
1100
+ <path fill-rule="evenodd" transform="rotate(45 9 9)"
1101
+ d="M9 1a8 8 0 1 1 0 16A8 8 0 1 1 9 1ZM8 3.343h2V8h4.657v2H10v4.657H8V10H3.343V8H8Z" />
1102
+ </svg>
1103
+ </span>
1104
+ }
1105
+ @if (!readOnly()) {
1106
+ <span
1107
+ class="nxe-datebox-button"
1108
+ [class.nxe-datebox-open]="opened()"
1109
+ (mousedown)="onButtonMousedown($event)"
1110
+ aria-hidden="true">
1111
+ @switch (type()) {
1112
+ @case ('time') {
1113
+ <!-- dx clock, a 16x16 ink box round the box centre: a 2px ring (an r 7
1114
+ circle stroked 2px), a 2x4 hand straight up from the centre, a 1.5x4
1115
+ hand turned 45° down-right, and a Ø2 hub where they meet. -->
1116
+ <svg class="nxe-datebox-icon nxe-datebox-icon-time" viewBox="0 0 18 18" focusable="false">
1117
+ <circle cx="9" cy="9" r="7" fill="none" stroke="currentColor" stroke-width="2" />
1118
+ <rect x="8" y="5" width="2" height="4" />
1119
+ <rect x="9" y="8.25" width="4" height="1.5" transform="rotate(45 9 9)" />
1120
+ <circle cx="9" cy="9" r="1" />
1121
+ </svg>
1122
+ }
1123
+ @case ('datetime') {
1124
+ <!-- dx eventall: the calendar page below, without its day marker. -->
1125
+ <svg class="nxe-datebox-icon nxe-datebox-icon-datetime" viewBox="0 0 18 18" focusable="false">
1126
+ <rect x="3" y="3" width="12" height="12" rx="1" fill="none" stroke="currentColor" stroke-width="2" />
1127
+ <rect x="3" y="3" width="12" height="3" />
1128
+ <rect x="4" y="1" width="2" height="1.5" />
1129
+ <rect x="12" y="1" width="2" height="1.5" />
1130
+ </svg>
1131
+ }
1132
+ @default {
1133
+ <!-- dx event, a 14x15 ink box from 1px below the top of the 18px box
1134
+ (where dx's glyph renders): the page is a 12x12 rect with 1px corners
1135
+ stroked 2px, so 14x14 with 2px corners outside and square inside; a
1136
+ 12x3 bar under its top wall makes the 4px header band; two 2x1.5
1137
+ binder tabs stand 1px proud of it; a 4x4 day marker at (9, 9). -->
1138
+ <svg class="nxe-datebox-icon nxe-datebox-icon-date" viewBox="0 0 18 18" focusable="false">
1139
+ <rect x="3" y="3" width="12" height="12" rx="1" fill="none" stroke="currentColor" stroke-width="2" />
1140
+ <rect x="3" y="3" width="12" height="3" />
1141
+ <rect x="4" y="1" width="2" height="1.5" />
1142
+ <rect x="12" y="1" width="2" height="1.5" />
1143
+ <rect x="9" y="9" width="4" height="4" />
1144
+ </svg>
1145
+ }
1146
+ }
1147
+ </span>
1148
+ }
1149
+ </div>
1150
+ <ng-content />
1151
+ @if (validationError(); as error) {
1152
+ @if (focused()) {
1153
+ <div class="nxe-invalid-message">{{ error }}</div>
1154
+ }
1155
+ }
1156
+
1157
+ <ng-template #calendarTemplate>
1158
+ <div class="nxe-calendar" (mousedown)="$event.preventDefault()">
1159
+ @if (type() !== 'time') {
1160
+ <div class="nxe-calendar-navigator">
1161
+ <button type="button" class="nxe-calendar-nav" (click)="navigate(-1)">
1162
+ <!-- dx chevronleft (8x14 ink, centred in its 18px box): two 45° arms
1163
+ whose outer edges run 7px out and 7px up and down from the point,
1164
+ their ends cut square 1.25px across and down (a 1.77px stroke),
1165
+ meeting 2.5px behind it. That draws 8.25x14, centred by putting the
1166
+ point at x 4.875, and rasterises to dx's 8x14. -->
1167
+ <svg class="nxe-calendar-chevron" viewBox="0 0 18 18" focusable="false">
1168
+ <polygon points="4.875 9 11.875 2 13.125 3.25 7.375 9 13.125 14.75 11.875 16" />
1169
+ </svg>
1170
+ </button>
1171
+ <button type="button" class="nxe-calendar-caption" (click)="zoomOut()">
1172
+ {{ caption() }}
1173
+ </button>
1174
+ <button type="button" class="nxe-calendar-nav" (click)="navigate(1)">
1175
+ <!-- dx chevronright: the same chevron mirrored about x 9. -->
1176
+ <svg class="nxe-calendar-chevron" viewBox="0 0 18 18" focusable="false">
1177
+ <polygon points="13.125 9 6.125 2 4.875 3.25 10.625 9 4.875 14.75 6.125 16" />
1178
+ </svg>
1179
+ </button>
1180
+ </div>
1181
+ @if (zoomLevel() === 'month') {
1182
+ <div class="nxe-calendar-weekdays">
1183
+ @for (wd of weekdays; track wd) {
1184
+ <span>{{ wd }}</span>
1185
+ }
1186
+ </div>
1187
+ <div class="nxe-calendar-grid">
1188
+ @for (week of weeks(); track $index) {
1189
+ @for (day of week; track day.toMillis()) {
1190
+ <button
1191
+ type="button"
1192
+ class="nxe-calendar-day"
1193
+ [class.nxe-calendar-other]="day.month !== viewMonth().month"
1194
+ [class.nxe-calendar-selected]="isSelected(day)"
1195
+ [class.nxe-calendar-today]="isToday(day)"
1196
+ [disabled]="isDisabledDay(day)"
1197
+ (click)="selectDay(day)">
1198
+ <span>{{ day.day }}</span>
1199
+ </button>
1200
+ }
1201
+ }
1202
+ </div>
1203
+ } @else {
1204
+ <div
1205
+ class="nxe-calendar-periods"
1206
+ [class.nxe-calendar-periods-century]="zoomLevel() === 'century'">
1207
+ @for (cell of periodCells(); track cell.anchor.toMillis()) {
1208
+ <button
1209
+ type="button"
1210
+ class="nxe-calendar-period"
1211
+ [class.nxe-calendar-other]="cell.other"
1212
+ [class.nxe-calendar-today]="cell.isNow"
1213
+ [disabled]="cell.disabled"
1214
+ (click)="selectPeriod(cell)">
1215
+ <span>{{ cell.label }}</span>
1216
+ </button>
1217
+ }
1218
+ </div>
1219
+ }
1220
+ }
1221
+ @if (type() !== 'date') {
1222
+ <div class="nxe-calendar-time">
1223
+ <input
1224
+ class="nxe-calendar-time-part"
1225
+ type="text"
1226
+ inputmode="numeric"
1227
+ maxlength="2"
1228
+ aria-label="Hour"
1229
+ [value]="hourText()"
1230
+ (change)="setTimePart('hour', $event)" />
1231
+ <span class="nxe-calendar-time-sep" aria-hidden="true">:</span>
1232
+ <input
1233
+ class="nxe-calendar-time-part"
1234
+ type="text"
1235
+ inputmode="numeric"
1236
+ maxlength="2"
1237
+ aria-label="Minute"
1238
+ [value]="minuteText()"
1239
+ (change)="setTimePart('minute', $event)" />
1240
+ <button type="button" class="nxe-calendar-time-btn" (click)="setNow()">Now</button>
1241
+ <button type="button" class="nxe-calendar-time-btn nxe-calendar-time-done" (click)="close()">
1242
+ Done
1243
+ </button>
1244
+ </div>
1245
+ }
1246
+ </div>
1247
+ </ng-template>
1248
+ `, isInline: true, styles: ["nxe-date-box .nxe-datebox-button{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-date-box .nxe-datebox-button svg{display:block;width:18px;height:18px;fill:currentColor}nxe-date-box:hover:not(.nxe-disabled) .nxe-datebox-button,nxe-date-box:active:not(.nxe-disabled) .nxe-datebox-button{background-color:#e6e6e6}nxe-date-box .nxe-datebox-button.nxe-datebox-open{background-color:#b3b3b3}nxe-date-box .nxe-datebox-icon{width:18px;height:18px;fill:currentColor}nxe-date-box .nxe-editor-container:has(>.nxe-datebox-button){--nxe-invalid-badge-inset: 38px}nxe-date-box .nxe-editor-container:has(>.nxe-clear-button-area):has(>.nxe-datebox-button){--nxe-invalid-badge-inset: 72px}nxe-date-box.nxe-readonly .nxe-datebox-button,nxe-date-box.nxe-disabled .nxe-datebox-button{cursor:default}nxe-date-box.nxe-readonly .nxe-datebox-button:hover,nxe-date-box.nxe-disabled .nxe-datebox-button:hover{background-color:transparent}.nxe-calendar{width:298px;box-sizing:border-box;padding:8px;background:#fff;border:1px solid #ddd;border-radius:6px;box-shadow:0 6px 12px #0000002e;color:#333;font-size:var(--nxe-ui-font-size, 12px);-webkit-user-select:none;user-select:none}.nxe-calendar-navigator{display:flex;height:36px;margin-bottom:8px}.nxe-calendar-nav,.nxe-calendar-caption{display:inline-flex;align-items:center;justify-content:center;border:1px solid #ddd;background:transparent;color:#333;cursor:pointer;padding:0}.nxe-calendar-nav:hover,.nxe-calendar-caption:hover{background:#0000000a}.nxe-calendar-nav:active,.nxe-calendar-caption:active{background:#0000003d}.nxe-calendar-nav{flex:0 0 36px}.nxe-calendar-nav:first-child{border-radius:4px 0 0 4px}.nxe-calendar-nav:last-child{border-radius:0 4px 4px 0}.nxe-calendar-caption{flex:1;margin:0 -1px;font-size:inherit;font-weight:600}.nxe-calendar-chevron{width:18px;height:18px;fill:currentColor}.nxe-calendar-weekdays{display:grid;grid-template-columns:repeat(7,1fr);align-items:center;height:24px;text-align:center;color:#333;font-size:10px;font-weight:500}.nxe-calendar-grid{display:grid;grid-template-columns:repeat(7,1fr)}.nxe-calendar-day,.nxe-calendar-period{display:flex;align-items:center;justify-content:center;border:0;background:transparent;cursor:pointer;padding:0;font-size:14px;line-height:1;color:#333}.nxe-calendar-day span,.nxe-calendar-period span{display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;border-radius:4px}.nxe-calendar-day:hover:not(:disabled) span,.nxe-calendar-period:hover:not(:disabled) span{background:#337ab73d}.nxe-calendar-day:disabled,.nxe-calendar-period:disabled{cursor:default;color:#33333361}.nxe-calendar-day{height:33px}.nxe-calendar-day span{width:28px;height:28px}.nxe-calendar-periods{display:grid;grid-template-columns:repeat(4,1fr);grid-auto-rows:74px}.nxe-calendar-period span{min-width:54px;height:54px;padding:0 4px}.nxe-calendar-periods-century .nxe-calendar-period span{width:40px;min-width:0;height:auto;min-height:56px;padding:4px 0;white-space:normal;text-align:center;line-height:1.4}.nxe-calendar-other{color:#33333361}.nxe-calendar-today span{border:2px solid #337ab7}.nxe-calendar-selected span,.nxe-calendar-selected:hover:not(:disabled) span{background:#337ab7;color:#fff;box-shadow:0 0 0 2px #337ab780;border:0}.nxe-calendar-time{display:flex;align-items:center;gap:4px;margin-top:8px;padding-top:8px;border-top:1px solid #ddd}.nxe-calendar-time-part{width:2.6em;height:2.2em;box-sizing:border-box;padding:0 .25em;border:1px solid #ddd;border-radius:4px;background:#fff;color:#333;font-size:inherit;font-variant-numeric:tabular-nums;text-align:center}.nxe-calendar-time-part:focus{outline:2px solid #337ab7;outline-offset:-2px}.nxe-calendar-time-sep{font-weight:600}.nxe-calendar-time-btn{height:2.2em;padding:0 .75em;border:1px solid #ddd;border-radius:4px;background:transparent;color:#333;font-size:inherit;cursor:pointer}.nxe-calendar-time-btn:hover{background:#0000000a}.nxe-calendar-time-btn:focus-visible{outline:2px solid #337ab7;outline-offset:-2px}.nxe-calendar-time-done{margin-left:auto}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
941
1249
  }
942
1250
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeDateBoxComponent, decorators: [{
943
1251
  type: Component,
944
- args: [{ selector: 'nxe-date-box', standalone: true, template: `
945
- <div class="nxe-editor-container">
946
- <input
947
- #field
948
- class="nxe-editor-input nxe-datebox-input"
949
- type="text"
950
- [value]="displayValue()"
951
- [readOnly]="readOnly()"
952
- [disabled]="disabled()"
953
- [attr.placeholder]="effectivePlaceholder() || null"
954
- autocomplete="off"
955
- (focus)="onFocus($event)"
956
- (blur)="onBlur($event)" />
957
- @if (!readOnly()) {
958
- <span
959
- class="nxe-datebox-button"
960
- [class.nxe-datebox-open]="opened()"
961
- (mousedown)="onButtonMousedown($event)"
962
- aria-hidden="true">
963
- @if (type() === 'datetime' || type() === 'time') {
964
- <svg class="nxe-datebox-icon" viewBox="0 0 24 24">
965
- <path
966
- d="M12 2a10 10 0 100 20 10 10 0 000-20zm0 18a8 8 0 110-16 8 8 0 010 16zm.5-13H11v6l5.2 3.1.8-1.3-4.5-2.7V7z" />
967
- </svg>
968
- } @else {
969
- <svg class="nxe-datebox-icon" viewBox="0 0 24 24">
970
- <path
971
- d="M19 3h-1V1h-2v2H8V1H6v2H5a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2V5a2 2 0 00-2-2zm0 16H5V8h14v11z" />
972
- </svg>
973
- }
974
- </span>
975
- }
976
- </div>
977
- <ng-content />
978
- @if (validationError(); as error) {
979
- @if (focused()) {
980
- <div class="nxe-invalid-message">{{ error }}</div>
981
- }
982
- }
983
-
984
- <ng-template #calendarTemplate>
985
- <div class="nxe-calendar" (mousedown)="$event.preventDefault()">
986
- @if (type() !== 'time') {
987
- <div class="nxe-calendar-navigator">
988
- <button type="button" class="nxe-calendar-nav" (click)="navigate(-1)">
989
- <svg class="nxe-calendar-chevron" viewBox="0 0 24 24">
990
- <path d="M15.4 7.4 14 6l-6 6 6 6 1.4-1.4L10.8 12z" />
991
- </svg>
992
- </button>
993
- <button type="button" class="nxe-calendar-caption" (click)="zoomOut()">
994
- {{ caption() }}
995
- </button>
996
- <button type="button" class="nxe-calendar-nav" (click)="navigate(1)">
997
- <svg class="nxe-calendar-chevron" viewBox="0 0 24 24">
998
- <path d="M8.6 16.6 10 18l6-6-6-6-1.4 1.4 4.6 4.6z" />
999
- </svg>
1000
- </button>
1001
- </div>
1002
- @if (zoomLevel() === 'month') {
1003
- <div class="nxe-calendar-weekdays">
1004
- @for (wd of weekdays; track wd) {
1005
- <span>{{ wd }}</span>
1006
- }
1007
- </div>
1008
- <div class="nxe-calendar-grid">
1009
- @for (week of weeks(); track $index) {
1010
- @for (day of week; track day.toMillis()) {
1011
- <button
1012
- type="button"
1013
- class="nxe-calendar-day"
1014
- [class.nxe-calendar-other]="day.month !== viewMonth().month"
1015
- [class.nxe-calendar-selected]="isSelected(day)"
1016
- [class.nxe-calendar-today]="isToday(day)"
1017
- [disabled]="isDisabledDay(day)"
1018
- (click)="selectDay(day)">
1019
- <span>{{ day.day }}</span>
1020
- </button>
1021
- }
1022
- }
1023
- </div>
1024
- } @else {
1025
- <div
1026
- class="nxe-calendar-periods"
1027
- [class.nxe-calendar-periods-century]="zoomLevel() === 'century'">
1028
- @for (cell of periodCells(); track cell.anchor.toMillis()) {
1029
- <button
1030
- type="button"
1031
- class="nxe-calendar-period"
1032
- [class.nxe-calendar-other]="cell.other"
1033
- [class.nxe-calendar-today]="cell.isNow"
1034
- [disabled]="cell.disabled"
1035
- (click)="selectPeriod(cell)">
1036
- <span>{{ cell.label }}</span>
1037
- </button>
1038
- }
1039
- </div>
1040
- }
1041
- }
1042
- @if (type() !== 'date') {
1043
- <div class="nxe-calendar-time">
1044
- <input
1045
- class="nxe-calendar-time-part"
1046
- type="text"
1047
- inputmode="numeric"
1048
- maxlength="2"
1049
- aria-label="Hour"
1050
- [value]="hourText()"
1051
- (change)="setTimePart('hour', $event)" />
1052
- <span class="nxe-calendar-time-sep" aria-hidden="true">:</span>
1053
- <input
1054
- class="nxe-calendar-time-part"
1055
- type="text"
1056
- inputmode="numeric"
1057
- maxlength="2"
1058
- aria-label="Minute"
1059
- [value]="minuteText()"
1060
- (change)="setTimePart('minute', $event)" />
1061
- <button type="button" class="nxe-calendar-time-btn" (click)="setNow()">Now</button>
1062
- <button type="button" class="nxe-calendar-time-btn nxe-calendar-time-done" (click)="close()">
1063
- Done
1064
- </button>
1065
- </div>
1066
- }
1067
- </div>
1068
- </ng-template>
1252
+ args: [{ selector: 'nxe-date-box', standalone: true, template: `
1253
+ <div class="nxe-editor-container">
1254
+ <input
1255
+ #field
1256
+ class="nxe-editor-input nxe-datebox-input"
1257
+ [attr.tabindex]="tabIndex()"
1258
+ [id]="controlId()"
1259
+ [attr.aria-label]="ariaLabel() || null"
1260
+ [attr.aria-labelledby]="ariaLabelledBy() || null"
1261
+ [attr.aria-invalid]="validationError() ? true : null"
1262
+ [attr.aria-required]="required() ? true : null"
1263
+ type="text"
1264
+ [value]="displayValue()"
1265
+ [readOnly]="readOnly()"
1266
+ [disabled]="disabled()"
1267
+ [attr.placeholder]="effectivePlaceholder() || null"
1268
+ autocomplete="off"
1269
+ (input)="onFieldInput($event)"
1270
+ (focus)="onFocus($event)"
1271
+ (blur)="onBlur($event)" />
1272
+ @if (clearButtonVisible()) {
1273
+ <span
1274
+ class="nxe-clear-button-area"
1275
+ (mousedown)="$event.preventDefault()"
1276
+ (click)="onClear($event)"
1277
+ aria-hidden="true">
1278
+ <!-- A Ø16 disc with a 2px × cut out (the dropdown editors' clear glyph). -->
1279
+ <svg viewBox="0 0 18 18" focusable="false">
1280
+ <path fill-rule="evenodd" transform="rotate(45 9 9)"
1281
+ d="M9 1a8 8 0 1 1 0 16A8 8 0 1 1 9 1ZM8 3.343h2V8h4.657v2H10v4.657H8V10H3.343V8H8Z" />
1282
+ </svg>
1283
+ </span>
1284
+ }
1285
+ @if (!readOnly()) {
1286
+ <span
1287
+ class="nxe-datebox-button"
1288
+ [class.nxe-datebox-open]="opened()"
1289
+ (mousedown)="onButtonMousedown($event)"
1290
+ aria-hidden="true">
1291
+ @switch (type()) {
1292
+ @case ('time') {
1293
+ <!-- dx clock, a 16x16 ink box round the box centre: a 2px ring (an r 7
1294
+ circle stroked 2px), a 2x4 hand straight up from the centre, a 1.5x4
1295
+ hand turned 45° down-right, and a Ø2 hub where they meet. -->
1296
+ <svg class="nxe-datebox-icon nxe-datebox-icon-time" viewBox="0 0 18 18" focusable="false">
1297
+ <circle cx="9" cy="9" r="7" fill="none" stroke="currentColor" stroke-width="2" />
1298
+ <rect x="8" y="5" width="2" height="4" />
1299
+ <rect x="9" y="8.25" width="4" height="1.5" transform="rotate(45 9 9)" />
1300
+ <circle cx="9" cy="9" r="1" />
1301
+ </svg>
1302
+ }
1303
+ @case ('datetime') {
1304
+ <!-- dx eventall: the calendar page below, without its day marker. -->
1305
+ <svg class="nxe-datebox-icon nxe-datebox-icon-datetime" viewBox="0 0 18 18" focusable="false">
1306
+ <rect x="3" y="3" width="12" height="12" rx="1" fill="none" stroke="currentColor" stroke-width="2" />
1307
+ <rect x="3" y="3" width="12" height="3" />
1308
+ <rect x="4" y="1" width="2" height="1.5" />
1309
+ <rect x="12" y="1" width="2" height="1.5" />
1310
+ </svg>
1311
+ }
1312
+ @default {
1313
+ <!-- dx event, a 14x15 ink box from 1px below the top of the 18px box
1314
+ (where dx's glyph renders): the page is a 12x12 rect with 1px corners
1315
+ stroked 2px, so 14x14 with 2px corners outside and square inside; a
1316
+ 12x3 bar under its top wall makes the 4px header band; two 2x1.5
1317
+ binder tabs stand 1px proud of it; a 4x4 day marker at (9, 9). -->
1318
+ <svg class="nxe-datebox-icon nxe-datebox-icon-date" viewBox="0 0 18 18" focusable="false">
1319
+ <rect x="3" y="3" width="12" height="12" rx="1" fill="none" stroke="currentColor" stroke-width="2" />
1320
+ <rect x="3" y="3" width="12" height="3" />
1321
+ <rect x="4" y="1" width="2" height="1.5" />
1322
+ <rect x="12" y="1" width="2" height="1.5" />
1323
+ <rect x="9" y="9" width="4" height="4" />
1324
+ </svg>
1325
+ }
1326
+ }
1327
+ </span>
1328
+ }
1329
+ </div>
1330
+ <ng-content />
1331
+ @if (validationError(); as error) {
1332
+ @if (focused()) {
1333
+ <div class="nxe-invalid-message">{{ error }}</div>
1334
+ }
1335
+ }
1336
+
1337
+ <ng-template #calendarTemplate>
1338
+ <div class="nxe-calendar" (mousedown)="$event.preventDefault()">
1339
+ @if (type() !== 'time') {
1340
+ <div class="nxe-calendar-navigator">
1341
+ <button type="button" class="nxe-calendar-nav" (click)="navigate(-1)">
1342
+ <!-- dx chevronleft (8x14 ink, centred in its 18px box): two 45° arms
1343
+ whose outer edges run 7px out and 7px up and down from the point,
1344
+ their ends cut square 1.25px across and down (a 1.77px stroke),
1345
+ meeting 2.5px behind it. That draws 8.25x14, centred by putting the
1346
+ point at x 4.875, and rasterises to dx's 8x14. -->
1347
+ <svg class="nxe-calendar-chevron" viewBox="0 0 18 18" focusable="false">
1348
+ <polygon points="4.875 9 11.875 2 13.125 3.25 7.375 9 13.125 14.75 11.875 16" />
1349
+ </svg>
1350
+ </button>
1351
+ <button type="button" class="nxe-calendar-caption" (click)="zoomOut()">
1352
+ {{ caption() }}
1353
+ </button>
1354
+ <button type="button" class="nxe-calendar-nav" (click)="navigate(1)">
1355
+ <!-- dx chevronright: the same chevron mirrored about x 9. -->
1356
+ <svg class="nxe-calendar-chevron" viewBox="0 0 18 18" focusable="false">
1357
+ <polygon points="13.125 9 6.125 2 4.875 3.25 10.625 9 4.875 14.75 6.125 16" />
1358
+ </svg>
1359
+ </button>
1360
+ </div>
1361
+ @if (zoomLevel() === 'month') {
1362
+ <div class="nxe-calendar-weekdays">
1363
+ @for (wd of weekdays; track wd) {
1364
+ <span>{{ wd }}</span>
1365
+ }
1366
+ </div>
1367
+ <div class="nxe-calendar-grid">
1368
+ @for (week of weeks(); track $index) {
1369
+ @for (day of week; track day.toMillis()) {
1370
+ <button
1371
+ type="button"
1372
+ class="nxe-calendar-day"
1373
+ [class.nxe-calendar-other]="day.month !== viewMonth().month"
1374
+ [class.nxe-calendar-selected]="isSelected(day)"
1375
+ [class.nxe-calendar-today]="isToday(day)"
1376
+ [disabled]="isDisabledDay(day)"
1377
+ (click)="selectDay(day)">
1378
+ <span>{{ day.day }}</span>
1379
+ </button>
1380
+ }
1381
+ }
1382
+ </div>
1383
+ } @else {
1384
+ <div
1385
+ class="nxe-calendar-periods"
1386
+ [class.nxe-calendar-periods-century]="zoomLevel() === 'century'">
1387
+ @for (cell of periodCells(); track cell.anchor.toMillis()) {
1388
+ <button
1389
+ type="button"
1390
+ class="nxe-calendar-period"
1391
+ [class.nxe-calendar-other]="cell.other"
1392
+ [class.nxe-calendar-today]="cell.isNow"
1393
+ [disabled]="cell.disabled"
1394
+ (click)="selectPeriod(cell)">
1395
+ <span>{{ cell.label }}</span>
1396
+ </button>
1397
+ }
1398
+ </div>
1399
+ }
1400
+ }
1401
+ @if (type() !== 'date') {
1402
+ <div class="nxe-calendar-time">
1403
+ <input
1404
+ class="nxe-calendar-time-part"
1405
+ type="text"
1406
+ inputmode="numeric"
1407
+ maxlength="2"
1408
+ aria-label="Hour"
1409
+ [value]="hourText()"
1410
+ (change)="setTimePart('hour', $event)" />
1411
+ <span class="nxe-calendar-time-sep" aria-hidden="true">:</span>
1412
+ <input
1413
+ class="nxe-calendar-time-part"
1414
+ type="text"
1415
+ inputmode="numeric"
1416
+ maxlength="2"
1417
+ aria-label="Minute"
1418
+ [value]="minuteText()"
1419
+ (change)="setTimePart('minute', $event)" />
1420
+ <button type="button" class="nxe-calendar-time-btn" (click)="setNow()">Now</button>
1421
+ <button type="button" class="nxe-calendar-time-btn nxe-calendar-time-done" (click)="close()">
1422
+ Done
1423
+ </button>
1424
+ </div>
1425
+ }
1426
+ </div>
1427
+ </ng-template>
1069
1428
  `, host: {
1070
1429
  class: 'nxe-widget nxe-editor nxe-datebox',
1071
1430
  '[class.nxe-focused]': 'focused()',
@@ -1073,13 +1432,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImpor
1073
1432
  }, providers: [
1074
1433
  { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NxeDateBoxComponent), multi: true },
1075
1434
  { provide: NXE_VALIDATION_TARGET, useExisting: forwardRef(() => NxeDateBoxComponent) },
1076
- ], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, styles: ["nxe-date-box .nxe-datebox-button{flex:0 0 34px;align-self:stretch;display:inline-flex;align-items:center;justify-content:center;width:34px;cursor:pointer;color:#333;border-radius:4px}nxe-date-box .nxe-datebox-icon{width:18px;height:18px;fill:currentColor}nxe-date-box .nxe-editor-container:has(>.nxe-datebox-button){--nxe-invalid-badge-inset: 38px}nxe-date-box.nxe-readonly .nxe-datebox-button,nxe-date-box.nxe-disabled .nxe-datebox-button{cursor:default}nxe-date-box.nxe-readonly .nxe-datebox-button:hover,nxe-date-box.nxe-disabled .nxe-datebox-button:hover{background-color:transparent}.nxe-calendar{width:298px;box-sizing:border-box;padding:8px;background:#fff;border:1px solid #ddd;border-radius:6px;box-shadow:0 6px 12px #0000002e;color:#333;font-size:12px;-webkit-user-select:none;user-select:none}.nxe-calendar-navigator{display:flex;height:36px;margin-bottom:8px}.nxe-calendar-nav,.nxe-calendar-caption{display:inline-flex;align-items:center;justify-content:center;border:1px solid #ddd;background:transparent;color:#333;cursor:pointer;padding:0}.nxe-calendar-nav:hover,.nxe-calendar-caption:hover{background:#0000000a}.nxe-calendar-nav{flex:0 0 36px}.nxe-calendar-nav:first-child{border-radius:4px 0 0 4px}.nxe-calendar-nav:last-child{border-radius:0 4px 4px 0}.nxe-calendar-caption{flex:1;margin:0 -1px;font-size:12px;font-weight:600}.nxe-calendar-chevron{width:18px;height:18px;fill:currentColor}.nxe-calendar-weekdays{display:grid;grid-template-columns:repeat(7,1fr);align-items:center;height:24px;text-align:center;color:#333;font-size:10px;font-weight:500}.nxe-calendar-grid{display:grid;grid-template-columns:repeat(7,1fr)}.nxe-calendar-day,.nxe-calendar-period{display:flex;align-items:center;justify-content:center;border:0;background:transparent;cursor:pointer;padding:0;font-size:14px;line-height:1;color:#333}.nxe-calendar-day span,.nxe-calendar-period span{display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;border-radius:4px}.nxe-calendar-day:hover:not(:disabled) span,.nxe-calendar-period:hover:not(:disabled) span{background:#337ab73d}.nxe-calendar-day:disabled,.nxe-calendar-period:disabled{cursor:default;color:#33333361}.nxe-calendar-day{height:33px}.nxe-calendar-day span{width:28px;height:28px}.nxe-calendar-periods{display:grid;grid-template-columns:repeat(4,1fr);grid-auto-rows:74px}.nxe-calendar-period span{min-width:54px;height:54px;padding:0 4px}.nxe-calendar-periods-century .nxe-calendar-period span{width:40px;min-width:0;height:auto;min-height:56px;padding:4px 0;white-space:normal;text-align:center;line-height:1.4}.nxe-calendar-other{color:#33333361}.nxe-calendar-today span{border:2px solid #337ab7}.nxe-calendar-selected span,.nxe-calendar-selected:hover:not(:disabled) span{background:#337ab7;color:#fff;box-shadow:0 0 0 2px #337ab780;border:0}.nxe-calendar-time{display:flex;align-items:center;gap:4px;margin-top:8px;padding-top:8px;border-top:1px solid #ddd}.nxe-calendar-time-part{width:2.6em;height:2.2em;box-sizing:border-box;padding:0 .25em;border:1px solid #ddd;border-radius:4px;background:#fff;color:#333;font-size:inherit;font-variant-numeric:tabular-nums;text-align:center}.nxe-calendar-time-part:focus{outline:2px solid #337ab7;outline-offset:-2px}.nxe-calendar-time-sep{font-weight:600}.nxe-calendar-time-btn{height:2.2em;padding:0 .75em;border:1px solid #ddd;border-radius:4px;background:transparent;color:#333;font-size:inherit;cursor:pointer}.nxe-calendar-time-btn:hover{background:#0000000a}.nxe-calendar-time-btn:focus-visible{outline:2px solid #337ab7;outline-offset:-2px}.nxe-calendar-time-done{margin-left:auto}\n"] }]
1077
- }], propDecorators: { calendarTemplate: [{ type: i0.ViewChild, args: ['calendarTemplate', { isSignal: true }] }], fieldRef: [{ type: i0.ViewChild, args: ['field', { isSignal: true }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], displayFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "displayFormat", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }] } });
1435
+ ], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, styles: ["nxe-date-box .nxe-datebox-button{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-date-box .nxe-datebox-button svg{display:block;width:18px;height:18px;fill:currentColor}nxe-date-box:hover:not(.nxe-disabled) .nxe-datebox-button,nxe-date-box:active:not(.nxe-disabled) .nxe-datebox-button{background-color:#e6e6e6}nxe-date-box .nxe-datebox-button.nxe-datebox-open{background-color:#b3b3b3}nxe-date-box .nxe-datebox-icon{width:18px;height:18px;fill:currentColor}nxe-date-box .nxe-editor-container:has(>.nxe-datebox-button){--nxe-invalid-badge-inset: 38px}nxe-date-box .nxe-editor-container:has(>.nxe-clear-button-area):has(>.nxe-datebox-button){--nxe-invalid-badge-inset: 72px}nxe-date-box.nxe-readonly .nxe-datebox-button,nxe-date-box.nxe-disabled .nxe-datebox-button{cursor:default}nxe-date-box.nxe-readonly .nxe-datebox-button:hover,nxe-date-box.nxe-disabled .nxe-datebox-button:hover{background-color:transparent}.nxe-calendar{width:298px;box-sizing:border-box;padding:8px;background:#fff;border:1px solid #ddd;border-radius:6px;box-shadow:0 6px 12px #0000002e;color:#333;font-size:var(--nxe-ui-font-size, 12px);-webkit-user-select:none;user-select:none}.nxe-calendar-navigator{display:flex;height:36px;margin-bottom:8px}.nxe-calendar-nav,.nxe-calendar-caption{display:inline-flex;align-items:center;justify-content:center;border:1px solid #ddd;background:transparent;color:#333;cursor:pointer;padding:0}.nxe-calendar-nav:hover,.nxe-calendar-caption:hover{background:#0000000a}.nxe-calendar-nav:active,.nxe-calendar-caption:active{background:#0000003d}.nxe-calendar-nav{flex:0 0 36px}.nxe-calendar-nav:first-child{border-radius:4px 0 0 4px}.nxe-calendar-nav:last-child{border-radius:0 4px 4px 0}.nxe-calendar-caption{flex:1;margin:0 -1px;font-size:inherit;font-weight:600}.nxe-calendar-chevron{width:18px;height:18px;fill:currentColor}.nxe-calendar-weekdays{display:grid;grid-template-columns:repeat(7,1fr);align-items:center;height:24px;text-align:center;color:#333;font-size:10px;font-weight:500}.nxe-calendar-grid{display:grid;grid-template-columns:repeat(7,1fr)}.nxe-calendar-day,.nxe-calendar-period{display:flex;align-items:center;justify-content:center;border:0;background:transparent;cursor:pointer;padding:0;font-size:14px;line-height:1;color:#333}.nxe-calendar-day span,.nxe-calendar-period span{display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;border-radius:4px}.nxe-calendar-day:hover:not(:disabled) span,.nxe-calendar-period:hover:not(:disabled) span{background:#337ab73d}.nxe-calendar-day:disabled,.nxe-calendar-period:disabled{cursor:default;color:#33333361}.nxe-calendar-day{height:33px}.nxe-calendar-day span{width:28px;height:28px}.nxe-calendar-periods{display:grid;grid-template-columns:repeat(4,1fr);grid-auto-rows:74px}.nxe-calendar-period span{min-width:54px;height:54px;padding:0 4px}.nxe-calendar-periods-century .nxe-calendar-period span{width:40px;min-width:0;height:auto;min-height:56px;padding:4px 0;white-space:normal;text-align:center;line-height:1.4}.nxe-calendar-other{color:#33333361}.nxe-calendar-today span{border:2px solid #337ab7}.nxe-calendar-selected span,.nxe-calendar-selected:hover:not(:disabled) span{background:#337ab7;color:#fff;box-shadow:0 0 0 2px #337ab780;border:0}.nxe-calendar-time{display:flex;align-items:center;gap:4px;margin-top:8px;padding-top:8px;border-top:1px solid #ddd}.nxe-calendar-time-part{width:2.6em;height:2.2em;box-sizing:border-box;padding:0 .25em;border:1px solid #ddd;border-radius:4px;background:#fff;color:#333;font-size:inherit;font-variant-numeric:tabular-nums;text-align:center}.nxe-calendar-time-part:focus{outline:2px solid #337ab7;outline-offset:-2px}.nxe-calendar-time-sep{font-weight:600}.nxe-calendar-time-btn{height:2.2em;padding:0 .75em;border:1px solid #ddd;border-radius:4px;background:transparent;color:#333;font-size:inherit;cursor:pointer}.nxe-calendar-time-btn:hover{background:#0000000a}.nxe-calendar-time-btn:focus-visible{outline:2px solid #337ab7;outline-offset:-2px}.nxe-calendar-time-done{margin-left:auto}\n"] }]
1436
+ }], propDecorators: { calendarTemplate: [{ type: i0.ViewChild, args: ['calendarTemplate', { isSignal: true }] }], fieldRef: [{ type: i0.ViewChild, args: ['field', { isSignal: true }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], displayFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "displayFormat", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], showClearButtonInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "showClearButton", required: false }] }] } });
1078
1437
 
1079
1438
  /**
1080
1439
  * `nxe-scroll-view` — drop-in replacement for DevExtreme `dx-scroll-view`.
1081
1440
  *
1082
- * In this app `dx-scroll-view` is used purely as a dumb scroll container: no `@ViewChild`, no
1441
+ * In this app `nxe-scroll-view` is used purely as a dumb scroll container: no `@ViewChild`, no
1083
1442
  * `.instance` methods, no events (see survey). Every live tag is the same literal triple
1084
1443
  * `[scrollByContent]="true" [scrollByThumb]="true" [showScrollbar]="'onScroll'"`. So this is a
1085
1444
  * styled `overflow:auto` wrapper that projects its content inside a `.nxe-scrollview-content`
@@ -1096,11 +1455,11 @@ class NxeScrollViewComponent {
1096
1455
  showScrollbar = input('onScroll', /* @ts-ignore */
1097
1456
  ...(ngDevMode ? [{ debugName: "showScrollbar" }] : /* istanbul ignore next */ []));
1098
1457
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeScrollViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1099
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.5", type: NxeScrollViewComponent, isStandalone: true, selector: "nxe-scroll-view", inputs: { scrollByContent: { classPropertyName: "scrollByContent", publicName: "scrollByContent", isSignal: true, isRequired: false, transformFunction: null }, scrollByThumb: { classPropertyName: "scrollByThumb", publicName: "scrollByThumb", isSignal: true, isRequired: false, transformFunction: null }, showScrollbar: { classPropertyName: "showScrollbar", publicName: "showScrollbar", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "nxe-widget nxe-scroll-view" }, ngImport: i0, template: '<div class="nxe-scrollview-content"><ng-content /></div>', isInline: true, styles: ["nxe-scroll-view{display:block;height:100%;overflow-x:hidden;overflow-y:auto;scrollbar-width:thin;scrollbar-color:rgba(191,191,191,.7) transparent}nxe-scroll-view::-webkit-scrollbar{width:8px;height:8px}nxe-scroll-view::-webkit-scrollbar-track{background:transparent}nxe-scroll-view::-webkit-scrollbar-thumb{background-color:#bfbfbfb3;border-radius:4px}nxe-scroll-view:hover::-webkit-scrollbar-thumb{background-color:#bfbfbf}nxe-scroll-view>.nxe-scrollview-content{display:flex;flex-direction:column;min-height:100%}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
1458
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.5", type: NxeScrollViewComponent, isStandalone: true, selector: "nxe-scroll-view", inputs: { scrollByContent: { classPropertyName: "scrollByContent", publicName: "scrollByContent", isSignal: true, isRequired: false, transformFunction: null }, scrollByThumb: { classPropertyName: "scrollByThumb", publicName: "scrollByThumb", isSignal: true, isRequired: false, transformFunction: null }, showScrollbar: { classPropertyName: "showScrollbar", publicName: "showScrollbar", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "nxe-widget nxe-scroll-view" }, ngImport: i0, template: '<div class="nxe-scrollview-content"><ng-content /></div>', isInline: true, styles: ["nxe-scroll-view{display:block;height:100%;overflow-x:hidden;overflow-y:auto;scrollbar-width:thin;scrollbar-color:rgba(191,191,191,.7) transparent}@supports not (scrollbar-width: thin){nxe-scroll-view::-webkit-scrollbar{width:8px;height:8px}nxe-scroll-view::-webkit-scrollbar-track{background:transparent}nxe-scroll-view::-webkit-scrollbar-thumb{background-color:#bfbfbfb3;border-radius:4px}nxe-scroll-view:hover::-webkit-scrollbar-thumb{background-color:#bfbfbf}}nxe-scroll-view>.nxe-scrollview-content{display:flex;flex-direction:column;min-height:100%}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
1100
1459
  }
1101
1460
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeScrollViewComponent, decorators: [{
1102
1461
  type: Component,
1103
- args: [{ selector: 'nxe-scroll-view', standalone: true, template: '<div class="nxe-scrollview-content"><ng-content /></div>', host: { class: 'nxe-widget nxe-scroll-view' }, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, styles: ["nxe-scroll-view{display:block;height:100%;overflow-x:hidden;overflow-y:auto;scrollbar-width:thin;scrollbar-color:rgba(191,191,191,.7) transparent}nxe-scroll-view::-webkit-scrollbar{width:8px;height:8px}nxe-scroll-view::-webkit-scrollbar-track{background:transparent}nxe-scroll-view::-webkit-scrollbar-thumb{background-color:#bfbfbfb3;border-radius:4px}nxe-scroll-view:hover::-webkit-scrollbar-thumb{background-color:#bfbfbf}nxe-scroll-view>.nxe-scrollview-content{display:flex;flex-direction:column;min-height:100%}\n"] }]
1462
+ args: [{ selector: 'nxe-scroll-view', standalone: true, template: '<div class="nxe-scrollview-content"><ng-content /></div>', host: { class: 'nxe-widget nxe-scroll-view' }, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, styles: ["nxe-scroll-view{display:block;height:100%;overflow-x:hidden;overflow-y:auto;scrollbar-width:thin;scrollbar-color:rgba(191,191,191,.7) transparent}@supports not (scrollbar-width: thin){nxe-scroll-view::-webkit-scrollbar{width:8px;height:8px}nxe-scroll-view::-webkit-scrollbar-track{background:transparent}nxe-scroll-view::-webkit-scrollbar-thumb{background-color:#bfbfbfb3;border-radius:4px}nxe-scroll-view:hover::-webkit-scrollbar-thumb{background-color:#bfbfbf}}nxe-scroll-view>.nxe-scrollview-content{display:flex;flex-direction:column;min-height:100%}\n"] }]
1104
1463
  }], propDecorators: { scrollByContent: [{ type: i0.Input, args: [{ isSignal: true, alias: "scrollByContent", required: false }] }], scrollByThumb: [{ type: i0.Input, args: [{ isSignal: true, alias: "scrollByThumb", required: false }] }], showScrollbar: [{ type: i0.Input, args: [{ isSignal: true, alias: "showScrollbar", required: false }] }] } });
1105
1464
 
1106
1465
  // @nexterajs/ui/forms — public API.