onejs-react 0.1.0 → 0.1.1
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 +84 -0
- package/package.json +2 -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 +341 -158
- package/src/index.ts +35 -2
- package/src/renderer.ts +50 -23
- package/src/screen.tsx +1 -1
- package/src/style-parser.ts +42 -70
- package/src/types.ts +196 -5
package/README.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# onejs-react
|
|
2
|
+
|
|
3
|
+
React 19 reconciler for Unity's UI Toolkit.
|
|
4
|
+
|
|
5
|
+
## Files
|
|
6
|
+
|
|
7
|
+
| File | Purpose |
|
|
8
|
+
|------|---------|
|
|
9
|
+
| `src/host-config.ts` | React reconciler implementation (createInstance, commitUpdate, etc.) |
|
|
10
|
+
| `src/renderer.ts` | Entry point: `render(element, container)` |
|
|
11
|
+
| `src/components.tsx` | Component wrappers: View, Text, Label, Button, TextField, etc. |
|
|
12
|
+
| `src/screen.tsx` | Responsive design: ScreenProvider, useBreakpoint, useScreenSize, useResponsive |
|
|
13
|
+
| `src/types.ts` | TypeScript type definitions |
|
|
14
|
+
| `src/index.ts` | Package exports |
|
|
15
|
+
|
|
16
|
+
## Components
|
|
17
|
+
|
|
18
|
+
| Component | UI Toolkit Element | Description |
|
|
19
|
+
|-----------|-------------------|-------------|
|
|
20
|
+
| `View` | VisualElement | Container element |
|
|
21
|
+
| `Text` | TextElement | Primary text display |
|
|
22
|
+
| `Label` | Label | Form labels, semantic labeling |
|
|
23
|
+
| `Button` | Button | Interactive button |
|
|
24
|
+
| `TextField` | TextField | Text input |
|
|
25
|
+
| `Toggle` | Toggle | Checkbox/toggle |
|
|
26
|
+
| `Slider` | Slider | Numeric slider |
|
|
27
|
+
| `ScrollView` | ScrollView | Scrollable container |
|
|
28
|
+
| `Image` | Image | Image display |
|
|
29
|
+
| `ListView` | ListView | Virtualized list |
|
|
30
|
+
|
|
31
|
+
**Raw text in JSX** (e.g., `<View>Hello</View>`) creates a `TextElement`, providing semantic distinction from explicit `<Label>` components.
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
import { render, View, Text, Label, Button } from 'onejs-react';
|
|
37
|
+
|
|
38
|
+
function App() {
|
|
39
|
+
return (
|
|
40
|
+
<View style={{ padding: 20 }}>
|
|
41
|
+
<Text text="Welcome!" style={{ fontSize: 24 }} />
|
|
42
|
+
<Button text="Click me" onClick={() => console.log('clicked')} />
|
|
43
|
+
<View>Raw text also works</View>
|
|
44
|
+
</View>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
render(<App />, __root);
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Key Concepts
|
|
52
|
+
|
|
53
|
+
- **Element types**: Use `ojs-` prefix internally (e.g., `ojs-view`, `ojs-button`) to avoid conflicts with HTML types
|
|
54
|
+
- **Style shorthands**: `padding`/`margin` are expanded to individual properties (UI Toolkit requirement)
|
|
55
|
+
- **Style cleanup**: When props change, removed style properties are cleared (not just new ones applied)
|
|
56
|
+
- **className updates**: Selective add/remove of classes (not full clear + reapply)
|
|
57
|
+
- **Event handlers**: Registered via `__eventAPI` from QuickJSBootstrap.js
|
|
58
|
+
- **Instance structure**: `{ element, type, props, eventHandlers: Map, appliedStyleKeys: Set }`
|
|
59
|
+
|
|
60
|
+
## Build & Test
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npm run typecheck # TypeScript check (no build output - consumed directly by App)
|
|
64
|
+
npm test # Run test suite
|
|
65
|
+
npm run test:watch # Run tests in watch mode
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Testing
|
|
69
|
+
|
|
70
|
+
Test suite uses Vitest with mocked Unity CS globals. Tests are in `src/__tests__/`:
|
|
71
|
+
|
|
72
|
+
| File | Coverage |
|
|
73
|
+
|------|----------|
|
|
74
|
+
| `host-config.test.ts` | Instance creation, style/className management, events, children |
|
|
75
|
+
| `renderer.test.tsx` | Integration tests: render(), unmount(), React state, effects |
|
|
76
|
+
| `components.test.tsx` | Component wrappers, prop passing, event mapping |
|
|
77
|
+
| `mocks.ts` | Mock implementations of Unity UI Toolkit classes |
|
|
78
|
+
| `setup.ts` | Global test setup for CS, __eventAPI |
|
|
79
|
+
|
|
80
|
+
## Dependencies
|
|
81
|
+
|
|
82
|
+
- `react-reconciler@0.31.x` (React 19 compatible)
|
|
83
|
+
- `vitest` (dev) - Test runner
|
|
84
|
+
- Peer: `react@18.x || 19.x`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "onejs-react",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "React 19 renderer for OneJS (Unity UI Toolkit)",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "src/index.ts",
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"react-reconciler": "^0.31.0"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
|
+
"@types/node": "^25.0.3",
|
|
36
37
|
"@types/react": "^19.0.0",
|
|
37
38
|
"@types/react-reconciler": "^0.28.9",
|
|
38
39
|
"react": "^19.0.0",
|
|
@@ -12,6 +12,7 @@ import React from 'react';
|
|
|
12
12
|
import { render } from '../renderer';
|
|
13
13
|
import {
|
|
14
14
|
View,
|
|
15
|
+
Text,
|
|
15
16
|
Label,
|
|
16
17
|
Button,
|
|
17
18
|
TextField,
|
|
@@ -108,6 +109,41 @@ describe('components', () => {
|
|
|
108
109
|
});
|
|
109
110
|
});
|
|
110
111
|
|
|
112
|
+
describe('Text', () => {
|
|
113
|
+
it('renders as TextElement', async () => {
|
|
114
|
+
const container = createMockContainer();
|
|
115
|
+
render(<Text text="Hello" />, container as any);
|
|
116
|
+
await flushMicrotasks();
|
|
117
|
+
|
|
118
|
+
expect(container.children[0].__csType).toBe('UnityEngine.UIElements.TextElement');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('sets text property', async () => {
|
|
122
|
+
const container = createMockContainer();
|
|
123
|
+
render(<Text text="Hello World" />, container as any);
|
|
124
|
+
await flushMicrotasks();
|
|
125
|
+
|
|
126
|
+
const el = container.children[0] as MockVisualElement;
|
|
127
|
+
expect(el.text).toBe('Hello World');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('applies styles', async () => {
|
|
131
|
+
const container = createMockContainer();
|
|
132
|
+
render(
|
|
133
|
+
<Text
|
|
134
|
+
text="Styled"
|
|
135
|
+
style={{ fontSize: 24, color: 'white' }}
|
|
136
|
+
/>,
|
|
137
|
+
container as any
|
|
138
|
+
);
|
|
139
|
+
await flushMicrotasks();
|
|
140
|
+
|
|
141
|
+
const el = container.children[0] as MockVisualElement;
|
|
142
|
+
expect(getStyleValue(el.style.fontSize)).toBe(24);
|
|
143
|
+
expect(el.style.color).toBeInstanceOf(MockColor);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
111
147
|
describe('Label', () => {
|
|
112
148
|
it('renders as Label element', async () => {
|
|
113
149
|
const container = createMockContainer();
|