@materials3d/element 0.1.0
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/README.md +70 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.js +163 -0
- package/dist/index.js.map +1 -0
- package/package.json +62 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Amir Abushanab
|
|
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/README.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<a href="https://github.com/Amir-Abushanab/materials3d"><img src="https://raw.githubusercontent.com/Amir-Abushanab/materials3d/main/brand/icon-192.png" alt="" width="64" align="right"></a>
|
|
2
|
+
|
|
3
|
+
# @materials3d/element
|
|
4
|
+
|
|
5
|
+
The `<materials-3d>` custom element: a drop-in Materials3D scene for Vue, Svelte or plain HTML.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @materials3d/element three
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```html
|
|
12
|
+
<script type="module">
|
|
13
|
+
import "@materials3d/element";
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<materials-3d
|
|
17
|
+
preset="skewer"
|
|
18
|
+
poster="/hero.webp"
|
|
19
|
+
min-size="520"
|
|
20
|
+
style="display:block; width:100%; height:100vh"
|
|
21
|
+
></materials-3d>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Light DOM, `display: block`, self-registering on import (guarded, so importing under Node during
|
|
25
|
+
SSR is a no-op). Poster first and lazy, with three code-split out of the initial load. `register(tag)`
|
|
26
|
+
registers the class under another tag name.
|
|
27
|
+
|
|
28
|
+
## Attributes
|
|
29
|
+
|
|
30
|
+
| attribute | notes |
|
|
31
|
+
| --------------------------- | --------------------------------------------------------------------------- |
|
|
32
|
+
| `preset` | a shipped preset name |
|
|
33
|
+
| `src` | URL of a config JSON, for example one of the files in `gallery/` |
|
|
34
|
+
| `config` | inline JSON, merged one level deep over `preset` and `src` |
|
|
35
|
+
| `transparent` | drop the backdrop, so the scene composites over the page |
|
|
36
|
+
| `paused` | live; toggles playback |
|
|
37
|
+
| `poster`, `poster-fit` | `poster-fit` is `fill` (default, matches the canvas), `cover` or `contain` |
|
|
38
|
+
| `lazy`, `webgl`, `min-size` | shell options |
|
|
39
|
+
| `renderer` | `webgpu` selects the experimental WebGPU/TSL engine; anything else is WebGL |
|
|
40
|
+
|
|
41
|
+
Boolean attributes: present means true, `"false"` or `"0"` means false, absent means the shell
|
|
42
|
+
default.
|
|
43
|
+
|
|
44
|
+
`poster`, `poster-fit`, `lazy`, `webgl`, `min-size` and `renderer` are read once at mount.
|
|
45
|
+
Changing one on a connected element does nothing until it is disconnected and reconnected.
|
|
46
|
+
`config`, `src`, `preset`, `transparent` and `paused` are live.
|
|
47
|
+
|
|
48
|
+
## Properties and events
|
|
49
|
+
|
|
50
|
+
The `config` property (a `Partial<SceneConfig>`) is merged last, one level deep, for framework
|
|
51
|
+
bindings. The read-only `handle` is the shell's `MaterialHandle`, or `null` before connect and
|
|
52
|
+
after disconnect. The element is declared in `HTMLElementTagNameMap`, so
|
|
53
|
+
`document.querySelector("materials-3d")` types as `Materials3DElement` and `.handle` typechecks
|
|
54
|
+
without a cast.
|
|
55
|
+
|
|
56
|
+
Events: `materials3d-ready` (detail: the renderer) and `materials3d-fallback` (detail: the reason
|
|
57
|
+
the scene stayed a poster).
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
const el = document.querySelector("materials-3d");
|
|
61
|
+
if (el) {
|
|
62
|
+
el.addEventListener("materials3d-ready", async () => {
|
|
63
|
+
const blob = await el.handle?.snapshot({ time: 0 });
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Peer dependency: `three >= 0.180 < 1`. `@types/three` is an optional peer.
|
|
69
|
+
|
|
70
|
+
MIT.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { FallbackReason, MaterialHandle, MaterialHandle as MaterialHandle$1, SceneConfig, SceneConfig as SceneConfig$1, SnapshotOptions } from "@materials3d/core";
|
|
2
|
+
//#region src/index.d.ts
|
|
3
|
+
declare const ElementBase: typeof HTMLElement;
|
|
4
|
+
/**
|
|
5
|
+
* `<materials-3d>`: the framework-agnostic drop-in (Vue/Svelte/plain HTML). Light DOM, `display:block`.
|
|
6
|
+
*
|
|
7
|
+
* Config attributes: `config` (JSON), `src` (URL to a config JSON), `preset` (name) and
|
|
8
|
+
* `transparent` (drop the backdrop; `"false"` or `"0"` keeps it), merged in that order, each one
|
|
9
|
+
* level deep (see `mergeSceneConfig`), then the `config` property last. `paused` starts and stops
|
|
10
|
+
* playback. These are LIVE: a change after mount is pushed to the running scene. A `src` is
|
|
11
|
+
* fetched once per URL and a `preset` resolved once per name, so an update re-reads neither.
|
|
12
|
+
*
|
|
13
|
+
* Shell options: `poster`, `poster-fit` (`fill` | `cover` | `contain`), `lazy`, `webgl`
|
|
14
|
+
* (`auto` | `force` | `off`), `min-size` (CSS px) and `renderer` (`webgl`, the default, or
|
|
15
|
+
* `webgpu` for the experimental node engine, a separate bundle). Read once at mount; changing one
|
|
16
|
+
* on a live element does nothing until it is re-connected (the same contract as the React
|
|
17
|
+
* wrapper's mount-time props).
|
|
18
|
+
*
|
|
19
|
+
* Also a `config` property and a read-only `handle` getter. Emits `materials3d-ready` (detail =
|
|
20
|
+
* renderer) and `materials3d-fallback` (detail = reason).
|
|
21
|
+
*/
|
|
22
|
+
declare class Materials3DElement extends ElementBase {
|
|
23
|
+
#private;
|
|
24
|
+
static get observedAttributes(): string[];
|
|
25
|
+
/** The live shell handle (null before connect / after disconnect). */
|
|
26
|
+
get handle(): MaterialHandle$1 | null;
|
|
27
|
+
/** Programmatic config, merged last (over the `preset`/`src`/`config` attributes). */
|
|
28
|
+
get config(): Partial<SceneConfig$1>;
|
|
29
|
+
set config(value: Partial<SceneConfig$1>);
|
|
30
|
+
connectedCallback(): void;
|
|
31
|
+
disconnectedCallback(): void;
|
|
32
|
+
attributeChangedCallback(name: string): void;
|
|
33
|
+
}
|
|
34
|
+
/** Define the element (idempotent, SSR/Node-import-safe). */
|
|
35
|
+
declare function register(tag?: string): void;
|
|
36
|
+
declare global {
|
|
37
|
+
interface HTMLElementTagNameMap {
|
|
38
|
+
/** So `document.querySelector("materials-3d")` types as the element. */
|
|
39
|
+
"materials-3d": Materials3DElement;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
//#endregion
|
|
43
|
+
export { type FallbackReason, type MaterialHandle, Materials3DElement, type SceneConfig, type SnapshotOptions, register };
|
|
44
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { createMaterials, mergeSceneConfig } from "@materials3d/core";
|
|
2
|
+
//#region src/index.ts
|
|
3
|
+
/** The attributes a live element answers to. The shell options are read once at mount instead. */
|
|
4
|
+
const LIVE_ATTRIBUTES = [
|
|
5
|
+
"config",
|
|
6
|
+
"src",
|
|
7
|
+
"preset",
|
|
8
|
+
"transparent",
|
|
9
|
+
"paused"
|
|
10
|
+
];
|
|
11
|
+
const ElementBase = typeof HTMLElement !== "undefined" ? HTMLElement : function Materials3DElementBase() {};
|
|
12
|
+
/**
|
|
13
|
+
* `<materials-3d>`: the framework-agnostic drop-in (Vue/Svelte/plain HTML). Light DOM, `display:block`.
|
|
14
|
+
*
|
|
15
|
+
* Config attributes: `config` (JSON), `src` (URL to a config JSON), `preset` (name) and
|
|
16
|
+
* `transparent` (drop the backdrop; `"false"` or `"0"` keeps it), merged in that order, each one
|
|
17
|
+
* level deep (see `mergeSceneConfig`), then the `config` property last. `paused` starts and stops
|
|
18
|
+
* playback. These are LIVE: a change after mount is pushed to the running scene. A `src` is
|
|
19
|
+
* fetched once per URL and a `preset` resolved once per name, so an update re-reads neither.
|
|
20
|
+
*
|
|
21
|
+
* Shell options: `poster`, `poster-fit` (`fill` | `cover` | `contain`), `lazy`, `webgl`
|
|
22
|
+
* (`auto` | `force` | `off`), `min-size` (CSS px) and `renderer` (`webgl`, the default, or
|
|
23
|
+
* `webgpu` for the experimental node engine, a separate bundle). Read once at mount; changing one
|
|
24
|
+
* on a live element does nothing until it is re-connected (the same contract as the React
|
|
25
|
+
* wrapper's mount-time props).
|
|
26
|
+
*
|
|
27
|
+
* Also a `config` property and a read-only `handle` getter. Emits `materials3d-ready` (detail =
|
|
28
|
+
* renderer) and `materials3d-fallback` (detail = reason).
|
|
29
|
+
*/
|
|
30
|
+
var Materials3DElement = class extends ElementBase {
|
|
31
|
+
static get observedAttributes() {
|
|
32
|
+
return [...LIVE_ATTRIBUTES];
|
|
33
|
+
}
|
|
34
|
+
#handle = null;
|
|
35
|
+
#config = {};
|
|
36
|
+
#debounce;
|
|
37
|
+
/**
|
|
38
|
+
* Which connect the in-flight `#mount` belongs to. `#mount` awaits the config, and a
|
|
39
|
+
* disconnect + reconnect during that await queues a SECOND mount, without this check both
|
|
40
|
+
* would finish and the first handle would be overwritten undestroyed (a leaked renderer).
|
|
41
|
+
*/
|
|
42
|
+
#mountId = 0;
|
|
43
|
+
/** The preset the last build resolved, so an update to another attribute skips the import. */
|
|
44
|
+
#preset = null;
|
|
45
|
+
/** The last `src` fetched successfully; a failed fetch is not cached, so the next update retries. */
|
|
46
|
+
#src = null;
|
|
47
|
+
/** The live shell handle (null before connect / after disconnect). */
|
|
48
|
+
get handle() {
|
|
49
|
+
return this.#handle;
|
|
50
|
+
}
|
|
51
|
+
/** Programmatic config, merged last (over the `preset`/`src`/`config` attributes). */
|
|
52
|
+
get config() {
|
|
53
|
+
return this.#config;
|
|
54
|
+
}
|
|
55
|
+
set config(value) {
|
|
56
|
+
this.#config = value ?? {};
|
|
57
|
+
this.#scheduleUpdate();
|
|
58
|
+
}
|
|
59
|
+
connectedCallback() {
|
|
60
|
+
if (!this.style.display) this.style.display = "block";
|
|
61
|
+
this.#mount(++this.#mountId);
|
|
62
|
+
}
|
|
63
|
+
disconnectedCallback() {
|
|
64
|
+
this.#mountId++;
|
|
65
|
+
clearTimeout(this.#debounce);
|
|
66
|
+
this.#handle?.destroy();
|
|
67
|
+
this.#handle = null;
|
|
68
|
+
}
|
|
69
|
+
attributeChangedCallback(name) {
|
|
70
|
+
if (!this.#handle) return;
|
|
71
|
+
if (name === "paused") {
|
|
72
|
+
if (this.#boolAttr("paused")) this.#handle.pause();
|
|
73
|
+
else this.#handle.play();
|
|
74
|
+
} else this.#scheduleUpdate();
|
|
75
|
+
}
|
|
76
|
+
async #mount(id) {
|
|
77
|
+
const config = await this.#buildConfig();
|
|
78
|
+
if (id !== this.#mountId || !this.isConnected) return;
|
|
79
|
+
const minSize = Number(this.getAttribute("min-size"));
|
|
80
|
+
const options = {
|
|
81
|
+
poster: this.getAttribute("poster") ?? void 0,
|
|
82
|
+
posterFit: this.getAttribute("poster-fit") ?? void 0,
|
|
83
|
+
lazy: this.#boolAttr("lazy"),
|
|
84
|
+
webgl: this.getAttribute("webgl") ?? void 0,
|
|
85
|
+
minSizeForWebGL: Number.isFinite(minSize) && minSize > 0 ? minSize : void 0,
|
|
86
|
+
paused: this.#boolAttr("paused"),
|
|
87
|
+
renderer: this.getAttribute("renderer") === "webgpu" ? "webgpu" : void 0,
|
|
88
|
+
onReady: (renderer) => this.dispatchEvent(new CustomEvent("materials3d-ready", { detail: renderer })),
|
|
89
|
+
onFallback: (reason) => this.dispatchEvent(new CustomEvent("materials3d-fallback", { detail: reason }))
|
|
90
|
+
};
|
|
91
|
+
this.#handle = createMaterials(this, config, options);
|
|
92
|
+
}
|
|
93
|
+
/** default ← preset ← src JSON ← config attribute ← config property, one level deep each. */
|
|
94
|
+
async #buildConfig() {
|
|
95
|
+
let base = {};
|
|
96
|
+
const presetName = this.getAttribute("preset");
|
|
97
|
+
if (presetName) {
|
|
98
|
+
if (this.#preset?.name !== presetName) {
|
|
99
|
+
const { PRESETS, isPresetName } = await import("@materials3d/core/presets");
|
|
100
|
+
const make = isPresetName(presetName) ? PRESETS[presetName] : () => ({});
|
|
101
|
+
this.#preset = {
|
|
102
|
+
name: presetName,
|
|
103
|
+
make
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
base = this.#preset.make();
|
|
107
|
+
}
|
|
108
|
+
const src = this.getAttribute("src");
|
|
109
|
+
if (src) {
|
|
110
|
+
if (this.#src?.url !== src) try {
|
|
111
|
+
const json = await fetch(src).then((r) => r.json());
|
|
112
|
+
if (json && typeof json === "object" && !Array.isArray(json)) this.#src = {
|
|
113
|
+
url: src,
|
|
114
|
+
config: json
|
|
115
|
+
};
|
|
116
|
+
} catch {}
|
|
117
|
+
if (this.#src?.url === src) base = mergeSceneConfig(base, structuredClone(this.#src.config));
|
|
118
|
+
}
|
|
119
|
+
const transparent = this.#boolAttr("transparent");
|
|
120
|
+
if (transparent !== void 0) base = {
|
|
121
|
+
...base,
|
|
122
|
+
transparentBackground: transparent
|
|
123
|
+
};
|
|
124
|
+
return mergeSceneConfig(mergeSceneConfig(base, parseJson(this.getAttribute("config"))), this.#config);
|
|
125
|
+
}
|
|
126
|
+
#scheduleUpdate() {
|
|
127
|
+
clearTimeout(this.#debounce);
|
|
128
|
+
this.#debounce = setTimeout(() => {
|
|
129
|
+
this.#update();
|
|
130
|
+
}, 50);
|
|
131
|
+
}
|
|
132
|
+
async #update() {
|
|
133
|
+
const handle = this.#handle;
|
|
134
|
+
if (!handle) return;
|
|
135
|
+
const config = await this.#buildConfig();
|
|
136
|
+
if (this.#handle !== handle) return;
|
|
137
|
+
handle.set(config);
|
|
138
|
+
}
|
|
139
|
+
/** Presence = true; `"false"`/`"0"` = false; absent = undefined (shell default). */
|
|
140
|
+
#boolAttr(name) {
|
|
141
|
+
if (!this.hasAttribute(name)) return void 0;
|
|
142
|
+
const value = this.getAttribute(name);
|
|
143
|
+
return value !== "false" && value !== "0";
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
function parseJson(json) {
|
|
147
|
+
if (!json) return {};
|
|
148
|
+
try {
|
|
149
|
+
return JSON.parse(json);
|
|
150
|
+
} catch {
|
|
151
|
+
return {};
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/** Define the element (idempotent, SSR/Node-import-safe). */
|
|
155
|
+
function register(tag = "materials-3d") {
|
|
156
|
+
if (typeof window === "undefined" || typeof customElements === "undefined") return;
|
|
157
|
+
if (!customElements.get(tag)) customElements.define(tag, Materials3DElement);
|
|
158
|
+
}
|
|
159
|
+
register();
|
|
160
|
+
//#endregion
|
|
161
|
+
export { Materials3DElement, register };
|
|
162
|
+
|
|
163
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":["#handle","#config","#scheduleUpdate","#mount","#mountId","#debounce","#boolAttr","#buildConfig","#preset","#src","#update"],"sources":["../src/index.ts"],"sourcesContent":["import { createMaterials, mergeSceneConfig } from \"@materials3d/core\";\nimport type {\n EngineFor,\n RendererKind,\n FallbackReason,\n SceneConfig,\n MaterialHandle,\n MaterialOptions,\n} from \"@materials3d/core\";\n\n/** The attributes a live element answers to. The shell options are read once at mount instead. */\nconst LIVE_ATTRIBUTES = [\"config\", \"src\", \"preset\", \"transparent\", \"paused\"] as const;\n\n// SSR-safe base: `class extends HTMLElement` evaluates HTMLElement at import time, which throws\n// under Node. Fall back to a dummy base there, the element is never instantiated server-side\n// (register() is guarded), so the missing DOM methods are never called.\nconst ElementBase: typeof HTMLElement =\n typeof HTMLElement !== \"undefined\"\n ? HTMLElement\n : (function Materials3DElementBase() {} as unknown as typeof HTMLElement);\n\n/**\n * `<materials-3d>`: the framework-agnostic drop-in (Vue/Svelte/plain HTML). Light DOM, `display:block`.\n *\n * Config attributes: `config` (JSON), `src` (URL to a config JSON), `preset` (name) and\n * `transparent` (drop the backdrop; `\"false\"` or `\"0\"` keeps it), merged in that order, each one\n * level deep (see `mergeSceneConfig`), then the `config` property last. `paused` starts and stops\n * playback. These are LIVE: a change after mount is pushed to the running scene. A `src` is\n * fetched once per URL and a `preset` resolved once per name, so an update re-reads neither.\n *\n * Shell options: `poster`, `poster-fit` (`fill` | `cover` | `contain`), `lazy`, `webgl`\n * (`auto` | `force` | `off`), `min-size` (CSS px) and `renderer` (`webgl`, the default, or\n * `webgpu` for the experimental node engine, a separate bundle). Read once at mount; changing one\n * on a live element does nothing until it is re-connected (the same contract as the React\n * wrapper's mount-time props).\n *\n * Also a `config` property and a read-only `handle` getter. Emits `materials3d-ready` (detail =\n * renderer) and `materials3d-fallback` (detail = reason).\n */\nexport class Materials3DElement extends ElementBase {\n static get observedAttributes(): string[] {\n return [...LIVE_ATTRIBUTES];\n }\n\n #handle: MaterialHandle | null = null;\n #config: Partial<SceneConfig> = {};\n #debounce?: ReturnType<typeof setTimeout>;\n /**\n * Which connect the in-flight `#mount` belongs to. `#mount` awaits the config, and a\n * disconnect + reconnect during that await queues a SECOND mount, without this check both\n * would finish and the first handle would be overwritten undestroyed (a leaked renderer).\n */\n #mountId = 0;\n /** The preset the last build resolved, so an update to another attribute skips the import. */\n #preset: { name: string; make: () => Partial<SceneConfig> } | null = null;\n /** The last `src` fetched successfully; a failed fetch is not cached, so the next update retries. */\n #src: { url: string; config: Partial<SceneConfig> } | null = null;\n\n /** The live shell handle (null before connect / after disconnect). */\n get handle(): MaterialHandle | null {\n return this.#handle;\n }\n\n /** Programmatic config, merged last (over the `preset`/`src`/`config` attributes). */\n get config(): Partial<SceneConfig> {\n return this.#config;\n }\n set config(value: Partial<SceneConfig>) {\n this.#config = value ?? {};\n this.#scheduleUpdate();\n }\n\n connectedCallback(): void {\n if (!this.style.display) this.style.display = \"block\";\n void this.#mount(++this.#mountId);\n }\n\n disconnectedCallback(): void {\n this.#mountId++; // invalidate a mount still awaiting its config\n clearTimeout(this.#debounce);\n this.#handle?.destroy();\n this.#handle = null;\n }\n\n attributeChangedCallback(name: string): void {\n if (!this.#handle) return;\n if (name === \"paused\") {\n if (this.#boolAttr(\"paused\")) this.#handle.pause();\n else this.#handle.play();\n } else {\n this.#scheduleUpdate();\n }\n }\n\n async #mount(id: number): Promise<void> {\n const config = await this.#buildConfig();\n if (id !== this.#mountId || !this.isConnected) return; // superseded while the config resolved\n const minSize = Number(this.getAttribute(\"min-size\"));\n const options: MaterialOptions<RendererKind> = {\n poster: this.getAttribute(\"poster\") ?? undefined,\n posterFit: (this.getAttribute(\"poster-fit\") as MaterialOptions[\"posterFit\"]) ?? undefined,\n lazy: this.#boolAttr(\"lazy\"),\n webgl: (this.getAttribute(\"webgl\") as MaterialOptions[\"webgl\"]) ?? undefined,\n minSizeForWebGL: Number.isFinite(minSize) && minSize > 0 ? minSize : undefined,\n paused: this.#boolAttr(\"paused\"),\n // `renderer=\"webgpu\"` fetches the node-renderer build. Anything else, including absent, is\n // the default WebGL engine, the option is opt-in precisely because it is a second bundle.\n renderer: this.getAttribute(\"renderer\") === \"webgpu\" ? \"webgpu\" : undefined,\n onReady: (renderer: EngineFor<RendererKind>) =>\n this.dispatchEvent(new CustomEvent(\"materials3d-ready\", { detail: renderer })),\n onFallback: (reason: FallbackReason) =>\n this.dispatchEvent(new CustomEvent(\"materials3d-fallback\", { detail: reason })),\n };\n this.#handle = createMaterials<RendererKind>(this, config, options);\n }\n\n /** default ← preset ← src JSON ← config attribute ← config property, one level deep each. */\n async #buildConfig(): Promise<Partial<SceneConfig>> {\n let base: Partial<SceneConfig> = {};\n const presetName = this.getAttribute(\"preset\");\n if (presetName) {\n if (this.#preset?.name !== presetName) {\n const { PRESETS, isPresetName } = await import(\"@materials3d/core/presets\");\n const make = isPresetName(presetName)\n ? PRESETS[presetName]\n : (): Partial<SceneConfig> => ({});\n this.#preset = { name: presetName, make };\n }\n base = this.#preset.make();\n }\n const src = this.getAttribute(\"src\");\n if (src) {\n if (this.#src?.url !== src) {\n try {\n const json: unknown = await fetch(src).then((r) => r.json());\n if (json && typeof json === \"object\" && !Array.isArray(json)) {\n this.#src = { url: src, config: json as Partial<SceneConfig> };\n }\n } catch {\n // ignore a failed/invalid config fetch; fall through to whatever we have\n }\n }\n // Cloned so the cache never shares an object with a config the shell may go on to hold.\n if (this.#src?.url === src) base = mergeSceneConfig(base, structuredClone(this.#src.config));\n }\n const transparent = this.#boolAttr(\"transparent\");\n if (transparent !== undefined) base = { ...base, transparentBackground: transparent };\n return mergeSceneConfig(\n mergeSceneConfig(base, parseJson(this.getAttribute(\"config\"))),\n this.#config,\n );\n }\n\n #scheduleUpdate(): void {\n clearTimeout(this.#debounce);\n this.#debounce = setTimeout(() => {\n void this.#update();\n }, 50);\n }\n\n async #update(): Promise<void> {\n const handle = this.#handle;\n if (!handle) return;\n const config = await this.#buildConfig();\n if (this.#handle !== handle) return; // destroyed or remounted while the config resolved\n handle.set(config);\n }\n\n /** Presence = true; `\"false\"`/`\"0\"` = false; absent = undefined (shell default). */\n #boolAttr(name: string): boolean | undefined {\n if (!this.hasAttribute(name)) return undefined;\n const value = this.getAttribute(name);\n return value !== \"false\" && value !== \"0\";\n }\n}\n\nfunction parseJson(json: string | null): Partial<SceneConfig> {\n if (!json) return {};\n try {\n return JSON.parse(json) as Partial<SceneConfig>;\n } catch {\n return {};\n }\n}\n\n/** Define the element (idempotent, SSR/Node-import-safe). */\nexport function register(tag = \"materials-3d\"): void {\n if (typeof window === \"undefined\" || typeof customElements === \"undefined\") return;\n if (!customElements.get(tag)) customElements.define(tag, Materials3DElement);\n}\n\n// Self-register on import so a bare `import \"@materials3d/element\"` makes <materials-3d> work. Guarded so\n// importing under Node (SSR) is a no-op rather than a ReferenceError.\nregister();\n\ndeclare global {\n interface HTMLElementTagNameMap {\n /** So `document.querySelector(\"materials-3d\")` types as the element. */\n \"materials-3d\": Materials3DElement;\n }\n}\n\nexport type {\n SceneConfig,\n MaterialHandle,\n FallbackReason,\n SnapshotOptions,\n} from \"@materials3d/core\";\n"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@materials3d/element",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Drop-in Materials3D scene for Vue, Svelte, or plain HTML: the <materials-3d> custom element",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"custom-elements",
|
|
7
|
+
"glass",
|
|
8
|
+
"refraction",
|
|
9
|
+
"svelte",
|
|
10
|
+
"three",
|
|
11
|
+
"vue",
|
|
12
|
+
"web-components",
|
|
13
|
+
"webgl"
|
|
14
|
+
],
|
|
15
|
+
"homepage": "https://github.com/Amir-Abushanab/materials3d/tree/main/packages/element#readme",
|
|
16
|
+
"bugs": "https://github.com/Amir-Abushanab/materials3d/issues",
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"author": "Amir Abushanab (https://github.com/Amir-Abushanab)",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/Amir-Abushanab/materials3d.git",
|
|
22
|
+
"directory": "packages/element"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist"
|
|
26
|
+
],
|
|
27
|
+
"type": "module",
|
|
28
|
+
"sideEffects": [
|
|
29
|
+
"./src/index.ts",
|
|
30
|
+
"./dist/index.js"
|
|
31
|
+
],
|
|
32
|
+
"exports": {
|
|
33
|
+
".": {
|
|
34
|
+
"types": "./dist/index.d.ts",
|
|
35
|
+
"default": "./dist/index.js"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@materials3d/core": "^0.1.0"
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"@types/three": ">=0.180",
|
|
46
|
+
"three": ">=0.180 <1"
|
|
47
|
+
},
|
|
48
|
+
"peerDependenciesMeta": {
|
|
49
|
+
"@types/three": {
|
|
50
|
+
"optional": true
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
"engines": {
|
|
54
|
+
"node": ">=20.19"
|
|
55
|
+
},
|
|
56
|
+
"scripts": {
|
|
57
|
+
"build": "tsdown",
|
|
58
|
+
"typecheck": "tsc --noEmit"
|
|
59
|
+
},
|
|
60
|
+
"main": "./dist/index.js",
|
|
61
|
+
"types": "./dist/index.d.ts"
|
|
62
|
+
}
|