onejs-react 0.1.8 → 0.1.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.
- package/package.json +1 -1
- package/src/__tests__/collection-sync.test.tsx +436 -0
- package/src/__tests__/components.test.tsx +3 -1
- package/src/__tests__/hooks.test.tsx +581 -0
- package/src/__tests__/host-config.test.ts +7 -5
- package/src/__tests__/mocks.ts +40 -0
- package/src/__tests__/setup.ts +5 -3
- package/src/__tests__/style-parser.test.ts +6 -4
- package/src/hooks.ts +184 -22
- package/src/host-config.ts +37 -77
- package/src/index.ts +2 -2
package/src/__tests__/setup.ts
CHANGED
|
@@ -13,13 +13,13 @@ import { createMockCS, resetAllMocks } from "./mocks";
|
|
|
13
13
|
|
|
14
14
|
// Extend globalThis type for our mocks
|
|
15
15
|
declare global {
|
|
16
|
-
// eslint-disable-next-line no-var
|
|
17
|
-
var CS: ReturnType<typeof createMockCS>;
|
|
18
16
|
// eslint-disable-next-line no-var
|
|
19
17
|
var __eventAPI: {
|
|
20
18
|
addEventListener: ReturnType<typeof vi.fn>;
|
|
21
19
|
removeEventListener: ReturnType<typeof vi.fn>;
|
|
22
20
|
removeAllEventListeners: ReturnType<typeof vi.fn>;
|
|
21
|
+
setParent: ReturnType<typeof vi.fn>;
|
|
22
|
+
removeParent: ReturnType<typeof vi.fn>;
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
25
|
|
|
@@ -33,13 +33,15 @@ beforeEach(() => {
|
|
|
33
33
|
resetAllMocks();
|
|
34
34
|
|
|
35
35
|
// Create fresh mock CS global
|
|
36
|
-
|
|
36
|
+
(globalThis as any).CS = createMockCS();
|
|
37
37
|
|
|
38
38
|
// Mock event API with spies
|
|
39
39
|
global.__eventAPI = {
|
|
40
40
|
addEventListener: vi.fn(),
|
|
41
41
|
removeEventListener: vi.fn(),
|
|
42
42
|
removeAllEventListeners: vi.fn(),
|
|
43
|
+
setParent: vi.fn(),
|
|
44
|
+
removeParent: vi.fn(),
|
|
43
45
|
};
|
|
44
46
|
|
|
45
47
|
// Use real console but spy on it for test assertions
|
|
@@ -303,10 +303,12 @@ describe("style-parser", () => {
|
|
|
303
303
|
expect(parseStyleValue("flexShrink", 0)).toBe(0)
|
|
304
304
|
})
|
|
305
305
|
|
|
306
|
-
it("
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
expect(parseStyleValue("
|
|
306
|
+
it("converts enum properties to Unity enum values", () => {
|
|
307
|
+
const CS = (globalThis as any).CS
|
|
308
|
+
const UIE = CS.UnityEngine.UIElements
|
|
309
|
+
expect(parseStyleValue("flexDirection", "row")).toBe(UIE.FlexDirection.Row)
|
|
310
|
+
expect(parseStyleValue("display", "none")).toBe(UIE.DisplayStyle.None)
|
|
311
|
+
expect(parseStyleValue("position", "absolute")).toBe(UIE.Position.Absolute)
|
|
310
312
|
})
|
|
311
313
|
|
|
312
314
|
it("passes through unknown properties unchanged", () => {
|
package/src/hooks.ts
CHANGED
|
@@ -1,30 +1,69 @@
|
|
|
1
|
-
import { useState, useEffect, useRef } from "react"
|
|
1
|
+
import { useState, useEffect, useRef, useReducer } from "react"
|
|
2
|
+
|
|
3
|
+
// QuickJS environment declarations
|
|
4
|
+
declare function requestAnimationFrame(callback: (time: number) => void): number;
|
|
5
|
+
declare function cancelAnimationFrame(id: number): void;
|
|
2
6
|
|
|
3
7
|
/**
|
|
4
8
|
* Syncs a value from C# (or any external source) to React state, checking every frame.
|
|
5
9
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
10
|
+
* Has two modes depending on whether a `select` function is provided:
|
|
11
|
+
*
|
|
12
|
+
* **Simple mode** (no selector): Compares values with `Object.is`. Best for primitives
|
|
13
|
+
* (numbers, strings, booleans) and cases where the getter returns a new object each time.
|
|
14
|
+
*
|
|
15
|
+
* **Selector mode**: Extracts an array of comparable values to watch. Re-renders only when
|
|
16
|
+
* any selected value changes. Essential for C# proxy objects where the proxy reference is
|
|
17
|
+
* cached — without a selector, you'd be comparing the same proxy to itself.
|
|
18
|
+
* The returned value is always read fresh from the getter during render.
|
|
8
19
|
*
|
|
9
20
|
* @param getter - Function that returns the current value (called every frame)
|
|
10
|
-
* @param
|
|
21
|
+
* @param selectOrDeps - Either a selector function or a dependency array
|
|
22
|
+
* @param deps - Optional dependency array (only when using a selector)
|
|
11
23
|
* @returns The current value, updated each frame if changed
|
|
12
24
|
*
|
|
13
25
|
* @example
|
|
14
|
-
* //
|
|
15
|
-
* const health = useFrameSync(() => player.
|
|
16
|
-
* const
|
|
26
|
+
* // Simple: sync a C# property (primitives)
|
|
27
|
+
* const health = useFrameSync(() => player.Health)
|
|
28
|
+
* const score = useFrameSync(() => gameManager.Score)
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* // Selector: watch specific properties on a C# proxy object
|
|
32
|
+
* const place = useFrameSync(
|
|
33
|
+
* () => gameState.currentPlace,
|
|
34
|
+
* (p) => [p?.Name, p?.NPCs?.Count, p?.Items?.Count]
|
|
35
|
+
* )
|
|
17
36
|
*
|
|
18
37
|
* @example
|
|
19
|
-
* //
|
|
20
|
-
* const
|
|
38
|
+
* // Selector: with a version stamp from C#
|
|
39
|
+
* const quest = useFrameSync(
|
|
40
|
+
* () => questManager.activeQuest ?? null,
|
|
41
|
+
* (q) => [q?.Version]
|
|
42
|
+
* )
|
|
21
43
|
*
|
|
22
44
|
* @example
|
|
23
|
-
* //
|
|
24
|
-
* const
|
|
45
|
+
* // Simple with dependencies (if the source reference can change)
|
|
46
|
+
* const health = useFrameSync(() => currentPlayer.Health, [currentPlayer])
|
|
25
47
|
*/
|
|
26
|
-
export function useFrameSync<T>(
|
|
27
|
-
|
|
48
|
+
export function useFrameSync<T>(
|
|
49
|
+
getter: () => T,
|
|
50
|
+
selectOrDeps?: ((value: T) => readonly unknown[]) | readonly unknown[],
|
|
51
|
+
deps?: readonly unknown[]
|
|
52
|
+
): T {
|
|
53
|
+
// Determine which mode we're in
|
|
54
|
+
const hasSelector = typeof selectOrDeps === "function"
|
|
55
|
+
const select = hasSelector ? selectOrDeps as (value: T) => readonly unknown[] : undefined
|
|
56
|
+
const effectDeps = hasSelector ? (deps ?? []) : (selectOrDeps as readonly unknown[] ?? [])
|
|
57
|
+
|
|
58
|
+
if (select) {
|
|
59
|
+
return useFrameSyncSelect(getter, select, effectDeps)
|
|
60
|
+
} else {
|
|
61
|
+
return useFrameSyncSimple(getter, effectDeps)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Simple mode: compare with Object.is. */
|
|
66
|
+
function useFrameSyncSimple<T>(getter: () => T, deps: readonly unknown[]): T {
|
|
28
67
|
const getInitialValue = (): T => {
|
|
29
68
|
try {
|
|
30
69
|
return getter()
|
|
@@ -38,11 +77,9 @@ export function useFrameSync<T>(getter: () => T, deps: readonly unknown[] = []):
|
|
|
38
77
|
const getterRef = useRef(getter)
|
|
39
78
|
const runningRef = useRef(false)
|
|
40
79
|
|
|
41
|
-
// Keep getter ref updated
|
|
42
80
|
getterRef.current = getter
|
|
43
81
|
|
|
44
82
|
useEffect(() => {
|
|
45
|
-
// Re-initialize when deps change
|
|
46
83
|
try {
|
|
47
84
|
const initial = getterRef.current()
|
|
48
85
|
lastValueRef.current = initial
|
|
@@ -81,20 +118,103 @@ export function useFrameSync<T>(getter: () => T, deps: readonly unknown[] = []):
|
|
|
81
118
|
return value
|
|
82
119
|
}
|
|
83
120
|
|
|
121
|
+
/** Selector mode: extract comparable values, always return fresh from getter. */
|
|
122
|
+
function useFrameSyncSelect<T>(
|
|
123
|
+
getter: () => T,
|
|
124
|
+
select: (value: T) => readonly unknown[],
|
|
125
|
+
deps: readonly unknown[]
|
|
126
|
+
): T {
|
|
127
|
+
const [, forceRender] = useReducer((x: number) => x + 1, 0)
|
|
128
|
+
const getterRef = useRef(getter)
|
|
129
|
+
const selectRef = useRef(select)
|
|
130
|
+
const lastSelectedRef = useRef<readonly unknown[]>([])
|
|
131
|
+
const runningRef = useRef(false)
|
|
132
|
+
const initializedRef = useRef(false)
|
|
133
|
+
|
|
134
|
+
getterRef.current = getter
|
|
135
|
+
selectRef.current = select
|
|
136
|
+
|
|
137
|
+
// Initialize dependency tracking on first render
|
|
138
|
+
if (!initializedRef.current) {
|
|
139
|
+
initializedRef.current = true
|
|
140
|
+
try {
|
|
141
|
+
const val = getter()
|
|
142
|
+
lastSelectedRef.current = select(val)
|
|
143
|
+
} catch {
|
|
144
|
+
// Getter or select failed, keep empty deps
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
useEffect(() => {
|
|
149
|
+
try {
|
|
150
|
+
const val = getterRef.current()
|
|
151
|
+
lastSelectedRef.current = selectRef.current(val)
|
|
152
|
+
} catch {
|
|
153
|
+
// Getter or select failed
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
runningRef.current = true
|
|
157
|
+
|
|
158
|
+
const check = () => {
|
|
159
|
+
if (!runningRef.current) return
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
const current = getterRef.current()
|
|
163
|
+
const selected = selectRef.current(current)
|
|
164
|
+
const prev = lastSelectedRef.current
|
|
165
|
+
const changed = selected.length !== prev.length ||
|
|
166
|
+
selected.some((val, i) => !Object.is(val, prev[i]))
|
|
167
|
+
|
|
168
|
+
if (changed) {
|
|
169
|
+
lastSelectedRef.current = selected
|
|
170
|
+
forceRender()
|
|
171
|
+
}
|
|
172
|
+
} catch {
|
|
173
|
+
// Getter or select might fail if object was destroyed
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (runningRef.current) {
|
|
177
|
+
requestAnimationFrame(check)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
requestAnimationFrame(check)
|
|
182
|
+
|
|
183
|
+
return () => {
|
|
184
|
+
runningRef.current = false
|
|
185
|
+
}
|
|
186
|
+
}, deps)
|
|
187
|
+
|
|
188
|
+
// Always read fresh from getter during render.
|
|
189
|
+
// This ensures we return the latest proxy with current C# state.
|
|
190
|
+
try {
|
|
191
|
+
return getterRef.current()
|
|
192
|
+
} catch {
|
|
193
|
+
return undefined as T
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
84
197
|
/**
|
|
85
|
-
*
|
|
86
|
-
* Useful for objects/structs where reference equality isn't sufficient.
|
|
198
|
+
* @deprecated Use `useFrameSync` with a selector instead.
|
|
87
199
|
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
200
|
+
* `useFrameSyncWith` compares the value returned by the getter using a custom
|
|
201
|
+
* equality function. However, this does NOT work with C# proxy objects because
|
|
202
|
+
* the proxy reference is cached — you end up comparing the same object to itself.
|
|
91
203
|
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
204
|
+
* Instead, use `useFrameSync` with a selector that extracts comparable values:
|
|
205
|
+
* ```ts
|
|
206
|
+
* // Before (broken with C# proxies):
|
|
94
207
|
* const pos = useFrameSyncWith(
|
|
95
208
|
* () => transform.position,
|
|
96
209
|
* (a, b) => a.x === b.x && a.y === b.y && a.z === b.z
|
|
97
210
|
* )
|
|
211
|
+
*
|
|
212
|
+
* // After (works correctly):
|
|
213
|
+
* const pos = useFrameSync(
|
|
214
|
+
* () => transform.position,
|
|
215
|
+
* (p) => [p.x, p.y, p.z]
|
|
216
|
+
* )
|
|
217
|
+
* ```
|
|
98
218
|
*/
|
|
99
219
|
export function useFrameSyncWith<T>(
|
|
100
220
|
getter: () => T,
|
|
@@ -214,3 +334,45 @@ export function useThrottledSync<T>(
|
|
|
214
334
|
|
|
215
335
|
return value
|
|
216
336
|
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Converts a C# collection (List<T>, array, etc.) to a JavaScript array.
|
|
340
|
+
*
|
|
341
|
+
* C# collections exposed through the OneJS proxy are not JS arrays — they
|
|
342
|
+
* lack .map(), .filter(), and other array methods. This utility converts
|
|
343
|
+
* them for use in React rendering.
|
|
344
|
+
*
|
|
345
|
+
* Supports objects with a `.Count` property (List<T>, IList) or a `.Length`
|
|
346
|
+
* property (C# arrays). Returns an empty array for null/undefined input.
|
|
347
|
+
*
|
|
348
|
+
* @param collection - A C# collection, or null/undefined
|
|
349
|
+
* @returns A JavaScript array containing the elements
|
|
350
|
+
*
|
|
351
|
+
* @example
|
|
352
|
+
* // Map over a C# List in JSX
|
|
353
|
+
* {toArray(inventory.Items).map(item => <ItemView key={item.Id} item={item} />)}
|
|
354
|
+
*
|
|
355
|
+
* @example
|
|
356
|
+
* // Convert a C# array
|
|
357
|
+
* const renderers = toArray(go.GetComponentsInChildren(CS.UnityEngine.Renderer))
|
|
358
|
+
*
|
|
359
|
+
* @example
|
|
360
|
+
* // Safe with null — returns []
|
|
361
|
+
* const npcs = toArray(currentPlace?.NPCs)
|
|
362
|
+
*
|
|
363
|
+
* @example
|
|
364
|
+
* // With explicit type parameter
|
|
365
|
+
* const items = toArray<Item>(questLog.ActiveQuests)
|
|
366
|
+
*/
|
|
367
|
+
export function toArray<T = unknown>(collection: unknown): T[] {
|
|
368
|
+
if (collection == null) return []
|
|
369
|
+
const col = collection as Record<string, unknown>
|
|
370
|
+
const len = typeof col.Count === "number" ? col.Count
|
|
371
|
+
: typeof col.Length === "number" ? col.Length
|
|
372
|
+
: 0
|
|
373
|
+
const result: T[] = []
|
|
374
|
+
for (let i = 0; i < len; i++) {
|
|
375
|
+
result.push((col as any)[i])
|
|
376
|
+
}
|
|
377
|
+
return result
|
|
378
|
+
}
|
package/src/host-config.ts
CHANGED
|
@@ -56,7 +56,10 @@ declare const CS: {
|
|
|
56
56
|
CollectionVirtualizationMethod: CSEnum;
|
|
57
57
|
DisplayStyle: CSEnum;
|
|
58
58
|
PickingMode: CSEnum;
|
|
59
|
+
SliderDirection: CSEnum;
|
|
59
60
|
};
|
|
61
|
+
ScaleMode: CSEnum;
|
|
62
|
+
Rect: new (...args: any[]) => any;
|
|
60
63
|
};
|
|
61
64
|
OneJS: {
|
|
62
65
|
GPU: {
|
|
@@ -578,9 +581,10 @@ function removeMergedTextChild(parentInstance: Instance, child: Instance) {
|
|
|
578
581
|
|
|
579
582
|
// Apply common props (text, value, label)
|
|
580
583
|
function applyCommonProps(element: CSObject, props: Record<string, unknown>) {
|
|
581
|
-
|
|
582
|
-
if (props.
|
|
583
|
-
if (props.
|
|
584
|
+
const el = element as any;
|
|
585
|
+
if (props.text !== undefined) el.text = props.text as string;
|
|
586
|
+
if (props.value !== undefined) el.value = props.value;
|
|
587
|
+
if (props.label !== undefined) el.label = props.label as string;
|
|
584
588
|
}
|
|
585
589
|
|
|
586
590
|
// Helper to set enum prop if defined
|
|
@@ -599,106 +603,62 @@ function setValueProp<T>(target: T, key: keyof T, props: Record<string, unknown>
|
|
|
599
603
|
|
|
600
604
|
// Apply TextField-specific properties
|
|
601
605
|
function applyTextFieldProps(element: CSObject, props: Record<string, unknown>) {
|
|
602
|
-
|
|
603
|
-
if (props.readOnly !== undefined)
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
if (props.
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
if (props.
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
if (props.
|
|
613
|
-
(element as { isPasswordField: boolean }).isPasswordField = props.isPasswordField as boolean;
|
|
614
|
-
}
|
|
615
|
-
if (props.maskChar !== undefined) {
|
|
616
|
-
(element as { maskChar: string }).maskChar = (props.maskChar as string).charAt(0);
|
|
617
|
-
}
|
|
618
|
-
if (props.isDelayed !== undefined) {
|
|
619
|
-
(element as { isDelayed: boolean }).isDelayed = props.isDelayed as boolean;
|
|
620
|
-
}
|
|
621
|
-
if (props.selectAllOnFocus !== undefined) {
|
|
622
|
-
(element as { selectAllOnFocus: boolean }).selectAllOnFocus = props.selectAllOnFocus as boolean;
|
|
623
|
-
}
|
|
624
|
-
if (props.selectAllOnMouseUp !== undefined) {
|
|
625
|
-
(element as { selectAllOnMouseUp: boolean }).selectAllOnMouseUp = props.selectAllOnMouseUp as boolean;
|
|
626
|
-
}
|
|
627
|
-
if (props.hideMobileInput !== undefined) {
|
|
628
|
-
(element as { hideMobileInput: boolean }).hideMobileInput = props.hideMobileInput as boolean;
|
|
629
|
-
}
|
|
630
|
-
if (props.autoCorrection !== undefined) {
|
|
631
|
-
(element as { autoCorrection: boolean }).autoCorrection = props.autoCorrection as boolean;
|
|
632
|
-
}
|
|
606
|
+
const el = element as any;
|
|
607
|
+
if (props.readOnly !== undefined) el.isReadOnly = props.readOnly;
|
|
608
|
+
if (props.multiline !== undefined) el.multiline = props.multiline;
|
|
609
|
+
if (props.maxLength !== undefined) el.maxLength = props.maxLength;
|
|
610
|
+
if (props.isPasswordField !== undefined) el.isPasswordField = props.isPasswordField;
|
|
611
|
+
if (props.maskChar !== undefined) el.maskChar = (props.maskChar as string).charAt(0);
|
|
612
|
+
if (props.isDelayed !== undefined) el.isDelayed = props.isDelayed;
|
|
613
|
+
if (props.selectAllOnFocus !== undefined) el.selectAllOnFocus = props.selectAllOnFocus;
|
|
614
|
+
if (props.selectAllOnMouseUp !== undefined) el.selectAllOnMouseUp = props.selectAllOnMouseUp;
|
|
615
|
+
if (props.hideMobileInput !== undefined) el.hideMobileInput = props.hideMobileInput;
|
|
616
|
+
if (props.autoCorrection !== undefined) el.autoCorrection = props.autoCorrection;
|
|
633
617
|
// Note: placeholder is handled differently in Unity - it's set via the textEdition interface
|
|
634
618
|
// For now we skip it as it requires more complex handling
|
|
635
619
|
}
|
|
636
620
|
|
|
637
621
|
// Apply Slider-specific properties
|
|
638
622
|
function applySliderProps(element: CSObject, props: Record<string, unknown>) {
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
if (props.
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
if (props.
|
|
646
|
-
(element as { showInputField: boolean }).showInputField = props.showInputField as boolean;
|
|
647
|
-
}
|
|
648
|
-
if (props.inverted !== undefined) {
|
|
649
|
-
(element as { inverted: boolean }).inverted = props.inverted as boolean;
|
|
650
|
-
}
|
|
651
|
-
if (props.pageSize !== undefined) {
|
|
652
|
-
(element as { pageSize: number }).pageSize = props.pageSize as number;
|
|
653
|
-
}
|
|
654
|
-
if (props.fill !== undefined) {
|
|
655
|
-
(element as { fill: boolean }).fill = props.fill as boolean;
|
|
656
|
-
}
|
|
623
|
+
const el = element as any;
|
|
624
|
+
if (props.lowValue !== undefined) el.lowValue = props.lowValue;
|
|
625
|
+
if (props.highValue !== undefined) el.highValue = props.highValue;
|
|
626
|
+
if (props.showInputField !== undefined) el.showInputField = props.showInputField;
|
|
627
|
+
if (props.inverted !== undefined) el.inverted = props.inverted;
|
|
628
|
+
if (props.pageSize !== undefined) el.pageSize = props.pageSize;
|
|
629
|
+
if (props.fill !== undefined) el.fill = props.fill;
|
|
657
630
|
if (props.direction !== undefined) {
|
|
658
|
-
|
|
659
|
-
(element as { direction: unknown }).direction = UIE.SliderDirection[props.direction as string];
|
|
631
|
+
el.direction = CS.UnityEngine.UIElements.SliderDirection[props.direction as string];
|
|
660
632
|
}
|
|
661
633
|
}
|
|
662
634
|
|
|
663
635
|
// Apply Toggle-specific properties
|
|
664
636
|
function applyToggleProps(element: CSObject, props: Record<string, unknown>) {
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
if (props.toggleOnLabelClick !== undefined) {
|
|
669
|
-
(element as { toggleOnLabelClick: boolean }).toggleOnLabelClick = props.toggleOnLabelClick as boolean;
|
|
670
|
-
}
|
|
637
|
+
const el = element as any;
|
|
638
|
+
if (props.text !== undefined) el.text = props.text;
|
|
639
|
+
if (props.toggleOnLabelClick !== undefined) el.toggleOnLabelClick = props.toggleOnLabelClick;
|
|
671
640
|
}
|
|
672
641
|
|
|
673
642
|
// Apply Image-specific properties
|
|
674
643
|
function applyImageProps(element: CSObject, props: Record<string, unknown>) {
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
if (props.
|
|
679
|
-
(element as { sprite: unknown }).sprite = props.sprite;
|
|
680
|
-
}
|
|
681
|
-
if (props.vectorImage !== undefined) {
|
|
682
|
-
(element as { vectorImage: unknown }).vectorImage = props.vectorImage;
|
|
683
|
-
}
|
|
644
|
+
const el = element as any;
|
|
645
|
+
if (props.image !== undefined) el.image = props.image;
|
|
646
|
+
if (props.sprite !== undefined) el.sprite = props.sprite;
|
|
647
|
+
if (props.vectorImage !== undefined) el.vectorImage = props.vectorImage;
|
|
684
648
|
if (props.scaleMode !== undefined) {
|
|
685
|
-
|
|
686
|
-
(element as { scaleMode: unknown }).scaleMode = scaleMode;
|
|
649
|
+
el.scaleMode = CS.UnityEngine.ScaleMode[props.scaleMode as string];
|
|
687
650
|
}
|
|
688
651
|
if (props.tintColor !== undefined) {
|
|
689
|
-
// Parse color string to Unity Color
|
|
690
652
|
const color = parseColor(props.tintColor as string);
|
|
691
|
-
if (color)
|
|
692
|
-
(element as { tintColor: unknown }).tintColor = color;
|
|
693
|
-
}
|
|
653
|
+
if (color) el.tintColor = color;
|
|
694
654
|
}
|
|
695
655
|
if (props.sourceRect !== undefined) {
|
|
696
656
|
const rect = props.sourceRect as { x: number; y: number; width: number; height: number };
|
|
697
|
-
|
|
657
|
+
el.sourceRect = new CS.UnityEngine.Rect(rect.x, rect.y, rect.width, rect.height);
|
|
698
658
|
}
|
|
699
659
|
if (props.uv !== undefined) {
|
|
700
660
|
const rect = props.uv as { x: number; y: number; width: number; height: number };
|
|
701
|
-
|
|
661
|
+
el.uv = new CS.UnityEngine.Rect(rect.x, rect.y, rect.width, rect.height);
|
|
702
662
|
}
|
|
703
663
|
}
|
|
704
664
|
|
package/src/index.ts
CHANGED
|
@@ -38,8 +38,8 @@ export type {
|
|
|
38
38
|
// Vector Drawing
|
|
39
39
|
export { Transform2D, useVectorContent } from './vector';
|
|
40
40
|
|
|
41
|
-
// Sync Hooks
|
|
42
|
-
export { useFrameSync, useFrameSyncWith, useThrottledSync } from './hooks';
|
|
41
|
+
// Sync Hooks & C# Interop Utilities
|
|
42
|
+
export { useFrameSync, useFrameSyncWith, useThrottledSync, toArray } from './hooks';
|
|
43
43
|
|
|
44
44
|
// Types
|
|
45
45
|
export type {
|