valyrian.js 7.2.11 → 8.0.0

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 (165) hide show
  1. package/README.md +6 -6
  2. package/dist/flux-store/index.d.ts +32 -0
  3. package/dist/flux-store/index.d.ts.map +1 -0
  4. package/dist/flux-store/index.js +258 -0
  5. package/dist/flux-store/index.js.map +7 -0
  6. package/dist/flux-store/index.min.js +1 -0
  7. package/dist/flux-store/index.min.js.map +1 -0
  8. package/dist/flux-store/index.mjs +237 -0
  9. package/dist/flux-store/index.mjs.map +7 -0
  10. package/dist/hooks/index.d.ts.map +1 -1
  11. package/dist/hooks/index.js +42 -75
  12. package/dist/hooks/index.js.map +2 -2
  13. package/dist/hooks/index.min.js +1 -0
  14. package/dist/hooks/index.min.js.map +1 -0
  15. package/dist/hooks/index.mjs +43 -76
  16. package/dist/hooks/index.mjs.map +2 -2
  17. package/dist/index.d.ts +52 -54
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +397 -328
  20. package/dist/index.js.map +3 -3
  21. package/dist/index.min.js +1 -1
  22. package/dist/index.min.js.map +1 -1
  23. package/dist/index.mjs +397 -328
  24. package/dist/index.mjs.map +3 -3
  25. package/dist/native-store/index.d.ts +14 -0
  26. package/dist/native-store/index.d.ts.map +1 -0
  27. package/dist/native-store/index.js +103 -0
  28. package/dist/native-store/index.js.map +7 -0
  29. package/dist/native-store/index.min.js +1 -0
  30. package/dist/native-store/index.min.js.map +1 -0
  31. package/dist/native-store/index.mjs +82 -0
  32. package/dist/native-store/index.mjs.map +7 -0
  33. package/dist/node/index.d.ts.map +1 -1
  34. package/dist/node/index.js +223 -86
  35. package/dist/node/index.js.map +4 -4
  36. package/dist/node/index.mjs +223 -86
  37. package/dist/node/index.mjs.map +4 -4
  38. package/dist/node/node.sw.js +152 -0
  39. package/dist/node/utils/inline.d.ts.map +1 -1
  40. package/dist/node/utils/node.sw.js +152 -0
  41. package/dist/node/utils/session-storage.d.ts +22 -0
  42. package/dist/node/utils/session-storage.d.ts.map +1 -0
  43. package/dist/node/utils/sw.d.ts.map +1 -1
  44. package/dist/node/utils/tree-adapter.d.ts +9 -0
  45. package/dist/node/utils/tree-adapter.d.ts.map +1 -1
  46. package/dist/pulse-store/index.d.ts +13 -0
  47. package/dist/pulse-store/index.d.ts.map +1 -0
  48. package/dist/pulse-store/index.js +143 -0
  49. package/dist/pulse-store/index.js.map +7 -0
  50. package/dist/pulse-store/index.min.js +1 -0
  51. package/dist/pulse-store/index.min.js.map +1 -0
  52. package/dist/pulse-store/index.mjs +122 -0
  53. package/dist/pulse-store/index.mjs.map +7 -0
  54. package/dist/request/index.d.ts.map +1 -1
  55. package/dist/request/index.js +68 -89
  56. package/dist/request/index.js.map +2 -2
  57. package/dist/request/index.min.js +1 -0
  58. package/dist/request/index.min.js.map +1 -0
  59. package/dist/request/index.mjs +68 -89
  60. package/dist/request/index.mjs.map +2 -2
  61. package/dist/router/index.d.ts +32 -31
  62. package/dist/router/index.d.ts.map +1 -1
  63. package/dist/router/index.js +256 -104
  64. package/dist/router/index.js.map +3 -3
  65. package/dist/router/index.min.js +1 -0
  66. package/dist/router/index.min.js.map +1 -0
  67. package/dist/router/index.mjs +256 -104
  68. package/dist/router/index.mjs.map +3 -3
  69. package/dist/signals/index.d.ts +6 -0
  70. package/dist/signals/index.d.ts.map +1 -0
  71. package/dist/signals/index.js +92 -0
  72. package/dist/signals/index.js.map +7 -0
  73. package/dist/signals/index.min.js +1 -0
  74. package/dist/signals/index.min.js.map +1 -0
  75. package/dist/signals/index.mjs +71 -0
  76. package/dist/signals/index.mjs.map +7 -0
  77. package/dist/suspense/index.d.ts +6 -0
  78. package/dist/suspense/index.d.ts.map +1 -0
  79. package/dist/suspense/index.js +67 -0
  80. package/dist/suspense/index.js.map +7 -0
  81. package/dist/suspense/index.min.js +1 -0
  82. package/dist/suspense/index.min.js.map +1 -0
  83. package/dist/suspense/index.mjs +46 -0
  84. package/dist/suspense/index.mjs.map +7 -0
  85. package/dist/sw/index.min.js +1 -0
  86. package/dist/sw/index.min.js.map +1 -0
  87. package/dist/translate/index.d.ts +19 -0
  88. package/dist/translate/index.d.ts.map +1 -0
  89. package/dist/translate/index.js +150 -0
  90. package/dist/translate/index.js.map +7 -0
  91. package/dist/translate/index.min.js +1 -0
  92. package/dist/translate/index.min.js.map +1 -0
  93. package/dist/translate/index.mjs +129 -0
  94. package/dist/translate/index.mjs.map +7 -0
  95. package/dist/tsconfig.tsbuildinfo +1 -1
  96. package/dist/utils/deep-freeze.d.ts +3 -0
  97. package/dist/utils/deep-freeze.d.ts.map +1 -0
  98. package/dist/utils/getter-setter.d.ts +3 -0
  99. package/dist/utils/getter-setter.d.ts.map +1 -0
  100. package/dist/utils/has-changed.d.ts +2 -0
  101. package/dist/utils/has-changed.d.ts.map +1 -0
  102. package/dist/utils/index.d.ts +4 -0
  103. package/dist/utils/index.d.ts.map +1 -0
  104. package/dist/utils/index.js +138 -0
  105. package/dist/utils/index.js.map +7 -0
  106. package/dist/utils/index.min.js +1 -0
  107. package/dist/utils/index.min.js.map +1 -0
  108. package/dist/utils/index.mjs +115 -0
  109. package/dist/utils/index.mjs.map +7 -0
  110. package/lib/flux-store/index.ts +301 -0
  111. package/lib/hooks/index.ts +52 -101
  112. package/lib/index.ts +479 -719
  113. package/lib/native-store/index.ts +106 -0
  114. package/lib/node/index.ts +5 -3
  115. package/lib/node/utils/icons.ts +5 -5
  116. package/lib/node/utils/inline.ts +17 -17
  117. package/lib/node/utils/node.sw.js +152 -0
  118. package/lib/node/utils/session-storage.ts +117 -0
  119. package/lib/node/utils/sw.ts +35 -11
  120. package/lib/node/utils/tree-adapter.ts +99 -52
  121. package/lib/pulse-store/index.ts +181 -0
  122. package/lib/request/index.ts +86 -116
  123. package/lib/router/index.ts +358 -170
  124. package/lib/signals/index.ts +98 -0
  125. package/lib/suspense/index.ts +57 -0
  126. package/lib/translate/index.ts +156 -0
  127. package/lib/utils/deep-freeze.ts +54 -0
  128. package/lib/utils/getter-setter.ts +40 -0
  129. package/lib/utils/has-changed.ts +43 -0
  130. package/lib/utils/index.ts +3 -0
  131. package/package.json +38 -50
  132. package/tsconfig.json +1 -1
  133. package/dist/dataset/index.d.ts +0 -24
  134. package/dist/dataset/index.d.ts.map +0 -1
  135. package/dist/dataset/index.js +0 -178
  136. package/dist/dataset/index.js.map +0 -7
  137. package/dist/dataset/index.mjs +0 -157
  138. package/dist/dataset/index.mjs.map +0 -7
  139. package/dist/node/node.sw.tpl +0 -133
  140. package/dist/node/utils/node.sw.tpl +0 -133
  141. package/dist/proxy-signal/index.d.ts +0 -23
  142. package/dist/proxy-signal/index.d.ts.map +0 -1
  143. package/dist/proxy-signal/index.js +0 -138
  144. package/dist/proxy-signal/index.js.map +0 -7
  145. package/dist/proxy-signal/index.mjs +0 -117
  146. package/dist/proxy-signal/index.mjs.map +0 -7
  147. package/dist/signal/index.d.ts +0 -20
  148. package/dist/signal/index.d.ts.map +0 -1
  149. package/dist/signal/index.js +0 -95
  150. package/dist/signal/index.js.map +0 -7
  151. package/dist/signal/index.mjs +0 -74
  152. package/dist/signal/index.mjs.map +0 -7
  153. package/dist/store/index.d.ts +0 -16
  154. package/dist/store/index.d.ts.map +0 -1
  155. package/dist/store/index.js +0 -93
  156. package/dist/store/index.js.map +0 -7
  157. package/dist/store/index.mjs +0 -72
  158. package/dist/store/index.mjs.map +0 -7
  159. package/lib/dataset/index.ts +0 -193
  160. package/lib/index.d.ts +0 -0
  161. package/lib/interfaces.ts.bak +0 -141
  162. package/lib/node/utils/node.sw.tpl +0 -133
  163. package/lib/proxy-signal/index.ts +0 -187
  164. package/lib/signal/index.ts +0 -161
  165. package/lib/store/index.ts +0 -101
