@liquid-glass-forge/react 0.0.0-stage → 0.1.0-rc.1
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 +21 -0
- package/NOTICE +7 -0
- package/README.md +28 -2
- package/dist/index.d.ts +29 -0
- package/dist/index.js +90 -0
- package/package.json +37 -4
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,29 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Liquid Glass Forge React
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Experimental preview. API and device/performance qualification remain open; iPad is unqualified.
|
|
4
|
+
|
|
5
|
+
Experimental preview `0.1.0-rc.1`. Pinned peers: React and React DOM **19.3.1**.
|
|
6
|
+
Framework peers are imported, never bundled into this package. The ESM entry
|
|
7
|
+
retains `use client` for Next.js client boundaries and has public JSX declarations.
|
|
8
|
+
|
|
9
|
+
Import `GlassSurface`, `GlassSceneProvider` and `useGlassSurface` from
|
|
10
|
+
`@liquid-glass-forge/react`. Import `@liquid-glass-forge/dom/styles.css` in the
|
|
11
|
+
application for the server/no-JavaScript material baseline. No stylesheet is
|
|
12
|
+
injected by JavaScript.
|
|
13
|
+
|
|
14
|
+
`GlassSurface` renders a stable div, decorative canvas and ordinary children.
|
|
15
|
+
It forwards the div ref, including callback-ref cleanup. Without a provider it
|
|
16
|
+
uses CSS material. A provider accepts an explicit `source`/`renderer` pair,
|
|
17
|
+
`paused`, `timeoutMs` and `onError`; multiple children share one scene.
|
|
18
|
+
Source or renderer identity changes replace the scene. Keep these objects stable
|
|
19
|
+
between intentional replacements. The caller owns these reusable objects;
|
|
20
|
+
effect cleanup releases scene resources without terminally disposing them.
|
|
21
|
+
|
|
22
|
+
`useGlassSurface({material})` returns `ref`, `targetRef` and `invalidate`. Render
|
|
23
|
+
the target as a dedicated direct canvas child with the decorative CSS class,
|
|
24
|
+
material data attribute, `aria-hidden`, presentation role and negative tab index.
|
|
25
|
+
React owns both DOM nodes. Do not concurrently control canvas backing dimensions
|
|
26
|
+
or draw into its context while mounted.
|
|
27
|
+
|
|
28
|
+
See repository `docs/PHASE-7.md` for usage, Strict Mode ownership, exact Vite/Next
|
|
29
|
+
consumer evidence and remaining integration limits. No registry package is published.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode, RefCallback } from 'react';
|
|
2
|
+
import type { GlassScene, GlassMaterial, GlassRenderer, SceneSource } from '@liquid-glass-forge/dom';
|
|
3
|
+
export interface GlassSceneProviderProps {
|
|
4
|
+
readonly children?: ReactNode;
|
|
5
|
+
readonly source?: SceneSource;
|
|
6
|
+
readonly renderer?: GlassRenderer;
|
|
7
|
+
readonly timeoutMs?: number;
|
|
8
|
+
readonly paused?: boolean;
|
|
9
|
+
readonly onError?: (error: unknown) => void;
|
|
10
|
+
}
|
|
11
|
+
/** Caller owns source/renderer objects. Each effect owns one engine lifetime. */
|
|
12
|
+
export declare function GlassSceneProvider({ children, source, renderer, timeoutMs, paused, onError }: GlassSceneProviderProps): import("react").FunctionComponentElement<import("react").ProviderProps<{
|
|
13
|
+
readonly scene: GlassScene | null;
|
|
14
|
+
} | null>>;
|
|
15
|
+
export interface UseGlassSurfaceOptions {
|
|
16
|
+
readonly material?: GlassMaterial;
|
|
17
|
+
}
|
|
18
|
+
export interface GlassSurfaceBinding {
|
|
19
|
+
readonly ref: RefCallback<HTMLElement>;
|
|
20
|
+
readonly targetRef: RefCallback<HTMLCanvasElement>;
|
|
21
|
+
readonly invalidate: () => void;
|
|
22
|
+
}
|
|
23
|
+
/** Supply a dedicated direct canvas child. React retains both DOM nodes. */
|
|
24
|
+
export declare function useGlassSurface({ material }?: UseGlassSurfaceOptions): GlassSurfaceBinding;
|
|
25
|
+
export interface GlassSurfaceProps extends HTMLAttributes<HTMLDivElement>, UseGlassSurfaceOptions {
|
|
26
|
+
readonly children?: ReactNode;
|
|
27
|
+
}
|
|
28
|
+
/** Stable server/client markup; no DOM access or engine allocation in render. */
|
|
29
|
+
export declare const GlassSurface: import("react").ForwardRefExoticComponent<GlassSurfaceProps & import("react").RefAttributes<HTMLDivElement>>;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { createContext, createElement, forwardRef, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
|
4
|
+
import { createGlassScene } from '@liquid-glass-forge/dom';
|
|
5
|
+
const SceneContext = createContext(null);
|
|
6
|
+
/** Caller owns source/renderer objects. Each effect owns one engine lifetime. */
|
|
7
|
+
export function GlassSceneProvider({ children, source, renderer, timeoutMs, paused = false, onError }) {
|
|
8
|
+
const [scene, setScene] = useState(null);
|
|
9
|
+
const error = useRef(onError);
|
|
10
|
+
const pausedRef = useRef(paused);
|
|
11
|
+
useEffect(() => { error.current = onError; pausedRef.current = paused; }, [onError, paused]);
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
const next = createGlassScene({ ...(source ? { source } : {}), ...(renderer ? { renderer } : {}),
|
|
14
|
+
...(timeoutMs === undefined ? {} : { timeoutMs }), onError: cause => error.current?.(cause) });
|
|
15
|
+
if (pausedRef.current)
|
|
16
|
+
next.pause();
|
|
17
|
+
setScene(next);
|
|
18
|
+
return () => { next.dispose(); setScene(null); };
|
|
19
|
+
}, [source, renderer, timeoutMs]);
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (!scene || scene.state === 'destroyed')
|
|
22
|
+
return;
|
|
23
|
+
if (paused)
|
|
24
|
+
scene.pause();
|
|
25
|
+
else if (scene.state === 'paused')
|
|
26
|
+
scene.resume();
|
|
27
|
+
}, [scene, paused]);
|
|
28
|
+
const value = useMemo(() => ({ scene }), [scene]);
|
|
29
|
+
return createElement(SceneContext.Provider, { value }, children);
|
|
30
|
+
}
|
|
31
|
+
/** Supply a dedicated direct canvas child. React retains both DOM nodes. */
|
|
32
|
+
export function useGlassSurface({ material = 'glass' } = {}) {
|
|
33
|
+
const shared = useContext(SceneContext);
|
|
34
|
+
const [element, setElement] = useState(null);
|
|
35
|
+
const [target, setTarget] = useState(null);
|
|
36
|
+
const attachment = useRef(null);
|
|
37
|
+
const materialRef = useRef(material);
|
|
38
|
+
useEffect(() => { materialRef.current = material; }, [material]);
|
|
39
|
+
const ref = useCallback((node) => { setElement(node); }, []);
|
|
40
|
+
const targetRef = useCallback((node) => { setTarget(node); }, []);
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (!element || !target || (shared && !shared.scene))
|
|
43
|
+
return;
|
|
44
|
+
const owned = shared ? null : createGlassScene();
|
|
45
|
+
const scene = shared?.scene ?? owned;
|
|
46
|
+
if (scene.state === 'destroyed')
|
|
47
|
+
return;
|
|
48
|
+
let current;
|
|
49
|
+
try {
|
|
50
|
+
current = scene.attach(element, { target, material: materialRef.current });
|
|
51
|
+
}
|
|
52
|
+
catch (cause) {
|
|
53
|
+
owned?.dispose();
|
|
54
|
+
throw cause;
|
|
55
|
+
}
|
|
56
|
+
attachment.current = current;
|
|
57
|
+
return () => {
|
|
58
|
+
if (attachment.current === current)
|
|
59
|
+
attachment.current = null;
|
|
60
|
+
current.dispose();
|
|
61
|
+
owned?.dispose();
|
|
62
|
+
};
|
|
63
|
+
}, [element, target, shared?.scene, !!shared]);
|
|
64
|
+
useEffect(() => { attachment.current?.update({ material }); }, [material]);
|
|
65
|
+
const invalidate = useCallback(() => { attachment.current?.invalidate(); }, []);
|
|
66
|
+
return useMemo(() => ({ ref, targetRef, invalidate }), [ref, targetRef, invalidate]);
|
|
67
|
+
}
|
|
68
|
+
/** Stable server/client markup; no DOM access or engine allocation in render. */
|
|
69
|
+
export const GlassSurface = forwardRef(function GlassSurface({ children, material = 'glass', className, ...attributes }, forwardedRef) {
|
|
70
|
+
const binding = useGlassSurface({ material });
|
|
71
|
+
const ref = useCallback((node) => {
|
|
72
|
+
binding.ref(node);
|
|
73
|
+
if (typeof forwardedRef === 'function') {
|
|
74
|
+
// ForwardedRef retains its legacy void signature; public RefCallback supports cleanup.
|
|
75
|
+
const cleanup = forwardedRef(node);
|
|
76
|
+
if (node)
|
|
77
|
+
return () => { binding.ref(null); if (typeof cleanup === 'function')
|
|
78
|
+
cleanup();
|
|
79
|
+
else
|
|
80
|
+
forwardedRef(null); };
|
|
81
|
+
}
|
|
82
|
+
else if (forwardedRef) {
|
|
83
|
+
forwardedRef.current = node;
|
|
84
|
+
if (node)
|
|
85
|
+
return () => { binding.ref(null); forwardedRef.current = null; };
|
|
86
|
+
}
|
|
87
|
+
return undefined;
|
|
88
|
+
}, [binding.ref, forwardedRef]);
|
|
89
|
+
return _jsxs("div", { ...attributes, className: ['lgf-host', className].filter(Boolean).join(' '), ref: ref, children: [_jsx("canvas", { className: "lgf-surface", "data-lgf-material": material, "aria-hidden": "true", role: "presentation", tabIndex: -1, ref: binding.targetRef }), children] });
|
|
90
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,39 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@liquid-glass-forge/react",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0-rc.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"engines": {
|
|
9
|
+
"node": ">=20.19.0"
|
|
10
|
+
},
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.js",
|
|
15
|
+
"default": "./dist/index.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"LICENSE",
|
|
21
|
+
"NOTICE",
|
|
22
|
+
"README.md"
|
|
23
|
+
],
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@liquid-glass-forge/dom": "0.1.0-rc.1"
|
|
26
|
+
},
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"react": "19.3.1",
|
|
29
|
+
"react-dom": "19.3.1"
|
|
30
|
+
},
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "git+https://github.com/namipsg/liquid-glass-forge.git",
|
|
34
|
+
"directory": "packages/react"
|
|
35
|
+
},
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
38
|
+
}
|
|
39
|
+
}
|