onejs-react 0.1.0 → 0.1.2
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/README.md +265 -0
- package/package.json +3 -1
- package/src/__tests__/components.test.tsx +36 -0
- package/src/__tests__/host-config.test.ts +141 -99
- package/src/__tests__/mocks.ts +17 -1
- package/src/__tests__/setup.ts +23 -11
- package/src/components.tsx +77 -48
- package/src/error-boundary.tsx +175 -0
- package/src/host-config.ts +503 -162
- package/src/index.ts +46 -2
- package/src/renderer.ts +50 -23
- package/src/screen.tsx +1 -1
- package/src/style-parser.ts +171 -79
- package/src/types.ts +326 -8
- package/src/vector.ts +312 -0
package/src/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Components
|
|
2
2
|
export {
|
|
3
3
|
View,
|
|
4
|
+
Text,
|
|
4
5
|
Label,
|
|
5
6
|
Button,
|
|
6
7
|
TextField,
|
|
@@ -12,7 +13,11 @@ export {
|
|
|
12
13
|
} from './components';
|
|
13
14
|
|
|
14
15
|
// Renderer
|
|
15
|
-
export { render, unmount } from './renderer';
|
|
16
|
+
export { render, unmount, flushSync, batchedUpdates, getDebugInfo } from './renderer';
|
|
17
|
+
|
|
18
|
+
// Error Handling
|
|
19
|
+
export { ErrorBoundary, formatError } from './error-boundary';
|
|
20
|
+
export type { ErrorBoundaryProps } from './error-boundary';
|
|
16
21
|
|
|
17
22
|
// Responsive Design
|
|
18
23
|
export {
|
|
@@ -30,18 +35,38 @@ export type {
|
|
|
30
35
|
BreakpointName,
|
|
31
36
|
} from './screen';
|
|
32
37
|
|
|
38
|
+
// Vector Drawing
|
|
39
|
+
export { Transform2D, useVectorContent } from './vector';
|
|
40
|
+
|
|
33
41
|
// Types
|
|
34
42
|
export type {
|
|
35
43
|
ViewStyle,
|
|
44
|
+
// Event data types
|
|
36
45
|
PointerEventData,
|
|
46
|
+
MouseEventData,
|
|
47
|
+
WheelEventData,
|
|
37
48
|
KeyEventData,
|
|
38
49
|
ChangeEventData,
|
|
50
|
+
FocusEventData,
|
|
51
|
+
DragEventData,
|
|
52
|
+
GeometryEventData,
|
|
53
|
+
NavigationEventData,
|
|
54
|
+
TransitionEventData,
|
|
55
|
+
// Event handler types
|
|
39
56
|
PointerEventHandler,
|
|
57
|
+
MouseEventHandler,
|
|
58
|
+
WheelEventHandler,
|
|
40
59
|
KeyEventHandler,
|
|
41
60
|
ChangeEventHandler,
|
|
42
61
|
FocusEventHandler,
|
|
62
|
+
DragEventHandler,
|
|
63
|
+
GeometryEventHandler,
|
|
64
|
+
NavigationEventHandler,
|
|
65
|
+
TransitionEventHandler,
|
|
66
|
+
// Component props
|
|
43
67
|
BaseProps,
|
|
44
68
|
ViewProps,
|
|
69
|
+
TextProps,
|
|
45
70
|
LabelProps,
|
|
46
71
|
ButtonProps,
|
|
47
72
|
TextFieldProps,
|
|
@@ -49,6 +74,25 @@ export type {
|
|
|
49
74
|
SliderProps,
|
|
50
75
|
ScrollViewProps,
|
|
51
76
|
ImageProps,
|
|
52
|
-
VisualElement,
|
|
53
77
|
ListViewProps,
|
|
78
|
+
// Container type for render()
|
|
79
|
+
RenderContainer,
|
|
80
|
+
// Element types for refs
|
|
81
|
+
VisualElement,
|
|
82
|
+
TextElement,
|
|
83
|
+
LabelElement,
|
|
84
|
+
ButtonElement,
|
|
85
|
+
TextFieldElement,
|
|
86
|
+
ToggleElement,
|
|
87
|
+
SliderElement,
|
|
88
|
+
ScrollViewElement,
|
|
89
|
+
ImageElement,
|
|
90
|
+
// Vector drawing types
|
|
91
|
+
Vector2,
|
|
92
|
+
Color,
|
|
93
|
+
Angle,
|
|
94
|
+
ArcDirection,
|
|
95
|
+
Painter2D,
|
|
96
|
+
MeshGenerationContext,
|
|
97
|
+
GenerateVisualContentCallback,
|
|
54
98
|
} from './types';
|
package/src/renderer.ts
CHANGED
|
@@ -1,30 +1,30 @@
|
|
|
1
1
|
import Reconciler from 'react-reconciler';
|
|
2
2
|
import type { ReactNode } from 'react';
|
|
3
3
|
import { hostConfig, type Container } from './host-config';
|
|
4
|
+
import type { RenderContainer, VisualElement } from './types';
|
|
4
5
|
|
|
5
6
|
declare const console: { log: (...args: unknown[]) => void; error: (...args: unknown[]) => void };
|
|
6
7
|
|
|
7
8
|
// Create the reconciler
|
|
8
9
|
const reconciler = Reconciler(hostConfig);
|
|
9
10
|
|
|
10
|
-
// Inject into dev tools
|
|
11
|
+
// Inject into dev tools with full configuration
|
|
12
|
+
// This enables React DevTools to inspect the component tree
|
|
11
13
|
reconciler.injectIntoDevTools({
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
bundleType: 1, // 0 for prod, 1 for dev
|
|
15
|
+
version: '19.0.0',
|
|
16
|
+
rendererPackageName: 'onejs-react',
|
|
15
17
|
});
|
|
16
18
|
|
|
17
19
|
// Track roots for hot reload / re-render
|
|
18
|
-
const roots = new Map<
|
|
20
|
+
const roots = new Map<RenderContainer, ReturnType<typeof reconciler.createContainer>>();
|
|
19
21
|
|
|
20
|
-
export function render(element: ReactNode, container:
|
|
21
|
-
console.log('[onejs-react] render() called');
|
|
22
|
+
export function render(element: ReactNode, container: RenderContainer): void {
|
|
22
23
|
let root = roots.get(container);
|
|
23
24
|
|
|
24
25
|
if (!root) {
|
|
25
|
-
console.log('[onejs-react] creating new container');
|
|
26
26
|
root = reconciler.createContainer(
|
|
27
|
-
container,
|
|
27
|
+
container as Container,
|
|
28
28
|
0, // LegacyRoot (0) vs ConcurrentRoot (1)
|
|
29
29
|
null, // hydrationCallbacks
|
|
30
30
|
false, // isStrictMode
|
|
@@ -36,30 +36,21 @@ export function render(element: ReactNode, container: Container): void {
|
|
|
36
36
|
roots.set(container, root);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
reconciler.updateContainer(element, root, null, () => {
|
|
41
|
-
console.log('[onejs-react] updateContainer callback fired');
|
|
42
|
-
});
|
|
39
|
+
reconciler.updateContainer(element, root, null, () => {});
|
|
43
40
|
|
|
44
41
|
// Try to flush synchronous work
|
|
45
|
-
console.log('[onejs-react] attempting to flush sync work');
|
|
46
42
|
try {
|
|
47
|
-
// flushSync may be exported differently - try flushSyncWork first
|
|
48
43
|
if (typeof (reconciler as any).flushSyncWork === 'function') {
|
|
49
44
|
(reconciler as any).flushSyncWork();
|
|
50
|
-
console.log('[onejs-react] flushSyncWork completed');
|
|
51
45
|
} else if (typeof (reconciler as any).flushSync === 'function') {
|
|
52
46
|
(reconciler as any).flushSync(() => {});
|
|
53
|
-
console.log('[onejs-react] flushSync completed');
|
|
54
|
-
} else {
|
|
55
|
-
console.log('[onejs-react] no sync flush method available, relying on microtasks');
|
|
56
47
|
}
|
|
57
48
|
} catch (e) {
|
|
58
|
-
|
|
49
|
+
// Sync flush failed, rely on microtasks
|
|
59
50
|
}
|
|
60
51
|
}
|
|
61
52
|
|
|
62
|
-
export function unmount(container:
|
|
53
|
+
export function unmount(container: RenderContainer): void {
|
|
63
54
|
const root = roots.get(container);
|
|
64
55
|
if (root) {
|
|
65
56
|
reconciler.updateContainer(null, root, null, () => {});
|
|
@@ -67,7 +58,43 @@ export function unmount(container: Container): void {
|
|
|
67
58
|
}
|
|
68
59
|
}
|
|
69
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Execute a callback synchronously, flushing all updates before returning.
|
|
63
|
+
* Useful for tests or when you need immediate UI updates.
|
|
64
|
+
*/
|
|
65
|
+
export function flushSync<T>(callback: () => T): T {
|
|
66
|
+
if (typeof (reconciler as any).flushSync === 'function') {
|
|
67
|
+
return (reconciler as any).flushSync(callback);
|
|
68
|
+
}
|
|
69
|
+
// Fallback: just call the callback
|
|
70
|
+
return callback();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Batch multiple updates together for better performance.
|
|
75
|
+
* All updates inside the callback are batched into a single render.
|
|
76
|
+
*/
|
|
77
|
+
export function batchedUpdates<T>(callback: () => T): T {
|
|
78
|
+
if (typeof (reconciler as any).batchedUpdates === 'function') {
|
|
79
|
+
return (reconciler as any).batchedUpdates(callback);
|
|
80
|
+
}
|
|
81
|
+
// Fallback: just call the callback
|
|
82
|
+
return callback();
|
|
83
|
+
}
|
|
84
|
+
|
|
70
85
|
// Export for testing/debugging
|
|
71
|
-
export function getRoot(container:
|
|
72
|
-
|
|
86
|
+
export function getRoot(container: RenderContainer) {
|
|
87
|
+
return roots.get(container);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Get debug info about all active render roots.
|
|
92
|
+
* Useful for debugging and DevTools integration.
|
|
93
|
+
*/
|
|
94
|
+
export function getDebugInfo() {
|
|
95
|
+
return {
|
|
96
|
+
activeRoots: roots.size,
|
|
97
|
+
reconcilerVersion: '0.31.0',
|
|
98
|
+
reactVersion: '19.0.0',
|
|
99
|
+
};
|
|
73
100
|
}
|
package/src/screen.tsx
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* Mobile-first: At 1400px width, sm/md/lg/xl are all active (not just xl).
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { createContext, useContext, useState, useEffect, ReactNode } from "react"
|
|
10
|
+
import { createContext, useContext, useState, useEffect, type ReactNode } from "react"
|
|
11
11
|
|
|
12
12
|
// Globals from QuickJS environment
|
|
13
13
|
declare const __root: {
|
package/src/style-parser.ts
CHANGED
|
@@ -13,6 +13,17 @@ declare const CS: {
|
|
|
13
13
|
Length: new (value: number, unit?: number) => CSLength;
|
|
14
14
|
LengthUnit: { Pixel: number; Percent: number };
|
|
15
15
|
StyleKeyword: { Auto: number; None: number; Initial: number };
|
|
16
|
+
// Enums for style properties
|
|
17
|
+
FlexDirection: Record<string, number>;
|
|
18
|
+
Wrap: Record<string, number>;
|
|
19
|
+
Align: Record<string, number>;
|
|
20
|
+
Justify: Record<string, number>;
|
|
21
|
+
Position: Record<string, number>;
|
|
22
|
+
Overflow: Record<string, number>;
|
|
23
|
+
DisplayStyle: Record<string, number>;
|
|
24
|
+
Visibility: Record<string, number>;
|
|
25
|
+
WhiteSpace: Record<string, number>;
|
|
26
|
+
TextAnchor: Record<string, number>;
|
|
16
27
|
};
|
|
17
28
|
};
|
|
18
29
|
};
|
|
@@ -80,12 +91,119 @@ const NUMBER_PROPERTIES = new Set([
|
|
|
80
91
|
"flexGrow", "flexShrink", "opacity",
|
|
81
92
|
])
|
|
82
93
|
|
|
83
|
-
// Enum
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
94
|
+
// Enum property mappings: React style value -> Unity enum value
|
|
95
|
+
// Keys are camelCase (React/CSS style), values map to Unity enum member names
|
|
96
|
+
const ENUM_MAPPINGS: Record<string, { enum: () => Record<string, number>, values: Record<string, string> }> = {
|
|
97
|
+
flexDirection: {
|
|
98
|
+
enum: () => CS.UnityEngine.UIElements.FlexDirection,
|
|
99
|
+
values: {
|
|
100
|
+
"row": "Row",
|
|
101
|
+
"row-reverse": "RowReverse",
|
|
102
|
+
"column": "Column",
|
|
103
|
+
"column-reverse": "ColumnReverse",
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
flexWrap: {
|
|
107
|
+
enum: () => CS.UnityEngine.UIElements.Wrap,
|
|
108
|
+
values: {
|
|
109
|
+
"nowrap": "NoWrap",
|
|
110
|
+
"wrap": "Wrap",
|
|
111
|
+
"wrap-reverse": "WrapReverse",
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
alignItems: {
|
|
115
|
+
enum: () => CS.UnityEngine.UIElements.Align,
|
|
116
|
+
values: {
|
|
117
|
+
"auto": "Auto",
|
|
118
|
+
"flex-start": "FlexStart",
|
|
119
|
+
"flex-end": "FlexEnd",
|
|
120
|
+
"center": "Center",
|
|
121
|
+
"stretch": "Stretch",
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
alignSelf: {
|
|
125
|
+
enum: () => CS.UnityEngine.UIElements.Align,
|
|
126
|
+
values: {
|
|
127
|
+
"auto": "Auto",
|
|
128
|
+
"flex-start": "FlexStart",
|
|
129
|
+
"flex-end": "FlexEnd",
|
|
130
|
+
"center": "Center",
|
|
131
|
+
"stretch": "Stretch",
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
alignContent: {
|
|
135
|
+
enum: () => CS.UnityEngine.UIElements.Align,
|
|
136
|
+
values: {
|
|
137
|
+
"auto": "Auto",
|
|
138
|
+
"flex-start": "FlexStart",
|
|
139
|
+
"flex-end": "FlexEnd",
|
|
140
|
+
"center": "Center",
|
|
141
|
+
"stretch": "Stretch",
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
justifyContent: {
|
|
145
|
+
enum: () => CS.UnityEngine.UIElements.Justify,
|
|
146
|
+
values: {
|
|
147
|
+
"flex-start": "FlexStart",
|
|
148
|
+
"flex-end": "FlexEnd",
|
|
149
|
+
"center": "Center",
|
|
150
|
+
"space-between": "SpaceBetween",
|
|
151
|
+
"space-around": "SpaceAround",
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
position: {
|
|
155
|
+
enum: () => CS.UnityEngine.UIElements.Position,
|
|
156
|
+
values: {
|
|
157
|
+
"relative": "Relative",
|
|
158
|
+
"absolute": "Absolute",
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
overflow: {
|
|
162
|
+
enum: () => CS.UnityEngine.UIElements.Overflow,
|
|
163
|
+
values: {
|
|
164
|
+
"visible": "Visible",
|
|
165
|
+
"hidden": "Hidden",
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
display: {
|
|
169
|
+
enum: () => CS.UnityEngine.UIElements.DisplayStyle,
|
|
170
|
+
values: {
|
|
171
|
+
"flex": "Flex",
|
|
172
|
+
"none": "None",
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
visibility: {
|
|
176
|
+
enum: () => CS.UnityEngine.UIElements.Visibility,
|
|
177
|
+
values: {
|
|
178
|
+
"visible": "Visible",
|
|
179
|
+
"hidden": "Hidden",
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
whiteSpace: {
|
|
183
|
+
enum: () => CS.UnityEngine.UIElements.WhiteSpace,
|
|
184
|
+
values: {
|
|
185
|
+
"normal": "Normal",
|
|
186
|
+
"nowrap": "NoWrap",
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Parse an enum style value
|
|
193
|
+
* @param key - Style property name
|
|
194
|
+
* @param value - String value from React style (e.g., "row", "flex-start")
|
|
195
|
+
* @returns Unity enum value or null if not found
|
|
196
|
+
*/
|
|
197
|
+
function parseEnumValue(key: string, value: string): number | null {
|
|
198
|
+
const mapping = ENUM_MAPPINGS[key]
|
|
199
|
+
if (!mapping) return null
|
|
200
|
+
|
|
201
|
+
const unityEnumName = mapping.values[value]
|
|
202
|
+
if (!unityEnumName) return null
|
|
203
|
+
|
|
204
|
+
const enumType = mapping.enum()
|
|
205
|
+
return enumType[unityEnumName] ?? null
|
|
206
|
+
}
|
|
89
207
|
|
|
90
208
|
/**
|
|
91
209
|
* Parse a length value from various formats
|
|
@@ -130,13 +248,39 @@ export function parseLength(value: number | string): CSLength | number | null {
|
|
|
130
248
|
}
|
|
131
249
|
|
|
132
250
|
/**
|
|
133
|
-
*
|
|
251
|
+
* Clamp a number to [0, 1] range
|
|
134
252
|
*/
|
|
135
|
-
function
|
|
136
|
-
|
|
137
|
-
|
|
253
|
+
function clamp01(n: number): number {
|
|
254
|
+
return n < 0 ? 0 : n > 1 ? 1 : n
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Create a Unity Color with clamped values
|
|
259
|
+
*/
|
|
260
|
+
function createColor(r: number, g: number, b: number, a: number): CSColor {
|
|
261
|
+
return new CS.UnityEngine.Color(clamp01(r), clamp01(g), clamp01(b), clamp01(a))
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Parse hex color string into RGBA components
|
|
266
|
+
* Supports: #rgb, #rgba, #rrggbb, #rrggbbaa
|
|
267
|
+
*/
|
|
268
|
+
function parseHexColor(hex: string): CSColor | null {
|
|
269
|
+
const len = hex.length
|
|
270
|
+
const isShort = len === 3 || len === 4
|
|
271
|
+
const isLong = len === 6 || len === 8
|
|
272
|
+
if (!isShort && !isLong) return null
|
|
273
|
+
|
|
274
|
+
const step = isShort ? 1 : 2
|
|
275
|
+
const parse = (i: number): number => {
|
|
276
|
+
const slice = hex.slice(i * step, i * step + step)
|
|
277
|
+
const expanded = isShort ? slice + slice : slice
|
|
278
|
+
return parseInt(expanded, 16) / 255
|
|
138
279
|
}
|
|
139
|
-
|
|
280
|
+
|
|
281
|
+
const r = parse(0), g = parse(1), b = parse(2)
|
|
282
|
+
const a = (len === 4 || len === 8) ? parse(3) : 1
|
|
283
|
+
return createColor(r, g, b, a)
|
|
140
284
|
}
|
|
141
285
|
|
|
142
286
|
/**
|
|
@@ -155,75 +299,21 @@ export function parseColor(value: string): CSColor | null {
|
|
|
155
299
|
return new CS.UnityEngine.Color(r, g, b, a)
|
|
156
300
|
}
|
|
157
301
|
|
|
158
|
-
// Hex colors
|
|
302
|
+
// Hex colors
|
|
159
303
|
if (trimmed.startsWith("#")) {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
if (hex.length === 3) {
|
|
163
|
-
// #rgb
|
|
164
|
-
const r = parseHexComponent(hex[0])
|
|
165
|
-
const g = parseHexComponent(hex[1])
|
|
166
|
-
const b = parseHexComponent(hex[2])
|
|
167
|
-
return new CS.UnityEngine.Color(r, g, b, 1)
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
if (hex.length === 4) {
|
|
171
|
-
// #rgba
|
|
172
|
-
const r = parseHexComponent(hex[0])
|
|
173
|
-
const g = parseHexComponent(hex[1])
|
|
174
|
-
const b = parseHexComponent(hex[2])
|
|
175
|
-
const a = parseHexComponent(hex[3])
|
|
176
|
-
return new CS.UnityEngine.Color(r, g, b, a)
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
if (hex.length === 6) {
|
|
180
|
-
// #rrggbb
|
|
181
|
-
const r = parseHexComponent(hex.slice(0, 2))
|
|
182
|
-
const g = parseHexComponent(hex.slice(2, 4))
|
|
183
|
-
const b = parseHexComponent(hex.slice(4, 6))
|
|
184
|
-
return new CS.UnityEngine.Color(r, g, b, 1)
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
if (hex.length === 8) {
|
|
188
|
-
// #rrggbbaa
|
|
189
|
-
const r = parseHexComponent(hex.slice(0, 2))
|
|
190
|
-
const g = parseHexComponent(hex.slice(2, 4))
|
|
191
|
-
const b = parseHexComponent(hex.slice(4, 6))
|
|
192
|
-
const a = parseHexComponent(hex.slice(6, 8))
|
|
193
|
-
return new CS.UnityEngine.Color(r, g, b, a)
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
return null
|
|
304
|
+
return parseHexColor(trimmed.slice(1))
|
|
197
305
|
}
|
|
198
306
|
|
|
199
|
-
// rgb(r, g, b) or rgba(r, g, b, a)
|
|
200
|
-
const rgbMatch = trimmed.match(/^rgba?\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*([\d.]+))?\s*\)$/)
|
|
307
|
+
// rgb(r, g, b) or rgba(r, g, b, a) - supports both integer and percentage values
|
|
308
|
+
const rgbMatch = trimmed.match(/^rgba?\s*\(\s*([\d.]+)(%?)\s*,\s*([\d.]+)(%?)\s*,\s*([\d.]+)(%?)\s*(?:,\s*([\d.]+))?\s*\)$/)
|
|
201
309
|
if (rgbMatch) {
|
|
202
|
-
const
|
|
203
|
-
const
|
|
204
|
-
const
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
Math.min(1, Math.max(0, b)),
|
|
210
|
-
Math.min(1, Math.max(0, a))
|
|
211
|
-
)
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// rgb with percentages: rgb(100%, 0%, 0%)
|
|
215
|
-
const rgbPercentMatch = trimmed.match(/^rgba?\s*\(\s*([\d.]+)%\s*,\s*([\d.]+)%\s*,\s*([\d.]+)%\s*(?:,\s*([\d.]+))?\s*\)$/)
|
|
216
|
-
if (rgbPercentMatch) {
|
|
217
|
-
const r = parseFloat(rgbPercentMatch[1]) / 100
|
|
218
|
-
const g = parseFloat(rgbPercentMatch[2]) / 100
|
|
219
|
-
const b = parseFloat(rgbPercentMatch[3]) / 100
|
|
220
|
-
const a = rgbPercentMatch[4] !== undefined ? parseFloat(rgbPercentMatch[4]) : 1
|
|
221
|
-
return new CS.UnityEngine.Color(
|
|
222
|
-
Math.min(1, Math.max(0, r)),
|
|
223
|
-
Math.min(1, Math.max(0, g)),
|
|
224
|
-
Math.min(1, Math.max(0, b)),
|
|
225
|
-
Math.min(1, Math.max(0, a))
|
|
226
|
-
)
|
|
310
|
+
const isPercent = rgbMatch[2] === "%"
|
|
311
|
+
const divisor = isPercent ? 100 : 255
|
|
312
|
+
const r = parseFloat(rgbMatch[1]) / divisor
|
|
313
|
+
const g = parseFloat(rgbMatch[3]) / divisor
|
|
314
|
+
const b = parseFloat(rgbMatch[5]) / divisor
|
|
315
|
+
const a = rgbMatch[7] !== undefined ? parseFloat(rgbMatch[7]) : 1
|
|
316
|
+
return createColor(r, g, b, a)
|
|
227
317
|
}
|
|
228
318
|
|
|
229
319
|
return null
|
|
@@ -264,9 +354,11 @@ export function parseStyleValue(key: string, value: unknown): unknown {
|
|
|
264
354
|
return value
|
|
265
355
|
}
|
|
266
356
|
|
|
267
|
-
// Enum properties -
|
|
268
|
-
if (
|
|
269
|
-
|
|
357
|
+
// Enum properties - convert string to Unity enum value
|
|
358
|
+
if (key in ENUM_MAPPINGS && typeof value === "string") {
|
|
359
|
+
const parsed = parseEnumValue(key, value)
|
|
360
|
+
if (parsed !== null) return parsed
|
|
361
|
+
// Fall through if parsing failed
|
|
270
362
|
}
|
|
271
363
|
|
|
272
364
|
// Unknown property - pass through unchanged
|