@nexterajs/ui 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/fesm2022/nexterajs-ui-core.mjs +189 -5
  2. package/fesm2022/nexterajs-ui-core.mjs.map +1 -1
  3. package/fesm2022/nexterajs-ui-dropdowns.mjs +314 -50
  4. package/fesm2022/nexterajs-ui-dropdowns.mjs.map +1 -1
  5. package/fesm2022/nexterajs-ui-file-uploader.mjs +6 -3
  6. package/fesm2022/nexterajs-ui-file-uploader.mjs.map +1 -1
  7. package/fesm2022/nexterajs-ui-forms.mjs +729 -370
  8. package/fesm2022/nexterajs-ui-forms.mjs.map +1 -1
  9. package/fesm2022/nexterajs-ui-grid.mjs +1188 -208
  10. package/fesm2022/nexterajs-ui-grid.mjs.map +1 -1
  11. package/fesm2022/nexterajs-ui-intl.mjs +15 -5
  12. package/fesm2022/nexterajs-ui-intl.mjs.map +1 -1
  13. package/fesm2022/nexterajs-ui-notify.mjs +351 -0
  14. package/fesm2022/nexterajs-ui-notify.mjs.map +1 -0
  15. package/fesm2022/nexterajs-ui-pivot.mjs +146 -116
  16. package/fesm2022/nexterajs-ui-pivot.mjs.map +1 -1
  17. package/fesm2022/nexterajs-ui-styling.mjs +568 -3
  18. package/fesm2022/nexterajs-ui-styling.mjs.map +1 -1
  19. package/fesm2022/nexterajs-ui-tree-view.mjs +147 -18
  20. package/fesm2022/nexterajs-ui-tree-view.mjs.map +1 -1
  21. package/fesm2022/nexterajs-ui-validation.mjs +247 -92
  22. package/fesm2022/nexterajs-ui-validation.mjs.map +1 -1
  23. package/fesm2022/nexterajs-ui.mjs +1 -0
  24. package/fesm2022/nexterajs-ui.mjs.map +1 -1
  25. package/package.json +5 -1
  26. package/styles/_dropdown.scss +149 -0
  27. package/styles/_editor.scss +23 -4
  28. package/styles/_icons.scss +92 -0
  29. package/styles/_mixins.scss +104 -1
  30. package/styles/_scrollbar.scss +134 -0
  31. package/styles/_toast.scss +243 -0
  32. package/styles/_tokens.scss +48 -0
  33. package/styles/index.scss +4 -0
  34. package/types/nexterajs-ui-core.d.ts +142 -22
  35. package/types/nexterajs-ui-dropdowns.d.ts +145 -27
  36. package/types/nexterajs-ui-file-uploader.d.ts +4 -1
  37. package/types/nexterajs-ui-forms.d.ts +103 -19
  38. package/types/nexterajs-ui-grid.d.ts +445 -57
  39. package/types/nexterajs-ui-notify.d.ts +147 -0
  40. package/types/nexterajs-ui-pivot.d.ts +35 -22
  41. package/types/nexterajs-ui-styling.d.ts +163 -3
  42. package/types/nexterajs-ui-tree-view.d.ts +50 -1
  43. package/types/nexterajs-ui-validation.d.ts +94 -15
  44. package/types/nexterajs-ui.d.ts +1 -0
