UNPKG

34.5 kBJavaScriptView Raw
1/** @license MIT https://github.com/jhdrn/myra/blob/master/LICENSE - Copyright (c) 2016-2021 Jonathan Hedrén */
2/**
3 * The renderingContext is used to obtain context varibles from within "hooks".
4 */
5var renderingContext;
6function getRenderingContext() {
7 return renderingContext;
8}
9/**
10 * Renders the component and handles it's "lifecycle" by triggering any effects.
11 */
12function renderComponent(parentElement, newVNode, oldVNode, isSvg) {
13 var oldNode;
14 var newProps = newVNode.props, rendition = newVNode.rendition;
15 if (rendition !== undefined) {
16 oldNode = rendition.domRef;
17 }
18 if (renderingContext === undefined) {
19 try {
20 renderingContext = {
21 vNode: newVNode,
22 isSvg: isSvg,
23 parentElement: parentElement,
24 hookIndex: 0
25 };
26 var newView = newVNode.view(newProps);
27 if (newView._ === 5 /* Memo */) {
28 if (oldVNode !== undefined && newView.compare(newVNode.props, oldVNode.props)) {
29 newVNode.domRef = oldNode;
30 renderingContext = undefined;
31 return;
32 }
33 newView = newView.view(newProps);
34 }
35 renderingContext = undefined;
36 render(parentElement, newView, newVNode.rendition, oldNode, isSvg);
37 newVNode.rendition = newView;
38 newVNode.domRef = newView.domRef;
39 // Trigger synchronous effects (useLayoutEffect)
40 triggerEffects(newVNode, parentElement, isSvg, true);
41 // Trigger asynchronous effects (useEffect)
42 triggerEffects(newVNode, parentElement, isSvg, false);
43 }
44 catch (err) {
45 tryHandleComponentError(parentElement, newVNode, isSvg, err);
46 }
47 }
48}
49/**
50 * Triggers all invokeable effects.
51 */
52function triggerEffects(newVNode, parentElement, isSvg, sync) {
53 var effects = newVNode.effects;
54 if (effects !== undefined) {
55 var _loop_1 = function (i) {
56 var t = effects[i];
57 if (t.invoke) {
58 if (t.sync && sync) {
59 attemptEffectCleanup(t);
60 t.cleanup = t.effect();
61 t.invoke = false;
62 }
63 else if (!sync) {
64 setTimeout(function () {
65 try {
66 attemptEffectCleanup(t);
67 t.cleanup = t.effect();
68 }
69 catch (err) {
70 tryHandleComponentError(parentElement, newVNode, isSvg, err);
71 }
72 }, 0);
73 t.invoke = false;
74 }
75 }
76 };
77 for (var i in effects) {
78 _loop_1(i);
79 }
80 }
81}
82/**
83 * Calls the cleanup function if it's set and then removes it from the wrapper
84 */
85function attemptEffectCleanup(t) {
86 if (t.cleanup !== undefined) {
87 try {
88 t.cleanup();
89 }
90 catch (err) {
91 console.error('An error occured during effect cleanup: ' + err);
92 }
93 t.cleanup = undefined;
94 }
95}
96/**
97 * Calls the error handler (if any) and renders the returned view.
98 */
99function tryHandleComponentError(parentElement, vNode, isSvg, err) {
100 // Do nothing if the parentElement is not longer connected to the DOM
101 if (parentElement.parentNode === null) {
102 return;
103 }
104 if (vNode.errorHandler !== undefined) {
105 renderingContext = undefined;
106 var oldNode = void 0;
107 if (vNode.rendition !== undefined) {
108 oldNode = vNode.rendition.domRef;
109 }
110 var errorView = vNode.errorHandler(err);
111 render(parentElement, errorView, vNode.rendition, oldNode, isSvg);
112 vNode.rendition = errorView;
113 vNode.domRef = errorView.domRef;
114 }
115 else {
116 throw err;
117 }
118}
119/**
120 * Traverses the virtual node hierarchy and unmounts any components in the
121 * hierarchy.
122 */
123function findAndUnmountComponentsRec(vNode) {
124 if (vNode === undefined) {
125 return;
126 }
127 if (vNode._ === 4 /* Component */) {
128 // Attempt to call any "cleanup" function for all effects before unmount.
129 var effects = vNode.effects;
130 if (effects !== undefined) {
131 for (var i in effects) {
132 attemptEffectCleanup(effects[i]);
133 }
134 }
135 findAndUnmountComponentsRec(vNode.rendition);
136 }
137 else if (vNode._ === 2 /* Element */ || vNode._ === 3 /* Fragment */) {
138 for (var _i = 0, _a = vNode.props.children; _i < _a.length; _i++) {
139 var c = _a[_i];
140 findAndUnmountComponentsRec(c);
141 }
142 }
143}
144/**
145 * Renders the view by traversing the virtual node tree recursively
146 */
147function render(parentDomNode, newVNode, oldVNode, existingDomNode, isSvg, action) {
148 if (isSvg === void 0) { isSvg = false; }
149 if (action === void 0) { action = null; }
150 if (action === null) {
151 // Decide what action to take
152 if (newVNode._ === 3 /* Fragment */ && oldVNode !== undefined && oldVNode._ === 3 /* Fragment */) {
153 action = 4 /* UPDATE */;
154 }
155 else if (oldVNode === undefined || oldVNode.domRef === undefined && oldVNode._ !== 3 /* Fragment */ && oldVNode._ !== 4 /* Component */) {
156 action = 1 /* APPEND */;
157 }
158 else if (oldVNode.domRef !== undefined && existingDomNode === undefined) {
159 action = 2 /* INSERT */;
160 }
161 else if (newVNode._ !== oldVNode._) {
162 action = 3 /* REPLACE */;
163 }
164 else if (newVNode._ === 2 /* Element */ && oldVNode._ === 2 /* Element */ &&
165 newVNode.tagName !== oldVNode.tagName) {
166 action = 3 /* REPLACE */;
167 }
168 else if (newVNode._ === 4 /* Component */ && oldVNode._ === 4 /* Component */ &&
169 newVNode.view !== oldVNode.view) {
170 action = 3 /* REPLACE */;
171 }
172 else {
173 action = 4 /* UPDATE */;
174 }
175 }
176 if (newVNode.tagName === 'svg') {
177 isSvg = true;
178 }
179 switch (action) {
180 case 1 /* APPEND */:
181 case 2 /* INSERT */:
182 case 3 /* REPLACE */:
183 renderCreate(parentDomNode, newVNode, oldVNode, existingDomNode, isSvg, action);
184 break;
185 case 4 /* UPDATE */:
186 renderUpdate(parentDomNode, newVNode, oldVNode, existingDomNode, isSvg);
187 break;
188 }
189}
190/**
191 * Creates a new DOM node from a VNode and then renders all it's children.
192 */
193function renderCreate(parentDomNode, newVNode, oldVNode, existingDomNode, isSvg, action) {
194 if (isSvg === void 0) { isSvg = false; }
195 if (action === void 0) { action = undefined; }
196 if (newVNode._ === 4 /* Component */) {
197 renderComponent(parentDomNode, newVNode, undefined, isSvg);
198 var domNode = newVNode.domRef;
199 if (domNode !== undefined) {
200 if (action === 1 /* APPEND */) {
201 parentDomNode.appendChild(domNode);
202 }
203 else if (action === 2 /* INSERT */) {
204 parentDomNode.insertBefore(domNode, oldVNode.domRef);
205 }
206 else if (action === 3 /* REPLACE */) {
207 // If it's a component node or an element node and it should be
208 // replaced, unmount any components in the tree.
209 if (oldVNode._ === 4 /* Component */ || oldVNode._ === 2 /* Element */ || oldVNode._ === 3 /* Fragment */) {
210 findAndUnmountComponentsRec(oldVNode);
211 }
212 // When using fragments, we can have cases where several "steps" in
213 // the hierarchy is skipped.
214 if (oldVNode.domRef === undefined && (oldVNode._ === 3 /* Fragment */ || oldVNode._ === 4 /* Component */)) {
215 replaceAndRemoveFragmentNodes(parentDomNode, oldVNode, domNode);
216 }
217 else {
218 parentDomNode.replaceChild(domNode, existingDomNode);
219 }
220 }
221 }
222 }
223 else if (newVNode._ === 3 /* Fragment */) {
224 // Skip creating a node for the fragment, instead render the children
225 // directly to the parent DOM node
226 for (var _i = 0, _a = newVNode.props.children; _i < _a.length; _i++) {
227 var c = _a[_i];
228 if (c !== undefined) {
229 render(parentDomNode, c, undefined, undefined, isSvg);
230 }
231 }
232 }
233 else if (newVNode._ !== 5 /* Memo */) {
234 var newNode = createNode(newVNode, isSvg);
235 newVNode.domRef = newNode;
236 if (action === 1 /* APPEND */) {
237 parentDomNode.appendChild(newNode);
238 }
239 else if (action === 2 /* INSERT */) {
240 parentDomNode.insertBefore(newNode, oldVNode.domRef);
241 }
242 else { // action === ACTION_REPLACE
243 // If it's a component node or an element node and it should be
244 // replaced, unmount any components in the tree.
245 if (oldVNode._ === 4 /* Component */ || oldVNode._ === 2 /* Element */ || oldVNode._ === 3 /* Fragment */) {
246 findAndUnmountComponentsRec(oldVNode);
247 }
248 // When using fragments, we can have cases where several "steps" in
249 // the hierarchy is skipped, thus we might need to remove multiple
250 // DOM nodes in addition to replacing one.
251 if (oldVNode.domRef === undefined && (oldVNode._ === 3 /* Fragment */ || oldVNode._ === 4 /* Component */)) {
252 var fragmentNode = oldVNode;
253 if (oldVNode._ === 4 /* Component */) {
254 fragmentNode = oldVNode.rendition;
255 }
256 replaceAndRemoveFragmentNodes(parentDomNode, fragmentNode, newNode);
257 }
258 else {
259 // If it's an element node remove old event listeners before
260 // replacing the node.
261 if (oldVNode._ === 2 /* Element */) {
262 for (var attr in oldVNode.props) {
263 if (attr.indexOf('on') === 0) {
264 removeAttr(attr, existingDomNode);
265 }
266 }
267 }
268 parentDomNode.replaceChild(newNode, existingDomNode);
269 }
270 }
271 // If it's an element node set attributes and event listeners
272 if (newVNode._ === 2 /* Element */) {
273 var props = newVNode.props;
274 for (var name_1 in props) {
275 if (name_1 === 'children' || name_1 === 'key') {
276 continue;
277 }
278 else if (name_1 === 'ref') {
279 props[name_1].current = newNode;
280 }
281 var attributeValue = props[name_1];
282 if (attributeValue !== undefined) {
283 setAttr(newNode, name_1, attributeValue);
284 }
285 }
286 for (var _b = 0, _c = props.children; _b < _c.length; _b++) {
287 var c = _c[_b];
288 if (c !== undefined) {
289 render(newNode, c, undefined, undefined, isSvg);
290 }
291 }
292 }
293 }
294}
295/**
296 * Finds fragment child nodes, replaces the first one with newNode and removes
297 * the rest.
298 */
299function replaceAndRemoveFragmentNodes(parentDomNode, fragmentVNode, newNode) {
300 var childNodes = getFragmentChildDomNodesRec(fragmentVNode);
301 for (var i = 0; i < childNodes.length; i++) {
302 var child = childNodes[i];
303 if (i === 0) {
304 parentDomNode.replaceChild(newNode, child);
305 }
306 else {
307 parentDomNode.removeChild(child);
308 }
309 }
310}
311/**
312 * Updates a DOM not from a new VNode.
313 */
314function renderUpdate(parentDomNode, newVNode, oldVNode, existingDomNode, isSvg) {
315 // if (!nodesEqual(oldVNode.node, existingDomNode)) {
316 // TODO: "debug mode" with warnings?
317 // console.error('The view is not matching the DOM. Are outside forces tampering with it?')
318 // }
319 if (isSvg === void 0) { isSvg = false; }
320 // update existing node
321 switch (newVNode._) {
322 case 2 /* Element */:
323 updateElementAttributes(newVNode, oldVNode, existingDomNode);
324 updateElementVNode(newVNode, oldVNode, existingDomNode, isSvg);
325 break;
326 case 1 /* Text */:
327 if (existingDomNode.textContent !== newVNode.value) {
328 existingDomNode.textContent = newVNode.value;
329 }
330 break;
331 case 4 /* Component */:
332 newVNode.rendition = oldVNode.rendition;
333 newVNode.data = oldVNode.data;
334 newVNode.effects = oldVNode.effects;
335 newVNode.errorHandler = oldVNode.errorHandler;
336 newVNode.link = oldVNode.link;
337 newVNode.link.vNode = newVNode;
338 renderComponent(parentDomNode, newVNode, oldVNode, isSvg);
339 break;
340 case 3 /* Fragment */:
341 updateElementVNode(newVNode, oldVNode, parentDomNode, // Fragments doesn´t reference any DOM node, instead pass the parent
342 isSvg);
343 break;
344 }
345 if (newVNode.domRef === undefined) {
346 // add a reference to the node
347 newVNode.domRef = existingDomNode;
348 }
349 if (newVNode !== oldVNode) {
350 // clean up
351 oldVNode.domRef = undefined;
352 }
353}
354/**
355 * Creates a DOM Node from a (non component or fragment) VNode.
356 */
357function createNode(vNode, isSvg) {
358 switch (vNode._) {
359 case 2 /* Element */:
360 if (isSvg) {
361 return document.createElementNS('http://www.w3.org/2000/svg', vNode.tagName);
362 }
363 return document.createElement(vNode.tagName);
364 case 1 /* Text */:
365 return document.createTextNode(vNode.value);
366 case 0 /* Nothing */:
367 return document.createComment('Nothing');
368 }
369}
370/**
371 * Updates an existing HTMLElement DOM node from a new VNode.
372 */
373function updateElementVNode(newVNode, oldVNode, existingDomNode, isSvg) {
374 var _a;
375 if (isSvg === void 0) { isSvg = false; }
376 var newChildVNodes = newVNode.props.children;
377 var oldChildVNodes = oldVNode.props.children;
378 var diffNoOfChildNodes = oldChildVNodes.length - newChildVNodes.length;
379 if (newChildVNodes.length > 0) {
380 // Create a map holding references to all the old child
381 // VNodes indexed by key
382 var keyMap = {};
383 // Node "pool" for reuse
384 var unkeyedNodes = [];
385 // Prepare the map with the keys from the new nodes
386 for (var i = 0; i < newChildVNodes.length; i++) {
387 var newChildVNode = newChildVNodes[i];
388 var props = newChildVNode.props;
389 if (props !== undefined && props !== null && props.key !== undefined) {
390 keyMap[props.key] = undefined;
391 }
392 }
393 // Go through the old child VNodes to see if there are any old ones matching the new VNodes
394 var matchingKeyedNodes = false;
395 for (var i = 0; i < oldChildVNodes.length; i++) {
396 var oldChildVNode = oldChildVNodes[i];
397 var props = oldChildVNode.props;
398 if (props !== undefined && props !== null && props.key !== undefined) {
399 // If the key has been added (from a new VNode), update it's value
400 if (props.key in keyMap) {
401 keyMap[props.key] = [oldChildVNode, oldChildVNode.domRef];
402 matchingKeyedNodes = true;
403 }
404 // else save the DOM node for reuse or removal
405 else if (existingDomNode.contains(oldChildVNode.domRef)) {
406 unkeyedNodes.push(oldChildVNode.domRef);
407 }
408 }
409 }
410 // If there was no matching keyed nodes, remove all old
411 // DOM nodes
412 if (!matchingKeyedNodes && Object.keys(keyMap).length > 0) {
413 existingDomNode.innerHTML = '';
414 unkeyedNodes.length = 0;
415 for (var i = newChildVNodes.length + diffNoOfChildNodes - 1; i > -1; i--) {
416 var oldChildVNode = oldChildVNodes[i];
417 oldChildVNode.domRef = undefined;
418 // Make sure any sub-components are "unmounted"
419 findAndUnmountComponentsRec(oldChildVNode);
420 }
421 }
422 var domNodeAtIndex = existingDomNode.firstChild;
423 var nextDomNode = null;
424 // Start iterating over the new nodes and render them
425 for (var i = 0; i < newChildVNodes.length; i++) {
426 var newChildVNode = newChildVNodes[i];
427 var oldChildVNode = oldChildVNodes[i];
428 var matchingChildDomNode = void 0;
429 var childAction = void 0;
430 if (domNodeAtIndex !== null) {
431 nextDomNode = domNodeAtIndex.nextSibling;
432 }
433 // If there is an old VNode, it's DOM ref should be
434 // treated as the current/matching DOM node
435 if (oldChildVNode !== undefined) {
436 matchingChildDomNode = oldChildVNode.domRef;
437 }
438 var newProps = newChildVNode.props;
439 // Check if the new VNode is "keyed"
440 if (newProps !== undefined
441 && oldChildVNodes.length > 0) {
442 var newChildVNodeKey = newProps.key;
443 if (newChildVNodeKey !== undefined) {
444 // Fetch the old keyed item from the key map
445 var keyMapEntry = keyMap[newChildVNodeKey];
446 // If there was no old matching key, reuse an old unkeyed node
447 if (keyMapEntry === undefined) {
448 matchingChildDomNode = unkeyedNodes.shift();
449 if (matchingChildDomNode !== undefined) {
450 // Make sure that the DOM node will be
451 // recreated when rendered
452 childAction = 3 /* REPLACE */;
453 }
454 }
455 // If there was a matching key, use the old vNodes dom ref
456 else {
457 oldChildVNode = keyMapEntry[0], matchingChildDomNode = keyMapEntry[1];
458 }
459 // Move the matching dom node to it's new position
460 if (matchingChildDomNode !== undefined && matchingChildDomNode !== domNodeAtIndex) {
461 // If there is no DOM node at the current index,
462 // the matching DOM node should be appended.
463 if (domNodeAtIndex === null) {
464 existingDomNode.appendChild(matchingChildDomNode);
465 }
466 // Move the node by replacing the node at the current index
467 else if (existingDomNode.contains(matchingChildDomNode)) {
468 existingDomNode.replaceChild(matchingChildDomNode, domNodeAtIndex);
469 nextDomNode = matchingChildDomNode.nextSibling;
470 }
471 else {
472 existingDomNode.insertBefore(matchingChildDomNode, domNodeAtIndex);
473 }
474 }
475 }
476 }
477 render(existingDomNode, newChildVNode, oldChildVNode, matchingChildDomNode, isSvg, childAction);
478 domNodeAtIndex = nextDomNode;
479 }
480 }
481 if (diffNoOfChildNodes > 0) {
482 // Remove old unused DOM nodes backwards from the end
483 for (var i = newChildVNodes.length + diffNoOfChildNodes - 1; i > newChildVNodes.length - 1; i--) {
484 var oldChildVNode = oldChildVNodes[i];
485 // Make sure any sub-components are "unmounted"
486 findAndUnmountComponentsRec(oldChildVNode);
487 if (oldChildVNode._ === 3 /* Fragment */) {
488 removeFragmentChildNodes(oldChildVNode, existingDomNode);
489 }
490 else if (oldChildVNode._ === 4 /* Component */ && ((_a = oldChildVNode.rendition) === null || _a === void 0 ? void 0 : _a._) === 3 /* Fragment */) {
491 removeFragmentChildNodes(oldChildVNode.rendition, existingDomNode);
492 }
493 else {
494 var oldChildDomNode = oldChildVNode.domRef;
495 if (oldChildDomNode !== undefined && existingDomNode.contains(oldChildDomNode)) {
496 existingDomNode.removeChild(oldChildDomNode);
497 }
498 }
499 }
500 }
501}
502/**
503 * Removes any Fragment child DOM nodes from parentDomElement
504 */
505function removeFragmentChildNodes(fragmentNode, parentDomElement) {
506 var childNodes = getFragmentChildDomNodesRec(fragmentNode);
507 for (var _i = 0, childNodes_1 = childNodes; _i < childNodes_1.length; _i++) {
508 var c = childNodes_1[_i];
509 parentDomElement.removeChild(c);
510 }
511}
512/**
513 * Recursively traverses the vNode tree and removes any Fragment child DOM nodes
514 * from domElement
515 */
516function getFragmentChildDomNodesRec(fragmentNode, parentDomElement) {
517 var _a;
518 var nodes = [];
519 for (var _i = 0, _b = fragmentNode.props.children; _i < _b.length; _i++) {
520 var fragmentChild = _b[_i];
521 if (fragmentChild._ === 3 /* Fragment */) {
522 nodes.push.apply(nodes, getFragmentChildDomNodesRec(fragmentChild));
523 }
524 else if (fragmentChild._ === 4 /* Component */ && ((_a = fragmentChild.rendition) === null || _a === void 0 ? void 0 : _a._) === 3 /* Fragment */) {
525 nodes.push.apply(nodes, getFragmentChildDomNodesRec(fragmentChild.rendition));
526 }
527 var childNode = fragmentChild.domRef;
528 if (childNode !== undefined) {
529 nodes.push(childNode);
530 }
531 }
532 return nodes;
533}
534/**
535 * Sets an attribute or event listener on an HTMLElement.
536 */
537function setAttr(element, attributeName, attributeValue) {
538 // The the "value" attribute shoud be set explicitly (and only if it has
539 // changed) to prevent jumping cursors in some browsers (Safari)
540 if (attributeName === 'value' && (element.tagName === 'INPUT' || element.tagName === 'TEXTAREA' || element.tagName === 'SELECT')) {
541 if (element.value !== attributeValue) {
542 element.value = attributeValue;
543 }
544 }
545 else if (attributeName in element) {
546 try {
547 element[attributeName] = attributeValue;
548 return;
549 }
550 catch (_) {
551 /** Ignore and use setAttribute instead */
552 }
553 }
554 var attrValueType = typeof attributeValue;
555 if (attrValueType !== 'function' && attrValueType !== 'object') {
556 element.setAttribute(attributeName, attributeValue);
557 }
558}
559/**
560 * Removes an attribute or event listener from an HTMLElement.
561 */
562function removeAttr(a, node) {
563 if (a.indexOf('on') === 0) {
564 node[a] = null;
565 }
566 else if (node.hasAttribute(a)) {
567 node.removeAttribute(a);
568 }
569}
570/**
571 * Sets/removes attributes on an DOM element node
572 */
573function updateElementAttributes(newVNode, oldVNode, existingDomNode) {
574 var newProps = newVNode.props;
575 var oldProps = oldVNode.props;
576 // remove any attributes that was added with the old virtual node but does
577 // not exist in the new virtual node or should be removed anyways (event listeners).
578 for (var attributeName in oldProps) {
579 if (attributeName === 'children' || attributeName === 'key' || attributeName === 'ref') {
580 continue;
581 }
582 if (newProps[attributeName] === undefined || attributeName.indexOf('on') === 0) {
583 removeAttr(attributeName, existingDomNode);
584 }
585 }
586 var attributeValue;
587 var oldAttributeValue;
588 var hasAttr;
589 // update any attribute where the attribute value has changed
590 for (var name_2 in newProps) {
591 if (name_2 === 'children' || name_2 === 'key') {
592 continue;
593 }
594 else if (name_2 === 'ref') {
595 newProps[name_2].current = existingDomNode;
596 continue;
597 }
598 attributeValue = newProps[name_2];
599 hasAttr = existingDomNode.hasAttribute(name_2);
600 // We need to check the actual DOM value of the "value" property
601 // otherwise it may not be updated if the new prop value equals the old
602 // prop value
603 if (name_2 === 'value' && name_2 in existingDomNode) {
604 oldAttributeValue = existingDomNode.value;
605 }
606 else {
607 oldAttributeValue = oldProps[name_2];
608 }
609 if ((name_2.indexOf('on') === 0 || attributeValue !== oldAttributeValue ||
610 !hasAttr) && attributeValue !== undefined) {
611 setAttr(existingDomNode, name_2, attributeValue);
612 }
613 else if (attributeValue === undefined && hasAttr) {
614 existingDomNode.removeAttribute(name_2);
615 }
616 }
617}
618
619function Fragment(props) {
620 var _a;
621 return {
622 _: 3 /* Fragment */,
623 props: {
624 children: (_a = props.children) !== null && _a !== void 0 ? _a : [],
625 key: props.key
626 }
627 };
628}
629
630function flattenChildren(children) {
631 var flattenedChildren = [];
632 for (var _i = 0, children_1 = children; _i < children_1.length; _i++) {
633 var child = children_1[_i];
634 if (child === null || child === undefined || typeof child === 'boolean') {
635 flattenedChildren.push({ _: 0 /* Nothing */ });
636 }
637 else if (Array.isArray(child)) {
638 for (var _a = 0, _b = flattenChildren(child); _a < _b.length; _a++) {
639 var c = _b[_a];
640 flattenedChildren.push(c);
641 }
642 }
643 else if (child._ === undefined) {
644 // Any node which is not a vNode will be converted to a TextVNode
645 flattenedChildren.push({
646 _: 1 /* Text */,
647 value: child
648 });
649 }
650 else {
651 flattenedChildren.push(child);
652 }
653 }
654 return flattenedChildren;
655}
656/**
657 * Creates a JSX.Element/VNode from a JSX tag.
658 */
659function h(tagNameOrComponent, props) {
660 var children = [];
661 for (var _i = 2; _i < arguments.length; _i++) {
662 children[_i - 2] = arguments[_i];
663 }
664 if (tagNameOrComponent === 'nothing' ||
665 tagNameOrComponent === undefined ||
666 tagNameOrComponent === null ||
667 typeof tagNameOrComponent === 'boolean') {
668 return { _: 0 /* Nothing */ };
669 }
670 if (props === null) {
671 props = {};
672 }
673 props.children = flattenChildren(children);
674 if (typeof tagNameOrComponent === 'string') {
675 return {
676 _: 2 /* Element */,
677 tagName: tagNameOrComponent,
678 props: props
679 };
680 }
681 else if (tagNameOrComponent === Fragment) {
682 return tagNameOrComponent(props);
683 }
684 var vNode = {
685 _: 4 /* Component */,
686 debounceRender: false,
687 props: props,
688 view: tagNameOrComponent
689 };
690 vNode.link = {
691 vNode: vNode
692 };
693 return vNode;
694}
695
696/**
697 * Better "typeof" which identifies arrays.
698 */
699function typeOf(obj) {
700 var objType = typeof obj;
701 if (objType === 'string' || objType === 'number' || objType === 'boolean' || objType === 'function') {
702 return objType;
703 }
704 if (obj === undefined) {
705 return 'undefined';
706 }
707 if (obj === null) {
708 return 'null';
709 }
710 return ({}).toString.call(obj).slice(8, -1).toLowerCase();
711}
712var basicEqualityTypes = ['string', 'number', 'boolean', 'undefined', 'null', 'function'];
713/**
714 * Does a deep equality check.
715 */
716function equal(a, b) {
717 var typeOfA = typeOf(a);
718 var typeOfB = typeOf(b);
719 if (basicEqualityTypes.indexOf(typeOfA) >= 0) {
720 return a === b;
721 }
722 else if (typeOfA === 'object' && typeOfB === 'object') {
723 if (a === b) {
724 return true;
725 }
726 if (Object.keys(a).length !== Object.keys(b).length) {
727 return false;
728 }
729 for (var k in a) {
730 if (a.hasOwnProperty(k)) {
731 if (!equal(a[k], b[k])) {
732 return false;
733 }
734 }
735 }
736 return true;
737 }
738 else if (typeOfA === 'array' && typeOfB === 'array') {
739 if (a === b) {
740 return true;
741 }
742 if (a.length !== b.length) {
743 return false;
744 }
745 for (var i in a) {
746 if (!equal(a[i], b[i])) {
747 return false;
748 }
749 }
750 return true;
751 }
752 else if (typeOfA === 'date' && typeOfB === 'date') {
753 return a.getTime() === b.getTime();
754 }
755 else if (typeOfA === 'regexp' && typeOfB === 'regexp') {
756 return a.toString() === b.toString();
757 }
758 else if (typeOfA === typeOfB) {
759 return a === b;
760 }
761 return false;
762}
763
764/**
765 *
766 * @param initialState the initial state
767 */
768function useState(initialState) {
769 var renderingContext = getRenderingContext();
770 var _a = renderingContext, hookIndex = _a.hookIndex, isSvg = _a.isSvg, parentElement = _a.parentElement, vNode = _a.vNode;
771 if (vNode.data === undefined) {
772 vNode.data = [];
773 }
774 if (vNode.data[hookIndex] === undefined) {
775 var link_1 = vNode.link;
776 var evolve_1 = function (update) {
777 var currentVNode = link_1.vNode;
778 try {
779 if (typeof update === 'function') {
780 update = update(currentVNode.data[hookIndex][0]);
781 }
782 currentVNode.data[hookIndex] = [update, evolve_1];
783 if (!currentVNode.debounceRender) {
784 requestAnimationFrame(function () {
785 link_1.vNode.debounceRender = false;
786 renderComponent(parentElement, link_1.vNode, undefined, isSvg);
787 });
788 }
789 currentVNode.debounceRender = true;
790 }
791 catch (err) {
792 requestAnimationFrame(function () {
793 tryHandleComponentError(parentElement, currentVNode, isSvg, err);
794 });
795 }
796 return currentVNode.data[hookIndex][0];
797 };
798 if (typeof initialState === 'function') {
799 initialState = initialState();
800 }
801 vNode.data[hookIndex] = [initialState, evolve_1];
802 }
803 var state = vNode.data[hookIndex];
804 renderingContext.hookIndex++;
805 return state;
806}
807/**
808 *
809 * @param current an optional value
810 */
811function useRef(current) {
812 var renderingContext = getRenderingContext();
813 var _a = renderingContext, hookIndex = _a.hookIndex, vNode = _a.vNode;
814 if (vNode.data === undefined) {
815 vNode.data = [];
816 }
817 if (vNode.data[hookIndex] === undefined) {
818 var link_2 = vNode.link;
819 vNode.data[hookIndex] = {
820 current: current,
821 get node() {
822 return link_2.vNode.domRef;
823 }
824 };
825 }
826 renderingContext.hookIndex++;
827 return vNode.data[hookIndex];
828}
829/**
830 *
831 * @param handler
832 */
833function useErrorHandler(handler) {
834 var renderingContext = getRenderingContext();
835 var vNode = renderingContext.vNode;
836 vNode.errorHandler = handler;
837}
838/**
839 *
840 * @param effect
841 * @param arg
842 */
843function useLayoutEffect(effect, arg) {
844 useEffectInternal(true, effect, arg);
845}
846/**
847 *
848 * @param effect
849 * @param arg
850 */
851function useEffect(effect, arg) {
852 useEffectInternal(false, effect, arg);
853}
854function useEffectInternal(sync, effect, arg) {
855 var renderingContext = getRenderingContext();
856 var _a = renderingContext, hookIndex = _a.hookIndex, vNode = _a.vNode;
857 if (vNode.effects === undefined) {
858 vNode.effects = [];
859 }
860 var t = vNode.effects[hookIndex];
861 if (t === undefined) {
862 vNode.effects[hookIndex] = {
863 arg: arg,
864 sync: sync,
865 invoke: true,
866 effect: effect,
867 };
868 }
869 else if (arg === undefined || !equal(t.arg, arg)) {
870 t.arg = arg;
871 t.effect = effect;
872 t.invoke = true;
873 }
874 renderingContext.hookIndex++;
875}
876/**
877 *
878 * @param fn
879 * @param inputs
880 */
881function useMemo(fn, inputs) {
882 var renderingContext = getRenderingContext();
883 var _a = renderingContext, hookIndex = _a.hookIndex, vNode = _a.vNode;
884 if (vNode.data === undefined) {
885 vNode.data = [];
886 }
887 var res;
888 if (vNode.data[hookIndex] === undefined) {
889 res = fn(inputs);
890 vNode.data[hookIndex] = [res, inputs];
891 }
892 else {
893 var _b = vNode.data[hookIndex], prevRes = _b[0], prevInputs = _b[1];
894 if (equal(prevInputs, inputs)) {
895 res = prevRes;
896 }
897 else {
898 res = fn(inputs);
899 vNode.data[hookIndex] = [res, inputs];
900 }
901 }
902 renderingContext.hookIndex++;
903 return res;
904}
905
906function shallowCompareProps(newProps, oldProps) {
907 var newPropsKeys = Object.keys(newProps);
908 if (newPropsKeys.length !== Object.keys(oldProps).length) {
909 return false;
910 }
911 for (var _i = 0, newPropsKeys_1 = newPropsKeys; _i < newPropsKeys_1.length; _i++) {
912 var k = newPropsKeys_1[_i];
913 if (k === 'children') {
914 continue;
915 }
916 if (newProps[k] !== oldProps[k]) {
917 return false;
918 }
919 }
920 return true;
921}
922/**
923 * Memoizes a component view, preventing unnecessary renders.
924 *
925 * If no custom compare function is supplied, a shallow comparison of the props'
926 * properties will decide whether the component will be rerendered or not.
927 *
928 * @param factory A component factory function
929 * @param compare An optional props equality comparer function. If true is
930 * returned the memoized view will be kept, otherwise the view
931 * will be rerendered.
932 */
933function memo(factory, compare) {
934 return function (props) {
935 return {
936 _: 5 /* Memo */,
937 compare: compare || shallowCompareProps,
938 view: factory,
939 props: props
940 };
941 };
942}
943
944/**
945 * Convenience function for type hinting
946 *
947 * @param fn
948 */
949function define(fn) {
950 return fn;
951}
952/**
953 * Mounts a virtual DOM node onto the supplied element.
954 */
955function mount(vNode, element) {
956 requestAnimationFrame(function () {
957 render(element, vNode, undefined, undefined);
958 });
959}
960
961export { Fragment, define, equal, h, memo, mount, typeOf, useEffect, useErrorHandler, useLayoutEffect, useMemo, useRef, useState };