package/lib/index.ts CHANGED
@@ -1,369 +1,168 @@
1
- /* eslint-disable no-use-before-define */
2
- /* eslint-disable indent */
3
- /* eslint-disable sonarjs/cognitive-complexity */
4
- /* eslint-disable complexity */
1
+ /* eslint-disable */
2
+ declare global {
3
+ var document: Document;
4
+ namespace JSX {
5
+ interface IntrinsicElements extends DefaultRecord {}
6
+ }
7
+ }
5
8
 
6
9
  interface DefaultRecord extends Record<string | number | symbol, any> {}
7
10
 
8
- // The VnodeProperties interface represents properties that can be passed to a virtual node.
9
11
  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
12
  key?: string | number;
13
- // A state object that is associated with the virtual node.
14
- state?: any;
15
13
  }
16
14
 
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
15
  export interface DomElement extends Element, DefaultRecord {}
20
16
 
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
17
  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;
18
+ (props: VnodeProperties, children: any[]): Vnode | any;
54
19
  }
55
20
 
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
21
  export interface POJOComponent extends DefaultRecord {
61
- // The view function that returns a virtual node or a list of virtual nodes.
62
22
  view: Component;
63
- // The props for the component.
64
- props?: VnodeProperties | null;
65
- // The children for the component.
66
- children?: any[];
67
23
  }
