@dcl/react-ecs 7.7.7-13655406288.commit-7b2a671 → 7.7.7
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 +131 -1
- package/dist/reconciler/index.js +15 -28
- package/dist/system.d.ts +5 -0
- package/dist/system.js +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1 +1,131 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @dcl/react-ecs
|
|
2
|
+
|
|
3
|
+
React bindings for Decentraland's Entity Component System (ECS), providing a declarative way to build UIs in Decentraland scenes using React's component model and JSX syntax.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Flexbox-based Layout**: Implements a subset of CSS Flexbox for powerful and intuitive UI layouts
|
|
8
|
+
- **React Components**: Familiar React-like component API for building UIs
|
|
9
|
+
- **Type Safety**: Full TypeScript support with proper type definitions
|
|
10
|
+
- **Event Handling**: Support for mouse events and user interactions
|
|
11
|
+
- **Performance**: Optimized reconciliation for minimal runtime overhead
|
|
12
|
+
- **Theme Support**: Built-in light and dark theme system with context-based switching
|
|
13
|
+
|
|
14
|
+
## Component Guidelines
|
|
15
|
+
|
|
16
|
+
All components in @dcl/react-ecs must:
|
|
17
|
+
|
|
18
|
+
- Return JSX.Elements for consistency and composability
|
|
19
|
+
- Support theme integration through context
|
|
20
|
+
- Be reusable and composable with other components
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install @dcl/react-ecs
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Basic Usage
|
|
29
|
+
|
|
30
|
+
```tsx
|
|
31
|
+
import { ReactEcs, UiEntity, Label, Button } from '@dcl/react-ecs'
|
|
32
|
+
|
|
33
|
+
export function MyUI() {
|
|
34
|
+
return (
|
|
35
|
+
<UiEntity
|
|
36
|
+
uiTransform={{
|
|
37
|
+
width: 300,
|
|
38
|
+
height: 100,
|
|
39
|
+
display: 'flex',
|
|
40
|
+
justifyContent: 'center',
|
|
41
|
+
alignItems: 'center'
|
|
42
|
+
}}
|
|
43
|
+
>
|
|
44
|
+
<Label value="Hello Decentraland!" />
|
|
45
|
+
<Button value="Click Me!" onMouseDown={() => console.log('clicked!')} />
|
|
46
|
+
</UiEntity>
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Theme System
|
|
52
|
+
|
|
53
|
+
The package includes a theme system for consistent UI styling:
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
import { ReactEcs, ThemeProvider, Button } from '@dcl/react-ecs'
|
|
57
|
+
|
|
58
|
+
// Wrap your UI with ThemeProvider
|
|
59
|
+
export function MyUI() {
|
|
60
|
+
return (
|
|
61
|
+
<ThemeProvider>
|
|
62
|
+
<Button value="Theme-Aware Button" />
|
|
63
|
+
</ThemeProvider>
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Use themes in custom components
|
|
68
|
+
function CustomComponent() {
|
|
69
|
+
const { theme, toggleTheme } = React.useContext(ThemeContext)
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<UiEntity
|
|
73
|
+
uiBackground={{
|
|
74
|
+
color: theme === 'light' ? '#FFFFFF' : '#000000'
|
|
75
|
+
}}
|
|
76
|
+
onMouseDown={toggleTheme}
|
|
77
|
+
/>
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Components
|
|
83
|
+
|
|
84
|
+
### Core Components
|
|
85
|
+
|
|
86
|
+
- **UiEntity**: Base component for UI elements
|
|
87
|
+
- **Label**: Text display component
|
|
88
|
+
- **Button**: Interactive button component
|
|
89
|
+
- **Input**: Text input field
|
|
90
|
+
- **Dropdown**: Selection dropdown menu
|
|
91
|
+
|
|
92
|
+
### Layout System
|
|
93
|
+
|
|
94
|
+
The layout system is based on Flexbox and supports the following properties:
|
|
95
|
+
|
|
96
|
+
- `display: 'flex'`
|
|
97
|
+
- `flexDirection: 'row' | 'column'`
|
|
98
|
+
- `justifyContent: 'flex-start' | 'center' | 'flex-end' | 'space-between' | 'space-around'`
|
|
99
|
+
- `alignItems: 'flex-start' | 'center' | 'flex-end' | 'stretch'`
|
|
100
|
+
- `width`, `height`
|
|
101
|
+
- `margin`, `padding`
|
|
102
|
+
- `positionType: 'absolute' | 'relative'`
|
|
103
|
+
|
|
104
|
+
## Event Handling
|
|
105
|
+
|
|
106
|
+
Components support the following mouse events:
|
|
107
|
+
|
|
108
|
+
```tsx
|
|
109
|
+
<Button onMouseDown={() => {}} onMouseUp={() => {}} onMouseEnter={() => {}} onMouseLeave={() => {}} />
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Technical Details
|
|
113
|
+
|
|
114
|
+
The package implements a custom React reconciler that bridges React's component model with Decentraland's ECS. It:
|
|
115
|
+
|
|
116
|
+
1. Translates JSX into ECS entities and components
|
|
117
|
+
2. Manages component lifecycle and updates
|
|
118
|
+
3. Handles event delegation and bubbling
|
|
119
|
+
4. Provides a performant update mechanism
|
|
120
|
+
|
|
121
|
+
## Related Documentation
|
|
122
|
+
|
|
123
|
+
For more details about the UI system architecture and design decisions:
|
|
124
|
+
|
|
125
|
+
- [ADR-124: Implementing Flexbox-based UI](https://adr.decentraland.org/adr/ADR-124)
|
|
126
|
+
- [ADR-125: User Interface Components](https://adr.decentraland.org/adr/ADR-125)
|
|
127
|
+
- [ADR-237: SDK 7 Custom UI Components](https://adr.decentraland.org/adr/ADR-237)
|
|
128
|
+
|
|
129
|
+
## License
|
|
130
|
+
|
|
131
|
+
Apache 2.0
|
package/dist/reconciler/index.js
CHANGED
|
@@ -124,6 +124,7 @@ export function createReconciler(engine, pointerEvents) {
|
|
|
124
124
|
}
|
|
125
125
|
function removeChildEntity(instance) {
|
|
126
126
|
changeEvents.delete(instance.entity);
|
|
127
|
+
clickEvents.delete(instance.entity);
|
|
127
128
|
engine.removeEntity(instance.entity);
|
|
128
129
|
for (const child of instance._child) {
|
|
129
130
|
removeChildEntity(child);
|
|
@@ -170,13 +171,23 @@ export function createReconciler(engine, pointerEvents) {
|
|
|
170
171
|
removeChildEntity(child);
|
|
171
172
|
}
|
|
172
173
|
function updateOnChange(entity, componentId, state) {
|
|
174
|
+
const hasEvent = changeEvents.has(entity);
|
|
173
175
|
const event = changeEvents.get(entity) || changeEvents.set(entity, new Map()).get(entity);
|
|
174
|
-
const oldState = event.get(componentId);
|
|
175
176
|
const onChangeCallback = state?.onChangeCallback;
|
|
176
177
|
const onSubmitCallback = state?.onSubmitCallback;
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
178
|
+
event.set(componentId, { onChangeCallback, onSubmitCallback });
|
|
179
|
+
// Create onChange callback if its the first callback event for this entity
|
|
180
|
+
if (!hasEvent) {
|
|
181
|
+
const resultComponentId = componentId === UiDropdown.componentId ? UiDropdownResult.componentId : UiInputResult.componentId;
|
|
182
|
+
engine.getComponent(resultComponentId).onChange(entity, (value) => {
|
|
183
|
+
if (value?.isSubmit) {
|
|
184
|
+
const onSubmit = changeEvents.get(entity)?.get(componentId)?.onSubmitCallback;
|
|
185
|
+
onSubmit && onSubmit(value?.value);
|
|
186
|
+
}
|
|
187
|
+
const onChange = changeEvents.get(entity)?.get(componentId)?.onChangeCallback;
|
|
188
|
+
onChange && onChange(value?.value);
|
|
189
|
+
});
|
|
190
|
+
}
|
|
180
191
|
}
|
|
181
192
|
const hostConfig = {
|
|
182
193
|
...noopConfig,
|
|
@@ -251,32 +262,8 @@ export function createReconciler(engine, pointerEvents) {
|
|
|
251
262
|
const root = reconciler.createContainer({}, 0, null, false, null, '',
|
|
252
263
|
/* istanbul ignore next */
|
|
253
264
|
function () { }, null);
|
|
254
|
-
// Maybe this could be something similar to Input system, but since we
|
|
255
|
-
// are going to use this only here, i prefer to scope it here.
|
|
256
|
-
function handleOnChange(componentId, resultComponent) {
|
|
257
|
-
for (const [entity, Result] of engine.getEntitiesWith(resultComponent)) {
|
|
258
|
-
const entityState = changeEvents.get(entity)?.get(componentId);
|
|
259
|
-
const isSubmit = !!Result.isSubmit;
|
|
260
|
-
if (entityState?.onChangeCallback && Result.value !== entityState.value) {
|
|
261
|
-
entityState.onChangeCallback(Result.value);
|
|
262
|
-
}
|
|
263
|
-
if (entityState?.onSubmitCallback && isSubmit && !entityState.isSubmit) {
|
|
264
|
-
entityState.onSubmitCallback(Result.value);
|
|
265
|
-
}
|
|
266
|
-
updateOnChange(entity, componentId, {
|
|
267
|
-
onChangeCallback: entityState?.onChangeCallback,
|
|
268
|
-
onSubmitCallback: entityState?.onSubmitCallback,
|
|
269
|
-
value: Result.value,
|
|
270
|
-
isSubmit
|
|
271
|
-
});
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
265
|
return {
|
|
275
266
|
update: function (component) {
|
|
276
|
-
if (changeEvents.size) {
|
|
277
|
-
handleOnChange(UiInput.componentId, UiInputResult);
|
|
278
|
-
handleOnChange(UiDropdown.componentId, UiDropdownResult);
|
|
279
|
-
}
|
|
280
267
|
return reconciler.updateContainer(component, root, null);
|
|
281
268
|
},
|
|
282
269
|
getEntities: () => Array.from(entities)
|
package/dist/system.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { IEngine, PointerEventsSystem } from '@dcl/ecs';
|
|
1
2
|
import type { ReactEcs } from './react-ecs';
|
|
2
3
|
/**
|
|
3
4
|
* @public
|
|
@@ -10,3 +11,7 @@ export interface ReactBasedUiSystem {
|
|
|
10
11
|
destroy(): void;
|
|
11
12
|
setUiRenderer(ui: UiComponent): void;
|
|
12
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* @public
|
|
16
|
+
*/
|
|
17
|
+
export declare function createReactBasedUiSystem(engine: IEngine, pointerSystem: PointerEventsSystem): ReactBasedUiSystem;
|
package/dist/system.js
CHANGED
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dcl/react-ecs",
|
|
3
3
|
"description": "Decentraland ECS",
|
|
4
|
-
"version": "7.7.7
|
|
4
|
+
"version": "7.7.7",
|
|
5
5
|
"author": "DCL",
|
|
6
6
|
"bugs": "https://github.com/decentraland/js-sdk-toolchain/issues",
|
|
7
7
|
"dependencies": {
|
|
8
|
-
"@dcl/ecs": "7.7.7
|
|
8
|
+
"@dcl/ecs": "7.7.7",
|
|
9
9
|
"react": "^18.2.0",
|
|
10
10
|
"react-reconciler": "^0.29.0"
|
|
11
11
|
},
|
|
@@ -40,5 +40,5 @@
|
|
|
40
40
|
"tsconfig": "./tsconfig.json"
|
|
41
41
|
},
|
|
42
42
|
"types": "./dist/index.d.ts",
|
|
43
|
-
"commit": "
|
|
43
|
+
"commit": "b532f7c70b2b1cbd4ea7e4a67831cc23a81d2459"
|
|
44
44
|
}
|