veles 0.0.7 → 0.0.9

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 some features, like proper TypeScript support, are not fully implemented yet
7
+ > This library is still in 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
 
@@ -12,272 +12,50 @@
12
12
 
13
13
  This library's primary focus is in performance. What this means is that it allows you to write your code in a way which will ensure that when the data in your app changes, only the smallest relevant parts will update. Despite of similarities with React in syntax, it does not follow the same waterfall style for component re-renders. Instead, it gives you API to subscribe to atomic changes in your tracked state and re-render only parts of the UI which actually depend on the value. Internally, it renders new HTML and replaces the old node. A similar approach is done for attributes, where in case of changes, only the relevant attribute will be updated in place, but nothing else will change.
14
14
 
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.
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, which is especially powerful in case of lists.
16
16
 
17
- ## API
17
+ ## Installation
18
18
 
19
- ### AttachComponent
19
+ The library is available on npm. To add it to your project, execute this in your project folder:
20
20
 
21
- Attach Veles tree to a regular DOM Node.
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
- 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.
25
+ Types are installed automatically with the same package.
174
26
 
175
- ### createState and subscribing to updates
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 Counter() {
183
- const counterState = createState(0);
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
- <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>
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 subscription will not cause any re-renders. By default, the first call will happen during the component initialization, and you can pass a second options object to alter this behaviour. You can either set `{ skipFirstCall: true }` to completely skip it, or you can specify to run it when the component is mounted in DOM: `{ callOnMount: true }`.
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
- ### Subscribing to partial updates
50
+ ### Resources
211
51
 
212
- In case you don't want to subscribe to the whole state, you have 2 options. You can either provide a `{ comparator: (prevValue, nextValue) => prevValue === nextValue }` property in the options object, or you can use `state.trackValueSelector()` method. You can also combine them, if you need that for some reason.
52
+ - [Getting started](https://bloomca.github.io/veles/)
53
+ - [API docs](https://bloomca.github.io/veles/api/)
54
+ - [Guides](https://bloomca.github.io/veles/guides/)
55
+ - [Differences from other frameworks](https://bloomca.github.io/veles/frameworks-difference.html)
213
56
 
214
- ### Combining different states
57
+ 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
58
 
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.
217
-
218
- ```jsx
219
- import { createState } from "veles";
59
+ ### Features
220
60
 
221
- function Counter() {
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
- ```
61
+ The library is under development, so some features are not available yet. Namely the TypeScript type inferring is not the best (although the library does support TypeScript), and Portals are not implemented yet.
@@ -24,56 +24,30 @@ function onUnmount(cb) {
24
24
  }
25
25
  }
26
26
 
27
- // src/_utils.ts
28
- function getComponentVelesNode(component) {
29
- const componentsTree = [];
30
- if ("velesStringElement" in component) {
31
- return {
32
- velesElementNode: component,
33
- componentsTree: []
34
- };
35
- }
36
- let childNode = component;
37
- while ("velesComponent" in childNode) {
38
- componentsTree.push(childNode);
39
- if ("velesStringElement" in childNode.tree) {
40
- return {
41
- velesElementNode: childNode.tree,
42
- componentsTree
43
- };
44
- } else {
45
- childNode = childNode.tree;
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 text = document.createTextNode(childComponent);
93
- htmlElement.append(text);
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 text = document.createTextNode(String(childComponent));
96
- htmlElement.append(text);
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
- } else {
104
- const { velesElementNode } = getComponentVelesNode(
105
- childComponentofPhantom
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 && "velesComponent" in childComponent && (childComponent == null ? void 0 : childComponent.velesComponent)) {
123
- const { componentsTree, velesElementNode } = getComponentVelesNode(childComponent);
124
- if (!velesElementNode) {
125
- console.error("can't find HTML tree in a component chain");
126
- } else {
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,65 @@ 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
- htmlElement.setAttribute(key, attributeValue);
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
- htmlElement.setAttribute(key, value);
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(key.slice(2).toLocaleLowerCase(), value);
145
+ } else {
146
+ if (typeof value === "boolean") {
147
+ if (value)
148
+ htmlElement.setAttribute(key, "");
149
+ } else {
150
+ htmlElement.setAttribute(key, value);
151
+ }
152
+ }
153
+ }
184
154
 
185
155
  // src/create-element/parse-component.ts
186
156
  function parseComponent({
187
157
  element,
188
158
  props
189
159
  }) {
190
- const componentUnmountCbs = [];
191
- const componentMountCbs = [];
160
+ const mountCbs = [];
161
+ const unmountCbs = [];
162
+ return {
163
+ velesComponentObject: true,
164
+ element,
165
+ props,
166
+ _privateMethods: {
167
+ _addMountHandler(cb) {
168
+ mountCbs.push(cb);
169
+ },
170
+ _addUnmountHandler: (cb) => {
171
+ unmountCbs.push(cb);
172
+ },
173
+ _callMountHandlers: () => {
174
+ mountCbs.forEach((cb) => cb());
175
+ },
176
+ _callUnmountHandlers: () => {
177
+ unmountCbs.forEach((cb) => cb());
178
+ }
179
+ }
180
+ };
181
+ }
182
+ function executeComponent({
183
+ element,
184
+ props
185
+ }) {
186
+ let componentUnmountCbs = [];
187
+ let componentMountCbs = [];
192
188
  const componentAPI = {
193
189
  onMount: (cb) => {
194
190
  componentMountCbs.push(cb);
@@ -199,17 +195,15 @@ function parseComponent({
199
195
  };
200
196
  addContext(componentAPI);
201
197
  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;
198
+ const componentTree = typeof _componentTree === "string" || !_componentTree ? createTextElement(_componentTree) : _componentTree;
208
199
  popContext();
209
200
  const velesComponent = {
210
201
  velesComponent: true,
211
202
  tree: componentTree,
212
203
  _privateMethods: {
204
+ _addMountHandler(cb) {
205
+ componentMountCbs.push(cb);
206
+ },
213
207
  _addUnmountHandler: (cb) => {
214
208
  componentAPI.onUnmount(cb);
215
209
  },
@@ -223,9 +217,6 @@ function parseComponent({
223
217
  },
224
218
  _callUnmountHandlers: () => {
225
219
  componentUnmountCbs.forEach((cb) => cb());
226
- if ("_privateMethods" in velesComponent.tree) {
227
- velesComponent.tree._privateMethods._callUnmountHandlers();
228
- }
229
220
  }
230
221
  }
231
222
  };
@@ -246,23 +237,25 @@ function createElement(element, props = {}) {
246
237
  htmlElement: newElement,
247
238
  velesNode
248
239
  });
249
- let unmountHandlers = [];
250
- const callUnmountHandlers = () => {
251
- unmountHandlers.forEach((cb) => cb());
252
- unmountHandlers = [];
253
- childComponents.forEach((childComponent) => {
254
- childComponent._privateMethods._callUnmountHandlers();
255
- });
256
- };
240
+ const unmountHandlers = [];
257
241
  velesNode.html = newElement;
258
242
  velesNode.velesNode = true;
259
243
  velesNode.childComponents = childComponents;
260
244
  velesNode.phantom = phantom;
245
+ const mountHandlers = [];
261
246
  velesNode._privateMethods = {
262
- _addUnmountHandler: (cb) => {
247
+ _addMountHandler(cb) {
248
+ mountHandlers.push(cb);
249
+ },
250
+ _callMountHandlers() {
251
+ mountHandlers.forEach((cb) => cb());
252
+ },
253
+ _addUnmountHandler(cb) {
263
254
  unmountHandlers.push(cb);
264
255
  },
265
- _callUnmountHandlers: callUnmountHandlers
256
+ _callUnmountHandlers() {
257
+ unmountHandlers.forEach((cb) => cb());
258
+ }
266
259
  };
267
260
  assignAttributes({ props: otherProps, htmlElement: newElement, velesNode });
268
261
  return velesNode;
@@ -274,12 +267,19 @@ function createElement(element, props = {}) {
274
267
  );
275
268
  }
276
269
 
270
+ // src/fragment.ts
271
+ function Fragment({ children }) {
272
+ return createElement("div", {
273
+ phantom: true,
274
+ children
275
+ });
276
+ }
277
+
277
278
  export {
278
- getComponentVelesNode,
279
- callMountHandlers,
280
- identity,
281
- unique,
282
279
  onMount,
283
280
  onUnmount,
284
- createElement
281
+ createTextElement,
282
+ executeComponent,
283
+ createElement,
284
+ Fragment
285
285
  };