68
24
 
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;
25
+ export type ValyrianComponent = Component | POJOComponent;
26
+
27
+ export interface VnodeComponentInterface extends Vnode {
28
+ tag: ValyrianComponent;
75
29
  }
76
30
 
77
- // The Children interface represents a list of virtual nodes or other values.
78
- export interface Children extends Array<VnodeInterface | VnodeComponentInterface | any> {}
31
+ export interface Children extends Array<Vnode | VnodeComponentInterface | ValyrianComponent | any> {}
79
32
 
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
33
  export interface Directive {
85
- // eslint-disable-next-line no-unused-vars
86
- (value: any, vnode: VnodeWithDom, oldVnode?: VnodeWithDom): void | boolean;
34
+ (value: any, vnode: VnodeWithDom, oldProps: VnodeProperties | null): void | boolean;
87
35
  }
88
36
 
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
- }
37
+ export const isNodeJs = Boolean(typeof process !== "undefined" && process.versions && process.versions.node);
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;
115
- // eslint-disable-next-line no-unused-vars, no-use-before-define
116
- fragment(_: any, ...children: Children): Children;
39
+ export class Vnode {
40
+ constructor(
41
+ public tag: string | Component | POJOComponent,
42
+ public props: null | VnodeProperties,
43
+ public children: Children,
44
+ public dom?: DomElement,
45
+ public isSVG?: boolean
46
+ ) {}
117
47
  }
118
48
 
119
- // 'textTag' is a constant string that is used to represent text nodes in the virtual DOM.
120
- const textTag = "#text";
121
-
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);
49
+ export interface VnodeWithDom extends Vnode {
50
+ tag: string;
51
+ dom: DomElement;
52
+ props: VnodeProperties;
130
53
  }
131
54
 
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
- );
149
- }
55
+ export const isPOJOComponent = (component: unknown): component is POJOComponent =>
56
+ Boolean(component && typeof component === "object" && "view" in component);
150
57
 
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
- };
58
+ export const isComponent = (component: unknown): component is Component =>
59
+ Boolean(typeof component === "function" || isPOJOComponent(component));
60
+ export const isVnode = (object?: unknown): object is Vnode => object instanceof Vnode;
156
61
 
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.
62
+ export const isVnodeComponent = (object?: unknown): object is VnodeComponentInterface => {
161
63
  return isVnode(object) && isComponent(object.tag);
162
64
  };
163
65
 
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.
66
+ export function v(tagOrComponent: string | ValyrianComponent, props: VnodeProperties, ...children: Children) {
67
+ return new Vnode(tagOrComponent, props, children);
68
+ }
69
+
70
+ v.fragment = (_: VnodeProperties, ...children: Children) => children;
71
+
72
+ export function hidrateDomToVnode(dom: any): VnodeWithDom | void {
169
73
  if (dom.nodeType === 3) {
170
- let vnode = new Vnode(textTag, {}, [dom.nodeValue]);
171
- vnode.dom = dom;
172
- return vnode as VnodeWithDom;
74
+ return dom.nodeValue;
173
75
  }
174
76
 
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));
77
+ if (dom.nodeType === 1) {
78
+ const tag = dom.nodeName.toLowerCase();
79
+ const props = {} as VnodeProperties;
80
+ const children = [] as Children;
81
+
82
+ for (let i = 0, l = dom.childNodes.length; i < l; i++) {
83
+ const childDom = dom.childNodes[i];
84
+ if (childDom.nodeType === 3) {
85
+ children.push(childDom.nodeValue);
86
+ } else if (childDom.nodeType === 1) {
87
+ const childVnode = hidrateDomToVnode(childDom);
88
+ children.push(childVnode);
89
+ }
183
90
  }
184
- }
185
91
 
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
- }
92
+ const attributes = dom.attributes;
93
+ for (let i = 0, l = attributes.length; i < l; i++) {
94
+ const attr = attributes[i];
95
+ props[attr.nodeName] = attr.nodeValue;
96
+ }
193
97
 
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;
98
+ const vnode = new Vnode(tag, props, children);
99
+ vnode.dom = dom;
100
+ vnode.isSVG = tag === "svg";
101
+ return vnode as VnodeWithDom;
102
+ }
200
103
  }
201
104
 
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
105
  export function trust(htmlString: string) {
208
- let div = createDomElement("div");
106
+ const div = document.createElement("div");
209
107
  div.innerHTML = htmlString.trim();
210
-
211
- return [].map.call(div.childNodes, (item) => domToVnode(item));
108
+ return Array.from(div.childNodes).map(hidrateDomToVnode);
212
109
  }
213
110
 
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
111
  let mainComponent: VnodeComponentInterface | null = null;
221
112
  let mainVnode: VnodeWithDom | null = null;
222
113
  let isMounted = false;
223
114
 
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
230
- };
231
-
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
115
+ export const current = {
116
+ vnode: null as Vnode | null,
117
+ component: null as ValyrianComponent | null,
118
+ event: null as Event | null
251
119
  };
