import { autoComponents } from './auto-components';
import { buildClass } from './three-base';
import { ThreeLunchbox } from './three-lunchbox';
import { IsClass } from './utils';
import { HtmlAnchor } from './html-anchor';
import type { Intersection, Object3DEventMap, Scene, Object3D } from 'three';
export * from './three-lunchbox';
export * from './html-anchor';
export { ThreeBase } from './three-base';
export * from './utils';
/** Every component in a Lunchbox scene is of the Lunchbox type - it contains its ThreeJS instance
* as a property called `instance`.
*
*
* ## Basic example
*
* HTML:
*
* ```html
*
* ```
*
* TS:
*
* ```ts
* const mesh = document.querySelector>('three-mesh');
* mesh?.instance; // this is typed as a THREE.Mesh, so you can do things like move it up:
* mesh?.instance.position.set(0, 1, 0); // - or anything else you can do with a mesh
* ```
*/
export type Lunchbox = Element & LunchboxProperties
export type LunchboxProperties = {
instance: T;
}
/** Options for initializing Lunchbox. */
interface LunchboxOptions {
/** Add THREE class names that should be registered first here. */
prependList?: string[];
}
/** Initialize Lunchbox. Required to register Lunchbox components. */
export const initLunchbox = ({
prependList = [],
}: LunchboxOptions = {}) => {
const toDefine = {
'three-lunchbox': ThreeLunchbox,
'html-anchor': HtmlAnchor,
}
Object.entries(toDefine).forEach(([k, v]) => {
if (!customElements.get(k)){
customElements.define(k, v);
}
});
// define components
[...prependList, ...autoComponents].forEach(className => {
const kebabCase = convertThreeClassToWebComponent(className);
// ignore if already defined
if (customElements.get(kebabCase)) {
return;
}
const result = buildClass(className as any);
if (result) {
customElements.define(kebabCase, result);
}
});
};
/** Create and register a custom Lunchbox component. For example:
*
* ```ts
* import { CustomGeometry } from 'your-custom-geometry-source';
* import { extend } from 'lunchboxjs';
*
* extend('custom-geometry', CustomGeometry);
* ```
*
* Now in your HTML, you can do:
*
* ```html
*
*
*
* ```
*/
export const extend = (name: string, classDefinition: IsClass, targetWindow = window) => {
if (targetWindow.customElements.get(name)) {
console.log(`${name} already registered as a custom element. Try a different name if registering is still required.`);
return;
}
const result = buildClass(classDefinition);
if (result) {
targetWindow.customElements.define(name, result);
} else {
throw new Error(`Could not extend ${name}. The second paramater must be a class.`);
}
};
// Utilities
// ==================
export const THREE_POINTER_MOVE_EVENT_NAME = 'threepointermove';
export const THREE_MOUSE_MOVE_EVENT_NAME = 'threemousemove';
export const THREE_CLICK_EVENT_NAME = 'threeclick';
export const BEFORE_RENDER_EVENT_NAME = 'beforerender';
export const AFTER_RENDER_EVENT_NAME = 'afterrender';
export type ThreeIntersectEvent = {
intersect: Intersection>;
element: Element | null;
}
export interface InstanceEvent {
instance: T;
}
export type InstanceAddedEvent = InstanceEvent & {
parent: Scene | Object3D
}
export interface LoadedEvent {
loaded: T;
}
// Components
export { autoComponents };
const convertThreeClassToWebComponent = (className: string) => {
// convert name to kebab-case; prepend `three-` if needed; `-g-l-` becomes `-gl-`
let kebabCase = className.split(/\.?(?=[A-Z])/).join('-').toLowerCase().replace(/-g-l-/, '-gl-');
if (!kebabCase.includes('-')) {
kebabCase = `three-${kebabCase}`;
}
return kebabCase;
};
/** The kebab-cased name of the ThreeJS web components automatically registered in Lunchbox. */
export const webComponentNames = autoComponents.map(convertThreeClassToWebComponent);