valyrian.js 7.2.10 → 7.2.12

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