252
120
 
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) {
121
+ export const reservedProps = new Set<string>([
122
+ "key",
123
+ "state",
124
+ "v-keep",
125
+ "v-text",
126
+ "v-if",
127
+ "v-for",
128
+ "v-show",
129
+ "v-class",
130
+ "v-html",
131
+ "v-model",
132
+ "v-create",
133
+ "v-update",
134
+ "v-cleanup"
135
+ ]);
136
+
137
+ const onCleanupSet = new Set<Function>();
138
+ const onMountSet = new Set<Function>();
139
+ const onUpdateSet = new Set<Function>();
140
+ const onUnmountSet = new Set<Function>();
141
+ export const onMount = (callback: Function) => !isMounted && onMountSet.add(callback);
142
+ export const onUpdate = (callback: Function) => onUpdateSet.add(callback);
143
+ export const onCleanup = (callback: Function) => onCleanupSet.add(callback);
144
+ export const onUnmount = (callback: Function) => !isMounted && onUnmountSet.add(callback);
145
+ const callSet = (set: Set<Function>) => {
146
+ for (const callback of set) {
285
147
  callback();
286
148
  }
287
-
288
149
  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);
313
-
314
- // If the default action of the event hasn't been prevented, update the DOM
315
- if (!e.defaultPrevented) {
316
- update();
317
- }
318
- return;
319
- }
320
- dom = dom.parentNode as DomElement;
321
- }
322
-
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);
338
- }
339
- vnode.tag = "#text";
340
- vnode.children = [];
341
- vnode.props = {};
342
- vnode.dom = newdom as unknown as DomElement;
343
- return false;
344
- }
345
150
  };
346
151
 
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),
351
-
352
- // The "v-unless" directive hides an element if the given condition is true
353
- "v-unless": hideDirective(true),
152
+ export const directives: Record<string, Directive> = {
153
+ "v-if": (value, vnode) => {
154
+ if (!Boolean(value)) {
155
+ const parentNode = vnode.dom?.parentNode;
156
+ if (parentNode) {
157
+ parentNode.replaceChild(document.createTextNode(""), vnode.dom);
158
+ }
354
159
 
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));
160
+ return false;
361
161
  }
362
- vnode.children = newChildren;
363
162
  },
364
163
 
