@blotch/angular-lynx 22.2.3 → 22.2.5
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 +46 -22
- package/fesm2022/{blotch-angular-lynx-web-glass-8MFrhPz0.mjs → blotch-angular-lynx-web-glass-zlVeee5K.mjs} +104 -29
- package/fesm2022/blotch-angular-lynx-web-glass-zlVeee5K.mjs.map +1 -0
- package/fesm2022/blotch-angular-lynx.mjs +559 -97
- package/fesm2022/blotch-angular-lynx.mjs.map +1 -1
- package/package.json +1 -1
- package/schematics/add-tailwind/index.js +14 -74
- package/schematics/add-tailwind/schema.json +6 -1
- package/schematics/collection.json +1 -1
- package/schematics/ng-add/index.js +4 -58
- package/schematics/ng-add/schema.json +7 -2
- package/schematics/ng-add/versions.js +8 -2
- package/schematics/utility/tailwind.js +167 -0
- package/types/blotch-angular-lynx.d.ts +324 -42
- package/fesm2022/blotch-angular-lynx-web-glass-8MFrhPz0.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -20,11 +20,11 @@ Then work as in any Angular app: `ng serve`, `ng build`, `ng test`, and `ng gene
|
|
|
20
20
|
|
|
21
21
|
- Standalone components with signals, `@if`/`@for`/`@switch`, DI, and pipes
|
|
22
22
|
- Zoneless change detection — signals as the primary reactivity model
|
|
23
|
-
- Full Angular Router with in-memory navigation for Lynx
|
|
23
|
+
- Full Angular Router with in-memory navigation for Lynx, including `routerLink` and `routerLinkActive` on any element
|
|
24
24
|
- Gestures — tap, pan, long-press, fling, pinch, rotation with composition
|
|
25
25
|
- Animations — CSS transitions, keyframes, and programmatic `animate()` API
|
|
26
26
|
- Content projection, portals, `@defer`, and lazy-loaded routes
|
|
27
|
-
- Tailwind CSS
|
|
27
|
+
- Tailwind CSS 4, with the build rewriting its CSS for Lynx (or Tailwind CSS 3 with `@lynx-js/tailwind-preset`)
|
|
28
28
|
|
|
29
29
|
## Documentation
|
|
30
30
|
|
|
@@ -32,6 +32,32 @@ Full docs and guides at [angularlynx.js.org](https://angularlynx.js.org).
|
|
|
32
32
|
|
|
33
33
|
---
|
|
34
34
|
|
|
35
|
+
## Routing
|
|
36
|
+
|
|
37
|
+
`provideRouter()` from `@blotch/angular-lynx` sets up Angular's Router with in-memory history. Everything else comes from `@angular/router`, as on the web, `routerLink` and `routerLinkActive` included:
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import { Component } from '@angular/core';
|
|
41
|
+
import { RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router';
|
|
42
|
+
import { LYNX_ELEMENTS } from '@blotch/angular-lynx';
|
|
43
|
+
|
|
44
|
+
@Component({
|
|
45
|
+
selector: 'app-root',
|
|
46
|
+
imports: [LYNX_ELEMENTS, RouterOutlet, RouterLink, RouterLinkActive],
|
|
47
|
+
template: `
|
|
48
|
+
<view routerLink="/settings" routerLinkActive="active">
|
|
49
|
+
<text>Settings</text>
|
|
50
|
+
</view>
|
|
51
|
+
<router-outlet />
|
|
52
|
+
`,
|
|
53
|
+
})
|
|
54
|
+
export class App {}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
- A tap on the element navigates: the renderer delivers Lynx's `tap` to `click` listeners, as Lynx for Web does, and `RouterLink` listens for `click`.
|
|
58
|
+
- While the link's route is active, `routerLinkActive` adds its classes and `LYNX_ELEMENTS` adds `selected` to the element's `accessibility-traits`, keeping its own (`LynxRouterLinkActive`). On a component's host, list `LynxRouterLinkActive` in `hostDirectives` next to `RouterLinkActive`.
|
|
59
|
+
- Event handlers can navigate directly, with `Router.navigate()` or `Location.back()`.
|
|
60
|
+
|
|
35
61
|
## Animation API
|
|
36
62
|
|
|
37
63
|
Three animation approaches are available, all running natively on the Lynx engine.
|
|
@@ -103,25 +129,23 @@ export class ExampleComponent {
|
|
|
103
129
|
#animation: LynxAnimation | null = null;
|
|
104
130
|
|
|
105
131
|
runAnimation(): void {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
);
|
|
124
|
-
}, 0);
|
|
132
|
+
const el = this.boxRef();
|
|
133
|
+
if (!el) return;
|
|
134
|
+
|
|
135
|
+
// Cancel previous animation before starting a new one.
|
|
136
|
+
this.#animation?.cancel();
|
|
137
|
+
|
|
138
|
+
this.#animation = el.nativeElement.animate(
|
|
139
|
+
[
|
|
140
|
+
{ transform: 'scale(1)', opacity: 1 },
|
|
141
|
+
{ transform: 'scale(1.3)', opacity: 0.7 },
|
|
142
|
+
{ transform: 'scale(1)', opacity: 1 },
|
|
143
|
+
],
|
|
144
|
+
{
|
|
145
|
+
duration: 600,
|
|
146
|
+
easing: 'ease-in-out',
|
|
147
|
+
},
|
|
148
|
+
);
|
|
125
149
|
}
|
|
126
150
|
}
|
|
127
151
|
```
|
|
@@ -181,4 +205,4 @@ Available events: `animationstart`, `animationend`, `animationcancel`, `animatio
|
|
|
181
205
|
#### Important Notes
|
|
182
206
|
|
|
183
207
|
- The `animate()` API is **main-thread only**. Calling it on the background thread throws an error.
|
|
184
|
-
-
|
|
208
|
+
- Call `animate()` straight from an event handler; it doesn't need a `setTimeout`.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const BLUR_VIEW_LAYOUT_CSS = ":host{--lynx-display-toggle:var(--lynx-display-linear);--lynx-display-linear:var(--lynx-display-toggle,);--lynx-display-flex:var(--lynx-display-toggle,);--lynx-linear-orientation-toggle:var(--lynx-linear-orientation-vertical);--lynx-linear-orientation-horizontal:var(--lynx-linear-orientation-toggle,);--lynx-linear-orientation-vertical:var(--lynx-linear-orientation-toggle,);--lynx-linear-orientation-horizontal-reverse:var(--lynx-linear-orientation-toggle,);--lynx-linear-orientation-vertical-reverse:var(--lynx-linear-orientation-toggle,);--linear-flex-direction:var(--lynx-linear-orientation-horizontal,row) var(--lynx-linear-orientation-vertical,column) var(--lynx-linear-orientation-horizontal-reverse,row-reverse) var(--lynx-linear-orientation-vertical-reverse,column-reverse);--linear-justify-content:var(--lynx-linear-orientation-horizontal,var(--justify-content-row)) var(--lynx-linear-orientation-vertical,var(--justify-content-column)) var(--lynx-linear-orientation-horizontal-reverse,var(--justify-content-row-reverse)) var(--lynx-linear-orientation-vertical-reverse,var(--justify-content-column-reverse));flex-wrap:var(--lynx-display-linear,nowrap) var(--lynx-display-flex,var(--flex-wrap));flex-direction:var(--lynx-display-linear,var(--linear-flex-direction)) var(--lynx-display-flex,var(--flex-direction));justify-content:var(--lynx-display-linear,var(--linear-justify-content))}";
|
|
1
|
+
const BLUR_VIEW_LAYOUT_CSS = ":host{--lynx-display-toggle:var(--lynx-display-linear);--lynx-display-linear:var(--lynx-display-toggle,);--lynx-display-flex:var(--lynx-display-toggle,);--lynx-linear-orientation-toggle:var(--lynx-linear-orientation-vertical);--lynx-linear-orientation-horizontal:var(--lynx-linear-orientation-toggle,);--lynx-linear-orientation-vertical:var(--lynx-linear-orientation-toggle,);--lynx-linear-orientation-horizontal-reverse:var(--lynx-linear-orientation-toggle,);--lynx-linear-orientation-vertical-reverse:var(--lynx-linear-orientation-toggle,);--linear-flex-direction:var(--lynx-linear-orientation-horizontal,row) var(--lynx-linear-orientation-vertical,column) var(--lynx-linear-orientation-horizontal-reverse,row-reverse) var(--lynx-linear-orientation-vertical-reverse,column-reverse);--linear-justify-content:var(--lynx-linear-orientation-horizontal,var(--justify-content-row)) var(--lynx-linear-orientation-vertical,var(--justify-content-column)) var(--lynx-linear-orientation-horizontal-reverse,var(--justify-content-row-reverse)) var(--lynx-linear-orientation-vertical-reverse,var(--justify-content-column-reverse));flex-wrap:var(--lynx-display-linear,nowrap) var(--lynx-display-flex,var(--flex-wrap));flex-direction:var(--lynx-display-linear,var(--linear-flex-direction)) var(--lynx-display-flex,var(--flex-direction));justify-content:var(--lynx-display-linear,var(--linear-justify-content))}slot{--lynx-display:inherit;--lynx-linear-orientation:inherit;--lynx-linear-weight-sum:inherit}";
|
|
2
2
|
const applyBlurViewLayout = (element) => {
|
|
3
3
|
const root = element.shadowRoot;
|
|
4
4
|
if (!root) return;
|
|
@@ -101,11 +101,16 @@ const defaultsByDocument = /* @__PURE__ */ new WeakMap();
|
|
|
101
101
|
const defaultStyle = (doc, win) => {
|
|
102
102
|
let defaults = defaultsByDocument.get(doc);
|
|
103
103
|
if (!defaults) {
|
|
104
|
+
const host = doc.createElement("div");
|
|
105
|
+
const root = host.attachShadow({ mode: "open" });
|
|
106
|
+
const reset = doc.createElement("style");
|
|
107
|
+
reset.textContent = ":host{all:initial}";
|
|
104
108
|
const probe = doc.createElement("div");
|
|
105
|
-
|
|
109
|
+
root.append(reset, probe);
|
|
110
|
+
doc.documentElement.appendChild(host);
|
|
106
111
|
const style = win.getComputedStyle(probe);
|
|
107
112
|
defaults = new Map(Array.from(style, (name) => [name, style.getPropertyValue(name)]));
|
|
108
|
-
|
|
113
|
+
host.remove();
|
|
109
114
|
defaultsByDocument.set(doc, defaults);
|
|
110
115
|
}
|
|
111
116
|
return defaults;
|
|
@@ -218,10 +223,11 @@ const snapshotBackdrop = async (root, options) => {
|
|
|
218
223
|
const width = (right - left) / zoom;
|
|
219
224
|
const height = (bottom - top) / zoom;
|
|
220
225
|
const scale = Math.min(options.scale, MAX_IMAGE_SIZE / Math.max(width, height));
|
|
221
|
-
const markup = new win.XMLSerializer().serializeToString(cloneBackdrop(root, options));
|
|
222
226
|
const pixelWidth = Math.max(1, Math.round(width * scale));
|
|
223
227
|
const pixelHeight = Math.max(1, Math.round(height * scale));
|
|
224
|
-
const
|
|
228
|
+
const frame = cloneBackdrop(root, options);
|
|
229
|
+
frame.style.cssText += `transform-origin:0 0;transform:scale(${pixelWidth / width},${pixelHeight / height}) translate(${-x}px,${-y}px);`;
|
|
230
|
+
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${pixelWidth}" height="${pixelHeight}"><foreignObject width="${pixelWidth}" height="${pixelHeight}">${new win.XMLSerializer().serializeToString(frame)}</foreignObject></svg>`;
|
|
225
231
|
const picture = new win.Image();
|
|
226
232
|
picture.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
|
|
227
233
|
await picture.decode();
|
|
@@ -375,7 +381,13 @@ void main() {
|
|
|
375
381
|
float reach = length(p - u_pointer.xy);
|
|
376
382
|
color += u_pointer.z * 0.2 * exp(-reach * reach / (1.2 * lens * lens));
|
|
377
383
|
|
|
378
|
-
|
|
384
|
+
// Light glass over white is white on white: the frost, the rim and the glow
|
|
385
|
+
// all brighten. UIKit outlines it with a gray hairline, about two device
|
|
386
|
+
// pixels deep, and so does this. The dark material keeps its bright rim.
|
|
387
|
+
float hairline = (1.0 - smoothstep(0.0, 2.5, depth * u_scale)) * (1.0 - u_dark);
|
|
388
|
+
color = clamp(color, 0.0, 1.0) * (1.0 - 0.32 * hairline);
|
|
389
|
+
|
|
390
|
+
gl_FragColor = vec4(color * coverage, coverage + shadow * (1.0 - coverage));
|
|
379
391
|
}`;
|
|
380
392
|
const compile = (gl, fragmentSource) => {
|
|
381
393
|
const program = gl.createProgram();
|
|
@@ -645,9 +657,47 @@ const readShape = (element, origin, zoom, elapsed, grow) => {
|
|
|
645
657
|
};
|
|
646
658
|
};
|
|
647
659
|
const CAPTURE_MARGIN = 48;
|
|
660
|
+
const CONTAINER_MARGIN = 32;
|
|
648
661
|
const MIN_CAPTURE_INTERVAL = 100;
|
|
649
662
|
const MAX_SCALE = 2;
|
|
650
663
|
const DARK_LUMINANCE = .4;
|
|
664
|
+
const isFixed = (win, element) => win.getComputedStyle(element).position === "fixed";
|
|
665
|
+
const backdropOf = (root) => {
|
|
666
|
+
const win = root.ownerDocument.defaultView;
|
|
667
|
+
let layer = isFixed(win, root) ? root : null;
|
|
668
|
+
let element = root.parentElement;
|
|
669
|
+
while (element?.parentElement) {
|
|
670
|
+
if (!layer) {
|
|
671
|
+
const style = win.getComputedStyle(element);
|
|
672
|
+
if (style.backgroundImage && style.backgroundImage !== "none" || parseNormalizedColor(style.backgroundColor)[3] >= 1) return {
|
|
673
|
+
element,
|
|
674
|
+
layer
|
|
675
|
+
};
|
|
676
|
+
if (style.position === "fixed") layer = element;
|
|
677
|
+
}
|
|
678
|
+
element = element.parentElement;
|
|
679
|
+
}
|
|
680
|
+
return element && {
|
|
681
|
+
element,
|
|
682
|
+
layer
|
|
683
|
+
};
|
|
684
|
+
};
|
|
685
|
+
const drawnAbove = (root, { element: backdrop, layer }) => {
|
|
686
|
+
const win = root.ownerDocument.defaultView;
|
|
687
|
+
if (layer) {
|
|
688
|
+
const zIndex = (element) => parseInt(win.getComputedStyle(element).zIndex, 10) || 0;
|
|
689
|
+
const layerZ = zIndex(layer);
|
|
690
|
+
return (element) => {
|
|
691
|
+
if (element === layer) return true;
|
|
692
|
+
if (element.contains(layer) || !isFixed(win, element)) return false;
|
|
693
|
+
const z = zIndex(element);
|
|
694
|
+
return z > layerZ || z === layerZ && (layer.compareDocumentPosition(element) & 4) !== 0;
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
const above = /* @__PURE__ */ new Set([root]);
|
|
698
|
+
for (let node = root; node && node !== backdrop; node = node.parentElement) for (let sibling = node.nextElementSibling; sibling; sibling = sibling.nextElementSibling) above.add(sibling);
|
|
699
|
+
return (element) => above.has(element);
|
|
700
|
+
};
|
|
651
701
|
var GlassGroup = class {
|
|
652
702
|
root;
|
|
653
703
|
container;
|
|
@@ -655,6 +705,7 @@ var GlassGroup = class {
|
|
|
655
705
|
#canvas;
|
|
656
706
|
#renderer;
|
|
657
707
|
#backdrop;
|
|
708
|
+
#margin;
|
|
658
709
|
#stopObserving = null;
|
|
659
710
|
#snapshot = null;
|
|
660
711
|
#stale = true;
|
|
@@ -670,12 +721,14 @@ var GlassGroup = class {
|
|
|
670
721
|
this.#win = doc.defaultView;
|
|
671
722
|
this.#canvas = doc.createElement("canvas");
|
|
672
723
|
this.#canvas.setAttribute("aria-hidden", "true");
|
|
673
|
-
this.#
|
|
724
|
+
this.#margin = container ? CONTAINER_MARGIN : 0;
|
|
725
|
+
const size = `calc(100% + ${2 * this.#margin}px)`;
|
|
726
|
+
this.#canvas.style.cssText = `position:absolute;left:${-this.#margin}px;top:${-this.#margin}px;width:${size};height:${size};pointer-events:none;z-index:-1;opacity:0;transition:opacity 0.2s`;
|
|
674
727
|
const shadow = root.shadowRoot;
|
|
675
728
|
shadow?.insertBefore(this.#canvas, shadow.querySelector("slot"));
|
|
676
729
|
this.#renderer = shadow ? GlassRenderer.create(this.#canvas) : null;
|
|
677
|
-
this.#backdrop = root
|
|
678
|
-
if (this.#renderer && this.#backdrop) this.#stopObserving = observeBackdrop(this.#backdrop, root, () => {
|
|
730
|
+
this.#backdrop = backdropOf(root);
|
|
731
|
+
if (this.#renderer && this.#backdrop) this.#stopObserving = observeBackdrop(this.#backdrop.element, this.#backdrop.layer ?? root, () => {
|
|
679
732
|
this.#stale = true;
|
|
680
733
|
});
|
|
681
734
|
}
|
|
@@ -690,7 +743,7 @@ var GlassGroup = class {
|
|
|
690
743
|
}
|
|
691
744
|
frame(elapsed) {
|
|
692
745
|
const renderer = this.#renderer;
|
|
693
|
-
const backdrop = this.#backdrop;
|
|
746
|
+
const backdrop = this.#backdrop?.element;
|
|
694
747
|
if (!renderer || !backdrop || this.#failed) return;
|
|
695
748
|
const origin = this.#canvas.getBoundingClientRect();
|
|
696
749
|
const area = backdrop.getBoundingClientRect();
|
|
@@ -700,14 +753,14 @@ var GlassGroup = class {
|
|
|
700
753
|
if (!snapshot) return;
|
|
701
754
|
const scale = Math.min(this.#win.devicePixelRatio || 1, MAX_SCALE) * zoom;
|
|
702
755
|
const elements = this.shapes;
|
|
703
|
-
const
|
|
756
|
+
const read = elements.map((element) => readShape(element, origin, zoom, elapsed, this.container));
|
|
704
757
|
let pointer = [
|
|
705
758
|
0,
|
|
706
759
|
0,
|
|
707
760
|
0
|
|
708
761
|
];
|
|
709
762
|
elements.forEach((element, i) => {
|
|
710
|
-
const value =
|
|
763
|
+
const value = read[i].press;
|
|
711
764
|
if (value > pointer[2]) {
|
|
712
765
|
const { x, y } = pressOf(element);
|
|
713
766
|
pointer = [
|
|
@@ -717,6 +770,7 @@ var GlassGroup = class {
|
|
|
717
770
|
];
|
|
718
771
|
}
|
|
719
772
|
});
|
|
773
|
+
const shapes = read.filter((shape) => shape.halfWidth >= .5 && shape.halfHeight >= .5);
|
|
720
774
|
const spacing = parseFloat(this.root.getAttribute("spacing") ?? "") || 0;
|
|
721
775
|
const frame = {
|
|
722
776
|
scale,
|
|
@@ -768,11 +822,9 @@ var GlassGroup = class {
|
|
|
768
822
|
this.#capturing = true;
|
|
769
823
|
this.#stale = false;
|
|
770
824
|
const started = win.performance.now();
|
|
771
|
-
const
|
|
772
|
-
for (let node = this.root; node; node = node.nextElementSibling) above.add(node);
|
|
773
|
-
const margin = CAPTURE_MARGIN * zoom;
|
|
825
|
+
const margin = (CAPTURE_MARGIN - this.#margin) * zoom;
|
|
774
826
|
snapshotBackdrop(backdrop, {
|
|
775
|
-
exclude: (
|
|
827
|
+
exclude: drawnAbove(this.root, this.#backdrop),
|
|
776
828
|
region: {
|
|
777
829
|
left: origin.left - margin,
|
|
778
830
|
top: origin.top - margin,
|
|
@@ -799,7 +851,16 @@ const DEFAULT_FALLBACK_BLUR = 12;
|
|
|
799
851
|
const fallbackGlassCss = (element) => {
|
|
800
852
|
const clear = element.getAttribute("glass-style") === "clear";
|
|
801
853
|
const filter = `blur(${parseFloat(element.getAttribute("blur-radius") ?? "") || (clear ? DEFAULT_FALLBACK_BLUR / 3 : DEFAULT_FALLBACK_BLUR)}px) saturate(${clear ? 140 : 180}%)`;
|
|
802
|
-
return `:host{backdrop-filter:${filter};-webkit-backdrop-filter:${filter};background-color:${element.getAttribute("glass-tint-color") || `rgba(255, 255, 255, ${clear ? .04 : .18})`}
|
|
854
|
+
return `:host{backdrop-filter:${filter};-webkit-backdrop-filter:${filter};background-color:${element.getAttribute("glass-tint-color") || `rgba(255, 255, 255, ${clear ? .04 : .18})`}}`;
|
|
855
|
+
};
|
|
856
|
+
const FALLBACK_EDGES = "inset 0 0 0 1px rgba(0, 0, 0, 0.08), inset 0 2px 0 rgba(255, 255, 255, 0.5), inset 0 0 0 2px rgba(255, 255, 255, 0.25)";
|
|
857
|
+
const SMALL_SHADOW_SIDE = 44;
|
|
858
|
+
const glassShadow = (element) => {
|
|
859
|
+
const reach = (Math.min(element.offsetWidth, element.offsetHeight) - SMALL_SHADOW_SIDE) / 116;
|
|
860
|
+
const t = Math.round(Math.min(Math.max(reach, 0), 1) * 10) / 10;
|
|
861
|
+
const mix = (small, large) => Math.round((small + (large - small) * t) * 1e3) / 1e3;
|
|
862
|
+
const shadow = `0 ${mix(2, 8)}px ${mix(10, 32)}px rgba(0, 0, 0, ${mix(.06, .08)})`;
|
|
863
|
+
return t ? `${shadow}, 0 1px 4px rgba(0, 0, 0, ${mix(0, .04)})` : shadow;
|
|
803
864
|
};
|
|
804
865
|
const materialCss = (element) => {
|
|
805
866
|
const effect = element.getAttribute("blur-effect") ?? "light";
|
|
@@ -810,12 +871,18 @@ const materialCss = (element) => {
|
|
|
810
871
|
const filter = `blur(${radius}px) brightness(1.35)`;
|
|
811
872
|
return `:host{backdrop-filter:${filter};-webkit-backdrop-filter:${filter}}`;
|
|
812
873
|
};
|
|
813
|
-
const hostStyleCss = (element, state) =>
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
874
|
+
const hostStyleCss = (element, state) => {
|
|
875
|
+
const shadows = [state.shadow ? glassShadow(element) : "", state.fallback ? FALLBACK_EDGES : ""].filter(Boolean);
|
|
876
|
+
return [
|
|
877
|
+
materialCss(element),
|
|
878
|
+
element.getAttribute("blur-effect") === "glass-container" ? ":host{overflow:visible!important}" : "",
|
|
879
|
+
state.hostsCanvas ? ":host{isolation:isolate}" : "",
|
|
880
|
+
state.rendered ? ":host{backdrop-filter:none!important;-webkit-backdrop-filter:none!important}" : "",
|
|
881
|
+
state.fallback ? fallbackGlassCss(element) : "",
|
|
882
|
+
shadows.length ? `:host{box-shadow:${shadows.join(", ")}}` : "",
|
|
883
|
+
state.scale !== 1 ? `:host{scale:${Math.round(state.scale * 1e3) / 1e3}}` : ""
|
|
884
|
+
].join("");
|
|
885
|
+
};
|
|
819
886
|
const applyHostStyle = (element, state) => {
|
|
820
887
|
const root = element.shadowRoot;
|
|
821
888
|
if (!root) return;
|
|
@@ -927,11 +994,19 @@ var GlassManager = class GlassManager {
|
|
|
927
994
|
if (group.active) rendered.add(shape);
|
|
928
995
|
}
|
|
929
996
|
}
|
|
930
|
-
for (const element of styled)
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
fallback
|
|
934
|
-
|
|
997
|
+
for (const element of styled) {
|
|
998
|
+
const group = this.#groups.get(element);
|
|
999
|
+
const glass = element.getAttribute("blur-effect") === "glass";
|
|
1000
|
+
const fallback = glass && !rendered.has(element);
|
|
1001
|
+
const lone = group !== void 0 && !group.container;
|
|
1002
|
+
applyHostStyle(element, {
|
|
1003
|
+
hostsCanvas: group?.active ?? false,
|
|
1004
|
+
rendered: rendered.has(element),
|
|
1005
|
+
fallback,
|
|
1006
|
+
shadow: glass && (lone || fallback),
|
|
1007
|
+
scale: lone ? 1 + PRESS_GROWTH * (pressOf(element)?.value ?? 0) : 1
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
935
1010
|
if (this.#groups.size) this.#wake();
|
|
936
1011
|
else this.#last = 0;
|
|
937
1012
|
}
|
|
@@ -948,4 +1023,4 @@ const attachWebGlass = (element) => {
|
|
|
948
1023
|
};
|
|
949
1024
|
export { attachWebGlass };
|
|
950
1025
|
|
|
951
|
-
//# sourceMappingURL=blotch-angular-lynx-web-glass-
|
|
1026
|
+
//# sourceMappingURL=blotch-angular-lynx-web-glass-zlVeee5K.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blotch-angular-lynx-web-glass-zlVeee5K.mjs","names":[],"sources":["../../src/lib/web-glass/blur-view-layout.ts","../../src/lib/web-glass/backdrop-observer.ts","../../src/lib/web-glass/css-color.ts","../../src/lib/web-glass/drawn-scale.ts","../../src/lib/web-glass/backdrop-clone.ts","../../src/lib/web-glass/backdrop-snapshot.ts","../../src/lib/web-glass/glass-shaders.ts","../../src/lib/web-glass/glass-gl.ts","../../src/lib/web-glass/glass-renderer.ts","../../src/lib/web-glass/glass-press.ts","../../src/lib/web-glass/glass-shapes.ts","../../src/lib/web-glass/glass-group.ts","../../src/lib/web-glass/glass-host-style.ts","../../src/lib/web-glass/glass-manager.ts","../../src/lib/web-glass/web-glass.ts"],"sourcesContent":["/**\n * web-elements' container layout rules for `<x-view>` (`linear.css`), as\n * `:host` rules for `<x-blur-view>`.\n *\n * On iOS a blur view lays its children out like any view: `flex-col`,\n * `flex-row` and the default linear layout all apply. web-elements gives\n * `<x-blur-view>` `display: flex` but leaves it out of the rules that turn\n * Lynx's layout variables (the display toggle, the linear orientation,\n * `--flex-direction`) into CSS; those list only `<x-view>` and the other\n * containers. So on web a blur view ignored `flex-col` and set its children\n * out in a row: the element-blur-view example's label sat centered across the\n * top of its panel instead of vertically centered at the left.\n *\n * A `:host` rule loses to every style from outside the shadow root, so a class\n * such as `flex` (which sets `--lynx-display-toggle`) or an inline style still\n * wins, exactly as it does over web-elements' own `x-view` rules.\n *\n * Its children need one more rule to lay out as flex items. web-elements\n * sizes an item (`flex-1`, a linear weight) by container style queries on its\n * parent's `--lynx-display` and `--lynx-linear-orientation`. A blur view's\n * children sit in its shadow root's `<slot>`, which is the parent those\n * queries see, and the variables don't inherit, so `flex-1` did nothing in a\n * blur view: a glass capsule beside a button in a glass group stayed as\n * narrow as its content. The slot takes them from the host.\n */\nexport const BLUR_VIEW_LAYOUT_CSS =\n ':host{' +\n '--lynx-display-toggle:var(--lynx-display-linear);' +\n '--lynx-display-linear:var(--lynx-display-toggle,);' +\n '--lynx-display-flex:var(--lynx-display-toggle,);' +\n '--lynx-linear-orientation-toggle:var(--lynx-linear-orientation-vertical);' +\n '--lynx-linear-orientation-horizontal:var(--lynx-linear-orientation-toggle,);' +\n '--lynx-linear-orientation-vertical:var(--lynx-linear-orientation-toggle,);' +\n '--lynx-linear-orientation-horizontal-reverse:var(--lynx-linear-orientation-toggle,);' +\n '--lynx-linear-orientation-vertical-reverse:var(--lynx-linear-orientation-toggle,);' +\n '--linear-flex-direction:var(--lynx-linear-orientation-horizontal,row) ' +\n 'var(--lynx-linear-orientation-vertical,column) ' +\n 'var(--lynx-linear-orientation-horizontal-reverse,row-reverse) ' +\n 'var(--lynx-linear-orientation-vertical-reverse,column-reverse);' +\n '--linear-justify-content:var(--lynx-linear-orientation-horizontal,var(--justify-content-row)) ' +\n 'var(--lynx-linear-orientation-vertical,var(--justify-content-column)) ' +\n 'var(--lynx-linear-orientation-horizontal-reverse,var(--justify-content-row-reverse)) ' +\n 'var(--lynx-linear-orientation-vertical-reverse,var(--justify-content-column-reverse));' +\n 'flex-wrap:var(--lynx-display-linear,nowrap) var(--lynx-display-flex,var(--flex-wrap));' +\n 'flex-direction:var(--lynx-display-linear,var(--linear-flex-direction)) ' +\n 'var(--lynx-display-flex,var(--flex-direction));' +\n 'justify-content:var(--lynx-display-linear,var(--linear-justify-content))' +\n '}' +\n 'slot{--lynx-display:inherit;--lynx-linear-orientation:inherit;' +\n '--lynx-linear-weight-sum:inherit}';\n\n/**\n * Gives a `<blur-view>` on Lynx for Web the layout of a `<view>` (see\n * {@link BLUR_VIEW_LAYOUT_CSS}), through a `<style>` in its own shadow root.\n * Like the glass styles, it stays inside the element: Lynx owns its `class`\n * and `style` attributes.\n */\nexport const applyBlurViewLayout = (element: HTMLElement): void => {\n const root = element.shadowRoot;\n if (!root) return;\n const style = element.ownerDocument.createElement('style');\n style.textContent = BLUR_VIEW_LAYOUT_CSS;\n root.appendChild(style);\n};\n","/**\n * Calls `onChange` whenever what's behind the glass may look different:\n * something in `backdrop` mutates, resizes, scrolls or loads, web fonts\n * finish loading, or the color scheme flips. Changes inside `glass` itself (a\n * blob dragged by its left/top) don't count, since they don't change what's\n * behind it. Returns a function that stops observing.\n */\nexport const observeBackdrop = (\n backdrop: HTMLElement,\n glass: Element,\n onChange: () => void,\n): (() => void) => {\n const win = backdrop.ownerDocument.defaultView!;\n const mutations = new win.MutationObserver((records) => {\n if (records.some((record) => !glass.contains(record.target))) onChange();\n });\n mutations.observe(backdrop, {\n subtree: true,\n childList: true,\n attributes: true,\n characterData: true,\n });\n const resizes = new win.ResizeObserver(onChange);\n resizes.observe(backdrop);\n // Scroll and image load events don't bubble, but they do reach a capturing\n // listener on an ancestor.\n backdrop.addEventListener('scroll', onChange, true);\n backdrop.addEventListener('load', onChange, true);\n const scheme = win.matchMedia('(prefers-color-scheme: dark)');\n scheme.addEventListener('change', onChange);\n // Text reflows once web fonts load, moving what's behind the glass.\n void backdrop.ownerDocument.fonts.ready.then(onChange);\n return () => {\n mutations.disconnect();\n resizes.disconnect();\n backdrop.removeEventListener('scroll', onChange, true);\n backdrop.removeEventListener('load', onChange, true);\n scheme.removeEventListener('change', onChange);\n };\n};\n","/** A color as red, green, blue and alpha channels, each from 0 to 1. */\nexport type Rgba = readonly [number, number, number, number];\n\nexport const TRANSPARENT: Rgba = [0, 0, 0, 0];\n\nconst RGB_FUNCTION = /^rgba?\\(([^)]*)\\)$/;\nconst HEX = /^#([0-9a-f]{6})$/i;\n\n/**\n * Parses the two forms a browser hands back for a color it has normalized:\n * six-digit hex (an opaque canvas `fillStyle`) and `rgb()`/`rgba()` (computed\n * styles, a translucent `fillStyle`). Anything else is transparent, so a value\n * the browser couldn't parse draws nothing rather than black.\n */\nexport const parseNormalizedColor = (value: string): Rgba => {\n const hex = HEX.exec(value);\n if (hex) {\n const n = parseInt(hex[1]!, 16);\n return [(n >> 16) / 255, ((n >> 8) & 255) / 255, (n & 255) / 255, 1];\n }\n const fn = RGB_FUNCTION.exec(value.trim());\n if (!fn) return TRANSPARENT;\n const parts = fn[1]!\n .split(/[\\s,/]+/)\n .filter(Boolean)\n .map(Number);\n if (parts.length < 3 || parts.some(Number.isNaN)) return TRANSPARENT;\n const [r, g, b, a = 1] = parts as [number, number, number, number?];\n return [r / 255, g / 255, b / 255, a];\n};\n\nconst cache = new Map<string, Rgba>();\nlet context: CanvasRenderingContext2D | null | undefined;\n\n/**\n * Resolves any CSS color an author can write (`red`, `#3b82f6`, `rgba(…)`)\n * into channels. A canvas `fillStyle` normalizes the value without touching the\n * DOM or forcing a style recalculation. An invalid value leaves `fillStyle` unchanged,\n * so a sentinel set first tells the two apart.\n */\nexport const resolveCssColor = (doc: Document, value: string | null): Rgba => {\n if (!value) return TRANSPARENT;\n const cached = cache.get(value);\n if (cached) return cached;\n context ??= doc.createElement('canvas').getContext('2d');\n if (!context) return TRANSPARENT;\n const sentinel = 'rgba(1, 2, 3, 0.5)';\n context.fillStyle = sentinel;\n context.fillStyle = value;\n const normalized = String(context.fillStyle);\n const color =\n normalized === sentinel ? TRANSPARENT : parseNormalizedColor(normalized);\n cache.set(value, color);\n return color;\n};\n\n/** Relative luminance, used to pick the light or dark glass material. */\nexport const luminance = ([r, g, b]: Rgba): number =>\n 0.2126 * r + 0.7152 * g + 0.0722 * b;\n","/**\n * Screen pixels per CSS pixel that `element` is drawn at: below 1 when a\n * transform on it or an ancestor scales it down, as the docs' previews do to\n * fit a phone-sized page in their pane. getBoundingClientRect() measures in\n * screen pixels, but layout and every CSS length are in CSS pixels, so the\n * glass code divides boxes by this to keep to CSS pixels.\n */\nexport const drawnScale = (element: HTMLElement): number => {\n const drawn = element.getBoundingClientRect().width;\n // A transform doesn't change offsetWidth.\n const laidOut = element.offsetWidth;\n // offsetWidth is rounded to a whole pixel, so a difference of less than one\n // is rounding, not scale.\n if (!laidOut || Math.abs(drawn - laidOut) < 1) return 1;\n return drawn / laidOut;\n};\n","import { parseNormalizedColor, type Rgba } from './css-color';\nimport { drawnScale } from './drawn-scale';\n\n/** A rectangle in viewport (client) coordinates. */\nexport type Region = {\n left: number;\n top: number;\n right: number;\n bottom: number;\n};\n\nexport type CloneOptions = {\n /** Leaves an element, and everything inside it, out of the copy. */\n exclude: (element: Element) => boolean;\n /**\n * The part of the page the glass can show. A clipping element wholly\n * outside it is copied empty, which keeps long lists cheap to capture.\n */\n region: Region;\n};\n\ntype Context = {\n doc: Document;\n win: Window;\n defaults: Map<string, string>;\n} & CloneOptions;\n\n// Properties that can't change a still image, or that would start animating\n// inside it. Custom properties are already resolved into the real ones.\nconst SKIPPED_PROPERTY =\n /^(--|transition|animation|will-change|cursor|pointer-events|user-select|-webkit-user-)/;\n// Widths whose computed value matches a plain div's only because that div has\n// no border or outline: `0px` comes from `border-style: none`. Lynx for Web\n// gives every element `border-style: solid`, so leaving the width out would\n// draw a 3px (`medium`) black border in the copy.\nconst ALWAYS_INLINED =\n /^(border-(top|right|bottom|left)-width|outline-width|column-rule-width)$/;\nconst SKIPPED_TAGS = new Set(['style', 'script', 'template', 'link', 'meta']);\n\n// Node types, spelled out: on Lynx for Web the main thread runs in an iframe's\n// realm while the elements belong to the page, so `instanceof` checks against\n// this realm's DOM classes would always fail.\nconst ELEMENT_NODE = 1;\nconst TEXT_NODE = 3;\n\nconst defaultsByDocument = new WeakMap<Document, Map<string, string>>();\n\n/**\n * A plain div's computed style, as the copy's image draws it: anything\n * matching it needn't be inlined. The image has the browser's own styles\n * and no others, so the div is probed in a shadow root of its own, reset\n * with `all: initial`. Probed in the page, it took the page's styles too:\n * the docs site makes every element `border-box`, which was then left out,\n * and in the image a padded card grew by its padding and pushed what's in\n * it down.\n */\nconst defaultStyle = (doc: Document, win: Window): Map<string, string> => {\n let defaults = defaultsByDocument.get(doc);\n if (!defaults) {\n const host = doc.createElement('div');\n const root = host.attachShadow({ mode: 'open' });\n const reset = doc.createElement('style');\n reset.textContent = ':host{all:initial}';\n const probe = doc.createElement('div');\n root.append(reset, probe);\n doc.documentElement.appendChild(host);\n const style = win.getComputedStyle(probe);\n defaults = new Map(\n Array.from(style, (name) => [name, style.getPropertyValue(name)]),\n );\n host.remove();\n defaultsByDocument.set(doc, defaults);\n }\n return defaults;\n};\n\n/**\n * Inlines a computed style, keeping each property that differs from a plain\n * div or from the parent. The parent check matters for inherited properties:\n * a black heading inside white text matches the default but would inherit\n * white in the copy if it were left out.\n */\nconst inlineStyle = (\n style: CSSStyleDeclaration,\n parent: CSSStyleDeclaration | null,\n defaults: Map<string, string>,\n): string => {\n let css = '';\n for (const name of Array.from(style)) {\n if (SKIPPED_PROPERTY.test(name)) continue;\n const value = style.getPropertyValue(name);\n if (\n ALWAYS_INLINED.test(name) ||\n value !== defaults.get(name) ||\n (parent && value !== parent.getPropertyValue(name))\n ) {\n css += `${name}:${value};`;\n }\n }\n return css;\n};\n\n/**\n * Children as they render: a shadow root's children instead of the light\n * ones, with each `<slot>` replaced by what's assigned to it. Every Lynx for\n * Web element is a custom element that draws its children through a slot.\n */\nconst renderedChildren = (element: Element): Node[] =>\n Array.from(element.shadowRoot?.childNodes ?? element.childNodes).flatMap(\n (child) =>\n (child as Element).localName === 'slot'\n ? (child as HTMLSlotElement).assignedNodes({ flatten: true })\n : [child],\n );\n\n/**\n * Moves a child of a scrolled element by the scroll offset, so the copy shows\n * what's scrolled into view rather than the top of the content.\n */\nconst scrollShift = (\n style: CSSStyleDeclaration,\n [x, y]: readonly [number, number],\n): string => {\n if (!x && !y) return '';\n if (style.position === 'fixed' || style.position === 'sticky') return '';\n if (style.position === 'static') {\n return `position:relative;left:${-x}px;top:${-y}px;`;\n }\n const left = (parseFloat(style.left) || 0) - x;\n const top = (parseFloat(style.top) || 0) - y;\n return `left:${left}px;top:${top}px;`;\n};\n\n/**\n * Inlines an image as a data URL: an SVG image can't load anything external.\n * A cross-origin image without CORS taints the canvas and is left blank.\n */\nconst mediaSource = (element: Element, doc: Document): string => {\n try {\n if (element.localName === 'canvas') {\n return (element as HTMLCanvasElement).toDataURL();\n }\n const image = element as HTMLImageElement;\n if (!image.complete || !image.naturalWidth) return '';\n const canvas = doc.createElement('canvas');\n canvas.width = image.naturalWidth;\n canvas.height = image.naturalHeight;\n canvas.getContext('2d')!.drawImage(image, 0, 0);\n return canvas.toDataURL();\n } catch {\n return '';\n }\n};\n\nconst outside = (rect: DOMRect, region: Region): boolean =>\n rect.right <= region.left ||\n rect.left >= region.right ||\n rect.bottom <= region.top ||\n rect.top >= region.bottom;\n\nconst cloneNode = (\n node: Node,\n context: Context,\n parent: CSSStyleDeclaration | null,\n scroll: readonly [number, number],\n): Node | null => {\n if (node.nodeType === TEXT_NODE) {\n return context.doc.createTextNode((node as Text).data);\n }\n if (node.nodeType !== ELEMENT_NODE) return null;\n const element = node as Element;\n if (SKIPPED_TAGS.has(element.localName) || context.exclude(element)) {\n return null;\n }\n const style = context.win.getComputedStyle(element);\n if (style.display === 'none') return null;\n\n const media = element.localName === 'img' || element.localName === 'canvas';\n const clone = context.doc.createElement(media ? 'img' : 'div');\n clone.setAttribute(\n 'style',\n inlineStyle(style, parent, context.defaults) + scrollShift(style, scroll),\n );\n if (media) {\n const source = mediaSource(element, context.doc);\n if (source) clone.setAttribute('src', source);\n return clone;\n }\n const clips = style.overflowX !== 'visible' || style.overflowY !== 'visible';\n if (clips && outside(element.getBoundingClientRect(), context.region)) {\n return clone;\n }\n const childScroll = [element.scrollLeft, element.scrollTop] as const;\n for (const child of renderedChildren(element)) {\n const copy = cloneNode(child, context, style, childScroll);\n if (copy) clone.appendChild(copy);\n }\n return clone;\n};\n\n/**\n * The color behind an element: its own background, or the first ancestor's\n * that isn't transparent, crossing shadow roots. White when nothing paints.\n */\nexport const effectiveBackground = (element: Element): Rgba => {\n const win = element.ownerDocument.defaultView!;\n let node: Element | null = element;\n while (node) {\n const color = parseNormalizedColor(\n win.getComputedStyle(node).backgroundColor,\n );\n if (color[3] > 0) return color;\n node =\n node.parentElement ??\n ((node.getRootNode() as Partial<ShadowRoot>).host as Element | null) ??\n null;\n }\n return [1, 1, 1, 1];\n};\n\n/**\n * Copies `root` into plain divs with inlined styles, ready to rasterize inside\n * an SVG `<foreignObject>`. The copy is pinned to the top-left of a frame the\n * size of `root`, painted with the color behind it, so transparent areas show\n * what the page shows there instead of nothing.\n *\n * The frame is rounded up to whole CSS pixels. The browser paints it in whole\n * pixels, so a fractional size would leave its last row or column part\n * transparent, and the glass shows a transparent image edge as a dark band.\n */\nexport const cloneBackdrop = (\n root: HTMLElement,\n options: CloneOptions,\n): HTMLElement => {\n const doc = root.ownerDocument;\n const win = doc.defaultView!;\n // The root's size in CSS pixels, which its copied contents lay out in,\n // rather than its scaled size when the page is drawn scaled.\n const bounds = root.getBoundingClientRect();\n const zoom = drawnScale(root);\n const width = bounds.width / zoom;\n const height = bounds.height / zoom;\n const [r, g, b, a] = effectiveBackground(root);\n const frame = doc.createElement('div');\n frame.setAttribute(\n 'style',\n `width:${Math.ceil(width)}px;height:${Math.ceil(height)}px;` +\n `overflow:hidden;` +\n `background:rgba(${r * 255},${g * 255},${b * 255},${a});`,\n );\n const context = { ...options, doc, win, defaults: defaultStyle(doc, win) };\n const clone = cloneNode(root, context, null, [0, 0]) as HTMLElement | null;\n if (clone) {\n // Whatever placed the root in the page (margins, offsets, transforms)\n // isn't part of the picture.\n clone.style.cssText +=\n `margin:0;position:relative;left:0;top:0;right:auto;bottom:auto;` +\n `transform:none;width:${width}px;height:${height}px;`;\n frame.appendChild(clone);\n }\n return frame;\n};\n","import {\n cloneBackdrop,\n effectiveBackground,\n type CloneOptions,\n} from './backdrop-clone';\nimport { luminance } from './css-color';\nimport { drawnScale } from './drawn-scale';\n\n/** An image of what sits behind a glass group. */\nexport type BackdropSnapshot = {\n image: HTMLCanvasElement;\n /** The captured region, relative to the backdrop root, in CSS pixels. */\n x: number;\n y: number;\n width: number;\n height: number;\n /** How bright the backdrop's background is, from 0 to 1. */\n luminance: number;\n};\n\nexport type SnapshotOptions = {\n /** Device pixels per CSS pixel to capture at. */\n scale: number;\n} & CloneOptions;\n\n// Keeps the image inside every GPU's texture limit (WebGL 1 guarantees far\n// less, but phones and desktops alike accept 4096).\nconst MAX_IMAGE_SIZE = 4096;\n\n/**\n * Captures part of `root` as an image. The browser can't hand page pixels to\n * WebGL, so the page is redrawn instead: the subtree is copied with inlined\n * styles (see backdrop-clone.ts), wrapped in an SVG `<foreignObject>` and\n * drawn to a canvas. The SVG is the size of `region`, so only the area the\n * glass can show is rasterized.\n *\n * Resolves to null when the region doesn't overlap `root`.\n */\nexport const snapshotBackdrop = async (\n root: HTMLElement,\n options: SnapshotOptions,\n): Promise<BackdropSnapshot | null> => {\n const doc = root.ownerDocument;\n const win = doc.defaultView!;\n const bounds = root.getBoundingClientRect();\n const left = Math.max(options.region.left, bounds.left);\n const top = Math.max(options.region.top, bounds.top);\n const right = Math.min(options.region.right, bounds.right);\n const bottom = Math.min(options.region.bottom, bounds.bottom);\n if (right <= left || bottom <= top) return null;\n // The region and the root's box are in screen pixels, but the copy lays out\n // in CSS pixels: the two differ when the page is drawn scaled.\n const zoom = drawnScale(root);\n const x = (left - bounds.left) / zoom;\n const y = (top - bounds.top) / zoom;\n const width = (right - left) / zoom;\n const height = (bottom - top) / zoom;\n const scale = Math.min(\n options.scale,\n MAX_IMAGE_SIZE / Math.max(width, height),\n );\n\n const pixelWidth = Math.max(1, Math.round(width * scale));\n const pixelHeight = Math.max(1, Math.round(height * scale));\n // The copy itself is moved and scaled so the region fills the image, with a\n // CSS transform. An SVG viewBox does the same in Chromium, but Safari\n // ignores a viewBox for a <foreignObject>'s content, drawing it from the\n // root's corner at one pixel per CSS pixel: a menu near the top right of\n // the page refracted the content to the left of it, and the image's\n // unfilled part as a dark band.\n //\n // The image's edges must be opaque: the glass clamps to them where its lens\n // looks past the image, so a transparent row there shows as a dark band.\n // Rounding the image's width and height separately leaves its shape a\n // little off the region's, so the copy is stretched to it rather than\n // scaled evenly. The copy's frame is rounded up to whole pixels (see\n // cloneBackdrop), so it covers the region's last row.\n const frame = cloneBackdrop(root, options);\n frame.style.cssText +=\n `transform-origin:0 0;transform:scale(${pixelWidth / width},` +\n `${pixelHeight / height}) translate(${-x}px,${-y}px);`;\n const markup = new win.XMLSerializer().serializeToString(frame);\n const svg =\n `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${pixelWidth}\" ` +\n `height=\"${pixelHeight}\">` +\n `<foreignObject width=\"${pixelWidth}\" height=\"${pixelHeight}\">` +\n `${markup}</foreignObject></svg>`;\n const picture = new win.Image();\n picture.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;\n await picture.decode();\n\n const image = doc.createElement('canvas');\n image.width = pixelWidth;\n image.height = pixelHeight;\n image.getContext('2d')!.drawImage(picture, 0, 0, pixelWidth, pixelHeight);\n return {\n image,\n x,\n y,\n width,\n height,\n luminance: luminance(effectiveBackground(root)),\n };\n};\n","/**\n * The most glass shapes one group draws. GLSL ES 1.0 (WebGL 1) needs a\n * constant array size and loop bound; a container rarely holds more than a\n * handful of shapes, and any past this aren't drawn.\n */\nexport const MAX_GLASS_SHAPES = 16;\n\n/** Full-screen quad. `v_uv` runs 0→1 with y pointing up, as GL does. */\nexport const QUAD_VERTEX_SHADER = `\nattribute vec2 a_position;\nvarying vec2 v_uv;\nvoid main() {\n v_uv = a_position * 0.5 + 0.5;\n gl_Position = vec4(a_position, 0.0, 1.0);\n}`;\n\n/** Copies a texture, downsampling the backdrop before it is blurred. */\nexport const COPY_FRAGMENT_SHADER = `\nprecision mediump float;\nvarying vec2 v_uv;\nuniform sampler2D u_source;\nvoid main() {\n gl_FragColor = texture2D(u_source, v_uv);\n}`;\n\n/**\n * One direction of a separable Gaussian blur. Sampling between two pixels lets\n * the linear filter average both at once, so five fetches cover nine pixels.\n */\nexport const BLUR_FRAGMENT_SHADER = `\nprecision mediump float;\nvarying vec2 v_uv;\nuniform sampler2D u_source;\nuniform vec2 u_direction;\nvoid main() {\n vec2 near = u_direction * 1.3846153846;\n vec2 far = u_direction * 3.2307692308;\n gl_FragColor = texture2D(u_source, v_uv) * 0.2270270270\n + (texture2D(u_source, v_uv + near) + texture2D(u_source, v_uv - near)) * 0.3162162162\n + (texture2D(u_source, v_uv + far) + texture2D(u_source, v_uv - far)) * 0.0702702703;\n}`;\n\n/**\n * Draws every glass shape of a group in one pass, in CSS pixels with y down.\n *\n * GLSL ES 1.0 only lets a loop's own counter index the uniform arrays, so the\n * per-shape distance is written out in each loop rather than in a helper.\n *\n * - Each shape is a rounded box signed distance field (SDF). A smooth minimum\n * unions them, so shapes closer than the container's `spacing` flow into one\n * another like iOS 26's glass container.\n * - The lens samples the backdrop outward near the rim, where the glass is\n * steepest, and slightly inward elsewhere. The rim shows the content just\n * outside it bent around the edge, and the middle reads a little magnified.\n * The SDF gradient gives the direction; its length fades the effect where\n * merged shapes crease, which would otherwise show a seam.\n * - `regular` glass mixes in a blurred, brightened backdrop; `clear` glass\n * keeps it sharp. Tint, a specular rim lit from the top left, and a press\n * glow for interactive glass go on top, and the light material's edge\n * gets a gray hairline, which is what shows it over white.\n */\nexport const GLASS_FRAGMENT_SHADER = `\nprecision highp float;\n#define MAX_SHAPES ${MAX_GLASS_SHAPES}\nvarying vec2 v_uv;\nuniform sampler2D u_backdrop;\nuniform sampler2D u_frosted;\nuniform vec2 u_size;\nuniform vec2 u_offset;\nuniform vec2 u_backdropSize;\nuniform float u_scale;\nuniform float u_smoothing;\nuniform float u_dark;\nuniform float u_shadow;\nuniform int u_count;\nuniform vec4 u_bounds[MAX_SHAPES];\nuniform vec4 u_material[MAX_SHAPES];\nuniform vec4 u_tint[MAX_SHAPES];\nuniform vec3 u_pointer;\n\nfloat roundedBox(vec2 p, vec2 halfSize, float radius) {\n vec2 q = abs(p) - halfSize + radius;\n return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - radius;\n}\n\nfloat smoothUnion(float a, float b, float k) {\n if (k <= 0.0) return min(a, b);\n float h = max(k - abs(a - b), 0.0) / k;\n return min(a, b) - h * h * k * 0.25;\n}\n\nfloat scene(vec2 p) {\n float d = 1e5;\n for (int i = 0; i < MAX_SHAPES; i++) {\n if (i >= u_count) break;\n d = smoothUnion(d, roundedBox(p - u_bounds[i].xy, u_bounds[i].zw, u_material[i].x), u_smoothing);\n }\n return d;\n}\n\nvoid main() {\n vec2 p = vec2(v_uv.x, 1.0 - v_uv.y) * u_size;\n\n // Union the shapes, and blend each shape's material by how close it is, so\n // a merged bridge between two tints shades from one to the other.\n float d = 1e5;\n float weights = 0.0;\n float frost = 0.0;\n float lens = 0.0;\n vec4 tint = vec4(0.0);\n for (int i = 0; i < MAX_SHAPES; i++) {\n if (i >= u_count) break;\n float di = roundedBox(p - u_bounds[i].xy, u_bounds[i].zw, u_material[i].x);\n d = smoothUnion(d, di, u_smoothing);\n float w = exp(-max(di, 0.0) / max(u_smoothing * 0.3, 1.0)) + 1e-6;\n weights += w;\n frost += u_material[i].y * w;\n lens += min(u_bounds[i].z, u_bounds[i].w) * w;\n tint += u_tint[i] * w;\n }\n frost /= weights;\n lens = max(lens / weights, 1.0);\n tint /= weights;\n\n // One device pixel of anti-aliasing at the edge.\n float coverage = clamp(0.5 - d * u_scale, 0.0, 1.0);\n // A container can hug its shapes, so the shadow fades out before the canvas\n // edge rather than stopping at it in a hard line.\n vec2 inside = min(p, u_size - p);\n float shadow = u_shadow * 0.1 * exp(-max(d, 0.0) / 6.0)\n * smoothstep(0.0, 12.0, min(inside.x, inside.y));\n if (coverage <= 0.0) {\n gl_FragColor = vec4(0.0, 0.0, 0.0, d < 24.0 ? shadow : 0.0);\n return;\n }\n\n vec2 e = vec2(0.5, 0.0);\n vec2 gradient = vec2(\n scene(p + e.xy) - scene(p - e.xy),\n scene(p + e.yx) - scene(p - e.yx));\n float slope = length(gradient);\n vec2 normal = slope > 0.0 ? gradient / slope : vec2(0.0);\n gradient = normal * min(slope, 1.0);\n\n float depth = max(-d, 0.0);\n float rim = pow(1.0 - clamp(depth / lens, 0.0, 1.0), 3.0);\n vec2 offset = gradient * (lens * 0.45 * rim - max(lens - depth, 0.0) * 0.05);\n vec2 base = p + u_offset;\n vec2 uv = (base + offset) / u_backdropSize;\n\n // A touch of dispersion: red bends a little more than blue.\n vec3 sharp = vec3(\n texture2D(u_backdrop, (base + offset * 1.035) / u_backdropSize).r,\n texture2D(u_backdrop, uv).g,\n texture2D(u_backdrop, (base + offset * 0.965) / u_backdropSize).b);\n vec3 color = mix(sharp, texture2D(u_frosted, uv).rgb, frost);\n\n float brightness = dot(color, vec3(0.2126, 0.7152, 0.0722));\n color = mix(vec3(brightness), color, 1.0 + 0.3 * frost);\n color = mix(color, mix(vec3(1.0), vec3(0.12), u_dark), frost * mix(0.22, 0.34, u_dark));\n color *= 1.0 - 0.08 * u_dark * (1.0 - frost);\n color = mix(color, tint.rgb, tint.a);\n\n vec2 light = normalize(vec2(-0.6, -0.8));\n float facing = dot(normal, light);\n color += (1.0 - smoothstep(0.0, 1.5, depth)) * (0.22 + 0.58 * pow(abs(facing), 1.5)) * mix(0.75, 0.5, u_dark);\n color += (1.0 - smoothstep(0.0, lens * 0.5, depth)) * max(facing, 0.0) * 0.16;\n color += (1.0 - smoothstep(0.0, lens * 0.35, depth)) * max(-facing, 0.0) * 0.07;\n\n float reach = length(p - u_pointer.xy);\n color += u_pointer.z * 0.2 * exp(-reach * reach / (1.2 * lens * lens));\n\n // Light glass over white is white on white: the frost, the rim and the glow\n // all brighten. UIKit outlines it with a gray hairline, about two device\n // pixels deep, and so does this. The dark material keeps its bright rim.\n float hairline = (1.0 - smoothstep(0.0, 2.5, depth * u_scale)) * (1.0 - u_dark);\n color = clamp(color, 0.0, 1.0) * (1.0 - 0.32 * hairline);\n\n gl_FragColor = vec4(color * coverage, coverage + shadow * (1.0 - coverage));\n}`;\n","import { QUAD_VERTEX_SHADER } from './glass-shaders';\n\nexport type Program = {\n program: WebGLProgram;\n uniform: (name: string) => WebGLUniformLocation | null;\n};\n\nexport type Target = {\n texture: WebGLTexture;\n framebuffer: WebGLFramebuffer;\n};\n\nexport const compile = (\n gl: WebGLRenderingContext,\n fragmentSource: string,\n): Program | null => {\n const program = gl.createProgram();\n for (const [type, source] of [\n [gl.VERTEX_SHADER, QUAD_VERTEX_SHADER],\n [gl.FRAGMENT_SHADER, fragmentSource],\n ] as const) {\n const shader = gl.createShader(type)!;\n gl.shaderSource(shader, source);\n gl.compileShader(shader);\n gl.attachShader(program, shader);\n }\n // Every program shares the one quad buffer bound to attribute 0.\n gl.bindAttribLocation(program, 0, 'a_position');\n gl.linkProgram(program);\n if (!gl.getProgramParameter(program, gl.LINK_STATUS)) return null;\n const locations = new Map<string, WebGLUniformLocation | null>();\n const uniform = (name: string) => {\n if (!locations.has(name)) {\n locations.set(name, gl.getUniformLocation(program, name));\n }\n return locations.get(name)!;\n };\n return { program, uniform };\n};\n\nexport const createTexture = (gl: WebGLRenderingContext): WebGLTexture => {\n const texture = gl.createTexture();\n gl.bindTexture(gl.TEXTURE_2D, texture);\n // WebGL 1 can't mipmap or repeat non-power-of-two textures; clamping also\n // stretches the backdrop's edge when the lens samples past it.\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n return texture;\n};\n\nexport const createTarget = (\n gl: WebGLRenderingContext,\n width: number,\n height: number,\n): Target => {\n const texture = createTexture(gl);\n gl.texImage2D(\n gl.TEXTURE_2D,\n 0,\n gl.RGBA,\n width,\n height,\n 0,\n gl.RGBA,\n gl.UNSIGNED_BYTE,\n null,\n );\n const framebuffer = gl.createFramebuffer();\n gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);\n gl.framebufferTexture2D(\n gl.FRAMEBUFFER,\n gl.COLOR_ATTACHMENT0,\n gl.TEXTURE_2D,\n texture,\n 0,\n );\n return { texture, framebuffer };\n};\n","import type { Rgba } from './css-color';\nimport {\n compile,\n createTarget,\n createTexture,\n type Program,\n type Target,\n} from './glass-gl';\nimport {\n BLUR_FRAGMENT_SHADER,\n COPY_FRAGMENT_SHADER,\n GLASS_FRAGMENT_SHADER,\n MAX_GLASS_SHAPES,\n} from './glass-shaders';\n\n/** One glass shape, in CSS pixels relative to the group's canvas. */\nexport type GlassShapeFrame = {\n centerX: number;\n centerY: number;\n halfWidth: number;\n halfHeight: number;\n radius: number;\n /** 1 for `regular` (frosted) glass, 0 for `clear`. */\n frost: number;\n tint: Rgba;\n};\n\n/** Everything one frame of a glass group draws. */\nexport type GlassFrame = {\n /** Device pixels per CSS pixel. */\n scale: number;\n /** Canvas size, in CSS pixels. */\n width: number;\n height: number;\n /** Where the canvas's top-left corner sits in the backdrop, in CSS pixels. */\n offsetX: number;\n offsetY: number;\n /** Smooth-union radius, in CSS pixels: twice the container's `spacing`. */\n smoothing: number;\n /** 1 for the dark material, 0 for light. */\n dark: number;\n /** 1 to draw a soft shadow around the shapes (containers only). */\n shadow: number;\n shapes: readonly GlassShapeFrame[];\n /** Press glow: x and y in CSS pixels, then strength from 0 to 1. */\n pointer: readonly [number, number, number];\n};\n\n// Frosted glass blurs a copy of the backdrop at a quarter of its resolution:\n// three blur passes there cost little and soften it as much as iOS's material.\nconst FROST_DOWNSAMPLE = 4;\nconst FROST_PASSES = 3;\n\n/**\n * Draws Liquid Glass with WebGL 1 for Lynx for Web, where `<blur-view>` only\n * knows `backdrop-filter: blur()`. The backdrop is an image of what sits\n * behind the glass; the group code takes it (see backdrop-snapshot.ts) and the\n * renderer refracts it through the glass shapes.\n */\nexport class GlassRenderer {\n readonly #gl: WebGLRenderingContext;\n readonly #copy: Program;\n readonly #blur: Program;\n readonly #glass: Program;\n readonly #quad: WebGLBuffer;\n #backdrop: WebGLTexture | null = null;\n #frosted: [Target, Target] | null = null;\n #frostedSize = '';\n #backdropWidth = 1;\n #backdropHeight = 1;\n\n /**\n * Use `GlassRenderer.create`, which builds the programs this needs.\n */\n constructor(\n gl: WebGLRenderingContext,\n copy: Program,\n blur: Program,\n glass: Program,\n ) {\n this.#gl = gl;\n this.#copy = copy;\n this.#blur = blur;\n this.#glass = glass;\n this.#quad = gl.createBuffer();\n gl.bindBuffer(gl.ARRAY_BUFFER, this.#quad);\n gl.bufferData(\n gl.ARRAY_BUFFER,\n new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]),\n gl.STATIC_DRAW,\n );\n gl.enableVertexAttribArray(0);\n gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);\n }\n\n /**\n * Null when the browser has no WebGL or can't build the shaders.\n */\n static create(canvas: HTMLCanvasElement): GlassRenderer | null {\n const gl = canvas.getContext('webgl', {\n alpha: true,\n premultipliedAlpha: true,\n antialias: false,\n depth: false,\n stencil: false,\n });\n if (!gl) return null;\n const copy = compile(gl, COPY_FRAGMENT_SHADER);\n const blur = compile(gl, BLUR_FRAGMENT_SHADER);\n const glass = compile(gl, GLASS_FRAGMENT_SHADER);\n if (!copy || !blur || !glass) return null;\n return new GlassRenderer(gl, copy, blur, glass);\n }\n\n /**\n * True once the GPU dropped the context; the group then starts over.\n */\n get lost(): boolean {\n return this.#gl.isContextLost();\n }\n\n /**\n * Uploads a new backdrop image and blurs a copy of it for frosted glass.\n * `width` and `height` are its size in CSS pixels.\n */\n setBackdrop(image: HTMLCanvasElement, width: number, height: number): void {\n const gl = this.#gl;\n this.#backdropWidth = width;\n this.#backdropHeight = height;\n this.#backdrop ??= createTexture(gl);\n gl.bindTexture(gl.TEXTURE_2D, this.#backdrop);\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);\n\n const w = Math.max(1, Math.round(image.width / FROST_DOWNSAMPLE));\n const h = Math.max(1, Math.round(image.height / FROST_DOWNSAMPLE));\n if (this.#frostedSize !== `${w}x${h}`) {\n this.#deleteFrosted();\n this.#frosted = [createTarget(gl, w, h), createTarget(gl, w, h)];\n this.#frostedSize = `${w}x${h}`;\n }\n const [a, b] = this.#frosted!;\n gl.viewport(0, 0, w, h);\n this.#pass(this.#copy, this.#backdrop, a);\n for (let i = 0; i < FROST_PASSES; i++) {\n this.#pass(this.#blur, a.texture, b, [1 / w, 0]);\n this.#pass(this.#blur, b.texture, a, [0, 1 / h]);\n }\n gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n }\n\n draw(frame: GlassFrame): void {\n const gl = this.#gl;\n const { uniform } = this.#glass;\n gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);\n gl.clearColor(0, 0, 0, 0);\n gl.clear(gl.COLOR_BUFFER_BIT);\n if (!this.#backdrop) return;\n\n gl.useProgram(this.#glass.program);\n gl.activeTexture(gl.TEXTURE1);\n gl.bindTexture(gl.TEXTURE_2D, this.#frosted![0].texture);\n gl.uniform1i(uniform('u_frosted'), 1);\n gl.activeTexture(gl.TEXTURE0);\n gl.bindTexture(gl.TEXTURE_2D, this.#backdrop);\n gl.uniform1i(uniform('u_backdrop'), 0);\n\n gl.uniform2f(uniform('u_size'), frame.width, frame.height);\n gl.uniform2f(uniform('u_offset'), frame.offsetX, frame.offsetY);\n gl.uniform2f(\n uniform('u_backdropSize'),\n this.#backdropWidth,\n this.#backdropHeight,\n );\n gl.uniform1f(uniform('u_scale'), frame.scale);\n gl.uniform1f(uniform('u_smoothing'), frame.smoothing);\n gl.uniform1f(uniform('u_dark'), frame.dark);\n gl.uniform1f(uniform('u_shadow'), frame.shadow);\n gl.uniform3f(uniform('u_pointer'), ...frame.pointer);\n\n const shapes = frame.shapes.slice(0, MAX_GLASS_SHAPES);\n const bounds = new Float32Array(MAX_GLASS_SHAPES * 4);\n const material = new Float32Array(MAX_GLASS_SHAPES * 4);\n const tint = new Float32Array(MAX_GLASS_SHAPES * 4);\n shapes.forEach((shape, i) => {\n bounds.set(\n [shape.centerX, shape.centerY, shape.halfWidth, shape.halfHeight],\n i * 4,\n );\n material.set([shape.radius, shape.frost, 0, 0], i * 4);\n tint.set(shape.tint, i * 4);\n });\n gl.uniform1i(uniform('u_count'), shapes.length);\n gl.uniform4fv(uniform('u_bounds'), bounds);\n gl.uniform4fv(uniform('u_material'), material);\n gl.uniform4fv(uniform('u_tint'), tint);\n gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);\n }\n\n dispose(): void {\n const gl = this.#gl;\n this.#deleteFrosted();\n gl.deleteTexture(this.#backdrop);\n gl.deleteBuffer(this.#quad);\n for (const { program } of [this.#copy, this.#blur, this.#glass]) {\n gl.deleteProgram(program);\n }\n // Browsers cap live WebGL contexts (16 in Chrome); give this one back now\n // instead of waiting for garbage collection.\n gl.getExtension('WEBGL_lose_context')?.loseContext();\n }\n\n #pass(\n program: Program,\n source: WebGLTexture,\n target: Target,\n direction?: readonly [number, number],\n ): void {\n const gl = this.#gl;\n gl.useProgram(program.program);\n gl.bindFramebuffer(gl.FRAMEBUFFER, target.framebuffer);\n gl.activeTexture(gl.TEXTURE0);\n gl.bindTexture(gl.TEXTURE_2D, source);\n gl.uniform1i(program.uniform('u_source'), 0);\n if (direction) gl.uniform2f(program.uniform('u_direction'), ...direction);\n gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);\n }\n\n #deleteFrosted(): void {\n for (const target of this.#frosted ?? []) {\n this.#gl.deleteTexture(target.texture);\n this.#gl.deleteFramebuffer(target.framebuffer);\n }\n this.#frosted = null;\n }\n}\n","/** How far an interactive glass shape is pressed, and where. */\nexport type PressState = {\n /** 1 while a pointer is down on the shape, else 0. */\n target: number;\n /** Eased from 0 (at rest) to 1 (fully pressed). */\n value: number;\n /** The pointer, in viewport (client) coordinates. */\n x: number;\n y: number;\n};\n\nconst presses = new WeakMap<Element, PressState>();\n\n// Time constants of the ease, in milliseconds: iOS's glass swells quickly\n// under a finger and settles back more slowly.\nconst PRESS_IN_MS = 70;\nconst PRESS_OUT_MS = 180;\n\n/**\n * Follows pointer presses on a `<blur-view>`, for `glass-interactive`. The\n * listeners are passive and never stop propagation, so the element's own\n * Lynx touch and tap handlers still run. The attribute is checked when a press\n * starts, since it can be bound and change at any time.\n */\nexport const trackPress = (element: HTMLElement): void => {\n const state: PressState = { target: 0, value: 0, x: 0, y: 0 };\n presses.set(element, state);\n const follow = (event: PointerEvent) => {\n state.x = event.clientX;\n state.y = event.clientY;\n };\n const release = () => {\n state.target = 0;\n };\n const options = { passive: true };\n element.addEventListener(\n 'pointerdown',\n (event) => {\n if (!element.hasAttribute('glass-interactive')) return;\n state.target = 1;\n follow(event);\n },\n options,\n );\n element.addEventListener(\n 'pointermove',\n (event) => {\n if (state.target) follow(event);\n },\n options,\n );\n for (const type of ['pointerup', 'pointercancel', 'pointerleave']) {\n element.addEventListener(type, release, options);\n }\n};\n\nexport const pressOf = (element: Element): PressState | undefined =>\n presses.get(element);\n\n/**\n * Eases a press toward its target over `elapsed` milliseconds. Returns the\n * new value, snapping to the target once it's indistinguishable.\n */\nexport const stepPress = (state: PressState, elapsed: number): number => {\n const time = state.target ? PRESS_IN_MS : PRESS_OUT_MS;\n const next =\n state.target + (state.value - state.target) * Math.exp(-elapsed / time);\n state.value = Math.abs(next - state.target) < 0.001 ? state.target : next;\n return state.value;\n};\n","import { resolveCssColor } from './css-color';\nimport { pressOf, stepPress } from './glass-press';\nimport type { GlassShapeFrame } from './glass-renderer';\n\n/**\n * What a `<blur-view>` is to the glass code:\n * - `container`: a `glass-container`, which draws its glass children as one\n * group so nearby shapes merge.\n * - `standalone`: a `glass` element outside any container, its own group.\n * - `member`: a `glass` element its parent container draws.\n */\nexport type GlassRole = 'container' | 'standalone' | 'member';\n\nexport const glassRole = (element: Element): GlassRole | null => {\n const effect = element.getAttribute('blur-effect');\n if (effect === 'glass-container') return 'container';\n if (effect !== 'glass') return null;\n const parent = element.parentElement?.getAttribute('blur-effect');\n return parent === 'glass-container' ? 'member' : 'standalone';\n};\n\n/**\n * The shapes a container groups. iOS only merges the glass views placed\n * directly in the container, so only direct children count here too.\n */\nexport const containerShapes = (container: Element): HTMLElement[] =>\n Array.from(container.children).filter(\n (child): child is HTMLElement =>\n child.getAttribute('blur-effect') === 'glass',\n );\n\n/**\n * A corner radius as the shader needs it: one value in pixels, no larger than\n * half the shorter side. Computed styles give pixels or a percentage, and\n * `rounded-full` (9999px) clamps to a circle or a pill.\n */\nexport const cornerRadius = (\n value: string,\n width: number,\n height: number,\n): number => {\n const shorter = Math.min(width, height);\n const radius = value.endsWith('%')\n ? (parseFloat(value) / 100) * shorter\n : parseFloat(value) || 0;\n return Math.min(radius, shorter / 2);\n};\n\n/** How much an interactive shape swells while pressed, as on iOS. */\nexport const PRESS_GROWTH = 0.06;\n\n/**\n * Reads one shape for a frame, relative to the group canvas at `origin`.\n * Advances its press animation by `elapsed` milliseconds; only containers let\n * a pressed shape `grow` here, since a standalone shape's canvas is its own\n * box. The manager swells a standalone shape's element instead.\n *\n * The shape's box and `origin` are in screen pixels, drawn at `zoom` screen\n * pixels per CSS pixel (see drawn-scale.ts); the frame is in CSS pixels, like\n * its corner radius.\n */\nexport const readShape = (\n element: HTMLElement,\n origin: DOMRect,\n zoom: number,\n elapsed: number,\n grow: boolean,\n): GlassShapeFrame & { press: number } => {\n const box = element.getBoundingClientRect();\n const left = (box.left - origin.left) / zoom;\n const top = (box.top - origin.top) / zoom;\n const width = box.width / zoom;\n const height = box.height / zoom;\n const style = element.ownerDocument.defaultView!.getComputedStyle(element);\n const press = pressOf(element);\n const pressed = press ? stepPress(press, elapsed) : 0;\n const swell = grow ? 1 + PRESS_GROWTH * pressed : 1;\n return {\n centerX: left + width / 2,\n centerY: top + height / 2,\n halfWidth: (width / 2) * swell,\n halfHeight: (height / 2) * swell,\n radius: cornerRadius(style.borderTopLeftRadius, width, height) * swell,\n frost: element.getAttribute('glass-style') === 'clear' ? 0 : 1,\n tint: resolveCssColor(\n element.ownerDocument,\n element.getAttribute('glass-tint-color'),\n ),\n press: pressed,\n };\n};\n","import { observeBackdrop } from './backdrop-observer';\nimport { snapshotBackdrop, type BackdropSnapshot } from './backdrop-snapshot';\nimport { parseNormalizedColor } from './css-color';\nimport { drawnScale } from './drawn-scale';\nimport { GlassRenderer, type GlassFrame } from './glass-renderer';\nimport { pressOf } from './glass-press';\nimport { containerShapes, readShape } from './glass-shapes';\n\n// The lens samples up to about half a shape's radius past its edge, so the\n// captured region reaches this far beyond the group.\nconst CAPTURE_MARGIN = 48;\n// How far a container's canvas reaches past its box: room for its shapes'\n// shadow, which fades out over about 20px, and for a pressed shape's swell.\n// A container hugs its shapes, so with no margin both were cut off in a\n// straight line at its edge.\nconst CONTAINER_MARGIN = 32;\n// Captures run on the main thread, so they're spaced out: at least this many\n// milliseconds apart, and four times as long as the last one took.\nconst MIN_CAPTURE_INTERVAL = 100;\n// Sharper than 2x costs memory and capture time for no visible gain.\nconst MAX_SCALE = 2;\n// Backdrops darker than this get the dark material, like iOS in dark mode.\nconst DARK_LUMINANCE = 0.4;\n\n/** What a glass group refracts (see backdropOf). */\nexport type Backdrop = {\n /** The element whose drawing the glass refracts. */\n readonly element: HTMLElement;\n /**\n * The fixed-position element the glass is in, or is, such as a Lynx\n * overlay on the web: it paints above the page wherever it sits in the\n * DOM. Null when the glass is in the page's flow.\n */\n readonly layer: HTMLElement | null;\n};\n\nconst isFixed = (win: Window, element: Element) =>\n win.getComputedStyle(element).position === 'fixed';\n\n/**\n * What a glass group refracts: the nearest ancestor that paints over\n * everything behind it, with an opaque background color or a background\n * image, or else the topmost one, the page. The glass's own parent is often a\n * row or a wrapper that draws nothing, while what the glass floats over\n * belongs to an ancestor: a card's gradient, or the screen's content. iOS's\n * glass shows whatever is on screen behind it.\n *\n * Glass that's fixed, or in something fixed below that ancestor, refracts\n * the page instead: a menu's overlay sits in the DOM beside the button that\n * opened it, often in a row or a card, but it's drawn over the whole screen.\n */\nexport const backdropOf = (root: HTMLElement): Backdrop | null => {\n const win = root.ownerDocument.defaultView!;\n let layer = isFixed(win, root) ? root : null;\n let element = root.parentElement;\n while (element?.parentElement) {\n if (!layer) {\n const style = win.getComputedStyle(element);\n if (\n (style.backgroundImage && style.backgroundImage !== 'none') ||\n parseNormalizedColor(style.backgroundColor)[3] >= 1\n ) {\n return { element, layer };\n }\n if (style.position === 'fixed') layer = element;\n }\n element = element.parentElement;\n }\n return element && { element, layer };\n};\n\n/**\n * Whether `element` is drawn above the glass, and so left out of what it\n * refracts. In the page's flow, that's the glass, and everything after it\n * and after each of its ancestors up to the backdrop: later content paints\n * on top. A fixed layer paints above the content around it, so there it's\n * the layer, and any other fixed element stacked above it: a higher\n * `z-index`, or the same one and later in the page, such as a second menu.\n */\nconst drawnAbove = (\n root: HTMLElement,\n { element: backdrop, layer }: Backdrop,\n): ((element: Element) => boolean) => {\n const win = root.ownerDocument.defaultView!;\n if (layer) {\n const zIndex = (element: Element) =>\n parseInt(win.getComputedStyle(element).zIndex, 10) || 0;\n const layerZ = zIndex(layer);\n return (element) => {\n if (element === layer) return true;\n if (element.contains(layer) || !isFixed(win, element)) return false;\n const z = zIndex(element);\n return (\n z > layerZ ||\n (z === layerZ &&\n // Node.DOCUMENT_POSITION_FOLLOWING, spelled out: the elements\n // belong to the page, not to the main thread's realm.\n (layer.compareDocumentPosition(element) & 4) !== 0)\n );\n };\n }\n const above = new Set<Element>([root]);\n for (\n let node: Element | null = root;\n node && node !== backdrop;\n node = node.parentElement\n ) {\n for (\n let sibling = node.nextElementSibling;\n sibling;\n sibling = sibling.nextElementSibling\n ) {\n above.add(sibling);\n }\n }\n return (element) => above.has(element);\n};\n\n/**\n * Draws one group of Liquid Glass on Lynx for Web: a `glass-container` and the\n * glass shapes directly inside it, or a lone `glass` element.\n *\n * A canvas in the root's shadow root, stacked under its children, draws every\n * shape refracting an image of the backdrop (see backdropOf), captured up to\n * the root. The image is captured again when anything in the backdrop (other\n * than the glass itself) changes, resizes or scrolls.\n */\nexport class GlassGroup {\n readonly root: HTMLElement;\n readonly container: boolean;\n readonly #win: Window;\n readonly #canvas: HTMLCanvasElement;\n readonly #renderer: GlassRenderer | null;\n readonly #backdrop: Backdrop | null;\n readonly #margin: number;\n #stopObserving: (() => void) | null = null;\n #snapshot: BackdropSnapshot | null = null;\n #stale = true;\n #capturing = false;\n #failed = false;\n #disposed = false;\n #nextCapture = 0;\n #drawn = '';\n\n constructor(root: HTMLElement, container: boolean) {\n this.root = root;\n this.container = container;\n const doc = root.ownerDocument;\n this.#win = doc.defaultView!;\n this.#canvas = doc.createElement('canvas');\n this.#canvas.setAttribute('aria-hidden', 'true');\n // A container's canvas reaches past it (see CONTAINER_MARGIN), and the\n // container doesn't clip it (see glass-host-style.ts). A lone shape's is\n // its own box: its shadow is CSS, and it swells as a whole element. It's\n // sized with width and height, not by insets: a canvas is a replaced\n // element, which insets place but don't stretch. It fades in over the\n // frosted fallback once the first frame is drawn.\n this.#margin = container ? CONTAINER_MARGIN : 0;\n const size = `calc(100% + ${2 * this.#margin}px)`;\n this.#canvas.style.cssText =\n `position:absolute;left:${-this.#margin}px;top:${-this.#margin}px;` +\n `width:${size};height:${size};` +\n 'pointer-events:none;z-index:-1;opacity:0;transition:opacity 0.2s';\n const shadow = root.shadowRoot;\n shadow?.insertBefore(this.#canvas, shadow.querySelector('slot'));\n this.#renderer = shadow ? GlassRenderer.create(this.#canvas) : null;\n this.#backdrop = backdropOf(root);\n if (this.#renderer && this.#backdrop) {\n // Changes inside the glass, or inside the fixed layer it's in, don't\n // change what's behind it.\n this.#stopObserving = observeBackdrop(\n this.#backdrop.element,\n this.#backdrop.layer ?? root,\n () => {\n this.#stale = true;\n },\n );\n }\n }\n\n /**\n * The glass elements this group draws.\n */\n get shapes(): HTMLElement[] {\n return this.container ? containerShapes(this.root) : [this.root];\n }\n\n /**\n * True once WebGL has drawn the glass, so the CSS fallback can go.\n */\n get active(): boolean {\n return this.#drawn !== '' && !this.#failed;\n }\n\n /**\n * True when the GPU dropped the context; the group must be rebuilt.\n */\n get lost(): boolean {\n return this.#renderer?.lost ?? false;\n }\n\n /**\n * Draws a frame if anything changed; `elapsed` is in milliseconds.\n */\n frame(elapsed: number): void {\n const renderer = this.#renderer;\n const backdrop = this.#backdrop?.element;\n if (!renderer || !backdrop || this.#failed) return;\n // Boxes are measured in screen pixels, and the frame is drawn in CSS\n // pixels, which differ when the page is drawn scaled (see drawn-scale.ts).\n const origin = this.#canvas.getBoundingClientRect();\n const area = backdrop.getBoundingClientRect();\n const zoom = drawnScale(backdrop);\n if (this.#needsCapture(origin, area, zoom)) {\n this.#capture(backdrop, origin, zoom);\n }\n const snapshot = this.#snapshot;\n if (!snapshot) return;\n\n // Device pixels per CSS pixel, so the canvas stays sharp at any zoom.\n const scale = Math.min(this.#win.devicePixelRatio || 1, MAX_SCALE) * zoom;\n const elements = this.shapes;\n const read = elements.map((element) =>\n readShape(element, origin, zoom, elapsed, this.container),\n );\n // The press glow follows whichever shape is pressed the most.\n let pointer: [number, number, number] = [0, 0, 0];\n elements.forEach((element, i) => {\n const value = read[i]!.press;\n if (value > pointer[2]) {\n const { x, y } = pressOf(element)!;\n pointer = [(x - origin.left) / zoom, (y - origin.top) / zoom, value];\n }\n });\n // A shape with no width or height draws nothing on iOS, but its distance\n // field is a line, which the smooth union bulges into the shapes beside\n // it: the search field's cancel button, collapsed beside the field.\n const shapes = read.filter(\n (shape) => shape.halfWidth >= 0.5 && shape.halfHeight >= 0.5,\n );\n const spacing = parseFloat(this.root.getAttribute('spacing') ?? '') || 0;\n const frame: GlassFrame = {\n scale,\n width: origin.width / zoom,\n height: origin.height / zoom,\n offsetX: (origin.left - area.left) / zoom - snapshot.x,\n offsetY: (origin.top - area.top) / zoom - snapshot.y,\n // Twice the spacing: the smooth union only bridges a gap up to half its\n // radius, and iOS merges shapes closer than `spacing`.\n smoothing: this.container ? spacing * 2 : 0,\n dark: this.#dark(snapshot),\n // Outside its shapes a container's canvas is empty space for a shadow;\n // a lone shape's canvas is its own box, where it would darken the corners.\n shadow: this.container ? 1 : 0,\n shapes,\n pointer,\n };\n const key = JSON.stringify(frame);\n if (key === this.#drawn) return;\n const width = Math.max(1, Math.round(frame.width * scale));\n const height = Math.max(1, Math.round(frame.height * scale));\n // Assigning a canvas size reallocates it even when the size is unchanged.\n if (this.#canvas.width !== width) this.#canvas.width = width;\n if (this.#canvas.height !== height) this.#canvas.height = height;\n renderer.draw(frame);\n this.#drawn = key;\n this.#canvas.style.opacity = '1';\n }\n\n dispose(): void {\n this.#disposed = true;\n this.#stopObserving?.();\n this.#renderer?.dispose();\n this.#canvas.remove();\n }\n\n #dark(snapshot: BackdropSnapshot): number {\n const forced = this.root.getAttribute('ios-user-interface-style');\n if (forced) return forced === 'dark' ? 1 : 0;\n return snapshot.luminance < DARK_LUMINANCE ? 1 : 0;\n }\n\n #needsCapture(origin: DOMRect, area: DOMRect, zoom: number): boolean {\n if (this.#capturing) return false;\n const snapshot = this.#snapshot;\n if (snapshot) {\n // Moving the whole group (not just its shapes) past the captured region\n // would refract the image's clamped edge, so capture around it again.\n const left = (Math.max(origin.left, area.left) - area.left) / zoom;\n const top = (Math.max(origin.top, area.top) - area.top) / zoom;\n const right = (Math.min(origin.right, area.right) - area.left) / zoom;\n const bottom = (Math.min(origin.bottom, area.bottom) - area.top) / zoom;\n if (\n left < snapshot.x - 1 ||\n top < snapshot.y - 1 ||\n right > snapshot.x + snapshot.width + 1 ||\n bottom > snapshot.y + snapshot.height + 1\n ) {\n this.#stale = true;\n }\n }\n return this.#stale && this.#win.performance.now() >= this.#nextCapture;\n }\n\n #capture(backdrop: HTMLElement, origin: DOMRect, zoom: number): void {\n const win = this.#win;\n this.#capturing = true;\n this.#stale = false;\n const started = win.performance.now();\n // The region is in screen pixels, and the margins in CSS pixels. It's\n // measured from the root, which a container's canvas reaches past: the\n // shapes inside it are what sample the image.\n const margin = (CAPTURE_MARGIN - this.#margin) * zoom;\n snapshotBackdrop(backdrop, {\n exclude: drawnAbove(this.root, this.#backdrop!),\n region: {\n left: origin.left - margin,\n top: origin.top - margin,\n right: origin.right + margin,\n bottom: origin.bottom + margin,\n },\n scale: Math.min(win.devicePixelRatio || 1, MAX_SCALE) * zoom,\n })\n .then((snapshot) => {\n if (this.#disposed || !snapshot) return;\n this.#renderer!.setBackdrop(\n snapshot.image,\n snapshot.width,\n snapshot.height,\n );\n this.#snapshot = snapshot;\n this.#drawn = '';\n })\n .catch(() => {\n // The image wouldn't decode, or it tainted the canvas (Safari can\n // treat any <foreignObject> as cross-origin): keep the CSS fallback.\n this.#failed = true;\n })\n .finally(() => {\n this.#capturing = false;\n const now = win.performance.now();\n this.#nextCapture =\n now + Math.max(MIN_CAPTURE_INTERVAL, (now - started) * 4);\n });\n }\n}\n","/** How a `<blur-view>` on web is styled while the glass code looks after it. */\nexport type HostStyleState = {\n /** It's a glass shape that WebGL isn't drawing (yet): show frosted CSS. */\n fallback: boolean;\n /** WebGL draws this shape, so web-elements' own backdrop blur must go. */\n rendered: boolean;\n /** It holds a group's canvas, which must stack under its children. */\n hostsCanvas: boolean;\n /**\n * It casts its shadow with CSS: a lone glass shape, whose canvas is its own\n * box, and any glass shape in the fallback. A container draws its shapes'\n * shadow on its canvas.\n */\n shadow: boolean;\n /** How much it's drawn scaled: above 1 while a lone shape swells. */\n scale: number;\n};\n\nconst styles = new WeakMap<Element, HTMLStyleElement>();\n\nconst DEFAULT_FALLBACK_BLUR = 12;\n\n/**\n * The frosted look a glass shape has before WebGL takes over, or without it:\n * a backdrop blur with a bright inner rim, roughly the pre-iOS-26 material.\n * Author styles on the element itself still win, since a `:host` rule loses to\n * them, so a background set in the template is kept. Its rims are in\n * FALLBACK_EDGES, since the shadow shares its `box-shadow`.\n */\nexport const fallbackGlassCss = (element: Element): string => {\n const clear = element.getAttribute('glass-style') === 'clear';\n const radius =\n parseFloat(element.getAttribute('blur-radius') ?? '') ||\n (clear ? DEFAULT_FALLBACK_BLUR / 3 : DEFAULT_FALLBACK_BLUR);\n const filter = `blur(${radius}px) saturate(${clear ? 140 : 180}%)`;\n const background =\n element.getAttribute('glass-tint-color') ||\n `rgba(255, 255, 255, ${clear ? 0.04 : 0.18})`;\n return (\n `:host{backdrop-filter:${filter};-webkit-backdrop-filter:${filter};` +\n `background-color:${background}}`\n );\n};\n\n/**\n * The fallback's rims, as the WebGL glass draws them: a gray hairline on the\n * outermost pixel, which is all that outlines the glass over white, and a\n * bright rim inside it, lit from the top, which shows over dark content.\n */\nexport const FALLBACK_EDGES =\n 'inset 0 0 0 1px rgba(0, 0, 0, 0.08), inset 0 2px 0 rgba(255, 255, 255, 0.5), ' +\n 'inset 0 0 0 2px rgba(255, 255, 255, 0.25)';\n\n// The shorter side, in px, of the smallest and the largest glass shadows: a\n// 44pt bar button's, and a menu's or an action sheet's.\nconst SMALL_SHADOW_SIDE = 44;\nconst LARGE_SHADOW_SIDE = 160;\n\n/**\n * The shadow under a glass shape the WebGL glass gives none (see\n * HostStyleState's `shadow`). UIKit's grows with the glass: faint under a\n * bar button, and wide and soft under a menu or an action sheet, where it's\n * 6% dark at the bottom edge and along the sides and fades out about 30pt\n * below. So it's scaled by the shape's shorter side, in tenths of the way\n * from one to the other, so a menu growing open rewrites its style a few\n * times rather than every frame. An element's own `overflow` doesn't clip\n * its box-shadow, so it shows around a menu panel that clips its content.\n */\nexport const glassShadow = (element: HTMLElement): string => {\n const side = Math.min(element.offsetWidth, element.offsetHeight);\n const reach =\n (side - SMALL_SHADOW_SIDE) / (LARGE_SHADOW_SIDE - SMALL_SHADOW_SIDE);\n const t = Math.round(Math.min(Math.max(reach, 0), 1) * 10) / 10;\n const mix = (small: number, large: number) =>\n Math.round((small + (large - small) * t) * 1000) / 1000;\n const shadow = `0 ${mix(2, 8)}px ${mix(10, 32)}px rgba(0, 0, 0, ${mix(0.06, 0.08)})`;\n // A large shape also sits on a tighter shadow where it meets the content.\n return t ? `${shadow}, 0 1px 4px rgba(0, 0, 0, ${mix(0, 0.04)})` : shadow;\n};\n\n/**\n * The UIKit blur material a non-glass `<blur-view>` gets on iOS, which\n * `blur-effect` picks (`light` by default). Besides blurring, each material\n * shifts what's behind it, while web-elements' `<x-blur-view>` only blurs. So\n * these approximate them, fitted to the iOS simulator's pixels in the\n * element-blur-view example: `light` brightens the backdrop by about a third,\n * `extra-light` washes it with white, and `dark` with black. Like on iOS, a\n * blur view with no blur radius shows no material. The `light` rule repeats\n * the blur because it replaces web-elements' own `backdrop-filter`.\n */\nexport const materialCss = (element: Element): string => {\n const effect = element.getAttribute('blur-effect') ?? 'light';\n const radius = parseFloat(element.getAttribute('blur-radius') ?? '');\n if (!radius || effect === 'glass' || effect === 'glass-container') {\n return '';\n }\n if (effect === 'extra-light') {\n return ':host{background-color:rgba(255, 255, 255, 0.75)}';\n }\n if (effect === 'dark') {\n return ':host{background-color:rgba(0, 0, 0, 0.6)}';\n }\n const filter = `blur(${radius}px) brightness(1.35)`;\n return `:host{backdrop-filter:${filter};-webkit-backdrop-filter:${filter}}`;\n};\n\nexport const hostStyleCss = (\n element: HTMLElement,\n state: HostStyleState,\n): string => {\n const shadows = [\n state.shadow ? glassShadow(element) : '',\n state.fallback ? FALLBACK_EDGES : '',\n ].filter(Boolean);\n return [\n materialCss(element),\n // A container's canvas reaches past its box, for the shadow and the\n // swell of its shapes (see glass-group.ts), and so do its shapes' CSS\n // shadows in the fallback. web-elements clips every `<x-blur-view>`, and\n // its rule comes from outside the shadow tree, so only an important\n // `:host` rule beats it. A container draws nothing but its shapes.\n element.getAttribute('blur-effect') === 'glass-container'\n ? ':host{overflow:visible!important}'\n : '',\n state.hostsCanvas ? ':host{isolation:isolate}' : '',\n state.rendered\n ? ':host{backdrop-filter:none!important;-webkit-backdrop-filter:none!important}'\n : '',\n state.fallback ? fallbackGlassCss(element) : '',\n shadows.length ? `:host{box-shadow:${shadows.join(', ')}}` : '',\n // The `scale` property rather than `transform`, which composes with a\n // transform the element has of its own, such as a popover arrow's turn.\n state.scale !== 1\n ? `:host{scale:${Math.round(state.scale * 1000) / 1000}}`\n : '',\n ].join('');\n};\n\n/**\n * Applies `state` through a `<style>` in the element's shadow root, written\n * only when it changes. The rules stay inside the element: Lynx owns its\n * `class` and `style` attributes, and an attribute of our own would show up in\n * the backdrop's mutation observer as a change to capture.\n */\nexport const applyHostStyle = (\n element: HTMLElement,\n state: HostStyleState,\n): void => {\n const root = element.shadowRoot;\n if (!root) return;\n const css = hostStyleCss(element, state);\n let style = styles.get(element);\n if (!style) {\n if (!css) return;\n style = element.ownerDocument.createElement('style');\n styles.set(element, style);\n }\n // Appended (again) last, so it follows web-elements' own dynamic style.\n if (style.parentNode !== root) root.appendChild(style);\n if (style.textContent !== css) style.textContent = css;\n};\n","import { GlassGroup } from './glass-group';\nimport { applyHostStyle } from './glass-host-style';\nimport { pressOf, trackPress } from './glass-press';\nimport { glassRole, PRESS_GROWTH } from './glass-shapes';\n\n// The attributes that change what a `<blur-view>` looks like as glass.\nconst GLASS_ATTRIBUTES = [\n 'blur-effect',\n 'blur-radius',\n 'glass-style',\n 'glass-tint-color',\n 'glass-interactive',\n 'spacing',\n 'ios-user-interface-style',\n];\n\n// How often removed `<blur-view>`s are checked for being put back.\nconst DETACHED_POLL_MS = 1000;\n// Caps a frame's elapsed time, so a tab returning from the background doesn't\n// jump its press animations.\nconst MAX_ELAPSED_MS = 100;\n\n// The page's window, as `Document.defaultView` types it: with constructors\n// such as IntersectionObserver, which `Window` alone doesn't declare.\ntype PageWindow = Window & typeof globalThis;\n\nconst managers = new WeakMap<PageWindow, GlassManager>();\n\n/**\n * Looks after every `<blur-view>` of one page on Lynx for Web. It tracks which\n * are on screen, builds a `GlassGroup` for each visible container or lone\n * glass element, and runs their frames while any is visible. Each element's\n * shadow-root style follows: frosted CSS until WebGL draws it, then none.\n */\nexport class GlassManager {\n readonly #win: PageWindow;\n readonly #visible = new Set<HTMLElement>();\n readonly #groups = new Map<HTMLElement, GlassGroup>();\n readonly #detached = new Set<WeakRef<HTMLElement>>();\n readonly #intersection: IntersectionObserver;\n readonly #attributes: MutationObserver;\n #frame = 0;\n #poll = 0;\n #last = 0;\n\n /**\n * Use `GlassManager.for`, which keeps one manager per page.\n */\n constructor(win: PageWindow) {\n this.#win = win;\n this.#intersection = new win.IntersectionObserver((entries) =>\n this.#onIntersection(entries),\n );\n this.#attributes = new win.MutationObserver(() => this.#wake());\n }\n\n static for(win: PageWindow): GlassManager {\n let manager = managers.get(win);\n if (!manager) {\n manager = new GlassManager(win);\n managers.set(win, manager);\n }\n return manager;\n }\n\n register(element: HTMLElement): void {\n this.#intersection.observe(element);\n this.#attributes.observe(element, {\n attributes: true,\n attributeFilter: GLASS_ATTRIBUTES,\n });\n trackPress(element);\n }\n\n #onIntersection(entries: IntersectionObserverEntry[]): void {\n for (const { target, isIntersecting } of entries) {\n const element = target as HTMLElement;\n if (isIntersecting) {\n this.#visible.add(element);\n continue;\n }\n this.#visible.delete(element);\n // Observing a removed element would keep it alive after Lynx drops it.\n // Hold it weakly instead, in case it's put back (a detached view).\n if (!element.isConnected) {\n this.#intersection.unobserve(element);\n this.#detached.add(new WeakRef(element));\n if (!this.#poll) {\n this.#poll = this.#win.setInterval(\n () => this.#checkDetached(),\n DETACHED_POLL_MS,\n );\n }\n }\n }\n this.#wake();\n }\n\n #checkDetached(): void {\n for (const ref of this.#detached) {\n const element = ref.deref();\n // Still removed, and not yet garbage collected: keep checking.\n if (element && !element.isConnected) continue;\n if (element) this.#intersection.observe(element);\n this.#detached.delete(ref);\n }\n if (!this.#detached.size) {\n this.#win.clearInterval(this.#poll);\n this.#poll = 0;\n }\n }\n\n #wake(): void {\n if (this.#frame) return;\n this.#frame = this.#win.requestAnimationFrame((now) => this.#tick(now));\n }\n\n #tick(now: number): void {\n this.#frame = 0;\n const elapsed = this.#last ? Math.min(now - this.#last, MAX_ELAPSED_MS) : 0;\n this.#last = now;\n\n const roots = new Map<HTMLElement, boolean>();\n for (const element of this.#visible) {\n const role = glassRole(element);\n if (role === 'container' || role === 'standalone') {\n roots.set(element, role === 'container');\n }\n }\n for (const [root, group] of this.#groups) {\n if (roots.get(root) !== group.container || group.lost) {\n group.dispose();\n this.#groups.delete(root);\n }\n }\n for (const [root, container] of roots) {\n if (!this.#groups.has(root)) {\n this.#groups.set(root, new GlassGroup(root, container));\n }\n }\n\n const styled = new Set(this.#visible);\n const rendered = new Set<HTMLElement>();\n for (const group of this.#groups.values()) {\n group.frame(elapsed);\n for (const shape of group.shapes) {\n styled.add(shape);\n if (group.active) rendered.add(shape);\n }\n }\n for (const element of styled) {\n const group = this.#groups.get(element);\n const glass = element.getAttribute('blur-effect') === 'glass';\n const fallback = glass && !rendered.has(element);\n // A lone shape's canvas is its own box, so it can't swell or cast a\n // shadow there the way a container's shapes do. It swells as a whole,\n // content and all, as UIKit's interactive glass does, and its CSS\n // shadow (see glassShadow) grows with it.\n const lone = group !== undefined && !group.container;\n applyHostStyle(element, {\n hostsCanvas: group?.active ?? false,\n rendered: rendered.has(element),\n fallback,\n shadow: glass && (lone || fallback),\n scale: lone ? 1 + PRESS_GROWTH * (pressOf(element)?.value ?? 0) : 1,\n });\n }\n\n // Keep drawing while glass is on screen: shapes can move every frame\n // (dragged, animated) without any event this code would hear about.\n if (this.#groups.size) this.#wake();\n else this.#last = 0;\n }\n}\n","import type { ElementRef } from '../types/lynx';\nimport { applyBlurViewLayout } from './blur-view-layout';\nimport { GlassManager } from './glass-manager';\n\n/**\n * Gives a `<blur-view>` on Lynx for Web what web-elements' `<x-blur-view>`\n * lacks: a view's layout (see blur-view-layout.ts), and Liquid Glass.\n *\n * On iOS 26, `blur-effect=\"glass\"` and `\"glass-container\"` render UIKit's\n * glass. Lynx for Web turns `<blur-view>` into an `<x-blur-view>` custom\n * element that only implements `blur-radius`, so glass elements would show at\n * most a frosted blur, and merging and refraction not at all. This draws them\n * with WebGL instead (see glass-group.ts), from the same attributes.\n *\n * Called for every `<blur-view>` the main thread creates. On web that thread\n * runs in an iframe while its elements live in the page, so everything here\n * goes through the element's own window, never this realm's globals.\n */\nexport const attachWebGlass = (element: ElementRef): void => {\n const node = element as Partial<HTMLElement>;\n const win = node.ownerDocument?.defaultView;\n if (!win) return;\n // Every blur view needs it, glass or not, and before its first paint, which\n // this microtask after creation still is.\n applyBlurViewLayout(node as HTMLElement);\n // Only a real element in a browser can host the glass.\n if (!win.IntersectionObserver) return;\n try {\n GlassManager.for(win).register(node as HTMLElement);\n } catch {\n // Glass is decoration: never let it break rendering.\n }\n};\n"],"mappings":"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"}
|