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/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,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 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
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
- /* 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.
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
- // 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.
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
- // Flat newTree
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
- 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;