@@ -0,0 +1,243 @@
1
+ // @nexterajs/ui — NxeToastService chrome.
2
+ //
3
+ // A value-for-value port of ngx-toastr's `toastr.css`, which this replaces. Every number, colour
4
+ // and easing here is copied deliberately rather than chosen: applications were pinned to that
5
+ // rendering, and "close enough" on a toast is a visible regression nothing tests.
6
+ //
7
+ // Load this where `ngx-toastr/toastr.css` used to sit in the cascade — BEFORE the application's
8
+ // own overrides — or a rule like `width: 350px` on a position class stops winning.
9
+
10
+ /* position */
11
+ .nxe-toast-center-center {
12
+ top: 50%;
13
+ left: 50%;
14
+ transform: translate(-50%, -50%);
15
+ }
16
+
17
+ .toast-top-center {
18
+ top: 0;
19
+ right: 0;
20
+ width: 100%;
21
+ }
22
+
23
+ .toast-bottom-center {
24
+ bottom: 0;
25
+ right: 0;
26
+ width: 100%;
27
+ }
28
+
29
+ .toast-top-full-width {
30
+ top: 0;
31
+ right: 0;
32
+ width: 100%;
33
+ }
34
+
35
+ .toast-bottom-full-width {
36
+ bottom: 0;
37
+ right: 0;
38
+ width: 100%;
39
+ }
40
+
41
+ .toast-top-left {
42
+ top: 12px;
43
+ left: 12px;
44
+ }
45
+
46
+ .toast-top-right {
47
+ top: 12px;
48
+ right: 12px;
49
+ }
50
+
51
+ .toast-bottom-right {
52
+ right: 12px;
53
+ bottom: 12px;
54
+ }
55
+
56
+ .toast-bottom-left {
57
+ bottom: 12px;
58
+ left: 12px;
59
+ }
60
+
61
+ /* toast styles */
62
+ .nxe-toast-title {
63
+ font-weight: bold;
64
+ }
65
+
66
+ .nxe-toast-message {
67
+ word-wrap: break-word;
68
+ }
69
+
70
+ .nxe-toast-message a,
71
+ .nxe-toast-message label {
72
+ color: #ffffff;
73
+ }
74
+
75
+ .nxe-toast-message a:hover {
76
+ color: #cccccc;
77
+ text-decoration: none;
78
+ }
79
+
80
+ .nxe-toast-close {
81
+ position: relative;
82
+ right: -0.3em;
83
+ top: -0.3em;
84
+ float: right;
85
+ font-size: 20px;
86
+ font-weight: bold;
87
+ color: #ffffff;
88
+ text-shadow: 0 1px 0 #ffffff;
89
+ }
90
+
91
+ .nxe-toast-close:hover,
92
+ .nxe-toast-close:focus {
93
+ color: #000000;
94
+ text-decoration: none;
95
+ cursor: pointer;
96
+ opacity: 0.4;
97
+ }
98
+
99
+ button.nxe-toast-close {
100
+ padding: 0;
101
+ cursor: pointer;
102
+ background: transparent;
103
+ border: 0;
104
+ }
105
+
106
+ .nxe-toast-container {
107
+ pointer-events: none;
108
+ position: fixed;
109
+ z-index: 999999;
110
+ }
111
+
112
+ .nxe-toast-container * {
113
+ box-sizing: border-box;
114
+ }
115
+
116
+ .nxe-toast-container .nxe-toast {
117
+ position: relative;
118
+ overflow: hidden;
119
+ margin: 0 0 6px;
120
+ padding: 15px 15px 15px 50px;
121
+ width: 300px;
122
+ border-radius: 3px 3px 3px 3px;
123
+ background-position: 15px center;
124
+ background-repeat: no-repeat;
125
+ background-size: 24px;
126
+ box-shadow: 0 0 12px #999999;
127
+ color: #ffffff;
128
+ }
129
+
130
+ .nxe-toast-container .nxe-toast:hover {
131
+ box-shadow: 0 0 12px #000000;
132
+ opacity: 1;
133
+ cursor: pointer;
134
+ }
135
+
136
+ /* The four icons, as shipped: Font Awesome info-circle, times-circle, check and
137
+ exclamation-triangle, inlined so the toast needs no icon font and no network request. */
138
+ .nxe-toast-info {
139
+ background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCA1MTIgNTEyJyB3aWR0aD0nNTEyJyBoZWlnaHQ9JzUxMic+PHBhdGggZmlsbD0ncmdiKDI1NSwyNTUsMjU1KScgZD0nTTI1NiA4QzExOS4wNDMgOCA4IDExOS4wODMgOCAyNTZjMCAxMzYuOTk3IDExMS4wNDMgMjQ4IDI0OCAyNDhzMjQ4LTExMS4wMDMgMjQ4LTI0OEM1MDQgMTE5LjA4MyAzOTIuOTU3IDggMjU2IDh6bTAgMTEwYzIzLjE5NiAwIDQyIDE4LjgwNCA0MiA0MnMtMTguODA0IDQyLTQyIDQyLTQyLTE4LjgwNC00Mi00MiAxOC44MDQtNDIgNDItNDJ6bTU2IDI1NGMwIDYuNjI3LTUuMzczIDEyLTEyIDEyaC04OGMtNi42MjcgMC0xMi01LjM3My0xMi0xMnYtMjRjMC02LjYyNyA1LjM3My0xMiAxMi0xMmgxMnYtNjRoLTEyYy02LjYyNyAwLTEyLTUuMzczLTEyLTEydi0yNGMwLTYuNjI3IDUuMzczLTEyIDEyLTEyaDY0YzYuNjI3IDAgMTIgNS4zNzMgMTIgMTJ2MTAwaDEyYzYuNjI3IDAgMTIgNS4zNzMgMTIgMTJ2MjR6Jy8+PC9zdmc+");
140
+ }
141
+
142
+ .nxe-toast-error {
143
+ background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCA1MTIgNTEyJyB3aWR0aD0nNTEyJyBoZWlnaHQ9JzUxMic+PHBhdGggZmlsbD0ncmdiKDI1NSwyNTUsMjU1KScgZD0nTTI1NiA4QzExOSA4IDggMTE5IDggMjU2czExMSAyNDggMjQ4IDI0OCAyNDgtMTExIDI0OC0yNDhTMzkzIDggMjU2IDh6bTEyMS42IDMxMy4xYzQuNyA0LjcgNC43IDEyLjMgMCAxN0wzMzggMzc3LjZjLTQuNyA0LjctMTIuMyA0LjctMTcgMEwyNTYgMzEybC02NS4xIDY1LjZjLTQuNyA0LjctMTIuMyA0LjctMTcgMEwxMzQuNCAzMzhjLTQuNy00LjctNC43LTEyLjMgMC0xN2w2NS42LTY1LTY1LjYtNjUuMWMtNC43LTQuNy00LjctMTIuMyAwLTE3bDM5LjYtMzkuNmM0LjctNC43IDEyLjMtNC43IDE3IDBsNjUgNjUuNyA2NS4xLTY1LjZjNC43LTQuNyAxMi4zLTQuNyAxNyAwbDM5LjYgMzkuNmM0LjcgNC43IDQuNyAxMi4zIDAgMTdMMzEyIDI1Nmw2NS42IDY1LjF6Jy8+PC9zdmc+");
144
+ }
145
+
146
+ .nxe-toast-success {
147
+ background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCA1MTIgNTEyJyB3aWR0aD0nNTEyJyBoZWlnaHQ9JzUxMic+PHBhdGggZmlsbD0ncmdiKDI1NSwyNTUsMjU1KScgZD0nTTE3My44OTggNDM5LjQwNGwtMTY2LjQtMTY2LjRjLTkuOTk3LTkuOTk3LTkuOTk3LTI2LjIwNiAwLTM2LjIwNGwzNi4yMDMtMzYuMjA0YzkuOTk3LTkuOTk4IDI2LjIwNy05Ljk5OCAzNi4yMDQgMEwxOTIgMzEyLjY5IDQzMi4wOTUgNzIuNTk2YzkuOTk3LTkuOTk3IDI2LjIwNy05Ljk5NyAzNi4yMDQgMGwzNi4yMDMgMzYuMjA0YzkuOTk3IDkuOTk3IDkuOTk3IDI2LjIwNiAwIDM2LjIwNGwtMjk0LjQgMjk0LjQwMWMtOS45OTggOS45OTctMjYuMjA3IDkuOTk3LTM2LjIwNC0uMDAxeicvPjwvc3ZnPg==");
148
+ }
149
+
150
+ .nxe-toast-warning {
151
+ background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCA1NzYgNTEyJyB3aWR0aD0nNTc2JyBoZWlnaHQ9JzUxMic+PHBhdGggZmlsbD0ncmdiKDI1NSwyNTUsMjU1KScgZD0nTTU2OS41MTcgNDQwLjAxM0M1ODcuOTc1IDQ3Mi4wMDcgNTY0LjgwNiA1MTIgNTI3Ljk0IDUxMkg0OC4wNTRjLTM2LjkzNyAwLTU5Ljk5OS00MC4wNTUtNDEuNTc3LTcxLjk4N0wyNDYuNDIzIDIzLjk4NWMxOC40NjctMzIuMDA5IDY0LjcyLTMxLjk1MSA4My4xNTQgMGwyMzkuOTQgNDE2LjAyOHpNMjg4IDM1NGMtMjUuNDA1IDAtNDYgMjAuNTk1LTQ2IDQ2czIwLjU5NSA0NiA0NiA0NiA0Ni0yMC41OTUgNDYtNDYtMjAuNTk1LTQ2LTQ2LTQ2em0tNDMuNjczLTE2NS4zNDZsNy40MTggMTM2Yy4zNDcgNi4zNjQgNS42MDkgMTEuMzQ2IDExLjk4MiAxMS4zNDZoNDguNTQ2YzYuMzczIDAgMTEuNjM1LTQuOTgyIDExLjk4Mi0xMS4zNDZsNy40MTgtMTM2Yy4zNzUtNi44NzQtNS4wOTgtMTIuNjU0LTExLjk4Mi0xMi42NTRoLTYzLjM4M2MtNi44ODQgMC0xMi4zNTYgNS43OC0xMS45ODEgMTIuNjU0eicvPjwvc3ZnPg==");
152
+ }
153
+
154
+ .nxe-toast-container.toast-top-center .nxe-toast,
155
+ .nxe-toast-container.toast-bottom-center .nxe-toast {
156
+ width: 300px;
157
+ margin-left: auto;
158
+ margin-right: auto;
159
+ }
160
+
161
+ .nxe-toast-container.toast-top-full-width .nxe-toast,
162
+ .nxe-toast-container.toast-bottom-full-width .nxe-toast {
163
+ width: 96%;
164
+ margin-left: auto;
165
+ margin-right: auto;
166
+ }
167
+
168
+ .nxe-toast {
169
+ // Required for parity, not a change to it. The original toast was a <div>; this one is a
170
+ // custom element, and an unknown element is `display: inline` by default — which would make
171
+ // the `width`, `padding` and `margin` above do nothing at all, silently.
172
+ display: block;
173
+
174
+ background-color: #030303;
175
+ pointer-events: auto;
176
+
177
+ // ngx-toastr animated between `inactive`/`active`/`removed` states that differ only in
178
+ // opacity, over 300ms ease-in. No movement — matching that exactly, not improving on it.
179
+ opacity: 1;
180
+ transition: opacity 300ms ease-in;
181
+ }
182
+
183
+ .nxe-toast-entering,
184
+ .nxe-toast-leaving {
185
+ opacity: 0;
186
+ }
187
+
188
+ .nxe-toast-success {
189
+ background-color: #51a351;
190
+ }
191
+
192
+ .nxe-toast-error {
193
+ background-color: #bd362f;
194
+ }
195
+
196
+ .nxe-toast-info {
197
+ background-color: #2f96b4;
198
+ }
199
+
200
+ .nxe-toast-warning {
201
+ background-color: #f89406;
202
+ }
203
+
204
+ .nxe-toast-progress {
205
+ position: absolute;
206
+ left: 0;
207
+ bottom: 0;
208
+ height: 4px;
209
+ background-color: #000000;
210
+ opacity: 0.4;
211
+ }
212
+
213
+ /* Responsive Design */
214
+ @media all and (max-width: 240px) {
215
+ .nxe-toast-container .nxe-toast.div {
216
+ padding: 8px 8px 8px 50px;
217
+ width: 11em;
218
+ }
219
+
220
+ .nxe-toast-container .nxe-toast-close {
221
+ right: -0.2em;
222
+ top: -0.2em;
223
+ }
224
+ }
225
+
226
+ @media all and (min-width: 241px) and (max-width: 480px) {
227
+ .nxe-toast-container .nxe-toast.div {
228
+ padding: 8px 8px 8px 50px;
229
+ width: 18em;
230
+ }
231
+
232
+ .nxe-toast-container .nxe-toast-close {
233
+ right: -0.2em;
234
+ top: -0.2em;
235
+ }
236
+ }
237
+
238
+ @media all and (min-width: 481px) and (max-width: 768px) {
239
+ .nxe-toast-container .nxe-toast.div {
240
+ padding: 15px 15px 15px 50px;
241
+ width: 25em;
242
+ }
243
+ }
@@ -66,6 +66,54 @@
66
66
  // carried the dx-era `.dx-overlay-content { min-width: 400px !important }` here until
