lunchboxjs 2.1.10 → 2.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -5
- package/src/auto-components.ts +94 -0
- package/src/html-anchor.ts +88 -0
- package/src/index.ts +138 -0
- package/src/parseAttributeValue.ts +43 -0
- package/src/setThreeProperty.ts +50 -0
- package/src/three-base.ts +283 -0
- package/src/three-lunchbox.ts +295 -0
- package/src/types.d.ts +1 -0
- package/src/utils.ts +195 -0
- package/dist/cypress/e2e/camera.cy.d.ts +0 -1
- package/dist/cypress/e2e/core-events.cy.d.ts +0 -1
- package/dist/cypress/e2e/core.cy.d.ts +0 -1
- package/dist/cypress/e2e/disposal.cy.d.ts +0 -1
- package/dist/cypress/e2e/docs-examples.cy.d.ts +0 -0
- package/dist/cypress/e2e/extend.cy.d.ts +0 -1
- package/dist/cypress/e2e/html-anchor-2.cy.d.ts +0 -0
- package/dist/cypress/e2e/html-anchor.cy.d.ts +0 -0
- package/dist/cypress/e2e/loader.cy.d.ts +0 -1
- package/dist/cypress/e2e/shadow-parents.cy.d.ts +0 -1
- package/dist/cypress/e2e/vue.cy.d.ts +0 -1
- package/dist/cypress/e2e/wrapped-lunchbox-2.cy.d.ts +0 -1
- package/dist/cypress/e2e/wrapped-lunchbox.cy.d.ts +0 -1
- package/dist/cypress/support/commands.d.ts +0 -0
- package/dist/cypress/support/e2e.d.ts +0 -1
- package/dist/cypress.config.d.ts +0 -3
- package/dist/demo.d.ts +0 -1
- package/dist/src/auto-components.d.ts +0 -3
- package/dist/src/html-anchor.d.ts +0 -13
- package/dist/src/index.d.ts +0 -77
- package/dist/src/parseAttributeValue.d.ts +0 -1
- package/dist/src/setThreeProperty.d.ts +0 -1
- package/dist/src/three-lunchbox.d.ts +0 -48
- package/dist/src/utils.d.ts +0 -18
- package/dist/tests/three-lunchbox.test.d.ts +0 -1
- package/dist/types.d.ts +0 -17
- package/dist/vite.config.d.ts +0 -2
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lunchboxjs",
|
|
3
3
|
"files": [
|
|
4
|
-
"dist"
|
|
4
|
+
"dist",
|
|
5
|
+
"src"
|
|
5
6
|
],
|
|
6
7
|
"main": "./dist/lunchboxjs.umd.cjs",
|
|
7
8
|
"module": "./dist/lunchboxjs.js",
|
|
@@ -9,12 +10,12 @@
|
|
|
9
10
|
".": {
|
|
10
11
|
"import": "./dist/lunchboxjs.js",
|
|
11
12
|
"require": "./dist/lunchboxjs.umd.cjs",
|
|
12
|
-
"types": "./
|
|
13
|
+
"types": "./src/types.d.ts"
|
|
13
14
|
}
|
|
14
15
|
},
|
|
15
|
-
"version": "2.1.
|
|
16
|
+
"version": "2.1.11",
|
|
16
17
|
"type": "module",
|
|
17
|
-
"types": "./
|
|
18
|
+
"types": "./src/types.d.ts",
|
|
18
19
|
"scripts": {
|
|
19
20
|
"dev": "vite",
|
|
20
21
|
"build": "tsc && vite build",
|
|
@@ -37,7 +38,6 @@
|
|
|
37
38
|
"three": "^0.164.1",
|
|
38
39
|
"typescript": "^5.2.2",
|
|
39
40
|
"vite": "^5.2.0",
|
|
40
|
-
"vite-plugin-dts": "^3.9.1",
|
|
41
41
|
"vitest": "^3.0.5"
|
|
42
42
|
}
|
|
43
43
|
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
|
|
3
|
+
/** All components that will automatically be registered when Lunchbox is initialized. */
|
|
4
|
+
export const autoComponents: Partial<keyof typeof THREE>[] = [
|
|
5
|
+
// ORDER MATTERS HERE!
|
|
6
|
+
// Place the objects most likely to wrap other objects at the beginning of the list.
|
|
7
|
+
|
|
8
|
+
// Main wrappers
|
|
9
|
+
'WebGLRenderer',
|
|
10
|
+
'Scene',
|
|
11
|
+
'Group',
|
|
12
|
+
|
|
13
|
+
// Secondary wrappers (objects, meshes, etc)
|
|
14
|
+
'Object3D',
|
|
15
|
+
'Mesh',
|
|
16
|
+
'Sprite',
|
|
17
|
+
|
|
18
|
+
// Tertiary items (individual geometries, materials, etc)
|
|
19
|
+
// Geometries
|
|
20
|
+
'BoxGeometry',
|
|
21
|
+
'BufferGeometry',
|
|
22
|
+
'CircleGeometry',
|
|
23
|
+
'ConeGeometry',
|
|
24
|
+
'CylinderGeometry',
|
|
25
|
+
'DodecahedronGeometry',
|
|
26
|
+
'ExtrudeGeometry',
|
|
27
|
+
'IcosahedronGeometry',
|
|
28
|
+
'InstancedBufferGeometry',
|
|
29
|
+
'LatheGeometry',
|
|
30
|
+
'OctahedronGeometry',
|
|
31
|
+
'PlaneGeometry',
|
|
32
|
+
'PolyhedronGeometry',
|
|
33
|
+
'RingGeometry',
|
|
34
|
+
'ShapeGeometry',
|
|
35
|
+
'SphereGeometry',
|
|
36
|
+
'TetrahedronGeometry',
|
|
37
|
+
'TorusGeometry',
|
|
38
|
+
'TorusKnotGeometry',
|
|
39
|
+
'TubeGeometry',
|
|
40
|
+
'WireframeGeometry',
|
|
41
|
+
// Materials
|
|
42
|
+
'PointsMaterial',
|
|
43
|
+
'ShaderMaterial',
|
|
44
|
+
'ShadowMaterial',
|
|
45
|
+
'SpriteMaterial',
|
|
46
|
+
'MeshToonMaterial',
|
|
47
|
+
'MeshBasicMaterial',
|
|
48
|
+
'MeshDepthMaterial',
|
|
49
|
+
'MeshPhongMaterial',
|
|
50
|
+
'LineBasicMaterial',
|
|
51
|
+
'RawShaderMaterial',
|
|
52
|
+
'MeshMatcapMaterial',
|
|
53
|
+
'MeshNormalMaterial',
|
|
54
|
+
'LineDashedMaterial',
|
|
55
|
+
'MeshLambertMaterial',
|
|
56
|
+
'MeshStandardMaterial',
|
|
57
|
+
'MeshDistanceMaterial',
|
|
58
|
+
'MeshPhysicalMaterial',
|
|
59
|
+
// Lights
|
|
60
|
+
'Light',
|
|
61
|
+
'SpotLight',
|
|
62
|
+
'SpotLightHelper',
|
|
63
|
+
'PointLight',
|
|
64
|
+
'PointLightHelper',
|
|
65
|
+
'AmbientLight',
|
|
66
|
+
'RectAreaLight',
|
|
67
|
+
'HemisphereLight',
|
|
68
|
+
'HemisphereLightHelper',
|
|
69
|
+
'DirectionalLight',
|
|
70
|
+
'DirectionalLightHelper',
|
|
71
|
+
// Cameras
|
|
72
|
+
'CubeCamera',
|
|
73
|
+
'ArrayCamera',
|
|
74
|
+
'StereoCamera',
|
|
75
|
+
'PerspectiveCamera',
|
|
76
|
+
'OrthographicCamera',
|
|
77
|
+
// Textures
|
|
78
|
+
'Texture',
|
|
79
|
+
'CubeTexture',
|
|
80
|
+
'DataTexture',
|
|
81
|
+
'DepthTexture',
|
|
82
|
+
'VideoTexture',
|
|
83
|
+
'CanvasTexture',
|
|
84
|
+
'CompressedTexture',
|
|
85
|
+
// Misc
|
|
86
|
+
'CatmullRomCurve3',
|
|
87
|
+
'Points',
|
|
88
|
+
'Raycaster',
|
|
89
|
+
'CameraHelper',
|
|
90
|
+
'Color',
|
|
91
|
+
|
|
92
|
+
// Loaders
|
|
93
|
+
'TextureLoader',
|
|
94
|
+
];
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { html, LitElement } from "lit";
|
|
2
|
+
import { Lunchbox, ThreeLunchbox } from ".";
|
|
3
|
+
import * as THREE from "three";
|
|
4
|
+
import { closestPassShadow } from "./utils";
|
|
5
|
+
|
|
6
|
+
export class HtmlAnchor extends LitElement {
|
|
7
|
+
private parentLunchbox: Lunchbox<THREE.Object3D> | null = null;
|
|
8
|
+
private frame = -1;
|
|
9
|
+
private scratchV3 = new THREE.Vector3();
|
|
10
|
+
|
|
11
|
+
/** Try attaching the update function to pass the parent's position to children. */
|
|
12
|
+
tryAttachUpdate() {
|
|
13
|
+
const instance = this.parentLunchbox?.instance;
|
|
14
|
+
if (!instance) return false;
|
|
15
|
+
if (!instance.isObject3D) throw new Error('html-anchor must be the child of an Object3D');
|
|
16
|
+
|
|
17
|
+
// const lunchboxParent = closestPassShadow(this, 'three-lunchbox') as ThreeLunchbox | null;
|
|
18
|
+
const lunchboxParent = closestPassShadow(this, (el) => {
|
|
19
|
+
return !!(el as ThreeLunchbox)?.three?.renderer;
|
|
20
|
+
}) as Pick<ThreeLunchbox, 'three'> | null;
|
|
21
|
+
|
|
22
|
+
if (!lunchboxParent) {
|
|
23
|
+
console.error('three-lunchbox parent required for html-anchor');
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
const camera = lunchboxParent.three.camera;
|
|
27
|
+
if (!camera) {
|
|
28
|
+
console.error('camera required for html-anchor');
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
const renderer = lunchboxParent.three.renderer;
|
|
32
|
+
if (!renderer?.domElement) {
|
|
33
|
+
console.error('renderer and DOM element required for html-anchor');
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const update = () => {
|
|
38
|
+
this.frame = requestAnimationFrame(update);
|
|
39
|
+
instance.getWorldPosition(this.scratchV3);
|
|
40
|
+
this.scratchV3.project(camera);
|
|
41
|
+
this.scratchV3.multiplyScalar(0.5).addScalar(0.5);
|
|
42
|
+
this.scratchV3.y = 1 - this.scratchV3.y;
|
|
43
|
+
const rendererSize = this.scratchV3.clone().set(renderer.domElement.width, renderer.domElement.height, 1);
|
|
44
|
+
rendererSize.divideScalar(devicePixelRatio);
|
|
45
|
+
this.scratchV3.multiply(rendererSize);
|
|
46
|
+
Array.from(this.children).forEach(child => {
|
|
47
|
+
(child as unknown as HTMLElement).style.setProperty('--left', `${this.scratchV3.x}px`);
|
|
48
|
+
(child as unknown as HTMLElement).style.setProperty('--top', `${this.scratchV3.y}px`);
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
update();
|
|
52
|
+
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Setup - save local parent and try adding update
|
|
57
|
+
connectedCallback() {
|
|
58
|
+
super.connectedCallback();
|
|
59
|
+
|
|
60
|
+
const parent = this.parentNode as Lunchbox<THREE.Object3D>;
|
|
61
|
+
if (!parent) {
|
|
62
|
+
throw new Error('html-anchor requires a 3D parent');
|
|
63
|
+
}
|
|
64
|
+
this.parentLunchbox = parent;
|
|
65
|
+
const attached = this.tryAttachUpdate();
|
|
66
|
+
if (!attached) {
|
|
67
|
+
this.parentLunchbox.addEventListener('instanceadded', () => {
|
|
68
|
+
const attachedAfterInstanceCreated = this.tryAttachUpdate();
|
|
69
|
+
if (!attachedAfterInstanceCreated) throw new Error('error attaching html-anchor to Object3D')
|
|
70
|
+
}, { once: true });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Teardown
|
|
76
|
+
disconnectedCallback(): void {
|
|
77
|
+
if (this.frame !== -1) cancelAnimationFrame(this.frame);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
protected render(): unknown {
|
|
82
|
+
return html`<slot></slot>`
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
protected createRenderRoot() {
|
|
86
|
+
return this;
|
|
87
|
+
}
|
|
88
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { autoComponents } from './auto-components';
|
|
2
|
+
import { buildClass } from './three-base';
|
|
3
|
+
import { ThreeLunchbox } from './three-lunchbox';
|
|
4
|
+
import { IsClass } from './utils';
|
|
5
|
+
import * as THREE from '../node_modules/@types/three';
|
|
6
|
+
import { HtmlAnchor } from './html-anchor';
|
|
7
|
+
|
|
8
|
+
export * from './three-lunchbox';
|
|
9
|
+
export * from './html-anchor';
|
|
10
|
+
export { ThreeBase } from './three-base';
|
|
11
|
+
|
|
12
|
+
/** Every component in a Lunchbox scene is of the Lunchbox type - it contains its ThreeJS instance
|
|
13
|
+
* as a property called `instance`.
|
|
14
|
+
*
|
|
15
|
+
*
|
|
16
|
+
* ## Basic example
|
|
17
|
+
*
|
|
18
|
+
* HTML:
|
|
19
|
+
*
|
|
20
|
+
* ```html
|
|
21
|
+
* <three-mesh></three-mesh>
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* TS:
|
|
25
|
+
*
|
|
26
|
+
* ```ts
|
|
27
|
+
* const mesh = document.querySelector<Lunchbox<THREE.Mesh>>('three-mesh');
|
|
28
|
+
* mesh?.instance; // this is typed as a THREE.Mesh, so you can do things like move it up:
|
|
29
|
+
* mesh?.instance.position.set(0, 1, 0); // - or anything else you can do with a mesh
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export type Lunchbox<T = THREE.Object3D> = Element & LunchboxProperties<T>
|
|
33
|
+
|
|
34
|
+
export type LunchboxProperties<T = THREE.Object3D> = {
|
|
35
|
+
instance: T;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Options for initializing Lunchbox. */
|
|
39
|
+
interface LunchboxOptions {
|
|
40
|
+
/** Add THREE class names that should be registered first here. */
|
|
41
|
+
prependList?: string[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Initialize Lunchbox. Required to register Lunchbox components. */
|
|
45
|
+
export const initLunchbox = ({
|
|
46
|
+
prependList = [],
|
|
47
|
+
}: LunchboxOptions = {}) => {
|
|
48
|
+
const toDefine = {
|
|
49
|
+
'three-lunchbox': ThreeLunchbox,
|
|
50
|
+
'html-anchor': HtmlAnchor,
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
Object.entries(toDefine).forEach(([k, v]) => {
|
|
54
|
+
if (!customElements.get(k)){
|
|
55
|
+
customElements.define(k, v);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// define components
|
|
60
|
+
[...prependList, ...autoComponents].forEach(className => {
|
|
61
|
+
const kebabCase = convertThreeClassToWebComponent(className);
|
|
62
|
+
|
|
63
|
+
// ignore if already defined
|
|
64
|
+
if (customElements.get(kebabCase)) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const result = buildClass(className as keyof typeof THREE);
|
|
69
|
+
if (result) {
|
|
70
|
+
customElements.define(kebabCase, result);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/** Create and register a custom Lunchbox component. For example:
|
|
76
|
+
*
|
|
77
|
+
* ```ts
|
|
78
|
+
* import { CustomGeometry } from 'your-custom-geometry-source';
|
|
79
|
+
* import { extend } from 'lunchboxjs';
|
|
80
|
+
*
|
|
81
|
+
* extend('custom-geometry', CustomGeometry);
|
|
82
|
+
* ```
|
|
83
|
+
*
|
|
84
|
+
* Now in your HTML, you can do:
|
|
85
|
+
*
|
|
86
|
+
* ```html
|
|
87
|
+
* <three-lunchbox>
|
|
88
|
+
* <custom-geometry args="[0, 1, 2]"></custom-geometry>
|
|
89
|
+
* </three-lunchbox>
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
export const extend = (name: string, classDefinition: IsClass, targetWindow = window) => {
|
|
93
|
+
if (targetWindow.customElements.get(name)) {
|
|
94
|
+
console.log(`${name} already registered as a custom element. Try a different name if registering is still required.`);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const result = buildClass(classDefinition);
|
|
99
|
+
if (result) {
|
|
100
|
+
targetWindow.customElements.define(name, result);
|
|
101
|
+
} else {
|
|
102
|
+
throw new Error(`Could not extend ${name}. The second paramater must be a class.`);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// Utilities
|
|
107
|
+
// ==================
|
|
108
|
+
export const THREE_POINTER_MOVE_EVENT_NAME = 'threepointermove';
|
|
109
|
+
export const THREE_MOUSE_MOVE_EVENT_NAME = 'threemousemove';
|
|
110
|
+
export const THREE_CLICK_EVENT_NAME = 'threeclick';
|
|
111
|
+
export const BEFORE_RENDER_EVENT_NAME = 'beforerender';
|
|
112
|
+
export const AFTER_RENDER_EVENT_NAME = 'afterrender';
|
|
113
|
+
export type ThreeIntersectEvent = {
|
|
114
|
+
intersect: THREE.Intersection<THREE.Object3D<THREE.Object3DEventMap>>;
|
|
115
|
+
element: Element | null;
|
|
116
|
+
}
|
|
117
|
+
export interface InstanceEvent<T = unknown> {
|
|
118
|
+
instance: T;
|
|
119
|
+
}
|
|
120
|
+
export type InstanceAddedEvent<T = unknown> = InstanceEvent<T> & {
|
|
121
|
+
parent: THREE.Scene | THREE.Object3D
|
|
122
|
+
}
|
|
123
|
+
export interface LoadedEvent<T = unknown> {
|
|
124
|
+
loaded: T;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Components
|
|
128
|
+
export { autoComponents };
|
|
129
|
+
const convertThreeClassToWebComponent = (className: string) => {
|
|
130
|
+
// convert name to kebab-case; prepend `three-` if needed; `-g-l-` becomes `-gl-`
|
|
131
|
+
let kebabCase = className.split(/\.?(?=[A-Z])/).join('-').toLowerCase().replace(/-g-l-/, '-gl-');
|
|
132
|
+
if (!kebabCase.includes('-')) {
|
|
133
|
+
kebabCase = `three-${kebabCase}`;
|
|
134
|
+
}
|
|
135
|
+
return kebabCase;
|
|
136
|
+
};
|
|
137
|
+
/** The kebab-cased name of the ThreeJS web components automatically registered in Lunchbox. */
|
|
138
|
+
export const webComponentNames = autoComponents.map(convertThreeClassToWebComponent);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { ThreeLunchbox } from "./three-lunchbox";
|
|
2
|
+
import * as THREE from 'three';
|
|
3
|
+
|
|
4
|
+
const valueShortcuts = {
|
|
5
|
+
'$scene': (element: HTMLElement) => {
|
|
6
|
+
// TODO: allow non-wrapper scene
|
|
7
|
+
const el = element.closest('three-lunchbox') as unknown as ThreeLunchbox | null;
|
|
8
|
+
return el?.three.scene;
|
|
9
|
+
},
|
|
10
|
+
'$camera': (element: HTMLElement) => {
|
|
11
|
+
// TODO: allow non-wrapper camera
|
|
12
|
+
const el = element.closest('three-lunchbox') as unknown as ThreeLunchbox | null;
|
|
13
|
+
return el?.three.camera;
|
|
14
|
+
},
|
|
15
|
+
'$renderer': (element: HTMLElement) => {
|
|
16
|
+
// TODO: allow non-wrapper renderer
|
|
17
|
+
const el = element.closest('three-lunchbox') as unknown as ThreeLunchbox | null;
|
|
18
|
+
return el?.three.renderer;
|
|
19
|
+
},
|
|
20
|
+
'$domElement': (element: HTMLElement) => {
|
|
21
|
+
// TODO: allow non-wrapper dom element
|
|
22
|
+
const el = element.closest('three-lunchbox') as unknown as ThreeLunchbox | null;
|
|
23
|
+
return el?.three.renderer?.domElement;
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const parseAttributeOrPropertyValue = (targetValue: unknown, element: HTMLElement) => {
|
|
28
|
+
// leave as-is if this isn't a string
|
|
29
|
+
if (typeof targetValue !== 'string') return targetValue;
|
|
30
|
+
|
|
31
|
+
// return `true` for blank values
|
|
32
|
+
if (targetValue === '') return true;
|
|
33
|
+
|
|
34
|
+
// look for Lunchbox-specific shortcuts
|
|
35
|
+
// TODO: allow extending these
|
|
36
|
+
const result = valueShortcuts[targetValue as keyof typeof valueShortcuts]?.(element);
|
|
37
|
+
|
|
38
|
+
// Color support
|
|
39
|
+
if (CSS.supports('color', targetValue)) return new THREE.Color(targetValue);
|
|
40
|
+
|
|
41
|
+
// default - return target value
|
|
42
|
+
return result ?? targetValue;
|
|
43
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { get, isNumber, set } from "./utils";
|
|
2
|
+
import * as THREE from 'three';
|
|
3
|
+
|
|
4
|
+
export const setThreeProperty = <T extends object>(target: T, split: string[], parsedValue: unknown) => {
|
|
5
|
+
const property: {
|
|
6
|
+
setScalar?: (n: number) => unknown,
|
|
7
|
+
set?: (...args: unknown[]) => unknown,
|
|
8
|
+
} | undefined = get(target, split);
|
|
9
|
+
|
|
10
|
+
if (isNumber(parsedValue) && property?.setScalar) {
|
|
11
|
+
// Set scalar
|
|
12
|
+
property.setScalar(+parsedValue);
|
|
13
|
+
} else if (property?.set) {
|
|
14
|
+
if (typeof parsedValue === 'string') {
|
|
15
|
+
const asNumbers = parsedValue.split(',');
|
|
16
|
+
const isAllNumbers = asNumbers.every(n => !n.match(/^[^\d,]+$/));
|
|
17
|
+
|
|
18
|
+
// handle hash hex numbers
|
|
19
|
+
if (parsedValue.toLowerCase().trim().match(/^#[\dabcdef]{3,6}$/)?.length) {
|
|
20
|
+
if (parsedValue.length === 4) {
|
|
21
|
+
const str = [parsedValue[1], parsedValue[1], parsedValue[2], parsedValue[2], parsedValue[3], parsedValue[3]].join('');
|
|
22
|
+
property.set(+`0x${str}`);
|
|
23
|
+
} else {
|
|
24
|
+
property.set(+`0x${parsedValue.slice(1)}`);
|
|
25
|
+
}
|
|
26
|
+
} else if (asNumbers?.length && isAllNumbers) {
|
|
27
|
+
// assume this is a string like `1,2,3`
|
|
28
|
+
// and try converting to an array of numbers
|
|
29
|
+
// (we get arrays as strings like this from Vue, for example)
|
|
30
|
+
property.set(...asNumbers.map(n => +n));
|
|
31
|
+
} else {
|
|
32
|
+
property.set(parsedValue);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
// Set as values in an array
|
|
37
|
+
const parsedValueAsArray = Array.isArray(parsedValue) ? parsedValue : [parsedValue];
|
|
38
|
+
property.set(...parsedValueAsArray);
|
|
39
|
+
}
|
|
40
|
+
} else {
|
|
41
|
+
// Manually set
|
|
42
|
+
set(target, split, parsedValue);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// handle common update functions
|
|
46
|
+
const targetAsMaterial = target as THREE.Material;
|
|
47
|
+
if (typeof targetAsMaterial.type === 'string' && targetAsMaterial.type?.toLowerCase().endsWith('material')) {
|
|
48
|
+
targetAsMaterial.needsUpdate = true;
|
|
49
|
+
}
|
|
50
|
+
};
|