@nexterajs/ui 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +16 -0
  3. package/README.md +64 -0
  4. package/fesm2022/nexterajs-ui-animations.mjs +184 -0
  5. package/fesm2022/nexterajs-ui-animations.mjs.map +1 -0
  6. package/fesm2022/nexterajs-ui-core.mjs +326 -0
  7. package/fesm2022/nexterajs-ui-core.mjs.map +1 -0
  8. package/fesm2022/nexterajs-ui-dropdowns.mjs +953 -0
  9. package/fesm2022/nexterajs-ui-dropdowns.mjs.map +1 -0
  10. package/fesm2022/nexterajs-ui-excel.mjs +779 -0
  11. package/fesm2022/nexterajs-ui-excel.mjs.map +1 -0
  12. package/fesm2022/nexterajs-ui-file-uploader.mjs +125 -0
  13. package/fesm2022/nexterajs-ui-file-uploader.mjs.map +1 -0
  14. package/fesm2022/nexterajs-ui-forms.mjs +1113 -0
  15. package/fesm2022/nexterajs-ui-forms.mjs.map +1 -0
  16. package/fesm2022/nexterajs-ui-grid.mjs +4145 -0
  17. package/fesm2022/nexterajs-ui-grid.mjs.map +1 -0
  18. package/fesm2022/nexterajs-ui-intl.mjs +377 -0
  19. package/fesm2022/nexterajs-ui-intl.mjs.map +1 -0
  20. package/fesm2022/nexterajs-ui-pivot.mjs +4739 -0
  21. package/fesm2022/nexterajs-ui-pivot.mjs.map +1 -0
  22. package/fesm2022/nexterajs-ui-styling.mjs +59 -0
  23. package/fesm2022/nexterajs-ui-styling.mjs.map +1 -0
  24. package/fesm2022/nexterajs-ui-tree-view.mjs +202 -0
  25. package/fesm2022/nexterajs-ui-tree-view.mjs.map +1 -0
  26. package/fesm2022/nexterajs-ui-validation.mjs +269 -0
  27. package/fesm2022/nexterajs-ui-validation.mjs.map +1 -0
  28. package/fesm2022/nexterajs-ui.mjs +23 -0
  29. package/fesm2022/nexterajs-ui.mjs.map +1 -0
  30. package/package.json +100 -0
  31. package/styles/_button-directives.scss +39 -0
  32. package/styles/_dropdown.scss +113 -0
  33. package/styles/_editor.scss +146 -0
  34. package/styles/_mixins.scss +2 -0
  35. package/styles/_tokens.scss +77 -0
  36. package/styles/_widget.scss +58 -0
  37. package/styles/index.scss +83 -0
  38. package/types/nexterajs-ui-animations.d.ts +22 -0
  39. package/types/nexterajs-ui-core.d.ts +328 -0
  40. package/types/nexterajs-ui-dropdowns.d.ts +334 -0
  41. package/types/nexterajs-ui-excel.d.ts +159 -0
  42. package/types/nexterajs-ui-file-uploader.d.ts +47 -0
  43. package/types/nexterajs-ui-forms.d.ts +296 -0
  44. package/types/nexterajs-ui-grid.d.ts +1485 -0
  45. package/types/nexterajs-ui-intl.d.ts +125 -0
  46. package/types/nexterajs-ui-pivot.d.ts +1470 -0
  47. package/types/nexterajs-ui-styling.d.ts +40 -0
  48. package/types/nexterajs-ui-tree-view.d.ts +69 -0
  49. package/types/nexterajs-ui-validation.d.ts +102 -0
  50. package/types/nexterajs-ui.d.ts +12 -0
