@ringozz/react-godot 1.0.0-0 → 1.0.0-10

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.
@@ -1,22 +1,148 @@
1
- /**********************************************************************
2
- Copyright (c) Vladimir Davidovich. All rights reserved.
3
- ***********************************************************************/
4
-
5
- import type { Signal } from '@ringozz/godot';
6
- import type React from 'react';
7
- import { useEffect, useRef } from 'react';
8
-
9
- export function useMutableCallback<T>(fn: T): React.RefObject<T> {
10
- const ref = useRef<T>(fn);
11
- useEffect(() => void (ref.current = fn), [fn]);
12
- return ref;
13
- }
14
-
15
- export function useSignal<T extends (...args: any[]) => any>(signal: Signal<T>, handler: T): void {
16
- const ref = useMutableCallback(handler);
17
- useEffect(() => {
18
- const fn = ((...args: any[]) => ref.current(...args)) as T;
19
- signal.connect(fn);
20
- return () => signal.disconnect(fn);
21
- }, [signal.getObjectId(), signal.getName()]);
22
- }
1
+ /**********************************************************************
2
+ Copyright (c) Vladimir Davidovich. All rights reserved.
3
+ ***********************************************************************/
4
+
5
+ import type { Signal } from '@ringozz/godot';
6
+ import type { Node } from '@ringozz/godot/Node';
7
+ import type { Object as Instance } from '@ringozz/godot/Object';
8
+ import { PropertyTweener } from '@ringozz/godot/PropertyTweener';
9
+ import type { GodotVar } from '@ringozz/godot/runtime';
10
+ import { Tween, type EaseType, type TransitionType } from '@ringozz/godot/Tween';
11
+ import type React from 'react';
12
+ import { useEffect, useMemo, useRef } from 'react';
13
+
14
+ // Tween/PropertyTweener are only type-used here; value-reference them so their
15
+ // `_R` class registration isn't tree-shaken (otherwise createTween's wrapper
16
+ // falls back to an ancestor class and loses pause/play/tweenProperty).
17
+ void Tween;
18
+ void PropertyTweener;
19
+
20
+ export function useMutableCallback<T>(fn: T): React.RefObject<T> {
21
+ const ref = useRef<T>(fn);
22
+ useEffect(() => void (ref.current = fn), [fn]);
23
+ return ref;
24
+ }
25
+
26
+ export function useSignal<T extends (...args: any[]) => any>(signal: Signal<T>, handler: T): void {
27
+ const ref = useMutableCallback(handler);
28
+ useEffect(() => {
29
+ const fn = ((...args: any[]) => ref.current(...args)) as T;
30
+ signal.connect(fn);
31
+ return () => signal.disconnect(fn);
32
+ // do not specify 'signal' dependency, because it changes often.
33
+ // specify invariant contents instead.
34
+ /* oxlint-disable-next-line react-hooks/exhaustive-deps */
35
+ }, [signal.getObjectId(), signal.getName()]);
36
+ }
37
+
38
+ // ---- useTween: tween-as-prop-value ----
39
+ //
40
+ // useTween(create, deps) returns [spring, tweenRef]. `spring` holds one
41
+ // value-assigner per `to` key — a plain function spread onto the node
42
+ // (`<Node3D {...spring} />`). Instance.assign (C++) treats any function on a
43
+ // non-signal prop as a value-assigner and invokes it with (node, nativeName);
44
+ // each contributes its own prop's tweener to one shared native Tween created on
45
+ // the first invocation. `tweenRef.current` is that Tween (null until mounted).
46
+ // Re-rendering with the same deps keeps the same assigners (the reconciler's
47
+ // identity diff drops them, so nothing restarts); changing deps creates a new
48
+ // tween (the previous one is killed).
49
+
50
+ export type TweenValue = number | number[] | GodotVar;
51
+
52
+ export interface TweenConfig {
53
+ /** Seconds (default 1). */
54
+ duration?: number;
55
+ /** Tween.setTrans — the default for the tween's tweeners. */
56
+ transition?: TransitionType;
57
+ /** Tween.setEase — the default for the tween's tweeners. */
58
+ ease?: EaseType;
59
+ /** PropertyTweener.setCustomInterpolator — a [0,1]→[0,1] easing function. */
60
+ easing?: (t: number) => number;
61
+ /** Tween.setSpeedScale. */
62
+ speedScale?: number;
63
+ }
64
+
65
+ export interface TweenProps<T extends Record<string, TweenValue> = Record<string, TweenValue>> {
66
+ /** Start values, applied on the first build (mount) before animating to `to`. */
67
+ from?: Partial<T>;
68
+ /** Target values — one assigner is returned per key. */
69
+ to: T;
70
+ config?: TweenConfig;
71
+ /** Seconds; per-tweener PropertyTweener.setDelay. */
72
+ delay?: number;
73
+ /** Tween.setLoops (0 = infinite). */
74
+ loops?: number;
75
+ /** Snap to `to` instead of animating. */
76
+ immediate?: boolean;
77
+ onStart?: () => void;
78
+ /** Fires on natural completion only (Godot's `finished`); kills/unmount don't fire it. */
79
+ onFinished?: () => void;
80
+ }
81
+
82
+ export type Spring<T extends Record<string, TweenValue>> = { [K in keyof T]: T[K] };
83
+
84
+ // tweenProperty/set reject JS arrays, so array goals are reconstructed as the
85
+ // property's value type (read via get(native).constructor).
86
+ function toValueType(node: Instance, native: string, value: TweenValue): unknown {
87
+ if (!Array.isArray(value)) return value;
88
+ const Ctor = (node.get(native) as any)?.constructor;
89
+ if (typeof Ctor === 'function') return new Ctor(...value);
90
+ return value;
91
+ }
92
+
93
+ function makeSpring<T extends Record<string, TweenValue>>(
94
+ tweenRef: { current: Tween | null },
95
+ props: TweenProps<T>,
96
+ ): Spring<T> {
97
+ const config = props.config ?? {};
98
+ const spring = {} as Spring<T>;
99
+ let started = false;
100
+ let applyFrom = false;
101
+
102
+ for (const [key, value] of Object.entries(props.to)) {
103
+ const fromValue = props.from?.[key];
104
+ const assigner = (node: Instance, native: string) => {
105
+ if (props.immediate) {
106
+ tweenRef.current?.kill();
107
+ tweenRef.current = null;
108
+ node.set(native, toValueType(node, native, value));
109
+ return;
110
+ }
111
+ if (!started) {
112
+ started = true;
113
+ applyFrom = tweenRef.current === null; // first build (mount) ⇒ apply `from`
114
+ tweenRef.current?.kill();
115
+ const t = (node as unknown as Node).createTween();
116
+ tweenRef.current = t;
117
+ if (config.transition !== undefined) t.setTrans(config.transition);
118
+ if (config.ease !== undefined) t.setEase(config.ease);
119
+ if (config.speedScale !== undefined) t.setSpeedScale(config.speedScale);
120
+ if (props.loops !== undefined) t.setLoops(props.loops);
121
+ if (props.onFinished) t.finished.connect(props.onFinished);
122
+ t.bindNode(node as unknown as Node);
123
+ t.setParallel(true);
124
+ props.onStart?.();
125
+ }
126
+ const tw = tweenRef.current!.tweenProperty(node, native, toValueType(node, native, value), config.duration ?? 1);
127
+ if (applyFrom && fromValue !== undefined) tw.from(toValueType(node, native, fromValue));
128
+ if (props.delay !== undefined) tw.setDelay(props.delay);
129
+ if (config.easing) tw.setCustomInterpolator(config.easing);
130
+ };
131
+ spring[key as keyof T] = assigner as unknown as T[keyof T];
132
+ }
133
+ return spring;
134
+ }
135
+
136
+ export function useTween<T extends Record<string, TweenValue>>(
137
+ create: () => TweenProps<T>,
138
+ deps: unknown[] = [],
139
+ ): [Spring<T>, React.RefObject<Tween | null>] {
140
+ const tweenRef = useRef<Tween | null>(null);
141
+ // 'create' is intentionally re-evaluated via `deps`, not listed here.
142
+ /* oxlint-disable-next-line react-hooks/exhaustive-deps */
143
+ const spring = useMemo(() => makeSpring(tweenRef, create()), deps);
144
+ // No unmount kill: the tween is `createTween()`-bound to the node, so Godot
145
+ // kills it when the node exits the tree. A `useEffect` cleanup would also run
146
+ // on StrictMode's synthetic mount teardown and kill the just-created tween.
147
+ return [spring, tweenRef];
148
+ }
package/src/react-jsx.ts CHANGED
@@ -1,41 +1,45 @@
1
- /**********************************************************************
2
- Copyright (c) Vladimir Davidovich. All rights reserved.
3
- ***********************************************************************/
4
-
5
- import { jsxDEV as reactJsxDEV } from 'react/jsx-dev-runtime';
6
- import type * as ReactJSX from 'react/jsx-runtime';
7
- import { Fragment, jsx as reactJsx, jsxs as reactJsxs } from 'react/jsx-runtime';
8
- import type { ComponentProps, Instance } from './react-types.ts';
9
- import { GodotVar } from '@ringozz/godot/runtime';
10
-
11
- export function jsx(type: any, props?: any, key?: any) {
12
- return reactJsx(GodotVar.isPrototypeOf(type) ? type.name : type, props, key);
13
- }
14
-
15
- export function jsxs(type: any, props?: any, key?: any) {
16
- return reactJsxs(GodotVar.isPrototypeOf(type) ? type.name : type, props, key);
17
- }
18
-
19
- export function jsxDEV(type: any, props?: any, key?: any, isStatic?: any, source?: any, self?: any) {
20
- return reactJsxDEV(GodotVar.isPrototypeOf(type) ? type.name : type, props, key, isStatic, source, self);
21
- }
22
-
23
- export { Fragment };
24
-
25
- declare module '@ringozz/react-godot/jsx-runtime' {
26
- export namespace JSX {
27
- interface IntrinsicElements extends ReactJSX.JSX.IntrinsicElements { }
28
- interface Element extends ReactJSX.JSX.Element { }
29
-
30
- type GodotConstructor = { new(...args: any[]): Instance } & Function;
31
-
32
- type ElementType =
33
- | ReactJSX.JSX.ElementType
34
- | GodotConstructor;
35
-
36
- type LibraryManagedAttributes<C, P> =
37
- C extends GodotConstructor
38
- ? ComponentProps<C>
39
- : ReactJSX.JSX.LibraryManagedAttributes<C, P>;
40
- }
41
- }
1
+ /**********************************************************************
2
+ Copyright (c) Vladimir Davidovich. All rights reserved.
3
+ ***********************************************************************/
4
+
5
+ import { jsxDEV as reactJsxDEV } from 'react/jsx-dev-runtime';
6
+ import type * as ReactJSX from 'react/jsx-runtime';
7
+ import { Fragment, jsx as reactJsx, jsxs as reactJsxs } from 'react/jsx-runtime';
8
+ import type { ComponentProps, GodotConstructor } from './react-types.ts';
9
+ import { GodotVar } from '@ringozz/godot/runtime';
10
+
11
+ export function jsx(type: any, props?: any, key?: any) {
12
+ return reactJsx(GodotVar.isPrototypeOf(type) ? type.name : type, props, key);
13
+ }
14
+
15
+ export function jsxs(type: any, props?: any, key?: any) {
16
+ return reactJsxs(GodotVar.isPrototypeOf(type) ? type.name : type, props, key);
17
+ }
18
+
19
+ export function jsxDEV(type: any, props?: any, key?: any, isStatic?: any, source?: any, self?: any) {
20
+ return reactJsxDEV(GodotVar.isPrototypeOf(type) ? type.name : type, props, key, isStatic, source, self);
21
+ }
22
+
23
+ export { Fragment };
24
+
25
+ // JSX namespace augmentation: these types are the public JSX contract for the
26
+ // `@ringozz/react-godot/jsx-runtime` import source, consumed by TypeScript in
27
+ // consumer files. They are intentionally not referenced within this module.
28
+ /* oxlint-disable no-unused-vars */
29
+ declare module '@ringozz/react-godot/jsx-runtime' {
30
+ export namespace JSX {
31
+ interface IntrinsicAttributes extends ReactJSX.JSX.IntrinsicAttributes { }
32
+ interface IntrinsicElements extends ReactJSX.JSX.IntrinsicElements { }
33
+ interface Element extends ReactJSX.JSX.Element { }
34
+
35
+ type ElementType =
36
+ | ReactJSX.JSX.ElementType
37
+ | GodotConstructor;
38
+
39
+ type LibraryManagedAttributes<C, P> =
40
+ C extends GodotConstructor
41
+ ? ComponentProps<C>
42
+ : ReactJSX.JSX.LibraryManagedAttributes<C, P>;
43
+ }
44
+ }
45
+ /* oxlint-enable no-unused-vars */
@@ -4,33 +4,49 @@
4
4
 