67
67
  // 2026-07-29 and dropped it — see nxe-ui/DEFAULTS.md.
68
68
  --nxe-ui-overlay-min-width: 0px;
69
+ // Header-filter popup floor. The popup is dx's own 252x325; broker's global
70
+ // `.dx-overlay-content{min-width:400px}` made it 400px wide in the dx era, which an app can
71
+ // restore here without widening every dropdown panel.
72
+ --nxe-ui-header-filter-min-width: 0px;
73
+ // Field-chooser popup floor. dx's own field chooser popup has no 400px floor; broker's global
74
+ // `.dx-overlay-content{min-width:400px}` made it 400px wide in the dx era, which an app can
75
+ // restore here without widening every dropdown panel.
76
+ --nxe-ui-fieldchooser-min-width: 0px;
77
+
78
+ // Check box hover border - dx `.dx-checkbox.dx-state-hover .dx-checkbox-icon{border-color:#265a87}`.
79
+ --nxe-ui-checkbox-hover-border: #265a87;
80
+
81
+ // Gap between a button's projected children. dx `.dx-button-content` has none; an app whose
82
+ // icon + text buttons relied on the library's former 4px can set it back.
83
+ --nxe-ui-button-gap: 0px;
84
+
85
+ // Data grid empty state — dx `.dx-datagrid-rowsview.dx-empty{height:100px}` and
86
+ // `.dx-datagrid-nodata{font-size:17px}`.
87
+ --nxe-ui-grid-empty-height: 100px;
88
+ --nxe-ui-grid-nodata-font-size: 17px;
89
+
90
+ // Data grid and tree list lines - dx.light's values. Whether a grid draws them is per grid
91
+ // ([showRowLines], [showColumnLines], [showBorders], [rowAlternationEnabled]) or app-wide
92
+ // (NXE_DEFAULT_OPTIONS.dataGrid / .treeList); these set how they look. Override them at :root
93
+ // for the whole app, or on one grid, e.g.
94
+ // nxe-data-grid.quotes { --nxe-ui-grid-column-line-color: #e2e8f0; }
95
+ // The grid's rules outweigh dx-compat's #ddd copies, so an override holds on selected, edited
96
+ // and group rows too. Literals, not var(--nxe-ui-border): a var() here resolves at :root, and a
97
+ // subtree override of --nxe-ui-border would never reach them.
98
+ --nxe-ui-grid-line-width: 1px; // every grid line and the showBorders box
99
+ --nxe-ui-grid-row-line-color: #ddd; // `.dx-row-lines>td{border-bottom}`, group rows and footers
100
+ --nxe-ui-grid-column-line-color: #ddd; // `.dx-column-lines>td{border-left;border-right}`
101
+ --nxe-ui-grid-border-color: #ddd; // showBorders: `.dx-datagrid-borders>.dx-datagrid-rowsview`,
102
+ // and the pager's top line (`.dx-pager{border-top}`), which
103
+ // lies on the box's bottom edge in a bordered grid
104
+ --nxe-ui-grid-header-line-color: #ddd; // `.dx-datagrid-headers .dx-datagrid-table .dx-row>td`
105
+ --nxe-ui-grid-alt-row-bg: #f5f5f5; // rowAlternationEnabled: `.dx-row-alt>td`
106
+
107
+ // Data grid group rows and expand cells - dx.light's values.
108
+ // group-row-color: `.dx-datagrid-rowsview .dx-row.dx-group-row:not(.dx-row-focused){color:#222}`
109
+ // summary-color: `.dx-datagrid-summary-item{color:rgba(51,51,51,.7)}` (aligned group sums)
110
+ // expand-offset: `.dx-command-expand.dx-datagrid-group-space{padding-top:7px}` - how far down
111
+ // the cell the 18px group / master-detail toggle starts. dx leaves it about
112
+ // 3.3px below the row's middle; an app that wants it centred sets
113
+ // (row height - 18px) / 2 here.
114
+ --nxe-ui-grid-group-row-color: #222;
115
+ --nxe-ui-grid-summary-color: rgba(51, 51, 51, 0.7);
116
+ --nxe-ui-grid-expand-offset: 7px;
69
117
 
