@filip.mazev/blocks 1.4.3 → 1.4.4
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/README.md +1 -1
- package/_index.scss +1 -1
- package/core/styles/_field-state.scss +16 -4
- package/core/styles/_index.scss +0 -1
- package/core/styles/_mixins.scss +2 -2
- package/core/styles/_motion.scss +1 -1
- package/core/styles/_palettes.scss +127 -132
- package/core/styles/_slider.scss +158 -40
- package/core/styles/_theme-engine.scss +43 -63
- package/core/styles/_utilities.scss +3 -4
- package/core/styles/_validation.scss +6 -1
- package/core/styles/_variables.scss +24 -14
- package/core/styles/themes/_cyberpunk-theme.scss +1 -1
- package/core/styles/themes/_green-theme.scss +1 -1
- package/core/styles/themes/_high-contrast-theme.scss +152 -133
- package/core/styles/themes/_orange-theme.scss +1 -1
- package/core/styles/themes/_purple-theme.scss +1 -1
- package/core/styles/themes/_red-theme.scss +1 -1
- package/core/styles/themes/_white-theme.scss +26 -20
- package/effects/README.md +5 -9
- package/effects/effects.css +200 -121
- package/effects/runtime/README.md +19 -11
- package/fesm2022/filip.mazev-blocks-core.mjs +27 -22
- package/fesm2022/filip.mazev-blocks-core.mjs.map +1 -1
- package/fesm2022/filip.mazev-blocks-effects-runtime.mjs +887 -67
- package/fesm2022/filip.mazev-blocks-effects-runtime.mjs.map +1 -1
- package/fesm2022/filip.mazev-blocks-effects.mjs +8 -48
- package/fesm2022/filip.mazev-blocks-effects.mjs.map +1 -1
- package/fesm2022/filip.mazev-blocks-forms.mjs +65 -61
- package/fesm2022/filip.mazev-blocks-forms.mjs.map +1 -1
- package/fesm2022/filip.mazev-blocks-modal.mjs +29 -32
- package/fesm2022/filip.mazev-blocks-modal.mjs.map +1 -1
- package/fesm2022/filip.mazev-blocks-navigation.mjs +35 -1
- package/fesm2022/filip.mazev-blocks-navigation.mjs.map +1 -1
- package/fesm2022/filip.mazev-blocks-primitives.mjs +106 -32
- package/fesm2022/filip.mazev-blocks-primitives.mjs.map +1 -1
- package/fesm2022/filip.mazev-blocks-toastr.mjs +5 -7
- package/fesm2022/filip.mazev-blocks-toastr.mjs.map +1 -1
- package/fesm2022/filip.mazev-blocks.mjs +1 -1
- package/fesm2022/filip.mazev-blocks.mjs.map +1 -1
- package/navigation/README.md +44 -0
- package/package.json +1 -1
- package/schematics/ng-add/index.js +4 -1
- package/types/filip.mazev-blocks-core.d.ts +3 -4
- package/types/filip.mazev-blocks-effects-runtime.d.ts +10 -8
- package/types/filip.mazev-blocks-effects.d.ts +5 -17
- package/types/filip.mazev-blocks-forms.d.ts +13 -11
- package/types/filip.mazev-blocks-modal.d.ts +1 -0
- package/types/filip.mazev-blocks-navigation.d.ts +21 -5
- package/types/filip.mazev-blocks-primitives.d.ts +10 -0
- package/types/filip.mazev-blocks-toastr.d.ts +4 -4
- package/types/filip.mazev-blocks.d.ts +1 -1
package/effects/effects.css
CHANGED
|
@@ -1,147 +1,226 @@
|
|
|
1
1
|
/* The material inherits Blocks colors, spacing, radius and elevation. No theme or palette is defined here. */
|
|
2
2
|
:where([data-bx-surface]) {
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
border: 1px solid var(--bx-glass-edge, var(--bx-border-subtle, GrayText));
|
|
4
|
+
border-radius: var(--bx-glass-radius, var(--bx-rounded-lg, 0));
|
|
5
5
|
}
|
|
6
|
+
|
|
6
7
|
[data-bx-surface] {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
|
|
8
|
+
isolation: isolate;
|
|
9
|
+
background: var(--bx-glass-surface, var(--bx-bg-surface, Canvas)) !important;
|
|
10
|
+
-webkit-backdrop-filter: blur(var(--bx-glass-blur, var(--bx-glass-material-blur, 10px))) saturate(1.15);
|
|
11
|
+
backdrop-filter: blur(var(--bx-glass-blur, var(--bx-glass-material-blur, 10px))) saturate(1.15);
|
|
12
|
+
box-shadow: var(--bx-glass-shadow, 0 2px 6px rgb(0 0 0 / .08));
|
|
13
|
+
color: var(--bx-glass-text, var(--bx-text-primary, CanvasText));
|
|
14
|
+
border-color: var(--bx-glass-edge, var(--bx-border-subtle, GrayText));
|
|
15
|
+
--bx-glass-material-blur: calc(4px + 24px * var(--bx-glassiness, .5) * var(--bx-glassiness, .5));
|
|
16
|
+
--bx-glass-material-opacity: calc(.06 + 1.02 * var(--bx-glassiness, .5) - .28 * var(--bx-glassiness, .5) * var(--bx-glassiness, .5));
|
|
17
|
+
}
|
|
18
|
+
|
|
18
19
|
.bx-glass-decoration {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
20
|
+
position: absolute;
|
|
21
|
+
inset: 0;
|
|
22
|
+
display: block;
|
|
23
|
+
z-index: -1;
|
|
24
|
+
pointer-events: none;
|
|
25
|
+
overflow: hidden;
|
|
26
|
+
border-radius: inherit;
|
|
27
|
+
box-sizing: border-box;
|
|
28
|
+
background: transparent;
|
|
29
|
+
opacity: var(--bx-glass-decoration-opacity, 1);
|
|
30
|
+
}
|
|
31
|
+
|
|
30
32
|
/* Keep the clipping box still: transforming it would enlarge an ancestor's scrollable area. */
|
|
31
|
-
.bx-glass-decoration
|
|
33
|
+
.bx-glass-decoration>.bx-glass-light,
|
|
32
34
|
.bx-glass-decoration::before,
|
|
33
35
|
.bx-glass-decoration::after {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
+
transform-origin: var(--bx-glass-light-x, 50%) var(--bx-glass-light-y, 50%);
|
|
37
|
+
transform: scale(calc(1 + var(--bx-glass-press, 0) * .012), calc(1 - var(--bx-glass-press, 0) * .018));
|
|
36
38
|
}
|
|
39
|
+
|
|
37
40
|
@supports (background: color-mix(in srgb, white, transparent)) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
41
|
+
[data-bx-surface] {
|
|
42
|
+
background: color-mix(in srgb, var(--bx-glass-surface, var(--bx-bg-surface, Canvas)) calc(max(var(--bx-glass-min-opacity, 0), var(--bx-glass-opacity, var(--bx-glass-material-opacity, .5))) * 100%), transparent) !important;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.bx-glass-decoration {
|
|
46
|
+
box-shadow: inset 0 1px 0 color-mix(in srgb, white calc(var(--bx-glass-rim-opacity, 1) * 10%), transparent),
|
|
47
|
+
inset 0 -1px 0 color-mix(in srgb, black calc(var(--bx-glass-rim-opacity, 1) * 6%), transparent);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
46
51
|
/* Nested glass keeps its tint and reflection; only the outer surface performs a backdrop pass. */
|
|
47
|
-
[data-bx-surface] [data-bx-surface] {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
background: var(--bx-glass-surface, var(--bx-bg-surface, Canvas)) !important;
|
|
51
|
-
-webkit-backdrop-filter: none;
|
|
52
|
-
backdrop-filter: none;
|
|
52
|
+
[data-bx-surface] [data-bx-surface] {
|
|
53
|
+
-webkit-backdrop-filter: none;
|
|
54
|
+
backdrop-filter: none;
|
|
53
55
|
}
|
|
56
|
+
|
|
54
57
|
.bx-glass-light {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
58
|
+
position: absolute;
|
|
59
|
+
display: block;
|
|
60
|
+
inset: 0;
|
|
61
|
+
pointer-events: none;
|
|
62
|
+
border-radius: inherit;
|
|
63
|
+
opacity: .04;
|
|
64
|
+
background: radial-gradient(ellipse at 25% 0%, white, transparent 68%);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.bx-glass-ambient,
|
|
68
|
+
.bx-glass-contribution {
|
|
69
|
+
position: absolute;
|
|
70
|
+
inset: 0;
|
|
71
|
+
border-radius: inherit;
|
|
72
|
+
pointer-events: none;
|
|
73
|
+
}
|
|
74
|
+
|
|
69
75
|
.bx-glass-rim {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
76
|
+
position: absolute;
|
|
77
|
+
/* The runtime aligns this box with the host's border, including asymmetric borders. */
|
|
78
|
+
inset: 0;
|
|
79
|
+
z-index: 1;
|
|
80
|
+
border-radius: inherit;
|
|
81
|
+
pointer-events: none;
|
|
82
|
+
padding: 2px;
|
|
83
|
+
box-sizing: border-box;
|
|
84
|
+
display: none;
|
|
85
|
+
opacity: var(--bx-glass-rim-visibility, 1);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.bx-glass-environment {
|
|
89
|
+
transition: opacity 180ms ease-out;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
[data-bx-reduced-motion="true"] > .bx-glass-rim > .bx-glass-environment {
|
|
93
|
+
transition: none;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
@supports (-webkit-mask-composite: xor) {
|
|
97
|
+
.bx-glass-rim {
|
|
98
|
+
display: block;
|
|
99
|
+
-webkit-mask: linear-gradient(#ffffff 0 0) content-box, linear-gradient(#ffffff 0 0);
|
|
100
|
+
-webkit-mask-composite: xor;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
81
104
|
@supports (mask-composite: exclude) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
105
|
+
.bx-glass-rim {
|
|
106
|
+
display: block;
|
|
107
|
+
mask: linear-gradient(#ffffff 0 0) content-box, linear-gradient(#ffffff 0 0);
|
|
108
|
+
mask-composite: exclude;
|
|
109
|
+
}
|
|
87
110
|
}
|
|
111
|
+
|
|
88
112
|
/* Enhanced mode adds a depth-colored reflection and a narrow specular edge, with no extra backdrop filter. */
|
|
89
|
-
[data-bx-glass-renderer="enhanced"]
|
|
90
|
-
[data-bx-glass-renderer="enhanced"]
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
113
|
+
[data-bx-glass-renderer="enhanced"]>.bx-glass-decoration::before,
|
|
114
|
+
[data-bx-glass-renderer="enhanced"]>.bx-glass-decoration::after {
|
|
115
|
+
content: '';
|
|
116
|
+
position: absolute;
|
|
117
|
+
inset: 0;
|
|
118
|
+
border-radius: inherit;
|
|
119
|
+
pointer-events: none;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
[data-bx-glass-renderer="enhanced"]>.bx-glass-decoration::before {
|
|
123
|
+
background: radial-gradient(ellipse at var(--bx-glass-reflection-x, 50%) var(--bx-glass-reflection-y, 50%), var(--bx-glass-reflection-color, transparent), transparent 75%);
|
|
124
|
+
opacity: var(--bx-glass-reflection-strength, 0);
|
|
125
|
+
}
|
|
126
|
+
|
|
101
127
|
@supports (mask-composite: exclude) {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
[data-bx-surface
|
|
128
|
+
[data-bx-glass-renderer="enhanced"]>.bx-glass-decoration::after {
|
|
129
|
+
padding: 1px;
|
|
130
|
+
background: radial-gradient(ellipse at 25% 0%, white, transparent 65%);
|
|
131
|
+
opacity: calc(.04 * var(--bx-glass-rim-opacity, .5));
|
|
132
|
+
mask: linear-gradient(#ffffff 0 0) content-box, linear-gradient(#ffffff 0 0);
|
|
133
|
+
mask-composite: exclude;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
[data-bx-surface]:focus-visible {
|
|
138
|
+
outline: 2px solid var(--bx-border-brand, Highlight);
|
|
139
|
+
outline-offset: -2px;
|
|
140
|
+
}
|
|
141
|
+
|
|
112
142
|
[data-bx-surface][data-bx-reduced-transparency="true"] {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
[data-bx-reduced-transparency="true"]
|
|
119
|
-
[data-bx-reduced-transparency="true"]
|
|
120
|
-
|
|
121
|
-
|
|
143
|
+
background: var(--bx-glass-surface, var(--bx-bg-surface, Canvas)) !important;
|
|
144
|
+
-webkit-backdrop-filter: none;
|
|
145
|
+
backdrop-filter: none;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
[data-bx-reduced-transparency="true"]>.bx-glass-decoration::before,
|
|
149
|
+
[data-bx-reduced-transparency="true"]>.bx-glass-decoration::after {
|
|
150
|
+
display: none;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
[data-bx-reduced-transparency="true"]>.bx-glass-decoration>.bx-glass-light {
|
|
154
|
+
opacity: .025;
|
|
155
|
+
}
|
|
156
|
+
|
|
122
157
|
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
|
|
123
|
-
|
|
158
|
+
[data-bx-surface] {
|
|
159
|
+
background: var(--bx-glass-surface, var(--bx-bg-surface, Canvas)) !important;
|
|
160
|
+
}
|
|
124
161
|
}
|
|
162
|
+
|
|
125
163
|
@media (prefers-reduced-transparency: reduce) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
164
|
+
[data-bx-surface]:not([data-bx-reduced-transparency="false"]) {
|
|
165
|
+
background: var(--bx-glass-surface, var(--bx-bg-surface, Canvas)) !important;
|
|
166
|
+
-webkit-backdrop-filter: none;
|
|
167
|
+
backdrop-filter: none;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
[data-bx-reduced-motion="true"]>.bx-glass-decoration>.bx-glass-light,
|
|
172
|
+
[data-bx-reduced-motion="true"]>.bx-glass-decoration::before,
|
|
173
|
+
[data-bx-reduced-motion="true"]>.bx-glass-decoration::after {
|
|
174
|
+
transform: none;
|
|
175
|
+
}
|
|
176
|
+
|
|
133
177
|
@media (prefers-reduced-motion: reduce) {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
178
|
+
|
|
179
|
+
[data-bx-surface]:not([data-bx-reduced-motion="false"])>.bx-glass-decoration>.bx-glass-light,
|
|
180
|
+
[data-bx-surface]:not([data-bx-reduced-motion="false"])>.bx-glass-decoration::before,
|
|
181
|
+
[data-bx-surface]:not([data-bx-reduced-motion="false"])>.bx-glass-decoration::after {
|
|
182
|
+
transform: none;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
138
186
|
@media (prefers-contrast: more) {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
187
|
+
[data-bx-surface] {
|
|
188
|
+
border-color: currentColor;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
[data-bx-surface] {
|
|
192
|
+
background: var(--bx-glass-surface, var(--bx-bg-surface, Canvas)) !important;
|
|
193
|
+
-webkit-backdrop-filter: none;
|
|
194
|
+
backdrop-filter: none;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.bx-glass-rim,
|
|
198
|
+
.bx-glass-light,
|
|
199
|
+
.bx-glass-ambient,
|
|
200
|
+
.bx-glass-decoration::before,
|
|
201
|
+
.bx-glass-decoration::after {
|
|
202
|
+
display: none;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
143
206
|
@media (forced-colors: active) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
207
|
+
[data-bx-surface] {
|
|
208
|
+
color: ButtonText;
|
|
209
|
+
border: 1px solid ButtonText;
|
|
210
|
+
box-shadow: none;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
[data-bx-surface] {
|
|
214
|
+
background: Canvas !important;
|
|
215
|
+
-webkit-backdrop-filter: none;
|
|
216
|
+
backdrop-filter: none;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.bx-glass-rim,
|
|
220
|
+
.bx-glass-light,
|
|
221
|
+
.bx-glass-ambient,
|
|
222
|
+
.bx-glass-decoration::before,
|
|
223
|
+
.bx-glass-decoration::after {
|
|
224
|
+
display: none;
|
|
225
|
+
}
|
|
147
226
|
}
|
|
@@ -13,7 +13,7 @@ import '@filip.mazev/blocks/effects/effects.css';
|
|
|
13
13
|
import { configureAppearance, mountSurface, createGlassTransition } from '@filip.mazev/blocks/effects/runtime';
|
|
14
14
|
|
|
15
15
|
const config = configureAppearance(document.documentElement, {
|
|
16
|
-
|
|
16
|
+
glassiness: 0.5,
|
|
17
17
|
quality: 'auto',
|
|
18
18
|
});
|
|
19
19
|
const buttonSkin = mountSurface(button);
|
|
@@ -22,7 +22,7 @@ const panelSkin = mountSurface(panel);
|
|
|
22
22
|
// The application or overlay library positions the panel before this call.
|
|
23
23
|
const transition = createGlassTransition(button, panel);
|
|
24
24
|
await transition.enter();
|
|
25
|
-
config.update({
|
|
25
|
+
config.update({ glassiness: .45 }); // All existing surfaces and body-mounted overlays update.
|
|
26
26
|
const result = await transition.exit();
|
|
27
27
|
if (result === 'finished') panel.hidden = true;
|
|
28
28
|
|
|
@@ -36,17 +36,17 @@ transition.destroy(); buttonSkin.destroy(); panelSkin.destroy(); config.destroy(
|
|
|
36
36
|
|
|
37
37
|
The renderer defines no brand palette or spacing system. It uses the existing Blocks semantic tokens and preserves each component's border radius, size and layout. Load the glass CSS once after your theme CSS, and set border radius on your native element when using the package outside Blocks.
|
|
38
38
|
|
|
39
|
-
`mountSurface(element)` includes a default token-based border. Buttons, links, native field surfaces and actionable ARIA roles automatically illuminate their border and nearby surfaces on interaction or an active ARIA state. Decorative containers receive nearby light without their own hover effect. The default color is the theme accent, or the error color inside an invalid Blocks form field. Override `emission`/`focusEmission` for a different semantic color; `interactive` explicitly overrides automatic control/action detection.
|
|
39
|
+
`mountSurface(element)` includes a default token-based border. Buttons, links, native field surfaces and actionable ARIA roles automatically illuminate their border and nearby surfaces on interaction or an active ARIA state. Decorative containers receive nearby light without their own hover effect, even when they contain a single input such as a slider. Only wrappers within `bx-form-field` infer interactivity from their input; other custom wrappers can opt in with `interactive: true`. The default color is the theme accent, or the error color inside an invalid Blocks form field. Override `emission`/`focusEmission` for a different semantic color; `interactive` explicitly overrides automatic control/action detection.
|
|
40
40
|
|
|
41
41
|
| Setting | Values | Inherited CSS property |
|
|
42
42
|
| --- | --- | --- |
|
|
43
|
-
| `
|
|
44
|
-
| `blur` | 0–40 CSS pixels | `--bx-glass-blur` |
|
|
45
|
-
| `tint` | 0–1, larger means more opaque | `--bx-glass-opacity` |
|
|
43
|
+
| `glassiness` | 0–1, balanced default 0.5 | `--bx-glassiness` |
|
|
46
44
|
| `quality` | `auto`, `baseline`, `enhanced` | root `data-bx-glass-quality` |
|
|
47
45
|
| `reducedTransparency` | boolean | root `data-bx-reduced-transparency` |
|
|
48
46
|
| `reducedMotion` | boolean | root `data-bx-reduced-motion` |
|
|
49
47
|
|
|
48
|
+
Glassiness controls tint and blur together. Tint is `0.06 + 1.02 × glassiness − 0.28 × glassiness²`; blur is `4 + 24 × glassiness²` CSS pixels. The minimum is nearly clear with a light blur, the midpoint uses 10 px blur and a balanced tint, and the maximum caps tint at 80% to remain glassy. Components retain their semantic minimum opacity where needed for readable controls. There are no selectable material presets or independent blur/tint options. Reduced transparency disables backdrop filtering and provides fully opaque surfaces without discarding the glassiness preference.
|
|
49
|
+
|
|
50
50
|
Local color aliases preserve component intent:
|
|
51
51
|
|
|
52
52
|
| Glass alias | Blocks fallback |
|
|
@@ -79,21 +79,29 @@ scene.update({ lights: [{ x: 300, y: 80, z: 220, intensity: 1 }] });
|
|
|
79
79
|
scene.destroy();
|
|
80
80
|
```
|
|
81
81
|
|
|
82
|
-
Coordinates are viewport CSS pixels. An element supplies its live bounding-box center (`offsetX`/`offsetY`, from 0 to 1, can move the source within it); a reflector uses its computed background color unless `color` is supplied. Signed `depth` places foreground and background reflection proxies relative to the glass plane. Light distance, height and falloff affect the position and strength of the highlight. Scrolling, resizing, visibility changes and explicit refreshes invalidate the scene; call `refresh()` after application-driven transforms or layout changes.
|
|
82
|
+
Coordinates are viewport CSS pixels. An element supplies its live bounding-box center (`offsetX`/`offsetY`, from 0 to 1, can move the source within it); a reflector uses its computed background color unless `color` is supplied. Signed `depth` places foreground and background reflection proxies relative to the glass plane. Light distance, height and falloff affect the position and strength of the highlight. Scrolling, resizing, visibility changes and explicit refreshes invalidate the scene; call `refresh()` after application-driven transforms or layout changes. For frequent updates that only move lights, `reuseGeometry: true` in the scene options retains receiver measurements until those invalidations. Ordinary scene updates continue to remeasure receivers.
|
|
83
|
+
|
|
84
|
+
Every mounted glass surface also gets subtle, localized edge colors from the visible layers around its perimeter. Different sources keep their own colors; ambient gradients crossfade beneath hover, focus, validation and explicit scene lights. The runtime samples nearby computed backgrounds and local pixels from visible images, video and canvas, including `pointer-events: none` backdrops. Hit-test order and stacking layers distinguish foreground from background; depth and distance reduce the contribution. CSS gradients are approximated from their color stops. If the browser blocks pixel access to cross-origin or tainted media, the runtime falls back to the element's computed CSS colors. Asset loads, layout/scroll changes, and visual class/style changes refresh the samples; call `refreshGlassLighting(element)` after application-driven changes to a custom surface's surroundings.
|
|
85
|
+
|
|
86
|
+
Portalled overlays can call `inheritGlassLighting(overlay, trigger)` before mounting their surfaces to receive light from the trigger's context. Call the returned cleanup function after removing the overlay. Tooltips and menus use this connection to preserve dialog boundaries and receive nearby colors. Tooltips remain transparent to pointer events.
|
|
87
|
+
|
|
88
|
+
For compound opaque shapes, `--bx-glass-unified-lighting: 1` exposes restrained samples of received lighting through `--bx-glass-lit-edge` and its `-top`, `-right`, `-bottom`, and `-left` variants. Tooltip bodies retain their neutral backing and use a subtle spatial rim; arrows sample their facing side instead of tinting the entire outline. Interior decorative lighting stays hidden to avoid coloring the fill or introducing seams.
|
|
83
89
|
|
|
84
|
-
This is a bounded, stylized lighting model.
|
|
90
|
+
This is a bounded, stylized lighting model. The browser's backdrop filter still displays content behind glass. Raster sampling uses a shared 32-by-32 scratch buffer and caches each source across receivers and frames; the package does **not** capture or mirror the page or arbitrary DOM into a texture, implement ray tracing, or physically refract page pixels. Registered foreground/background objects remain available as explicit reflection proxies when a more deliberate scene is needed. Gradient-backed reflectors can supply an explicit representative color.
|
|
85
91
|
|
|
86
92
|
## Overlay fades
|
|
87
93
|
|
|
88
|
-
Overlays fade in over 180 ms and out over 140 ms. Their dimensions, clipping and text size remain unchanged. The
|
|
94
|
+
Overlays fade in over 180 ms and out over 140 ms. Their dimensions, clipping and text size remain unchanged. The fade composites the material surface itself, keeping its configured blur and tint throughout. The surrounding positioning pane stays opaque so it does not create a backdrop root that cuts off the page behind the glass. Reversing an active fade continues from its current opacity; the interrupted promise resolves to `cancelled`.
|
|
89
95
|
|
|
90
|
-
The transition temporarily owns `opacity
|
|
96
|
+
The transition temporarily owns the first material surface’s `opacity` and the target’s `visibility`. If no material is present, it fades the target. The overlay positioner retains all geometry and transforms. Supply an already positioned target and destroy the transition when its overlay is removed. Both endpoints must belong to the same document. `refresh()` remains a compatibility no-op because a fade requires no geometry measurements. Version 0.1’s `createLiquidTransition` and associated type names remain deprecated aliases for `createGlassTransition`.
|
|
91
97
|
|
|
92
98
|
## Performance and accessibility
|
|
93
99
|
|
|
94
100
|
- One lazy animation clock and delegated press, hover-boundary and focus handling per document. Moving the pointer within an element does not change lighting. Settled scenes schedule no animation frames.
|
|
95
101
|
- At most four explicit lights and four reflectors per scene, and 24 visible surface lighting updates per frame. Interaction and active-state emitters share one delegated event stream and semantic-attribute observer; there is no observer per control.
|
|
96
|
-
-
|
|
102
|
+
- Ambient geometry/style invalidations coalesce over 180 ms. A sampling frame handles at most four receivers, yielding after about 4 ms between receivers; a single receiver/readback remains atomic. Sixteen perimeter probes discover nearby colors, and media inventories update on insertion rather than on each frame.
|
|
103
|
+
- Static images and failed readbacks are cached until their assets change. Only media used by visible receivers is polled: live canvas/video checks begin at 500 ms and back off to 2 seconds while unchanged. Color-only changes reuse source bindings and geometry without hit testing; coverage or layout changes rediscover the layers. Unchanged samples do not repaint. Image/video samples respect object fit and position.
|
|
104
|
+
- Each surface uses two shared ambient edge layers for a 180 ms crossfade, instead of a DOM layer and animation per sample. Semantic lighting retains its independent contributions and immediate interaction updates. Hidden documents, reduced-motion receivers and unused media stop live polling; removing the last receiver tears down sampling. Static scenes schedule no polling or animation frames.
|
|
97
105
|
- Nested surfaces retain their tint and reflection but skip additional backdrop filters.
|
|
98
106
|
- `baseline` uses one backdrop pass and a dynamic highlight. Supported `enhanced` rendering adds a narrow specular edge and colored reflection overlay without another backdrop pass. `auto` chooses the baseline for coarse pointers, data saving or low reported CPU concurrency; capability checks guard enhancements. Hardware hints are conservative defaults, not measured frame-rate guarantees.
|
|
99
107
|
- Operating-system reduced motion/transparency, increased contrast and forced colors receive simpler rendering. A global `reducedTransparency: false` leaves the OS transparency preference enabled; explicit surface options can override it. An explicit reduced-motion setting takes precedence so Blocks can preserve its existing manual accessibility override; omit it to follow the OS. Reduced motion finishes transitions immediately. Unsupported backdrop filtering or color mixing produces an opaque fallback.
|
|
@@ -31,10 +31,12 @@ var DeviceTypeService = class DeviceTypeService {
|
|
|
31
31
|
if (this.isBrowser) {
|
|
32
32
|
const win = this.document.defaultView;
|
|
33
33
|
const screenObj = win?.screen;
|
|
34
|
-
|
|
34
|
+
const orientation = screenObj?.orientation?.type ?? screenObj?.mozOrientation ?? screenObj?.msOrientation;
|
|
35
|
+
this.screenOrientation = this.toDeviceOrientation(orientation) ?? (win?.matchMedia("(orientation: portrait)").matches ? "portrait-primary" : "landscape-primary");
|
|
35
36
|
if (screenObj?.orientation) screenObj.orientation.addEventListener("change", (ev) => {
|
|
36
|
-
const
|
|
37
|
-
|
|
37
|
+
const eventOrientation = ev.target?.type;
|
|
38
|
+
const supportedOrientation = this.toDeviceOrientation(eventOrientation);
|
|
39
|
+
if (supportedOrientation) this.screenOrientation = supportedOrientation;
|
|
38
40
|
});
|
|
39
41
|
}
|
|
40
42
|
}
|
|
@@ -71,6 +73,9 @@ var DeviceTypeService = class DeviceTypeService {
|
|
|
71
73
|
const win = this.document.defaultView;
|
|
72
74
|
return win?.navigator?.userAgent || win?.navigator?.vendor || win?.opera || "";
|
|
73
75
|
}
|
|
76
|
+
toDeviceOrientation(value) {
|
|
77
|
+
return value === "portrait-primary" || value === "landscape-primary" || value === "portrait-secondary" || value === "landscape-secondary" ? value : void 0;
|
|
78
|
+
}
|
|
74
79
|
get isDesktopDevice() {
|
|
75
80
|
return !this.isMobileDevice() && !this.isTabletDevice();
|
|
76
81
|
}
|
|
@@ -91,7 +96,8 @@ var DeviceTypeService = class DeviceTypeService {
|
|
|
91
96
|
const ua = this.userAgent;
|
|
92
97
|
if (/windows phone/i.test(ua)) return MobileOS.WindowsPhone;
|
|
93
98
|
if (/android/i.test(ua)) return MobileOS.Android;
|
|
94
|
-
|
|
99
|
+
const view = this.document.defaultView;
|
|
100
|
+
if (/iPad|iPhone|iPod/.test(ua) && !view?.MSStream) return MobileOS.iOS;
|
|
95
101
|
return MobileOS.Unknown;
|
|
96
102
|
}
|
|
97
103
|
}
|
|
@@ -338,9 +344,7 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
338
344
|
}]
|
|
339
345
|
});
|
|
340
346
|
const DEFAULT_APPEARANCE_CONFIG = Object.freeze({
|
|
341
|
-
|
|
342
|
-
blur: 10,
|
|
343
|
-
tint: .4,
|
|
347
|
+
glassiness: .5,
|
|
344
348
|
quality: "auto",
|
|
345
349
|
reducedTransparency: false
|
|
346
350
|
});
|
|
@@ -455,7 +459,7 @@ var ThemingService = class ThemingService {
|
|
|
455
459
|
palettes = THEME_PALETTES;
|
|
456
460
|
_palette = signal("white", ...ngDevMode ? [{ debugName: "_palette" }] : /* istanbul ignore next */ []);
|
|
457
461
|
palette = this._palette.asReadonly();
|
|
458
|
-
paletteLabel = computed(() => this.palettes.find((item) => item.id === this.palette()).label, ...ngDevMode ? [{ debugName: "paletteLabel" }] : /* istanbul ignore next */ []);
|
|
462
|
+
paletteLabel = computed(() => this.palettes.find((item) => item.id === this.palette())?.label ?? this.palettes[0].label, ...ngDevMode ? [{ debugName: "paletteLabel" }] : /* istanbul ignore next */ []);
|
|
459
463
|
platformId = inject(PLATFORM_ID);
|
|
460
464
|
isBrowser = isPlatformBrowser(this.platformId);
|
|
461
465
|
_systemTheme = signal("light", ...ngDevMode ? [{ debugName: "_systemTheme" }] : /* istanbul ignore next */ []);
|
|
@@ -502,25 +506,18 @@ var ThemingService = class ThemingService {
|
|
|
502
506
|
if (this.isBrowser) this.writeStored("theme-palette", palette);
|
|
503
507
|
}
|
|
504
508
|
setAppearance(settings) {
|
|
509
|
+
const current = this.appearance();
|
|
510
|
+
const glassiness = settings.glassiness ?? current.glassiness;
|
|
505
511
|
const next = {
|
|
506
|
-
|
|
507
|
-
|
|
512
|
+
glassiness: Number.isFinite(glassiness) ? Math.min(1, Math.max(0, glassiness)) : DEFAULT_APPEARANCE_CONFIG.glassiness,
|
|
513
|
+
quality: settings.quality ?? current.quality,
|
|
514
|
+
reducedTransparency: (settings.reducedTransparency ?? current.reducedTransparency) === true
|
|
508
515
|
};
|
|
509
|
-
const finite = (value, fallback, min, max) => Number.isFinite(value) ? Math.min(max, Math.max(min, value)) : fallback;
|
|
510
|
-
next.blur = finite(next.blur, DEFAULT_APPEARANCE_CONFIG.blur, 0, 40);
|
|
511
|
-
next.tint = finite(next.tint, DEFAULT_APPEARANCE_CONFIG.tint, 0, 1);
|
|
512
|
-
if (![
|
|
513
|
-
"glass",
|
|
514
|
-
"frosted",
|
|
515
|
-
"solid"
|
|
516
|
-
].includes(next.material)) next.material = "glass";
|
|
517
516
|
if (![
|
|
518
517
|
"auto",
|
|
519
518
|
"baseline",
|
|
520
519
|
"enhanced"
|
|
521
520
|
].includes(next.quality)) next.quality = "auto";
|
|
522
|
-
for (const key of Object.keys(next)) if (!(key in DEFAULT_APPEARANCE_CONFIG)) delete next[key];
|
|
523
|
-
next.reducedTransparency = next.reducedTransparency === true;
|
|
524
521
|
this._appearance.set(next);
|
|
525
522
|
this.appearanceController?.update(next);
|
|
526
523
|
if (this.isBrowser) this.writeStored("blocks-appearance", JSON.stringify(next));
|
|
@@ -537,7 +534,15 @@ var ThemingService = class ThemingService {
|
|
|
537
534
|
restoreAppearance() {
|
|
538
535
|
try {
|
|
539
536
|
const stored = this.readStored("blocks-appearance");
|
|
540
|
-
if (stored)
|
|
537
|
+
if (stored) {
|
|
538
|
+
const saved = JSON.parse(stored);
|
|
539
|
+
if (!saved || typeof saved !== "object") return;
|
|
540
|
+
this.setAppearance({
|
|
541
|
+
glassiness: saved.glassiness ?? saved.tint ?? DEFAULT_APPEARANCE_CONFIG.glassiness,
|
|
542
|
+
quality: saved.quality ?? DEFAULT_APPEARANCE_CONFIG.quality,
|
|
543
|
+
reducedTransparency: saved.reducedTransparency === true || saved.material === "solid"
|
|
544
|
+
});
|
|
545
|
+
}
|
|
541
546
|
} catch {}
|
|
542
547
|
}
|
|
543
548
|
readStored(key) {
|
|
@@ -1083,7 +1088,7 @@ function inheritOverlayTheme(source, pane) {
|
|
|
1083
1088
|
const current = /* @__PURE__ */ new Set();
|
|
1084
1089
|
for (let index = 0; index < styles.length; index++) {
|
|
1085
1090
|
const property = styles.item(index);
|
|
1086
|
-
if (property.startsWith("--bx-") && !property.startsWith("--bx-glass-")) {
|
|
1091
|
+
if (property.startsWith("--bx-") && property !== "--bx-glassiness" && (!property.startsWith("--bx-glass-") || property === "--bx-glass-radius")) {
|
|
1087
1092
|
pane.style.setProperty(property, styles.getPropertyValue(property));
|
|
1088
1093
|
current.add(property);
|
|
1089
1094
|
}
|