@boyernick/standard-ui-react 0.4.2-canary.65 → 0.4.2-canary.67
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/package.json
CHANGED
|
@@ -18,13 +18,24 @@ export interface GlassConfig {
|
|
|
18
18
|
rim?: number;
|
|
19
19
|
/** Rim light width. Default 3.5, less on small panes. */
|
|
20
20
|
rimWidth?: number;
|
|
21
|
+
/**
|
|
22
|
+
* A hairline shade just inside the outline, deepest away from the light.
|
|
23
|
+
* On a light page the rim light has nothing to brighten, so without it a
|
|
24
|
+
* pane's only edge is its soft shadow. Default 0.16 over a light ground,
|
|
25
|
+
* 0 over a dark one.
|
|
26
|
+
*/
|
|
27
|
+
edge?: number;
|
|
21
28
|
/** Specular sheen on the lit side of the bezel. Default 0.35. */
|
|
22
29
|
specular?: number;
|
|
23
30
|
/** Frost blur, as a Gaussian sigma. Default 3. */
|
|
24
31
|
frost?: number;
|
|
25
32
|
/** Film grain. Default 0.07. */
|
|
26
33
|
grain?: number;
|
|
27
|
-
/**
|
|
34
|
+
/**
|
|
35
|
+
* Multiplier on the ground seen through the glass (1 = undimmed). Default
|
|
36
|
+
* 0.8 over a dark ground, 1 over a light one: dimming a white page only
|
|
37
|
+
* turned the glass a flat mid-grey.
|
|
38
|
+
*/
|
|
28
39
|
dim?: number;
|
|
29
40
|
/** Fraction of the height where the glass starts fading out. Default 1 (no fade). */
|
|
30
41
|
fadeStart?: number;
|
|
@@ -90,6 +101,7 @@ export interface ResolvedGlass {
|
|
|
90
101
|
dispersionBlue: number;
|
|
91
102
|
rim: number;
|
|
92
103
|
rimWidth: number;
|
|
104
|
+
edge: number;
|
|
93
105
|
specular: number;
|
|
94
106
|
frost: number;
|
|
95
107
|
grain: number;
|
|
@@ -121,6 +133,7 @@ export function resolveGlass(
|
|
|
121
133
|
width: number,
|
|
122
134
|
height: number,
|
|
123
135
|
cssRadius: number,
|
|
136
|
+
options?: { lightGround?: boolean },
|
|
124
137
|
): ResolvedGlass {
|
|
125
138
|
const short = Math.max(1, Math.min(width, height));
|
|
126
139
|
const radius = pick(config.cornerRadius, cssRadius, 0, short / 2);
|
|
@@ -139,10 +152,11 @@ export function resolveGlass(
|
|
|
139
152
|
dispersionBlue: pick(config.dispersionBlue, 0.8, 0, 1),
|
|
140
153
|
rim: pick(config.rim, 0.5, 0, 2),
|
|
141
154
|
rimWidth: pick(config.rimWidth, Math.min(3.5, Math.max(1.25, short * 0.04)), 0.5, 12),
|
|
155
|
+
edge: pick(config.edge, options?.lightGround ? 0.16 : 0, 0, 1),
|
|
142
156
|
specular: pick(config.specular, 0.35, 0, 2),
|
|
143
157
|
frost: pick(config.frost, 3, 0, 16),
|
|
144
158
|
grain: pick(config.grain, 0.07, 0, 0.5),
|
|
145
|
-
dim: pick(config.dim, 0.8, 0, 1.5),
|
|
159
|
+
dim: pick(config.dim, options?.lightGround ? 1 : 0.8, 0, 1.5),
|
|
146
160
|
fadeStart: pick(config.fadeStart, 1, 0, 1),
|
|
147
161
|
fadeEnd: pick(config.fadeEnd, 1, 0, 1),
|
|
148
162
|
shadow: pick(config.shadow, 0.18, 0, 1),
|
|
@@ -240,6 +240,33 @@ function imageCopy(image: HTMLImageElement, ready: () => void): CanvasImageSourc
|
|
|
240
240
|
return null;
|
|
241
241
|
}
|
|
242
242
|
|
|
243
|
+
/** Whether a CSS colour is light (relative luminance over one half), cached per colour. */
|
|
244
|
+
const groundLightness = new Map<string, boolean>();
|
|
245
|
+
let lightnessProbe: CanvasRenderingContext2D | null = null;
|
|
246
|
+
|
|
247
|
+
function isLightGround(color: string) {
|
|
248
|
+
const known = groundLightness.get(color);
|
|
249
|
+
if (known !== undefined) return known;
|
|
250
|
+
lightnessProbe ??= Object.assign(document.createElement("canvas"), { width: 1, height: 1 })
|
|
251
|
+
.getContext("2d", { willReadFrequently: true });
|
|
252
|
+
let light = false;
|
|
253
|
+
if (lightnessProbe) {
|
|
254
|
+
// The canvas parses any CSS colour the page can use, oklab and all.
|
|
255
|
+
lightnessProbe.clearRect(0, 0, 1, 1);
|
|
256
|
+
lightnessProbe.fillStyle = "#000";
|
|
257
|
+
lightnessProbe.fillStyle = color;
|
|
258
|
+
lightnessProbe.fillRect(0, 0, 1, 1);
|
|
259
|
+
const [r, g, b] = lightnessProbe.getImageData(0, 0, 1, 1).data;
|
|
260
|
+
const linear = (value: number) => {
|
|
261
|
+
const c = value / 255;
|
|
262
|
+
return c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
|
|
263
|
+
};
|
|
264
|
+
light = 0.2126 * linear(r) + 0.7152 * linear(g) + 0.0722 * linear(b) > 0.5;
|
|
265
|
+
}
|
|
266
|
+
groundLightness.set(color, light);
|
|
267
|
+
return light;
|
|
268
|
+
}
|
|
269
|
+
|
|
243
270
|
/** Whether drawing this source would taint a canvas (its pixels are cross-origin). */
|
|
244
271
|
function unreadable(source: CanvasImageSource) {
|
|
245
272
|
const probe = document.createElement("canvas");
|
|
@@ -580,7 +607,9 @@ export function createLensGlass({ root, glassElements, defaults = {} }: LensGlas
|
|
|
580
607
|
const style = getComputedStyle(element);
|
|
581
608
|
const config = { ...defaults, ...parseConfig(element.dataset.config) };
|
|
582
609
|
const cssRadius = parseFloat(style.borderTopLeftRadius) || 0;
|
|
583
|
-
const glass = resolveGlass(config, rect.width, rect.height, cssRadius
|
|
610
|
+
const glass = resolveGlass(config, rect.width, rect.height, cssRadius, {
|
|
611
|
+
lightGround: isLightGround(ground),
|
|
612
|
+
});
|
|
584
613
|
|
|
585
614
|
const pad = samplePadding(glass) + OVERFLOW;
|
|
586
615
|
const region: Box = {
|
|
@@ -736,7 +765,7 @@ export function createLensGlass({ root, glassElements, defaults = {} }: LensGlas
|
|
|
736
765
|
["u_radius", glass.radius], ["u_power", glass.power], ["u_bezel", glass.bezel],
|
|
737
766
|
["u_bend", glass.bend], ["u_dispersionRed", glass.dispersionRed],
|
|
738
767
|
["u_dispersionBlue", glass.dispersionBlue], ["u_rim", glass.rim],
|
|
739
|
-
["u_rimWidth", glass.rimWidth], ["u_specular", glass.specular], ["u_frost", glass.frost],
|
|
768
|
+
["u_rimWidth", glass.rimWidth], ["u_edge", glass.edge], ["u_specular", glass.specular], ["u_frost", glass.frost],
|
|
740
769
|
["u_grain", glass.grain], ["u_dim", glass.dim], ["u_fadeStart", glass.fadeStart],
|
|
741
770
|
["u_fadeEnd", glass.fadeEnd], ["u_shadow", glass.shadow],
|
|
742
771
|
["u_hover", glass.button ? pane.hover : 0], ["u_press", glass.button ? pane.press : 0],
|
|
@@ -31,6 +31,7 @@ uniform float u_dispersionRed;
|
|
|
31
31
|
uniform float u_dispersionBlue;
|
|
32
32
|
uniform float u_rim;
|
|
33
33
|
uniform float u_rimWidth;
|
|
34
|
+
uniform float u_edge;
|
|
34
35
|
uniform float u_specular;
|
|
35
36
|
uniform float u_frost;
|
|
36
37
|
uniform float u_grain;
|
|
@@ -125,6 +126,11 @@ void main() {
|
|
|
125
126
|
color += u_rim * (1.0 + 0.4 * u_hover) * rim * (0.55 + 0.45 * facing);
|
|
126
127
|
color += u_specular * 0.35 * pow(t, 3.0) * facing;
|
|
127
128
|
|
|
129
|
+
// Hairline edge: a light ground clips the rim to white, so shade one CSS
|
|
130
|
+
// pixel inside the outline instead, more on the side away from the light.
|
|
131
|
+
float hairline = 1.0 - smoothstep(0.0, 1.0, inside);
|
|
132
|
+
color = min(color, vec3(1.0)) * (1.0 - u_edge * hairline * (1.0 - 0.45 * facing));
|
|
133
|
+
|
|
128
134
|
// Grain, fixed to the device pixel grid so a still pane never needs redrawing.
|
|
129
135
|
color += (hash(floor(scenePx * u_dpr)) - 0.5) * u_grain;
|
|
130
136
|
|