@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.
- package/LICENSE +202 -0
- package/NOTICE +16 -0
- package/README.md +64 -0
- package/fesm2022/nexterajs-ui-animations.mjs +184 -0
- package/fesm2022/nexterajs-ui-animations.mjs.map +1 -0
- package/fesm2022/nexterajs-ui-core.mjs +326 -0
- package/fesm2022/nexterajs-ui-core.mjs.map +1 -0
- package/fesm2022/nexterajs-ui-dropdowns.mjs +953 -0
- package/fesm2022/nexterajs-ui-dropdowns.mjs.map +1 -0
- package/fesm2022/nexterajs-ui-excel.mjs +779 -0
- package/fesm2022/nexterajs-ui-excel.mjs.map +1 -0
- package/fesm2022/nexterajs-ui-file-uploader.mjs +125 -0
- package/fesm2022/nexterajs-ui-file-uploader.mjs.map +1 -0
- package/fesm2022/nexterajs-ui-forms.mjs +1113 -0
- package/fesm2022/nexterajs-ui-forms.mjs.map +1 -0
- package/fesm2022/nexterajs-ui-grid.mjs +4145 -0
- package/fesm2022/nexterajs-ui-grid.mjs.map +1 -0
- package/fesm2022/nexterajs-ui-intl.mjs +377 -0
- package/fesm2022/nexterajs-ui-intl.mjs.map +1 -0
- package/fesm2022/nexterajs-ui-pivot.mjs +4739 -0
- package/fesm2022/nexterajs-ui-pivot.mjs.map +1 -0
- package/fesm2022/nexterajs-ui-styling.mjs +59 -0
- package/fesm2022/nexterajs-ui-styling.mjs.map +1 -0
- package/fesm2022/nexterajs-ui-tree-view.mjs +202 -0
- package/fesm2022/nexterajs-ui-tree-view.mjs.map +1 -0
- package/fesm2022/nexterajs-ui-validation.mjs +269 -0
- package/fesm2022/nexterajs-ui-validation.mjs.map +1 -0
- package/fesm2022/nexterajs-ui.mjs +23 -0
- package/fesm2022/nexterajs-ui.mjs.map +1 -0
- package/package.json +100 -0
- package/styles/_button-directives.scss +39 -0
- package/styles/_dropdown.scss +113 -0
- package/styles/_editor.scss +146 -0
- package/styles/_mixins.scss +2 -0
- package/styles/_tokens.scss +77 -0
- package/styles/_widget.scss +58 -0
- package/styles/index.scss +83 -0
- package/types/nexterajs-ui-animations.d.ts +22 -0
- package/types/nexterajs-ui-core.d.ts +328 -0
- package/types/nexterajs-ui-dropdowns.d.ts +334 -0
- package/types/nexterajs-ui-excel.d.ts +159 -0
- package/types/nexterajs-ui-file-uploader.d.ts +47 -0
- package/types/nexterajs-ui-forms.d.ts +296 -0
- package/types/nexterajs-ui-grid.d.ts +1485 -0
- package/types/nexterajs-ui-intl.d.ts +125 -0
- package/types/nexterajs-ui-pivot.d.ts +1470 -0
- package/types/nexterajs-ui-styling.d.ts +40 -0
- package/types/nexterajs-ui-tree-view.d.ts +69 -0
- package/types/nexterajs-ui-validation.d.ts +102 -0
- 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
|