veles 0.0.2 → 0.0.3
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 +128 -151
- package/dist/{chunk-EVX3ZDYT.js → chunk-V3EV7UG6.js} +27 -16
- package/dist/index.cjs +53 -41
- package/dist/index.d.cts +7 -7
- package/dist/index.d.ts +7 -7
- package/dist/index.js +29 -27
- package/dist/{jsx-runtime-vDysOz5d.d.cts → jsx-runtime-C2XLvZ55.d.cts} +9 -2
- package/dist/{jsx-runtime-vDysOz5d.d.ts → jsx-runtime-C2XLvZ55.d.ts} +9 -2
- package/dist/jsx-runtime.cjs +12 -16
- package/dist/jsx-runtime.d.cts +1 -1
- package/dist/jsx-runtime.d.ts +1 -1
- package/dist/jsx-runtime.js +1 -2
- package/package.json +1 -1
- package/dist/chunk-EVX3ZDYT.js.map +0 -1
- package/dist/index.cjs.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/jsx-runtime.cjs.map +0 -1
- package/dist/jsx-runtime.js.map +0 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
[](https://bundlephobia.com/result?p=veles)
|
|
5
5
|
[](https://www.npmjs.com/package/veles)
|
|
6
6
|
|
|
7
|
-
> The library is in very early stages and
|
|
7
|
+
> The library is in very early stages and some features, like proper TypeScript support, are not fully implemented yet
|
|
8
8
|
|
|
9
9
|
`Veles` is a component-based performance-focused UI library. The main goal of this library is to provide a composable way to build highly interactive interfaces, which should be performant out of the box, as long as you follow the recommendations.
|
|
10
10
|
|
|
@@ -24,30 +24,26 @@ Attach Veles tree to a regular DOM Node.
|
|
|
24
24
|
> As of right now, this method will wrap the component's HTML into one additional `div`. This will probably go away in the future, but for now it simplifies some things significantly.
|
|
25
25
|
|
|
26
26
|
```js
|
|
27
|
-
import {
|
|
27
|
+
import { attachComponent } from "veles";
|
|
28
|
+
|
|
29
|
+
const App () => <div>App</div>
|
|
28
30
|
|
|
29
31
|
const appContainer = document.getElementById("app");
|
|
30
|
-
attachComponent({ htmlElement: appContainer, component:
|
|
32
|
+
attachComponent({ htmlElement: appContainer, component: <App /> });
|
|
31
33
|
```
|
|
32
34
|
|
|
33
|
-
###
|
|
34
|
-
|
|
35
|
-
Create Veles tree. Accepts strings for regular valid HTML elements (like `div`, `span`, etc) and functions which are expected to return another Veles tree from `createElement`.
|
|
35
|
+
### JSX support
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
> JSX should be almost fully supported as long as you specify `Veles.createElement` pragma
|
|
39
|
-
|
|
40
|
-
```js
|
|
41
|
-
import { createElement } from "veles";
|
|
37
|
+
Veles supports JSX transformation, so as long as you specify `importSource: "veles"` (this is for Babel, the other JS transpilers should have similar options) it will work as expected.
|
|
42
38
|
|
|
39
|
+
```jsx
|
|
43
40
|
function App() {
|
|
44
|
-
return
|
|
45
|
-
class
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
});
|
|
41
|
+
return (
|
|
42
|
+
<div class="app-container">
|
|
43
|
+
<h1>Veles App</h1>
|
|
44
|
+
<p>Random description</p>
|
|
45
|
+
</div>
|
|
46
|
+
);
|
|
51
47
|
}
|
|
52
48
|
```
|
|
53
49
|
|
|
@@ -59,29 +55,27 @@ function App() {
|
|
|
59
55
|
|
|
60
56
|
The simplest way to react to state changes in the UI is to use `useValue` method from the state object. Let's build a simple counter to demonstrate:
|
|
61
57
|
|
|
62
|
-
```
|
|
63
|
-
import {
|
|
58
|
+
```jsx
|
|
59
|
+
import { createState } from "veles";
|
|
64
60
|
|
|
65
61
|
function Counter() {
|
|
66
62
|
const counterState = createState(0);
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
63
|
+
const increment = () =>
|
|
64
|
+
counterState.setValue((currentValue) => currentValue + 1);
|
|
65
|
+
const decrement = () =>
|
|
66
|
+
counterState.setValue((currentValue) => currentValue - 1);
|
|
67
|
+
return (
|
|
68
|
+
<div>
|
|
69
|
+
<h1>Counter</h1>
|
|
70
|
+
<div>
|
|
71
|
+
{counterState.useValue(
|
|
72
72
|
(counterValue) => `counter value is: ${counterValue}`
|
|
73
|
-
)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
);
|
|
80
|
-
},
|
|
81
|
-
children: "+",
|
|
82
|
-
}),
|
|
83
|
-
],
|
|
84
|
-
});
|
|
73
|
+
)}
|
|
74
|
+
</div>
|
|
75
|
+
<button onClick={increment}>+</button>
|
|
76
|
+
<button onClick={decrement}>-</button>
|
|
77
|
+
</div>
|
|
78
|
+
);
|
|
85
79
|
}
|
|
86
80
|
```
|
|
87
81
|
|
|
@@ -89,8 +83,8 @@ function Counter() {
|
|
|
89
83
|
|
|
90
84
|
If you have an object in your store, even atomic updates will be wasteful. Let's say you have an object with several fields, but you are only interested in the `title` property. If you use `useValue`, it will do unnecessary work. To help with that, there is a `useValueSelector` state method, which accepts a selector function as the first parameter. Here is an example:
|
|
91
85
|
|
|
92
|
-
```
|
|
93
|
-
import {
|
|
86
|
+
```jsx
|
|
87
|
+
import { createState } from "veles";
|
|
94
88
|
|
|
95
89
|
function App() {
|
|
96
90
|
const taskState = createState({
|
|
@@ -98,17 +92,17 @@ function App() {
|
|
|
98
92
|
title: "title",
|
|
99
93
|
description: "long description",
|
|
100
94
|
});
|
|
101
|
-
return
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
95
|
+
return (
|
|
96
|
+
<div>
|
|
97
|
+
<h1>App</h1>
|
|
98
|
+
<div>
|
|
99
|
+
{taskState.useSelectorValue(
|
|
106
100
|
(task) => task.title,
|
|
107
101
|
(title) => `task title: ${title}`
|
|
108
|
-
)
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
102
|
+
)}
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
);
|
|
112
106
|
}
|
|
113
107
|
```
|
|
114
108
|
|
|
@@ -123,8 +117,8 @@ Lists performance is one of the cornerstones of this library, and to help with t
|
|
|
123
117
|
|
|
124
118
|
Let's build a simple list component:
|
|
125
119
|
|
|
126
|
-
```
|
|
127
|
-
import { createState
|
|
120
|
+
```jsx
|
|
121
|
+
import { createState } from "veles";
|
|
128
122
|
|
|
129
123
|
function List() {
|
|
130
124
|
const listState = createState([
|
|
@@ -133,23 +127,18 @@ function List() {
|
|
|
133
127
|
{ id: 3, name: "third task" },
|
|
134
128
|
]);
|
|
135
129
|
|
|
136
|
-
return
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
listState.useValueIterator<{ id: number; name: string }>(
|
|
130
|
+
return <div>
|
|
131
|
+
<h1>List</h1>
|
|
132
|
+
{listState.useValueIterator<{ id: number; name: string }>(
|
|
140
133
|
{ key: "id" },
|
|
141
|
-
({ elementState }) =>
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
}
|
|
150
|
-
),
|
|
151
|
-
],
|
|
152
|
-
});
|
|
134
|
+
({ elementState }) => <div>
|
|
135
|
+
{elementState.useValueSelector(
|
|
136
|
+
(element) => element.name,
|
|
137
|
+
(name) => <div>{name}</div>
|
|
138
|
+
)}
|
|
139
|
+
</div>
|
|
140
|
+
)}
|
|
141
|
+
</div>
|
|
153
142
|
}
|
|
154
143
|
```
|
|
155
144
|
|
|
@@ -157,41 +146,38 @@ function List() {
|
|
|
157
146
|
|
|
158
147
|
In order to avoid unnecessary re-renders when you to change properties on a wrapper element/component, there is a special state method `useAttribute`. It allows to recalculate value for an attribute every time the state changes, but instead of re-rendering, it calls the `htmlNode.setAttribute` method. Here is an example:
|
|
159
148
|
|
|
160
|
-
```
|
|
161
|
-
import {
|
|
149
|
+
```jsx
|
|
150
|
+
import { createState } from "veles";
|
|
162
151
|
|
|
163
152
|
function Counter() {
|
|
164
153
|
const counterState = createState(0);
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
)
|
|
171
|
-
|
|
172
|
-
onClick
|
|
173
|
-
|
|
174
|
-
(currentCounterValue) => currentCounterValue + 1
|
|
175
|
-
);
|
|
176
|
-
},
|
|
177
|
-
style: counterState.useAttribute(
|
|
154
|
+
const increment = () =>
|
|
155
|
+
counterState.setValue((currentValue) => currentValue + 1);
|
|
156
|
+
return (
|
|
157
|
+
<div>
|
|
158
|
+
<h1>Counter</h1>
|
|
159
|
+
<div>{counterState.useValue((value) => `counter value is ${value}`)}</div>
|
|
160
|
+
<button
|
|
161
|
+
onClick={increment}
|
|
162
|
+
style={counterState.useAttribute(
|
|
178
163
|
(currentValue) => `width: ${50 + currentValue}px;`
|
|
179
|
-
)
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
164
|
+
)}
|
|
165
|
+
>
|
|
166
|
+
+
|
|
167
|
+
</button>
|
|
168
|
+
</div>
|
|
169
|
+
);
|
|
184
170
|
}
|
|
185
171
|
```
|
|
186
172
|
|
|
187
|
-
You can see that we dynamically change the width of the button with every press.
|
|
173
|
+
You can see that we dynamically change the width of the button with every press. This will be the only change, the rest of the application will not be re-rendered or any code will not be executed. You still need to be mindful what are you changing, as with some CSS changes you can force reflows/repaints and that can still cause some performance issues.
|
|
188
174
|
|
|
189
175
|
### createState and subscribing to updates
|
|
190
176
|
|
|
191
177
|
What if you don't want to render anything when the value changes, but you want to call your code? The state provides `trackValue` method, which does exactly that. Here is an example:
|
|
192
178
|
|
|
193
|
-
```
|
|
194
|
-
import {
|
|
179
|
+
```jsx
|
|
180
|
+
import { createState } from "veles";
|
|
195
181
|
|
|
196
182
|
function Counter() {
|
|
197
183
|
const counterState = createState(0);
|
|
@@ -199,23 +185,23 @@ function Counter() {
|
|
|
199
185
|
counterState.trackValue((counterValue) => {
|
|
200
186
|
console.log(`new counter value is ${counterValue}`);
|
|
201
187
|
});
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
|
|
188
|
+
const increment = () =>
|
|
189
|
+
counterState.setValue((currentValue) => currentValue + 1);
|
|
190
|
+
const decrement = () =>
|
|
191
|
+
counterState.setValue((currentValue) => currentValue - 1);
|
|
192
|
+
|
|
193
|
+
return (
|
|
194
|
+
<div>
|
|
195
|
+
<h1>Counter</h1>
|
|
196
|
+
<div>
|
|
197
|
+
{counterState.useValue(
|
|
198
|
+
(counterValue) => `counter value is: ${counterValue}`
|
|
199
|
+
)}
|
|
200
|
+
</div>
|
|
201
|
+
<button onClick={increment}>+</button>
|
|
202
|
+
<button onClick={decrement}>-</button>
|
|
203
|
+
</div>
|
|
204
|
+
);
|
|
219
205
|
}
|
|
220
206
|
```
|
|
221
207
|
|
|
@@ -229,8 +215,8 @@ In case you don't want to subscribe to the whole state, you have 2 options. You
|
|
|
229
215
|
|
|
230
216
|
Since `createState` is the only way to add dynamic behaviour to the application, sooner or later you'll need to build UI which depends on several states. To do so, you can use `combineState` function which accepts any amount of state objects, and returns an array with all combined values in it.
|
|
231
217
|
|
|
232
|
-
```
|
|
233
|
-
import {
|
|
218
|
+
```jsx
|
|
219
|
+
import { createState } from "veles";
|
|
234
220
|
|
|
235
221
|
function Counter() {
|
|
236
222
|
const firstcounterState = createState(0);
|
|
@@ -239,44 +225,37 @@ function Counter() {
|
|
|
239
225
|
firstcounterState,
|
|
240
226
|
secondCounterState
|
|
241
227
|
);
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
)
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
(currentCounterValue) => currentCounterValue + 1
|
|
274
|
-
);
|
|
275
|
-
},
|
|
276
|
-
children: "add to the second counter",
|
|
277
|
-
}),
|
|
278
|
-
],
|
|
279
|
-
});
|
|
228
|
+
const incrementFirstCounter = () =>
|
|
229
|
+
firstcounterState.setValue(
|
|
230
|
+
(currentCounterValue) => currentCounterValue + 1
|
|
231
|
+
);
|
|
232
|
+
const incrementSecondCounter = () =>
|
|
233
|
+
secondCounterState.setValue(
|
|
234
|
+
(currentCounterValue) => currentCounterValue + 1
|
|
235
|
+
);
|
|
236
|
+
return (
|
|
237
|
+
<div>
|
|
238
|
+
<h1>Counters</h1>
|
|
239
|
+
<div>
|
|
240
|
+
{firstcounterState.useValue(
|
|
241
|
+
(value) => `first counter value is: ${value}`
|
|
242
|
+
)}
|
|
243
|
+
</div>
|
|
244
|
+
<div>
|
|
245
|
+
{secondCounterState.useValue(
|
|
246
|
+
(value) => `second counter value is: ${value}`
|
|
247
|
+
)}
|
|
248
|
+
</div>
|
|
249
|
+
<div>
|
|
250
|
+
{combinedCounterState.useValueSelector(
|
|
251
|
+
([firstValue, secondValue]) => firstValue + secondValue,
|
|
252
|
+
(counterValue) => `combined counter value is: ${counterValue}`
|
|
253
|
+
)}
|
|
254
|
+
</div>
|
|
255
|
+
<button onClick={incrementFirstCounter}>Increment first counter</button>
|
|
256
|
+
<button onClick={incrementSecondCounter}>Increment second counter</button>
|
|
257
|
+
</div>
|
|
258
|
+
);
|
|
280
259
|
}
|
|
281
260
|
```
|
|
282
261
|
|
|
@@ -287,8 +266,8 @@ Right now there are `onMount` and `onUnmount` lifecycle hooks. In your component
|
|
|
287
266
|
> [!NOTE]
|
|
288
267
|
> You can only use them during the original initialization of the component. If you want to add some callbacks later, use the same version passed as a second argument to your component
|
|
289
268
|
|
|
290
|
-
```
|
|
291
|
-
import {
|
|
269
|
+
```jsx
|
|
270
|
+
import { onMount, onUnmount } from "veles";
|
|
292
271
|
|
|
293
272
|
function App(_props, componentAPI) {
|
|
294
273
|
// could be used as `componentAPI.onMount()`
|
|
@@ -299,8 +278,6 @@ function App(_props, componentAPI) {
|
|
|
299
278
|
onUnmount(() => {
|
|
300
279
|
console.log("called when the component unmounts");
|
|
301
280
|
});
|
|
302
|
-
return
|
|
303
|
-
children: "Application",
|
|
304
|
-
});
|
|
281
|
+
return <div>Application<div>
|
|
305
282
|
}
|
|
306
283
|
```
|
|
@@ -47,6 +47,20 @@ function getComponentVelesNode(component) {
|
|
|
47
47
|
}
|
|
48
48
|
return { velesElementNode: childNode, componentsTree };
|
|
49
49
|
}
|
|
50
|
+
function callMountHandlers(component) {
|
|
51
|
+
if ("velesStringElement" in component) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if ("velesComponent" in component) {
|
|
55
|
+
component._privateMethods._callMountHandlers();
|
|
56
|
+
callMountHandlers(component.tree);
|
|
57
|
+
}
|
|
58
|
+
if ("velesNode" in component) {
|
|
59
|
+
component.childComponents.forEach(
|
|
60
|
+
(childComponent) => callMountHandlers(childComponent)
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
50
64
|
function identity(value1, value2) {
|
|
51
65
|
return value1 === value2;
|
|
52
66
|
}
|
|
@@ -66,6 +80,9 @@ function parseChildren({
|
|
|
66
80
|
if (typeof childComponent === "string") {
|
|
67
81
|
const text = document.createTextNode(childComponent);
|
|
68
82
|
htmlElement.append(text);
|
|
83
|
+
} else if (typeof childComponent === "number") {
|
|
84
|
+
const text = document.createTextNode(String(childComponent));
|
|
85
|
+
htmlElement.append(text);
|
|
69
86
|
} else if (typeof childComponent === "object" && childComponent && "velesNode" in childComponent && (childComponent == null ? void 0 : childComponent.velesNode)) {
|
|
70
87
|
if (childComponent.phantom) {
|
|
71
88
|
childComponent.childComponents.forEach((childComponentofPhantom) => {
|
|
@@ -73,14 +90,13 @@ function parseChildren({
|
|
|
73
90
|
htmlElement.append(childComponentofPhantom.html);
|
|
74
91
|
childComponentofPhantom.parentVelesElement = velesNode;
|
|
75
92
|
} else {
|
|
76
|
-
const {
|
|
93
|
+
const { velesElementNode } = getComponentVelesNode(
|
|
94
|
+
childComponentofPhantom
|
|
95
|
+
);
|
|
77
96
|
if (!velesElementNode) {
|
|
78
97
|
console.error("can't find HTML tree in a component chain");
|
|
79
98
|
} else {
|
|
80
99
|
htmlElement.append(velesElementNode.html);
|
|
81
|
-
componentsTree.forEach((component) => {
|
|
82
|
-
component._privateMethods._callMountHandlers();
|
|
83
|
-
});
|
|
84
100
|
velesElementNode.parentVelesElement = velesNode;
|
|
85
101
|
}
|
|
86
102
|
}
|
|
@@ -109,11 +125,6 @@ function parseChildren({
|
|
|
109
125
|
console.error("can't find HTML tree in a component chain");
|
|
110
126
|
} else {
|
|
111
127
|
htmlElement.append(velesElementNode2.html);
|
|
112
|
-
setTimeout(() => {
|
|
113
|
-
componentsTree2.forEach((component) => {
|
|
114
|
-
component._privateMethods._callMountHandlers();
|
|
115
|
-
});
|
|
116
|
-
}, 0);
|
|
117
128
|
velesElementNode2.parentVelesElement = velesNode;
|
|
118
129
|
}
|
|
119
130
|
}
|
|
@@ -122,11 +133,6 @@ function parseChildren({
|
|
|
122
133
|
} else {
|
|
123
134
|
htmlElement.append(velesElementNode.html);
|
|
124
135
|
}
|
|
125
|
-
setTimeout(() => {
|
|
126
|
-
componentsTree.forEach((component) => {
|
|
127
|
-
component._privateMethods._callMountHandlers();
|
|
128
|
-
});
|
|
129
|
-
}, 0);
|
|
130
136
|
velesElementNode.parentVelesElement = velesNode;
|
|
131
137
|
childComponents.push(childComponent);
|
|
132
138
|
}
|
|
@@ -197,7 +203,12 @@ function parseComponent({
|
|
|
197
203
|
componentAPI.onUnmount(cb);
|
|
198
204
|
},
|
|
199
205
|
_callMountHandlers: () => {
|
|
200
|
-
componentMountCbs.forEach((cb) =>
|
|
206
|
+
componentMountCbs.forEach((cb) => {
|
|
207
|
+
const mountCbResult = cb();
|
|
208
|
+
if (typeof mountCbResult === "function") {
|
|
209
|
+
componentAPI.onUnmount(mountCbResult);
|
|
210
|
+
}
|
|
211
|
+
});
|
|
201
212
|
},
|
|
202
213
|
_callUnmountHandlers: () => {
|
|
203
214
|
componentUnmountCbs.forEach((cb) => cb());
|
|
@@ -262,10 +273,10 @@ function Fragment({ children }) {
|
|
|
262
273
|
|
|
263
274
|
export {
|
|
264
275
|
getComponentVelesNode,
|
|
276
|
+
callMountHandlers,
|
|
265
277
|
identity,
|
|
266
278
|
onMount,
|
|
267
279
|
onUnmount,
|
|
268
280
|
createElement,
|
|
269
281
|
Fragment
|
|
270
282
|
};
|
|
271
|
-
//# sourceMappingURL=chunk-EVX3ZDYT.js.map
|