@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.
- package/fesm2022/nexterajs-ui-core.mjs +189 -5
- package/fesm2022/nexterajs-ui-core.mjs.map +1 -1
- package/fesm2022/nexterajs-ui-dropdowns.mjs +314 -50
- package/fesm2022/nexterajs-ui-dropdowns.mjs.map +1 -1
- package/fesm2022/nexterajs-ui-file-uploader.mjs +6 -3
- package/fesm2022/nexterajs-ui-file-uploader.mjs.map +1 -1
- package/fesm2022/nexterajs-ui-forms.mjs +729 -370
- package/fesm2022/nexterajs-ui-forms.mjs.map +1 -1
- package/fesm2022/nexterajs-ui-grid.mjs +1188 -208
- package/fesm2022/nexterajs-ui-grid.mjs.map +1 -1
- package/fesm2022/nexterajs-ui-intl.mjs +15 -5
- package/fesm2022/nexterajs-ui-intl.mjs.map +1 -1
- package/fesm2022/nexterajs-ui-notify.mjs +351 -0
- package/fesm2022/nexterajs-ui-notify.mjs.map +1 -0
- package/fesm2022/nexterajs-ui-pivot.mjs +146 -116
- package/fesm2022/nexterajs-ui-pivot.mjs.map +1 -1
- package/fesm2022/nexterajs-ui-styling.mjs +568 -3
- package/fesm2022/nexterajs-ui-styling.mjs.map +1 -1
- package/fesm2022/nexterajs-ui-tree-view.mjs +147 -18
- package/fesm2022/nexterajs-ui-tree-view.mjs.map +1 -1
- package/fesm2022/nexterajs-ui-validation.mjs +247 -92
- package/fesm2022/nexterajs-ui-validation.mjs.map +1 -1
- package/fesm2022/nexterajs-ui.mjs +1 -0
- package/fesm2022/nexterajs-ui.mjs.map +1 -1
- package/package.json +5 -1
- package/styles/_dropdown.scss +149 -0
- package/styles/_editor.scss +23 -4
- package/styles/_icons.scss +92 -0
- package/styles/_mixins.scss +104 -1
- package/styles/_scrollbar.scss +134 -0
- package/styles/_toast.scss +243 -0
- package/styles/_tokens.scss +48 -0
- package/styles/index.scss +4 -0
- package/types/nexterajs-ui-core.d.ts +142 -22
- package/types/nexterajs-ui-dropdowns.d.ts +145 -27
- package/types/nexterajs-ui-file-uploader.d.ts +4 -1
- package/types/nexterajs-ui-forms.d.ts +103 -19
- package/types/nexterajs-ui-grid.d.ts +445 -57
- package/types/nexterajs-ui-notify.d.ts +147 -0
- package/types/nexterajs-ui-pivot.d.ts +35 -22
- package/types/nexterajs-ui-styling.d.ts +163 -3
- package/types/nexterajs-ui-tree-view.d.ts +50 -1
- package/types/nexterajs-ui-validation.d.ts +94 -15
- package/types/nexterajs-ui.d.ts +1 -0
package/styles/_dropdown.scss
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
// Reproduces the DevExtreme `.dx-dropdownlist-popup`. Global (loaded via styling/index.scss) 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: #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: #e6e6e6;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
#{$open} {
|
|
198
|
+
background-color: #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: #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
|
}
|
package/styles/_editor.scss
CHANGED
|
@@ -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
|
|
@@ -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.
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
|
|
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,9 +99,25 @@
|
|
|
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
|
+
|
|
99
115
|
.nxe-editor-input {
|
|
100
116
|
-webkit-appearance: none;
|
|
101
117
|
appearance: none;
|
|
118
|
+
// dx: `.dx-widget * { box-sizing: border-box }`. Without it, a host with no preflight grew the
|
|
119
|
+
// 34px min-height by the 15px of padding and every editor rendered 51px tall, not 36px.
|
|
120
|
+
box-sizing: border-box;
|
|
102
121
|
flex: 1 1 auto;
|
|
103
122
|
margin: 0;
|
|
104
123
|
width: 100%;
|
|
@@ -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
|
+
}
|
package/styles/_mixins.scss
CHANGED
|
@@ -1,2 +1,105 @@
|
|
|
1
1
|
// @nexterajs/ui — shared SCSS mixins.
|
|
2
|
-
|
|
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: 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
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// @nexterajs/ui — [nxeScrollbar] chrome.
|
|
2
|
+
//
|
|
3
|
+
// A value-for-value port of `perfect-scrollbar.css`, which this replaces. Applications style these
|
|
4
|
+
// rails heavily, so every number and easing is copied rather than chosen — the selector names are
|
|
5
|
+
// the only thing that changed, one-for-one:
|
|
6
|
+
//
|
|
7
|
+
// .ps -> .nxe-scrollbar .ps__rail-x -> .nxe-scrollbar__rail-x
|
|
8
|
+
// .ps--active-x -> .nxe-scrollbar--active-x .ps__thumb-x -> .nxe-scrollbar__thumb-x
|
|
9
|
+
// .ps--focus -> .nxe-scrollbar--focus .ps--clicking -> .nxe-scrollbar--clicking
|
|
10
|
+
// .ps--scrolling-x -> .nxe-scrollbar--scrolling-x (and the -y twins of each)
|
|
11
|
+
//
|
|
12
|
+
// Load this where `perfect-scrollbar/css/perfect-scrollbar.css` used to sit in the cascade —
|
|
13
|
+
// BEFORE the application's own rail overrides — or those overrides stop winning.
|
|
14
|
+
|
|
15
|
+
//
|
|
16
|
+
// Container style
|
|
17
|
+
//
|
|
18
|
+
// The original set `overflow: hidden !important` and drove scrolling entirely from JavaScript:
|
|
19
|
+
// wheel, touch, keyboard and momentum were all reimplemented. This keeps `overflow: auto` and
|
|
20
|
+
// merely hides the native bar, so the browser still does the scrolling — which is more faithful
|
|
21
|
+
// on touch and with a trackpad, and is why the directive needs no input handling of its own.
|
|
22
|
+
// Layout is unchanged either way: a hidden native scrollbar occupies no width, exactly as the
|
|
23
|
+
// simulated rails did.
|
|
24
|
+
.nxe-scrollbar {
|
|
25
|
+
overflow: auto;
|
|
26
|
+
overflow-anchor: none;
|
|
27
|
+
touch-action: auto;
|
|
28
|
+
position: relative;
|
|
29
|
+
|
|
30
|
+
scrollbar-width: none;
|
|
31
|
+
-ms-overflow-style: none;
|
|
32
|
+
|
|
33
|
+
&::-webkit-scrollbar {
|
|
34
|
+
width: 0;
|
|
35
|
+
height: 0;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
//
|
|
40
|
+
// Scrollbar rail styles
|
|
41
|
+
//
|
|
42
|
+
.nxe-scrollbar__rail-x {
|
|
43
|
+
display: none;
|
|
44
|
+
opacity: 0;
|
|
45
|
+
transition:
|
|
46
|
+
background-color 0.2s linear,
|
|
47
|
+
opacity 0.2s linear;
|
|
48
|
+
height: 15px;
|
|
49
|
+
// there must be 'bottom' or 'top' for the x rail
|
|
50
|
+
bottom: 0px;
|
|
51
|
+
// please don't change 'position'
|
|
52
|
+
position: absolute;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.nxe-scrollbar__rail-y {
|
|
56
|
+
display: none;
|
|
57
|
+
opacity: 0;
|
|
58
|
+
transition:
|
|
59
|
+
background-color 0.2s linear,
|
|
60
|
+
opacity 0.2s linear;
|
|
61
|
+
width: 15px;
|
|
62
|
+
// there must be 'right' or 'left' for the y rail
|
|
63
|
+
right: 0;
|
|
64
|
+
// please don't change 'position'
|
|
65
|
+
position: absolute;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.nxe-scrollbar--active-x > .nxe-scrollbar__rail-x,
|
|
69
|
+
.nxe-scrollbar--active-y > .nxe-scrollbar__rail-y {
|
|
70
|
+
display: block;
|
|
71
|
+
background-color: transparent;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.nxe-scrollbar:hover > .nxe-scrollbar__rail-x,
|
|
75
|
+
.nxe-scrollbar:hover > .nxe-scrollbar__rail-y,
|
|
76
|
+
.nxe-scrollbar--focus > .nxe-scrollbar__rail-x,
|
|
77
|
+
.nxe-scrollbar--focus > .nxe-scrollbar__rail-y,
|
|
78
|
+
.nxe-scrollbar--scrolling-x > .nxe-scrollbar__rail-x,
|
|
79
|
+
.nxe-scrollbar--scrolling-y > .nxe-scrollbar__rail-y {
|
|
80
|
+
opacity: 0.6;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.nxe-scrollbar .nxe-scrollbar__rail-x:hover,
|
|
84
|
+
.nxe-scrollbar .nxe-scrollbar__rail-y:hover,
|
|
85
|
+
.nxe-scrollbar .nxe-scrollbar__rail-x:focus,
|
|
86
|
+
.nxe-scrollbar .nxe-scrollbar__rail-y:focus,
|
|
87
|
+
.nxe-scrollbar .nxe-scrollbar__rail-x.nxe-scrollbar--clicking,
|
|
88
|
+
.nxe-scrollbar .nxe-scrollbar__rail-y.nxe-scrollbar--clicking {
|
|
89
|
+
background-color: #eee;
|
|
90
|
+
opacity: 0.9;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
//
|
|
94
|
+
// Scrollbar thumb styles
|
|
95
|
+
//
|
|
96
|
+
.nxe-scrollbar__thumb-x {
|
|
97
|
+
background-color: #aaa;
|
|
98
|
+
border-radius: 6px;
|
|
99
|
+
transition:
|
|
100
|
+
background-color 0.2s linear,
|
|
101
|
+
height 0.2s ease-in-out;
|
|
102
|
+
height: 6px;
|
|
103
|
+
// there must be 'bottom' for the x thumb
|
|
104
|
+
bottom: 2px;
|
|
105
|
+
// please don't change 'position'
|
|
106
|
+
position: absolute;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.nxe-scrollbar__thumb-y {
|
|
110
|
+
background-color: #aaa;
|
|
111
|
+
border-radius: 6px;
|
|
112
|
+
transition:
|
|
113
|
+
background-color 0.2s linear,
|
|
114
|
+
width 0.2s ease-in-out;
|
|
115
|
+
width: 6px;
|
|
116
|
+
// there must be 'right' for the y thumb
|
|
117
|
+
right: 2px;
|
|
118
|
+
// please don't change 'position'
|
|
119
|
+
position: absolute;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.nxe-scrollbar__rail-x:hover > .nxe-scrollbar__thumb-x,
|
|
123
|
+
.nxe-scrollbar__rail-x:focus > .nxe-scrollbar__thumb-x,
|
|
124
|
+
.nxe-scrollbar__rail-x.nxe-scrollbar--clicking .nxe-scrollbar__thumb-x {
|
|
125
|
+
background-color: #999;
|
|
126
|
+
height: 11px;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.nxe-scrollbar__rail-y:hover > .nxe-scrollbar__thumb-y,
|
|
130
|
+
.nxe-scrollbar__rail-y:focus > .nxe-scrollbar__thumb-y,
|
|
131
|
+
.nxe-scrollbar__rail-y.nxe-scrollbar--clicking .nxe-scrollbar__thumb-y {
|
|
132
|
+
background-color: #999;
|
|
133
|
+
width: 11px;
|
|
134
|
+
}
|