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/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
- export interface Props {
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
- oncreate?: {
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
- // eslint-disable-next-line no-unused-vars, no-use-before-define
31
- new (tag: string | Component | POJOComponent, props: Props, children: Children): VnodeInterface;
32
- // eslint-disable-next-line no-unused-vars, no-use-before-define
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: Props;
35
- // eslint-disable-next-line no-unused-vars, no-use-before-define
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
- // eslint-disable-next-line no-unused-vars, no-use-before-define
47
- (props?: Props | null, ...children: any[]): VnodeInterface | Children | any;
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?: Props | null;
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: 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, no-use-before-define
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: Props | null, ...children: Children):
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
- export const Vnode = function Vnode(this: VnodeInterface, tag: string, props: Props, children: Children) {
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
- // Transforms a DOM node to a VNode
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: 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
- onMountSet.add(callback);
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
- onUnmountSet.add(callback);
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 user has defined a handler for the event, we call it right away
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
- /* Set attribute ------------------------------------------------------------ */
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
- // Is keyed list and update in place
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
- // Flat newTree
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
- v.fragment = (props: Props, ...children: Children) => children;
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;