70
118
  // foreground.divider — slate.200
71
119
  --nxe-divider-color: #e2e8f0;
package/styles/index.scss CHANGED
@@ -10,6 +10,7 @@
10
10
  @use 'editor';
11
11
  @use 'dropdown';
12
12
  @use 'button-directives';
13
+ @use 'icons';
13
14
 
14
15
  // The widget base (font family/size/weight/line-height). Must come before the component chrome
15
16
  // below so those can override it, and it is what carries the contract across a CDK portal.
@@ -23,6 +24,9 @@
23
24
  // Shared select-family dropdown panel (select-box, tag-box).
24
25
  @include dropdown.nxe-dropdown;
25
26
 
27
+ // The `<i class="nxe-icon nxe-icon-NAME">` glyph layer (nxe-button's `icon`).
28
+ @include icons.nxe-icons;
29
+
26
30
  // Pointer-drag chrome for NxeDragGesture, shared by the data grid's column/group drags and the
27
31
  // pivot's field-panel/chooser chip drags. Global because a page can render a pivot and NO data
28
32
  // grid (an ANALYTIC report is exactly that), and component styles only ship while an instance of
@@ -22,6 +22,17 @@ interface NxeValueChangedEvent<T> {
22
22
  /** The component host element (DevExtreme `element`). */
23
23
  element: HTMLElement;
24
24
  }
