@ringozz/react-godot 0.1.0
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 +28 -0
- package/src/index.ts +58 -0
- package/src/react-fiber.ts +307 -0
- package/src/react-hooks.ts +22 -0
- package/src/react-jsx.ts +41 -0
- package/src/react-types.ts +36 -0
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ringozz/react-godot",
|
|
3
|
+
"author": "Vladimir Davidovich",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "React reconciler bindings for Godot Engine via @ringozz/godot",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./src/index.ts",
|
|
8
|
+
"types": "./src/index.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": "./src/index.ts",
|
|
11
|
+
"./jsx-runtime": "./src/react-jsx.ts",
|
|
12
|
+
"./jsx-dev-runtime": "./src/react-jsx.ts"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"src/"
|
|
16
|
+
],
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"react-reconciler": "^0.33.0"
|
|
19
|
+
},
|
|
20
|
+
"peerDependencies": {
|
|
21
|
+
"@ringozz/godot": "^0.1.0",
|
|
22
|
+
"react": "^19.2.0"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/react": "^19.2.0",
|
|
26
|
+
"@types/react-reconciler": "^0.33.0"
|
|
27
|
+
}
|
|
28
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**********************************************************************
|
|
2
|
+
Copyright (c) Vladimir Davidovich. All rights reserved.
|
|
3
|
+
***********************************************************************/
|
|
4
|
+
|
|
5
|
+
import Reconciler from 'react-reconciler';
|
|
6
|
+
import Constants from 'react-reconciler/constants.js';
|
|
7
|
+
import type { Node } from '@ringozz/godot/Node';
|
|
8
|
+
import * as FiberConfig from './react-fiber.ts';
|
|
9
|
+
export * from './react-hooks.ts';
|
|
10
|
+
export type * from './react-types.ts';
|
|
11
|
+
|
|
12
|
+
const reconciler = Reconciler(FiberConfig);
|
|
13
|
+
reconciler.injectIntoDevTools(undefined as never);
|
|
14
|
+
|
|
15
|
+
export function createRoot(parent: Node) {
|
|
16
|
+
const isStrictMode = process.env.NODE_ENV === 'development';
|
|
17
|
+
const concurrentUpdatesByDefaultOverride = false;
|
|
18
|
+
const identifierPrefix = '';
|
|
19
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
20
|
+
const onUncaughtError = (reconciler as any).defaultOnUncaughtError;
|
|
21
|
+
const onCaughtError = (reconciler as any).defaultOnCaughtError;
|
|
22
|
+
const onRecoverableError = (reconciler as any).defaultOnRecoverableError;
|
|
23
|
+
const onDefaultTransitionIndicator = () => { };
|
|
24
|
+
const root = reconciler.createContainer(
|
|
25
|
+
parent,
|
|
26
|
+
Constants.ConcurrentRoot,
|
|
27
|
+
null,
|
|
28
|
+
isStrictMode,
|
|
29
|
+
concurrentUpdatesByDefaultOverride,
|
|
30
|
+
identifierPrefix,
|
|
31
|
+
onUncaughtError,
|
|
32
|
+
onCaughtError,
|
|
33
|
+
onRecoverableError,
|
|
34
|
+
onDefaultTransitionIndicator
|
|
35
|
+
);
|
|
36
|
+
return {
|
|
37
|
+
render: (component: React.ReactNode) => new Promise<void>((resolve, reject) => {
|
|
38
|
+
try {
|
|
39
|
+
reconciler.updateContainer(component, root, null, resolve);
|
|
40
|
+
} catch (e) {
|
|
41
|
+
reject(e);
|
|
42
|
+
}
|
|
43
|
+
}),
|
|
44
|
+
unmount: () => {
|
|
45
|
+
try {
|
|
46
|
+
reconciler.updateContainerSync(null, root, null);
|
|
47
|
+
reconciler.flushSyncWork();
|
|
48
|
+
return Promise.resolve();
|
|
49
|
+
} catch (e) {
|
|
50
|
+
return Promise.reject(e);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function createPortal(children: React.ReactNode, container: Node, key?: string | null): React.ReactPortal {
|
|
57
|
+
return reconciler.createPortal(children, container, null, key) as unknown as React.ReactPortal;
|
|
58
|
+
}
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
/**********************************************************************
|
|
2
|
+
Copyright (c) Vladimir Davidovich. All rights reserved.
|
|
3
|
+
***********************************************************************/
|
|
4
|
+
|
|
5
|
+
import { ClassDB } from '@ringozz/godot/ClassDB';
|
|
6
|
+
import type { Node } from '@ringozz/godot/Node';
|
|
7
|
+
import { Object as Instance } from '@ringozz/godot/Object';
|
|
8
|
+
import { RefCounted } from '@ringozz/godot/RefCounted';
|
|
9
|
+
import type Reconciler from 'react-reconciler';
|
|
10
|
+
import Constants from 'react-reconciler/constants.js';
|
|
11
|
+
import pkg from '../package.json' with { type: 'json' };
|
|
12
|
+
import type { InstanceProps } from './react-types.ts';
|
|
13
|
+
|
|
14
|
+
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
15
|
+
|
|
16
|
+
export const rendererPackageName = pkg.name;
|
|
17
|
+
export const rendererVersion = pkg.version;
|
|
18
|
+
export const supportsMutation = true;
|
|
19
|
+
export const supportsPersistence = false;
|
|
20
|
+
export const supportsHydration = false;
|
|
21
|
+
export const isPrimaryRenderer = true;
|
|
22
|
+
|
|
23
|
+
type Type = string;
|
|
24
|
+
type Props = InstanceProps & Record<string, unknown>;
|
|
25
|
+
type Container = Instance;
|
|
26
|
+
type TextInstance = Instance;
|
|
27
|
+
type SuspenseInstance = Instance;
|
|
28
|
+
type PublicInstance = Instance;
|
|
29
|
+
type HostContext = unknown;
|
|
30
|
+
|
|
31
|
+
const EMPTY = Object.freeze({});
|
|
32
|
+
const attachments = new WeakMap<Instance, string>();
|
|
33
|
+
|
|
34
|
+
export function createInstance<T extends Instance>(type: Type, props: Props, root: Container, hostContext: HostContext, internalHandle: Reconciler.OpaqueHandle): T {
|
|
35
|
+
const { attach, object, ...rest } = props;
|
|
36
|
+
if (object instanceof RefCounted)
|
|
37
|
+
object.reference();
|
|
38
|
+
|
|
39
|
+
const instance = (object ?? ClassDB.instantiate(type)) as T;
|
|
40
|
+
if (attach)
|
|
41
|
+
attachments.set(instance, attach);
|
|
42
|
+
|
|
43
|
+
commitUpdate(instance, type, EMPTY, rest, internalHandle);
|
|
44
|
+
return instance;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function createTextInstance(text: string, root: Container, hostContext: HostContext, internalHandle: Reconciler.OpaqueHandle): TextInstance {
|
|
48
|
+
throw new Error('Text nodes are not supported: use a text prop, or a single string/all-string children (they are flattened into the node\'s `text` property).');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const appendInitialChild = appendChild;
|
|
52
|
+
|
|
53
|
+
export function finalizeInitialChildren(instance: Instance, type: Type, props: Props, root: Container, hostContext: HostContext): boolean {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function shouldSetTextContent(type: Type, props: Props): boolean {
|
|
58
|
+
const c = props.children;
|
|
59
|
+
if (typeof c === 'string' || typeof c === 'number')
|
|
60
|
+
return true;
|
|
61
|
+
return Array.isArray(c) && c.every((v) => typeof v === 'string' || typeof v === 'number');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function getRootHostContext(root: Container): HostContext | null {
|
|
65
|
+
return EMPTY;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function getChildHostContext(parentHostContext: HostContext, type: Type, root: Container): HostContext {
|
|
69
|
+
return parentHostContext;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function getPublicInstance(instance: Instance | TextInstance): PublicInstance {
|
|
73
|
+
return instance;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function prepareForCommit(containerInfo: Container): Props | null {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function resetAfterCommit(containerInfo: Container): void {
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function preparePortalMount(containerInfo: Container): void {
|
|
84
|
+
// No-op: portal children attach to their target Node via appendChildToContainer.
|
|
85
|
+
// Mirrors ReactDOM's empty implementation.
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export const noTimeout = -1;
|
|
89
|
+
export const scheduleTimeout = setTimeout;
|
|
90
|
+
export const cancelTimeout = clearTimeout;
|
|
91
|
+
export const supportsMicrotasks = true;
|
|
92
|
+
export const scheduleMicrotask = queueMicrotask;
|
|
93
|
+
|
|
94
|
+
let currentUpdatePriority = Constants.NoEventPriority;
|
|
95
|
+
export function setCurrentUpdatePriority(newPriority: Reconciler.EventPriority) {
|
|
96
|
+
currentUpdatePriority = newPriority;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function getCurrentUpdatePriority(): Reconciler.EventPriority {
|
|
100
|
+
return currentUpdatePriority;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const currentEvent = () => globalThis.window?.event;
|
|
104
|
+
|
|
105
|
+
export function resolveUpdatePriority(): Reconciler.EventPriority {
|
|
106
|
+
if (currentUpdatePriority)
|
|
107
|
+
return currentUpdatePriority;
|
|
108
|
+
const event = currentEvent();
|
|
109
|
+
switch (event?.type) {
|
|
110
|
+
case 'click':
|
|
111
|
+
case 'contextmenu':
|
|
112
|
+
case 'keydown':
|
|
113
|
+
case 'keypress':
|
|
114
|
+
case 'keyup':
|
|
115
|
+
case 'pointercancel':
|
|
116
|
+
case 'pointerdown':
|
|
117
|
+
case 'pointerup':
|
|
118
|
+
return Constants.DiscreteEventPriority;
|
|
119
|
+
case 'pointerenter':
|
|
120
|
+
case 'pointerleave':
|
|
121
|
+
case 'pointermove':
|
|
122
|
+
case 'pointerout':
|
|
123
|
+
case 'pointerover':
|
|
124
|
+
case 'resize':
|
|
125
|
+
case 'wheel':
|
|
126
|
+
return Constants.ContinuousEventPriority;
|
|
127
|
+
default:
|
|
128
|
+
return Constants.DefaultEventPriority;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
let schedulerEvent: Event | undefined = undefined;
|
|
133
|
+
export function trackSchedulerEvent() {
|
|
134
|
+
schedulerEvent = currentEvent();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function resolveEventType(): null | string {
|
|
138
|
+
const event = currentEvent();
|
|
139
|
+
return event && event !== schedulerEvent ? event.type : null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function resolveEventTimeStamp(): number {
|
|
143
|
+
const event = currentEvent();
|
|
144
|
+
return event && event !== schedulerEvent ? event.timeStamp : -1.1;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function requestPostPaintCallback() {
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function resetFormInstance() {
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export const NotPendingTransition = null;
|
|
154
|
+
export const HostTransitionContext = null as never;
|
|
155
|
+
|
|
156
|
+
export function shouldAttemptEagerTransition() {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function getInstanceFromNode(node: unknown): Reconciler.Fiber | null | undefined {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function beforeActiveInstanceBlur(): void {
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function afterActiveInstanceBlur(): void {
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function prepareScopeUpdate(scopeInstance: unknown, instance: unknown): void {
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function getInstanceFromScope(scopeInstance: unknown): Instance | null {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function detachDeletedInstance(node: Instance): void {
|
|
178
|
+
node.free();
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function maySuspendCommit(type: Type, props: Props) {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function preloadInstance(type: Type, props: Props) {
|
|
186
|
+
return true; // Return true to indicate it's already loaded
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function startSuspendingCommit() {
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function suspendInstance(type: Type, props: Props) {
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function waitForCommitToBeReady() {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function appendChild(parentInstance: Instance, child: Instance | TextInstance): void {
|
|
200
|
+
const attached = attachments.get(child);
|
|
201
|
+
if (attached) {
|
|
202
|
+
Instance.assign(parentInstance, { [attached]: child });
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
(parentInstance as Node).addChild(child as Node);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function appendChildToContainer(container: Container, child: Instance | TextInstance): void {
|
|
209
|
+
appendChild(container, child);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function insertBefore(parentInstance: Instance, child: Instance | TextInstance, beforeChild: Instance | TextInstance | SuspenseInstance): void {
|
|
213
|
+
const attached = attachments.get(child);
|
|
214
|
+
if (attached) {
|
|
215
|
+
Instance.assign(parentInstance, { [attached]: child });
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
if ((child as Node).getParent() !== null) {
|
|
219
|
+
(parentInstance as Node).removeChild(child as Node);
|
|
220
|
+
}
|
|
221
|
+
(parentInstance as Node).addChild(child as Node);
|
|
222
|
+
(parentInstance as Node).moveChild(child as Node, (beforeChild as Node).getIndex());
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export function insertInContainerBefore(container: Container, child: Instance | TextInstance, beforeChild: Instance | TextInstance | SuspenseInstance): void {
|
|
226
|
+
insertBefore(container, child, beforeChild);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function removeChild(parentInstance: Instance, child: Instance | TextInstance | SuspenseInstance): void {
|
|
230
|
+
const attached = attachments.get(child);
|
|
231
|
+
if (attached) {
|
|
232
|
+
Instance.assign(parentInstance, { [attached]: undefined });
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
(parentInstance as Node).removeChild(child as Node);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export function removeChildFromContainer(container: Container, child: Instance | TextInstance | SuspenseInstance): void {
|
|
239
|
+
removeChild(container, child);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export function resetTextContent(instance: Instance): void {
|
|
243
|
+
instance.set('text', undefined);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export function commitTextUpdate(instance: TextInstance, oldText: string, newText: string): void {
|
|
247
|
+
instance.set('text', newText);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export function commitMount(instance: Instance, type: Type, props: Props, internalInstanceHandle: Reconciler.OpaqueHandle): void {
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function isEqual(a: unknown, b: unknown) {
|
|
254
|
+
if (a === b) return true;
|
|
255
|
+
|
|
256
|
+
const i1 = (a as Iterable<unknown> | undefined)?.[Symbol.iterator]?.();
|
|
257
|
+
const i2 = (b as Iterable<unknown> | undefined)?.[Symbol.iterator]?.();
|
|
258
|
+
while (i1 && i2) {
|
|
259
|
+
const n1 = i1.next();
|
|
260
|
+
const n2 = i2.next();
|
|
261
|
+
if (n1.done !== n2.done) return false; // Different lengths
|
|
262
|
+
if (n1.done) return true; // Both finished
|
|
263
|
+
if (n1.value !== n2.value) return false; // Values mismatch
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export function commitUpdate(instance: Instance, type: Type, prevProps: Props, nextProps: Props, internalHandle: Reconciler.OpaqueHandle): void {
|
|
268
|
+
const { attach: attachOld, ref: refOld, children: childrenOld, ...restOld } = prevProps;
|
|
269
|
+
const { attach: attachNew, ref: refNew, children: childrenNew, ...restNew } = nextProps;
|
|
270
|
+
if (attachOld !== attachNew)
|
|
271
|
+
throw new Error(`Cannot change attachment ${attachOld} to ${attachNew}`);
|
|
272
|
+
|
|
273
|
+
const toText = (c: unknown) => Array.isArray(c) ? c.join('') : c as string;
|
|
274
|
+
if (shouldSetTextContent(type, prevProps)) restOld['text'] = toText(childrenOld);
|
|
275
|
+
if (shouldSetTextContent(type, nextProps)) restNew['text'] = toText(childrenNew);
|
|
276
|
+
|
|
277
|
+
for (const [key, oldVal] of Object.entries(restOld)) {
|
|
278
|
+
const newVal = restNew[key];
|
|
279
|
+
if (isEqual(oldVal, newVal))
|
|
280
|
+
delete restNew[key];
|
|
281
|
+
else if (newVal === undefined)
|
|
282
|
+
restNew[key] = undefined;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
Instance.assign(instance, restNew);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export function hideInstance(instance: Instance | TextInstance): void {
|
|
289
|
+
instance.set('visible', false);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export const hideTextInstance = hideInstance;
|
|
293
|
+
|
|
294
|
+
export function unhideInstance(instance: Instance | TextInstance): void {
|
|
295
|
+
instance.set('visible', true);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export const unhideTextInstance = unhideInstance;
|
|
299
|
+
|
|
300
|
+
export function clearContainer(container: Container): void {
|
|
301
|
+
// No-op: Godot containers (e.g. the SceneTree root) can hold non-React
|
|
302
|
+
// children such as portal targets. The reconciler calls this on every
|
|
303
|
+
// initial mount; queue-freeing the container's children here would destroy
|
|
304
|
+
// those foreign nodes and leave React fibers referencing freed instances.
|
|
305
|
+
// React-managed children are removed by the reconciler's deletion path.
|
|
306
|
+
}
|
|
307
|
+
|
|
@@ -0,0 +1,22 @@
|
|
|
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
|
+
}
|
package/src/react-jsx.ts
ADDED
|
@@ -0,0 +1,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, 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
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**********************************************************************
|
|
2
|
+
Copyright (c) Vladimir Davidovich. All rights reserved.
|
|
3
|
+
***********************************************************************/
|
|
4
|
+
|
|
5
|
+
import type React from 'react';
|
|
6
|
+
import type { Object } from '@ringozz/godot/Object';
|
|
7
|
+
import type { ValueTypes } from '@ringozz/godot';
|
|
8
|
+
|
|
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
|
+
type ConstructorRepresentation<T = any> = new (...args: any[]) => T;
|
|
14
|
+
|
|
15
|
+
export type Instance = Object;
|
|
16
|
+
|
|
17
|
+
export type InstanceProps<T extends Instance = Instance> = {
|
|
18
|
+
/** An existing instance to render instead of creating a new one. */
|
|
19
|
+
object?: T;
|
|
20
|
+
/** Attaches the element to a named property of the parent instead of adding it as a child. */
|
|
21
|
+
attach?: string;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
type ReactProps<P> = React.PropsWithChildren<React.RefAttributes<P>>;
|
|
25
|
+
|
|
26
|
+
type WidenVT<T> = {
|
|
27
|
+
[K in keyof T]: T[K] extends ValueTypes ? T[K] | number[] : T[K];
|
|
28
|
+
};
|
|
29
|
+
|
|
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
|
+
>;
|