@playcanvas/react
Version:
A React renderer for PlayCanvas – build interactive 3D applications using React's declarative paradigm.
122 lines • 4.34 kB
JavaScript
"use client";
import React, { useEffect, useMemo } from 'react';
import { useGltf } from "../hooks/use-gltf.js";
import { ActionType } from "../types.js";
import { defaultPathMatcher } from "../utils/path-matcher.js";
import { ModifyLight } from "./ModifyLight.js";
import { ModifyRender } from "./ModifyRender.js";
import { ModifyCamera } from "./ModifyCamera.js";
let ruleIdCounter = 0;
/**
* ModifyNode component for defining entity modification rules
* Must be a direct child of <Gltf>
* Renders null - only registers rules
*
* @example
* ```tsx
* <Modify.Node path="head.*[light]" clearChildren>
* <Light color="red" />
* <Modify.Component type="render">
* {(props) => <Render {...props} castShadows={false} />}
* </Modify.Component>
* </Modify.Node>
* ```
*/
const ModifyNode = ({ path, clearChildren = false, children }) => {
const { registerRule, unregisterRule } = useGltf();
const ruleId = useMemo(() => `rule-${ruleIdCounter++}`, []);
const rule = useMemo(() => {
const actions = [];
const addChildren = [];
// Process children to separate Modify.* components from additions
React.Children.forEach(children, (child) => {
if (React.isValidElement(child)) {
const type = child.type;
const displayName = type?.displayName || type?.name;
// Check for Modify.Light, Modify.Render, Modify.Camera
if (displayName === 'ModifyLight') {
const componentAction = createComponentAction('light', child.props, ruleId);
if (componentAction)
actions.push(componentAction);
}
else if (displayName === 'ModifyRender') {
const componentAction = createComponentAction('render', child.props, ruleId);
if (componentAction)
actions.push(componentAction);
}
else if (displayName === 'ModifyCamera') {
const componentAction = createComponentAction('camera', child.props, ruleId);
if (componentAction)
actions.push(componentAction);
}
else {
// This is a new child to add
addChildren.push(child);
}
}
else {
// Non-element children (text, etc.)
addChildren.push(child);
}
});
// Add clearChildren action if specified
if (clearChildren) {
actions.push({
type: ActionType.CLEAR_CHILDREN,
ruleId,
specificity: defaultPathMatcher.getSpecificity(path)
});
}
// Add children action if there are any
if (addChildren.length > 0) {
actions.push({
type: ActionType.ADD_CHILDREN,
children: addChildren,
ruleId,
specificity: defaultPathMatcher.getSpecificity(path)
});
}
return {
id: ruleId,
path,
actions,
specificity: defaultPathMatcher.getSpecificity(path)
};
}, [path, clearChildren, children, ruleId]);
useEffect(() => {
registerRule(rule);
return () => {
unregisterRule(ruleId);
};
}, [rule, registerRule, unregisterRule, ruleId]);
return null;
};
ModifyNode.displayName = 'ModifyNode';
/**
* Creates a component modification action from component props
*/
function createComponentAction(componentType, props, ruleId) {
const { remove, ...mergeProps } = props;
const baseAction = {
ruleId,
componentType,
specificity: 0 // Will be set by parent rule
};
// Create modify action with all props (remove will be handled in RuleProcessor)
return {
...baseAction,
type: ActionType.MODIFY_COMPONENT,
props: { remove, ...mergeProps }
};
}
/**
* Modify compound component
* Provides Modify.Node, Modify.Light, Modify.Render, and Modify.Camera
*/
export const Modify = {
Node: ModifyNode,
Light: ModifyLight,
Render: ModifyRender,
Camera: ModifyCamera
};
//# sourceMappingURL=Modify.js.map