@nexterajs/ui 0.0.1 → 0.1.0

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 (63) hide show
  1. package/README.md +279 -34
  2. package/fesm2022/nexterajs-ui-charts.mjs +1096 -0
  3. package/fesm2022/nexterajs-ui-charts.mjs.map +1 -0
  4. package/fesm2022/nexterajs-ui-core.mjs +835 -11
  5. package/fesm2022/nexterajs-ui-core.mjs.map +1 -1
  6. package/fesm2022/nexterajs-ui-display.mjs +1142 -0
  7. package/fesm2022/nexterajs-ui-display.mjs.map +1 -0
  8. package/fesm2022/nexterajs-ui-dropdowns.mjs +621 -69
  9. package/fesm2022/nexterajs-ui-dropdowns.mjs.map +1 -1
  10. package/fesm2022/nexterajs-ui-file-uploader.mjs +576 -11
  11. package/fesm2022/nexterajs-ui-file-uploader.mjs.map +1 -1
  12. package/fesm2022/nexterajs-ui-forms.mjs +2702 -452
  13. package/fesm2022/nexterajs-ui-forms.mjs.map +1 -1
  14. package/fesm2022/nexterajs-ui-grid.mjs +2350 -336
  15. package/fesm2022/nexterajs-ui-grid.mjs.map +1 -1
  16. package/fesm2022/nexterajs-ui-intl.mjs +117 -15
  17. package/fesm2022/nexterajs-ui-intl.mjs.map +1 -1
  18. package/fesm2022/nexterajs-ui-navigation.mjs +1054 -0
  19. package/fesm2022/nexterajs-ui-navigation.mjs.map +1 -0
  20. package/fesm2022/nexterajs-ui-notify.mjs +353 -0
  21. package/fesm2022/nexterajs-ui-notify.mjs.map +1 -0
  22. package/fesm2022/nexterajs-ui-overlay.mjs +1671 -0
  23. package/fesm2022/nexterajs-ui-overlay.mjs.map +1 -0
  24. package/fesm2022/nexterajs-ui-pivot.mjs +191 -136
  25. package/fesm2022/nexterajs-ui-pivot.mjs.map +1 -1
  26. package/fesm2022/nexterajs-ui-shell.mjs +542 -0
  27. package/fesm2022/nexterajs-ui-shell.mjs.map +1 -0
  28. package/fesm2022/nexterajs-ui-styling.mjs +568 -3
  29. package/fesm2022/nexterajs-ui-styling.mjs.map +1 -1
  30. package/fesm2022/nexterajs-ui-tree-view.mjs +150 -18
  31. package/fesm2022/nexterajs-ui-tree-view.mjs.map +1 -1
  32. package/fesm2022/nexterajs-ui-validation.mjs +283 -97
  33. package/fesm2022/nexterajs-ui-validation.mjs.map +1 -1
  34. package/fesm2022/nexterajs-ui.mjs +6 -0
  35. package/fesm2022/nexterajs-ui.mjs.map +1 -1
  36. package/package.json +37 -2
  37. package/styles/_dropdown.scss +150 -1
  38. package/styles/_editor.scss +75 -21
  39. package/styles/_icons.scss +92 -0
  40. package/styles/_mixins.scss +104 -1
  41. package/styles/_scrollbar.scss +134 -0
  42. package/styles/_status.scss +21 -0
  43. package/styles/_toast.scss +243 -0
  44. package/styles/_tokens.scss +467 -2
  45. package/styles/_widget.scss +14 -3
  46. package/styles/index.scss +10 -4
  47. package/types/nexterajs-ui-charts.d.ts +446 -0
  48. package/types/nexterajs-ui-core.d.ts +515 -30
  49. package/types/nexterajs-ui-display.d.ts +720 -0
  50. package/types/nexterajs-ui-dropdowns.d.ts +318 -57
  51. package/types/nexterajs-ui-file-uploader.d.ts +240 -8
  52. package/types/nexterajs-ui-forms.d.ts +772 -43
  53. package/types/nexterajs-ui-grid.d.ts +878 -135
  54. package/types/nexterajs-ui-intl.d.ts +39 -4
  55. package/types/nexterajs-ui-navigation.d.ts +531 -0
  56. package/types/nexterajs-ui-notify.d.ts +149 -0
  57. package/types/nexterajs-ui-overlay.d.ts +703 -0
  58. package/types/nexterajs-ui-pivot.d.ts +91 -70
  59. package/types/nexterajs-ui-shell.d.ts +294 -0
  60. package/types/nexterajs-ui-styling.d.ts +163 -3
  61. package/types/nexterajs-ui-tree-view.d.ts +56 -4
  62. package/types/nexterajs-ui-validation.d.ts +110 -22
  63. package/types/nexterajs-ui.d.ts +6 -0