365
- // The "v-show" directive shows or hides an element by setting the "display" style property
366
- "v-show": (bool: boolean, vnode: VnodeWithDom) => {
164
+ "v-show": (value, vnode) => {
165
+ const bool = Boolean(value);
367
166
  (
368
167
  vnode.dom as unknown as {
369
168
  style: { display: string };
@@ -371,37 +170,32 @@ export const directives: Directives = {
371
170
  ).style.display = bool ? "" : "none";
372
171
  },
373
172
 
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)];
173
+ "v-html": (value, vnode) => {
174
+ vnode.children = trust(value as string);
387
175
  },
388
176
 
389
177
  // 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) => {
178
+ "v-model": (model, vnode) => {
179
+ // eslint-disable-next-line prefer-const
180
+ if ("name" in vnode.props === false) {
181
+ return;
182
+ }
183
+
391
184
  let value;
185
+ const property = vnode.props.name;
186
+ let event = "oninput";
187
+
392
188
  // This function updates the model property when the input element's value changes
393
189
  let handler = (e: Event) => (model[property] = (e.target as DomElement & Record<string, any>).value);
394
190
  if (vnode.tag === "input") {
395
- // If the element is an input, use the "input" event by default
396
- event = event || "oninput";
397
191
  // Depending on the type of input element, use a different handler function
398
192
  switch (vnode.props.type) {
399
193
  case "checkbox": {
400
194
  if (Array.isArray(model[property])) {
401
195
  // If the model property is an array, add or remove the value from the array when the checkbox is checked or unchecked
402
196
  handler = (e: Event) => {
403
- let val = (e.target as DomElement & Record<string, any>).value;
404
- let idx = model[property].indexOf(val);
197
+ const val = (e.target as DomElement & Record<string, any>).value;
198
+ const idx = model[property].indexOf(val);
405
199
  if (idx === -1) {
406
200
  model[property].push(val);
407
201
  } else {
@@ -444,14 +238,14 @@ export const directives: Directives = {
444
238
  }
445
239
  } else if (vnode.tag === "select") {
446
240
  // If the element is a select element, use the "click" event by default
447
- event = event || "onclick";
241
+ event = "onclick";
448
242
  if (vnode.props.multiple) {
449
243
  // If the select element allows multiple selections, update the model property with an array of selected values
450
244
  handler = (e: Event & Record<string, any>) => {
451
- let val = (e.target as DomElement & Record<string, any>).value;
245
+ const val = (e.target as DomElement & Record<string, any>).value;
452
246
  if (e.ctrlKey) {
453
247
  // If the Ctrl key is pressed, add or remove the value from the array
454
- let idx = model[property].indexOf(val);
248
+ const idx = model[property].indexOf(val);
455
249
  if (idx === -1) {
456
250
  model[property].push(val);
457
251
  } else {
@@ -464,30 +258,28 @@ export const directives: Directives = {
464
258
  }
465
259
  };
466
260
  // Set the "selected" attribute on the options based on whether they are in the model property array
467
- vnode.children.forEach((child: VnodeInterface) => {
261
+ vnode.children.forEach((child: VnodeWithDom) => {
468
262
  if (child.tag === "option") {
469
- let value = "value" in child.props ? child.props.value : child.children.join("").trim();
263
+ const value = "value" in child.props ? child.props.value : child.children.join("").trim();
470
264
  child.props.selected = model[property].indexOf(value) !== -1;
471
265
  }
472
266
  });
473
267
  } else {
474
268
  // 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) => {
269
+ vnode.children.forEach((child: VnodeWithDom) => {
476
270
  if (child.tag === "option") {
477
- let value = "value" in child.props ? child.props.value : child.children.join("").trim();
271
+ const value = "value" in child.props ? child.props.value : child.children.join("").trim();
478
272
  child.props.selected = value === model[property];
479
273
  }
480
274
  });
481
275
  }
482
276
  } else if (vnode.tag === "textarea") {
483
- // If the element is a textarea, use the "input" event by default
484
- event = event || "oninput";
485
277
  // Set the textarea's content to the value of the model property
486
278
  vnode.children = [model[property]];
487
279
  }
488
280
 
489
281
  // We assume that the prev handler if any will not be changed by the user across patchs
490
- let prevHandler = vnode.props[event];
282
+ const prevHandler = vnode.props[event];
491
283
 
492
284
  // Set the event handler on the element
493
285
  // eslint-disable-next-line no-use-before-define
@@ -501,524 +293,492 @@ export const directives: Directives = {
501
293
  prevHandler(e);
502
294
  }
503
295
  },
504
- vnode,
505
- oldVnode
296
+ vnode
506
297
  );
507
298
  },
508
299
 
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);
300
+ "v-create": (callback, vnode, oldProps) => {
301
+ if (!oldProps) {
302
+ const cleanup = callback(vnode);
517
303
 
518
- // If the callback function returns a function, call it when the update is gonna be cleaned up
519
304
  if (typeof cleanup === "function") {
520
305
  onCleanup(cleanup);
521
306
  }
522
307
  }
523
308
  },
524
309
 
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
310
+ "v-update": (callback, vnode, oldProps) => {
311
+ if (oldProps) {
312
+ const cleanup = callback(vnode, oldProps);
313
+
539
314
  if (typeof cleanup === "function") {
540
315
  onCleanup(cleanup);
541
316
  }
542
317
  }
543
318
  },
544
319
 
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));
320
+ "v-cleanup": (callback, vnode) => {
321
+ onCleanup(() => callback(vnode));
322
+ },
323
+
324
+ "v-class": (value, vnode) => {
325
+ if (typeof value === "string") {
326
+ vnode.dom.className = value;
327
+ } else if (Array.isArray(value)) {
328
+ vnode.dom.className = value.join(" ");
329
+ } else if (typeof value === "object") {
330
+ const classList = vnode.dom.classList;
331
+ for (const name in value) {
332
+ const val = typeof value[name] === "function" ? (value[name] as Function)() : value[name];
333
+ classList.toggle(name, val);
334
+ }
335
+ }
336
+ },
337
+
338
+ // Frequent used properties
339
+ class(value, vnode) {
340
+ if (vnode.dom.className !== value) {
341
+ vnode.dom.className = value;
342
+ }
343
+ },
344
+
345
+ className(value, vnode) {
346
+ directives.class(value, vnode, null);
347
+ },
348
+
349
+ id: (value, vnode) => {
350
+ vnode.dom.id = value;
351
+ },
352
+
353
+ style: (value, vnode) => {
354
+ if (typeof value === "string") {
355
+ vnode.dom.style = value;
356
+ } else if (typeof value === "object") {
357
+ vnode.dom.style = "";
358
+ const domStyle = vnode.dom.style;
359
+ for (const name in value) {
360
+ domStyle[name] = value[name];
361
+ }
362
+ }
556
363
  }
557
364
  };
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.
365
+
560
366
  export function directive(name: string, directive: Directive) {
561
- let directiveName = `v-${name}`;
367
+ const directiveName = `v-${name}`;
562
368
  directives[directiveName] = directive;
563
- reservedProps[directiveName] = true;
369
+ reservedProps.add(directiveName);
370
+ }
371
+
372
+ export function setPropNameReserved(name: string) {
373
+ reservedProps.add(name);
374
+ }
375
+
376
+ const eventListenerNames = new Set<string>();
377
+
378
+ function eventListener(e: Event) {
379
+ current.event = e;
380
+ let dom = e.target as DomElement;
381
+ const name = `on${e.type}`;
382
+
383
+ while (dom) {
384
+ const oldProps = dom.props;
385
+ if (oldProps && oldProps[name]) {
386
+ oldProps[name](e, dom);
387
+
388
+ if (!e.defaultPrevented) {
389
+ // eslint-disable-next-line no-use-before-define
390
+ update();
391
+ }
392
+ return;
393
+ }
394
+ dom = dom.parentNode as DomElement;
395
+ }
396
+
397
+ current.event = null;
564
398
  }
565
399
 
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.
400
+ function sharedSetAttribute(name: string, value: any, newVnode: VnodeWithDom): void | boolean {
401
+ const newVnodeDom = newVnode.dom;
574
402
  if (typeof value === "function") {
575
- // Only add the event listener if it hasn't been added yet.
576
- if (name in eventListenerNames === false) {
403
+ if (!eventListenerNames.has(name)) {
404
+ // We attach the delegated event listener to the main vnode dom element, which is the root of the component
577
405
  (mainVnode as VnodeWithDom).dom.addEventListener(name.slice(2), eventListener);
578
- eventListenerNames[name] = true;
406
+ eventListenerNames.add(name);
579
407
  }
580
- newVnode.dom[`v-${name}`] = value;
581
408
  return;
582
409
  }
583
410
 
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
- }
411
+ if (name in newVnodeDom) {
412
+ newVnodeDom[name] = value;
591
413
  return;
592
414
  }
593
415
 
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
- }
416
+ if (value === false) {
417
+ newVnodeDom.removeAttribute(name);
418
+ } else {
419
+ newVnodeDom.setAttribute(name, value);
602
420
  }
603
421
  }
604
422
 
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;
423
+ export function setAttribute(name: string, value: any, newVnode: VnodeWithDom): void {
424
+ if (!reservedProps.has(name)) {
425
+ newVnode.props[name] = value;
426
+ sharedSetAttribute(name, value, newVnode);
610
427
  }
611
- newVnode.props[name] = value;
612
- sharedSetAttribute(name, value, newVnode as VnodeWithDom, oldVnode);
613
428
  }
614
429
 
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;
430
+ export function updateAttributes(newVnode: VnodeWithDom, oldProps: VnodeProperties | null): void {
431
+ const vnodeDom = newVnode.dom;
432
+ const vnodeProps = newVnode.props;
433
+
434
+ if (oldProps) {
435
+ for (const name in oldProps) {
436
+ if (name in vnodeProps === false && !eventListenerNames.has(name) && !reservedProps.has(name)) {
437
+ if (name in vnodeDom) {
438
+ vnodeDom[name] = null;
631
439
  } else {
632
- newVnode.dom.removeAttribute(name);
440
+ vnodeDom.removeAttribute(name);
633
441
  }
634
442
  }
635
443
  }
636
444
  }
637
445
 
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) {
446
+ for (const name in vnodeProps) {
447
+ if (directives[name]) {
448
+ if (directives[name](vnodeProps[name], newVnode, oldProps) === false) {
646
449
  break;
647
450
  }
648
451
  continue;
649
452
  }
650
- sharedSetAttribute(name, newVnode.props[name], newVnode, oldVnode);
453
+
454
+ if (!reservedProps.has(name)) {
455
+ sharedSetAttribute(name, vnodeProps[name], newVnode);
456
+ }
651
457
  }
652
458
  }
653
459
 
654
- /* patch ------------------------------------------------------------------- */
460
+ export function createElement(tag: string, isSVG: boolean): DomElement {
461
+ return isSVG
462
+ ? document.createElementNS("http://www.w3.org/2000/svg", tag)
463
+ : (document.createElement(tag) as DomElement);
464
+ }
655
465
 
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;
466
+ function flatTree(newVnode: VnodeWithDom) {
467
+ current.vnode = newVnode;
468
+
469
+ if ("v-for" in newVnode.props) {
470
+ const children = [];
471
+ const set = newVnode.props["v-for"];
472
+ const l = set.length;
473
+ const callback = newVnode.children[0];
474
+
475
+ for (let i = 0; i < l; i++) {
476
+ const newChild = callback(set[i], i);
477
+ if (newChild instanceof Vnode) {
478
+ newChild.props = newChild.props || {};
479
+ newChild.isSVG = newVnode.isSVG || newChild.tag === "svg";
480
+ }
481
+ children[i] = newChild;
482
+ }
483
+
484
+ return children;
662
485
  }
663
486
 
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;
487
+ let i = 0;
488
+ const originalChildren = newVnode.children;
489
+ let children = originalChildren;
490
+
491
+ while (i < children.length) {
492
+ const newChild = children[i];
493
+
494
+ if (newChild == null) {
495
+ if (children === originalChildren) {
496
+ children = [...originalChildren];
497
+ }
498
+ children.splice(i, 1);
499
+ continue;
681
500
  }
682
501
 
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;
502
+ if (Array.isArray(newChild)) {
503
+ if (children === originalChildren) {
504
+ children = [...originalChildren];
505
+ }
506
+ children.splice(i, 1, ...newChild);
507
+ continue;
687
508
  }
688
509
 
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);
510
+ if (newChild instanceof Vnode) {
511
+ newChild.props = newChild.props || {};
512
+ newChild.isSVG = newVnode.isSVG || newChild.tag === "svg";
513
+
514
+ if (typeof newChild.tag !== "string") {
515
+ if (children === originalChildren) {
516
+ children = [...originalChildren];
707
517
  }
708
518
 
709
- // If the old child does not exist, create a new DOM element for the new child and update its attributes
710
- } else {
711
- newChild.dom = createDomElement(newChild.tag, newChild.isSVG);
712
- updateAttributes(newChild);
713
- }
519
+ const component = (current.component = newChild.tag);
714
520
 
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);
521
+ children[i] = (isPOJOComponent(component) ? component.view : component).bind(component)(
522
+ newChild.props,
523
+ newChild.children
524
+ );
718
525
 
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]);
526
+ continue;
722
527
  }
723
-
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
528
  }