5
5
  import type React from 'react';
6
6
  import type { Object } from '@ringozz/godot/Object';
7
- import type { ValueTypes } from '@ringozz/godot';
7
+ import type { Signal, ValueTypes } from '@ringozz/godot';
8
+ import type { PackedScene } from '@ringozz/godot/PackedScene';
9
+
10
+ // Identity check sensitive to readonly (deferred-conditional trick; the
11
+ // comparison lives in return-type position, so it is independent of
12
+ // strictFunctionTypes).
13
+ type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends
14
+ (<T>() => T extends Y ? 1 : 2) ? true : false;
8
15
 
9
- type FunctionKeys<T> = { [K in keyof T]: T[K] extends Function ? K : never }[keyof T];
10
- type Properties<T> = Omit<T, FunctionKeys<T>>;
11
- type Overwrite<P, O> = Properties<P> & O;
12
- type Mutable<P> = { [K in keyof P]: P[K] | Readonly<P[K]> };
13
16
  type ConstructorRepresentation<T = any> = new (...args: any[]) => T;
14
17
 
15
18
  export type Instance = Object;
16
19
 
20
+ export type GodotConstructor<T extends Instance = Instance> = ConstructorRepresentation<T> & Function;
21
+
17
22
  export type InstanceProps<T extends Instance = Instance> = {
18
23
  /** An existing instance to render instead of creating a new one. */
19
- object?: T;
24
+ object?: T | PackedScene;
20
25
  /** Attaches the element to a named property of the parent instead of adding it as a child. */
21
26
  attach?: string;
22
27
  };
