@liquid-glass-forge/dom 0.0.0-stage → 0.1.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nami Poursafargholi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/NOTICE ADDED
@@ -0,0 +1,7 @@
1
+ Liquid Glass Forge
2
+ Copyright (c) 2026 Nami Poursafargholi. MIT licensed.
3
+
4
+ Independent implementation inspired by Apple's Liquid Glass design.
5
+ Not affiliated with, sponsored by or endorsed by Apple Inc.
6
+ Apple and its design/assets remain subject to their respective rights.
7
+ No Apple assets or upstream Liquid Glass/Sileo implementation is included.
package/README.md CHANGED
@@ -1,3 +1,66 @@
1
- # Temporary Holding Version
1
+ # <img src="https://raw.githubusercontent.com/namipsg/liquid-glass-forge/main/showcase/public/frameworks/vanilla.svg" width="20" height="20" alt=""> @liquid-glass-forge/dom
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Render glass on native HTML controls with explicit media sources and shared scenes.
4
+
5
+ [Live showcase](https://namipsg.github.io/liquid-glass-forge/#playground) · [Examples](https://namipsg.github.io/liquid-glass-forge/examples.html) · [Documentation](https://github.com/namipsg/liquid-glass-forge/blob/main/docs/HOSTS.md) · [Support](https://namipsg.github.io/liquid-glass-forge/support.html)
6
+
7
+ ## Install in Vanilla JS
8
+
9
+ ```sh
10
+ npm install @liquid-glass-forge/dom@next
11
+ ```
12
+
13
+ The starter uses CSS glass. Keep the Forge packages at matching versions.
14
+ The [live installer](https://namipsg.github.io/liquid-glass-forge/?framework=vanilla#developers)
15
+ shows verified exact versions and has a copy button.
16
+
17
+ [Open the Vanilla JS starter](https://github.com/namipsg/liquid-glass-forge/blob/main/docs/QUICKSTART.md#vanilla) · [Add WebGL refraction](https://github.com/namipsg/liquid-glass-forge/blob/main/docs/QUICKSTART.md#add-webgl-refraction)
18
+
19
+ [![A live glass lens bends the lettering beneath it](https://raw.githubusercontent.com/namipsg/liquid-glass-forge/main/docs/assets/glass-playground.gif)](https://namipsg.github.io/liquid-glass-forge/#playground)
20
+
21
+ ## Usage
22
+
23
+ Experimental preview `0.1.0-rc.2` sources, coordinate mapping and vanilla host/lifecycle.
24
+ Requires exact matching core/CPU package versions. ESM and TypeScript
25
+ declarations are included; imports are SSR-safe and start no timers or network.
26
+
27
+ ```ts
28
+ import { SourceProvider, mapSourceRegion, createGlassScene } from '@liquid-glass-forge/dom';
29
+ const p = new SourceProvider({ kind: 'pixels', width: 1, height: 1,
30
+ data: new Uint8Array([255, 0, 0, 255]) });
31
+ const snapshot = await p.read(); // independent, owned straight-sRGB RGBA8 bytes
32
+ p.destroy();
33
+ ```
34
+
35
+ Sources: explicit bytes, sRGB ImageData, borrowed ImageBitmap, decoded URL image,
36
+ readable canvas/OffscreenCanvas, or borrowed HTMLVideoElement. Inputs remain
37
+ caller-owned. Reads have bounded cancellation and latest-generation semantics.
38
+ For explicit bytes or sRGB ImageData, `readInto(target)` copies every pixel into
39
+ an attached caller-owned RGBA8 view of the exact size. It returns borrowed
40
+ metadata and never transfers or retains the target; source/target aliasing is
41
+ rejected. Reuse the target only after consumers have captured or finished it.
42
+ The ordinary `read()` continues to return a fresh owned snapshot.
43
+ Canvas drawing needs explicit invalidation; video subscription can observe frame
44
+ revisions through a cancellable frame callback or bounded event/timer fallback.
45
+
46
+ `mapSourceRegion` maps explicit viewport rectangles, object fit/position,
47
+ source-pixel padding, clipping, DPR and positive axis scale/translation. It returns
48
+ typed invalid/empty/unsupported results. Preserve sample/crop/output offsets;
49
+ it does not parse arbitrary CSS or capture DOM.
50
+
51
+ See the repository [source contract](https://github.com/namipsg/liquid-glass-forge/blob/main/docs/SOURCES.md)
52
+ for full API semantics and limits. `createGlassScene()` mounts CSS/opaque material with bounded lifecycle and shared
53
+ dirty/visibility scheduling. Load the `./styles.css` asset export; JavaScript imports
54
+ inject no styles. `scene.attach(element)`, `ready`, `update`, `pause`, `resume` and
55
+ `dispose` preserve native content and release owned resources. The optional
56
+ source/renderer pair is a low-level adapter seam; `createCpuRenderer` supplies
57
+ JavaScript CPU optics with optional workers, capped backing resolution and
58
+ cancellable update pacing. See the project Phase 5 document for usage and
59
+ source-bound performance/device qualification. A `surface(request)` callback
60
+ can supply per-request radius, bevel and strength; values are validated and
61
+ captured before asynchronous work. Diagnostics report effective optics, actual
62
+ execution start, rate wait and presentation pacing. Active completion pacing is
63
+ included in the documented service measurements; idle hosts schedule no work.
64
+ See the [host contract](https://github.com/namipsg/liquid-glass-forge/blob/main/docs/HOSTS.md).
65
+ Framework adapters and public browser-support tiers are outside this package.
66
+ Performance claims are limited to the repository's measured workload/environment.
package/dist/cpu.d.ts ADDED
@@ -0,0 +1,90 @@
1
+ import type { SourceRect } from '@liquid-glass-forge/core';
2
+ import type { CpuEffects, CpuExecutorOptions, CpuTimings } from '@liquid-glass-forge/cpu';
3
+ import type { FramePixels, GlassRenderer, RenderRequest } from './scene.js';
4
+ export interface CpuSurfaceOptions {
5
+ readonly radius?: number;
6
+ readonly bevel?: number;
7
+ readonly strength?: number;
8
+ }
9
+ export interface CpuRendererOptions {
10
+ readonly executor?: CpuExecutorOptions;
11
+ /** Distances are measured in effective output backing pixels. */
12
+ readonly surface?: CpuSurfaceOptions | ((request: RenderRequest) => CpuSurfaceOptions);
13
+ readonly effects?: CpuEffects;
14
+ /** Omitted means sample the complete source. */
15
+ readonly sourceRect?: SourceRect | ((request: RenderRequest) => SourceRect);
16
+ readonly quality?: {
17
+ readonly maxPixels?: number;
18
+ readonly maxWidth?: number;
19
+ readonly maxHeight?: number;
20
+ readonly maxUpdatesPerSecond?: number;
21
+ readonly maxSourcePixels?: number;
22
+ /** Explicit active-interaction policy; balanced/default retains its 30 Hz limit. */
23
+ readonly cadence?: 'balanced' | 'interactive';
24
+ };
25
+ }
26
+ /** One scene owns this adapter. Initialization owns resources; construction only snapshots data. */
27
+ export declare class CpuRenderer implements GlassRenderer {
28
+ #private;
29
+ constructor(options?: CpuRendererOptions);
30
+ private executorDiagnostics;
31
+ get diagnostics(): Readonly<{
32
+ backend: 'cpu';
33
+ execution: "main" | "worker";
34
+ disposed: boolean;
35
+ initialized: boolean;
36
+ requested: Readonly<{
37
+ width: number;
38
+ height: number;
39
+ }> | undefined;
40
+ effective: Readonly<{
41
+ width: number;
42
+ height: number;
43
+ }> | undefined;
44
+ requestedWidth: number | undefined;
45
+ requestedHeight: number | undefined;
46
+ effectiveWidth: number | undefined;
47
+ effectiveHeight: number | undefined;
48
+ qualityReduced: boolean;
49
+ reasons: readonly string[];
50
+ quality: Required<Omit<{
51
+ readonly maxPixels?: number;
52
+ readonly maxWidth?: number;
53
+ readonly maxHeight?: number;
54
+ readonly maxUpdatesPerSecond?: number;
55
+ readonly maxSourcePixels?: number;
56
+ /** Explicit active-interaction policy; balanced/default retains its 30 Hz limit. */
57
+ readonly cadence?: 'balanced' | 'interactive';
58
+ }, "cadence">>;
59
+ cadencePolicy?: "interactive";
60
+ timings: (CpuTimings & {
61
+ readonly captureMs: number;
62
+ readonly rateWaitMs: number;
63
+ readonly presentationWaitMs: number;
64
+ }) | undefined;
65
+ surface: Readonly<Required<CpuSurfaceOptions>> | undefined;
66
+ executor: Readonly<{
67
+ execution: "main" | "worker";
68
+ inFlight: 0 | 1;
69
+ pending: 0 | 1;
70
+ mainCachedBytes: number;
71
+ workerCachedBytes: number;
72
+ sharedKernelBytes: number;
73
+ fixedKernelReservationBytes: number;
74
+ pooledBytes: number;
75
+ inputPooledBytes: number;
76
+ inputPooledBuffers: number;
77
+ leasedBytes: number;
78
+ reservedBytes: number;
79
+ lostBytes: number;
80
+ completedBatches: number;
81
+ discardedBatches: number;
82
+ disposed: boolean;
83
+ fallbackReason?: string;
84
+ }> | undefined;
85
+ }>;
86
+ initialize(signal: AbortSignal): () => void;
87
+ render(request: RenderRequest, signal: AbortSignal): Promise<FramePixels>;
88
+ dispose(): void;
89
+ }
90
+ export declare function createCpuRenderer(options?: CpuRendererOptions): CpuRenderer;
package/dist/cpu.js ADDED
@@ -0,0 +1,240 @@
1
+ import { createFrame, MAX_PIXELS, validateSource } from '@liquid-glass-forge/core';
2
+ import { CpuError, CpuExecutor, CpuDeadlineScheduler, snapshotCpuEffects } from '@liquid-glass-forge/cpu';
3
+ function object(input, keys, name) {
4
+ if (!input || typeof input !== 'object' || Array.isArray(input) || Reflect.ownKeys(input).some(k => typeof k !== 'string' || !keys.includes(k))) {
5
+ throw new CpuError('INVALID_OPTIONS', `Invalid ${name}`);
6
+ }
7
+ }
8
+ function finite(value, low, high, name) {
9
+ if (!Number.isFinite(value) || value < low || value > high)
10
+ throw new CpuError('INVALID_OPTIONS', `Invalid ${name}`);
11
+ }
12
+ function sourceRect(input) {
13
+ object(input, ['x', 'y', 'width', 'height'], 'sourceRect');
14
+ finite(input.x, 0, MAX_PIXELS, 'sourceRect.x');
15
+ finite(input.y, 0, MAX_PIXELS, 'sourceRect.y');
16
+ finite(input.width, Number.MIN_VALUE, MAX_PIXELS, 'sourceRect.width');
17
+ finite(input.height, Number.MIN_VALUE, MAX_PIXELS, 'sourceRect.height');
18
+ return Object.freeze({ x: input.x, y: input.y, width: input.width, height: input.height });
19
+ }
20
+ function surfaceOptions(input) {
21
+ object(input, ['radius', 'bevel', 'strength'], 'surface');
22
+ if ('then' in input)
23
+ throw new CpuError('INVALID_OPTIONS', 'Surface options must be synchronous data');
24
+ if (input.radius !== undefined)
25
+ finite(input.radius, 0, 1_000_000, 'radius');
26
+ if (input.bevel !== undefined)
27
+ finite(input.bevel, Number.MIN_VALUE, 1_000_000, 'bevel');
28
+ if (input.strength !== undefined)
29
+ finite(input.strength, 0, 1_000_000, 'strength');
30
+ return Object.freeze({ ...input });
31
+ }
32
+ /** Floor after uniform scaling, then account for min-one rounding on very thin surfaces. */
33
+ function boundedSize(width, height, q) {
34
+ const scale = Math.min(1, q.maxWidth / width, q.maxHeight / height, Math.sqrt(q.maxPixels / width / height));
35
+ let w = Math.max(1, Math.floor(width * scale)), h = Math.max(1, Math.floor(height * scale));
36
+ if (w * h > q.maxPixels) {
37
+ if (w >= h)
38
+ w = Math.max(1, Math.floor(q.maxPixels / h));
39
+ else
40
+ h = Math.max(1, Math.floor(q.maxPixels / w));
41
+ }
42
+ return Object.freeze({ width: w, height: h });
43
+ }
44
+ /** One scene owns this adapter. Initialization owns resources; construction only snapshots data. */
45
+ export class CpuRenderer {
46
+ #executorOptions;
47
+ #surface;
48
+ #lastSurface;
49
+ #effects;
50
+ #sourceRect;
51
+ #quality;
52
+ #cadence;
53
+ #executor;
54
+ #pacer;
55
+ #cleanup;
56
+ #disposed = false;
57
+ #generation = 0;
58
+ #revision = 0;
59
+ #job;
60
+ #lastStart;
61
+ #lastReady;
62
+ #requested;
63
+ #effective;
64
+ #reasons = Object.freeze([]);
65
+ #timings;
66
+ #lastExecutor;
67
+ constructor(options = {}) {
68
+ object(options, ['executor', 'surface', 'effects', 'sourceRect', 'quality'], 'CPU renderer options');
69
+ const executor = options.executor === undefined ? {} : options.executor;
70
+ object(executor, ['cacheSources', 'worker', 'workerFactory', 'timeoutMs', 'maxPoolBytes', 'maxOwnedBytes'], 'executor options');
71
+ // CpuExecutor construction itself is data-only and is the canonical validator.
72
+ new CpuExecutor(executor);
73
+ this.#executorOptions = Object.freeze({ cacheSources: true, ...executor });
74
+ const surface = options.surface === undefined ? {} : options.surface;
75
+ this.#surface = typeof surface === 'function' ? surface : surfaceOptions(surface);
76
+ this.#effects = snapshotCpuEffects(options.effects);
77
+ this.#sourceRect = typeof options.sourceRect === 'function' || options.sourceRect === undefined ? options.sourceRect : sourceRect(options.sourceRect);
78
+ const quality = options.quality === undefined ? {} : options.quality;
79
+ object(quality, ['maxPixels', 'maxWidth', 'maxHeight', 'maxUpdatesPerSecond', 'maxSourcePixels', 'cadence'], 'quality');
80
+ if (quality.cadence !== undefined && !['balanced', 'interactive'].includes(quality.cadence))
81
+ throw new CpuError('INVALID_OPTIONS', 'Invalid CPU cadence policy');
82
+ this.#cadence = quality.cadence ?? 'balanced';
83
+ const q = { maxPixels: quality.maxPixels === undefined ? 57600 : quality.maxPixels,
84
+ maxWidth: quality.maxWidth === undefined ? 320 : quality.maxWidth, maxHeight: quality.maxHeight === undefined ? 180 : quality.maxHeight,
85
+ maxUpdatesPerSecond: quality.maxUpdatesPerSecond === undefined ? 30 : quality.maxUpdatesPerSecond,
86
+ maxSourcePixels: quality.maxSourcePixels === undefined ? 230400 : quality.maxSourcePixels };
87
+ for (const key of ['maxPixels', 'maxWidth', 'maxHeight', 'maxSourcePixels']) {
88
+ finite(q[key], 1, MAX_PIXELS, key);
89
+ if (!Number.isSafeInteger(q[key]))
90
+ throw new CpuError('INVALID_OPTIONS', `${key} must be an integer`);
91
+ }
92
+ finite(q.maxUpdatesPerSecond, 1, this.#cadence === 'interactive' ? 60 : 30, 'maxUpdatesPerSecond');
93
+ this.#quality = Object.freeze(q);
94
+ }
95
+ executorDiagnostics() { return this.#executor.diagnostics; }
96
+ get diagnostics() {
97
+ const executor = this.#executor?.diagnostics ?? this.#lastExecutor;
98
+ return Object.freeze({ backend: 'cpu', execution: executor?.execution ?? 'main', disposed: this.#disposed,
99
+ initialized: !!this.#executor, requested: this.#requested, effective: this.#effective,
100
+ requestedWidth: this.#requested?.width, requestedHeight: this.#requested?.height,
101
+ effectiveWidth: this.#effective?.width, effectiveHeight: this.#effective?.height,
102
+ qualityReduced: this.#reasons.length > 0, reasons: this.#reasons, quality: this.#quality,
103
+ ...(this.#cadence === 'interactive' ? { cadencePolicy: this.#cadence } : {}), timings: this.#timings, surface: this.#lastSurface, executor });
104
+ }
105
+ initialize(signal) {
106
+ if (this.#disposed)
107
+ throw new CpuError('DISPOSED', 'CPU renderer disposed');
108
+ if (signal.aborted)
109
+ throw new CpuError('ABORTED', 'CPU renderer initialization aborted');
110
+ this.#cleanup?.();
111
+ const executor = new CpuExecutor(this.#executorOptions), pacer = new CpuDeadlineScheduler(), generation = ++this.#generation;
112
+ this.#executor = executor;
113
+ this.#pacer = pacer;
114
+ this.#lastStart = undefined;
115
+ this.#lastReady = undefined;
116
+ let cleaned = false;
117
+ const cleanup = () => {
118
+ if (cleaned)
119
+ return;
120
+ cleaned = true;
121
+ signal.removeEventListener('abort', cleanup);
122
+ if (generation === this.#generation && this.#executor === executor) {
123
+ this.#generation++;
124
+ this.#job?.abort(new CpuError('ABORTED', 'CPU renderer initialization ended'));
125
+ this.#job = undefined;
126
+ this.#executor = undefined;
127
+ this.#pacer = undefined;
128
+ this.#cleanup = undefined;
129
+ this.#lastReady = undefined;
130
+ }
131
+ executor.dispose();
132
+ pacer.dispose();
133
+ this.#lastExecutor = executor.diagnostics;
134
+ };
135
+ this.#cleanup = cleanup;
136
+ signal.addEventListener('abort', cleanup, { once: true });
137
+ if (signal.aborted)
138
+ cleanup();
139
+ return cleanup;
140
+ }
141
+ async render(request, signal) {
142
+ if (this.#disposed)
143
+ throw new CpuError('DISPOSED', 'CPU renderer disposed');
144
+ const executor = this.#executor, pacer = this.#pacer, generation = this.#generation;
145
+ if (!executor || !pacer)
146
+ throw new CpuError('NOT_INITIALIZED', 'Initialize CPU renderer before rendering');
147
+ this.#job?.abort(new CpuError('SUPERSEDED', 'CPU render superseded'));
148
+ const controller = new AbortController();
149
+ this.#job = controller;
150
+ const abort = () => controller.abort(new CpuError('ABORTED', 'CPU render aborted'));
151
+ signal.addEventListener('abort', abort, { once: true });
152
+ if (signal.aborted)
153
+ abort();
154
+ const current = () => {
155
+ if (controller.signal.aborted)
156
+ throw controller.signal.reason;
157
+ if (this.#disposed || generation !== this.#generation || executor !== this.#executor || this.#job !== controller)
158
+ throw new CpuError('STALE', 'CPU render no longer current');
159
+ };
160
+ try {
161
+ current();
162
+ if (!request || !Number.isSafeInteger(request.width) || !Number.isSafeInteger(request.height) || request.width < 1 || request.height < 1)
163
+ throw new CpuError('INVALID_OPTIONS', 'Invalid requested CPU dimensions');
164
+ validateSource(request.snapshot.source);
165
+ const source = request.snapshot.source;
166
+ if (source.width * source.height > this.#quality.maxSourcePixels)
167
+ throw new CpuError('SOURCE_LIMIT', 'CPU source exceeds maxSourcePixels');
168
+ const size = boundedSize(request.width, request.height, this.#quality), reasons = [];
169
+ if (request.width > this.#quality.maxWidth)
170
+ reasons.push('maxWidth');
171
+ if (request.height > this.#quality.maxHeight)
172
+ reasons.push('maxHeight');
173
+ if (request.width * request.height > this.#quality.maxPixels)
174
+ reasons.push('maxPixels');
175
+ const rect = typeof this.#sourceRect === 'function' ? sourceRect(this.#sourceRect(request)) : this.#sourceRect;
176
+ const surface = typeof this.#surface === 'function' ? surfaceOptions(this.#surface(request)) : this.#surface;
177
+ if (this.#revision === Number.MAX_SAFE_INTEGER)
178
+ throw new CpuError('INVALID_OPTIONS', 'CPU renderer revision exhausted');
179
+ const frame = createFrame({ contractVersion: 1, generation: request.snapshot.generation, revision: ++this.#revision, sourceRevision: source.revision,
180
+ surfaces: [{ id: 'cpu', ...size, ...surface, ...(rect === undefined ? {} : { sourceRect: rect }) }] }, source);
181
+ const effectiveSurface = frame.surfaces[0];
182
+ this.#lastSurface = Object.freeze({ radius: effectiveSurface.radius, bevel: effectiveSurface.bevel, strength: effectiveSurface.strength });
183
+ this.#requested = Object.freeze({ width: request.width, height: request.height });
184
+ this.#effective = size;
185
+ const period = 1000 / this.#quality.maxUpdatesPerSecond;
186
+ // Reserve measured computation time before the next presentation. Fast
187
+ // kernels should wait before execution instead of spending a whole frame
188
+ // in presentation smoothing. Source capture and cancellation stay owned.
189
+ const computeMargin = Math.min(period, Math.max(4, (this.#timings?.roundTripMs ?? 0) * 1.25 + 1));
190
+ const notBefore = this.#lastStart === undefined ? 0 : Math.max(this.#lastStart + period, (this.#lastReady ?? 0) + period - computeMargin);
191
+ const paceOutput = this.#lastStart !== undefined && performance.now() - this.#lastStart < 2000 / this.#quality.maxUpdatesPerSecond;
192
+ current();
193
+ this.#reasons = Object.freeze([...reasons]);
194
+ // The executor captures owned bytes synchronously before its deferred start.
195
+ // This avoids a second adapter snapshot during a rate wait.
196
+ const pending = executor.render(source, frame, {
197
+ ...(this.#effects === undefined ? {} : { effects: this.#effects }), signal: controller.signal, notBefore,
198
+ onStart: startedAt => { current(); this.#lastStart = startedAt; }
199
+ });
200
+ const batch = await pending;
201
+ try {
202
+ current();
203
+ // Keep completion cadence without an unconditional execution-relative
204
+ // delay. The next execution already reserves its measured compute time.
205
+ const presentationWaitMs = paceOutput && this.#lastReady !== undefined
206
+ ? await pacer.waitUntil(this.#lastReady + period, controller.signal) : 0;
207
+ current();
208
+ const output = batch.surfaces[0], rateWaitMs = batch.timings.scheduledWaitMs ?? 0;
209
+ if (rateWaitMs > 0 || presentationWaitMs > 0)
210
+ reasons.push('maxUpdatesPerSecond');
211
+ this.#reasons = Object.freeze(reasons);
212
+ this.#timings = Object.freeze({ ...batch.timings, captureMs: 0, rateWaitMs, presentationWaitMs });
213
+ this.#lastReady = performance.now();
214
+ return Object.freeze({ width: output.width, height: output.height, data: output.data, release: () => {
215
+ batch.release();
216
+ if (this.#executor !== executor && this.#generation === generation + 1)
217
+ this.#lastExecutor = executor.diagnostics;
218
+ } });
219
+ }
220
+ catch (cause) {
221
+ batch.release();
222
+ if (this.#executor !== executor && this.#generation === generation + 1)
223
+ this.#lastExecutor = executor.diagnostics;
224
+ throw cause;
225
+ }
226
+ }
227
+ finally {
228
+ signal.removeEventListener('abort', abort);
229
+ if (this.#job === controller)
230
+ this.#job = undefined;
231
+ }
232
+ }
233
+ dispose() {
234
+ if (this.#disposed)
235
+ return;
236
+ this.#disposed = true;
237
+ this.#cleanup?.();
238
+ }
239
+ }
240
+ export function createCpuRenderer(options = {}) { return new CpuRenderer(options); }
package/dist/host.d.ts ADDED
@@ -0,0 +1,41 @@
1
+ import { type PixelBytes } from '@liquid-glass-forge/core';
2
+ export type GlassMaterial = 'glass' | 'opaque';
3
+ export interface HostPixels {
4
+ readonly width: number;
5
+ readonly height: number;
6
+ readonly data: PixelBytes;
7
+ }
8
+ export interface HostCanvas {
9
+ readonly width: number;
10
+ readonly height: number;
11
+ readonly canvas: HTMLCanvasElement;
12
+ }
13
+ export interface HostPresented {
14
+ readonly width: number;
15
+ readonly height: number;
16
+ readonly presentedTarget: HTMLCanvasElement;
17
+ }
18
+ export type HostFrame = HostPixels | HostCanvas | HostPresented;
19
+ export interface GlassHostOptions {
20
+ readonly target?: HTMLCanvasElement;
21
+ readonly material?: GlassMaterial;
22
+ }
23
+ export interface GlassHost {
24
+ readonly element: HTMLElement;
25
+ readonly target: HTMLCanvasElement;
26
+ measure(): Readonly<{
27
+ width: number;
28
+ height: number;
29
+ }>;
30
+ setMaterial(material: GlassMaterial): void;
31
+ prepareCanvas(width: number, height: number): HTMLCanvasElement;
32
+ present(pixels: HostFrame): void;
33
+ clear(): void;
34
+ dispose(): void;
35
+ }
36
+ export declare class HostError extends Error {
37
+ readonly code: 'INVALID_HOST' | 'INVALID_PIXELS' | 'UNAVAILABLE_HOST' | 'DISPOSED';
38
+ constructor(code: 'INVALID_HOST' | 'INVALID_PIXELS' | 'UNAVAILABLE_HOST' | 'DISPOSED', message: string, options?: ErrorOptions);
39
+ }
40
+ /** Own only a decorative target and explicitly touched host styles. Never move semantic children. */
41
+ export declare function mountGlassHost(element: HTMLElement, options?: GlassHostOptions): GlassHost;