727
529
 
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
- }
734
- }
530
+ i++;
531
+ }
532
+
533
+ return children;
534
+ }
535
+
536
+ function createNewElement(newChild: VnodeWithDom, newVnode: VnodeWithDom, oldChild: DomElement | null) {
537
+ const dom = createElement(newChild.tag, newChild.isSVG as boolean);
538
+ if (oldChild) {
539
+ newVnode.dom.replaceChild(dom, oldChild);
540
+ } else {
541
+ newVnode.dom.appendChild(dom);
542
+ }
543
+ newChild.dom = dom;
544
+ updateAttributes(newChild, null);
545
+ newChild.dom.props = newChild.props;
546
+ if ("v-text" in newChild.props) {
547
+ newChild.dom.textContent = newChild.props["v-text"];
735
548
  return;
736
549
  }
737
550
 
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 = "";
551
+ const children = flatTree(newChild);
552
+ if (children.length === 0) {
553
+ newChild.dom.textContent = "";
741
554
  return;
742
555
  }
743
556
 
744
- // Set the global current object to the new and old virtual DOM nodes
745
- current.vnode = newVnode;
746
- current.oldVnode = oldVnode;
557
+ for (let i = 0, l = children.length; i < l; i++) {
558
+ if (children[i] instanceof Vnode === false) {
559
+ newChild.dom.appendChild(document.createTextNode(children[i]));
560
+ continue;
561
+ }
562
+ createNewElement(children[i], newChild, null);
563
+ }
564
+ }
747
565
 
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];
566
+ function patchKeyed(newVnode: VnodeWithDom, children: Children) {
567
+ const oldTree = [...Array.from(newVnode.dom.childNodes)] as unknown as DomElement[];
568
+ const childNodes = newVnode.dom.childNodes;
569
+ const oldKeyedList: Record<string, number> = {};
570
+ const newKeyedList: Record<string, number> = {};
753
571
 
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
- }
572
+ for (let i = 0, l = oldTree.length; i < l; i++) {
573
+ const oldProps = oldTree[i].props;
574
+ if (oldProps) {
575
+ oldKeyedList[oldProps.key as string] = i;
576
+ }
770
577
 
771
- continue;
578
+ if (i < children.length && children[i] instanceof Vnode) {
579
+ newKeyedList[children[i].props.key as string] = i;
772
580
  }
581
+ }
773
582
 
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);
583
+ for (let i = 0, l = children.length; i < l; i++) {
584
+ const newChild = children[i];
585
+ const oldChild = oldTree[oldKeyedList[newChild.props.key as string]];
586
+
587
+ if (!oldChild) {
588
+ createNewElement(newChild, newVnode, childNodes[i] as DomElement | null);
777
589
  continue;
778
590
  }
779
591
 
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;
592
+ newChild.dom = oldChild;
593
+ const currentChild = childNodes[i];
594
+ if (!currentChild) {
595
+ newVnode.dom.appendChild(oldChild);
596
+ } else if (currentChild !== oldChild) {
597
+ newVnode.dom.replaceChild(oldChild, currentChild);
784
598
  }
785
599
 
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]);
600
+ if ("v-keep" in newChild.props === false || oldChild.props["v-keep"] !== newChild.props["v-keep"]) {
601
+ updateAttributes(newChild as VnodeWithDom, oldChild.props);
602
+ oldChild.props = newChild.props;
603
+
604
+ if ("v-text" in newChild.props) {
605
+ // eslint-disable-next-line eqeqeq
606
+ if (oldChild.textContent != newChild.props["v-text"]) {
607
+ oldChild.textContent = newChild.props["v-text"];
608
+ }
609
+ continue;
610
+ }
611
+ // eslint-disable-next-line no-use-before-define
612
+ patch(newChild as VnodeWithDom);
613
+ }
614
+ }
615
+
616
+ for (let i = children.length, l = childNodes.length; i < l; i++) {
617
+ childNodes[i]?.remove();
788
618
  }