23
28
 
24
29
  type ReactProps<P> = React.PropsWithChildren<React.RefAttributes<P>>;
25
30
 
26
- type WidenVT<T> = {
27
- [K in keyof T]: T[K] extends ValueTypes ? T[K] | number[] : T[K];
31
+ // Settable, non-method keys. `Pick<T, K>` preserves the readonly modifier, so
32
+ // getter-only (read-only) props, e.g. Node.multiplayer, compare unequal to
33
+ // their `-readonly` copy and drop out; write-only accessors pass through with
34
+ // their setter parameter type.
35
+ type SettableKey<T, K extends keyof T> =
36
+ Equal<Pick<T, K>, { -readonly [P in K]: T[K] }> extends true
37
+ ? (T[K] extends Function ? never : K)
38
+ : never;
39
+
40
+ // JSX props are settable instance members, typed as the setter accepts them:
41
+ // ValueTypes also accept JSX arrays, and signals (getter `Signal<CB>`, setter
42
+ // `CB | null`) take the callback directly. Maps over `keyof T` (homomorphic)
43
+ // so optional/readonly modifiers survive.
44
+ type Properties<T> = {
45
+ [K in keyof T as SettableKey<T, K>]:
46
+ T[K] extends ValueTypes ? T[K] | number[]
47
+ : T[K] extends Signal<infer CB> ? CB | null
48
+ : T[K];
28
49
  };
29
50
 
30
- type ElementProps<T extends ConstructorRepresentation, P = InstanceType<T>> = Partial<
31
- Overwrite<WidenVT<P>, ReactProps<P>>
32
- >;
33
-
34
- export type ComponentProps<T extends ConstructorRepresentation> = Mutable<
35
- Overwrite<ElementProps<T>, Omit<InstanceProps<InstanceType<T>>, 'object'>>
36
- >;
51
+ export type ComponentProps<T extends GodotConstructor, P extends Instance = InstanceType<T>> =
52
+ Partial<Properties<P> & ReactProps<P>> & InstanceProps<P>;