veles 0.0.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Seva Zaikov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,304 @@
1
+ # Veles
2
+
3
+ ![Tests status](https://github.com/bloomca/veles/actions/workflows/pull-request-workflow.yaml/badge.svg)
4
+
5
+ > The library is in very early stages and is not published yet as some crucial APIs are still under development
6
+
7
+ `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.
8
+
9
+ ## Performance
10
+
11
+ 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.
12
+
13
+ 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.
14
+
15
+ ## API
16
+
17
+ ### AttachComponent
18
+
19
+ Attach Veles tree to a regular DOM Node.
20
+
21
+ > [!NOTE]
22
+ > 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.
23
+
24
+ ```js
25
+ import { createElement, attachComponent } from "veles";
26
+
27
+ const appContainer = document.getElementById("app");
28
+ attachComponent({ htmlElement: appContainer, component: createElement(App) });
29
+ ```
30
+
31
+ ### createElement
32
+
33
+ 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`.
34
+
35
+ > [!NOTE]
36
+ > JSX should be almost fully supported as long as you specify `Veles.createElement` pragma
37
+
38
+ ```js
39
+ import { createElement } from "veles";
40
+
41
+ function App() {
42
+ return createElement("div", {
43
+ class: "app-container",
44
+ children: [
45
+ createElement("h1", { children: "Veles app" }),
46
+ createElement("p", { children: "Random description" }),
47
+ ],
48
+ });
49
+ }
50
+ ```
51
+
52
+ ### createState
53
+
54
+ `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.
55
+
56
+ `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.
57
+
58
+ 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:
59
+
60
+ ```js
61
+ import { createElement, createState } from "veles";
62
+
63
+ function Counter() {
64
+ const counterState = createState(0);
65
+ return createElement("div", {
66
+ children: [
67
+ createElement("h1", { children: "Counter" }),
68
+ createElement("div", {
69
+ children: counterState.useValue(
70
+ (counterValue) => `counter value is: ${counterValue}`
71
+ ),
72
+ }),
73
+ createElement("button", {
74
+ onClick: () => {
75
+ counterState.setValue(
76
+ (currentCounterValue) => currentCounterValue + 1
77
+ );
78
+ },
79
+ children: "+",
80
+ }),
81
+ ],
82
+ });
83
+ }
84
+ ```
85
+
86
+ ### createState and partial subscriptions
87
+
88
+ 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:
89
+
90
+ ```js
91
+ import { createElement, createState } from "veles";
92
+
93
+ function App() {
94
+ const taskState = createState({
95
+ id: 5,
96
+ title: "title",
97
+ description: "long description",
98
+ });
99
+ return createElement("div", {
100
+ children: [
101
+ createElement("h1", { children: "App" }),
102
+ createElement("div", {
103
+ children: taskState.useSelectorValue(
104
+ (task) => task.title,
105
+ (title) => `task title: ${title}`
106
+ ),
107
+ }),
108
+ ],
109
+ });
110
+ }
111
+ ```
112
+
113
+ The component which listens for `title` will only be rendered again when the title changes.
114
+
115
+ ### createState and lists
116
+
117
+ 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.
118
+
119
+ > [!NOTE]
120
+ > 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.
121
+
122
+ Let's build a simple list component:
123
+
124
+ ```js
125
+ import { createState, createElement } from "veles";
126
+
127
+ function List() {
128
+ const listState = createState([
129
+ { id: 1, name: "first task" },
130
+ { id: 2, name: "second task" },
131
+ { id: 3, name: "third task" },
132
+ ]);
133
+
134
+ return createElement("div", {
135
+ children: [
136
+ createElement("h1", { children: "list" }),
137
+ listState.useValueIterator<{ id: number; name: string }>(
138
+ { key: "id" },
139
+ ({ elementState }) => {
140
+ return createElement("div", {
141
+ children: [
142
+ elementState.useValueSelector((element) => element.name, (name) =>
143
+ createElement("div", { children: name })
144
+ ),
145
+ ],
146
+ });
147
+ }
148
+ ),
149
+ ],
150
+ });
151
+ }
152
+ ```
153
+
154
+ ### createState and attributes
155
+
156
+ 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:
157
+
158
+ ```js
159
+ import { createElement, createState } from "veles";
160
+
161
+ function Counter() {
162
+ const counterState = createState(0);
163
+ return createElement("div", {
164
+ children: [
165
+ createElement("h1", { children: "Counter" }),
166
+ counterState.useValue((counterValue) =>
167
+ createElement("div", { children: `counter value is: ${counterValue}` })
168
+ ),
169
+ createElement("button", {
170
+ onClick: () => {
171
+ counterState.setValue(
172
+ (currentCounterValue) => currentCounterValue + 1
173
+ );
174
+ },
175
+ style: counterState.useAttribute(
176
+ (currentValue) => `width: ${50 + currentValue}px;`
177
+ ),
178
+ children: "+",
179
+ }),
180
+ ],
181
+ });
182
+ }
183
+ ```
184
+
185
+ You can see that we dynamically change the width of the button with every press.
186
+
187
+ ### createState and subscribing to updates
188
+
189
+ 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:
190
+
191
+ ```js
192
+ import { createElement, createState } from "veles";
193
+
194
+ function Counter() {
195
+ const counterState = createState(0);
196
+
197
+ counterState.trackValue((counterValue) => {
198
+ console.log(`new counter value is ${counterValue}`);
199
+ });
200
+
201
+ return createElement("div", {
202
+ children: [
203
+ createElement("h1", { children: "Counter" }),
204
+ counterState.useValue((counterValue) =>
205
+ createElement("div", { children: `counter value is: ${counterValue}` })
206
+ ),
207
+ createElement("button", {
208
+ onClick: () => {
209
+ counterState.setValue(
210
+ (currentCounterValue) => currentCounterValue + 1
211
+ );
212
+ },
213
+ children: "+",
214
+ }),
215
+ ],
216
+ });
217
+ }
218
+ ```
219
+
220
+ 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 }`.
221
+
222
+ ### Subscribing to partial updates
223
+
224
+ 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.
225
+
226
+ ### Combining different states
227
+
228
+ 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.
229
+
230
+ ```js
231
+ import { createElement, createState } from "veles";
232
+
233
+ function Counter() {
234
+ const firstcounterState = createState(0);
235
+ const secondCounterState = createState(0);
236
+ const combinedCounterState = combineState(
237
+ firstcounterState,
238
+ secondCounterState
239
+ );
240
+ return createElement("div", {
241
+ children: [
242
+ createElement("h1", { children: "Counter" }),
243
+ firstcounterState.useValue((counterValue) =>
244
+ createElement("div", {
245
+ children: `first counter value is: ${counterValue}`,
246
+ })
247
+ ),
248
+ secondCounterState.useValue((counterValue) =>
249
+ createElement("div", {
250
+ children: `second counter value is: ${counterValue}`,
251
+ })
252
+ ),
253
+ combinedCounterState.useValueSelector(
254
+ ([firstValue, secondValue]) => firstValue + secondValue,
255
+ (counterValue) =>
256
+ createElement("div", {
257
+ children: `combined counter value is: ${counterValue}`,
258
+ })
259
+ ),
260
+ createElement("button", {
261
+ onClick: () => {
262
+ firstcounterState.setValue(
263
+ (currentCounterValue) => currentCounterValue + 1
264
+ );
265
+ },
266
+ children: "add to the first counter",
267
+ }),
268
+ createElement("button", {
269
+ onClick: () => {
270
+ secondCounterState.setValue(
271
+ (currentCounterValue) => currentCounterValue + 1
272
+ );
273
+ },
274
+ children: "add to the second counter",
275
+ }),
276
+ ],
277
+ });
278
+ }
279
+ ```
280
+
281
+ ### Components lifecycle
282
+
283
+ Right now there are `onMount` and `onUnmount` lifecycle hooks. In your component, just import and call them to add a callback.
284
+
285
+ > [!NOTE]
286
+ > 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
287
+
288
+ ```js
289
+ import { createElement, onMount, onUnmount } from "veles";
290
+
291
+ function App(_props, componentAPI) {
292
+ // could be used as `componentAPI.onMount()`
293
+ onMount(() => {
294
+ console.log("called when the component mounts");
295
+ });
296
+ // could be used as `componentAPI.onUnmount()`
297
+ onUnmount(() => {
298
+ console.log("called when the component unmounts");
299
+ });
300
+ return createElement("div", {
301
+ children: "Application",
302
+ });
303
+ }
304
+ ```
@@ -0,0 +1,271 @@
1
+ // src/hooks/lifecycle.ts
2
+ var contextStack = [];
3
+ var currentContext = null;
4
+ function addContext(newContext) {
5
+ contextStack.push(newContext);
6
+ currentContext = newContext;
7
+ }
8
+ function popContext() {
9
+ contextStack.pop();
10
+ currentContext = contextStack[contextStack.length - 1];
11
+ }
12
+ function onMount(cb) {
13
+ if (currentContext) {
14
+ currentContext.onMount(cb);
15
+ } else {
16
+ console.error("missing current context");
17
+ }
18
+ }
19
+ function onUnmount(cb) {
20
+ if (currentContext) {
21
+ currentContext.onUnmount(cb);
22
+ } else {
23
+ console.error("missing current context");
24
+ }
25
+ }
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;
46
+ }
47
+ }
48
+ return { velesElementNode: childNode, componentsTree };
49
+ }
50
+ function identity(value1, value2) {
51
+ return value1 === value2;
52
+ }
53
+
54
+ // src/create-element/parse-children.ts
55
+ function parseChildren({
56
+ children,
57
+ htmlElement,
58
+ velesNode
59
+ }) {
60
+ const childComponents = [];
61
+ if (children === void 0 || children === null) {
62
+ return childComponents;
63
+ }
64
+ (Array.isArray(children) ? children : [children]).forEach(
65
+ (childComponent) => {
66
+ if (typeof childComponent === "string") {
67
+ const text = document.createTextNode(childComponent);
68
+ htmlElement.append(text);
69
+ } else if (typeof childComponent === "object" && childComponent && "velesNode" in childComponent && (childComponent == null ? void 0 : childComponent.velesNode)) {
70
+ if (childComponent.phantom) {
71
+ childComponent.childComponents.forEach((childComponentofPhantom) => {
72
+ if ("velesNode" in childComponentofPhantom) {
73
+ htmlElement.append(childComponentofPhantom.html);
74
+ childComponentofPhantom.parentVelesElement = velesNode;
75
+ } else {
76
+ const { componentsTree, velesElementNode } = getComponentVelesNode(childComponentofPhantom);
77
+ if (!velesElementNode) {
78
+ console.error("can't find HTML tree in a component chain");
79
+ } else {
80
+ htmlElement.append(velesElementNode.html);
81
+ componentsTree.forEach((component) => {
82
+ component._privateMethods._callMountHandlers();
83
+ });
84
+ velesElementNode.parentVelesElement = velesNode;
85
+ }
86
+ }
87
+ });
88
+ childComponent.parentVelesElement = velesNode;
89
+ childComponents.push(childComponent);
90
+ } else {
91
+ htmlElement.append(childComponent.html);
92
+ childComponent.parentVelesElement = velesNode;
93
+ childComponents.push(childComponent);
94
+ }
95
+ } else if (typeof childComponent === "object" && childComponent && "velesComponent" in childComponent && (childComponent == null ? void 0 : childComponent.velesComponent)) {
96
+ const { componentsTree, velesElementNode } = getComponentVelesNode(childComponent);
97
+ if (!velesElementNode) {
98
+ console.error("can't find HTML tree in a component chain");
99
+ } else {
100
+ if ("velesNode" in velesElementNode && velesElementNode.phantom) {
101
+ velesElementNode.childComponents.forEach(
102
+ (childComponentofPhantom) => {
103
+ if ("velesNode" in childComponentofPhantom) {
104
+ htmlElement.append(childComponentofPhantom.html);
105
+ childComponentofPhantom.parentVelesElement = velesNode;
106
+ } else {
107
+ const { componentsTree: componentsTree2, velesElementNode: velesElementNode2 } = getComponentVelesNode(childComponentofPhantom);
108
+ if (!velesElementNode2) {
109
+ console.error("can't find HTML tree in a component chain");
110
+ } else {
111
+ htmlElement.append(velesElementNode2.html);
112
+ setTimeout(() => {
113
+ componentsTree2.forEach((component) => {
114
+ component._privateMethods._callMountHandlers();
115
+ });
116
+ }, 0);
117
+ velesElementNode2.parentVelesElement = velesNode;
118
+ }
119
+ }
120
+ }
121
+ );
122
+ } else {
123
+ htmlElement.append(velesElementNode.html);
124
+ }
125
+ setTimeout(() => {
126
+ componentsTree.forEach((component) => {
127
+ component._privateMethods._callMountHandlers();
128
+ });
129
+ }, 0);
130
+ velesElementNode.parentVelesElement = velesNode;
131
+ childComponents.push(childComponent);
132
+ }
133
+ } else if (typeof childComponent === "object" && childComponent && "velesStringElement" in childComponent && (childComponent == null ? void 0 : childComponent.velesStringElement)) {
134
+ htmlElement.append(childComponent.html);
135
+ childComponent.parentVelesElement = velesNode;
136
+ childComponents.push(childComponent);
137
+ }
138
+ }
139
+ );
140
+ return childComponents;
141
+ }
142
+
143
+ // src/create-element/assign-attributes.ts
144
+ function assignAttributes({
145
+ props,
146
+ htmlElement,
147
+ velesNode
148
+ }) {
149
+ Object.entries(props).forEach(([key, value]) => {
150
+ const isFunction = typeof value === "function";
151
+ if (isFunction && value.velesAttribute === true) {
152
+ const attributeValue = value(htmlElement, key, velesNode);
153
+ htmlElement.setAttribute(key, attributeValue);
154
+ } else if (
155
+ // basically, any form of `on` handlers, like `onClick`, `onCopy`, etc
156
+ isFunction && key.length > 2 && key.startsWith("on")
157
+ ) {
158
+ htmlElement.addEventListener(
159
+ key[2].toLocaleLowerCase() + key.slice(3),
160
+ value
161
+ );
162
+ } else {
163
+ htmlElement.setAttribute(key, value);
164
+ }
165
+ });
166
+ }
167
+
168
+ // src/create-element/parse-component.ts
169
+ function parseComponent({
170
+ element,
171
+ props
172
+ }) {
173
+ const componentUnmountCbs = [];
174
+ const componentMountCbs = [];
175
+ const componentAPI = {
176
+ onMount: (cb) => {
177
+ componentMountCbs.push(cb);
178
+ },
179
+ onUnmount: (cb) => {
180
+ componentUnmountCbs.push(cb);
181
+ }
182
+ };
183
+ addContext(componentAPI);
184
+ const _componentTree = element(props, componentAPI);
185
+ const componentTree = typeof _componentTree === "string" || !_componentTree ? {
186
+ velesStringElement: true,
187
+ html: document.createTextNode(
188
+ typeof _componentTree === "string" ? _componentTree : ""
189
+ )
190
+ } : _componentTree;
191
+ popContext();
192
+ const velesComponent = {
193
+ velesComponent: true,
194
+ tree: componentTree,
195
+ _privateMethods: {
196
+ _addUnmountHandler: (cb) => {
197
+ componentAPI.onUnmount(cb);
198
+ },
199
+ _callMountHandlers: () => {
200
+ componentMountCbs.forEach((cb) => cb());
201
+ },
202
+ _callUnmountHandlers: () => {
203
+ componentUnmountCbs.forEach((cb) => cb());
204
+ if ("_privateMethods" in velesComponent.tree) {
205
+ velesComponent.tree._privateMethods._callUnmountHandlers();
206
+ }
207
+ }
208
+ }
209
+ };
210
+ return velesComponent;
211
+ }
212
+
213
+ // src/create-element/create-element.ts
214
+ function createElement(element, props = {}) {
215
+ if (typeof element === "string") {
216
+ const { children, ref, phantom = false, ...otherProps } = props;
217
+ const newElement = document.createElement(element);
218
+ const velesNode = {};
219
+ if (ref == null ? void 0 : ref.velesRef) {
220
+ ref.current = newElement;
221
+ }
222
+ const childComponents = parseChildren({
223
+ children,
224
+ htmlElement: newElement,
225
+ velesNode
226
+ });
227
+ let unmountHandlers = [];
228
+ const callUnmountHandlers = () => {
229
+ unmountHandlers.forEach((cb) => cb());
230
+ unmountHandlers = [];
231
+ childComponents.forEach((childComponent) => {
232
+ childComponent._privateMethods._callUnmountHandlers();
233
+ });
234
+ };
235
+ velesNode.html = newElement;
236
+ velesNode.velesNode = true;
237
+ velesNode.childComponents = childComponents;
238
+ velesNode.phantom = phantom;
239
+ velesNode._privateMethods = {
240
+ _addUnmountHandler: (cb) => {
241
+ unmountHandlers.push(cb);
242
+ },
243
+ _callUnmountHandlers: callUnmountHandlers
244
+ };
245
+ assignAttributes({ props: otherProps, htmlElement: newElement, velesNode });
246
+ return velesNode;
247
+ } else if (typeof element === "function") {
248
+ return parseComponent({ element, props });
249
+ }
250
+ throw new Error(
251
+ "Veles createElement expects a valid DOM string or another component"
252
+ );
253
+ }
254
+
255
+ // src/fragment.ts
256
+ function Fragment({ children }) {
257
+ return createElement("div", {
258
+ phantom: true,
259
+ children
260
+ });
261
+ }
262
+
263
+ export {
264
+ getComponentVelesNode,
265
+ identity,
266
+ onMount,
267
+ onUnmount,
268
+ createElement,
269
+ Fragment
270
+ };
271
+ //# sourceMappingURL=chunk-EVX3ZDYT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/hooks/lifecycle.ts","../src/utils.ts","../src/create-element/parse-children.ts","../src/create-element/assign-attributes.ts","../src/create-element/parse-component.ts","../src/create-element/create-element.ts","../src/fragment.ts"],"sourcesContent":["import { ComponentAPI } from \"../types\";\n\n// lifecycle hooks\n// currently, all components need to be synchronous\n// so we execute them and set background context\n// since components can be nested, we need to keep the array\nconst contextStack: ComponentAPI[] = [];\n// all hooks need to know the current context\n// it should way more convenient this way compared to passing\n// `componentAPI` to every method\nlet currentContext: ComponentAPI | null = null;\n\nfunction addContext(newContext: ComponentAPI) {\n contextStack.push(newContext);\n currentContext = newContext;\n}\n\nfunction popContext() {\n contextStack.pop();\n currentContext = contextStack[contextStack.length - 1];\n}\n\nfunction onMount(cb: Function) {\n if (currentContext) {\n currentContext.onMount(cb);\n } else {\n console.error(\"missing current context\");\n }\n}\n\nfunction onUnmount(cb: Function) {\n if (currentContext) {\n currentContext.onUnmount(cb);\n } else {\n console.error(\"missing current context\");\n }\n}\n\nexport { addContext, popContext, onMount, onUnmount };\n","import type { VelesComponent, VelesElement, VelesStringElement } from \"./types\";\n\nfunction getComponentVelesNode(\n component: VelesComponent | VelesElement | VelesStringElement\n): {\n velesElementNode: VelesElement | VelesStringElement;\n componentsTree: VelesComponent[];\n} {\n const componentsTree: VelesComponent[] = [];\n\n if (\"velesStringElement\" in component) {\n return {\n velesElementNode: component,\n componentsTree: [],\n };\n }\n\n let childNode: VelesComponent | VelesElement = component;\n // we can have multiple components nested, we need to get\n // to the actual HTML to attach it\n while (\"velesComponent\" in childNode) {\n componentsTree.push(childNode);\n if (\"velesStringElement\" in childNode.tree) {\n return {\n velesElementNode: childNode.tree,\n componentsTree,\n };\n } else {\n childNode = childNode.tree;\n }\n }\n\n return { velesElementNode: childNode, componentsTree };\n}\n\nfunction identity<T>(value1: T, value2: T) {\n return value1 === value2;\n}\n\nexport { getComponentVelesNode, identity };\n","import { getComponentVelesNode } from \"../utils\";\n\nimport type {\n VelesComponent,\n VelesElement,\n VelesStringElement,\n VelesElementProps,\n} from \"../types\";\n\nfunction parseChildren({\n children,\n htmlElement,\n velesNode,\n}: {\n children: VelesElementProps[\"children\"];\n htmlElement: HTMLElement;\n velesNode: VelesElement;\n}) {\n const childComponents: (\n | VelesElement\n | VelesComponent\n | VelesStringElement\n )[] = [];\n\n if (children === undefined || children === null) {\n return childComponents;\n }\n\n (Array.isArray(children) ? children : [children]).forEach(\n (childComponent) => {\n if (typeof childComponent === \"string\") {\n const text = document.createTextNode(childComponent);\n htmlElement.append(text);\n } else if (\n typeof childComponent === \"object\" &&\n childComponent &&\n \"velesNode\" in childComponent &&\n childComponent?.velesNode\n ) {\n if (childComponent.phantom) {\n // we need to get ALL the children of it and attach it to this node\n childComponent.childComponents.forEach((childComponentofPhantom) => {\n if (\"velesNode\" in childComponentofPhantom) {\n htmlElement.append(childComponentofPhantom.html);\n childComponentofPhantom.parentVelesElement = velesNode;\n } else {\n const { componentsTree, velesElementNode } =\n getComponentVelesNode(childComponentofPhantom);\n\n if (!velesElementNode) {\n console.error(\"can't find HTML tree in a component chain\");\n } else {\n htmlElement.append(velesElementNode.html);\n\n // TODO: address the same concern as below\n componentsTree.forEach((component) => {\n component._privateMethods._callMountHandlers();\n });\n\n velesElementNode.parentVelesElement = velesNode;\n }\n }\n });\n childComponent.parentVelesElement = velesNode;\n childComponents.push(childComponent);\n } else {\n // TODO: check that it is a valid DOM Node\n htmlElement.append(childComponent.html);\n childComponent.parentVelesElement = velesNode;\n childComponents.push(childComponent);\n }\n } else if (\n typeof childComponent === \"object\" &&\n childComponent &&\n \"velesComponent\" in childComponent &&\n childComponent?.velesComponent\n ) {\n // we need to save the whole components chain, so that\n // we can trigger `mount` hooks on all of them correctly\n const { componentsTree, velesElementNode } =\n getComponentVelesNode(childComponent);\n\n if (!velesElementNode) {\n console.error(\"can't find HTML tree in a component chain\");\n } else {\n if (\"velesNode\" in velesElementNode && velesElementNode.phantom) {\n // we need to get ALL the children of it and attach it to this node\n velesElementNode.childComponents.forEach(\n (childComponentofPhantom) => {\n if (\"velesNode\" in childComponentofPhantom) {\n htmlElement.append(childComponentofPhantom.html);\n childComponentofPhantom.parentVelesElement = velesNode;\n } else {\n const { componentsTree, velesElementNode } =\n getComponentVelesNode(childComponentofPhantom);\n\n if (!velesElementNode) {\n console.error(\"can't find HTML tree in a component chain\");\n } else {\n htmlElement.append(velesElementNode.html);\n\n // Same explanation as below. Components are mounted synchronously\n setTimeout(() => {\n componentsTree.forEach((component) => {\n component._privateMethods._callMountHandlers();\n });\n }, 0);\n velesElementNode.parentVelesElement = velesNode;\n }\n }\n }\n );\n } else {\n htmlElement.append(velesElementNode.html);\n }\n\n /**\n * Components are mounted synchronously, so we can safely wait for the next\n * CPU tick and be sure that new markup is attached to DOM.\n */\n setTimeout(() => {\n componentsTree.forEach((component) => {\n component._privateMethods._callMountHandlers();\n });\n }, 0);\n velesElementNode.parentVelesElement = velesNode;\n childComponents.push(childComponent);\n }\n } else if (\n typeof childComponent === \"object\" &&\n childComponent &&\n \"velesStringElement\" in childComponent &&\n childComponent?.velesStringElement\n ) {\n // TODO: check that it is a valid DOM Node\n htmlElement.append(childComponent.html);\n childComponent.parentVelesElement = velesNode;\n childComponents.push(childComponent);\n }\n }\n );\n\n return childComponents;\n}\n\nexport { parseChildren };\n","import type { VelesElement } from \"../types\";\n\nfunction assignAttributes({\n props,\n htmlElement,\n velesNode,\n}: {\n props: Record<string, any>;\n htmlElement: HTMLElement;\n velesNode: VelesElement;\n}) {\n Object.entries(props).forEach(([key, value]) => {\n const isFunction = typeof value === \"function\";\n if (isFunction && value.velesAttribute === true) {\n const attributeValue = value(htmlElement, key, velesNode);\n htmlElement.setAttribute(key, attributeValue);\n } else if (\n // basically, any form of `on` handlers, like `onClick`, `onCopy`, etc\n isFunction &&\n key.length > 2 &&\n key.startsWith(\"on\")\n ) {\n // TODO: think if this is robust enough\n htmlElement.addEventListener(\n key[2].toLocaleLowerCase() + key.slice(3),\n value\n );\n } else {\n htmlElement.setAttribute(key, value);\n }\n });\n}\n\nexport { assignAttributes };\n","import { addContext, popContext } from \"../hooks/lifecycle\";\n\nimport type {\n VelesComponent,\n VelesStringElement,\n VelesElementProps,\n ComponentAPI,\n ComponentFunction,\n} from \"../types\";\n\nfunction parseComponent({\n element,\n props,\n}: {\n element: ComponentFunction;\n props: VelesElementProps;\n}) {\n const componentUnmountCbs: Function[] = [];\n const componentMountCbs: Function[] = [];\n const componentAPI: ComponentAPI = {\n onMount: (cb) => {\n componentMountCbs.push(cb);\n },\n onUnmount: (cb) => {\n componentUnmountCbs.push(cb);\n },\n };\n // at this moment we enter new context\n addContext(componentAPI);\n const _componentTree = element(props, componentAPI);\n\n const componentTree =\n typeof _componentTree === \"string\" || !_componentTree\n ? ({\n velesStringElement: true,\n html: document.createTextNode(\n typeof _componentTree === \"string\" ? _componentTree : \"\"\n ),\n } as VelesStringElement)\n : _componentTree;\n\n // here we exit our context\n popContext();\n const velesComponent: VelesComponent = {\n velesComponent: true,\n tree: componentTree,\n _privateMethods: {\n _addUnmountHandler: (cb: Function) => {\n componentAPI.onUnmount(cb);\n },\n _callMountHandlers: () => {\n componentMountCbs.forEach((cb) => cb());\n },\n _callUnmountHandlers: () => {\n componentUnmountCbs.forEach((cb) => cb());\n // this should trigger recursive checks, whether it is a VelesNode or VelesComponent\n // string Nodes don't have lifecycle handlers\n if (\"_privateMethods\" in velesComponent.tree) {\n velesComponent.tree._privateMethods._callUnmountHandlers();\n }\n },\n },\n };\n\n return velesComponent;\n}\n\nexport { parseComponent };\n","import { parseChildren } from \"./parse-children\";\nimport { assignAttributes } from \"./assign-attributes\";\nimport { parseComponent } from \"./parse-component\";\n\nimport type {\n VelesComponent,\n VelesElement,\n VelesElementProps,\n ComponentFunction,\n} from \"../types\";\n\nfunction createElement(\n element: string | ComponentFunction,\n props: VelesElementProps = {}\n): VelesElement | VelesComponent {\n if (typeof element === \"string\") {\n const { children, ref, phantom = false, ...otherProps } = props;\n const newElement = document.createElement(element);\n const velesNode = {} as VelesElement;\n\n if (ref?.velesRef) {\n ref.current = newElement;\n }\n\n const childComponents = parseChildren({\n children,\n htmlElement: newElement,\n velesNode,\n });\n\n // these handlers are attached directly to the DOM element\n // specifically, the top level node which is rendered after\n // using `useValue` function and also listeners from\n // `useAttribute`\n let unmountHandlers: Function[] = [];\n const callUnmountHandlers = () => {\n unmountHandlers.forEach((cb) => cb());\n unmountHandlers = [];\n\n childComponents.forEach((childComponent) => {\n childComponent._privateMethods._callUnmountHandlers();\n });\n };\n\n velesNode.html = newElement;\n velesNode.velesNode = true;\n velesNode.childComponents = childComponents;\n velesNode.phantom = phantom;\n velesNode._privateMethods = {\n _addUnmountHandler: (cb: Function) => {\n unmountHandlers.push(cb);\n },\n _callUnmountHandlers: callUnmountHandlers,\n };\n\n // assign all the DOM attributes, including event listeners\n assignAttributes({ props: otherProps, htmlElement: newElement, velesNode });\n\n return velesNode;\n\n // functions mean that we want to render another component\n } else if (typeof element === \"function\") {\n return parseComponent({ element, props });\n }\n\n // otherwise we use the API wrong, so we throw an error\n throw new Error(\n \"Veles createElement expects a valid DOM string or another component\"\n );\n}\n\nexport { createElement };\n","import { createElement } from \"./create-element\";\n\nimport type { VelesChildren } from \"./types\";\n\nfunction Fragment({ children }: { children: VelesChildren }) {\n return createElement(\"div\", {\n phantom: true,\n children,\n });\n}\n\nexport { Fragment };\n"],"mappings":"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","names":["componentsTree","velesElementNode"]}