619
+ }
620
+
621
+ // eslint-disable-next-line complexity
622
+ function patch(newVnode: VnodeWithDom): void {
623
+ const children = flatTree(newVnode);
624
+
625
+ const dom = newVnode.dom;
789
626
 
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);
627
+ if (children.length === 0) {
628
+ if (dom.childNodes.length) {
629
+ dom.textContent = "";
630
+ }
631
+ return;
632
+ }
633
+
634
+ const oldDomChildren = dom.childNodes as unknown as DomElement[];
635
+ const oldChildrenLength = oldDomChildren.length;
636
+ if (oldChildrenLength > 0) {
637
+ const firstOldProps = oldDomChildren[0].props;
638
+ const firstVnode = children[0] as VnodeWithDom;
639
+ if (firstOldProps && firstVnode instanceof Vnode && "key" in firstVnode.props && "key" in firstOldProps) {
640
+ patchKeyed(newVnode, children);
641
+ return;
642
+ }
643
+ }
644
+
645
+ const childrenLength = children.length;
646
+ if (oldChildrenLength === 0) {
647
+ for (let i = 0; i < childrenLength; i++) {
648
+ if (children[i] instanceof Vnode === false) {
649
+ dom.appendChild(document.createTextNode(children[i]));
650
+ continue;
651
+ }
652
+ createNewElement(children[i], newVnode, null);
653
+ }
654
+ return;
655
+ }
656
+
657
+ for (let i = 0; i < childrenLength; i++) {
658
+ const newChild = children[i] as VnodeWithDom;
659
+ const isText = newChild instanceof Vnode === false;
660
+ const oldChild = oldDomChildren[i];
661
+
662
+ if (!oldChild) {
663
+ if (isText) {
664
+ newVnode.dom.appendChild(document.createTextNode(newChild as unknown as string));
801
665
  continue;
802
666
  }
803
667
 
804
- // If there is an old child at the same index
805
- let oldChild = oldTree[i];
668
+ createNewElement(newChild as VnodeWithDom, 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 (isText) {
673
+ if (oldChild.nodeType !== 3) {
674
+ newVnode.dom.replaceChild(document.createTextNode(newChild as unknown as string), 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 as unknown as string)) {
680
+ oldChild.nodeValue = newChild as unknown as string;
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, 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);
712
+ patch(newChild);
873
713
  }
874
714
 
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);
715
+ for (let i = childrenLength, l = oldDomChildren.length; i < l; i++) {
716
+ oldDomChildren[i]?.remove();
878
717
  }
879
718
  }
880
719
 
881
- // Update the main Vnode
720
+ export function updateVnode(vnode: VnodeWithDom): string | void {
721
+ callSet(onCleanupSet);
722
+ vnode.props = vnode.props || {};
723
+ patch(vnode);
724
+ callSet(isMounted ? onUpdateSet : onMountSet);
725
+ isMounted = true;
726
+ current.vnode = null;
727
+ current.component = null;
728
+ }
729
+
882
730
  export function update(): void | string {
883
- // If the main Vnode exists
884
731
  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
-
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
732
+ mainVnode.children = [mainComponent];
733
+ updateVnode(mainVnode as VnodeWithDom);
909
734
  if (isNodeJs) {
910
735
  return mainVnode.dom.innerHTML;
911
736
  }
912
737
  }
913
738
  }
