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 CHANGED
@@ -4,7 +4,7 @@
4
4
  [![Build Size](https://img.shields.io/bundlephobia/minzip/veles?label=bundle%20size)](https://bundlephobia.com/result?p=veles)
5
5
  [![Version](https://img.shields.io/npm/v/veles)](https://www.npmjs.com/package/veles)
6
6
 
7
- > The library is in very early stages and is not published yet as some crucial APIs are still under development
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 { createElement, attachComponent } from "veles";
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: createElement(App) });
32
+ attachComponent({ htmlElement: appContainer, component: <App /> });
31
33
  ```
32
34
 
33
- ### createElement
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
- > [!NOTE]
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 createElement("div", {
45
- class: "app-container",
46
- children: [
47
- createElement("h1", { children: "Veles app" }),
48
- createElement("p", { children: "Random description" }),
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
- ```js
63
- import { createElement, createState } from "veles";
58
+ ```jsx
59
+ import { createState } from "veles";
64
60
 
65
61
  function Counter() {
66
62
  const counterState = createState(0);
67
- return createElement("div", {
68
- children: [
69
- createElement("h1", { children: "Counter" }),
70
- createElement("div", {
71
- children: counterState.useValue(
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
- createElement("button", {
76
- onClick: () => {
77
- counterState.setValue(
78
- (currentCounterValue) => currentCounterValue + 1
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
- ```js
93
- import { createElement, createState } from "veles";
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 createElement("div", {
102
- children: [
103
- createElement("h1", { children: "App" }),
104
- createElement("div", {
105
- children: taskState.useSelectorValue(
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
- ```js
127
- import { createState, createElement } from "veles";
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 createElement("div", {
137
- children: [
138
- createElement("h1", { children: "list" }),
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
- return createElement("div", {
143
- children: [
144
- elementState.useValueSelector((element) => element.name, (name) =>
145
- createElement("div", { children: name })
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
- ```js
161
- import { createElement, createState } from "veles";
149
+ ```jsx
150
+ import { createState } from "veles";
162
151
 
163
152
  function Counter() {
164
153
  const counterState = createState(0);
165
- return createElement("div", {
166
- children: [
167
- createElement("h1", { children: "Counter" }),
168
- counterState.useValue((counterValue) =>
169
- createElement("div", { children: `counter value is: ${counterValue}` })
170
- ),
171
- createElement("button", {
172
- onClick: () => {
173
- counterState.setValue(
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
- children: "+",
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
- ```js
194
- import { createElement, createState } from "veles";
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
- return createElement("div", {
204
- children: [
205
- createElement("h1", { children: "Counter" }),
206
- counterState.useValue((counterValue) =>
207
- createElement("div", { children: `counter value is: ${counterValue}` })
208
- ),
209
- createElement("button", {
210
- onClick: () => {
211
- counterState.setValue(
212
- (currentCounterValue) => currentCounterValue + 1
213
- );
214
- },
215
- children: "+",
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
- ```js
233
- import { createElement, createState } from "veles";
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
- return createElement("div", {
243
- children: [
244
- createElement("h1", { children: "Counter" }),
245
- firstcounterState.useValue((counterValue) =>
246
- createElement("div", {
247
- children: `first counter value is: ${counterValue}`,
248
- })
249
- ),
250
- secondCounterState.useValue((counterValue) =>
251
- createElement("div", {
252
- children: `second counter value is: ${counterValue}`,
253
- })
254
- ),
255
- combinedCounterState.useValueSelector(
256
- ([firstValue, secondValue]) => firstValue + secondValue,
257
- (counterValue) =>
258
- createElement("div", {
259
- children: `combined counter value is: ${counterValue}`,
260
- })
261
- ),
262
- createElement("button", {
263
- onClick: () => {
264
- firstcounterState.setValue(
265
- (currentCounterValue) => currentCounterValue + 1
266
- );
267
- },
268
- children: "add to the first counter",
269
- }),
270
- createElement("button", {
271
- onClick: () => {
272
- secondCounterState.setValue(
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
- ```js
291
- import { createElement, onMount, onUnmount } from "veles";
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 createElement("div", {
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 { componentsTree, velesElementNode } = getComponentVelesNode(childComponentofPhantom);
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) => 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