Add signal support
Some checks failed
Lint / lint (push) Failing after 9s

This commit is contained in:
Julien Valverdé
2026-01-04 18:13:09 +01:00
parent 7f1be2cd1d
commit eae6647b7e
3 changed files with 58 additions and 21 deletions

View File

@@ -14,15 +14,12 @@ const TextEdit = Component.fromClass(Godot.TextEdit)
* A form UI where React controls the state * A form UI where React controls the state
*/ */
export function ControlledFormRoot() { export function ControlledFormRoot() {
const rootRef = CenterContainer.useRef()
Component.useSignal(rootRef, "ready", () => {
console.log("ready")
})
return ( return (
<CenterContainer <CenterContainer
ref={rootRef}
anchors_preset={Godot.Control.LayoutPreset.PRESET_FULL_RECT} anchors_preset={Godot.Control.LayoutPreset.PRESET_FULL_RECT}
ready={function(this) {
console.log("ready")
}}
> >
<PanelContainer> <PanelContainer>
<MarginContainer> <MarginContainer>

View File

@@ -10,6 +10,10 @@ export type Props<T extends Godot.Node<Godot.NodePathMap>> = {
} & { } & {
// biome-ignore lint/complexity/noBannedTypes: using Function here is completely fine // biome-ignore lint/complexity/noBannedTypes: using Function here is completely fine
[K in keyof T as T[K] extends Function | Godot.Signal ? never : K]?: T[K] [K in keyof T as T[K] extends Function | Godot.Signal ? never : K]?: T[K]
} & {
[K in keyof T as T[K] extends Godot.Signal ? K : never]?: T[K] extends Godot.Signal<infer F>
? (this: T, ...args: Parameters<F>) => ReturnType<F>
: never
} }
export interface Prototype<T extends Godot.Node<Godot.NodePathMap>> { export interface Prototype<T extends Godot.Node<Godot.NodePathMap>> {

View File

@@ -1,4 +1,5 @@
import { Control, Node, type NodePathMap, PackedScene, ResourceLoader } from "godot" /** biome-ignore-all lint/complexity/noBannedTypes: "Function" is used as a type in GodotJS, keeping it for consistency */
import { Callable, Control, Node, type NodePathMap, PackedScene, ResourceLoader, Signal } from "godot"
import ReactReconciler, { type HostConfig } from "react-reconciler" import ReactReconciler, { type HostConfig } from "react-reconciler"
import { hasProperty } from "./utils.js" import { hasProperty } from "./utils.js"
@@ -225,20 +226,55 @@ export const make = () => {
const applyNextProps = (instance: Node<NodePathMap>, nextProps: Record<string, unknown>) => { const applyNextProps = (instance: Node<NodePathMap>, nextProps: Record<string, unknown>) => {
Object.keys(nextProps).forEach(name => { Object.keys(nextProps).forEach(name => {
(instance as any)[name] = nextProps[name] if (!hasProperty(instance, name)) return
})
if (hasProperty(nextProps, "name")) { if (name === "name") {
if (typeof nextProps.name !== "string") if (typeof nextProps[name] !== "string")
throw new Error("Prop 'name' should be a string") throw new Error("Prop 'name' should be a string")
instance.set_name(nextProps.name) instance.set_name(nextProps[name])
return
}
if (instance[name] instanceof Signal) {
if ((typeof nextProps[name] !== "function") || nextProps[name] === undefined)
throw new Error(`Prop '${ name }' should be a function or undefined`)
instance[`${ name }_event`] = nextProps[name]
if (!isNodeSignalRegistered(instance, name)) {
const callable = Callable.create(instance, function(this, ...args) {
if (this[`${ name }_event`])
(this[`${ name }_event`] as Function)(...args)
})
instance[`${ name }_callable`] = callable
instance.connect(name, callable)
}
return
} }
if (instance instanceof Control) { if (instance instanceof Control) {
if (hasProperty(nextProps, "anchors_preset")) { if (name === "anchors_preset") {
if (typeof nextProps.anchors_preset !== "number") if (typeof nextProps[name] !== "number")
throw new Error("Prop 'anchors_preset' should be a number") throw new Error("Prop 'anchors_preset' should be a number")
instance.set_anchors_preset(nextProps.anchors_preset) instance.set_anchors_preset(nextProps[name])
return
} }
} }
instance[name] = nextProps[name]
})
} }
type NodeWithSignalMetadata<A extends string> = Node<NodePathMap> & {
[K in `${ A }_callable`]: Callable
} & {
[K in `${ A }_event`]?: Function
}
const isNodeSignalRegistered = <A extends string>(
u: Node<NodePathMap>,
name: A,
): u is NodeWithSignalMetadata<A> => (
hasProperty(u, `${ name }_callable`) &&
u[`${ name }_callable`] instanceof Callable
)