veles 0.0.7 → 0.0.8
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 +24 -247
- package/dist/{chunk-ILNLS6QO.js → chunk-4QXZSBVZ.js} +124 -121
- package/dist/chunk-GVPFIZZW.js +928 -0
- package/dist/index-BA0L7WRK.d.ts +60 -0
- package/dist/index-BOpDMzdf.d.cts +60 -0
- package/dist/index.cjs +969 -535
- package/dist/index.d.cts +14 -5
- package/dist/index.d.ts +14 -5
- package/dist/index.js +14 -10
- package/dist/jsx-runtime.cjs +91 -122
- package/dist/jsx-runtime.d.cts +7 -3252
- package/dist/jsx-runtime.d.ts +7 -3252
- package/dist/jsx-runtime.js +2 -4
- package/dist/types.d-CjiJHqth.d.cts +3954 -0
- package/dist/types.d-CjiJHqth.d.ts +3954 -0
- package/dist/utils/index.cjs +901 -501
- package/dist/utils/index.d.cts +2 -2
- package/dist/utils/index.d.ts +2 -2
- package/dist/utils/index.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-MH6DPZ3V.js +0 -490
- package/dist/chunk-X6QYYW56.js +0 -15
- package/dist/create-state-Bo6TT4qP.d.ts +0 -33
- package/dist/create-state-D1JASFVs.d.cts +0 -33
- package/dist/fragment-CU26z590.d.cts +0 -9
- package/dist/fragment-IVSEC7-Q.d.ts +0 -9
- package/dist/types.d-DgVBp6oa.d.cts +0 -256
- package/dist/types.d-DgVBp6oa.d.ts +0 -256
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
|
-
>
|
|
7
|
+
> This library is very early stages, so the API is not 100% finalized
|
|
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
|
|
|
@@ -14,270 +14,47 @@ This library's primary focus is in performance. What this means is that it allow
|
|
|
14
14
|
|
|
15
15
|
It is important to note that the performance benefits will only be observed (and relevant as well) in case of a pretty high interactivity. It might not be faster than any other UI framework on the first render, the biggest improvement lies in the power of subscribing to individual changes.
|
|
16
16
|
|
|
17
|
-
##
|
|
17
|
+
## Installation
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
The library is available on npm. To add it to your project, execute this in your project folder:
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
> [!NOTE]
|
|
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
|
-
|
|
26
|
-
```js
|
|
27
|
-
import { attachComponent } from "veles";
|
|
28
|
-
|
|
29
|
-
const App () => <div>App</div>
|
|
30
|
-
|
|
31
|
-
const appContainer = document.getElementById("app");
|
|
32
|
-
attachComponent({ htmlElement: appContainer, component: <App /> });
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
### JSX support
|
|
36
|
-
|
|
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.
|
|
38
|
-
|
|
39
|
-
```jsx
|
|
40
|
-
function App() {
|
|
41
|
-
return (
|
|
42
|
-
<div class="app-container">
|
|
43
|
-
<h1>Veles App</h1>
|
|
44
|
-
<p>Random description</p>
|
|
45
|
-
</div>
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
### createState
|
|
51
|
-
|
|
52
|
-
`createState` is the API which is responsible for the interactivity in Veles applications, and it is the only one. You can either pass the initial value and then update it manually in callbacks or some other subscriptions, or you can pass a function as the second argument and you can subscribe to any external data store and update the state reacting to it.
|
|
53
|
-
|
|
54
|
-
`createState` returns an object with a variety of subscription methods. It is important to understand that just creating the state object does not affect the component at all. When the state value updates, only the components which are rendered by these subscription methods will update, but the component where the state was created is not affected.
|
|
55
|
-
|
|
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:
|
|
57
|
-
|
|
58
|
-
```jsx
|
|
59
|
-
import { createState } from "veles";
|
|
60
|
-
|
|
61
|
-
function Counter() {
|
|
62
|
-
const counterState = createState(0);
|
|
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
|
-
(counterValue) => `counter value is: ${counterValue}`
|
|
73
|
-
)}
|
|
74
|
-
</div>
|
|
75
|
-
<button onClick={increment}>+</button>
|
|
76
|
-
<button onClick={decrement}>-</button>
|
|
77
|
-
</div>
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
### createState and partial subscriptions
|
|
83
|
-
|
|
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:
|
|
85
|
-
|
|
86
|
-
```jsx
|
|
87
|
-
import { createState } from "veles";
|
|
88
|
-
|
|
89
|
-
function App() {
|
|
90
|
-
const taskState = createState({
|
|
91
|
-
id: 5,
|
|
92
|
-
title: "title",
|
|
93
|
-
description: "long description",
|
|
94
|
-
});
|
|
95
|
-
return (
|
|
96
|
-
<div>
|
|
97
|
-
<h1>App</h1>
|
|
98
|
-
<div>
|
|
99
|
-
{taskState.useSelectorValue(
|
|
100
|
-
(task) => task.title,
|
|
101
|
-
(title) => `task title: ${title}`
|
|
102
|
-
)}
|
|
103
|
-
</div>
|
|
104
|
-
</div>
|
|
105
|
-
);
|
|
106
|
-
}
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
The component which listens for `title` will only be rendered again when the title changes.
|
|
110
|
-
|
|
111
|
-
### createState and lists
|
|
112
|
-
|
|
113
|
-
Lists performance is one of the cornerstones of this library, and to help with that, it provides a special state method `useValueIterator`. This method ensures that when the state changes, instead of re-rendering the whole list, it checks each list element individually, moves them into correct order without unnecessary re-renders, and in case of changes element data, it will update the passed state object, so that only subscribed parts will re-render.
|
|
114
|
-
|
|
115
|
-
> [!NOTE]
|
|
116
|
-
> the library determines the uniqueness by calculating the key. You can either pass a string which will be a property name, or you can pass a function which will be executed with the element and the index. If the result is different from any previous calculations, it treats it as a new component.
|
|
117
|
-
|
|
118
|
-
Let's build a simple list component:
|
|
119
|
-
|
|
120
|
-
```jsx
|
|
121
|
-
import { createState } from "veles";
|
|
122
|
-
|
|
123
|
-
function List() {
|
|
124
|
-
const listState = createState([
|
|
125
|
-
{ id: 1, name: "first task" },
|
|
126
|
-
{ id: 2, name: "second task" },
|
|
127
|
-
{ id: 3, name: "third task" },
|
|
128
|
-
]);
|
|
129
|
-
|
|
130
|
-
return <div>
|
|
131
|
-
<h1>List</h1>
|
|
132
|
-
{listState.useValueIterator<{ id: number; name: string }>(
|
|
133
|
-
{ key: "id" },
|
|
134
|
-
({ elementState }) => <div>
|
|
135
|
-
{elementState.useValueSelector(
|
|
136
|
-
(element) => element.name,
|
|
137
|
-
(name) => <div>{name}</div>
|
|
138
|
-
)}
|
|
139
|
-
</div>
|
|
140
|
-
)}
|
|
141
|
-
</div>
|
|
142
|
-
}
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
### createState and attributes
|
|
146
|
-
|
|
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:
|
|
148
|
-
|
|
149
|
-
```jsx
|
|
150
|
-
import { createState } from "veles";
|
|
151
|
-
|
|
152
|
-
function Counter() {
|
|
153
|
-
const counterState = createState(0);
|
|
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(
|
|
163
|
-
(currentValue) => `width: ${50 + currentValue}px;`
|
|
164
|
-
)}
|
|
165
|
-
>
|
|
166
|
-
+
|
|
167
|
-
</button>
|
|
168
|
-
</div>
|
|
169
|
-
);
|
|
170
|
-
}
|
|
21
|
+
```sh
|
|
22
|
+
npm install --save veles
|
|
171
23
|
```
|
|
172
24
|
|
|
173
|
-
|
|
25
|
+
Types are installed automatically with the same package.
|
|
174
26
|
|
|
175
|
-
|
|
176
|
-
|
|
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:
|
|
27
|
+
## A basic example
|
|
178
28
|
|
|
179
29
|
```jsx
|
|
180
30
|
import { createState } from "veles";
|
|
181
31
|
|
|
182
|
-
function
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
counterState.trackValue((counterValue) => {
|
|
186
|
-
console.log(`new counter value is ${counterValue}`);
|
|
187
|
-
});
|
|
188
|
-
const increment = () =>
|
|
189
|
-
counterState.setValue((currentValue) => currentValue + 1);
|
|
190
|
-
const decrement = () =>
|
|
191
|
-
counterState.setValue((currentValue) => currentValue - 1);
|
|
192
|
-
|
|
32
|
+
function NameComponent() {
|
|
33
|
+
const nameState = createState("");
|
|
193
34
|
return (
|
|
194
35
|
<div>
|
|
195
|
-
<
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
)}
|
|
200
|
-
|
|
201
|
-
<
|
|
202
|
-
<button onClick={decrement}>-</button>
|
|
36
|
+
<input
|
|
37
|
+
type="text"
|
|
38
|
+
name="name"
|
|
39
|
+
value={nameState.useAttribute()}
|
|
40
|
+
onInput={(e) => nameState.setValue(e.target.value)}
|
|
41
|
+
/>
|
|
42
|
+
<p>{nameState.useValue()}</p>
|
|
203
43
|
</div>
|
|
204
44
|
);
|
|
205
45
|
}
|
|
206
46
|
```
|
|
207
47
|
|
|
208
|
-
This
|
|
48
|
+
This will render an input and will update the Text node dynamically, without re-rendering the whole component. For a more advanced example, please head to [the docs](https://bloomca.github.io/veles/#advanced-example).
|
|
209
49
|
|
|
210
|
-
###
|
|
50
|
+
### Resources
|
|
211
51
|
|
|
212
|
-
|
|
52
|
+
- [Getting started](https://bloomca.github.io/veles/)
|
|
53
|
+
- [API docs](https://bloomca.github.io/veles/api/)
|
|
54
|
+
- [Differences from other frameworks](https://bloomca.github.io/veles/frameworks-difference.html)
|
|
213
55
|
|
|
214
|
-
|
|
56
|
+
There also a companion app ([veles-calendar-app](https://github.com/Bloomca/veles-calendar-app)), which is developed using Veles and is supposed to push it to the limits, identify the issues and ideally improve performance even more.
|
|
215
57
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
```jsx
|
|
219
|
-
import { createState } from "veles";
|
|
58
|
+
### Features
|
|
220
59
|
|
|
221
|
-
|
|
222
|
-
const firstcounterState = createState(0);
|
|
223
|
-
const secondCounterState = createState(0);
|
|
224
|
-
const combinedCounterState = combineState(
|
|
225
|
-
firstcounterState,
|
|
226
|
-
secondCounterState
|
|
227
|
-
);
|
|
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
|
-
);
|
|
259
|
-
}
|
|
260
|
-
```
|
|
261
|
-
|
|
262
|
-
### Components lifecycle
|
|
263
|
-
|
|
264
|
-
Right now there are `onMount` and `onUnmount` lifecycle hooks. In your component, just import and call them to add a callback.
|
|
265
|
-
|
|
266
|
-
> [!NOTE]
|
|
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
|
|
268
|
-
|
|
269
|
-
```jsx
|
|
270
|
-
import { onMount, onUnmount } from "veles";
|
|
271
|
-
|
|
272
|
-
function App(_props, componentAPI) {
|
|
273
|
-
// could be used as `componentAPI.onMount()`
|
|
274
|
-
onMount(() => {
|
|
275
|
-
console.log("called when the component mounts");
|
|
276
|
-
});
|
|
277
|
-
// could be used as `componentAPI.onUnmount()`
|
|
278
|
-
onUnmount(() => {
|
|
279
|
-
console.log("called when the component unmounts");
|
|
280
|
-
});
|
|
281
|
-
return <div>Application<div>
|
|
282
|
-
}
|
|
283
|
-
```
|
|
60
|
+
The library is under development, so some features are not available yet, namely Context and Portals.
|
|
@@ -24,56 +24,30 @@ function onUnmount(cb) {
|
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
// src/
|
|
28
|
-
function
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
27
|
+
// src/create-element/create-text-element.ts
|
|
28
|
+
function createTextElement(text) {
|
|
29
|
+
const mountHandlers = [];
|
|
30
|
+
const unmountHandlers = [];
|
|
31
|
+
return {
|
|
32
|
+
velesStringElement: true,
|
|
33
|
+
// in case there is no text, we create an empty Text node, so we still can
|
|
34
|
+
// have a reference to it, replace it, call lifecycle methods, etc
|
|
35
|
+
html: document.createTextNode(text || ""),
|
|
36
|
+
_privateMethods: {
|
|
37
|
+
_addMountHandler(cb) {
|
|
38
|
+
mountHandlers.push(cb);
|
|
39
|
+
},
|
|
40
|
+
_callMountHandlers() {
|
|
41
|
+
mountHandlers.forEach((cb) => cb());
|
|
42
|
+
},
|
|
43
|
+
_addUnmountHandler: (cb) => {
|
|
44
|
+
unmountHandlers.push(cb);
|
|
45
|
+
},
|
|
46
|
+
_callUnmountHandlers: () => {
|
|
47
|
+
unmountHandlers.forEach((cb) => cb());
|
|
48
|
+
}
|
|
46
49
|
}
|
|
47
|
-
}
|
|
48
|
-
return { velesElementNode: childNode, componentsTree };
|
|
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
|
-
}
|
|
64
|
-
function identity(value1, value2) {
|
|
65
|
-
return value1 === value2;
|
|
66
|
-
}
|
|
67
|
-
function unique(arr) {
|
|
68
|
-
const map = /* @__PURE__ */ new Map();
|
|
69
|
-
const resultArr = [];
|
|
70
|
-
arr.forEach((element) => {
|
|
71
|
-
if (map.has(element))
|
|
72
|
-
return;
|
|
73
|
-
map.set(element, true);
|
|
74
|
-
resultArr.push(element);
|
|
75
|
-
});
|
|
76
|
-
return resultArr;
|
|
50
|
+
};
|
|
77
51
|
}
|
|
78
52
|
|
|
79
53
|
// src/create-element/parse-children.ts
|
|
@@ -86,30 +60,36 @@ function parseChildren({
|
|
|
86
60
|
if (children === void 0 || children === null) {
|
|
87
61
|
return childComponents;
|
|
88
62
|
}
|
|
63
|
+
let lastInsertedNode = null;
|
|
89
64
|
(Array.isArray(children) ? children : [children]).forEach(
|
|
90
65
|
(childComponent) => {
|
|
91
66
|
if (typeof childComponent === "string") {
|
|
92
|
-
const
|
|
93
|
-
htmlElement.append(
|
|
67
|
+
const textNode = createTextElement(childComponent);
|
|
68
|
+
htmlElement.append(textNode.html);
|
|
69
|
+
lastInsertedNode = textNode.html;
|
|
70
|
+
childComponents.push(textNode);
|
|
94
71
|
} else if (typeof childComponent === "number") {
|
|
95
|
-
const
|
|
96
|
-
htmlElement.append(
|
|
72
|
+
const textNode = createTextElement(String(childComponent));
|
|
73
|
+
htmlElement.append(textNode.html);
|
|
74
|
+
lastInsertedNode = textNode.html;
|
|
75
|
+
childComponents.push(textNode);
|
|
97
76
|
} else if (typeof childComponent === "object" && childComponent && "velesNode" in childComponent && (childComponent == null ? void 0 : childComponent.velesNode)) {
|
|
98
77
|
if (childComponent.phantom) {
|
|
99
78
|
childComponent.childComponents.forEach((childComponentofPhantom) => {
|
|
100
79
|
if ("velesNode" in childComponentofPhantom) {
|
|
101
80
|
htmlElement.append(childComponentofPhantom.html);
|
|
102
81
|
childComponentofPhantom.parentVelesElement = velesNode;
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
);
|
|
82
|
+
lastInsertedNode = childComponentofPhantom.html;
|
|
83
|
+
} else if ("velesStringElement" in childComponentofPhantom) {
|
|
84
|
+
const velesElementNode = childComponentofPhantom;
|
|
107
85
|
if (!velesElementNode) {
|
|
108
86
|
console.error("can't find HTML tree in a component chain");
|
|
109
87
|
} else {
|
|
110
88
|
htmlElement.append(velesElementNode.html);
|
|
89
|
+
lastInsertedNode = velesElementNode.html;
|
|
111
90
|
velesElementNode.parentVelesElement = velesNode;
|
|
112
91
|
}
|
|
92
|
+
} else {
|
|
113
93
|
}
|
|
114
94
|
});
|
|
115
95
|
childComponent.parentVelesElement = velesNode;
|
|
@@ -118,39 +98,18 @@ function parseChildren({
|
|
|
118
98
|
htmlElement.append(childComponent.html);
|
|
119
99
|
childComponent.parentVelesElement = velesNode;
|
|
120
100
|
childComponents.push(childComponent);
|
|
101
|
+
lastInsertedNode = childComponent.html;
|
|
121
102
|
}
|
|
122
|
-
} else if (typeof childComponent === "object" && childComponent && "
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
if ("velesNode" in velesElementNode && velesElementNode.phantom) {
|
|
128
|
-
velesElementNode.childComponents.forEach(
|
|
129
|
-
(childComponentofPhantom) => {
|
|
130
|
-
if ("velesNode" in childComponentofPhantom) {
|
|
131
|
-
htmlElement.append(childComponentofPhantom.html);
|
|
132
|
-
childComponentofPhantom.parentVelesElement = velesNode;
|
|
133
|
-
} else {
|
|
134
|
-
const { componentsTree: componentsTree2, velesElementNode: velesElementNode2 } = getComponentVelesNode(childComponentofPhantom);
|
|
135
|
-
if (!velesElementNode2) {
|
|
136
|
-
console.error("can't find HTML tree in a component chain");
|
|
137
|
-
} else {
|
|
138
|
-
htmlElement.append(velesElementNode2.html);
|
|
139
|
-
velesElementNode2.parentVelesElement = velesNode;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
);
|
|
144
|
-
} else {
|
|
145
|
-
htmlElement.append(velesElementNode.html);
|
|
146
|
-
}
|
|
147
|
-
velesElementNode.parentVelesElement = velesNode;
|
|
148
|
-
childComponents.push(childComponent);
|
|
149
|
-
}
|
|
103
|
+
} else if (typeof childComponent === "object" && childComponent && "velesComponentObject" in childComponent) {
|
|
104
|
+
childComponent.insertAfter = lastInsertedNode;
|
|
105
|
+
childComponent.parentVelesElement = velesNode;
|
|
106
|
+
childComponents.push(childComponent);
|
|
107
|
+
lastInsertedNode = childComponent;
|
|
150
108
|
} else if (typeof childComponent === "object" && childComponent && "velesStringElement" in childComponent && (childComponent == null ? void 0 : childComponent.velesStringElement)) {
|
|
151
109
|
htmlElement.append(childComponent.html);
|
|
152
110
|
childComponent.parentVelesElement = velesNode;
|
|
153
111
|
childComponents.push(childComponent);
|
|
112
|
+
lastInsertedNode = childComponent.html;
|
|
154
113
|
}
|
|
155
114
|
}
|
|
156
115
|
);
|
|
@@ -167,28 +126,68 @@ function assignAttributes({
|
|
|
167
126
|
const isFunction = typeof value === "function";
|
|
168
127
|
if (isFunction && value.velesAttribute === true) {
|
|
169
128
|
const attributeValue = value(htmlElement, key, velesNode);
|
|
170
|
-
|
|
171
|
-
} else if (
|
|
172
|
-
// basically, any form of `on` handlers, like `onClick`, `onCopy`, etc
|
|
173
|
-
isFunction && key.length > 2 && key.startsWith("on")
|
|
174
|
-
) {
|
|
175
|
-
htmlElement.addEventListener(
|
|
176
|
-
key[2].toLocaleLowerCase() + key.slice(3),
|
|
177
|
-
value
|
|
178
|
-
);
|
|
129
|
+
assignAttribute({ key, value: attributeValue, htmlElement });
|
|
179
130
|
} else {
|
|
180
|
-
|
|
131
|
+
assignAttribute({ key, value, htmlElement });
|
|
181
132
|
}
|
|
182
133
|
});
|
|
183
134
|
}
|
|
135
|
+
function assignAttribute({
|
|
136
|
+
key,
|
|
137
|
+
value,
|
|
138
|
+
htmlElement
|
|
139
|
+
}) {
|
|
140
|
+
if (
|
|
141
|
+
// basically, any form of `on` handlers, like `onClick`, `onCopy`, etc
|
|
142
|
+
typeof value === "function" && key.startsWith("on")
|
|
143
|
+
) {
|
|
144
|
+
htmlElement.addEventListener(
|
|
145
|
+
key[2].toLocaleLowerCase() + key.slice(3),
|
|
146
|
+
value
|
|
147
|
+
);
|
|
148
|
+
} else {
|
|
149
|
+
if (typeof value === "boolean") {
|
|
150
|
+
if (value)
|
|
151
|
+
htmlElement.setAttribute(key, "");
|
|
152
|
+
} else {
|
|
153
|
+
htmlElement.setAttribute(key, value);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
184
157
|
|
|
185
158
|
// src/create-element/parse-component.ts
|
|
186
159
|
function parseComponent({
|
|
187
160
|
element,
|
|
188
161
|
props
|
|
189
162
|
}) {
|
|
190
|
-
const
|
|
191
|
-
const
|
|
163
|
+
const mountCbs = [];
|
|
164
|
+
const unmountCbs = [];
|
|
165
|
+
return {
|
|
166
|
+
velesComponentObject: true,
|
|
167
|
+
element,
|
|
168
|
+
props,
|
|
169
|
+
_privateMethods: {
|
|
170
|
+
_addMountHandler(cb) {
|
|
171
|
+
mountCbs.push(cb);
|
|
172
|
+
},
|
|
173
|
+
_addUnmountHandler: (cb) => {
|
|
174
|
+
unmountCbs.push(cb);
|
|
175
|
+
},
|
|
176
|
+
_callMountHandlers: () => {
|
|
177
|
+
mountCbs.forEach((cb) => cb());
|
|
178
|
+
},
|
|
179
|
+
_callUnmountHandlers: () => {
|
|
180
|
+
unmountCbs.forEach((cb) => cb());
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
function executeComponent({
|
|
186
|
+
element,
|
|
187
|
+
props
|
|
188
|
+
}) {
|
|
189
|
+
let componentUnmountCbs = [];
|
|
190
|
+
let componentMountCbs = [];
|
|
192
191
|
const componentAPI = {
|
|
193
192
|
onMount: (cb) => {
|
|
194
193
|
componentMountCbs.push(cb);
|
|
@@ -199,17 +198,15 @@ function parseComponent({
|
|
|
199
198
|
};
|
|
200
199
|
addContext(componentAPI);
|
|
201
200
|
const _componentTree = element(props, componentAPI);
|
|
202
|
-
const componentTree = typeof _componentTree === "string" || !_componentTree ?
|
|
203
|
-
velesStringElement: true,
|
|
204
|
-
html: document.createTextNode(
|
|
205
|
-
typeof _componentTree === "string" ? _componentTree : ""
|
|
206
|
-
)
|
|
207
|
-
} : _componentTree;
|
|
201
|
+
const componentTree = typeof _componentTree === "string" || !_componentTree ? createTextElement(_componentTree) : _componentTree;
|
|
208
202
|
popContext();
|
|
209
203
|
const velesComponent = {
|
|
210
204
|
velesComponent: true,
|
|
211
205
|
tree: componentTree,
|
|
212
206
|
_privateMethods: {
|
|
207
|
+
_addMountHandler(cb) {
|
|
208
|
+
componentMountCbs.push(cb);
|
|
209
|
+
},
|
|
213
210
|
_addUnmountHandler: (cb) => {
|
|
214
211
|
componentAPI.onUnmount(cb);
|
|
215
212
|
},
|
|
@@ -223,9 +220,6 @@ function parseComponent({
|
|
|
223
220
|
},
|
|
224
221
|
_callUnmountHandlers: () => {
|
|
225
222
|
componentUnmountCbs.forEach((cb) => cb());
|
|
226
|
-
if ("_privateMethods" in velesComponent.tree) {
|
|
227
|
-
velesComponent.tree._privateMethods._callUnmountHandlers();
|
|
228
|
-
}
|
|
229
223
|
}
|
|
230
224
|
}
|
|
231
225
|
};
|
|
@@ -246,23 +240,25 @@ function createElement(element, props = {}) {
|
|
|
246
240
|
htmlElement: newElement,
|
|
247
241
|
velesNode
|
|
248
242
|
});
|
|
249
|
-
|
|
250
|
-
const callUnmountHandlers = () => {
|
|
251
|
-
unmountHandlers.forEach((cb) => cb());
|
|
252
|
-
unmountHandlers = [];
|
|
253
|
-
childComponents.forEach((childComponent) => {
|
|
254
|
-
childComponent._privateMethods._callUnmountHandlers();
|
|
255
|
-
});
|
|
256
|
-
};
|
|
243
|
+
const unmountHandlers = [];
|
|
257
244
|
velesNode.html = newElement;
|
|
258
245
|
velesNode.velesNode = true;
|
|
259
246
|
velesNode.childComponents = childComponents;
|
|
260
247
|
velesNode.phantom = phantom;
|
|
248
|
+
const mountHandlers = [];
|
|
261
249
|
velesNode._privateMethods = {
|
|
262
|
-
|
|
250
|
+
_addMountHandler(cb) {
|
|
251
|
+
mountHandlers.push(cb);
|
|
252
|
+
},
|
|
253
|
+
_callMountHandlers() {
|
|
254
|
+
mountHandlers.forEach((cb) => cb());
|
|
255
|
+
},
|
|
256
|
+
_addUnmountHandler(cb) {
|
|
263
257
|
unmountHandlers.push(cb);
|
|
264
258
|
},
|
|
265
|
-
_callUnmountHandlers
|
|
259
|
+
_callUnmountHandlers() {
|
|
260
|
+
unmountHandlers.forEach((cb) => cb());
|
|
261
|
+
}
|
|
266
262
|
};
|
|
267
263
|
assignAttributes({ props: otherProps, htmlElement: newElement, velesNode });
|
|
268
264
|
return velesNode;
|
|
@@ -274,12 +270,19 @@ function createElement(element, props = {}) {
|
|
|
274
270
|
);
|
|
275
271
|
}
|
|
276
272
|
|
|
273
|
+
// src/fragment.ts
|
|
274
|
+
function Fragment({ children }) {
|
|
275
|
+
return createElement("div", {
|
|
276
|
+
phantom: true,
|
|
277
|
+
children
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
|
|
277
281
|
export {
|
|
278
|
-
getComponentVelesNode,
|
|
279
|
-
callMountHandlers,
|
|
280
|
-
identity,
|
|
281
|
-
unique,
|
|
282
282
|
onMount,
|
|
283
283
|
onUnmount,
|
|
284
|
-
|
|
284
|
+
createTextElement,
|
|
285
|
+
executeComponent,
|
|
286
|
+
createElement,
|
|
287
|
+
Fragment
|
|
285
288
|
};
|