valyrian.js 7.0.2 → 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 -11
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/index.mjs +15 -11
- package/dist/router/index.d.ts +1 -1
- package/dist/router/index.d.ts.map +1 -1
- package/lib/index.ts +331 -56
- 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,32 +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
|
},
|
|
271
|
-
"v-model": ([model, property, event]: any[], vnode: VnodeWithDom, oldVnode?: VnodeWithDom) => {
|
|
272
|
-
// We try to identify if the old vnode has the v-model directive initialized or not
|
|
273
|
-
// If it is, we don't need to reinitialize it
|
|
274
|
-
if (oldVnode && oldVnode.dom === vnode.dom) {
|
|
275
|
-
let [oldModel, oldProperty, oldEvent] = oldVnode.props["v-model"] || [];
|
|
276
|
-
|
|
277
|
-
if (oldModel === model && oldProperty === property && oldEvent === event) {
|
|
278
|
-
return;
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
390
|
|
|
391
|
+
// The "v-model" directive binds the value of an input element to a model property
|
|
392
|
+
"v-model": ([model, property, event]: any[], vnode: VnodeWithDom, oldVnode?: VnodeWithDom) => {
|
|
282
393
|
let value;
|
|
394
|
+
// This function updates the model property when the input element's value changes
|
|
283
395
|
let handler = (e: Event) => (model[property] = (e.target as DomElement & Record<string, any>).value);
|
|
284
396
|
if (vnode.tag === "input") {
|
|
397
|
+
// If the element is an input, use the "input" event by default
|
|
285
398
|
event = event || "oninput";
|
|
399
|
+
// Depending on the type of input element, use a different handler function
|
|
286
400
|
switch (vnode.props.type) {
|
|
287
401
|
case "checkbox": {
|
|
288
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
|
|
289
404
|
handler = (e: Event) => {
|
|
290
405
|
let val = (e.target as DomElement & Record<string, any>).value;
|
|
291
406
|
let idx = model[property].indexOf(val);
|
|
@@ -295,8 +410,10 @@ export const directives: Directives = {
|
|
|
295
410
|
model[property].splice(idx, 1);
|
|
296
411
|
}
|
|
297
412
|
};
|
|
413
|
+
// If the value is in the array, set the checkbox to be checked
|
|
298
414
|
value = model[property].indexOf(vnode.dom.value) !== -1;
|
|
299
415
|
} else if ("value" in vnode.props) {
|
|
416
|
+
// If the input element has a "value" attribute, use it to determine the checked state
|
|
300
417
|
handler = () => {
|
|
301
418
|
if (model[property] === vnode.props.value) {
|
|
302
419
|
model[property] = null;
|
|
@@ -306,29 +423,36 @@ export const directives: Directives = {
|
|
|
306
423
|
};
|
|
307
424
|
value = model[property] === vnode.props.value;
|
|
308
425
|
} else {
|
|
426
|
+
// If there is no "value" attribute, use a boolean value for the model property
|
|
309
427
|
handler = () => (model[property] = !model[property]);
|
|
310
428
|
value = model[property];
|
|
311
429
|
}
|
|
430
|
+
// Set the "checked" attribute on the input element
|
|
312
431
|
// eslint-disable-next-line no-use-before-define
|
|
313
432
|
sharedSetAttribute("checked", value, vnode);
|
|
314
433
|
break;
|
|
315
434
|
}
|
|
316
435
|
case "radio": {
|
|
436
|
+
// If the element is a radio button, set the "checked" attribute based on the value of the model property
|
|
317
437
|
// eslint-disable-next-line no-use-before-define
|
|
318
438
|
sharedSetAttribute("checked", model[property] === vnode.dom.value, vnode);
|
|
319
439
|
break;
|
|
320
440
|
}
|
|
321
441
|
default: {
|
|
442
|
+
// For all other input types, set the "value" attribute based on the value of the model property
|
|
322
443
|
// eslint-disable-next-line no-use-before-define
|
|
323
444
|
sharedSetAttribute("value", model[property], vnode);
|
|
324
445
|
}
|
|
325
446
|
}
|
|
326
447
|
} else if (vnode.tag === "select") {
|
|
448
|
+
// If the element is a select element, use the "click" event by default
|
|
327
449
|
event = event || "onclick";
|
|
328
450
|
if (vnode.props.multiple) {
|
|
451
|
+
// If the select element allows multiple selections, update the model property with an array of selected values
|
|
329
452
|
handler = (e: Event & Record<string, any>) => {
|
|
330
453
|
let val = (e.target as DomElement & Record<string, any>).value;
|
|
331
454
|
if (e.ctrlKey) {
|
|
455
|
+
// If the Ctrl key is pressed, add or remove the value from the array
|
|
332
456
|
let idx = model[property].indexOf(val);
|
|
333
457
|
if (idx === -1) {
|
|
334
458
|
model[property].push(val);
|
|
@@ -336,10 +460,12 @@ export const directives: Directives = {
|
|
|
336
460
|
model[property].splice(idx, 1);
|
|
337
461
|
}
|
|
338
462
|
} else {
|
|
463
|
+
// If the Ctrl key is not pressed, set the model property to an array with the selected value
|
|
339
464
|
model[property].splice(0, model[property].length);
|
|
340
465
|
model[property].push(val);
|
|
341
466
|
}
|
|
342
467
|
};
|
|
468
|
+
// Set the "selected" attribute on the options based on whether they are in the model property array
|
|
343
469
|
vnode.children.forEach((child: VnodeInterface) => {
|
|
344
470
|
if (child.tag === "option") {
|
|
345
471
|
let value = "value" in child.props ? child.props.value : child.children.join("").trim();
|
|
@@ -347,6 +473,7 @@ export const directives: Directives = {
|
|
|
347
473
|
}
|
|
348
474
|
});
|
|
349
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
|
|
350
477
|
vnode.children.forEach((child: VnodeInterface) => {
|
|
351
478
|
if (child.tag === "option") {
|
|
352
479
|
let value = "value" in child.props ? child.props.value : child.children.join("").trim();
|
|
@@ -355,20 +482,23 @@ export const directives: Directives = {
|
|
|
355
482
|
});
|
|
356
483
|
}
|
|
357
484
|
} else if (vnode.tag === "textarea") {
|
|
485
|
+
// If the element is a textarea, use the "input" event by default
|
|
358
486
|
event = event || "oninput";
|
|
487
|
+
// Set the textarea's content to the value of the model property
|
|
359
488
|
vnode.children = [model[property]];
|
|
360
489
|
}
|
|
361
490
|
|
|
362
491
|
// We assume that the prev handler if any will not be changed by the user across patchs
|
|
363
492
|
let prevHandler = vnode.props[event];
|
|
364
493
|
|
|
494
|
+
// Set the event handler on the element
|
|
365
495
|
// eslint-disable-next-line no-use-before-define
|
|
366
496
|
sharedSetAttribute(
|
|
367
497
|
event,
|
|
368
498
|
(e: Event) => {
|
|
369
499
|
handler(e);
|
|
370
500
|
|
|
371
|
-
// If the
|
|
501
|
+
// If the previous handler is defined, call it after the model has been updated
|
|
372
502
|
if (prevHandler) {
|
|
373
503
|
prevHandler(e);
|
|
374
504
|
}
|
|
@@ -378,43 +508,73 @@ export const directives: Directives = {
|
|
|
378
508
|
);
|
|
379
509
|
},
|
|
380
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.
|
|
381
514
|
// eslint-disable-next-line no-unused-vars
|
|
382
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
|
|
383
517
|
if (!oldVnode) {
|
|
384
|
-
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
|
+
}
|
|
385
524
|
}
|
|
386
525
|
},
|
|
387
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.
|
|
388
530
|
"v-update": (
|
|
389
531
|
// eslint-disable-next-line no-unused-vars
|
|
390
532
|
callback: (vnode: VnodeWithDom, oldVnode: VnodeWithDom) => void,
|
|
391
533
|
vnode: VnodeWithDom,
|
|
392
534
|
oldVnode?: VnodeWithDom
|
|
393
535
|
) => {
|
|
536
|
+
// If this is an update, call the callback function with the new and old virtual nodes
|
|
394
537
|
if (oldVnode) {
|
|
395
|
-
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
|
+
}
|
|
396
544
|
}
|
|
397
545
|
},
|
|
398
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.
|
|
399
550
|
"v-cleanup": (
|
|
400
551
|
// eslint-disable-next-line no-unused-vars
|
|
401
552
|
callback: (vnode: VnodeWithDom, oldVnode?: VnodeWithDom) => void,
|
|
402
553
|
vnode: VnodeWithDom,
|
|
403
554
|
oldVnode?: VnodeWithDom
|
|
404
555
|
) => {
|
|
556
|
+
// Add the callback function to the list of cleanup functions to be called when the update is cleaned up
|
|
405
557
|
onCleanup(() => callback(vnode, oldVnode));
|
|
406
558
|
}
|
|
407
559
|
};
|
|
408
|
-
|
|
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.
|
|
409
562
|
export function directive(name: string, directive: Directive) {
|
|
410
563
|
let directiveName = `v-${name}`;
|
|
411
564
|
directives[directiveName] = directive;
|
|
412
565
|
reservedProps[directiveName] = true;
|
|
413
566
|
}
|
|
414
567
|
|
|
415
|
-
|
|
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.
|
|
416
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.
|
|
417
576
|
if (typeof value === "function") {
|
|
577
|
+
// Only add the event listener if it hasn't been added yet.
|
|
418
578
|
if (name in eventListenerNames === false) {
|
|
419
579
|
(mainVnode as VnodeWithDom).dom.addEventListener(name.slice(2), eventListener);
|
|
420
580
|
eventListenerNames[name] = true;
|
|
@@ -423,6 +583,8 @@ function sharedSetAttribute(name: string, value: any, newVnode: VnodeWithDom, ol
|
|
|
423
583
|
return;
|
|
424
584
|
}
|
|
425
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.
|
|
426
588
|
if (name in newVnode.dom && newVnode.isSVG === false) {
|
|
427
589
|
// eslint-disable-next-line eqeqeq
|
|
428
590
|
if (newVnode.dom[name] != value) {
|
|
@@ -431,6 +593,8 @@ function sharedSetAttribute(name: string, value: any, newVnode: VnodeWithDom, ol
|
|
|
431
593
|
return;
|
|
432
594
|
}
|
|
433
595
|
|
|
596
|
+
// If oldVnode is not provided or the attribute value has changed, update the
|
|
597
|
+
// attribute on the DOM element.
|
|
434
598
|
if (!oldVnode || value !== oldVnode.props[name]) {
|
|
435
599
|
if (value === false) {
|
|
436
600
|
newVnode.dom.removeAttribute(name);
|
|
@@ -440,6 +604,8 @@ function sharedSetAttribute(name: string, value: any, newVnode: VnodeWithDom, ol
|
|
|
440
604
|
}
|
|
441
605
|
}
|
|
442
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.
|
|
443
609
|
export function setAttribute(name: string, value: any, newVnode: VnodeWithDom, oldVnode?: VnodeWithDom): void {
|
|
444
610
|
if (name in reservedProps) {
|
|
445
611
|
return;
|
|
@@ -448,7 +614,17 @@ export function setAttribute(name: string, value: any, newVnode: VnodeWithDom, o
|
|
|
448
614
|
sharedSetAttribute(name, value, newVnode as VnodeWithDom, oldVnode);
|
|
449
615
|
}
|
|
450
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.
|
|
451
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.
|
|
452
628
|
if (oldVnode) {
|
|
453
629
|
for (let name in oldVnode.props) {
|
|
454
630
|
if (name in newVnode.props === false && name in eventListenerNames === false && name in reservedProps === false) {
|
|
@@ -461,8 +637,13 @@ export function updateAttributes(newVnode: VnodeWithDom, oldVnode?: VnodeWithDom
|
|
|
461
637
|
}
|
|
462
638
|
}
|
|
463
639
|
|
|
640
|
+
// Iterate over the attributes in newVnode.props and update the DOM element with
|
|
641
|
+
// the attributes using the sharedSetAttribute function.
|
|
464
642
|
for (let name in newVnode.props) {
|
|
465
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.
|
|
466
647
|
if (name in directives && directives[name](newVnode.props[name], newVnode, oldVnode) === false) {
|
|
467
648
|
break;
|
|
468
649
|
}
|
|
@@ -476,53 +657,73 @@ export function updateAttributes(newVnode: VnodeWithDom, oldVnode?: VnodeWithDom
|
|
|
476
657
|
|
|
477
658
|
// Patch a DOM node with a new VNode tree
|
|
478
659
|
export function patch(newVnode: VnodeWithDom, oldVnode?: VnodeWithDom): void {
|
|
660
|
+
// Get the children of the new and old virtual DOM nodes
|
|
479
661
|
let newTree = newVnode.children;
|
|
480
662
|
let oldTree = oldVnode?.children || [];
|
|
663
|
+
// Get the length of the old tree
|
|
481
664
|
let oldTreeLength = oldTree.length;
|
|
482
665
|
|
|
483
|
-
//
|
|
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.
|
|
484
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
|
|
485
671
|
let newTreeLength = newTree.length;
|
|
486
672
|
|
|
673
|
+
// Create an object that maps keys to indices in the old tree
|
|
487
674
|
let oldKeyedList: { [key: string]: number } = {};
|
|
488
675
|
for (let i = 0; i < oldTreeLength; i++) {
|
|
489
676
|
oldKeyedList[oldTree[i].props.key] = i;
|
|
490
677
|
}
|
|
491
678
|
|
|
679
|
+
// Create an object that maps keys to indices in the new tree
|
|
492
680
|
let newKeyedList: { [key: string]: number } = {};
|
|
493
681
|
for (let i = 0; i < newTreeLength; i++) {
|
|
494
682
|
newKeyedList[newTree[i].props.key] = i;
|
|
495
683
|
}
|
|
496
684
|
|
|
685
|
+
// Iterate over the new tree
|
|
497
686
|
for (let i = 0; i < newTreeLength; i++) {
|
|
687
|
+
// Get the current new child and the corresponding old child
|
|
498
688
|
let newChild = newTree[i];
|
|
499
689
|
let oldChild = oldTree[oldKeyedList[newChild.props.key]];
|
|
690
|
+
// Initialize a flag to determine whether to patch the child
|
|
500
691
|
let shouldPatch = true;
|
|
501
692
|
|
|
693
|
+
// If the old child exists, update the DOM element of the new child to match the old child's DOM element
|
|
502
694
|
if (oldChild) {
|
|
503
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
|
|
504
697
|
if ("v-keep" in newChild.props && newChild.props["v-keep"] === oldChild.props["v-keep"]) {
|
|
505
698
|
newChild.children = oldChild.children;
|
|
699
|
+
// Set the shouldPatch flag to false
|
|
506
700
|
shouldPatch = false;
|
|
507
701
|
} else {
|
|
508
702
|
updateAttributes(newChild, oldChild);
|
|
509
703
|
}
|
|
704
|
+
|
|
705
|
+
// If the old child does not exist, create a new DOM element for the new child and update its attributes
|
|
510
706
|
} else {
|
|
511
707
|
newChild.dom = createDomElement(newChild.tag, newChild.isSVG);
|
|
512
708
|
updateAttributes(newChild);
|
|
513
709
|
}
|
|
514
710
|
|
|
711
|
+
// If the new child's DOM element is not the i-th child of the parent DOM element, insert it
|
|
515
712
|
if (!newVnode.dom.childNodes[i]) {
|
|
516
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
|
|
517
716
|
} else if (newVnode.dom.childNodes[i] !== newChild.dom) {
|
|
518
717
|
newVnode.dom.replaceChild(newChild.dom, newVnode.dom.childNodes[i]);
|
|
519
718
|
}
|
|
520
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
|
|
521
721
|
shouldPatch && patch(newChild, oldChild);
|
|
522
722
|
}
|
|
523
723
|
|
|
524
|
-
// For the rest of the children, we should remove them
|
|
724
|
+
// For the rest of the children, we should remove them from the DOM
|
|
525
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
|
|
526
727
|
if (!newKeyedList[oldTree[i].props.key]) {
|
|
527
728
|
oldTree[i].dom.parentNode && oldTree[i].dom.parentNode.removeChild(oldTree[i].dom);
|
|
528
729
|
}
|
|
@@ -530,21 +731,27 @@ export function patch(newVnode: VnodeWithDom, oldVnode?: VnodeWithDom): void {
|
|
|
530
731
|
return;
|
|
531
732
|
}
|
|
532
733
|
|
|
734
|
+
// If the new tree has no children, set the text content of the parent DOM element to an empty string
|
|
533
735
|
if (newTree.length === 0) {
|
|
534
736
|
newVnode.dom.textContent = "";
|
|
535
737
|
return;
|
|
536
738
|
}
|
|
537
739
|
|
|
740
|
+
// Set the global current object to the new and old virtual DOM nodes
|
|
538
741
|
current.vnode = newVnode;
|
|
539
742
|
current.oldVnode = oldVnode;
|
|
540
743
|
|
|
541
|
-
//
|
|
744
|
+
// Flatten the new tree
|
|
542
745
|
for (let i = 0; i < newTree.length; i++) {
|
|
543
746
|
let newChild = newTree[i];
|
|
544
747
|
|
|
748
|
+
// If the new child is a Vnode and is not a text node
|
|
545
749
|
if (newChild instanceof Vnode && newChild.tag !== textTag) {
|
|
750
|
+
// If the tag of the new child is not a string, it is a component
|
|
546
751
|
if (typeof newChild.tag !== "string") {
|
|
752
|
+
// Set the current component to the tag of the new child
|
|
547
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
|
|
548
755
|
newTree.splice(
|
|
549
756
|
i--,
|
|
550
757
|
1,
|
|
@@ -556,57 +763,81 @@ export function patch(newVnode: VnodeWithDom, oldVnode?: VnodeWithDom): void {
|
|
|
556
763
|
continue;
|
|
557
764
|
}
|
|
558
765
|
|
|
766
|
+
// Set the isSVG flag for the new child if it is an SVG element or if the parent is an SVG element
|
|
559
767
|
newChild.isSVG = newVnode.isSVG || newChild.tag === "svg";
|
|
560
768
|
|
|
769
|
+
// If there is an old child at the same index
|
|
561
770
|
if (i < oldTreeLength) {
|
|
562
771
|
let oldChild = oldTree[i];
|
|
772
|
+
// If the tag of the new child is the same as the tag of the old child
|
|
563
773
|
if (newChild.tag === oldChild.tag) {
|
|
774
|
+
// Set the dom property of the new child to the dom property of the old child
|
|
564
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
|
|
565
777
|
if ("v-keep" in newChild.props && newChild.props["v-keep"] === oldChild.props["v-keep"]) {
|
|
566
778
|
newChild.children = oldChild.children;
|
|
567
779
|
continue;
|
|
568
780
|
}
|
|
569
781
|
|
|
782
|
+
// Update the attributes of the new child based on the old child
|
|
570
783
|
updateAttributes(newChild as VnodeWithDom, oldChild);
|
|
784
|
+
// Recursively patch the new and old children
|
|
571
785
|
patch(newChild as VnodeWithDom, oldChild);
|
|
572
786
|
continue;
|
|
573
787
|
}
|
|
574
788
|
|
|
789
|
+
// Create a new dom element for the new child
|
|
575
790
|
newChild.dom = createDomElement(newChild.tag, newChild.isSVG);
|
|
791
|
+
// Update the attributes of the new child
|
|
576
792
|
updateAttributes(newChild as VnodeWithDom);
|
|
793
|
+
// Replace the old child in the dom with the new child
|
|
577
794
|
newVnode.dom.replaceChild(newChild.dom, oldChild.dom);
|
|
795
|
+
// Recursively patch the new child
|
|
578
796
|
patch(newChild as VnodeWithDom);
|
|
579
797
|
continue;
|
|
580
798
|
}
|
|
581
799
|
|
|
800
|
+
// Create a new dom element for the new child
|
|
582
801
|
newChild.dom = createDomElement(newChild.tag, newChild.isSVG);
|
|
802
|
+
// Update the attributes of the new child
|
|
583
803
|
updateAttributes(newChild as VnodeWithDom);
|
|
804
|
+
// Append the new child to the dom
|
|
584
805
|
newVnode.dom.appendChild(newChild.dom);
|
|
806
|
+
// Recursively patch the new child
|
|
585
807
|
patch(newChild as VnodeWithDom);
|
|
586
808
|
continue;
|
|
587
809
|
}
|
|
588
810
|
|
|
811
|
+
// If the new child is an array, flatten it and continue the loop
|
|
589
812
|
if (Array.isArray(newChild)) {
|
|
590
813
|
newTree.splice(i--, 1, ...newChild);
|
|
591
814
|
continue;
|
|
592
815
|
}
|
|
593
816
|
|
|
817
|
+
// If the new child is null or undefined, remove it from the new tree and continue the loop
|
|
594
818
|
if (newChild === null || newChild === undefined) {
|
|
595
819
|
newTree.splice(i--, 1);
|
|
596
820
|
continue;
|
|
597
821
|
}
|
|
598
822
|
|
|
823
|
+
// If the new child is not a Vnode, wrap it in a text Vnode
|
|
599
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
|
|
600
826
|
if (newChild instanceof Vnode) {
|
|
601
827
|
newTree[i].dom = newChild.dom;
|
|
828
|
+
// Set the new child to the text content of its dom property
|
|
602
829
|
newChild = (newChild as VnodeWithDom).dom.textContent;
|
|
603
830
|
}
|
|
604
831
|
|
|
832
|
+
// If there is an old child at the same index
|
|
605
833
|
if (i < oldTreeLength) {
|
|
606
834
|
let oldChild = oldTree[i];
|
|
607
835
|
|
|
836
|
+
// If the old child is a text node
|
|
608
837
|
if (oldChild.tag === textTag) {
|
|
838
|
+
// Set the dom property of the text Vnode to the dom property of the old child
|
|
609
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
|
|
610
841
|
// eslint-disable-next-line eqeqeq
|
|
611
842
|
if (newChild != oldChild.dom.textContent) {
|
|
612
843
|
oldChild.dom.textContent = newChild;
|
|
@@ -614,61 +845,93 @@ export function patch(newVnode: VnodeWithDom, oldVnode?: VnodeWithDom): void {
|
|
|
614
845
|
continue;
|
|
615
846
|
}
|
|
616
847
|
|
|
848
|
+
// Create a new text node for the new child
|
|
617
849
|
newTree[i].dom = document.createTextNode(newChild);
|
|
850
|
+
// Replace the old child in the dom with the new text node
|
|
618
851
|
newVnode.dom.replaceChild(newTree[i].dom, oldChild.dom);
|
|
619
852
|
continue;
|
|
620
853
|
}
|
|
621
854
|
|
|
855
|
+
// Create a new text node for the new child
|
|
622
856
|
newTree[i].dom = document.createTextNode(newChild);
|
|
857
|
+
// Append the new text node to the dom
|
|
623
858
|
newVnode.dom.appendChild(newTree[i].dom);
|
|
624
859
|
}
|
|
625
860
|
|
|
861
|
+
// Remove any old children that are no longer present in the new tree
|
|
626
862
|
for (let i = newTree.length; i < oldTreeLength; i++) {
|
|
627
863
|
newVnode.dom.removeChild(oldTree[i].dom);
|
|
628
864
|
}
|
|
629
865
|
}
|
|
630
866
|
|
|
867
|
+
// Update the main Vnode
|
|
631
868
|
export function update(): void | string {
|
|
869
|
+
// If the main Vnode exists
|
|
632
870
|
if (mainVnode) {
|
|
871
|
+
// Call any cleanup functions that are registered with the onCleanupSet set
|
|
633
872
|
callSet(onCleanupSet);
|
|
873
|
+
// Store a reference to the old main Vnode
|
|
634
874
|
let oldMainVnode = mainVnode;
|
|
875
|
+
// Create a new main Vnode with the main component as its only child
|
|
635
876
|
mainVnode = new Vnode(oldMainVnode.tag, oldMainVnode.props, [mainComponent]) as VnodeWithDom;
|
|
636
877
|
mainVnode.dom = oldMainVnode.dom;
|
|
637
878
|
mainVnode.isSVG = oldMainVnode.isSVG;
|
|
879
|
+
|
|
880
|
+
// Recursively patch the new and old main Vnodes
|
|
638
881
|
patch(mainVnode, oldMainVnode);
|
|
882
|
+
|
|
883
|
+
// Call any update or mount functions that are registered with the onUpdateSet or onMountSet set
|
|
639
884
|
callSet(isMounted ? onUpdateSet : onMountSet);
|
|
885
|
+
|
|
886
|
+
// Set the isMounted flag to true
|
|
640
887
|
isMounted = true;
|
|
888
|
+
|
|
889
|
+
// Reset the current vnode, oldVnode, and component properties
|
|
641
890
|
current.vnode = null;
|
|
642
891
|
current.oldVnode = null;
|
|
643
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
|
|
644
895
|
if (isNodeJs) {
|
|
645
896
|
return mainVnode.dom.innerHTML;
|
|
646
897
|
}
|
|
647
898
|
}
|
|
648
899
|
}
|
|
649
900
|
|
|
901
|
+
// Unmount the main Vnode
|
|
650
902
|
export function unmount() {
|
|
903
|
+
// If the main Vnode exists
|
|
651
904
|
if (mainVnode) {
|
|
905
|
+
// Set the main component to a null Vnode
|
|
652
906
|
mainComponent = new Vnode(() => null, {}, []) as VnodeComponentInterface;
|
|
907
|
+
// Update the main Vnode
|
|
653
908
|
let result = update();
|
|
909
|
+
// Call any unmount functions that are registered with the onUnmountSet set
|
|
654
910
|
callSet(onUnmountSet);
|
|
655
911
|
|
|
912
|
+
// Remove any event listeners that were added to the main Vnode's dom element
|
|
656
913
|
for (let name in eventListenerNames) {
|
|
657
914
|
mainVnode.dom.removeEventListener(name.slice(2).toLowerCase(), eventListener);
|
|
658
915
|
Reflect.deleteProperty(eventListenerNames, name);
|
|
659
916
|
}
|
|
660
917
|
|
|
918
|
+
// Reset the main component and main Vnode
|
|
661
919
|
mainComponent = null;
|
|
662
920
|
mainVnode = null;
|
|
921
|
+
// Set the isMounted flag to false
|
|
663
922
|
isMounted = false;
|
|
923
|
+
// Reset the current vnode, oldVnode, and component properties
|
|
664
924
|
current.vnode = null;
|
|
665
925
|
current.oldVnode = null;
|
|
666
926
|
current.component = null;
|
|
927
|
+
// Return the result of updating the main Vnode
|
|
667
928
|
return result;
|
|
668
929
|
}
|
|
669
930
|
}
|
|
670
|
-
|
|
931
|
+
// This function takes in a DOM element or a DOM element selector and a component to be mounted on it.
|
|
671
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.
|
|
672
935
|
let container =
|
|
673
936
|
typeof dom === "string"
|
|
674
937
|
? isNodeJs
|
|
@@ -676,23 +939,35 @@ export function mount(dom, component) {
|
|
|
676
939
|
: document.querySelectorAll(dom)[0]
|
|
677
940
|
: dom;
|
|
678
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.
|
|
679
945
|
let vnodeComponent = isVnodeComponent(component)
|
|
680
946
|
? component
|
|
681
947
|
: isComponent(component)
|
|
682
948
|
? new Vnode(component, {}, [])
|
|
683
949
|
: new Vnode(() => component, {}, []);
|
|
684
950
|
|
|
951
|
+
// If a main component already exists and it's not the same as the current 'vnodeComponent', unmount it.
|
|
685
952
|
if (mainComponent && mainComponent.tag !== vnodeComponent.tag) {
|
|
686
953
|
unmount();
|
|
687
954
|
}
|
|
688
955
|
|
|
956
|
+
// Set the 'vnodeComponent' as the main component.
|
|
689
957
|
mainComponent = vnodeComponent as VnodeComponentInterface;
|
|
958
|
+
// Convert the container element to a Vnode.
|
|
690
959
|
mainVnode = domToVnode(container);
|
|
960
|
+
// Update the DOM with the new component.
|
|
691
961
|
return update();
|
|
692
962
|
}
|
|
693
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.
|
|
694
966
|
export const v: V = (tagOrComponent, props = {}, ...children) => {
|
|
967
|
+
// Return a new Vnode object using the given arguments.
|
|
695
968
|
return new Vnode(tagOrComponent, props || {}, children);
|
|
696
969
|
};
|
|
697
970
|
|
|
698
|
-
|
|
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;
|