25
+ /** Which native event an editor is re-emitting in dx's shape. */
26
+ type NxeEditorEventKind = 'keydown' | 'keyup' | 'input' | 'change' | 'focusin' | 'focusout';
27
+ /**
28
+ * dx's payload for the keyboard, focus and input events: the native event plus the widget and
29
+ * its host element, which is what handlers written against DevExtreme expect to receive.
30
+ */
31
+ interface NxeEditorEvent {
32
+ event: Event;
33
+ component: unknown;
34
+ element: HTMLElement;
35
+ }
25
36
 
26
37
  /** A single failed validation rule. */
27
38
  interface NxeBrokenRule {
@@ -47,35 +58,40 @@ interface NxeValidationTarget {
47
58
  getValue(): unknown;
48
59
  validationGroup(): string | undefined;
49
60
  setValidationError(message: string | null): void;
61
+ /**
62
+ * Tell the editor whether a `required` rule applies, so it can render `aria-required`.
63
+ * Optional: a target predating this simply does not advertise the requirement.
64
+ */
65
+ setRequired?(required: boolean): void;
50
66
  }
51
67
  /** DI token the editor provides so its nested `nxe-validator` can adapt it. */
52
68
  declare const NXE_VALIDATION_TARGET: InjectionToken<NxeValidationTarget>;
53
69
  /** A member registered in a validation group (an `nxe-validator`). */
54
70
  interface NxeValidationGroupMember {
55
71
  validate(): NxeValidationResult;
72
+ /** Clear the member's error state without validating. Optional for older members. */
73
+ reset?(): void;
56
74
  }
57
-
58
75
  /**
59
- * Signal-based `ControlValueAccessor` base for every nxe form editor
60
- * (`nxe-text-box`, `nxe-number-box`, `nxe-date-box`, `nxe-check-box`, `nxe-text-area`,
61
- * `nxe-select-box`, `nxe-tag-box`, `nxe-radio`, ...).
76
+ * An `nxe-validation-group` ancestor, exposed so a validator or a button inside it can join it
77
+ * without naming a group.
62
78
  *
63
- * It supports BOTH template-driven (`[(ngModel)]`) and reactive (`formControlName`) forms
64
- * transparently, as well as DevExtreme-style `[value]` / `[(value)]` two-way binding, and
65
- * emits the DevExtreme-shaped {@link NxeValueChangedEvent} from `onValueChanged`.
66
- *
67
- * Concrete components MUST register themselves as the value accessor, e.g.:
68
- * ```ts
69
- * @Component({
70
- * selector: 'nxe-text-box',
71
- * providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NxeTextBoxComponent), multi: true }],
72
- * ...
73
- * })
74
- * export class NxeTextBoxComponent extends NxeFormControlBase<string> { ... }
75
- * ```
76
- *
77
- * Subclasses call {@link commitValue} on user interaction and {@link markAsTouched} on blur.
79
+ * DevExtreme lets a group be addressed two ways: by a string name shared between a validator and
80
+ * a button, or structurally by wrapping both in a `dxValidationGroup`. The string form is what
81
+ * the consuming applications use; this token is the structural one, and an explicit string
82
+ * always wins over it, as in dx.
78
83
  */
84
+ interface NxeValidationGroupScope {
85
+ /** Opaque, unique per instance — never collides with an application's own group names. */
86
+ readonly groupKey: string;
87
+ }
88
+ declare const NXE_VALIDATION_GROUP: InjectionToken<NxeValidationGroupScope>;
89
+ /** DevExtreme-shaped `onValidated` payload. */
90
+ interface NxeValidatedEvent extends NxeValidationResult {
91
+ value: unknown;
92
+ validationGroup: string;
93
+ }
94
+
79
95
  declare abstract class NxeFormControlBase<T> implements ControlValueAccessor, NxeValidationTarget {
80
96
  protected readonly elementRef: ElementRef<HTMLElement>;
81
97
  /**
@@ -99,6 +115,35 @@ declare abstract class NxeFormControlBase<T> implements ControlValueAccessor, Nx
99
115
  readonly readOnly: _angular_core.InputSignalWithTransform<boolean, unknown>;
100
116
  /** Validation group this editor belongs to (its nested `nxe-validator` registers here). */
101
117
  readonly validationGroupInput: _angular_core.InputSignal<string>;
118
+ /**
119
+ * `id` for the INNER control, not the host.
120
+ *
121
+ * A caption written as `<label for="x">` has to reach the focusable element, and the host of
122
+ * a custom element is not it. Without this an editor cannot be labelled at all, which is why
123
+ * `aria-label` sits beside it: one of the two has to be available.
124
+ */
125
+ readonly inputId: _angular_core.InputSignal<string>;
126
+ readonly ariaLabel: _angular_core.InputSignal<string>;
127
+ readonly ariaLabelledBy: _angular_core.InputSignal<string>;
128
+ /** Stable per instance, so the inner control always has an id something can point at. */
129
+ private readonly fallbackId;
130
+ readonly controlId: _angular_core.Signal<string>;
131
+ /**
132
+ * Ids for a popup listbox and its options.
133
+ *
134
+ * `aria-activedescendant` is how a combobox says which option is current while focus stays in
135
+ * the text field, and it can only point at an id — so the options need stable ones.
136
+ */
137
+ panelId(): string;
138
+ optionId(index: number): string;
139
+ /**
140
+ * Whether a nested validator carries a `required` rule.
141
+ *
142
+ * Set by the validator rather than declared on the editor: the rule is where the requirement
143
+ * is actually stated, and duplicating it as an input would let the two disagree.
144
+ */
145
+ readonly required: _angular_core.WritableSignal<boolean>;
146
+ setRequired(value: boolean): void;
102
147
  /** The current validation error message (set by a nested `nxe-validator`), or null. */
103
148
  readonly validationError: _angular_core.WritableSignal<string>;
104
149
  /** Whether the control has been blurred at least once. */
@@ -107,6 +152,39 @@ declare abstract class NxeFormControlBase<T> implements ControlValueAccessor, Nx
107
152
  readonly dirty: _angular_core.WritableSignal<boolean>;
108
153
  /** DevExtreme-compatible value-change event. */
109
154
  readonly onValueChanged: _angular_core.OutputEmitterRef<NxeValueChangedEvent<T>>;
155
+ /**
156
+ * dx's keyboard, focus and input events.
157
+ *
158
+ * Native events already reach the host, so these are not about capability — they are about
159
+ * the call site. A migrated `(onEnterKey)` handler should keep working without being
160
+ * rewritten as a `(keydown)` with a key check, and `onEnterKey` in particular has no native
161
+ * equivalent at all.
162
+ */
163
+ readonly onKeyDown: _angular_core.OutputEmitterRef<NxeEditorEvent>;
164
+ readonly onKeyUp: _angular_core.OutputEmitterRef<NxeEditorEvent>;
165
+ readonly onEnterKey: _angular_core.OutputEmitterRef<NxeEditorEvent>;
166
+ readonly onInput: _angular_core.OutputEmitterRef<NxeEditorEvent>;
167
+ readonly onChange: _angular_core.OutputEmitterRef<NxeEditorEvent>;
168
+ readonly onFocusIn: _angular_core.OutputEmitterRef<NxeEditorEvent>;
169
+ readonly onFocusOut: _angular_core.OutputEmitterRef<NxeEditorEvent>;
170
+ /**
171
+ * When a typed value is committed: dx's `'change'` (on blur), `'input'` (per keystroke) or
172
+ * `'keyup'`.
173
+ *
174
+ * Honoured by the text editors. The number and date boxes stay on `'change'` whatever this
175
+ * says, because their displayed text is derived from the parsed value: committing mid-entry
176
+ * would rewrite the field while the user is still typing into it.
177
+ */
178
+ readonly valueChangeEvent: _angular_core.InputSignal<"keyup" | "input" | "change">;
179
+ /** dx universal widget options. */
180
+ readonly hint: _angular_core.InputSignal<string>;
181
+ readonly width: _angular_core.InputSignal<string | number>;
182
+ readonly visible: _angular_core.InputSignalWithTransform<boolean, unknown>;
183
+ readonly tabIndex: _angular_core.InputSignal<number>;
184
+ readonly elementAttr: _angular_core.InputSignal<Record<string, string>>;
185
+ protected readonly widthStyle: _angular_core.Signal<string>;
186
+ protected readonly elementAttrClass: _angular_core.Signal<string>;
187
+ protected readonly elementAttrId: _angular_core.Signal<string>;
110
188
  private _onChange;
111
189
  private _onTouched;
112
190
  /** Host element (the DevExtreme event payload `element`). */
@@ -124,10 +202,24 @@ declare abstract class NxeFormControlBase<T> implements ControlValueAccessor, Nx
124
202
  * DevExtreme-shaped `onValueChanged`. No-op when the value is unchanged.
125
203
  */
126
204
  protected commitValue(next: T | null, event?: Event | null): void;
205
+ /**
206
+ * Re-emit a native event in dx's shape.
207
+ *
208
+ * `onEnterKey` is the one that earns its keep: it has no native equivalent, so every call
209
+ * site would otherwise carry its own key check.
210
+ */
211
+ protected emitEditorEvent(kind: NxeEditorEventKind, event: Event): void;
212
+ /**
213
+ * Commit `next` if `valueChangeEvent` says this is the moment.
214
+ *
215
+ * Blur always commits, whatever the mode: a value the user typed and then left must not be
216
+ * discarded because the configured event never fired.
217
+ */
218
+ protected commitOn(kind: 'input' | 'keyup' | 'change', next: T | null, event: Event): void;
127
219
  /** Mark the control as touched (e.g. on blur), notifying Angular forms exactly once. */
128
220
  protected markAsTouched(): void;
129
221
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<NxeFormControlBase<any>, never>;
130
- static ɵdir: _angular_core.ɵɵDirectiveDeclaration<NxeFormControlBase<any>, never, never, { "value": { "alias": "value"; "required": false; "isSignal": true; }; "disabledInput": { "alias": "disabled"; "required": false; "isSignal": true; }; "readOnly": { "alias": "readOnly"; "required": false; "isSignal": true; }; "validationGroupInput": { "alias": "validationGroup"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; "onValueChanged": "onValueChanged"; }, never, never, true, never>;
222
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<NxeFormControlBase<any>, never, never, { "value": { "alias": "value"; "required": false; "isSignal": true; }; "disabledInput": { "alias": "disabled"; "required": false; "isSignal": true; }; "readOnly": { "alias": "readOnly"; "required": false; "isSignal": true; }; "validationGroupInput": { "alias": "validationGroup"; "required": false; "isSignal": true; }; "inputId": { "alias": "inputId"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "aria-label"; "required": false; "isSignal": true; }; "ariaLabelledBy": { "alias": "aria-labelledby"; "required": false; "isSignal": true; }; "valueChangeEvent": { "alias": "valueChangeEvent"; "required": false; "isSignal": true; }; "hint": { "alias": "hint"; "required": false; "isSignal": true; }; "width": { "alias": "width"; "required": false; "isSignal": true; }; "visible": { "alias": "visible"; "required": false; "isSignal": true; }; "tabIndex": { "alias": "tabIndex"; "required": false; "isSignal": true; }; "elementAttr": { "alias": "elementAttr"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; "onValueChanged": "onValueChanged"; "onKeyDown": "onKeyDown"; "onKeyUp": "onKeyUp"; "onEnterKey": "onEnterKey"; "onInput": "onInput"; "onChange": "onChange"; "onFocusIn": "onFocusIn"; "onFocusOut": "onFocusOut"; }, never, never, true, never>;
131
223
  }
132
224
 
133
225
  /**
@@ -219,10 +311,12 @@ interface NxeDefaultOptions {
219
311
  readonly dateBox?: {
220
312
  displayFormat?: string;
221
313
  placeholder?: string;
314
+ showClearButton?: boolean;
222
315
  };
223
316
  readonly textBox?: {
224
317
  placeholder?: string;
225
318
  hoverStateEnabled?: boolean;
319
+ showClearButton?: boolean;
226
320
  };
227
321
  readonly textArea?: {
228
322
  placeholder?: string;
@@ -230,6 +324,7 @@ interface NxeDefaultOptions {
230
324
  };
231
325
  readonly numberBox?: {
232
326
  format?: string;
327
+ showClearButton?: boolean;
233
328
  };
234
329
  readonly button?: {
235
330
  focusStateEnabled?: boolean;
@@ -306,6 +401,24 @@ declare function coerceArray<T>(value: T | readonly T[] | null | undefined): T[]
306
401
  */
307
402
  declare function nxeOptionalBoolean(value: unknown): boolean | undefined;
308
403
 
404
+ /**
405
+ * DevExtreme's `displayExpr` / `valueExpr` / `searchExpr` accessor.
406
+ *
407
+ * dx accepts four spellings and the library accepted one — a flat property name — so a dotted
408
+ * path silently read `item['user.name']` and returned undefined. Silent, because an accessor
409
+ * that misses looks exactly like a field that is empty.
410
+ *
411
+ * undefined / '' the item itself (dx: no expression)
412
+ * 'this' the item itself (dx's documented sentinel)
413
+ * 'user.name' a dotted path
414
+ * (item) => ... a function accessor
415
+ */
416
+ type NxeExpr<T = unknown> = string | ((item: T) => unknown) | undefined | null;
417
+ /** Resolve `expr` against `item`. */
418
+ declare function nxeResolveExpr<T = unknown>(expr: NxeExpr<T>, item: T): unknown;
419
+ /** Resolve `expr` and render it as display text: `null`/`undefined` become the empty string. */
420
+ declare function nxeResolveText<T = unknown>(expr: NxeExpr<T>, item: T): string;
421
+
309
422
  /**
310
423
  * Root registry mapping a `validationGroup` string to the `nxe-validator`s in it, mirroring
311
424
  * DevExtreme's global ValidationEngine.
@@ -318,11 +431,18 @@ declare class NxeValidationGroupRegistry {
318
431
  private readonly groups;
319
432
  register(group: string, member: NxeValidationGroupMember): void;
320
433
  unregister(group: string, member: NxeValidationGroupMember): void;
434
+ /**
435
+ * Clear every member's error state without validating.
436
+ *
437
+ * dx's `ValidationGroup.reset()`. The distinction from `validate()` matters after a Cancel:
438
+ * the fields should stop showing errors, not be re-checked and light up again.
439
+ */
440
+ reset(group: string): void;
321
441
  /** Validate every member of the group; valid (and empty) groups return `isValid: true`. */
322
442
  validate(group: string): NxeValidationResult;
323
443
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<NxeValidationGroupRegistry, never>;
324
444
  static ɵprov: _angular_core.ɵɵInjectableDeclaration<NxeValidationGroupRegistry>;
325
445
  }
326
446
 
327
- export { NXE_DEFAULT_OPTIONS, NXE_OPTION, NXE_VALIDATION_TARGET, NxeConfigOptionBase, NxeFormControlBase, NxeTemplateDirective, NxeValidationGroupRegistry, coerceArray, nxeOptionalBoolean };
328
- export type { NxeBrokenRule, NxeConfigOption, NxeDefaultOptions, NxeGridDefaultOptions, NxePickerDefaultOptions, NxeValidationGroupMember, NxeValidationResult, NxeValidationTarget, NxeValueChangedEvent };
447
+ export { NXE_DEFAULT_OPTIONS, NXE_OPTION, NXE_VALIDATION_GROUP, NXE_VALIDATION_TARGET, NxeConfigOptionBase, NxeFormControlBase, NxeTemplateDirective, NxeValidationGroupRegistry, coerceArray, nxeOptionalBoolean, nxeResolveExpr, nxeResolveText };
448
+ export type { NxeBrokenRule, NxeConfigOption, NxeDefaultOptions, NxeEditorEvent, NxeEditorEventKind, NxeExpr, NxeGridDefaultOptions, NxePickerDefaultOptions, NxeValidatedEvent, NxeValidationGroupMember, NxeValidationGroupScope, NxeValidationResult, NxeValidationTarget, NxeValueChangedEvent };