914
739
 
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
923
- callSet(onCleanupSet);
924
-
925
- // Recursively patch the new and old main Vnodes
926
- patch(vnode, oldVnode);
740
+ let debouncedUpdateTimeout: any;
927
741
 
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;
742
+ const clearDebouncedUpdateMethod = isNodeJs ? clearTimeout : cancelAnimationFrame;
743
+ const setDebouncedUpdateMethod = isNodeJs ? () => setTimeout(update, 5) : () => requestAnimationFrame(update);
935
744
 
936
- // Call any update or mount functions that are registered with the onUpdateSet or onMountSet set
937
- callSet(isMounted ? onUpdateSet : onMountSet);
938
-
939
- // Set the isMounted flag to true
940
- isMounted = true;
941
-
942
- // Reset the current vnode, oldVnode, and component properties
943
- current.vnode = null;
944
- current.oldVnode = null;
945
- current.component = null;
946
-
947
- if (isNodeJs) {
948
- return vnode.dom.innerHTML;
949
- }
745
+ export function debouncedUpdate() {
746
+ clearDebouncedUpdateMethod(debouncedUpdateTimeout);
747
+ debouncedUpdateTimeout = setDebouncedUpdateMethod();
950
748
  }
951
749
 
952
- // Unmount the main Vnode
953
750
  export function unmount() {
954
- // If the main Vnode exists
955
751
  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
752
+ mainComponent = v(() => null, {}) as VnodeComponentInterface;
753
+ const result = update();
961
754
  callSet(onUnmountSet);
962
-
963
- // Remove any event listeners that were added to the main Vnode's dom element
964
- for (let name in eventListenerNames) {
755
+ for (const name in eventListenerNames) {
965
756
  mainVnode.dom.removeEventListener(name.slice(2).toLowerCase(), eventListener);
966
757
  Reflect.deleteProperty(eventListenerNames, name);
967
758
  }
968
759
 
969
- // Reset the main component and main Vnode
970
760
  mainComponent = null;
971
761
  mainVnode = null;
972
- // Set the isMounted flag to false
973
762
  isMounted = false;
974
- // Reset the current vnode, oldVnode, and component properties
975
763
  current.vnode = null;
976
- current.oldVnode = null;
977
764
  current.component = null;
978
- // Return the result of updating the main Vnode
765
+ current.event = null;
979
766
  return result;
980
767
  }
981
768
  }
982
- // This function takes in a DOM element or a DOM element selector and a component to be mounted on it.
983
- 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();
769
+
770
+ export function mount(dom: string | DomElement, component: ValyrianComponent | VnodeComponentInterface | any) {
771
+ const container =
772
+ typeof dom === "string" ? (isNodeJs ? createElement(dom, dom === "svg") : document.querySelector(dom)) : dom;
773
+
774
+ if (isComponent(component)) {
775
+ mainComponent = v(component, {}, []) as VnodeComponentInterface;
776
+ } else if (isVnodeComponent(component)) {
777
+ mainComponent = component;
778
+ } else {
779
+ mainComponent = v(() => component, {}, []) as VnodeComponentInterface;
1005
780
  }
1006
781
 
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.
782
+ mainVnode = hidrateDomToVnode(container) as VnodeWithDom;
1012
783
  return update();
1013
784
  }
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;