valyrian.js 7.0.3 → 7.0.4
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/dist/index.d.ts +7 -19
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -5
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/index.mjs +15 -5
- package/dist/router/index.d.ts +1 -1
- package/dist/router/index.d.ts.map +1 -1
- package/lib/index.ts +331 -46
- package/lib/router/index.ts +4 -3
- package/package.json +1 -1
package/lib/index.ts
CHANGED
|
@@ -1,120 +1,182 @@
|
|
|
1
|
+
/* eslint-disable no-use-before-define */
|
|
1
2
|
/* eslint-disable indent */
|
|
2
3
|
/* eslint-disable sonarjs/cognitive-complexity */
|
|
3
4
|
/* eslint-disable complexity */
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
// The VnodeProperties interface represents properties that can be passed to a virtual node.
|
|
7
|
+
export interface VnodeProperties {
|
|
8
|
+
// A unique key for the virtual node, which can be a string or a number.
|
|
9
|
+
// This is useful for optimizing updates in a list of nodes.
|
|
6
10
|
key?: string | number;
|
|
11
|
+
// A state object that is associated with the virtual node.
|
|
7
12
|
state?: any;
|
|
8
|
-
|
|
9
|
-
// eslint-disable-next-line no-unused-vars, no-use-before-define
|
|
10
|
-
(vnode: VnodeInterface): never;
|
|
11
|
-
};
|
|
12
|
-
onupdate?: {
|
|
13
|
-
// eslint-disable-next-line no-unused-vars, no-use-before-define
|
|
14
|
-
(vnode: VnodeInterface, oldVnode: VnodeInterface): never;
|
|
15
|
-
};
|
|
16
|
-
onremove?: {
|
|
17
|
-
// eslint-disable-next-line no-unused-vars, no-use-before-define
|
|
18
|
-
(oldVnode: VnodeInterface): never;
|
|
19
|
-
};
|
|
20
|
-
shouldupdate?: {
|
|
21
|
-
// eslint-disable-next-line no-unused-vars, no-use-before-define
|
|
22
|
-
(vnode: VnodeInterface, oldVnode: VnodeInterface): undefined | boolean;
|
|
23
|
-
};
|
|
13
|
+
// An index signature that allows for any other properties to be added.
|
|
24
14
|
[key: string | number | symbol]: any;
|
|
25
15
|
}
|
|
16
|
+
|
|
17
|
+
// The DomElement interface extends the Element interface with an index signature.
|
|
18
|
+
// This allows for any additional properties to be added to DOM elements.
|
|
26
19
|
export interface DomElement extends Element {
|
|
27
20
|
[key: string]: any;
|
|
28
21
|
}
|
|
22
|
+
|
|
23
|
+
// The VnodeInterface represents a virtual node. It has a number of optional fields,
|
|
24
|
+
// including a tag, props, children, and a DOM element.
|
|
29
25
|
export interface VnodeInterface {
|
|
30
|
-
//
|
|
31
|
-
|
|
32
|
-
// eslint-disable-next-line no-unused-vars
|
|
26
|
+
// The constructor for the virtual node. It takes a tag, props, and children as arguments.
|
|
27
|
+
// The tag can be a string, a component, or a POJO component.
|
|
28
|
+
// eslint-disable-next-line no-unused-vars
|
|
29
|
+
new (tag: string | Component | POJOComponent, props: VnodeProperties, children: Children): VnodeInterface;
|
|
30
|
+
// The tag for the virtual node. It can be a string, a component, or a POJO component.
|
|
33
31
|
tag: string | Component | POJOComponent;
|
|
34
|
-
props
|
|
35
|
-
|
|
32
|
+
// The props for the virtual node.
|
|
33
|
+
props: VnodeProperties;
|
|
34
|
+
// The children for the virtual node.
|
|
36
35
|
children: Children;
|
|
36
|
+
// A boolean indicating whether the virtual node is an SVG element.
|
|
37
37
|
isSVG?: boolean;
|
|
38
|
+
// The DOM element that corresponds to the virtual node.
|
|
38
39
|
dom?: DomElement;
|
|
40
|
+
// A boolean indicating whether the virtual node has been processed in the keyed diffing algorithm.
|
|
39
41
|
processed?: boolean;
|
|
42
|
+
// An index signature that allows for any additional properties to be added to the virtual node.
|
|
40
43
|
[key: string | number | symbol]: any;
|
|
41
44
|
}
|
|
45
|
+
|
|
46
|
+
// The VnodeWithDom interface represents a virtual node that has a DOM element associated with it.
|
|
42
47
|
export interface VnodeWithDom extends VnodeInterface {
|
|
43
48
|
dom: DomElement;
|
|
44
49
|
}
|
|
50
|
+
|
|
51
|
+
// The Component interface represents a function that returns a virtual node or a list of virtual nodes.
|
|
52
|
+
// It can also have additional properties.
|
|
45
53
|
export interface Component {
|
|
46
|
-
//
|
|
47
|
-
|
|
54
|
+
// The function that returns a virtual node or a list of virtual nodes.
|
|
55
|
+
// It can take props and children as arguments.
|
|
56
|
+
// eslint-disable-next-line no-unused-vars
|
|
57
|
+
(props?: VnodeProperties | null, ...children: any[]): VnodeInterface | Children | any;
|
|
58
|
+
// An index signature that allows for any additional properties to be added to the component.
|
|
48
59
|
[key: string]: any;
|
|
49
60
|
}
|
|
61
|
+
|
|
62
|
+
// The POJOComponent interface represents a "plain old JavaScript object" (POJO) component.
|
|
63
|
+
// It has a view function that returns a virtual node or a list of virtual nodes,
|
|
64
|
+
// as well as optional props and children.
|
|
65
|
+
// It can be used also to identify class instance components.
|
|
50
66
|
export interface POJOComponent {
|
|
67
|
+
// The view function that returns a virtual node or a list of virtual nodes.
|
|
51
68
|
view: Component;
|
|
52
|
-
props
|
|
69
|
+
// The props for the component.
|
|
70
|
+
props?: VnodeProperties | null;
|
|
71
|
+
// The children for the component.
|
|
53
72
|
children?: any[];
|
|
73
|
+
// An index signature that allows for any additional properties to be added to the POJO component.
|
|
54
74
|
[key: string]: any;
|
|
55
75
|
}
|
|
76
|
+
|
|
77
|
+
// The VnodeComponentInterface represents a virtual node that has a component as its tag.
|
|
78
|
+
// It has props and children, just like a regular virtual node.
|
|
56
79
|
export interface VnodeComponentInterface extends VnodeInterface {
|
|
57
80
|
tag: Component | POJOComponent;
|
|
58
|
-
props:
|
|
59
|
-
// eslint-disable-next-line no-unused-vars, no-use-before-define
|
|
81
|
+
props: VnodeProperties;
|
|
60
82
|
children: Children;
|
|
61
83
|
}
|
|
84
|
+
|
|
85
|
+
// The Children interface represents a list of virtual nodes or other values.
|
|
62
86
|
export interface Children extends Array<VnodeInterface | VnodeComponentInterface | any> {}
|
|
87
|
+
|
|
88
|
+
// The Directive interface represents a function that can be applied to a virtual node.
|
|
89
|
+
// It receives the value, virtual node, and old virtual node as arguments, and can return a boolean value.
|
|
90
|
+
// If only the virtual node is passed, it means its the on create phase for the v-node.
|
|
91
|
+
// If the old virtual node is also passed, it means its the on update phase for the v-node.
|
|
63
92
|
export interface Directive {
|
|
64
|
-
// eslint-disable-next-line no-unused-vars
|
|
93
|
+
// eslint-disable-next-line no-unused-vars
|
|
65
94
|
(value: any, vnode: VnodeWithDom, oldVnode?: VnodeWithDom): void | boolean;
|
|
66
95
|
}
|
|
96
|
+
|
|
97
|
+
// The Directives interface is a mapping of directive names to Directive functions.
|
|
67
98
|
export interface Directives {
|
|
68
99
|
[key: string]: Directive;
|
|
69
100
|
}
|
|
101
|
+
|
|
102
|
+
// The ReservedProps interface is a mapping of reserved prop names to the value `true`.
|
|
103
|
+
// These prop names cannot be used as custom prop names.
|
|
70
104
|
export interface ReservedProps {
|
|
71
105
|
[key: string]: true;
|
|
72
106
|
}
|
|
107
|
+
|
|
108
|
+
// The Current interface represents the current component and virtual node that are being processed.
|
|
73
109
|
export interface Current {
|
|
110
|
+
// The current component. It can be a component, a POJO component, or null.
|
|
74
111
|
component: Component | POJOComponent | null;
|
|
112
|
+
// The current virtual node. It must have a DOM element associated with it.
|
|
75
113
|
vnode: VnodeWithDom | null;
|
|
114
|
+
// The old virtual node. It must have a DOM element associated with it.
|
|
76
115
|
oldVnode?: VnodeWithDom | null;
|
|
77
116
|
}
|
|
117
|
+
|
|
118
|
+
// The V function is the main function for creating virtual nodes.
|
|
119
|
+
// It takes a tag or component, props, and children as arguments, and returns a virtual node.
|
|
78
120
|
export interface V {
|
|
79
121
|
// eslint-disable-next-line no-unused-vars, no-use-before-define
|
|
80
|
-
(tagOrComponent: string | Component | POJOComponent, props:
|
|
122
|
+
(tagOrComponent: string | Component | POJOComponent, props: VnodeProperties | null, ...children: Children):
|
|
81
123
|
| VnodeInterface
|
|
82
124
|
| VnodeComponentInterface;
|
|
83
125
|
// eslint-disable-next-line no-unused-vars, no-use-before-define
|
|
84
126
|
fragment(_: any, ...children: Children): Children;
|
|
85
127
|
}
|
|
86
|
-
|
|
128
|
+
// 'textTag' is a constant string that is used to represent text nodes in the virtual DOM.
|
|
87
129
|
const textTag = "#text";
|
|
88
130
|
|
|
131
|
+
// 'isNodeJs' is a boolean that is true if the code is running in a Node.js environment and false otherwise.
|
|
132
|
+
// It is determined by checking if the 'process' global object is defined and has a 'versions' property.
|
|
89
133
|
export let isNodeJs = Boolean(typeof process !== "undefined" && process.versions && process.versions.node);
|
|
90
134
|
|
|
135
|
+
// 'createDomElement' is a function that creates a new DOM element with the specified tag name.
|
|
136
|
+
// If 'isSVG' is true, it creates an SVG element instead of a regular DOM element.
|
|
91
137
|
export function createDomElement(tag: string, isSVG: boolean = false): DomElement {
|
|
92
138
|
return isSVG ? document.createElementNS("http://www.w3.org/2000/svg", tag) : document.createElement(tag);
|
|
93
139
|
}
|
|
94
140
|
|
|
95
|
-
|
|
141
|
+
// 'Vnode' is a class that represents a virtual DOM node.
|
|
142
|
+
// It has three properties: 'tag', 'props', and 'children'.
|
|
143
|
+
// 'Vnode' is exported as an object with a type of 'VnodeInterface'.
|
|
144
|
+
// The 'as unknown as VnodeInterface' is used to tell TypeScript that the 'Vnode' function has the same type as 'VnodeInterface'.
|
|
145
|
+
export const Vnode = function Vnode(this: VnodeInterface, tag: string, props: VnodeProperties, children: Children) {
|
|
146
|
+
// 'this' refers to the current instance of 'Vnode'.
|
|
96
147
|
this.tag = tag;
|
|
97
148
|
this.props = props;
|
|
98
149
|
this.children = children;
|
|
99
150
|
} as unknown as VnodeInterface;
|
|
100
151
|
|
|
152
|
+
// 'isComponent' is a function that returns true if the given 'component' is a valid component and false otherwise.
|
|
153
|
+
// A component is either a function or an object with a 'view' function.
|
|
101
154
|
export function isComponent(component): component is Component {
|
|
102
155
|
return component && (typeof component === "function" || (typeof component === "object" && "view" in component));
|
|
103
156
|
}
|
|
104
157
|
|
|
158
|
+
// 'isVnode' is a function that returns true if the given 'object' is a 'Vnode' instance and false otherwise.
|
|
105
159
|
export const isVnode = (object?: unknown | VnodeInterface): object is VnodeInterface => {
|
|
160
|
+
// Use the 'instanceof' operator to check if 'object' is an instance of 'Vnode'.
|
|
106
161
|
return object instanceof Vnode;
|
|
107
162
|
};
|
|
108
163
|
|
|
164
|
+
// 'isVnodeComponent' is a function that returns true if the given 'object' is a 'Vnode' instance with a 'tag' property that is a valid component.
|
|
165
|
+
// It returns false otherwise.
|
|
109
166
|
export const isVnodeComponent = (object?: unknown | VnodeComponentInterface): object is VnodeComponentInterface => {
|
|
167
|
+
// Check if 'object' is a 'Vnode' instance and its 'tag' property is a valid component.
|
|
110
168
|
return isVnode(object) && isComponent(object.tag);
|
|
111
169
|
};
|
|
112
170
|
|
|
113
|
-
//
|
|
171
|
+
// 'domToVnode' is a function that converts a DOM node to a 'Vnode' instance.
|
|
114
172
|
function domToVnode(dom: any): VnodeWithDom {
|
|
115
173
|
let children: VnodeWithDom[] = [];
|
|
174
|
+
// Iterate through all child nodes of 'dom'.
|
|
116
175
|
for (let i = 0, l = dom.childNodes.length; i < l; i++) {
|
|
117
176
|
let childDom = dom.childNodes[i];
|
|
177
|
+
// If the child node is a text node, create a 'Vnode' instance with the 'textTag' constant as the 'tag' property.
|
|
178
|
+
// Set the 'dom' property of the 'Vnode' instance to the child DOM node.
|
|
179
|
+
// Push the 'Vnode' instance to the 'children' array.
|
|
118
180
|
if (childDom.nodeType === 3) {
|
|
119
181
|
let vnode = new Vnode(textTag, {}, []);
|
|
120
182
|
vnode.dom = childDom;
|
|
@@ -122,22 +184,34 @@ function domToVnode(dom: any): VnodeWithDom {
|
|
|
122
184
|
continue;
|
|
123
185
|
}
|
|
124
186
|
|
|
187
|
+
// If the child node is an element node, recursively call 'domToVnode' to convert it to a 'Vnode' instance.
|
|
188
|
+
// Push the 'Vnode' instance to the 'children' array.
|
|
125
189
|
if (childDom.nodeType === 1) {
|
|
126
190
|
children.push(domToVnode(childDom));
|
|
127
191
|
}
|
|
128
192
|
}
|
|
129
193
|
|
|
130
|
-
let props:
|
|
194
|
+
let props: VnodeProperties = {};
|
|
195
|
+
// Iterate through all attributes of 'dom'.
|
|
131
196
|
for (let i = 0, l = dom.attributes.length; i < l; i++) {
|
|
132
197
|
let attr = dom.attributes[i];
|
|
198
|
+
// Add the attribute to the 'props' object, using the attribute's name as the key and its value as the value.
|
|
133
199
|
props[attr.nodeName] = attr.nodeValue;
|
|
134
200
|
}
|
|
135
201
|
|
|
202
|
+
// Create a new 'Vnode' instance with the 'tag' property set to the lowercase version of the DOM node's tag name.
|
|
203
|
+
// Set the 'props' and 'children' properties to the 'props' and 'children' arrays respectively.
|
|
204
|
+
// Set the 'dom' property of the 'Vnode' instance to the DOM node.
|
|
136
205
|
let vnode = new Vnode(dom.tagName.toLowerCase(), props, children);
|
|
137
206
|
vnode.dom = dom;
|
|
138
207
|
return vnode as VnodeWithDom;
|
|
139
208
|
}
|
|
140
209
|
|
|
210
|
+
// This function takes in an HTML string and creates a virtual node representation of it
|
|
211
|
+
// using the `domToVnode` function. It does this by creating a new `div` element, setting
|
|
212
|
+
// its `innerHTML` to the provided HTML string, and then using `map` to iterate over the
|
|
213
|
+
// `childNodes` of the `div` element, passing each one to `domToVnode` to create a virtual
|
|
214
|
+
// node representation of it. The resulting array of virtual nodes is then returned.
|
|
141
215
|
export function trust(htmlString: string) {
|
|
142
216
|
let div = createDomElement("div");
|
|
143
217
|
div.innerHTML = htmlString.trim();
|
|
@@ -149,10 +223,13 @@ export function trust(htmlString: string) {
|
|
|
149
223
|
/* Main Component implementation */
|
|
150
224
|
/* ========================================================================== */
|
|
151
225
|
|
|
226
|
+
// These variables are used to store the main component, the main virtual node, and whether
|
|
227
|
+
// the main component is currently mounted.
|
|
152
228
|
let mainComponent: VnodeComponentInterface | null = null;
|
|
153
229
|
let mainVnode: VnodeWithDom | null = null;
|
|
154
230
|
let isMounted = false;
|
|
155
231
|
|
|
232
|
+
// This object is used to store the current virtual node and component being rendered.
|
|
156
233
|
export const current: Current = {
|
|
157
234
|
vnode: null,
|
|
158
235
|
oldVnode: null,
|
|
@@ -160,6 +237,8 @@ export const current: Current = {
|
|
|
160
237
|
};
|
|
161
238
|
|
|
162
239
|
/* Reserved props ----------------------------------------------------------- */
|
|
240
|
+
// This object is used to store the names of reserved props, which are props that are reserved
|
|
241
|
+
// for special purposes and should not be used as regular component props.
|
|
163
242
|
export const reservedProps: Record<string, true> = {
|
|
164
243
|
key: true,
|
|
165
244
|
state: true,
|
|
@@ -179,13 +258,18 @@ export const reservedProps: Record<string, true> = {
|
|
|
179
258
|
};
|
|
180
259
|
|
|
181
260
|
/* Mounting, Updating, Cleanup and Unmounting ------------------------------- */
|
|
261
|
+
// These sets are used to store callbacks for various lifecycle events: mounting, updating,
|
|
262
|
+
// cleaning up, and unmounting.
|
|
182
263
|
const onCleanupSet: Set<Function> = new Set();
|
|
183
264
|
const onMountSet: Set<Function> = new Set();
|
|
184
265
|
const onUpdateSet: Set<Function> = new Set();
|
|
185
266
|
const onUnmountSet: Set<Function> = new Set();
|
|
186
267
|
|
|
268
|
+
// These functions allow users to register callbacks for the corresponding lifecycle events.
|
|
187
269
|
export function onMount(callback) {
|
|
188
|
-
|
|
270
|
+
if (!isMounted) {
|
|
271
|
+
onMountSet.add(callback);
|
|
272
|
+
}
|
|
189
273
|
}
|
|
190
274
|
|
|
191
275
|
export function onUpdate(callback) {
|
|
@@ -197,9 +281,12 @@ export function onCleanup(callback) {
|
|
|
197
281
|
}
|
|
198
282
|
|
|
199
283
|
export function onUnmount(callback) {
|
|
200
|
-
|
|
284
|
+
if (!isMounted) {
|
|
285
|
+
onUnmountSet.add(callback);
|
|
286
|
+
}
|
|
201
287
|
}
|
|
202
288
|
|
|
289
|
+
// This function is used to call all the callbacks in a given set.
|
|
203
290
|
function callSet(set) {
|
|
204
291
|
for (let callback of set) {
|
|
205
292
|
callback();
|
|
@@ -209,15 +296,27 @@ function callSet(set) {
|
|
|
209
296
|
}
|
|
210
297
|
|
|
211
298
|
/* Event listener ----------------------------------------------------------- */
|
|
299
|
+
|
|
300
|
+
// This object stores the names of event listeners that have been added
|
|
212
301
|
const eventListenerNames: Record<string, true> = {};
|
|
302
|
+
|
|
303
|
+
// This function is called when an event occurs
|
|
213
304
|
function eventListener(e: Event) {
|
|
305
|
+
// Convert the target of the event to a DOM element
|
|
214
306
|
let dom = e.target as DomElement;
|
|
307
|
+
|
|
308
|
+
// Create the name of the event listener by adding "v-on" to the event type
|
|
215
309
|
let name = `v-on${e.type}`;
|
|
310
|
+
|
|
311
|
+
// Keep going up the DOM tree until we find an element with an event listener
|
|
312
|
+
// matching the event type
|
|
216
313
|
while (dom) {
|
|
217
314
|
if (dom[name]) {
|
|
315
|
+
// Call the event listener function
|
|
218
316
|
dom[name](e, dom);
|
|
317
|
+
|
|
318
|
+
// If the default action of the event hasn't been prevented, update the DOM
|
|
219
319
|
if (!e.defaultPrevented) {
|
|
220
|
-
// eslint-disable-next-line no-use-before-define
|
|
221
320
|
update();
|
|
222
321
|
}
|
|
223
322
|
return;
|
|
@@ -227,8 +326,13 @@ function eventListener(e: Event) {
|
|
|
227
326
|
}
|
|
228
327
|
|
|
229
328
|
/* Directives --------------------------------------------------------------- */
|
|
329
|
+
|
|
330
|
+
// This function creates a directive that hides an element based on a condition
|
|
230
331
|
let hideDirective = (test: boolean) => (bool: boolean, vnode: VnodeInterface, oldnode?: VnodeInterface) => {
|
|
332
|
+
// If test is true, use the value of bool. Otherwise, use the opposite of bool.
|
|
231
333
|
let value = test ? bool : !bool;
|
|
334
|
+
|
|
335
|
+
// If the value is true, hide the element by replacing it with a text node
|
|
232
336
|
if (value) {
|
|
233
337
|
let newdom = document.createTextNode("");
|
|
234
338
|
if (oldnode && oldnode.dom && oldnode.dom.parentNode) {
|
|
@@ -242,9 +346,15 @@ let hideDirective = (test: boolean) => (bool: boolean, vnode: VnodeInterface, ol
|
|
|
242
346
|
}
|
|
243
347
|
};
|
|
244
348
|
|
|
349
|
+
// This object stores all the available directives
|
|
245
350
|
export const directives: Directives = {
|
|
351
|
+
// The "v-if" directive hides an element if the given condition is false
|
|
246
352
|
"v-if": hideDirective(false),
|
|
353
|
+
|
|
354
|
+
// The "v-unless" directive hides an element if the given condition is true
|
|
247
355
|
"v-unless": hideDirective(true),
|
|
356
|
+
|
|
357
|
+
// The "v-for" directive creates a loop and applies a callback function to each item in the loop
|
|
248
358
|
"v-for": (set: unknown[], vnode: VnodeWithDom) => {
|
|
249
359
|
let newChildren: VnodeInterface[] = [];
|
|
250
360
|
let callback = vnode.children[0];
|
|
@@ -253,6 +363,8 @@ export const directives: Directives = {
|
|
|
253
363
|
}
|
|
254
364
|
vnode.children = newChildren;
|
|
255
365
|
},
|
|
366
|
+
|
|
367
|
+
// The "v-show" directive shows or hides an element by setting the "display" style property
|
|
256
368
|
"v-show": (bool: boolean, vnode: VnodeWithDom) => {
|
|
257
369
|
(
|
|
258
370
|
vnode.dom as unknown as {
|
|
@@ -260,22 +372,35 @@ export const directives: Directives = {
|
|
|
260
372
|
}
|
|
261
373
|
).style.display = bool ? "" : "none";
|
|
262
374
|
},
|
|
375
|
+
|
|
376
|
+
// The "v-class" directive adds or removes class names from an element based on a condition
|
|
263
377
|
"v-class": (classes: { [x: string]: boolean }, vnode: VnodeWithDom) => {
|
|
378
|
+
// Loop through all the class names in the classes object
|
|
264
379
|
for (let name in classes) {
|
|
380
|
+
// Add or remove the class name from the element's class list based on the value in the classes object
|
|
265
381
|
(vnode.dom as DomElement).classList.toggle(name, classes[name]);
|
|
266
382
|
}
|
|
267
383
|
},
|
|
384
|
+
|
|
385
|
+
// The "v-html" directive sets the inner HTML of an element to the given HTML string
|
|
268
386
|
"v-html": (html: string, vnode: VnodeWithDom) => {
|
|
387
|
+
// Set the children of the vnode to a trusted version of the HTML string
|
|
269
388
|
vnode.children = [trust(html)];
|
|
270
389
|
},
|
|
390
|
+
|
|
391
|
+
// The "v-model" directive binds the value of an input element to a model property
|
|
271
392
|
"v-model": ([model, property, event]: any[], vnode: VnodeWithDom, oldVnode?: VnodeWithDom) => {
|
|
272
393
|
let value;
|
|
394
|
+
// This function updates the model property when the input element's value changes
|
|
273
395
|
let handler = (e: Event) => (model[property] = (e.target as DomElement & Record<string, any>).value);
|
|
274
396
|
if (vnode.tag === "input") {
|
|
397
|
+
// If the element is an input, use the "input" event by default
|
|
275
398
|
event = event || "oninput";
|
|
399
|
+
// Depending on the type of input element, use a different handler function
|
|
276
400
|
switch (vnode.props.type) {
|
|
277
401
|
case "checkbox": {
|
|
278
402
|
if (Array.isArray(model[property])) {
|
|
403
|
+
// If the model property is an array, add or remove the value from the array when the checkbox is checked or unchecked
|
|
279
404
|
handler = (e: Event) => {
|
|
280
405
|
let val = (e.target as DomElement & Record<string, any>).value;
|
|
281
406
|
let idx = model[property].indexOf(val);
|
|
@@ -285,8 +410,10 @@ export const directives: Directives = {
|
|
|
285
410
|
model[property].splice(idx, 1);
|
|
286
411
|
}
|
|
287
412
|
};
|
|
413
|
+
// If the value is in the array, set the checkbox to be checked
|
|
288
414
|
value = model[property].indexOf(vnode.dom.value) !== -1;
|
|
289
415
|
} else if ("value" in vnode.props) {
|
|
416
|
+
// If the input element has a "value" attribute, use it to determine the checked state
|
|
290
417
|
handler = () => {
|
|
291
418
|
if (model[property] === vnode.props.value) {
|
|
292
419
|
model[property] = null;
|
|
@@ -296,29 +423,36 @@ export const directives: Directives = {
|
|
|
296
423
|
};
|
|
297
424
|
value = model[property] === vnode.props.value;
|
|
298
425
|
} else {
|
|
426
|
+
// If there is no "value" attribute, use a boolean value for the model property
|
|
299
427
|
handler = () => (model[property] = !model[property]);
|
|
300
428
|
value = model[property];
|
|
301
429
|
}
|
|
430
|
+
// Set the "checked" attribute on the input element
|
|
302
431
|
// eslint-disable-next-line no-use-before-define
|
|
303
432
|
sharedSetAttribute("checked", value, vnode);
|
|
304
433
|
break;
|
|
305
434
|
}
|
|
306
435
|
case "radio": {
|
|
436
|
+
// If the element is a radio button, set the "checked" attribute based on the value of the model property
|
|
307
437
|
// eslint-disable-next-line no-use-before-define
|
|
308
438
|
sharedSetAttribute("checked", model[property] === vnode.dom.value, vnode);
|
|
309
439
|
break;
|
|
310
440
|
}
|
|
311
441
|
default: {
|
|
442
|
+
// For all other input types, set the "value" attribute based on the value of the model property
|
|
312
443
|
// eslint-disable-next-line no-use-before-define
|
|
313
444
|
sharedSetAttribute("value", model[property], vnode);
|
|
314
445
|
}
|
|
315
446
|
}
|
|
316
447
|
} else if (vnode.tag === "select") {
|
|
448
|
+
// If the element is a select element, use the "click" event by default
|
|
317
449
|
event = event || "onclick";
|
|
318
450
|
if (vnode.props.multiple) {
|
|
451
|
+
// If the select element allows multiple selections, update the model property with an array of selected values
|
|
319
452
|
handler = (e: Event & Record<string, any>) => {
|
|
320
453
|
let val = (e.target as DomElement & Record<string, any>).value;
|
|
321
454
|
if (e.ctrlKey) {
|
|
455
|
+
// If the Ctrl key is pressed, add or remove the value from the array
|
|
322
456
|
let idx = model[property].indexOf(val);
|
|
323
457
|
if (idx === -1) {
|
|
324
458
|
model[property].push(val);
|
|
@@ -326,10 +460,12 @@ export const directives: Directives = {
|
|
|
326
460
|
model[property].splice(idx, 1);
|
|
327
461
|
}
|
|
328
462
|
} else {
|
|
463
|
+
// If the Ctrl key is not pressed, set the model property to an array with the selected value
|
|
329
464
|
model[property].splice(0, model[property].length);
|
|
330
465
|
model[property].push(val);
|
|
331
466
|
}
|
|
332
467
|
};
|
|
468
|
+
// Set the "selected" attribute on the options based on whether they are in the model property array
|
|
333
469
|
vnode.children.forEach((child: VnodeInterface) => {
|
|
334
470
|
if (child.tag === "option") {
|
|
335
471
|
let value = "value" in child.props ? child.props.value : child.children.join("").trim();
|
|
@@ -337,6 +473,7 @@ export const directives: Directives = {
|
|
|
337
473
|
}
|
|
338
474
|
});
|
|
339
475
|
} else {
|
|
476
|
+
// If the select element does not allow multiple selections, set the "selected" attribute on the options based on the value of the model property
|
|
340
477
|
vnode.children.forEach((child: VnodeInterface) => {
|
|
341
478
|
if (child.tag === "option") {
|
|
342
479
|
let value = "value" in child.props ? child.props.value : child.children.join("").trim();
|
|
@@ -345,20 +482,23 @@ export const directives: Directives = {
|
|
|
345
482
|
});
|
|
346
483
|
}
|
|
347
484
|
} else if (vnode.tag === "textarea") {
|
|
485
|
+
// If the element is a textarea, use the "input" event by default
|
|
348
486
|
event = event || "oninput";
|
|
487
|
+
// Set the textarea's content to the value of the model property
|
|
349
488
|
vnode.children = [model[property]];
|
|
350
489
|
}
|
|
351
490
|
|
|
352
491
|
// We assume that the prev handler if any will not be changed by the user across patchs
|
|
353
492
|
let prevHandler = vnode.props[event];
|
|
354
493
|
|
|
494
|
+
// Set the event handler on the element
|
|
355
495
|
// eslint-disable-next-line no-use-before-define
|
|
356
496
|
sharedSetAttribute(
|
|
357
497
|
event,
|
|
358
498
|
(e: Event) => {
|
|
359
499
|
handler(e);
|
|
360
500
|
|
|
361
|
-
// If the
|
|
501
|
+
// If the previous handler is defined, call it after the model has been updated
|
|
362
502
|
if (prevHandler) {
|
|
363
503
|
prevHandler(e);
|
|
364
504
|
}
|
|
@@ -368,43 +508,73 @@ export const directives: Directives = {
|
|
|
368
508
|
);
|
|
369
509
|
},
|
|
370
510
|
|
|
511
|
+
// The "v-create" directive is called when a new virtual node is created.
|
|
512
|
+
// The provided callback function is called with the new virtual node as an argument.
|
|
513
|
+
// This directive is only called once per virtual node, when it is first created.
|
|
371
514
|
// eslint-disable-next-line no-unused-vars
|
|
372
515
|
"v-create": (callback: (vnode: VnodeWithDom) => void, vnode: VnodeWithDom, oldVnode?: VnodeWithDom) => {
|
|
516
|
+
// If this is not an update, call the callback function with the new virtual node
|
|
373
517
|
if (!oldVnode) {
|
|
374
|
-
callback(vnode);
|
|
518
|
+
let cleanup = callback(vnode);
|
|
519
|
+
|
|
520
|
+
// If the callback function returns a function, call it when the update is gonna be cleaned up
|
|
521
|
+
if (typeof cleanup === "function") {
|
|
522
|
+
onCleanup(cleanup);
|
|
523
|
+
}
|
|
375
524
|
}
|
|
376
525
|
},
|
|
377
526
|
|
|
527
|
+
// The "v-update" directive is called when an existing virtual node is updated.
|
|
528
|
+
// The provided callback function is called with the new and old virtual nodes as arguments.
|
|
529
|
+
// This directive is only called once per virtual node update.
|
|
378
530
|
"v-update": (
|
|
379
531
|
// eslint-disable-next-line no-unused-vars
|
|
380
532
|
callback: (vnode: VnodeWithDom, oldVnode: VnodeWithDom) => void,
|
|
381
533
|
vnode: VnodeWithDom,
|
|
382
534
|
oldVnode?: VnodeWithDom
|
|
383
535
|
) => {
|
|
536
|
+
// If this is an update, call the callback function with the new and old virtual nodes
|
|
384
537
|
if (oldVnode) {
|
|
385
|
-
callback(vnode, oldVnode);
|
|
538
|
+
let cleanup = callback(vnode, oldVnode);
|
|
539
|
+
|
|
540
|
+
// If the callback function returns a function, call it when the update is gonna be cleaned up
|
|
541
|
+
if (typeof cleanup === "function") {
|
|
542
|
+
onCleanup(cleanup);
|
|
543
|
+
}
|
|
386
544
|
}
|
|
387
545
|
},
|
|
388
546
|
|
|
547
|
+
// The "v-cleanup" directive is called when the update is cleaned up.
|
|
548
|
+
// The provided callback function is called with the old virtual node as an argument.
|
|
549
|
+
// This directive is only called once per virtual node, when the update is cleaned up.
|
|
389
550
|
"v-cleanup": (
|
|
390
551
|
// eslint-disable-next-line no-unused-vars
|
|
391
552
|
callback: (vnode: VnodeWithDom, oldVnode?: VnodeWithDom) => void,
|
|
392
553
|
vnode: VnodeWithDom,
|
|
393
554
|
oldVnode?: VnodeWithDom
|
|
394
555
|
) => {
|
|
556
|
+
// Add the callback function to the list of cleanup functions to be called when the update is cleaned up
|
|
395
557
|
onCleanup(() => callback(vnode, oldVnode));
|
|
396
558
|
}
|
|
397
559
|
};
|
|
398
|
-
|
|
560
|
+
// Add a directive to the global directives object, with the key being the name
|
|
561
|
+
// preceded by "v-". Also add the name to the global reservedProps object.
|
|
399
562
|
export function directive(name: string, directive: Directive) {
|
|
400
563
|
let directiveName = `v-${name}`;
|
|
401
564
|
directives[directiveName] = directive;
|
|
402
565
|
reservedProps[directiveName] = true;
|
|
403
566
|
}
|
|
404
567
|
|
|
405
|
-
|
|
568
|
+
// Set an attribute on a virtual DOM node and update the actual DOM element.
|
|
569
|
+
// If the attribute value is a function, add an event listener for the attribute
|
|
570
|
+
// name to the DOM element represented by mainVnode.
|
|
571
|
+
// If oldVnode is provided, compare the new attribute value to the old value
|
|
572
|
+
// and only update the attribute if the values are different.
|
|
406
573
|
function sharedSetAttribute(name: string, value: any, newVnode: VnodeWithDom, oldVnode?: VnodeWithDom): void | boolean {
|
|
574
|
+
// If the attribute value is a function, add an event listener for the attribute
|
|
575
|
+
// name to the DOM element represented by mainVnode.
|
|
407
576
|
if (typeof value === "function") {
|
|
577
|
+
// Only add the event listener if it hasn't been added yet.
|
|
408
578
|
if (name in eventListenerNames === false) {
|
|
409
579
|
(mainVnode as VnodeWithDom).dom.addEventListener(name.slice(2), eventListener);
|
|
410
580
|
eventListenerNames[name] = true;
|
|
@@ -413,6 +583,8 @@ function sharedSetAttribute(name: string, value: any, newVnode: VnodeWithDom, ol
|
|
|
413
583
|
return;
|
|
414
584
|
}
|
|
415
585
|
|
|
586
|
+
// If the attribute is present on the DOM element and newVnode is not an SVG,
|
|
587
|
+
// update the attribute if the value has changed.
|
|
416
588
|
if (name in newVnode.dom && newVnode.isSVG === false) {
|
|
417
589
|
// eslint-disable-next-line eqeqeq
|
|
418
590
|
if (newVnode.dom[name] != value) {
|
|
@@ -421,6 +593,8 @@ function sharedSetAttribute(name: string, value: any, newVnode: VnodeWithDom, ol
|
|
|
421
593
|
return;
|
|
422
594
|
}
|
|
423
595
|
|
|
596
|
+
// If oldVnode is not provided or the attribute value has changed, update the
|
|
597
|
+
// attribute on the DOM element.
|
|
424
598
|
if (!oldVnode || value !== oldVnode.props[name]) {
|
|
425
599
|
if (value === false) {
|
|
426
600
|
newVnode.dom.removeAttribute(name);
|
|
@@ -430,6 +604,8 @@ function sharedSetAttribute(name: string, value: any, newVnode: VnodeWithDom, ol
|
|
|
430
604
|
}
|
|
431
605
|
}
|
|
432
606
|
|
|
607
|
+
// Set an attribute on a virtual DOM node and update the actual DOM element.
|
|
608
|
+
// Skip the attribute if it is in the reservedProps object.
|
|
433
609
|
export function setAttribute(name: string, value: any, newVnode: VnodeWithDom, oldVnode?: VnodeWithDom): void {
|
|
434
610
|
if (name in reservedProps) {
|
|
435
611
|
return;
|
|
@@ -438,7 +614,17 @@ export function setAttribute(name: string, value: any, newVnode: VnodeWithDom, o
|
|
|
438
614
|
sharedSetAttribute(name, value, newVnode as VnodeWithDom, oldVnode);
|
|
439
615
|
}
|
|
440
616
|
|
|
617
|
+
// Update the attributes on a virtual DOM node. If oldVnode is provided, remove
|
|
618
|
+
// attributes from the DOM element that are not present in newVnode.props but are
|
|
619
|
+
// present in oldVnode.props. Then, iterate over the attributes in newVnode.props
|
|
620
|
+
// and update the DOM element with the attributes using the sharedSetAttribute
|
|
621
|
+
// function. If an attribute is in the reservedProps object and has a corresponding
|
|
622
|
+
// directive in the directives object, call the directive with the attribute value
|
|
623
|
+
// and the two virtual DOM nodes as arguments. If the directive returns false, exit
|
|
624
|
+
// the loop.
|
|
441
625
|
export function updateAttributes(newVnode: VnodeWithDom, oldVnode?: VnodeWithDom): void {
|
|
626
|
+
// If oldVnode is provided, remove attributes from the DOM element that are not
|
|
627
|
+
// present in newVnode.props but are present in oldVnode.props.
|
|
442
628
|
if (oldVnode) {
|
|
443
629
|
for (let name in oldVnode.props) {
|
|
444
630
|
if (name in newVnode.props === false && name in eventListenerNames === false && name in reservedProps === false) {
|
|
@@ -451,8 +637,13 @@ export function updateAttributes(newVnode: VnodeWithDom, oldVnode?: VnodeWithDom
|
|
|
451
637
|
}
|
|
452
638
|
}
|
|
453
639
|
|
|
640
|
+
// Iterate over the attributes in newVnode.props and update the DOM element with
|
|
641
|
+
// the attributes using the sharedSetAttribute function.
|
|
454
642
|
for (let name in newVnode.props) {
|
|
455
643
|
if (name in reservedProps) {
|
|
644
|
+
// If there is a directive for the attribute, call it with the attribute value
|
|
645
|
+
// and the two virtual DOM nodes as arguments. If the directive returns false,
|
|
646
|
+
// exit the loop.
|
|
456
647
|
if (name in directives && directives[name](newVnode.props[name], newVnode, oldVnode) === false) {
|
|
457
648
|
break;
|
|
458
649
|
}
|
|
@@ -466,53 +657,73 @@ export function updateAttributes(newVnode: VnodeWithDom, oldVnode?: VnodeWithDom
|
|
|
466
657
|
|
|
467
658
|
// Patch a DOM node with a new VNode tree
|
|
468
659
|
export function patch(newVnode: VnodeWithDom, oldVnode?: VnodeWithDom): void {
|
|
660
|
+
// Get the children of the new and old virtual DOM nodes
|
|
469
661
|
let newTree = newVnode.children;
|
|
470
662
|
let oldTree = oldVnode?.children || [];
|
|
663
|
+
// Get the length of the old tree
|
|
471
664
|
let oldTreeLength = oldTree.length;
|
|
472
665
|
|
|
473
|
-
//
|
|
666
|
+
// If the old tree has children and the first child of the new tree is a VNode with a "key"
|
|
667
|
+
// attribute and the first child of the old tree is a VNode with a "key" attribute, update
|
|
668
|
+
// the DOM element in place by comparing the keys of the nodes in the trees.
|
|
474
669
|
if (oldTreeLength && newTree[0] instanceof Vnode && "key" in newTree[0].props && "key" in oldTree[0].props) {
|
|
670
|
+
// Get the lengths of the new and old trees
|
|
475
671
|
let newTreeLength = newTree.length;
|
|
476
672
|
|
|
673
|
+
// Create an object that maps keys to indices in the old tree
|
|
477
674
|
let oldKeyedList: { [key: string]: number } = {};
|
|
478
675
|
for (let i = 0; i < oldTreeLength; i++) {
|
|
479
676
|
oldKeyedList[oldTree[i].props.key] = i;
|
|
480
677
|
}
|
|
481
678
|
|
|
679
|
+
// Create an object that maps keys to indices in the new tree
|
|
482
680
|
let newKeyedList: { [key: string]: number } = {};
|
|
483
681
|
for (let i = 0; i < newTreeLength; i++) {
|
|
484
682
|
newKeyedList[newTree[i].props.key] = i;
|
|
485
683
|
}
|
|
486
684
|
|
|
685
|
+
// Iterate over the new tree
|
|
487
686
|
for (let i = 0; i < newTreeLength; i++) {
|
|
687
|
+
// Get the current new child and the corresponding old child
|
|
488
688
|
let newChild = newTree[i];
|
|
489
689
|
let oldChild = oldTree[oldKeyedList[newChild.props.key]];
|
|
690
|
+
// Initialize a flag to determine whether to patch the child
|
|
490
691
|
let shouldPatch = true;
|
|
491
692
|
|
|
693
|
+
// If the old child exists, update the DOM element of the new child to match the old child's DOM element
|
|
492
694
|
if (oldChild) {
|
|
493
695
|
newChild.dom = oldChild.dom;
|
|
696
|
+
// If the new and old children have the same "v-keep" attribute value, update the children of the new child to match the old child's children
|
|
494
697
|
if ("v-keep" in newChild.props && newChild.props["v-keep"] === oldChild.props["v-keep"]) {
|
|
495
698
|
newChild.children = oldChild.children;
|
|
699
|
+
// Set the shouldPatch flag to false
|
|
496
700
|
shouldPatch = false;
|
|
497
701
|
} else {
|
|
498
702
|
updateAttributes(newChild, oldChild);
|
|
499
703
|
}
|
|
704
|
+
|
|
705
|
+
// If the old child does not exist, create a new DOM element for the new child and update its attributes
|
|
500
706
|
} else {
|
|
501
707
|
newChild.dom = createDomElement(newChild.tag, newChild.isSVG);
|
|
502
708
|
updateAttributes(newChild);
|
|
503
709
|
}
|
|
504
710
|
|
|
711
|
+
// If the new child's DOM element is not the i-th child of the parent DOM element, insert it
|
|
505
712
|
if (!newVnode.dom.childNodes[i]) {
|
|
506
713
|
newVnode.dom.appendChild(newChild.dom);
|
|
714
|
+
|
|
715
|
+
// If the new child's DOM element is not the same as the i-th child of the parent DOM element, replace the i-th child with the new child's DOM element
|
|
507
716
|
} else if (newVnode.dom.childNodes[i] !== newChild.dom) {
|
|
508
717
|
newVnode.dom.replaceChild(newChild.dom, newVnode.dom.childNodes[i]);
|
|
509
718
|
}
|
|
510
719
|
|
|
720
|
+
// If the shouldPatch flag is true, recursively call the patch function on the new child, passing in the old child as the second argument
|
|
511
721
|
shouldPatch && patch(newChild, oldChild);
|
|
512
722
|
}
|
|
513
723
|
|
|
514
|
-
// For the rest of the children, we should remove them
|
|
724
|
+
// For the rest of the children, we should remove them from the DOM
|
|
515
725
|
for (let i = newTreeLength; i < oldTreeLength; i++) {
|
|
726
|
+
// If the i-th child of the old tree does not have a corresponding key in the new tree, remove its DOM element from the parent DOM element
|
|
516
727
|
if (!newKeyedList[oldTree[i].props.key]) {
|
|
517
728
|
oldTree[i].dom.parentNode && oldTree[i].dom.parentNode.removeChild(oldTree[i].dom);
|
|
518
729
|
}
|
|
@@ -520,21 +731,27 @@ export function patch(newVnode: VnodeWithDom, oldVnode?: VnodeWithDom): void {
|
|
|
520
731
|
return;
|
|
521
732
|
}
|
|
522
733
|
|
|
734
|
+
// If the new tree has no children, set the text content of the parent DOM element to an empty string
|
|
523
735
|
if (newTree.length === 0) {
|
|
524
736
|
newVnode.dom.textContent = "";
|
|
525
737
|
return;
|
|
526
738
|
}
|
|
527
739
|
|
|
740
|
+
// Set the global current object to the new and old virtual DOM nodes
|
|
528
741
|
current.vnode = newVnode;
|
|
529
742
|
current.oldVnode = oldVnode;
|
|
530
743
|
|
|
531
|
-
//
|
|
744
|
+
// Flatten the new tree
|
|
532
745
|
for (let i = 0; i < newTree.length; i++) {
|
|
533
746
|
let newChild = newTree[i];
|
|
534
747
|
|
|
748
|
+
// If the new child is a Vnode and is not a text node
|
|
535
749
|
if (newChild instanceof Vnode && newChild.tag !== textTag) {
|
|
750
|
+
// If the tag of the new child is not a string, it is a component
|
|
536
751
|
if (typeof newChild.tag !== "string") {
|
|
752
|
+
// Set the current component to the tag of the new child
|
|
537
753
|
current.component = newChild.tag;
|
|
754
|
+
// Replace the new child with the result of calling its view or bind method, passing in the props and children as arguments
|
|
538
755
|
newTree.splice(
|
|
539
756
|
i--,
|
|
540
757
|
1,
|
|
@@ -546,57 +763,81 @@ export function patch(newVnode: VnodeWithDom, oldVnode?: VnodeWithDom): void {
|
|
|
546
763
|
continue;
|
|
547
764
|
}
|
|
548
765
|
|
|
766
|
+
// Set the isSVG flag for the new child if it is an SVG element or if the parent is an SVG element
|
|
549
767
|
newChild.isSVG = newVnode.isSVG || newChild.tag === "svg";
|
|
550
768
|
|
|
769
|
+
// If there is an old child at the same index
|
|
551
770
|
if (i < oldTreeLength) {
|
|
552
771
|
let oldChild = oldTree[i];
|
|
772
|
+
// If the tag of the new child is the same as the tag of the old child
|
|
553
773
|
if (newChild.tag === oldChild.tag) {
|
|
774
|
+
// Set the dom property of the new child to the dom property of the old child
|
|
554
775
|
newChild.dom = oldChild.dom;
|
|
776
|
+
// If the v-keep prop is the same for both the new and old child, set the children of the new child to the children of the old child
|
|
555
777
|
if ("v-keep" in newChild.props && newChild.props["v-keep"] === oldChild.props["v-keep"]) {
|
|
556
778
|
newChild.children = oldChild.children;
|
|
557
779
|
continue;
|
|
558
780
|
}
|
|
559
781
|
|
|
782
|
+
// Update the attributes of the new child based on the old child
|
|
560
783
|
updateAttributes(newChild as VnodeWithDom, oldChild);
|
|
784
|
+
// Recursively patch the new and old children
|
|
561
785
|
patch(newChild as VnodeWithDom, oldChild);
|
|
562
786
|
continue;
|
|
563
787
|
}
|
|
564
788
|
|
|
789
|
+
// Create a new dom element for the new child
|
|
565
790
|
newChild.dom = createDomElement(newChild.tag, newChild.isSVG);
|
|
791
|
+
// Update the attributes of the new child
|
|
566
792
|
updateAttributes(newChild as VnodeWithDom);
|
|
793
|
+
// Replace the old child in the dom with the new child
|
|
567
794
|
newVnode.dom.replaceChild(newChild.dom, oldChild.dom);
|
|
795
|
+
// Recursively patch the new child
|
|
568
796
|
patch(newChild as VnodeWithDom);
|
|
569
797
|
continue;
|
|
570
798
|
}
|
|
571
799
|
|
|
800
|
+
// Create a new dom element for the new child
|
|
572
801
|
newChild.dom = createDomElement(newChild.tag, newChild.isSVG);
|
|
802
|
+
// Update the attributes of the new child
|
|
573
803
|
updateAttributes(newChild as VnodeWithDom);
|
|
804
|
+
// Append the new child to the dom
|
|
574
805
|
newVnode.dom.appendChild(newChild.dom);
|
|
806
|
+
// Recursively patch the new child
|
|
575
807
|
patch(newChild as VnodeWithDom);
|
|
576
808
|
continue;
|
|
577
809
|
}
|
|
578
810
|
|
|
811
|
+
// If the new child is an array, flatten it and continue the loop
|
|
579
812
|
if (Array.isArray(newChild)) {
|
|
580
813
|
newTree.splice(i--, 1, ...newChild);
|
|
581
814
|
continue;
|
|
582
815
|
}
|
|
583
816
|
|
|
817
|
+
// If the new child is null or undefined, remove it from the new tree and continue the loop
|
|
584
818
|
if (newChild === null || newChild === undefined) {
|
|
585
819
|
newTree.splice(i--, 1);
|
|
586
820
|
continue;
|
|
587
821
|
}
|
|
588
822
|
|
|
823
|
+
// If the new child is not a Vnode, wrap it in a text Vnode
|
|
589
824
|
newTree[i] = new Vnode(textTag, {}, []);
|
|
825
|
+
// If the new child is a Vnode, set the dom property of the text Vnode to the dom property of the new child
|
|
590
826
|
if (newChild instanceof Vnode) {
|
|
591
827
|
newTree[i].dom = newChild.dom;
|
|
828
|
+
// Set the new child to the text content of its dom property
|
|
592
829
|
newChild = (newChild as VnodeWithDom).dom.textContent;
|
|
593
830
|
}
|
|
594
831
|
|
|
832
|
+
// If there is an old child at the same index
|
|
595
833
|
if (i < oldTreeLength) {
|
|
596
834
|
let oldChild = oldTree[i];
|
|
597
835
|
|
|
836
|
+
// If the old child is a text node
|
|
598
837
|
if (oldChild.tag === textTag) {
|
|
838
|
+
// Set the dom property of the text Vnode to the dom property of the old child
|
|
599
839
|
newTree[i].dom = oldChild.dom;
|
|
840
|
+
// If the text content of the old child is different from the new child, update the text content of the old child
|
|
600
841
|
// eslint-disable-next-line eqeqeq
|
|
601
842
|
if (newChild != oldChild.dom.textContent) {
|
|
602
843
|
oldChild.dom.textContent = newChild;
|
|
@@ -604,61 +845,93 @@ export function patch(newVnode: VnodeWithDom, oldVnode?: VnodeWithDom): void {
|
|
|
604
845
|
continue;
|
|
605
846
|
}
|
|
606
847
|
|
|
848
|
+
// Create a new text node for the new child
|
|
607
849
|
newTree[i].dom = document.createTextNode(newChild);
|
|
850
|
+
// Replace the old child in the dom with the new text node
|
|
608
851
|
newVnode.dom.replaceChild(newTree[i].dom, oldChild.dom);
|
|
609
852
|
continue;
|
|
610
853
|
}
|
|
611
854
|
|
|
855
|
+
// Create a new text node for the new child
|
|
612
856
|
newTree[i].dom = document.createTextNode(newChild);
|
|
857
|
+
// Append the new text node to the dom
|
|
613
858
|
newVnode.dom.appendChild(newTree[i].dom);
|
|
614
859
|
}
|
|
615
860
|
|
|
861
|
+
// Remove any old children that are no longer present in the new tree
|
|
616
862
|
for (let i = newTree.length; i < oldTreeLength; i++) {
|
|
617
863
|
newVnode.dom.removeChild(oldTree[i].dom);
|
|
618
864
|
}
|
|
619
865
|
}
|
|
620
866
|
|
|
867
|
+
// Update the main Vnode
|
|
621
868
|
export function update(): void | string {
|
|
869
|
+
// If the main Vnode exists
|
|
622
870
|
if (mainVnode) {
|
|
871
|
+
// Call any cleanup functions that are registered with the onCleanupSet set
|
|
623
872
|
callSet(onCleanupSet);
|
|
873
|
+
// Store a reference to the old main Vnode
|
|
624
874
|
let oldMainVnode = mainVnode;
|
|
875
|
+
// Create a new main Vnode with the main component as its only child
|
|
625
876
|
mainVnode = new Vnode(oldMainVnode.tag, oldMainVnode.props, [mainComponent]) as VnodeWithDom;
|
|
626
877
|
mainVnode.dom = oldMainVnode.dom;
|
|
627
878
|
mainVnode.isSVG = oldMainVnode.isSVG;
|
|
879
|
+
|
|
880
|
+
// Recursively patch the new and old main Vnodes
|
|
628
881
|
patch(mainVnode, oldMainVnode);
|
|
882
|
+
|
|
883
|
+
// Call any update or mount functions that are registered with the onUpdateSet or onMountSet set
|
|
629
884
|
callSet(isMounted ? onUpdateSet : onMountSet);
|
|
885
|
+
|
|
886
|
+
// Set the isMounted flag to true
|
|
630
887
|
isMounted = true;
|
|
888
|
+
|
|
889
|
+
// Reset the current vnode, oldVnode, and component properties
|
|
631
890
|
current.vnode = null;
|
|
632
891
|
current.oldVnode = null;
|
|
633
892
|
current.component = null;
|
|
893
|
+
|
|
894
|
+
// If the code is running in a Node.js environment, return the inner HTML of the main Vnode's dom element
|
|
634
895
|
if (isNodeJs) {
|
|
635
896
|
return mainVnode.dom.innerHTML;
|
|
636
897
|
}
|
|
637
898
|
}
|
|
638
899
|
}
|
|
639
900
|
|
|
901
|
+
// Unmount the main Vnode
|
|
640
902
|
export function unmount() {
|
|
903
|
+
// If the main Vnode exists
|
|
641
904
|
if (mainVnode) {
|
|
905
|
+
// Set the main component to a null Vnode
|
|
642
906
|
mainComponent = new Vnode(() => null, {}, []) as VnodeComponentInterface;
|
|
907
|
+
// Update the main Vnode
|
|
643
908
|
let result = update();
|
|
909
|
+
// Call any unmount functions that are registered with the onUnmountSet set
|
|
644
910
|
callSet(onUnmountSet);
|
|
645
911
|
|
|
912
|
+
// Remove any event listeners that were added to the main Vnode's dom element
|
|
646
913
|
for (let name in eventListenerNames) {
|
|
647
914
|
mainVnode.dom.removeEventListener(name.slice(2).toLowerCase(), eventListener);
|
|
648
915
|
Reflect.deleteProperty(eventListenerNames, name);
|
|
649
916
|
}
|
|
650
917
|
|
|
918
|
+
// Reset the main component and main Vnode
|
|
651
919
|
mainComponent = null;
|
|
652
920
|
mainVnode = null;
|
|
921
|
+
// Set the isMounted flag to false
|
|
653
922
|
isMounted = false;
|
|
923
|
+
// Reset the current vnode, oldVnode, and component properties
|
|
654
924
|
current.vnode = null;
|
|
655
925
|
current.oldVnode = null;
|
|
656
926
|
current.component = null;
|
|
927
|
+
// Return the result of updating the main Vnode
|
|
657
928
|
return result;
|
|
658
929
|
}
|
|
659
930
|
}
|
|
660
|
-
|
|
931
|
+
// This function takes in a DOM element or a DOM element selector and a component to be mounted on it.
|
|
661
932
|
export function mount(dom, component) {
|
|
933
|
+
// Check if the 'dom' argument is a string. If it is, select the first element that matches the given selector.
|
|
934
|
+
// Otherwise, use the 'dom' argument as the container.
|
|
662
935
|
let container =
|
|
663
936
|
typeof dom === "string"
|
|
664
937
|
? isNodeJs
|
|
@@ -666,23 +939,35 @@ export function mount(dom, component) {
|
|
|
666
939
|
: document.querySelectorAll(dom)[0]
|
|
667
940
|
: dom;
|
|
668
941
|
|
|
942
|
+
// Check if the 'component' argument is a Vnode component or a regular component.
|
|
943
|
+
// If it's a regular component, create a new Vnode component using the 'component' argument as the tag.
|
|
944
|
+
// If it's not a component at all, create a new Vnode component with the 'component' argument as the rendering function.
|
|
669
945
|
let vnodeComponent = isVnodeComponent(component)
|
|
670
946
|
? component
|
|
671
947
|
: isComponent(component)
|
|
672
948
|
? new Vnode(component, {}, [])
|
|
673
949
|
: new Vnode(() => component, {}, []);
|
|
674
950
|
|
|
951
|
+
// If a main component already exists and it's not the same as the current 'vnodeComponent', unmount it.
|
|
675
952
|
if (mainComponent && mainComponent.tag !== vnodeComponent.tag) {
|
|
676
953
|
unmount();
|
|
677
954
|
}
|
|
678
955
|
|
|
956
|
+
// Set the 'vnodeComponent' as the main component.
|
|
679
957
|
mainComponent = vnodeComponent as VnodeComponentInterface;
|
|
958
|
+
// Convert the container element to a Vnode.
|
|
680
959
|
mainVnode = domToVnode(container);
|
|
960
|
+
// Update the DOM with the new component.
|
|
681
961
|
return update();
|
|
682
962
|
}
|
|
683
963
|
|
|
964
|
+
// This is a utility function for creating Vnode objects.
|
|
965
|
+
// It takes in a tag or component, and optional props and children arguments.
|
|
684
966
|
export const v: V = (tagOrComponent, props = {}, ...children) => {
|
|
967
|
+
// Return a new Vnode object using the given arguments.
|
|
685
968
|
return new Vnode(tagOrComponent, props || {}, children);
|
|
686
969
|
};
|
|
687
970
|
|
|
688
|
-
|
|
971
|
+
// This utility function creates a fragment Vnode.
|
|
972
|
+
// It takes in a placeholder and the children arguments, returns only the children.
|
|
973
|
+
v.fragment = (_: VnodeProperties, ...children: Children) => children;
|