@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 CHANGED
@@ -1 +1,131 @@
1
- # React ECS
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
@@ -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
- const value = state?.value ?? oldState?.value;
178
- const isSubmit = state?.isSubmit ?? oldState?.isSubmit;
179
- event.set(componentId, { onChangeCallback, onSubmitCallback, value, isSubmit });
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
@@ -1,6 +1,6 @@
1
1
  import { createReconciler } from './reconciler';
2
2
  /**
3
- * @internal
3
+ * @public
4
4
  */
5
5
  export function createReactBasedUiSystem(engine, pointerSystem) {
6
6
  const renderer = createReconciler(engine, pointerSystem);
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-13655406288.commit-7b2a671",
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-13655406288.commit-7b2a671",
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": "7b2a6716919850a45b2f84abc9d05a1500ad6af3"
43
+ "commit": "b532f7c70b2b1cbd4ea7e4a67831cc23a81d2459"
44
44
  }