@@ -1 +1 @@
1
- {"version":3,"file":"nexterajs-ui.mjs","sources":["../../../projects/nextera-ui/src/public-api.ts","../../../projects/nextera-ui/src/nexterajs-ui.ts"],"sourcesContent":["// @nexterajs/ui — compatibility barrel.\r\n//\r\n// Re-exports every secondary entry point so importing from the bare specifier keeps working.\r\n// Prefer the specific entry point (@nexterajs/ui/grid) in new code: it keeps the pivot engine\r\n// and the xlsx writer out of your bundle and out of your dev-server rebuilds.\r\nexport * from '@nexterajs/ui/animations';\r\nexport * from '@nexterajs/ui/core';\r\nexport * from '@nexterajs/ui/dropdowns';\r\nexport * from '@nexterajs/ui/excel';\r\nexport * from '@nexterajs/ui/file-uploader';\r\nexport * from '@nexterajs/ui/forms';\r\nexport * from '@nexterajs/ui/grid';\r\nexport * from '@nexterajs/ui/intl';\r\nexport * from '@nexterajs/ui/pivot';\r\nexport * from '@nexterajs/ui/styling';\r\nexport * from '@nexterajs/ui/tree-view';\r\nexport * from '@nexterajs/ui/validation';\r\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":";;;;;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;;ACJA;;AAEG"}
1
+ {"version":3,"file":"nexterajs-ui.mjs","sources":["../../../projects/nextera-ui/src/public-api.ts","../../../projects/nextera-ui/src/nexterajs-ui.ts"],"sourcesContent":["// @nexterajs/ui — compatibility barrel.\r\n//\r\n// Re-exports every secondary entry point so importing from the bare specifier keeps working.\r\n// Prefer the specific entry point (@nexterajs/ui/grid) in new code: it keeps the pivot engine\r\n// and the xlsx writer out of your bundle and out of your dev-server rebuilds.\r\nexport * from '@nexterajs/ui/animations';\r\nexport * from '@nexterajs/ui/charts';\r\nexport * from '@nexterajs/ui/core';\r\nexport * from '@nexterajs/ui/display';\r\nexport * from '@nexterajs/ui/dropdowns';\r\nexport * from '@nexterajs/ui/excel';\r\nexport * from '@nexterajs/ui/file-uploader';\r\nexport * from '@nexterajs/ui/forms';\r\nexport * from '@nexterajs/ui/grid';\r\nexport * from '@nexterajs/ui/intl';\r\nexport * from '@nexterajs/ui/navigation';\r\nexport * from '@nexterajs/ui/notify';\r\nexport * from '@nexterajs/ui/overlay';\r\nexport * from '@nexterajs/ui/pivot';\r\nexport * from '@nexterajs/ui/shell';\r\nexport * from '@nexterajs/ui/styling';\r\nexport * from '@nexterajs/ui/tree-view';\r\nexport * from '@nexterajs/ui/validation';\r\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;;ACJA;;AAEG"}
package/package.json CHANGED
@@ -1,20 +1,31 @@
1
1
  {
2
2
  "name": "@nexterajs/ui",
3
- "version": "0.0.1",
3
+ "version": "0.1.0",
4
4
  "peerDependencies": {
5
5
  "@angular/animations": "^22.0.0",
6
6
  "@angular/cdk": "^22.0.0",
7
7
  "@angular/common": "^22.0.0",
8
8
  "@angular/core": "^22.0.0",
9
9
  "@angular/forms": "^22.0.0",
10
+ "@angular/router": "^22.0.0",
10
11
  "luxon": "^3.0.0",
11
12
  "rxjs": "^7.8.0"
12
13
  },
14
+ "peerDependenciesMeta": {
15
+ "@angular/router": {
16
+ "optional": true
17
+ }
18
+ },
13
19
  "dependencies": {
14
20
  "tslib": "^2.3.0"
15
21
  },
16
22
  "sideEffects": false,
23
+ "sass": "styles/index.scss",
17
24
  "exports": {
25
+ "./styles": {
26
+ "sass": "./styles/index.scss",
27
+ "default": "./styles/index.scss"
28
+ },
18
29
  "./styles/*": {
19
30
  "sass": "./styles/*",
20
31
  "default": "./styles/*"
@@ -30,10 +41,18 @@
30
41
  "types": "./types/nexterajs-ui-animations.d.ts",
31
42
  "default": "./fesm2022/nexterajs-ui-animations.mjs"
32
43
  },
44
+ "./charts": {
45
+ "types": "./types/nexterajs-ui-charts.d.ts",
46
+ "default": "./fesm2022/nexterajs-ui-charts.mjs"
47
+ },
33
48
  "./core": {
34
49
  "types": "./types/nexterajs-ui-core.d.ts",
35
50
  "default": "./fesm2022/nexterajs-ui-core.mjs"
36
51
  },
52
+ "./display": {
53
+ "types": "./types/nexterajs-ui-display.d.ts",
54
+ "default": "./fesm2022/nexterajs-ui-display.mjs"
55
+ },
37
56
  "./dropdowns": {
38
57
  "types": "./types/nexterajs-ui-dropdowns.d.ts",
39
58
  "default": "./fesm2022/nexterajs-ui-dropdowns.mjs"
@@ -58,10 +77,26 @@
58
77
  "types": "./types/nexterajs-ui-intl.d.ts",
59
78
  "default": "./fesm2022/nexterajs-ui-intl.mjs"
60
79
  },
80
+ "./navigation": {
81
+ "types": "./types/nexterajs-ui-navigation.d.ts",
82
+ "default": "./fesm2022/nexterajs-ui-navigation.mjs"
83
+ },
84
+ "./notify": {
85
+ "types": "./types/nexterajs-ui-notify.d.ts",
86
+ "default": "./fesm2022/nexterajs-ui-notify.mjs"
87
+ },
88
+ "./overlay": {
89
+ "types": "./types/nexterajs-ui-overlay.d.ts",
90
+ "default": "./fesm2022/nexterajs-ui-overlay.mjs"
91
+ },
61
92
  "./pivot": {
62
93
  "types": "./types/nexterajs-ui-pivot.d.ts",
63
94
  "default": "./fesm2022/nexterajs-ui-pivot.mjs"
64
95
  },
96
+ "./shell": {
97
+ "types": "./types/nexterajs-ui-shell.d.ts",
98
+ "default": "./fesm2022/nexterajs-ui-shell.mjs"
99
+ },
65
100
  "./styling": {
66
101
  "types": "./types/nexterajs-ui-styling.d.ts",
67
102
  "default": "./fesm2022/nexterajs-ui-styling.mjs"
@@ -83,7 +118,7 @@
83
118
  "engines": {
84
119
  "node": "^22.22.3 || ^24.15.0 || >=26.0.0"
85
120
  },
86
- "description": "Angular 22 component library replacing DevExtreme: data grid, pivot grid, editors, pickers and a zero-dependency XLSX engine, in twelve tree-shakeable entry points.",
121
+ "description": "Angular 22 component library replacing DevExtreme: data grid, pivot grid, editors, pickers and a zero-dependency XLSX engine, in eighteen tree-shakeable entry points, with one global style bundle.",
87
122
  "keywords": [
88
123
  "angular",
89
124
  "angular-components",
@@ -1,7 +1,10 @@
1
1
  // Shared dropdown list panel for the nxe select family (select-box, tag-box, drop-down-box).
2
- // Reproduces the DevExtreme `.dx-dropdownlist-popup`. Global (loaded via styling/index.scss) so it
2
+ // Reproduces the DevExtreme `.dx-dropdownlist-popup`. Global (part of the styles/index.scss bundle) so it
3
3
  // applies inside the CDK overlay regardless of which component opened it.
4
4
  //
5
+ // The file also holds the editors' trailing-button rule set (`nxe-dropdown-editor-buttons`, below),
6
+ // which is NOT global: each dropdown editor includes it in its own component stylesheet.
7
+ //
5
8
  // Every value below is checked against the reference deployment's bundle
6
9
  // (tools/visual-parity/reference/bright-styles.css); the source rule is named in a comment.
7
10
  // Typography deliberately comes from `.nxe-widget` on the PANE (styling/_widget.scss) — the panel
@@ -110,4 +113,150 @@
110
113
  min-height: 3em;
111
114
  color: var(--nxe-ui-text-muted);
112
115
  }
116
+
117
+ // The panes that replace DevExtreme popups sit flush against their editor (dx measures the
118
+ // overlay at the editor's bottom edge). Host apps migrated from broker carry
119
+ // `.cdk-overlay-pane { margin-top: 5px }` (styles/material/_menu.scss), which was written for
120
+ // mat-menu — dx popups were never CDK panes, so it never reached them. At (0,2,0) this beats it.
121
+ // `.nxe-autocomplete-pane` is deliberately absent: it replaces mat-autocomplete, which did get
122
+ // the 5px, and so did the mat-menu panes that host nxe-list action menus.
123
+ .cdk-overlay-pane.nxe-select-pane,
124
+ .cdk-overlay-pane.nxe-dropdownbox-pane,
125
+ .cdk-overlay-pane.nxe-autocomplete-box-pane,
126
+ .cdk-overlay-pane.nxe-datebox-pane {
127
+ margin-top: 0;
128
+ }
129
+ }
130
+
131
+ // ------------------------------------------------------------------------------------------------
132
+ // Trailing buttons of the dropdown editors — one rule set for select-box, tag-box, drop-down-box
133
+ // and auto-complete. Include it INSIDE the component's host selector:
134
+ //
135
+ // nxe-select-box { @include dropdown.nxe-dropdown-editor-buttons; }
136
+ //
137
+ // so every selector starts with the host element. That keeps them in component styles (present in
138
+ // karma, and loaded after the app's global sheet) while staying LOW enough for an app to recolour:
139
+ // `nxe-select-box .nxe-select-arrow` is (0,1,1), so e.g. a sign-in screen's
140
+ // `.sign-in-company .nxe-select-arrow { color }` at (0,2,0) still wins.
141
+ //
142
+ // Colours are dx.light literals, like the rest of the editor chrome (styles/_editor.scss) — there
143
+ // are no dark editor tokens to route them through.
144
+ // ------------------------------------------------------------------------------------------------
145
+
146
+ // The 34px drop button box, shared with the date box's calendar button.
147
+ // dx: `.dx-dropdowneditor-button { width:34px; min-width:34px; padding:1px; border-radius:4px;
148
+ // height:100% }` around `.dx-dropdowneditor-icon { width:32px; height:100%;
149
+ // border:1px solid transparent; border-radius:3px }`. The state fill is painted on that 32px
150
+ // inner icon, never on the button — which is why measuring `.dx-dropdowneditor-button` found it
151
+ // transparent. `background-clip: content-box` paints the same 32px box (corner radius 4 - 1 = 3px)
152
+ // without an extra element.
153
+ @mixin nxe-drop-button {
154
+ box-sizing: border-box;
155
+ position: relative;
156
+ flex: 0 0 34px;
157
+ // Full editor height rather than a fixed 34px: in a grid cell whose editor is shorter the fill
158
+ // becomes 32 x (height - 2), as dx's does, and the glyph stays centred.
159
+ align-self: stretch;
160
+ display: inline-flex;
161
+ align-items: center;
162
+ justify-content: center;
163
+ width: 34px;
164
+ min-width: 34px;
165
+ padding: 1px;
166
+ border-radius: 4px;
167
+ background-clip: content-box;
168
+ color: var(--nxe-ui-text, #333);
169
+ cursor: pointer;
170
+
171
+ svg {
172
+ display: block;
173
+ width: 18px;
174
+ height: 18px;
175
+ fill: currentColor;
176
+ }
177
+ }
178
+
179
+ // dx: `.dx-dropdowneditor.dx-state-active .dx-dropdowneditor-icon,
180
+ // .dx-dropdowneditor.dx-state-hover:not(.dx-custom-button-hovered) .dx-dropdowneditor-icon
181
+ // { background-color:#e6e6e6 }` and
182
+ // `.dx-dropdowneditor.dx-dropdowneditor-active .dx-dropdowneditor-icon
183
+ // { background-color:#b3b3b3; opacity:1 }`.
184
+ //
185
+ // PRECEDENCE — do not reorder, do not add !important. dx's hover selector is (0,4,0) (the :not()
186
+ // counts as a class) and beats the open one at (0,3,0), so an open editor under the pointer shows
187
+ // #e6e6e6 and turns #b3b3b3 only once the pointer leaves. Here hover/active is (0,3,1) against open
188
+ // at (0,2,1) — the same relationship, so source order does not decide it and must not be made to.
189
+ // (The library's panes open with a transparent CDK backdrop, so the host stops matching :hover
190
+ // while open and the fill reads #b3b3b3 for the whole open period. That residual is known.)
191
+ @mixin nxe-drop-button-states($button, $open) {
192
+ &:hover:not(.nxe-disabled) #{$button},
193
+ &:active:not(.nxe-disabled) #{$button} {
194
+ background-color: var(--nxe-ui-neutral-150, #e6e6e6);
195
+ }
196
+
197
+ #{$open} {
198
+ background-color: var(--nxe-ui-neutral-350, #b3b3b3);
199
+ }
200
+ }
201
+
202
+ // The clear (×) area — one definition for the dropdown editors' `.nxe-select-clear` (below) and the
203
+ // text editors' `.nxe-clear-button-area` (styles/_editor.scss: text-box, number-box, date-box).
204
+ // dx: `.dx-show-clear-button .dx-clear-button-area { width:34px; min-width:34px; height:100% }`
205
+ // holding `.dx-icon-clear { color:#999; top:50%; margin-top:-17px; padding:8px; width:34px;
206
+ // height:34px; font-size:18px }` — an 18px glyph box centred in the area. #999 in every state: dx
207
+ // has no hover colour for it.
208
+ @mixin nxe-clear-button {
209
+ box-sizing: border-box;
210
+ flex: 0 0 34px;
211
+ align-self: stretch;
212
+ display: inline-flex;
213
+ align-items: center;
214
+ justify-content: center;
215
+ width: 34px;
216
+ min-width: 34px;
217
+ padding: 0;
218
+ color: var(--nxe-ui-text-muted, #999);
219
+ cursor: pointer;
220
+
221
+ svg {
222
+ display: block;
223
+ width: 18px;
224
+ height: 18px;
225
+ fill: currentColor;
226
+ }
227
+ }
228
+
229
+ @mixin nxe-dropdown-editor-buttons {
230
+ // Clear (×).
231
+ .nxe-select-clear {
232
+ @include nxe-clear-button;
233
+ }
234
+
235
+ // Drop-down arrow: an 8x4 #333 triangle centred in its 18px box.
236
+ .nxe-select-arrow {
237
+ @include nxe-drop-button;
238
+ }
239
+
240
+ @include nxe-drop-button-states('.nxe-select-arrow', '&.nxe-open .nxe-select-arrow');
241
+
242
+ // dx: `.dx-dropdowneditor-button-visible .dx-texteditor-input { padding-inline-end: 0 }` — the
243
+ // arrow supplies the trailing gap. Descendant, not child, so the tag box's input (inside its
244
+ // tag container) is covered too.
245
+ .nxe-editor-container:has(> .nxe-select-arrow) .nxe-editor-input {
246
+ padding-inline-end: 0;
247
+ }
248
+
249
+ // Invalid badge (styles/_editor.scss) clears the trailing buttons. dx draws it 4px inside
250
+ // `.dx-texteditor-input-container` (`.dx-texteditor.dx-invalid .dx-texteditor-input-container
251
+ // ::after { inset-inline-end:4px }`), and that container ends where the 34px buttons begin:
252
+ // 34 + 4 with one button, 34 + 34 + 4 with both. (dx's own 38px/68px rules target a
253
+ // `.dx-texteditor-container::after` that has no content, so they never paint.)
254
+ .nxe-editor-container:has(> .nxe-select-clear),
255
+ .nxe-editor-container:has(> .nxe-select-arrow) {
256
+ --nxe-invalid-badge-inset: 38px;
257
+ }
258
+
259
+ .nxe-editor-container:has(> .nxe-select-clear):has(> .nxe-select-arrow) {
260
+ --nxe-invalid-badge-inset: 72px;
261
+ }
113
262
  }
@@ -3,6 +3,7 @@
3
3
  //
4
4
  // Typography is NOT set here — it comes from `.nxe-widget` on the host (styling/_widget.scss), so
5
5
  // it resolves per host app instead of assuming orbit's 14.4px root.
6
+ @use 'dropdown';
6
7
 
7
8
  @mixin nxe-editor-chrome {
8
9
  // Base box at ZERO specificity (`:where`) so per-instance Tailwind utilities applied on the
@@ -12,20 +13,20 @@
12
13
  position: relative;
13
14
  display: block;
14
15
  box-sizing: border-box;
15
- background: #fff;
16
- border: 1px solid #ddd;
16
+ background: var(--nxe-ui-surface, #fff);
17
+ border: 1px solid var(--nxe-ui-border, #ddd);
17
18
  border-radius: 4px;
18
- color: #333;
19
+ color: var(--nxe-ui-text, #333);
19
20
  line-height: 1.35715;
20
21
  }
21
22
 
22
23
  .nxe-editor {
23
24
  &:hover {
24
- border-color: rgba(51, 122, 183, 0.4); // faded primary blue
25
+ border-color: var(--nxe-ui-accent-faded, rgba(51, 122, 183, 0.4)); // faded primary blue
25
26
  }
26
27
 
27
28
  &.nxe-focused {
28
- border-color: #337ab7; // solid primary blue
29
+ border-color: var(--nxe-ui-accent, #337ab7); // solid primary blue
29
30
  }
30
31
 
31
32
  &.nxe-readonly {
@@ -45,11 +46,11 @@
45
46
  // The width is compensated with an inset shadow rather than a 2px border: growing the border
46
47
  // would shift the input inside it by a pixel and jog the text as the field goes valid.
47
48
  &.nxe-invalid {
48
- border-color: rgba(217, 83, 79, 0.4);
49
+ border-color: var(--nxe-ui-invalid-faded, rgba(217, 83, 79, 0.4));
49
50
 
50
51
  &.nxe-focused,
51
52
  &:hover {
52
- border-color: #d9534f;
53
+ border-color: var(--nxe-ui-invalid, #d9534f);
53
54
  }
54
55
 
55
56
  // Invalid badge — dx `.dx-invalid.dx-texteditor.dx-editor-outlined.dx-show-invalid-badge
@@ -70,8 +71,8 @@
70
71
  width: 18px;
71
72
  height: 18px;
72
73
  border-radius: 50%;
73
- background-color: #d9534f;
74
- color: #fff;
74
+ background-color: var(--nxe-ui-invalid, #d9534f);
75
+ color: var(--nxe-ui-invalid-contrast, #fff);
75
76
  font-weight: 700;
76
77
  font-size: 13px;
77
78
  line-height: 18px;
@@ -79,10 +80,12 @@
79
80
  }
80
81
 
81
82
  // dx `.dx-texteditor.dx-invalid .dx-texteditor-input { padding-inline-end: 34px }` — keeps
82
- // a long value from running under the badge. Only for a container holding nothing but the
83
- // input: where a trailing button exists the value already stops short of it, and dx leaves
84
- // those inputs unpadded too (`.dx-dropdowneditor-button-visible` keeps padding-end 0).
85
- > .nxe-editor-container:not(:has(> :not(.nxe-editor-input))) .nxe-editor-input {
83
+ // a long value from running under the badge. For a container holding the input alone or
84
+ // with the text editors' clear area: the badge sits inside the input's own box in both
85
+ // (a dx text or number box with its clear button shown keeps the 34px — probed). The
86
+ // dropdown editors' buttons are left to their own rules.
87
+ > .nxe-editor-container:not(:has(> :not(.nxe-editor-input, .nxe-clear-button-area)))
88
+ .nxe-editor-input {
86
89
  padding-inline-end: 34px;
87
90
  }
88
91
  }
@@ -96,18 +99,69 @@
96
99
  width: 100%;
97
100
  }
98
101
 
102
+ // `showClearButton` on the text editors (text-box, number-box, date-box): the same 34px area and
103
+ // #999 glyph as the dropdown editors' clear (styles/_dropdown.scss), after the input.
104
+ .nxe-clear-button-area {
105
+ @include dropdown.nxe-clear-button;
106
+ }
107
+
108
+ // The badge moves off the clear area: dx draws it 4px inside `.dx-texteditor-input-container`,
109
+ // which ends where the 34px area begins (38px). The date box adds its own 72px case for the
110
+ // clear area plus its calendar button.
111
+ .nxe-editor-container:has(> .nxe-clear-button-area) {
112
+ --nxe-invalid-badge-inset: 38px;
113
+ }
114
+
115
+ // `showPasswordToggle` on the text box: a real <button> (so it is a tab stop with a name and
116
+ // aria-pressed) in the clear button's 34px area, after the input and the clear button. The
117
+ // badge clears it the same way it clears the clear area, and both together make 72px.
118
+ .nxe-password-toggle {
119
+ @include dropdown.nxe-clear-button;
120
+ appearance: none;
121
+ border: 0;
122
+ background: transparent;
123
+ font: inherit;
124
+
125
+ &:hover {
126
+ color: var(--nxe-ui-text, #333);
127
+ }
128
+
129
+ &:focus-visible {
130
+ outline: 2px solid var(--nxe-ui-focus-ring-color, #5c95c5);
131
+ outline-offset: -2px;
132
+ border-radius: 4px;
133
+ }
134
+
135
+ .nxe-password-toggle-slash {
136
+ fill: currentColor;
137
+ stroke: var(--nxe-ui-surface, #fff);
138
+ stroke-width: 1px;
139
+ }
140
+ }
141
+
142
+ .nxe-editor-container:has(> .nxe-password-toggle) {
143
+ --nxe-invalid-badge-inset: 38px;
144
+ }
145
+
146
+ .nxe-editor-container:has(> .nxe-password-toggle):has(> .nxe-clear-button-area) {
147
+ --nxe-invalid-badge-inset: 72px;
148
+ }
149
+
99
150
  .nxe-editor-input {
100
151
  -webkit-appearance: none;
101
152
  appearance: none;
153
+ // dx: `.dx-widget * { box-sizing: border-box }`. Without it, a host with no preflight grew the
154
+ // 34px min-height by the 15px of padding and every editor rendered 51px tall, not 36px.
155
+ box-sizing: border-box;
102
156
  flex: 1 1 auto;
103
157
  margin: 0;
104
158
  width: 100%;
105
- min-height: 34px;
106
- padding: 7px 9px 8px;
159
+ min-height: var(--nxe-ui-control-min-height, 34px);
160
+ padding: var(--nxe-ui-editor-padding, 7px 9px 8px);
107
161
  border: 0;
108
162
  outline: 0;
109
163
  background: transparent;
110
- color: #333;
164
+ color: var(--nxe-ui-text, #333);
111
165
  // dx `.dx-texteditor-input { max-width: 100%; text-overflow: ellipsis }` — a value longer
112
166
  // than the field is truncated with an ellipsis rather than clipped mid-glyph.
113
167
  max-width: 100%;
@@ -116,14 +170,14 @@
116
170
  border-radius: 4px;
117
171
 
118
172
  &::placeholder {
119
- color: #999;
173
+ color: var(--nxe-ui-text-muted, #999);
120
174
  }
121
175
 
122
176
  &:disabled {
123
177
  // dx keeps the input itself opaque; the host `.nxe-disabled` dims the whole editor
124
178
  opacity: 1;
125
- color: #333;
126
- -webkit-text-fill-color: #333;
179
+ color: var(--nxe-ui-text, #333);
180
+ -webkit-text-fill-color: var(--nxe-ui-text, #333);
127
181
  }
128
182
  }
129
183
 
@@ -137,8 +191,8 @@
137
191
  max-width: 100%;
138
192
  padding: 10px;
139
193
  border-radius: 4px;
140
- background-color: #d9534f;
141
- color: #fff;
194
+ background-color: var(--nxe-ui-invalid, #d9534f);
195
+ color: var(--nxe-ui-invalid-contrast, #fff);
142
196
  font-size: 0.85em;
143
197
  line-height: normal;
144
198
  word-wrap: break-word;
@@ -0,0 +1,92 @@
1
+ // @nexterajs/ui — the icon layer: `<i class="nxe-icon nxe-icon-NAME">`, the element nxe-button's
2
+ // `icon` input renders in place of dx's `<i class="dx-icon dx-icon-NAME">`.
3
+ //
4
+ // dx draws these from its DXIcons font. The library does not ship or reference that font
5
+ // (DevExtreme is not Apache-licensed), so each glyph below is redrawn: an 18-unit SVG (1 unit =
6
+ // 1px, unless a glyph shares another box's geometry and says so) of rectangles and straight-edged
7
+ // polygons laid out from the metrics measured off dx's rendering (ink box, wall and stroke widths,
8
+ // position), and used as a MASK painted in `currentColor`, so it
9
+ // takes the button's text colour as dx's glyphs do. Where two shapes meet they overlap, so no
10
+ // anti-aliased seam shows at a fractional position.
11
+ //
12
+ // An unknown name draws nothing in its 18x18 box, as dx does (`caret-down`, which DXIcons lacks,
13
+ // measures 18x18 with no ink). That is what the transparent default mask is for: `mask:
14
+ // var(--nxe-icon)` with the property unset is invalid at computed-value time, the mask drops to
15
+ // `none`, and the box paints as a solid square of `currentColor`.
16
+ //
17
+ // Scoped to <i>. The grid's filter-row `<span class="nxe-icon …">` text glyphs and the pivot field
18
+ // chooser's `<span class="nxe-area-icon nxe-icon-filter">` draw themselves and must not match.
19
+
20
+ // $box is the viewBox side: 18 draws in px, a smaller one scales that glyph up to the 18px box.
21
+ @function nxe-icon-mask($body, $box: 18) {
22
+ @return url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 #{$box} #{$box}'%3E#{$body}%3C/svg%3E");
23
+ }
24
+
25
+ @mixin nxe-icons {
26
+ // dx: `.dx-icon { display:inline-block }`, `.dx-button .dx-icon { width:18px; height:18px;
27
+ // vertical-align:middle; margin-inline:0 }`.
28
+ i.nxe-icon {
29
+ --nxe-icon: linear-gradient(transparent, transparent);
30
+ display: inline-block;
31
+ flex-shrink: 0;
32
+ box-sizing: border-box;
33
+ width: 18px;
34
+ height: 18px;
35
+ vertical-align: middle;
36
+ background-color: currentColor;
37
+ -webkit-mask: var(--nxe-icon) center / 18px 18px no-repeat;
38
+ mask: var(--nxe-icon) center / 18px 18px no-repeat;
39
+ }
40
+
41
+ // `filter` (dx U+F050): the grid header's funnel (nxe-data-grid.component.scss
42
+ // `.nxe-grid-hf-icon`), the same geometry every funnel in the library uses, drawn in its 14-unit
43
+ // box and scaled to fill the 18px one: a band 3/14 deep across the full width whose lower
44
+ // corners run in 45° shoulders down to a 4/14 waist, and a 4/14 stem rect standing inside the
45
+ // shoulders from the band down to the bottom edge.
46
+ i.nxe-icon.nxe-icon-filter {
47
+ --nxe-icon: #{nxe-icon-mask(
48
+ "%3Cpolygon points='0 0 14 0 14 3 9 8 5 8 0 3'/%3E"
49
+ + "%3Crect x='5' y='3' width='4' height='11'/%3E",
50
+ 14
51
+ )};
52
+ }
53
+
54
+ // `columnchooser` (dx U+F04D): a 16x15 ink box, 1px in and 2px down. A 12x12 frame of 2px walls
55
+ // (x 1-13, y 2-14) whose lower right corner is covered by an 8x11 panel (x 9-17, y 6-17) with
56
+ // three 4x1 slots through it (x 11-15, every third row from y 8). All rects: the frame's four
57
+ // walls, stopped where the panel starts; the panel's two 2px stiles and its four 2px rails.
58
+ i.nxe-icon.nxe-icon-columnchooser {
59
+ --nxe-icon: #{nxe-icon-mask(
60
+ "%3Crect x='1' y='2' width='12' height='2'/%3E"
61
+ + "%3Crect x='1' y='2' width='2' height='12'/%3E"
62
+ + "%3Crect x='1' y='12' width='9' height='2'/%3E"
63
+ + "%3Crect x='11' y='2' width='2' height='5'/%3E"
64
+ + "%3Crect x='9' y='6' width='2' height='11'/%3E"
65
+ + "%3Crect x='15' y='6' width='2' height='11'/%3E"
66
+ + "%3Crect x='9' y='6' width='8' height='2'/%3E"
67
+ + "%3Crect x='9' y='9' width='8' height='2'/%3E"
68
+ + "%3Crect x='9' y='12' width='8' height='2'/%3E"
69
+ + "%3Crect x='9' y='15' width='8' height='2'/%3E"
70
+ )};
71
+ }
72
+
73
+ // Forced colours (Windows high contrast) replace every background-color with Canvas — the colour
74
+ // behind the icon — so the masked glyph would vanish. `preserve-parent-color` opts the icon out
75
+ // of that forcing while its `color` still takes the parent's forced text colour (CanvasText on
76
+ // nxe-button's host, ButtonText in a native button), so the glyph matches the label beside it.
77
+ // Where that value is unknown, `none` with CanvasText: plain `none` alone would leave
78
+ // currentColor at the author's unforced colour (#333 on a black canvas).
79
+ @media (forced-colors: active) {
80
+ i.nxe-icon {
81
+ forced-color-adjust: none;
82
+ background-color: CanvasText;
83
+ }
84
+
85
+ @supports (forced-color-adjust: preserve-parent-color) {
86
+ i.nxe-icon {
87
+ forced-color-adjust: preserve-parent-color;
88
+ background-color: currentColor;
89
+ }
90
+ }
91
+ }
92
+ }
@@ -1,2 +1,105 @@
1
1
  // @nexterajs/ui — shared SCSS mixins.
2
- // Grows as components are added (e.g. form-control framing, grid theming). Empty for now.
2
+ @use 'sass:math';
3
+
4
+ // Keeps a mark drawn as a background visible under forced colours (Windows high contrast). There
5
+ // the UA replaces every background-color with Canvas, the colour behind the mark, so the tick and
6
+ // the square would vanish and a checked box would read as unchecked (dx's font tick is text and
7
+ // survives). The pseudo-element opts out of the forcing and paints in CanvasText — the colour the
8
+ // box's border is forced to — so the mark keeps the user's contrast.
9
+ @mixin nxe-forced-colors-mark {
10
+ @media (forced-colors: active) {
11
+ forced-color-adjust: none;
12
+ background-color: CanvasText;
13
+ }
14
+ }
15
+
16
+ // The checked glyph of a dx 22px check box (`.dx-checkbox-checked .dx-checkbox-icon::before`, the
17
+ // DXIcons check at 16px), for a pseudo-element of a 22px border-box box with a 1px border.
18
+ //
19
+ // Redrawn, not taken from the font: a 2px stroke bent through a right angle — the short arm falling
20
+ // 5.4px, the long arm rising 9.6px — written as the outline of that stroke, which gives a 15x11 ink
21
+ // box. It is placed 3.5px in and 6px down from the box's outer edge, where dx's measured tick sits
22
+ // (15x11 at +3.5/+6.2, its centre 0.5px below the box's). Every length is stated, so the glyph does
23
+ // not move with the host's box-sizing. Not mirrored under RTL, like dx's: 2.5px from either inline
24
+ // edge is the same place in the 20px padding box.
25
+ @mixin nxe-checkmark($color: var(--nxe-ui-accent, #337ab7)) {
26
+ content: '';
27
+ position: absolute;
28
+ box-sizing: border-box;
29
+ top: 5px;
30
+ inset-inline-start: 2.5px;
31
+ width: 15px;
32
+ height: 11px;
33
+ background-color: $color;
34
+ clip-path: polygon(
35
+ 0 5.586px,
36
+ 5.414px 11px,
37
+ 15px 1.414px,
38
+ 13.586px 0,
39
+ 5.414px 8.172px,
40
+ 1.414px 4.172px
41
+ );
42
+
43
+ @include nxe-forced-colors-mark;
44
+ }
45
+
46
+ // The indeterminate mark of the same box — dx: `.dx-checkbox-indeterminate .dx-checkbox-icon::before
47
+ // { width:.545455em; height:.545455em (12px at 22px); top:50%; margin-top:-.2727275em;
48
+ // inset-inline-start:50%; margin-inline-start:-.2727275em; background-color:#337ab7;
49
+ // border-radius:2px }` — a 12px square centred in the box.
50
+ @mixin nxe-check-indeterminate($color: var(--nxe-ui-accent, #337ab7)) {
51
+ content: '';
52
+ position: absolute;
53
+ box-sizing: border-box;
54
+ top: 50%;
55
+ inset-inline-start: 50%;
56
+ width: 12px;
57
+ height: 12px;
58
+ margin-top: -6px;
59
+ margin-inline-start: -6px;
60
+ border-radius: 2px;
61
+ background-color: $color;
62
+
63
+ @include nxe-forced-colors-mark;
64
+ }
65
+
66
+ // A thin, unobtrusive scrollbar on a natively-scrolling element.
67
+ //
68
+ // Read this before adding `::-webkit-scrollbar` rules anywhere else. From Chrome 121, setting
69
+ // either standard property (`scrollbar-width` / `scrollbar-color`) makes Chromium ignore the
70
+ // webkit pseudo-elements **on that same element**. The two do not compose: an element that sets
71
+ // both gets the standard rendering and the webkit block becomes dead code that still reads as
72
+ // live, which is the worst of both — it looks maintained and changes nothing.
73
+ //
74
+ // So the pseudo-elements live behind `@supports not (scrollbar-width: thin)`, which is true only
75
+ // on the engines that never implemented the standard properties (Chrome < 121, Safari < 18.2).
76
+ // Exactly one of the two branches applies anywhere.
77
+ @mixin nxe-thin-scrollbar(
78
+ $thumb: var(--nxe-ui-scrollbar-thumb, rgba(0, 0, 0, 0.5)),
79
+ $track: transparent,
80
+ $size: 8px,
81
+ $thumb-hover: $thumb
82
+ ) {
83
+ scrollbar-width: thin;
84
+ scrollbar-color: $thumb $track;
85
+
86
+ @supports not (scrollbar-width: thin) {
87
+ &::-webkit-scrollbar {
88
+ width: $size;
89
+ height: $size;
90
+ }
91
+
92
+ &::-webkit-scrollbar-track {
93
+ background: $track;
94
+ }
95
+
96
+ &::-webkit-scrollbar-thumb {
97
+ background-color: $thumb;
98
+ border-radius: math.div($size, 2);
99
+ }
100
+
101
+ &:hover::-webkit-scrollbar-thumb {
102
+ background-color: $thumb-hover;
103
+ }
104
+ }
105
+ }