@@ -0,0 +1,1113 @@
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';
4
+ import { NG_VALUE_ACCESSOR } from '@angular/forms';
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
+ import { Overlay } from '@angular/cdk/overlay';
7
+ import { TemplatePortal } from '@angular/cdk/portal';
8
+ import { DateTime } from 'luxon';
9
+ import { merge, filter } from 'rxjs';
10
+ import { nxePane, trackPaneFlip } from '@nexterajs/ui/styling';
11
+
12
+ /**
13
+ * dx size coercion: `height="35"` and `[height]="35"` both mean 35px, while `'100%'` or `'3rem'`
14
+ * pass through untouched. Templates write the attribute form, so the string case is the common one.
15
+ */
16
+ function cssSize(value) {
17
+ if (value === undefined || value === null || value === '') {
18
+ return null;
19
+ }
20
+ return /^-?\d+(\.\d+)?$/.test(String(value)) ? `${value}px` : String(value);
21
+ }
22
+ /**
23
+ * `nxe-button` — drop-in replacement for DevExtreme `dx-button`.
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]`).
30
+ *
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.
33
+ *
34
+ * `onClick` emits {@link NxeButtonClickEvent}; native `(click)` also fires (27 sites bind it).
35
+ */
36
+ class NxeButtonComponent {
37
+ registry = inject(NxeValidationGroupRegistry);
38
+ elementRef = inject(ElementRef);
39
+ /** Legacy `text` label (rendered as `.dx-button-text` when set, no projected content). */
40
+ text = input(/* @ts-ignore */
41
+ ...(ngDevMode ? [undefined, { debugName: "text" }] : /* istanbul ignore next */ []));
42
+ /** Style intent (`default`=blue, `success`=green, `danger`=red, `normal`=white). */
43
+ type = input('normal', /* @ts-ignore */
44
+ ...(ngDevMode ? [{ debugName: "type" }] : /* istanbul ignore next */ []));
45
+ /** Disabled state (`[disabled]`); blocks `onClick` and dims the button. */
46
+ disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
47
+ /** Named validation group resolved via {@link NxeValidationGroupRegistry} on click. */
48
+ validationGroup = input('', /* @ts-ignore */
49
+ ...(ngDevMode ? [{ debugName: "validationGroup" }] : /* istanbul ignore next */ []));
50
+ /** DevExtreme parity input; only ever `false` in this codebase (suppresses tab focus). */
51
+ focusStateEnabled = input(true, { ...(ngDevMode ? { debugName: "focusStateEnabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
52
+ /**
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
55
+ * here (a `mat-icon` plus a `span`), so the two must coexist rather than compete.
56
+ */
57
+ icon = input(undefined, /* @ts-ignore */
58
+ ...(ngDevMode ? [{ debugName: "icon" }] : /* istanbul ignore next */ []));
59
+ /** dx `height` — a bare number (or numeric string) means px; anything else passes through. */
60
+ height = input(undefined, /* @ts-ignore */
61
+ ...(ngDevMode ? [{ debugName: "height" }] : /* istanbul ignore next */ []));
62
+ /** dx `width` — same coercion as {@link height}. */
63
+ width = input(undefined, /* @ts-ignore */
64
+ ...(ngDevMode ? [{ debugName: "width" }] : /* istanbul ignore next */ []));
65
+ heightStyle = computed(() => cssSize(this.height()), /* @ts-ignore */
66
+ ...(ngDevMode ? [{ debugName: "heightStyle" }] : /* istanbul ignore next */ []));
67
+ widthStyle = computed(() => cssSize(this.width()), /* @ts-ignore */
68
+ ...(ngDevMode ? [{ debugName: "widthStyle" }] : /* istanbul ignore next */ []));
69
+ /** DevExtreme-compatible click event. */
70
+ onClick = output();
71
+ handleKeydown(event) {
72
+ // Non-button host: Enter/Space don't synthesize a click, so activate explicitly.
73
+ event.preventDefault();
74
+ this.handleClick(event);
75
+ }
76
+ handleClick(event) {
77
+ if (this.disabled()) {
78
+ event.preventDefault();
79
+ event.stopImmediatePropagation();
80
+ return;
81
+ }
82
+ const group = this.validationGroup() ?? '';
83
+ this.onClick.emit({
84
+ event,
85
+ validationGroup: { validate: () => this.registry.validate(group) },
86
+ component: this,
87
+ element: this.elementRef.nativeElement,
88
+ });
89
+ }
90
+ 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">
93
+ @if (icon()) {
94
+ <i class="dx-icon dx-icon-{{ icon() }}"></i>
95
+ }
96
+ <ng-content />
97
+ @if (text()) {
98
+ <span class="dx-button-text">{{ text() }}</span>
99
+ }
100
+ </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 });
102
+ }
103
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeButtonComponent, decorators: [{
104
+ type: Component,
105
+ args: [{ selector: 'nxe-button', standalone: true, template: `
106
+ <span class="dx-button-content">
107
+ @if (icon()) {
108
+ <i class="dx-icon dx-icon-{{ icon() }}"></i>
109
+ }
110
+ <ng-content />
111
+ @if (text()) {
112
+ <span class="dx-button-text">{{ text() }}</span>
113
+ }
114
+ </span>
115
+ `, host: {
116
+ class: 'nxe-widget',
117
+ role: 'button',
118
+ '[attr.tabindex]': 'disabled() || !focusStateEnabled() ? null : 0',
119
+ '[attr.aria-disabled]': 'disabled() ? true : null',
120
+ '[class.nxe-button-default]': "type() === 'default'",
121
+ '[class.nxe-button-success]': "type() === 'success'",
122
+ '[class.nxe-button-danger]': "type() === 'danger'",
123
+ '[class.nxe-button-has-text]': '!!text()',
124
+ '[class.dx-button-has-text]': '!!text()',
125
+ '[class.dx-button-has-icon]': '!!icon()',
126
+ '[style.height]': 'heightStyle()',
127
+ '[style.width]': 'widthStyle()',
128
+ '[class.nxe-disabled]': 'disabled()',
129
+ '(click)': 'handleClick($event)',
130
+ '(keydown.enter)': 'handleKeydown($event)',
131
+ '(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"] }]
133
+ }], 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
+
135
+ /**
136
+ * `nxe-text-box` — drop-in replacement for DevExtreme `dx-text-box`.
137
+ *
138
+ * A signal-CVA editor (extends {@link NxeFormControlBase}) supporting `formControlName`,
139
+ * `[(ngModel)]`, one-way `[value]`, and `[readOnly]`. Like DevExtreme's default
140
+ * (`valueChangeEvent='change'`), the value commits on **blur** — not on every keystroke.
141
+ *
142
+ * It provides {@link NXE_VALIDATION_TARGET} so a projected `nxe-validator` can read its value
143
+ * and push back the invalid message, which renders as the red `.nxe-invalid-message` bubble.
144
+ */
145
+ class NxeTextBoxComponent extends NxeFormControlBase {
146
+ mode = input('text', /* @ts-ignore */
147
+ ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
148
+ placeholderInput = input(undefined, { ...(ngDevMode ? { debugName: "placeholderInput" } : /* istanbul ignore next */ {}), alias: 'placeholder' });
149
+ placeholder = computed(() => this.placeholderInput() ?? this.nxeDefaults.textBox?.placeholder ?? '', /* @ts-ignore */
150
+ ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
151
+ /** DevExtreme parity input; cosmetic, unused in behaviour. */
152
+ hoverStateEnabledInput = input(undefined, { ...(ngDevMode ? { debugName: "hoverStateEnabledInput" } : /* istanbul ignore next */ {}), alias: 'hoverStateEnabled', transform: nxeOptionalBoolean });
153
+ hoverStateEnabled = computed(() => this.hoverStateEnabledInput() ?? this.nxeDefaults.textBox?.hoverStateEnabled ?? false, /* @ts-ignore */
154
+ ...(ngDevMode ? [{ debugName: "hoverStateEnabled" }] : /* istanbul ignore next */ []));
155
+ focused = signal(false, /* @ts-ignore */
156
+ ...(ngDevMode ? [{ debugName: "focused" }] : /* istanbul ignore next */ []));
157
+ displayValue = computed(() => this.value() ?? '', /* @ts-ignore */
158
+ ...(ngDevMode ? [{ debugName: "displayValue" }] : /* istanbul ignore next */ []));
159
+ onBlur(event) {
160
+ this.commitValue(event.target.value, event);
161
+ this.focused.set(false);
162
+ this.markAsTouched();
163
+ }
164
+ 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: [
166
+ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NxeTextBoxComponent), multi: true },
167
+ { 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
+ }
187
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
188
+ }
189
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeTextBoxComponent, decorators: [{
190
+ type: Component,
191
+ args: [{
192
+ selector: 'nxe-text-box',
193
+ 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
+ }
213
+ `,
214
+ host: {
215
+ class: 'nxe-widget nxe-editor nxe-textbox',
216
+ '[class.nxe-focused]': 'focused()',
217
+ '[class.nxe-invalid]': '!!validationError()',
218
+ },
219
+ providers: [
220
+ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NxeTextBoxComponent), multi: true },
221
+ { provide: NXE_VALIDATION_TARGET, useExisting: forwardRef(() => NxeTextBoxComponent) },
222
+ ],
223
+ changeDetection: ChangeDetectionStrategy.OnPush,
224
+ encapsulation: ViewEncapsulation.None,
225
+ }]
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 }] }] } });
227
+
228
+ /**
229
+ * `nxe-text-area` — drop-in replacement for DevExtreme `dx-text-area`.
230
+ *
231
+ * Identical to {@link NxeTextBoxComponent} but renders a `<textarea>`. Supports an optional fixed
232
+ * `[height]` (px) and `placeholder`; commits on blur. Hosts a projected `nxe-validator` via
233
+ * {@link NXE_VALIDATION_TARGET}.
234
+ */
235
+ class NxeTextAreaComponent extends NxeFormControlBase {
236
+ placeholderInput = input(undefined, { ...(ngDevMode ? { debugName: "placeholderInput" } : /* istanbul ignore next */ {}), alias: 'placeholder' });
237
+ placeholder = computed(() => this.placeholderInput() ?? this.nxeDefaults.textArea?.placeholder ?? '', /* @ts-ignore */
238
+ ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
239
+ /** Fixed height in px (DevExtreme `[height]`); unset = auto. */
240
+ heightInput = input(undefined, { ...(ngDevMode ? { debugName: "heightInput" } : /* istanbul ignore next */ {}), alias: 'height' });
241
+ height = computed(() => this.heightInput() ?? this.nxeDefaults.textArea?.height ?? 80, /* @ts-ignore */
242
+ ...(ngDevMode ? [{ debugName: "height" }] : /* istanbul ignore next */ []));
243
+ focused = signal(false, /* @ts-ignore */
244
+ ...(ngDevMode ? [{ debugName: "focused" }] : /* istanbul ignore next */ []));
245
+ displayValue = computed(() => this.value() ?? '', /* @ts-ignore */
246
+ ...(ngDevMode ? [{ debugName: "displayValue" }] : /* istanbul ignore next */ []));
247
+ onBlur(event) {
248
+ this.commitValue(event.target.value, event);
249
+ this.focused.set(false);
250
+ this.markAsTouched();
251
+ }
252
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeTextAreaComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
253
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", type: NxeTextAreaComponent, isStandalone: true, selector: "nxe-text-area", inputs: { placeholderInput: { classPropertyName: "placeholderInput", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, heightInput: { classPropertyName: "heightInput", publicName: "height", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.nxe-focused": "focused()", "class.nxe-invalid": "!!validationError()" }, classAttribute: "nxe-widget nxe-editor nxe-textarea" }, providers: [
254
+ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NxeTextAreaComponent), multi: true },
255
+ { provide: NXE_VALIDATION_TARGET, useExisting: forwardRef(() => NxeTextAreaComponent) },
256
+ ], usesInheritance: true, ngImport: i0, template: `
257
+ <div class="nxe-editor-container">
258
+ <textarea
259
+ class="nxe-editor-input nxe-textarea-input"
260
+ [value]="displayValue()"
261
+ [readOnly]="readOnly()"
262
+ [disabled]="disabled()"
263
+ [attr.placeholder]="placeholder() || null"
264
+ [style.height.px]="height() || null"
265
+ (blur)="onBlur($event)"
266
+ (focus)="focused.set(true)"></textarea>
267
+ </div>
268
+ <ng-content />
269
+ @if (validationError(); as error) {
270
+ @if (focused()) {
271
+ <div class="nxe-invalid-message">{{ error }}</div>
272
+ }
273
+ }
274
+ `, isInline: true, styles: ["nxe-text-area .nxe-textarea-input{display:block;resize:none;overflow:auto;white-space:pre-wrap;line-height:1.35715}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
275
+ }
276
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeTextAreaComponent, decorators: [{
277
+ type: Component,
278
+ args: [{ selector: 'nxe-text-area', standalone: true, template: `
279
+ <div class="nxe-editor-container">
280
+ <textarea
281
+ class="nxe-editor-input nxe-textarea-input"
282
+ [value]="displayValue()"
283
+ [readOnly]="readOnly()"
284
+ [disabled]="disabled()"
285
+ [attr.placeholder]="placeholder() || null"
286
+ [style.height.px]="height() || null"
287
+ (blur)="onBlur($event)"
288
+ (focus)="focused.set(true)"></textarea>
289
+ </div>
290
+ <ng-content />
291
+ @if (validationError(); as error) {
292
+ @if (focused()) {
293
+ <div class="nxe-invalid-message">{{ error }}</div>
294
+ }
295
+ }
296
+ `, host: {
297
+ class: 'nxe-widget nxe-editor nxe-textarea',
298
+ '[class.nxe-focused]': 'focused()',
299
+ '[class.nxe-invalid]': '!!validationError()',
300
+ }, providers: [
301
+ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NxeTextAreaComponent), multi: true },
302
+ { provide: NXE_VALIDATION_TARGET, useExisting: forwardRef(() => NxeTextAreaComponent) },
303
+ ], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, styles: ["nxe-text-area .nxe-textarea-input{display:block;resize:none;overflow:auto;white-space:pre-wrap;line-height:1.35715}\n"] }]
304
+ }], propDecorators: { placeholderInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], heightInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }] } });
305
+
306
+ /**
307
+ * `nxe-check-box` — drop-in replacement for DevExtreme `dx-check-box`.
308
+ *
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`
313
+ * rule is invalid while unchecked, matching DevExtreme's RequiredRule treating `false` as empty).
314
+ */
315
+ class NxeCheckBoxComponent extends NxeFormControlBase {
316
+ /** Inline label rendered next to the box. */
317
+ text = input(/* @ts-ignore */
318
+ ...(ngDevMode ? [undefined, { debugName: "text" }] : /* istanbul ignore next */ []));
319
+ toggle(event) {
320
+ if (this.disabled() || this.readOnly()) {
321
+ return;
322
+ }
323
+ this.commitValue(!this.value(), event);
324
+ this.markAsTouched();
325
+ }
326
+ onSpace(event) {
327
+ event.preventDefault();
328
+ this.toggle(event);
329
+ }
330
+ 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: [
332
+ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NxeCheckBoxComponent), multi: true },
333
+ { provide: NXE_VALIDATION_TARGET, useExisting: forwardRef(() => NxeCheckBoxComponent) },
334
+ ], usesInheritance: true, ngImport: i0, template: `
335
+ <span class="nxe-checkbox-container">
336
+ <span class="nxe-checkbox-icon" aria-hidden="true"></span>
337
+ @if (text()) {
338
+ <span class="nxe-checkbox-text">{{ text() }}</span>
339
+ }
340
+ </span>
341
+ <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 });
343
+ }
344
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeCheckBoxComponent, decorators: [{
345
+ type: Component,
346
+ args: [{ selector: 'nxe-check-box', standalone: true, template: `
347
+ <span class="nxe-checkbox-container">
348
+ <span class="nxe-checkbox-icon" aria-hidden="true"></span>
349
+ @if (text()) {
350
+ <span class="nxe-checkbox-text">{{ text() }}</span>
351
+ }
352
+ </span>
353
+ <ng-content />
354
+ `, host: {
355
+ class: 'nxe-widget nxe-checkbox',
356
+ role: 'checkbox',
357
+ '[attr.aria-checked]': '!!value()',
358
+ '[attr.aria-disabled]': 'disabled() ? true : null',
359
+ '[attr.aria-readonly]': 'readOnly() ? true : null',
360
+ '[attr.tabindex]': 'disabled() || readOnly() ? null : 0',
361
+ '[class.nxe-checkbox-checked]': '!!value()',
362
+ '[class.nxe-disabled]': 'disabled()',
363
+ '[class.nxe-readonly]': 'readOnly()',
364
+ '[class.nxe-invalid]': '!!validationError()',
365
+ '(click)': 'toggle($event)',
366
+ '(keydown.space)': 'onSpace($event)',
367
+ '(blur)': 'markAsTouched()',
368
+ }, providers: [
369
+ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NxeCheckBoxComponent), multi: true },
370
+ { 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"] }]
372
+ }], propDecorators: { text: [{ type: i0.Input, args: [{ isSignal: true, alias: "text", required: false }] }] } });
373
+
374
+ /** Coerce a static (`min="1"`) or bound (`[min]="0"`) attribute to a number, or undefined. */
375
+ function toNumber(value) {
376
+ if (value == null || value === '') {
377
+ return undefined;
378
+ }
379
+ const num = Number(value);
380
+ return isNaN(num) ? undefined : num;
381
+ }
382
+ /**
383
+ * `nxe-number-box` — drop-in replacement for DevExtreme `dx-number-box`.
384
+ *
385
+ * A numeric signal-CVA editor (extends {@link NxeFormControlBase}) storing/emitting `number | null`.
386
+ * Implements the DevExtreme number-format engine ({@link parseNumberFormat}/{@link formatNumber}):
387
+ * displays the masked, grouped string when blurred and a raw editable number while focused;
388
+ * commits (clamped to `[min,max]`, rounded to the format precision) on blur — never per keystroke.
389
+ *
390
+ * Supports `formControlName`/`[(ngModel)]`/`[(value)]`/`[value]`, `[readOnly]`, `[min]`/`[max]`,
391
+ * `format` (string LDML pattern), `(valueChange)` (numeric), and a projected `nxe-validator`.
392
+ */
393
+ class NxeNumberBoxComponent extends NxeFormControlBase {
394
+ /** LDML format pattern (e.g. `#,##0.00`); empty = raw number. */
395
+ formatInput = input(undefined, { ...(ngDevMode ? { debugName: "formatInput" } : /* istanbul ignore next */ {}), alias: 'format' });
396
+ format = computed(() => this.formatInput() ?? this.nxeDefaults.numberBox?.format ?? '', /* @ts-ignore */
397
+ ...(ngDevMode ? [{ debugName: "format" }] : /* istanbul ignore next */ []));
398
+ min = input(undefined, { ...(ngDevMode ? { debugName: "min" } : /* istanbul ignore next */ {}), transform: toNumber });
399
+ max = input(undefined, { ...(ngDevMode ? { debugName: "max" } : /* istanbul ignore next */ {}), transform: toNumber });
400
+ focused = signal(false, /* @ts-ignore */
401
+ ...(ngDevMode ? [{ debugName: "focused" }] : /* istanbul ignore next */ []));
402
+ parsedFormat = computed(() => parseNumberFormat(this.format()), /* @ts-ignore */
403
+ ...(ngDevMode ? [{ debugName: "parsedFormat" }] : /* istanbul ignore next */ []));
404
+ /** Locale characters, configured once by the application. Defaults to en-US. */
405
+ separators = inject(NXE_NUMBER_SEPARATORS);
406
+ displayValue = computed(() => {
407
+ const value = this.value();
408
+ if (this.focused()) {
409
+ // Raw editable text, but with this locale's decimal character: `String(0.125)` gives
410
+ // a JS-style dot, and parseNumber would read that as a group separator and commit 125.
411
+ return value == null ? '' : String(value).replace('.', this.separators.decimal);
412
+ }
413
+ return formatNumber(value, this.parsedFormat(), this.separators);
414
+ }, /* @ts-ignore */
415
+ ...(ngDevMode ? [{ debugName: "displayValue" }] : /* istanbul ignore next */ []));
416
+ onBlur(event) {
417
+ let num = parseNumber(event.target.value, this.separators);
418
+ if (num != null) {
419
+ const min = this.min();
420
+ const max = this.max();
421
+ if (min != null && num < min) {
422
+ num = min;
423
+ }
424
+ if (max != null && num > max) {
425
+ num = max;
426
+ }
427
+ const fmt = this.parsedFormat();
428
+ if (fmt) {
429
+ num = roundHalfAwayFromZero(num, fmt.maxFrac);
430
+ }
431
+ }
432
+ this.commitValue(num, event);
433
+ this.focused.set(false);
434
+ this.markAsTouched();
435
+ }
436
+ 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: [
438
+ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NxeNumberBoxComponent), multi: true },
439
+ { 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
+ }
459
+ `, isInline: true, styles: ["nxe-number-box.text-right .nxe-numberbox-input{text-align:right}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
460
+ }
461
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeNumberBoxComponent, decorators: [{
462
+ 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
+ }
482
+ `, host: {
483
+ class: 'nxe-widget nxe-editor nxe-numberbox',
484
+ '[class.nxe-focused]': 'focused()',
485
+ '[class.nxe-invalid]': '!!validationError()',
486
+ }, providers: [
487
+ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NxeNumberBoxComponent), multi: true },
488
+ { provide: NXE_VALIDATION_TARGET, useExisting: forwardRef(() => NxeNumberBoxComponent) },
489
+ ], 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 }] }] } });
491
+
492
+ /** Normalize a `[min]`/`[max]` binding (Date object or serialized string) to a DateTime. */
493
+ function toBoundDate(bound) {
494
+ if (!bound) {
495
+ return null;
496
+ }
497
+ if (bound instanceof Date) {
498
+ const dt = DateTime.fromJSDate(bound);
499
+ return dt.isValid ? dt : null;
500
+ }
501
+ return parseDateValue(bound);
502
+ }
503
+ /**
504
+ * `nxe-date-box` — drop-in replacement for DevExtreme `dx-date-box`.
505
+ *
506
+ * A signal-CVA editor (extends {@link NxeFormControlBase}) whose value is the app-wide
507
+ * `"yyyy-MM-ddTHH:mm:ss"` string (preserving the global `dateSerializationFormat`). The field is
508
+ * typeable (parsed on blur per `displayFormat`) and opens a CDK-overlay calendar from the icon
509
+ * button. Built on Luxon. Default `displayFormat` is `dd-MMM-yyyy`. Hosts a projected
510
+ * `nxe-validator` via {@link NXE_VALIDATION_TARGET}.
511
+ */
512
+ class NxeDateBoxComponent extends NxeFormControlBase {
513
+ overlay = inject(Overlay);
514
+ viewContainerRef = inject(ViewContainerRef);
515
+ calendarTemplate = viewChild.required('calendarTemplate', /* @ts-ignore */
516
+ ...(ngDevMode ? [{ debugName: "calendarTemplate" }] : /* istanbul ignore next */ []));
517
+ fieldRef = viewChild.required('field', /* @ts-ignore */
518
+ ...(ngDevMode ? [{ debugName: "fieldRef" }] : /* istanbul ignore next */ []));
519
+ type = input('date', /* @ts-ignore */
520
+ ...(ngDevMode ? [{ debugName: "type" }] : /* istanbul ignore next */ []));
521
+ /* Undefined rather than a literal default, so the default can follow `type`. Additive: no call
522
+ site passes either input, and one that does still wins. */
523
+ displayFormat = input(undefined, /* @ts-ignore */
524
+ ...(ngDevMode ? [{ debugName: "displayFormat" }] : /* istanbul ignore next */ []));
525
+ placeholder = input(undefined, /* @ts-ignore */
526
+ ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
527
+ /** DevExtreme `[min]`/`[max]` bounds: calendar days outside are disabled, typed values clamp. */
528
+ min = input(null, /* @ts-ignore */
529
+ ...(ngDevMode ? [{ debugName: "min" }] : /* istanbul ignore next */ []));
530
+ max = input(null, /* @ts-ignore */
531
+ ...(ngDevMode ? [{ debugName: "max" }] : /* istanbul ignore next */ []));
532
+ focused = signal(false, /* @ts-ignore */
533
+ ...(ngDevMode ? [{ debugName: "focused" }] : /* istanbul ignore next */ []));
534
+ opened = signal(false, /* @ts-ignore */
535
+ ...(ngDevMode ? [{ debugName: "opened" }] : /* istanbul ignore next */ []));
536
+ viewMonth = signal(DateTime.now().startOf('month'), /* @ts-ignore */
537
+ ...(ngDevMode ? [{ debugName: "viewMonth" }] : /* istanbul ignore next */ []));
538
+ /** Calendar zoom level; caption clicks zoom out, cell picks zoom back in (dx-calendar parity). */
539
+ zoomLevel = signal('month', /* @ts-ignore */
540
+ ...(ngDevMode ? [{ debugName: "zoomLevel" }] : /* istanbul ignore next */ []));
541
+ weekdays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
542
+ /** The format actually used, from `displayFormat` or the default for this `type`. */
543
+ effectiveDisplayFormat = computed(() => {
544
+ const stated = this.displayFormat() ?? this.nxeDefaults.dateBox?.displayFormat;
545
+ if (stated) {
546
+ return stated;
547
+ }
548
+ switch (this.type()) {
549
+ case 'time':
550
+ return NXE_DEFAULT_TIME_DISPLAY_FORMAT;
551
+ case 'datetime':
552
+ return NXE_DEFAULT_DATETIME_DISPLAY_FORMAT;
553
+ default:
554
+ return NXE_DEFAULT_DISPLAY_FORMAT;
555
+ }
556
+ }, /* @ts-ignore */
557
+ ...(ngDevMode ? [{ debugName: "effectiveDisplayFormat" }] : /* istanbul ignore next */ []));
558
+ /** dx's default placeholder is '', but orbit's global default is the format itself. */
559
+ effectivePlaceholder = computed(() => this.placeholder() ?? this.nxeDefaults.dateBox?.placeholder ?? this.effectiveDisplayFormat(), /* @ts-ignore */
560
+ ...(ngDevMode ? [{ debugName: "effectivePlaceholder" }] : /* istanbul ignore next */ []));
561
+ selected = computed(() => parseDateValue(this.value()), /* @ts-ignore */
562
+ ...(ngDevMode ? [{ debugName: "selected" }] : /* istanbul ignore next */ []));
563
+ displayValue = computed(() => formatDisplay(this.selected(), this.effectiveDisplayFormat()), /* @ts-ignore */
564
+ ...(ngDevMode ? [{ debugName: "displayValue" }] : /* istanbul ignore next */ []));
565
+ weeks = computed(() => buildCalendarWeeks(this.viewMonth()), /* @ts-ignore */
566
+ ...(ngDevMode ? [{ debugName: "weeks" }] : /* istanbul ignore next */ []));
567
+ /** Two-digit hour/minute for the popup's time strip; blank when nothing is selected. */
568
+ hourText = computed(() => this.selected()?.toFormat('HH') ?? '', /* @ts-ignore */
569
+ ...(ngDevMode ? [{ debugName: "hourText" }] : /* istanbul ignore next */ []));
570
+ minuteText = computed(() => this.selected()?.toFormat('mm') ?? '', /* @ts-ignore */
571
+ ...(ngDevMode ? [{ debugName: "minuteText" }] : /* istanbul ignore next */ []));
572
+ /**
573
+ * The field's text as it stood when focus arrived. `onBlur` compares against it and commits
574
+ * nothing when the user only tabbed through — see the comment there.
575
+ */
576
+ textAtFocus = '';
577
+ caption = computed(() => {
578
+ const view = this.viewMonth();
579
+ switch (this.zoomLevel()) {
580
+ case 'month':
581
+ return view.toFormat('MMMM yyyy');
582
+ case 'year':
583
+ return view.toFormat('yyyy');
584
+ case 'decade': {
585
+ const start = view.year - (view.year % 10);
586
+ return `${start}-${start + 9}`;
587
+ }
588
+ case 'century': {
589
+ const start = view.year - (view.year % 100);
590
+ return `${start}-${start + 99}`;
591
+ }
592
+ }
593
+ }, /* @ts-ignore */
594
+ ...(ngDevMode ? [{ debugName: "caption" }] : /* istanbul ignore next */ []));
595
+ periodCells = computed(() => {
596
+ const zoom = this.zoomLevel();
597
+ if (zoom === 'month') {
598
+ return [];
599
+ }
600
+ const now = DateTime.now();
601
+ const min = this.minDate();
602
+ const max = this.maxDate();
603
+ const span = zoom === 'year' ? { months: 1 } : zoom === 'decade' ? { years: 1 } : { years: 10 };
604
+ return buildPeriodCells(zoom, this.viewMonth()).map((cell) => {
605
+ const end = cell.anchor.plus(span).minus({ days: 1 }).endOf('day');
606
+ return {
607
+ ...cell,
608
+ isNow: now >= cell.anchor && now <= end,
609
+ disabled: (!!min && end < min.startOf('day')) || (!!max && cell.anchor > max.endOf('day')),
610
+ };
611
+ });
612
+ }, /* @ts-ignore */
613
+ ...(ngDevMode ? [{ debugName: "periodCells" }] : /* istanbul ignore next */ []));
614
+ minDate = computed(() => toBoundDate(this.min()), /* @ts-ignore */
615
+ ...(ngDevMode ? [{ debugName: "minDate" }] : /* istanbul ignore next */ []));
616
+ maxDate = computed(() => toBoundDate(this.max()), /* @ts-ignore */
617
+ ...(ngDevMode ? [{ debugName: "maxDate" }] : /* istanbul ignore next */ []));
618
+ isDisabledDay(day) {
619
+ const min = this.minDate();
620
+ const max = this.maxDate();
621
+ return (!!min && day.startOf('day') < min.startOf('day')) || (!!max && day.startOf('day') > max.startOf('day'));
622
+ }
623
+ clampToBounds(dt) {
624
+ const min = this.minDate();
625
+ const max = this.maxDate();
626
+ if (min && dt < min.startOf('day'))
627
+ return min.startOf('day');
628
+ /* Land on the bound's DAY but keep the value's own time. Returning max.startOf('day') would
629
+ silently zero an 18:00 whenever the entry ran past the bound — invisible for a date-only
630
+ field, and a wiped time for a datetime one. */
631
+ if (max && dt > max.endOf('day')) {
632
+ return max.startOf('day').set({ hour: dt.hour, minute: dt.minute, second: dt.second });
633
+ }
634
+ return dt;
635
+ }
636
+ overlayRef;
637
+ closeSub;
638
+ flipSub;
639
+ isSelected(day) {
640
+ const sel = this.selected();
641
+ return !!sel && day.hasSame(sel, 'day');
642
+ }
643
+ isToday(day) {
644
+ return day.hasSame(DateTime.now(), 'day');
645
+ }
646
+ navigate(delta) {
647
+ const months = { month: 1, year: 12, decade: 120, century: 1200 }[this.zoomLevel()];
648
+ this.viewMonth.update((m) => m.plus({ months: months * delta }));
649
+ }
650
+ zoomOut() {
651
+ const next = { month: 'year', year: 'decade', decade: 'century' };
652
+ const zoom = this.zoomLevel();
653
+ if (zoom !== 'century') {
654
+ this.zoomLevel.set(next[zoom]);
655
+ }
656
+ }
657
+ selectPeriod(cell) {
658
+ if (cell.disabled) {
659
+ return;
660
+ }
661
+ const previous = { year: 'month', decade: 'year', century: 'decade' };
662
+ this.viewMonth.set(cell.anchor.startOf('month'));
663
+ this.zoomLevel.set(previous[this.zoomLevel()]);
664
+ }
665
+ onButtonMousedown(event) {
666
+ // Keep field focus (avoid a blur-driven re-commit) and toggle the calendar.
667
+ event.preventDefault();
668
+ if (this.disabled() || this.readOnly()) {
669
+ return;
670
+ }
671
+ this.opened() ? this.close() : this.open();
672
+ }
673
+ onFocus(event) {
674
+ this.textAtFocus = event.target.value;
675
+ this.focused.set(true);
676
+ }
677
+ /**
678
+ * Commit what the user TYPED — and nothing when they typed nothing.
679
+ *
680
+ * The unchanged-text guard is load-bearing, not an optimization. `displayFormat` renders a
681
+ * strict subset of the stored value: `dd-MMM-yyyy` shows `2026-08-12T09:00:00` as `12-Aug-2026`,
682
+ * and re-parsing that yields midnight. This method used to commit unconditionally, so simply
683
+ * focusing and tabbing past a field rewrote 09:00 to 00:00 and 09:30:45 to 09:30:00 — silently,
684
+ * because `commitValue` only no-ops on an identical string and these differ. Latent while
685
+ * nothing rendered a time; straight data loss the moment `type="datetime"` exists.
686
+ *
687
+ * A rejected entry keeps the previous value and repaints the field. The repaint is required:
688
+ * `[value]` is still bound to the same `displayValue()` Angular last wrote, so it will not
689
+ * re-render on its own and the unparseable text would sit there looking accepted.
690
+ */
691
+ onBlur(event) {
692
+ const element = event.target;
693
+ /* Read before clearing: it doubles as "we captured a baseline this focus cycle". A blur with
694
+ no preceding focus cannot happen to a real user — browsers always fire focus first — so
695
+ the only sequence that reaches the fall-through is a synthetic one, where the old
696
+ parse-everything behaviour is both harmless and what the existing specs assert. */
697
+ const hadBaseline = this.focused();
698
+ this.focused.set(false);
699
+ this.markAsTouched();
700
+ if (hadBaseline && element.value === this.textAtFocus) {
701
+ return;
702
+ }
703
+ if (!element.value.trim()) {
704
+ this.commitValue(null, event);
705
+ this.syncField();
706
+ return;
707
+ }
708
+ const dt = parseDisplay(element.value, this.effectiveDisplayFormat(), this.selected());
709
+ if (dt) {
710
+ this.commitValue(serializeDate(this.clampToBounds(dt)), event);
711
+ }
712
+ this.syncField();
713
+ }
714
+ /**
715
+ * Repaint the field from the committed value and re-baseline {@link textAtFocus}.
716
+ *
717
+ * Called after every non-typed commit as well, so a later blur cannot replay stale typed text
718
+ * over a value the popup has since changed.
719
+ */
720
+ syncField() {
721
+ const element = this.fieldRef().nativeElement;
722
+ element.value = this.displayValue();
723
+ this.textAtFocus = element.value;
724
+ }
725
+ selectDay(day) {
726
+ if (this.isDisabledDay(day)) {
727
+ return;
728
+ }
729
+ let dt = day.startOf('day');
730
+ if (this.type() === 'datetime') {
731
+ const base = this.selected();
732
+ if (base) {
733
+ dt = dt.set({ hour: base.hour, minute: base.minute, second: base.second });
734
+ }
735
+ }
736
+ this.commitValue(serializeDate(dt), null);
737
+ this.syncField();
738
+ /* Only a plain date picker is finished at this point. In datetime mode the time strip is
739
+ the other half of the job, and closing here would put it out of reach. */
740
+ if (this.type() === 'date') {
741
+ this.close();
742
+ }
743
+ }
744
+ /**
745
+ * Commit one part of the time from the popup strip.
746
+ *
747
+ * Writes straight through to the value rather than holding a draft: `selectDay` already commits
748
+ * immediately, the component keeps no other pending state, and a stray backdrop click must not
749
+ * discard a time the user has just set. dx buffers behind OK/Cancel; this is the divergence.
750
+ */
751
+ setTimePart(part, event) {
752
+ const element = event.target;
753
+ const typed = Number.parseInt(element.value, 10);
754
+ const base = this.selected() ?? DateTime.now().startOf('day');
755
+ const ceiling = part === 'hour' ? 23 : 59;
756
+ const next = Number.isNaN(typed) ? base[part] : Math.min(Math.max(typed, 0), ceiling);
757
+ this.commitValue(serializeDate(base.set({ [part]: next })), event);
758
+ this.syncField();
759
+ /* Same non-repaint hazard as onBlur: when the clamp lands back on the value already held,
760
+ the [value] binding is unchanged and `7` would stay on screen instead of `07`. */
761
+ element.value = part === 'hour' ? this.hourText() : this.minuteText();
762
+ }
763
+ setNow() {
764
+ const now = DateTime.now();
765
+ const base = this.selected() ?? now;
766
+ this.commitValue(serializeDate(base.set({ hour: now.hour, minute: now.minute, second: 0 })), null);
767
+ this.syncField();
768
+ }
769
+ open() {
770
+ this.viewMonth.set((this.selected() ?? DateTime.now()).startOf('month'));
771
+ this.zoomLevel.set('month');
772
+ if (!this.overlayRef) {
773
+ this.overlayRef = this.overlay.create({
774
+ positionStrategy: this.overlay
775
+ .position()
776
+ .flexibleConnectedTo(this.elementRef)
777
+ .withPositions([
778
+ { originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top' },
779
+ { originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom' },
780
+ ]),
781
+ scrollStrategy: this.overlay.scrollStrategies.reposition(),
782
+ hasBackdrop: true,
783
+ backdropClass: 'cdk-overlay-transparent-backdrop',
784
+ panelClass: nxePane('nxe-datebox-pane'),
785
+ });
786
+ this.flipSub = trackPaneFlip(this.overlayRef);
787
+ }
788
+ if (this.overlayRef.hasAttached()) {
789
+ return;
790
+ }
791
+ this.overlayRef.attach(new TemplatePortal(this.calendarTemplate(), this.viewContainerRef));
792
+ this.closeSub = merge(this.overlayRef.backdropClick(), this.overlayRef.keydownEvents().pipe(filter((e) => e.key === 'Escape'))).subscribe(() => this.close());
793
+ this.opened.set(true);
794
+ }
795
+ /** Protected, not private: the time strip's Done button closes the popup from the template. */
796
+ close() {
797
+ if (!this.overlayRef?.hasAttached()) {
798
+ return;
799
+ }
800
+ this.overlayRef.detach();
801
+ this.closeSub?.unsubscribe();
802
+ this.flipSub?.unsubscribe();
803
+ this.opened.set(false);
804
+ this.markAsTouched();
805
+ }
806
+ ngOnDestroy() {
807
+ this.closeSub?.unsubscribe();
808
+ this.flipSub?.unsubscribe();
809
+ this.overlayRef?.dispose();
810
+ }
811
+ 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: [
813
+ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NxeDateBoxComponent), multi: true },
814
+ { 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 });
941
+ }
942
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeDateBoxComponent, decorators: [{
943
+ 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>
1069
+ `, host: {
1070
+ class: 'nxe-widget nxe-editor nxe-datebox',
1071
+ '[class.nxe-focused]': 'focused()',
1072
+ '[class.nxe-invalid]': '!!validationError()',
1073
+ }, providers: [
1074
+ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NxeDateBoxComponent), multi: true },
1075
+ { 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 }] }] } });
1078
+
1079
+ /**
1080
+ * `nxe-scroll-view` — drop-in replacement for DevExtreme `dx-scroll-view`.
1081
+ *
1082
+ * In this app `dx-scroll-view` is used purely as a dumb scroll container: no `@ViewChild`, no
1083
+ * `.instance` methods, no events (see survey). Every live tag is the same literal triple
1084
+ * `[scrollByContent]="true" [scrollByThumb]="true" [showScrollbar]="'onScroll'"`. So this is a
1085
+ * styled `overflow:auto` wrapper that projects its content inside a `.nxe-scrollview-content`
1086
+ * flex-column box (matching dx's content layout, which some consumers' inner grids rely on for
1087
+ * height) and renders a thin grey dx-style scrollbar. The three inputs are accepted for binding
1088
+ * parity only — behaviour is native scrolling.
1089
+ */
1090
+ class NxeScrollViewComponent {
1091
+ /** Accepted for parity with dx-scroll-view; behaviour is native overflow scrolling. */
1092
+ scrollByContent = input(true, /* @ts-ignore */
1093
+ ...(ngDevMode ? [{ debugName: "scrollByContent" }] : /* istanbul ignore next */ []));
1094
+ scrollByThumb = input(true, /* @ts-ignore */
1095
+ ...(ngDevMode ? [{ debugName: "scrollByThumb" }] : /* istanbul ignore next */ []));
1096
+ showScrollbar = input('onScroll', /* @ts-ignore */
1097
+ ...(ngDevMode ? [{ debugName: "showScrollbar" }] : /* istanbul ignore next */ []));
1098
+ 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 });
1100
+ }
1101
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeScrollViewComponent, decorators: [{
1102
+ 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"] }]
1104
+ }], 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
+
1106
+ // @nexterajs/ui/forms — public API.
1107
+
1108
+ /**
1109
+ * Generated bundle index. Do not edit.
1110
+ */
1111
+
1112
+ export { NxeButtonComponent, NxeCheckBoxComponent, NxeDateBoxComponent, NxeNumberBoxComponent, NxeScrollViewComponent, NxeTextAreaComponent, NxeTextBoxComponent };
1113
+ //# sourceMappingURL=nexterajs-ui-forms.mjs.map